Skip to content

Commit 13c0631

Browse files
authored
feat(plugins): add an H3 hexagonal grid plugin (#1511)
* feat(plugins): add an H3 hexagonal grid plugin Renders the H3 grid over the current viewport at a chosen resolution and lets users identify a cell to inspect its ID, parent, children, neighbors and center, then export the grid or selection as GeoJSON or CSV. * Address CodeRabbit review feedback - Split paint/layout updates out of refresh() so style-only setting changes no longer rebuild the whole viewport grid; only a resolution change regenerates geometry. - Drop empty clipped rings when splitting a boundary at the antimeridian and fall back to a Polygon when one side clips to nothing, so the export never emits a MultiPolygon containing an empty linear ring. - Switch the color pickers from `input` to `change` so dragging does not destroy the picker via the panel re-render. - Coalesce moveend-driven refreshes through requestAnimationFrame so inertial pans do not queue back-to-back grid rebuilds. - Assert each split ring is non-empty and closed in the dateline test.
1 parent a3de391 commit 13c0631

8 files changed

Lines changed: 1006 additions & 0 deletions

File tree

apps/geolibre-desktop/src/components/layout/TopToolbar.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
setAnnotationLabels,
2323
setBasemapControlLabels,
2424
setGraticuleLabels,
25+
setH3Labels,
2526
setMapillaryLabels,
2627
setEarthdataGisLabels,
2728
setOpenAerialMapLabels,
@@ -522,6 +523,37 @@ export function TopToolbar({
522523
labelColor: t("graticule.labelColor"),
523524
labelSize: t("graticule.labelSize"),
524525
});
526+
setH3Labels({
527+
title: t("h3Plugin.title"),
528+
getTitle: () => i18n.t("h3Plugin.title"),
529+
controlTitle: t("h3Plugin.controlTitle"),
530+
resolution: t("h3Plugin.resolution"),
531+
cellCount: (count) => t("h3Plugin.cellCount", { count }),
532+
tooManyCells: (limit) => t("h3Plugin.tooManyCells", { limit }),
533+
fillColor: t("h3Plugin.fillColor"),
534+
fillOpacity: t("h3Plugin.fillOpacity"),
535+
lineColor: t("h3Plugin.lineColor"),
536+
lineWidth: t("h3Plugin.lineWidth"),
537+
showLabels: t("h3Plugin.showLabels"),
538+
identifyHint: t("h3Plugin.identifyHint"),
539+
selectedCell: t("h3Plugin.selectedCell"),
540+
noSelection: t("h3Plugin.noSelection"),
541+
copyId: t("h3Plugin.copyId"),
542+
copied: t("h3Plugin.copied"),
543+
parent: t("h3Plugin.parent"),
544+
children: t("h3Plugin.children"),
545+
neighbors: t("h3Plugin.neighbors"),
546+
baseCell: t("h3Plugin.baseCell"),
547+
center: t("h3Plugin.center"),
548+
pentagon: t("h3Plugin.pentagon"),
549+
yes: t("h3Plugin.yes"),
550+
no: t("h3Plugin.no"),
551+
zoomToCell: t("h3Plugin.zoomToCell"),
552+
addAsLayer: t("h3Plugin.addAsLayer"),
553+
exportGeoJson: t("h3Plugin.exportGeoJson"),
554+
exportCsv: t("h3Plugin.exportCsv"),
555+
includeNeighbors: t("h3Plugin.includeNeighbors"),
556+
});
525557
setTimelapseLabels({
526558
title: t("timelapse.title"),
527559
provider: t("timelapse.provider"),

apps/geolibre-desktop/src/hooks/usePlugins.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
maplibreOvertureMapsPlugin,
4242
queryOvertureFeatures,
4343
maplibreGraticulePlugin,
44+
maplibreH3Plugin,
4445
maplibreCloudsPlugin,
4546
maplibrePrecipitationPlugin,
4647
maplibreMapillaryPlugin,
@@ -180,6 +181,7 @@ manager.registerAll([
180181
maplibreElevationProfilePlugin,
181182
maplibreSwipePlugin,
182183
maplibreGraticulePlugin,
184+
maplibreH3Plugin,
183185
maplibreCloudsPlugin,
184186
maplibrePrecipitationPlugin,
185187
maplibreEffectsPlugin,

apps/geolibre-desktop/src/i18n/locales/en.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,6 +2651,36 @@
26512651
"labelColor": "Label color",
26522652
"labelSize": "Label size"
26532653
},
2654+
"h3Plugin": {
2655+
"title": "H3 Grid",
2656+
"controlTitle": "H3 grid settings",
2657+
"resolution": "Resolution",
2658+
"cellCount": "{{count}} cells in view",
2659+
"tooManyCells": "This view exceeds the {{limit}} cell limit. Zoom in or lower the resolution.",
2660+
"fillColor": "Fill color",
2661+
"fillOpacity": "Fill opacity",
2662+
"lineColor": "Outline color",
2663+
"lineWidth": "Outline width",
2664+
"showLabels": "Show cell IDs",
2665+
"identifyHint": "Click the map to identify an H3 cell.",
2666+
"selectedCell": "Selected cell",
2667+
"noSelection": "No cell selected",
2668+
"copyId": "Copy ID",
2669+
"copied": "Copied",
2670+
"parent": "Parent",
2671+
"children": "Children",
2672+
"neighbors": "Neighbors",
2673+
"baseCell": "Base cell",
2674+
"center": "Center",
2675+
"pentagon": "Pentagon",
2676+
"yes": "Yes",
2677+
"no": "No",
2678+
"zoomToCell": "Zoom to cell",
2679+
"addAsLayer": "Add grid as layer",
2680+
"exportGeoJson": "Export GeoJSON",
2681+
"exportCsv": "Export CSV",
2682+
"includeNeighbors": "Include selected cell neighbors"
2683+
},
26542684
"timelapse": {
26552685
"title": "Timelapse",
26562686
"provider": "Imagery source",

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/plugins/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"cog-tiler-wasm": "^0.3.1",
3434
"geotiff": "^3.0.5",
3535
"geotiff-geokeys-to-proj4": "^2024.4.13",
36+
"h3-js": "^4.5.0",
3637
"h5wasm": "^0.10.3",
3738
"jspdf": "^4.2.1",
3839
"mapillary-js": "^4.1.2",

packages/plugins/src/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,24 @@ export {
473473
type GraticuleLabelFormat,
474474
type GraticuleLabelEdges,
475475
} from "./plugins/maplibre-graticule";
476+
export {
477+
maplibreH3Plugin,
478+
H3_PLUGIN_ID,
479+
H3_VIEWPORT_CELL_LIMIT,
480+
DEFAULT_H3_GRID_SETTINGS,
481+
DEFAULT_H3_LABELS,
482+
getH3GridSettings,
483+
setH3GridSettings,
484+
setH3Labels,
485+
normalizeH3GridSettings,
486+
h3LabelMinZoom,
487+
h3CellFeature,
488+
h3GridForBounds,
489+
h3BoundaryGeometry,
490+
unwrapH3Boundary,
491+
type H3GridSettings,
492+
type H3Labels,
493+
} from "./plugins/maplibre-h3";
476494
export type { WeatherAnimationState, WeatherLayerController } from "./plugins/weather-layer";
477495
export {
478496
maplibreCloudsPlugin,

0 commit comments

Comments
 (0)