Changelog
Source:NEWS.md
obisindicators 0.12.2
-
The monthly upload validates
collection.jsonon the host, not in the container.om-catalog validate h3is a Node tool and the obis-h3 image carries no Node, so the first run of step g2 in the container (2026-11-01, obisindicators 0.11.0’s step) died onnpx: command not foundafter a complete, validated export and published nothing.data-raw/publish_obis_h3.shnow runs the validation first (OM_CATALOG; a failure, or a missing tool, uploads nothing), andrefresh_obis_h3.shonly validates where the tool exists, says so whenOBIS_UPLOAD=hostdefers it, and still fails inawsmode without it. Pinned intest-publish-obis-h3.R.
obisindicators 0.12.1
-
The monthly obis-h3 upload writes both stores (Ocean Metrics build week 6). New
data-raw/publish_obis_h3.shuploads a staged release on the host through oceanmetrics/platformscripts/om-publish.sh: the release folder, a count check on S3 and R2, thenlatest.json, then the stablecollection.json, to R2oceanmetrics-io-public-enam(served bystorage.oceanmetrics.io) and S3oceanmetrics.io-public, never deleting, the replaced R2 copies of the two pointers kept underobis-h3/_history/<date>/.refresh_obis_h3.shgainsOBIS_UPLOAD=host(stage only;aws, the S3-only upload, stays the default when it runs alone) andrun_refresh_docker.shsets it and runs the host upload after the container, so the R2 keys never enter the image. -
refresh_obis_h3.sh’sPUBLIC_BASE(the root of the hrefs inlatest.jsonandcollection.json, and where the previous release is read) defaults tohttps://storage.oceanmetrics.io/obis-h3, so the next release keeps the storage-host hrefs the published files carry since the cutover of 2026-10-23.
obisindicators 0.12.0
-
species_flatper resolution.obis_h3_export_parquet()writesspecies_flat/res=1.parquetandres=2.parquetbesideres=3.parquet(res_species_flatnow defaults to1:3): the finest resolution comes from the store as before (withh3), each coarser one is it rolled up, one row per cell and species withnsummed,year_minthe least andyear_maxthe greatest (NULL only when every year is NULL), so a res-1 hexagon reads its own rows instead of its res-3 children’s. The roll-up files have the columnscell_id, name_key, aphiaid, n, year_min, year_max(noh3). New exportedobis_h3_species_rollup_sql()(the roll-up, equal toobis_h3_species_sql()at that resolution straight from the store) andobis_h3_write_species_flat(). -
The res-3 species table split by res-1 parent:
species_flat/res=3/p=<res-1 parent>.parquet, one file per parent with records, without theh3column, sorted bycell_id, aphiaidin row groups ofsplit_row_group_rows(default 2048; at most 2 x, as DuckDB writes them: 4,081 rows in v20261012) withcell_idstatistics, so a cell’s file is small enough for its footer to come with the first range read. The files union tores=3.parquet, which is kept. New argumentsspecies_flat_split(defaultc("3" = 1L), resolution = parent resolution; NULL for none) andsplit_row_group_rows.release.jsonlayers.species_flatgainstemplate_parent,parent_template,parent_res,by_resandsplit;files.parquetgives each split file’sparent. -
The species lookup carries each species’ EOV and its lineage has a file of its own.
species/species.parquetgainseov: the EOV whose layer counts the aphiaid, the store’seovtable joined ontaxonID = aphiaidas theeov/layers joinocc_h3(obis_h3_species_eov_sql()); NULL when no EOV counts it, and the column is left out when the store has noeovtable. The seeds are disjoint subtrees of one tree, so an aphiaid is in one EOV at most; an aphiaid in two is an error, not a choice.species/lineage.parquetholdsaphiaid, name_key, lineageonly, sorted by aphiaid, so a WoRMS subtree reads the lineage without the names. Both are written in row groups ofrow_group_rows(obis_h3_write_species_lookup()). Noredlist_codecolumn: the store’socc_h3andtaxontables carry no IUCN Red List field (the OBIS snapshot’sinterpreted.redlist_categoryis not brought into the store). -
obis_h3_amend_species()adds these files to a release built by 0.11 in place: the lookup rewritten witheov(same rows, same order), the lineage file, the roll-ups and the split from the existingres=3.parquet(not rewritten),release.jsonandfiles.parquet(the new files added, every other row kept). Used to amend v20261012. -
The collection describes them.
obis_h3_stac_collection()listsobis:species.flatat every flat resolution with the split’sparent_template(species_flat/res={res}/p={parent}.parquet),parent_resand the columns of each file,obis:species.lineage,obis:species.eov, and the assetsspecies_flat_res<r>andspecies_lineage;obis_h3_stac_stable()makes those hrefs absolute. The h3 profile 1.1 closesh3:species_table(no keys beyond its seven), so the flat files stay described underobis:species.flat, where the atlas reads them;h3:species_tableis unchanged.
obisindicators 0.11.0
-
Every indicator layer file is sorted by
cell_idin small row groups.obis_h3_export_parquet()gainsrow_group_rows(default 10000; DuckDB row groups are at most 2 xrow_group_rows; DuckDB rounds to 2,048-row vectors: 12,045 rows at most in v20261012) and writes each file of theall,eov,taxon, decade and year layers with its own sortedCOPY(obis_h3_write_partitions(),order_by = "cell_id"), so thecell_idstatistics of consecutive row groups are disjoint and a reader that knows the cells under a tile range-reads only those groups. DuckDB’sPARTITION_BYwrote the groups out of order (the v20261012all/res=5file had 6 groups of up to 122,880 rows, not incell_idorder). The paths are unchanged (the explicit writer encodes partition values as DuckDB did: all 7,370 v20261012 taxon paths compare equal).release.jsonrecordssorted_by,row_group_rowsandrow_group_rows_max(the largest group written).max_openis no longer used.stats.parquetis written in row groups ofrow_group_rowstoo (sorted bylayer, key, so a legend reads only its layer’s groups; the v20261012 file’s first 122,880-row group heldall,eovand half the taxa). -
The heads are built, as taxon layers.
obis_h3_export_parquet()writes the taxa ofspecies/heads.json(Animalia and every taxon spanning two or more named classes) under the existingtaxon/rank=<rank>/taxon=<name>/res=<r>/layout atres_heads(default 1-7, res 6 and 7 split byparent_res_taxon), so a reader draws a head as any taxon layer and never reads the class files; they are instats.parquet,taxon_groups.parquetandfiles.parquetlike the rank layers.rankis the WoRMS rank lowercased without a botanical alias (kingdom,subphylum,superclass; “Phylum (Division)” isphylum). New exportedobis_h3_heads_sql(): the WoRMS subtree over the lookup’slineage, species counted byname_key, so a head equalsobis_h3_species_subtree_sql()cell for cell (tested at res 3-5, and against h3t’s recursive-CTE subtree at res 1-7). A head is not written when a layer already serves it: a subtree holding every aphiaid is thealllayer (Biota: equal toallcell for cell at res 4 on v20261012), and a head whose rank and name are a rank layer keeps that layer, which is built from the OBIS rank column (the 40 phyla among the v20261012 heads; Chordata’s phylum layer differs from its WoRMS subtree in 1,967 of 173,824 res-4 cells).heads.jsongives each head’sserved_by(head,all,rank_layer,homonym) andlayer;release.jsongetslayers.taxon.headsandlayers.species.heads.n_built. New argumentsheads(default TRUE) andres_heads. -
The species table carries the first and last year.
obis_h3_species_sql()and every species file gainyear_minandyear_max(SMALLINT; the first and last year the aphiaid was recorded in the cell, NULL when no record has a year; within the decade in the decade variant), for the Cell pane. -
The decade species table is on by default, at res 3 and 4:
species_decadesnow defaults to TRUE and the newres_species_decade(defaultc(3, 4)) sets its resolutions (it followedres_speciesbefore), for the decade filter on a WoRMS subtree.layers.species_decadeinrelease.jsonlists itsresolutions,decadesandcolumns(withperiod = "decade"). -
species_flat/res=<r>.parquet: the species table in one file per resolution for every class, sorted bycell_id, aphiaidin row groups ofrow_group_rows, so the species of a cell are a range read of one file (spike 2: 0.53 MB read for a res-1 cell against 5.3 MB from the class files). New argumentres_species_flat(default 3);release.jsongetslayers.species_flat. -
Exact species per place.
obis_place_indicators_exact()returnsspecies(new argumentspecies = TRUE): one row per place xaphiaidxname_keywithnrecords andyear_min,year_max, from the same point-in-polygon pass as the indicators, so the Place page’s species list is the record and not the H3 cells’ (the species’nsum to the place’sn, and its distinctname_keycount is itssp). New argumentlookup(the release’sspecies/species.parquet, a path or a data frame) gives the release’sname_keyby name, so the rows join the lookup; a name it lacks gets a key above its largest.obis_place_exact_write()writesplace_exact/coll=<coll>/species.parquet(place_id, aphiaid, name_key, n, year_min, year_max, most records first) and counts it inmeta.json;obis_h3_add_place_exact()lists it infiles.parquetand adds its template and columns to arelease.jsonentry written before 0.11.0. The export passes the store’s lookup;data-raw/run_place_exact.Rreads<out dir>/species/species.parquet(orOBIS_SPECIES_LOOKUP) and fails when the species do not sum to the indicators. -
collection.jsonfor theh3profile 1.1, with the atlas hooks.obis_h3_stac_collection()now writes the collection with idobis_h3(wasobis-h3; the atlas names it?c=obis_h3) for profilehttps://oceanmetrics.io/schemas/h3/v1.1.0/schema.json:h3:periodsas two period sets (decades atres_decade, years atres_year, each with its template),h3:layout.row_group_rows,h3:species_table.columnswithname_key,year_min,year_maxand the decade variant’speriod,h3:heads({layer: "taxon"}),cube:dimensions(layer: all and the EOVs;indicator;period: all, decades as<yyy0>s, years;res),atlas:metric(es),item_assets, and one STAC Item per map layer (items/<all|eov>.json, withdata,decadeandyearassets: a folder and the path template under it).stats.parquetis the asset with rolelegend,taxon_groups.parquetthe one with rolesearch. New argumentitems. -
New
obis_h3_stac_stable(release_dir, base_url)writes the copy for a stable url,obis-h3/collection.json, with every href and template absolute under the release folder;data-raw/refresh_obis_h3.shwrites it afterlatest.json(step g2), validated withom-catalog validate h3(exit 5 when it fails). Both validate against the 1.1 schema (jsonvalidate and the node CLI), with counter-fixtures (a period set withoutplaceholder,year_minwithoutyear_max) failing. - Fix:
taxon_groups.parquetno longer fails for a taxon layer without res 1.
obisindicators 0.10.1
-
New
obis_h3_add_place_exact(release_dir, coll, source, manifest): registers aplace_exact/coll=<coll>/collection written into a release that was built without it (or without that collection), for example the gazetteerplaces(the 18 national marine sanctuaries) computed withdata-raw/run_place_exact.Ron a staging copy of the published release.release.jsongets the collection underlayers$place_exact$collections(from itsmeta.json) and the layer’srows,filesandbytesover every collection on disk;files.parquethas itsplace_exactrows replaced by one row per file on disk andfiles$n_filesis updated (manifest = FALSEleaves it alone). Other layers,stats.parquetand a hand-edited layernoteare untouched, and a second call gives the same files. - The exact place layer’s
template,columnsandnoteinrelease.jsoncome from one internal helper shared by the export and the new function.
obisindicators 0.10.0
-
Year layers.
obis_h3_export_parquet()writesyear/all/year=<yyyy>/res=<r>/data_0.parquetandyear/eov/eov=<eov>/year=<yyyy>/res=<r>/data_0.parquet: the decade layers per year, one partition per year present frommin(date_year)tomax(date_year). New argumentsres_year(default 1-5;integer(0)skips them) andyears(default NULL, every year). The SQL is the new exportedobis_h3_year_sql(), built on the same indicator SQL asidx_h3and the decade layers (which now share its builder).release.jsongainslayers.year_all,layers.year_eov,res_year,years(the years written),year_rangeandyear_note;files.parquetandstats.parquetcover them. -
stats.parquethas ayearcolumn (NA outside the year layers), so its rows are one per layer x key x decade x year x res x variable. -
The species x hex x count table (layer
species, sized and checked against h3t in spike 2 of the Ocean Metrics restructure), so a browser answers a WoRMS subtree or a cell’s species list from static files:species/res=<r>/class=<class>/data_0.parquetwith columnsh3, cell_id, name_key, aphiaid, n(n= records) at the resolutionsres_species(default 3, 4, 5; res 4 rolled up from the res-5 tier), one file per (res, class), sorted bycell_id, aphiaidin row groups of 100k (ZSTD); a NULL class is writtenclass=_none, since DuckDB-WASM reads__HIVE_DEFAULT_PARTITION__as a string.species/species.parquetis the lookup, one row per aphiaid withname_key(a dense integer per species name: richness counts names, as h3t does), the OBIS ranks,lineage(the aphiaid then its ancestors up the store’staxontable, walked from the occurrence’s own aphiaid, so a synonym keeps its own parent) andrecords.species/heads.jsonlists the taxa that still need a pre-aggregated head (Animalia and every taxon spanning two or more named classes: 125 in the v20261012 global store); the heads themselves are not built in this version.species_decades = TRUEalso writesspecies_decade/res=<r>/decade=<d>/class=<class>/data_0.parquet(default off). A store without aphiaids (the demo store built fromocc_SAtlantic) gets no species layer. - New exported functions for the table, each tested on a synthetic store:
obis_h3_species_sql(),obis_h3_species_lookup_sql(),obis_h3_species_heads(),obis_h3_write_partitions()(the explicit per-partition writer: DuckDB’sPARTITION_BYwrote thousands of files per resolution and lost the sort) andobis_h3_species_subtree_sql()(a subtree’s indicators from the exported files, which reproduce the h3t subtree SQL cell for cell:nandspexactly, Shannon, Simpson and ES to 1e-9). -
collection.json: the export writes a STAC Collection of the release for the Ocean Metricsh3profile,obis_h3_stac_collection(), withh3:species_tablefor the species layer and the other layers underobis:layers. It validates againsth3.schema.json(tested with jsonvalidate and withom-catalog validate h3when the platform checkout is present). -
release.json’slayers.speciescarries theh3:species_tablefields (href_template,resolutions,partition_by,sorted_by,columns,lookup) plusname_key,null_class,heads, the column types and the rows, files and bytes per resolution (by_res). - The
placeandplace_exactlayers of 0.8.0 and 0.9.0 are unchanged: they stay out ofstats.parquet, andcollection.jsonlists them underobis:layers.
obisindicators 0.9.0
-
New
obis_place_indicators_exact(): indicators per place from the OBIS occurrences inside the true polygon, with no H3 approximation. It takes the occurrences (a data frame, or OBIS open-data parquet files, nested or flat, filtered asbuild_obis_h3_duckdb()does) and the places (ansfobject or a GeoParquet withplace_idand WKBgeometry, such as a gazetteerplaces.parquet), splits each polygon into its parts and cuts them into quadrants until every piece has at mostmax_vertices(default 256) vertices (a place split at +/-180 gets local parts, so memory stays a few times the geometry size), joins every distinct location to the pieces with DuckDB spatial, and computes the indicators with the same SQL as the store’s layers. Polygons split at +/-180 work as they are. Returnsindicators(place xeovxdecade;eovis"all"or an EOV from theeovmembership given as a data frame or a store connection;decadeNA = all years),years(place xeovxyear) anddiagnostics. Columns:n,sp,shannon,simpson,es,hill_1(exp(shannon)),hill_2(1 / simpson),bp(Berger-Parker,max(ni) / n) anddensity(n / area_km2, records per km2, fromarea_km2or the geodesic polygon area).diagnosticsholdsrecords_in,records_outandrecords_total(inside plus outside equals the total unless places overlap),records_in_uniqueand the overlapping locations with theirplace_ids, which also raise a warning. Resource guards (memory_limit,threads,temp_dir,max_temp_dir_size) as inbuild_obis_h3_duckdb(). The results equalcalc_indicators()on the points inside the polygon (tested for all taxa, per decade, per year and for each of the seven EOVs). - New
obis_place_exact_write()writes that result asplace_exact/coll=<coll>/indicators.parquetandyears.parquet(ZSTD, values only, no geometry) plus ameta.json(places, rows, record totals, overlaps, package version). -
obis_h3_export_parquet()gainsplace_exact(a named list of slug = places GeoParquet; defaultNULL, no change),place_exact_occ(the occurrence parquet files),temp_dirandmax_temp_dir_size: the exact place tables are written toplace_exact/coll=<slug>/in the release and listed infiles.parquet(layerplace_exact) andrelease.json(layers$place_exact);stats.parquetdoes not cover them. The monthly refresh turns it on withOBIS_PLACE_EXACT_COLLECTIONS="<slug> ...", reading$PLACES_INTERNAL/<slug>/places.parquet(default/share/data/places-internal), passed through, and mounted read-only, byrun_refresh_docker.sh.data-raw/run_place_exact.Rruns the same computation standalone. -
obis_place_indicators_exact()takesmax_verticesinstead ofgrid_deg(deprecated: ignored, with a message). The 1 degree grid clip repeated each place’s whole geometry in every cell of its bounding box, which ran out of memory on EEZs and high seas that span +/-180; the quadrant subdivision writes one table per round and copies a geometry at most four times.diagnosticsreportsmax_vertices,max_piece_verticesandsubdivision_roundsinstead ofgrid_deg;data-raw/run_place_exact.RreadsOBIS_MAX_VERTICES. - No change to the indicator math or to the output of any existing function.
obisindicators 0.8.0
-
New
obis_place_indicators(): indicators per place, pooled from area-weighted H3 cells. It takes a place-cell membership (place_id,h3,share, as the Ocean Metrics gazetteer publishes it per collection) and, for each place, sums every species’ records over the member cells weighted by the cell’s share of area inside the place (ni = sum(records * share); the gazetteer keeps shares of at least 1%), then computesn,sp, Shannon, Simpson and ES(50) with the store’s indicator SQL. Layersallandeov(per EOV), each for all years and per decade (1960-2020, as the export’s decade layers); alsocells(member cells with records) andarea(their summed share). These are true per-place indices of the pooled, area-weighted counts, not means of hexagon values:nis fractional because of the weighting, richness counts every species with any weighted record inside the place, and ES(50) is NA where the weightednis below 50. - New
obis_place_membership()resolves a gazetteer collection (slug, directory, URL ormembership.json) to its membership parquet files at one resolution. -
obis_h3_export_parquet()gainsplace_collections(defaultNULL, no change): a named list of collections, each written as one fileplace/coll=<slug>/data_0.parquetwith every place, layer and decade, listed infiles.parquet(layerplace) andrelease.json(layers$place, with each collection’s membership version).stats.parquetdoes not cover it. The monthly refresh turns it on withOBIS_PLACE_COLLECTIONS="<slug> ..."(optionallyGAZETTEER_BASE), passed through byrun_refresh_docker.sh. - The shared indicator SQL (
.h3t_indicators_sql()) is documented for fractional counts; it needed no change, and integer results are unchanged.
obisindicators 0.7.3
-
release.json’sobis_snapshotis the OBIS data date, the date of the newest object ins3://obis-open-data/occurrence/the store was built from, not the date the refresh ran. OBIS updates that folder one dataset at a time and publishes no export date, sorefresh_obis_h3.shreads it off the synced files (whichaws s3 syncstamps with their S3LastModified) and passes it asOBIS_DATA_DATE; without it, the date in the release tag is used as before. For v20261012 that is 2026-10-11 (synced 2026-10-12). The release tag stays the sync date. -
latest.jsonalso carriesworms_version, the ChecklistBank WoRMS version the taxonomy came from.
obisindicators 0.7.2
-
Family is a precomputed taxon rank.
H3T_IDX_RANKSgains"family", so the store’sidx_h3_taxonand the export’staxon/rank=family/layer carry every family (about 5,800 in the v20260728 store), and a single-family h3t query takes the precomputed path.migrate_add_idx_h3_taxon.Radds it to an existing store. -
The monthly
obis-h3refresh runs in Docker on the Mac mini. Newdata-raw/docker/Dockerfile(R 4.6.1, CRAN from a dated Posit Package Manager snapshot, duckdb 1.5.5, the h3/spatial/httpfs extensions, AWS CLI v2) anddata-raw/run_refresh_docker.sh(Colima VM started and stopped per run, data on the external drive mounted at/share/data, the new store rsynced to msens for h3t).refresh_obis_h3.shruns R directly whenBUILD_CONTAINERis empty (inside the image); msens’sdocker exec plumbermode is unchanged. -
refresh_obis_h3.shsources every file inR/for the export: the subset it sourced before lackedeov.R, so the export stopped attaxon_groups(could not find function "obis_eov_label"). -
obis_h3_export_parquet()computesstats.parquetone layer and variable at a time: the single UNPIVOT over every layer held all values inquantile_cont()states at once and, with the family rank, was killed for lack of memory in a 24 GiB VM. Same rows, values and column types (regression test). -
The WoRMS taxonomy is refreshed by the monthly run. New step (a2) in
refresh_obis_h3.sh: when theversionof ChecklistBank dataset 2011 (WoRMS’s monthly DwC-A, public) differs fromtaxon.parquet.json, it downloads the archive, runsbuild_taxon_parquet.Rand records the version, whichrelease.jsoncarries asworms_version. New flags--skip-taxonomyand--taxonomy-only. The build’s gap-fill of AphiaIDs missing from the download (WoRMS REST) is unchanged.run_refresh_docker.shskips a separate mount for a checkout that sits inside the data folder (Colima refuses overlapping mounts). -
release.json’sobisindicators_versioncomes from the checkout’sDESCRIPTIONwhenOBIS_PKG_ROOTis set (.obisindicators_version()), so the image needs no installed package.
obisindicators 0.7.1
-
obis_h3_export_parquet()splits the fine resolutions by parent cell, so a browser loads only the cells covering its viewport instead of a whole 33 MBall/res=7file. New argumentsparent_res(defaultc("6" = 2L, "7" = 3L): res 6 by its res-2 parent, res 7 by its res-3 parent, for theallandeovlayers),parent_res_taxon(default the res-0 base cell, which keeps the taxon layer to thousands rather than millions of files),parent_min_rows(default 50000: smaller (layer key, res) partitions stay whole) andmax_open(partitions per COPY). Split partitions are written to.../res=<r>/p=<parent h3>/data_0.parquet; res 1-5 and the decade layers stay whole.files.parquetgains aparentcolumn (NULL for whole files) andrelease.jsondocuments each layer’sparent_resand the rule (parents). -
New
data-raw/refresh_obis_h3.sh: the monthlyobis-h3release pipeline (sync OBIS open data, build the store,obis_h3_export_parquet(), validate against the previous release, upload to S3, then writeobis-h3/latest.json). Scheduled fromoceanmetrics/erddap-placeson the msens runner; documented indata-raw/README.md. Supports--skip-sync/-build/-export/-upload,--releaseand--dry-run.
obisindicators 0.7.0
-
New
obis_h3_export_parquet(): export a store to hive-partitioned Parquet for a serverless web app (static hosting + DuckDB-WASM + deck.glH3HexagonLayer). Writes<dir_out>/<release>/withall/res=N/(idx_h3),eov/eov=<eov>/res=N/(idx_h3_eov),taxon/rank=<rank>/taxon=<taxon>/res=N/(idx_h3_taxon), and two layers computed fromocc_h3per decade (decade/all/decade=<yyy0>/res=N/,decade/eov/eov=<eov>/decade=<yyy0>/res=N/; res 1-5 and decades 1960-2020 by default). Every file hash3(lowercase hex string),cell_id,n,sp,shannon,simpson,es. Also writesstats.parquet(min, 2nd, 50th, 98th percentile, max and cell count per layer x key x res x variable, for colour ramps),taxon_groups.parquet(the taxa and EOVs on offer, with totals and on-disk / URL paths),files.parquet(a manifest of every file with its row count and size, since HTTP cannot glob) andrelease.json(release, snapshot, package and DuckDB versions, row counts per layer, layout and columns). Each partition is exactly onedata_0.parquet; the taxon layer is written in batches of at most 100 partitions per COPY to stay under the OS open-file limit. Partition values are percent-encoded by DuckDB;url_pathgives the form to fetch over HTTP. - The indicator SQL behind
idx_h3,idx_h3_taxonandidx_h3_eovnow comes from one internal builder (.h3t_indicators_sql()), which the decade layers of the export reuse. The output of the store build is unchanged. -
jsonliteadded to Suggests (used by the export).
obisindicators 0.6.1
-
Precomputed store articles: the figures now show on the pkgdown site (and in the
paper/gallery on GitHub). The vignettes that query anobis_h3store (scaling,taxon_children) rendered code-only (eval = FALSE) because the store is not available on GitHub Actions. They move tovignettes/articles/(pkgdown-only, excluded from the package build) as*.Rmd.origsources thatdata-raw/precompute_articles.Rknits locally against a store into committed*.Rmd+figures/; CI renders those as-is. The tile maps stay live.- The
paper/Quarto notebooks are folded into the articles, which now double as the manuscript’s figure notebooks:scaling(Tables 2, 5; Fig. 3),taxon_children(Table 3; Fig. 4), and new articleseov(Table 4; Figs. 1–2),decadal(Fig. 5) andplaces(Fig. 6). Each article saves its manuscript figure + CSV topaper/figures/(now committed) viapaper/_common.R;paper/README.mdis the figure gallery and index. - Site navigation: articles are grouped into “Shipped data” and “The obis_h3 store”. No changes to exported functions.
- The
-
Analysis-side helpers for the paper’s figures and case studies — the tile-SQL builders now have data-frame counterparts, so every figure in
paper/is a call to a tested, exported function rather than notebook code. No new indicator math: everything runs the SQL already pinned tocalc_indicators()/calc_spue(), or calls those references directly.- Store (
R/store.R):obis_store_connect()(read-only +LOAD h3),obis_store_stats()(tables, totals, cells by resolution — Table 2),obis_h3t_query()(run any tile SQL at a fixed resolution, keyed on the hex string),obis_cell_indicators()(n/sp/shannon/es per cell for any filter, readingidx_h3/idx_h3_eovwhen it can),obis_bench()+obis_bench_queries()(cold/warm timings of the four serving paths),h3_res_table(). - Scale (
R/scale.R):calc_scale_curves()andcalc_spue_scale()— occupied cells, records-per-cell quantiles, ES-eligible fraction, medians and reliability floors across H3 resolutions (Fig. 3). - Comparisons (
R/compare.R):calc_rank_vs_subtree()+obis_rank_presets()(records by DwC rank column vs AphiaID subtree — Table 3),calc_eov_totals()+compare_eov_totals()(per-EOV totals and before/after a taxonomy change — Table 4),calc_spue_cells(),h3_raster_to_cells()(aggregate an SDM raster to H3 by pixel centres or hexagon centroids) andcompare_spue_sdm()(Spearman + calibration bins, effort-gated — Fig. 4). - Periods (
R/temporal.R):calc_period_indicators()andcalc_period_change()(decadal ES50/richness per cell, change where both decades are reliable — Fig. 5). - Places (
R/place.R):place_cells()andcalc_place_indicators()(roll H3 cells up to sanctuary/EEZ polygons and compute indicators withcalc_indicators()— Fig. 6). - Visualize (
R/visualize.R):hex_sf()(cells → dateline-wrapped polygons),gmap_cells()(map a per-cell table with an optional reliability mask),plot_scale_curves(),plot_spue_sdm();gmap_indicator()gains abboxargument and drops the deprecatedaes_string(). - Tests:
tests/testthat/test-paper-helpers.Ron a new synthetic fixture (helper-paper.R: turtles + birds with DwC ranks and years, bakedidx_h3andidx_h3_eov) asserts exact values for every helper, including precomputed-vs-live agreement ofobis_cell_indicators(). -
paper/: Quarto notebooks, one per figure/table (store stats + benchmarks, rank vs subtree, gap-fill EOV totals, EOV maps, scale curves, SPUE vs SDM, decadal change, place roll-ups), driven byOBIS_H3_DUCKDB; pluspaper/build_demo_store.Rto build a regional demo store (Gulf of Mexico + Caribbean) from a local OBIS export and a WoRMS table. - Suggests:
terra(raster aggregation),patchwork(figure panels).
- Store (
obisindicators 0.5.0
-
Essential Ocean Variables (EOVs) — new
R/eov.Rmaps the GOOS/IOOS biology & ecosystems EOVs onto the H3 store. The IOOS Marine Life Data Network defines each EOV as a handful of root WoRMS AphiaIDs (33 seeds across 7 EOVs: fish, hardCorals, mangroves, marineMammals, seabirds, seagrasses, seaTurtles), which is exactly a multi-seed version of the existing subtree walk.-
obis_eov_seeds()/obis_eov_aphiaid()— the EOV definitions. -
obis_eov_sql()— tile SQL for an EOV, routing to a precomputed layer when it can and to the live AphiaID-subtree path when it must (year filters, several EOVs at once, or a store without the layer baked). -
obis_eov_bake()— addseov(membership) andidx_h3_eov(precomputed indicators, res 1-7) to a store; now also a step inbuild_obis_h3_duckdb(), skipped with a note whentaxonis absent. - New driver
data-raw/migrate_add_eov.R.
-
-
WoRMS gap-fill — new
R/taxon_gapfill.Rcloses a silent coverage hole. The bulk WoRMStaxon.txtdownload is not a complete cover of the AphiaIDs OBIS carries: on the 2026-07 global store 12,021 of 167,190 distinctocc_h3.aphiaidvalues (~7%, covering 8.3M of 121.9M records) were absent fromtaxon, so they were invisible to everyaphiaid/ EOV / SPUE query.-
obis_taxon_orphans()— report the gap. -
wm_aphia_records()— batched, parallel WoRMS REST lookups (AphiaRecordsByAphiaIDs, 50 ids/request), the per-id supplement to the bulk download. -
obis_taxon_fill_gaps()— fills orphans and iterates to transitive closure, because inserting an orphan whose ancestors are still missing leaves it disconnected from any seed above it. Warns and reportsclosed = FALSErather than implying a whole tree ifmax_roundsis hit. - New driver
data-raw/migrate_fill_taxon_gaps.R. - Adds
httr2to Suggests.
-
Two taxon-group presets in the h3-db app returned zero records (fixed in
MarineSensitivity/apps/h3-db, noted here because it motivated the EOV design): filtering the DwCclasscolumn cannot match when WoRMS files the name at another rank —class='Actinopterygii'(a Gigaclass; OBIS’s class isTeleostei, 44.2M records) andclass='Anthozoa'(a Subphylum; OBIS usesHexacorallia/Octocorallia) both matched nothing. AphiaID subtree walking is rank-agnostic and immune to this.Parity:
.h3t_idx_eov_sql()is a fifth copy of thecalc_indicators()ES(n) term and is pinned cell-for-cell by the newtests/testthat/test-eov-parity.R.
obisindicators 0.4.2
-
Vignette H3 tile maps now actually render (the
mapgl/MapLibre chunks that 0.4.1 still left as code-only):-
vignette("h3t")renders the interactive ES50 hexagon map;vignette("taxon_children")renders the Cetacea-records map and the SPUE map. These are client-side MapLibre widgets — tiles are fetched from the deployedh3tservice in the reader’s browser, so they need no store/S3 at build time. -
New Remote dependency: the maps require the h3t-antimeridian fix in
mapgl(renders H3 hexagons that cross the dateline), pinned viaRemotes: mapgl=bbest/mapgl@fix/h3t-antimeridian(walkerke/mapgl#211). Switch to upstreammapglonce that PR merges. - Fixed a latent bug in the
h3tmap example —interpolate(values=)now has the same length asstops=(a 3-stop viridis ramp), which previously would have errored had the chunk been evaluated. -
vignette("scaling")stays code-only: every chunk runs live benchmark queries against the deployed DuckDB store (no map widgets), which CI cannot reach.
-
obisindicators 0.4.1
-
Vignettes now render their evaluable R chunks on the pkgdown site instead of showing code-only:
-
vignette("h3")runs the full h3-grid →calc_indicators()→gmap_indicator()pipeline on shippedocc_1Mand renders the ES(50) map (only thedeckglinstall/demo stays code-only). -
vignette("taxon_children")now renders the generatedWITH RECURSIVEtile SQL and tile URLs fromobis_h3t_sql(aphiaid=)/obis_spue_sql()(pure builders, no store needed); only the DB-connection andmapglwidget chunks stay code-only. -
vignette("h3t")suppresses package-load messages so the evaluatedobis_h3t_sql()output is clean. - Chunks that genuinely require the deployed S3 / DuckDB store /
h3ttile service (vignette("scaling"), the build/deploy/map chunks) remain illustrative code, as they cannot run in CI.
-
obisindicators 0.4.0
-
Automatic per-tile spatial pruning via
hex_prune(supersedes 0.3.0’s client-side{{bbox}}placeholder). The tile bbox is fully determined by thez/x/ytile address, so the client no longer states it — theh3tserver derives each tile’s covering coarse H3 cells fromz/x/yand injects the prune itself:-
build_obis_h3_duckdb()now materializes a coarse H3-parent columnhex_prune = h3_cell_to_parent(cell_id, LEAST(res, H3T_PRUNE_RES))(res 3) onocc_h3andidx_h3and clusters both by(res, hex_prune, cell_id). This replaces the interimlat/lngclustering — pruning on the H3 index is 2D (tighter than a latitude band), needs no extra float columns, and the covering ids are canonical H3 (they match across the R build and the server with no version drift). Store schema change — rebuild, or migrate withdata-raw/migrate_add_spatial_cluster.R(dropslat/lng, addshex_prune). -
obis_h3t_sql()/obis_spue_sql()now emit plain per-resolution SELECTs — thebbox_placeholderargument is removed. The server (MarineSensitivity/server/h3t,CalCOFI/api-h3t-py) rewrites the query to addhex_prune IN (<covering cells>)to any scan of a table carrying that column; stores without it are untouched. - The math is unchanged and still pinned to
calc_indicators()/calc_spue();test-h3t-prune.Rasserts the prune is result-preserving (ahex_prune-pruned scan + the server’s outer centroid filter returns exactly the same cells and values as the unpruned scan).
-
obisindicators 0.3.0
-
Spatial (bbox) tile pruning (interim — the
{{bbox}}placeholder andlat/lngclustering here were replaced by thehex_pruneapproach in 0.4.0; kept for history). Liveocc_h3/idx_h3tile maps were pruned per tile instead of aggregating the whole globe for every tile:-
build_obis_h3_duckdb()materialized cell-centroidlat/lngcolumns onocc_h3andidx_h3and clustered both by(res, lat, lng). -
obis_h3t_sql()/obis_spue_sql()gained abbox_placeholderargument (default"{{bbox}}") that theh3tservice substituted per tile with alat/lngpredicate. - The math stayed pinned to
calc_indicators()/calc_spue(); the prune was asserted result-preserving.
-
obisindicators 0.2.0
- Added WoRMS taxonomy children resolution and an effort proxy (SPUE) so OBIS can be filtered by any WoRMS AphiaID at any rank from the local snapshot (see
vignette("taxon_children"),vignette("scaling")):-
obis_taxon_children()/obis_taxon_subtree_sql()recursively walk ataxontable (baked into the store;data-raw/build_taxon_parquet.R+data-raw/migrate_add_taxon.R) to return every descendant taxon. -
obis_h3t_sql(aphiaid=)serves arbitrary-rank children maps by filteringocc_h3.aphiaidto the resolved subtree via aWITH RECURSIVECTE. -
calc_spue()(R reference) +obis_spue_sql()compute the presence-only effort proxyrecords(target subtree) / records(effort subtree)per cell, pinned bytest-spue-parity.R.
-
- Added an H3 tiling (h3t) workflow to serve indicators as on-demand H3 hexagon map tiles (see
vignette("h3t")):-
build_obis_h3_duckdb()builds an authoritative DuckDB store from OBIS open-data parquet — a precomputed all-taxa indicator layer (idx_h3, res 1–7) plus a species-level store (occ_h3, tiers 3/5/7) for on-the-fly taxon/year-filtered queries. The ES50/Shannon/Simpson/richness math is the SQL translation ofcalc_indicators(), pinned by a new parity test. -
obis_h3t_sql()/obis_h3t_url()compose the validated read-onlySELECTand the base64-encoded tile URL for theh3tservice (MarineSensitivity/server/h3t) andmapgl::add_h3t_source(). - canonical SQL in
inst/sql/; server build driver indata-raw/build_obis_h3_duckdb.R.
-
obisindicators 0.0.2
- Renamed functions for consistency:
-
calc_es50()->calc_indicators() -
gmap_metric()->gmap_indicator()
-
- Fixed
gmap_indicator()to use default Robinson projection. - Updated vignettes with name changes and use of “indicators” over “metrics”.
- Supplemented documentation for
calc_indicators()with expected input and output columns to data frame. - Added documentation for
occ_fkandocc_1960stoocc_2010s - Made generation of occ_* datasets more reproducible with
set.seed()and sampled versions of dataset to minimize file size on Github indata-raw/occ.R.