This is a follow-up to an earlier post covering the iNaturalist integration in Geotour’s CesiumJS 3D map. The architecture described below is a separate implementation built into the platform’s 2D map — a Leaflet-based listings interface that runs alongside the 3D terrain viewer as part of the same WordPress theme.
Context
The Geotour 2D map serves as the primary spatial index for the portal’s content database. It displays the documented locations across Crete (archaeological sites, gorges, Byzantine monuments, natural landmarks) and provides filtering, sidebar browsing, and route planning. The iNaturalist Biodiversity Explorer is one of several toggleable thematic overlays built on top of this map, alongside Natura 2000 boundaries, CORINE land cover, live seismic data (EMSC), and NASA FIRMS active fire hotspots.
The core map is built on Leaflet.js, with a MapLibre GL layer underneath for vector tile rendering. The biodiversity layer sits entirely within Leaflet and makes no use of the MapLibre surface.
Three-Tier API Architecture
The previous 3D implementation managed point rendering through a single CesiumJS imagery layer and handled data through a BiodiversityManager.js module. The 2D approach presented a different set of constraints — Leaflet renders at screen resolution rather than 3D tile resolution, and the panel UI needed to coexist with an active sidebar and other summary panels.
The result is a three-tier approach, where each tier serves a specific purpose:
Tier 1 — Density tile layer (/v1/points/{z}/{x}/{y}.png)
The base observation layer uses iNaturalist’s tile endpoint directly as a L.tileLayer:
const BIODIVERSITY_TILE_BASE = ‘https://api.inaturalist.org/v1/points’;
const BIODIVERSITY_PLACE_ID = 7095; // iNaturalist’s place_id for Crete, GR
_biodiversityTileUrl() {
const params = new URLSearchParams({
place_id: String(BIODIVERSITY_PLACE_ID),
verifiable: 'true',
});
if (this.biodiversityGroupFilter) {
params.set('iconic_taxa', this.biodiversityGroupFilter);
params.set('color', this.\_biodiversityColorFor(this.biodiversityGroupFilter).replace('#', ''));
}
return \`${BIODIVERSITY_TILE_BASE}/{z}/{x}/{y}.png?${params.toString()}\`;
}
This layer renders every verifiable observation server-side at iNaturalist’s infrastructure, with no per-page cap. When the user applies a group filter, tileLayer.setUrl() swaps in the new iconic_taxa and color params without rebuilding the layer — the map simply re-requests the updated tiles for the current viewport. Opacity is set at 0.85 so the base map remains readable underneath.
Tier 2 — Species counts panel (/v1/observations/species_counts)
The panel data comes from the species_counts endpoint, fetched for the current viewport on every pan or zoom (debounced at 450ms). Parameters used:
swlat, swlng, nelat, nelng (bounding box from Leaflet getBounds())
place_id=7095
verifiable=true
geo=true
per_page=200 (iNaturalist’s hard maximum for this endpoint)
locale=en
The 200-result cap means that in an area with very high observation density (e.g., the north coast around Heraklion), the species list will be truncated. The tile layer still shows the full picture regardless; the truncation only affects the panel’s count and species list, which the UI notes explicitly.
Tier 3 — Individual observations (/v1/observations)
When a user selects a species row in the panel, a targeted fetch retrieves up to 50 observations of that taxon within the current viewport:
taxon_id={id}
place_id=7095
(bounding box)
verifiable=true
geo=true
per_page=50
locale=en
Each observation is rendered as a L.circleMarker (9px radius, group color) with a L.circle halo at 40m radius (fillOpacity: 0.35). The popup contains the observation photo (if any), common name, scientific name, observer login, date, and a direct link to the observation on iNaturalist. Requests use AbortController, so clicking a different species row while a fetch is in-flight cancels the previous request rather than allowing a stale result to overwrite the markers.
Tier 4 — Taxon detail (/v1/taxa/{id})
An optional fourth call is made when the user opens the species detail modal via an info button on a species row. This fetches the taxon record for Wikipedia extract, conservation status, and a higher-resolution photo URL. iNaturalist photo URLs follow a consistent size-suffix convention (square, small, medium, large), so the implementation swaps the suffix to retrieve the large variant for the modal without storing a separate URL.
Taxonomic Groups and Color Mapping
The implementation uses iNaturalist’s iconic taxon system directly. The full map used in both the tile color parameter and the panel legend.
These colors serve two roles simultaneously: the color parameter passed to the tile URL when a group filter is active, and the fill color for the Tier 3 highlight markers and halos. By defining them once and passing the same map to both the tile URL builder and the Leaflet marker renderer, there is no risk of the points on the tile layer and the highlight markers appearing in different colors for the same group.
Panel Structure and Behavior
The Biodiversity Explorer panel opens as a centered overlay (the same panel system used by CORINE and Earthquake summaries). It has two tabs:
Groups tab — aggregates the current species_counts response by iconic_taxon_name, showing the number of species and total observations per group, sorted by observation count. Clicking a group row applies a filter.
Top Species tab — lists the top 30 species from the current species_counts response (filtered by the active group if one is set). Each row shows a thumbnail photo, common name, scientific name where different, and observation count. Clicking a species row triggers the Tier 3 fetch and collapses the panel to a small pill at the top of the screen, so the halo markers are visible underneath.
The filter is “sticky toggle” — clicking the same group a second time clears it and restores all groups in the species list, while also swapping the tile layer back to the unfiltered URL. Applying a group filter automatically switches to the Top Species tab, since that is the list the filter actually narrows.
Viewport-Based Refresh
When the layer is active and the user pans or zooms, _scheduleBiodiversityRefresh() is called from the map’s moveend handler with a 450ms debounce. This re-runs _loadBiodiversityData(), which fetches a new species_counts response for the updated bounding box and re-renders the panel. An _biodiversityInflight flag prevents concurrent requests — if a pan triggers a debounced fetch while the previous one is still in flight, the second call returns immediately. The tile layer updates automatically because Leaflet requests tiles for the new viewport on every move, with no custom logic required.
The panel only re-renders when it is open. If the user has closed it, data is still fetched and stored in biodiversitySpeciesData, so reopening the panel reflects the current view immediately.
Comparison with the 3D Implementation
The 3D CesiumJS implementation used a single camera.moveEnd listener, Cesium billboards clamped to the terrain, and a Discovery Slider for ranking species by observation rarity. The 2D implementation drops the rarity slider in favour of direct group filtering (a more direct interaction for a map that also carries several other data layers simultaneously). The halo approach is carried over from the 3D version but uses L.circle instead of Cesium’s EllipseGraphics. The tile endpoint usage is identical in concept; the URL template format simply adapts to Leaflet’s {z}/{x}/{y} syntax.
One addition in the 2D version: the species detail modal (Tier 4 call) did not exist in the 3D implementation, which linked directly to the iNaturalist taxon page. The 2D modal keeps users within the map context while still linking out to the full taxon page for complete information.
Direct link to the 2D map
https://www.geotour.gr/listing/
Activate the Biodiversity layer from the toolbar at the top of the map, then open the Biodiversity Explorer panel from the legend that appears at the right side once the layer is on. The 3D terrain viewer with the original implementation is at https://www.geotour.gr/vt/3dmap/ .
Feedback on the API usage and any observations about the data visible for Crete are welcome.
