Skip to content

Commit 3bb7574

Browse files
authored
Fix heatmap and cluster layer ordering (#1943)
* Fix heatmap and cluster layer ordering * Sort GeoJSON layer ID imports * Order rebuilt heatmap and cluster layers from the Vector control Bumps maplibre-gl-vector to 0.10.12, which carries opengeos/maplibre-gl-vector#67. A point-renderer switch on a layer owned by the Add Vector Layer control made the control rebuild its map layers, but it emitted `layerupdated` before the (async) rebuild replaced `layerIds`. GeoLibre mirrors those ids into `metadata.nativeLayerIds` and restacks from them, so it kept ids for layers the map no longer had and never saw the replacements: getBeforeStyleLayerId could not anchor and syncExternalNativeLayer moved nothing. A layer set to the heatmap renderer therefore kept whatever stacking the rebuild appended it with, drawing above layers the panel puts on top of it. 0.10.12 emits once the rebuild lands, so the ids stay live. The preceding commits fix the same symptom on the core GeoJSON path, where the heatmap/cluster/text/label style layers were missing from the ordered candidate list outright. Both paths are now verified in the browser: with the upper layer clustered and the lower one switched to heatmap last, the draw order comes out heatmap -> cluster -> cluster-count -> circle, matching the layer panel. Re-checked MAX_VECTOR_BYTES against `src/lib/utils/remote.ts` in the bumped package as CLAUDE.md requires: still `2 ** 31 - 1`, mirror unchanged.
1 parent e0e12de commit 3bb7574

5 files changed

Lines changed: 37 additions & 7 deletions

File tree

apps/geolibre-desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"maplibre-gl-swipe": "^0.11.2",
9595
"maplibre-gl-time-slider": "^1.8.4",
9696
"maplibre-gl-usgs-lidar": "^0.11.1",
97-
"maplibre-gl-vector": "^0.10.11",
97+
"maplibre-gl-vector": "^0.10.12",
9898
"openai": "^7.3.0",
9999
"qrcode.react": "^4.2.0",
100100
"react": "^19.2.8",

package-lock.json

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

packages/map/src/map-controller.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,21 @@ import { LayerControl, type CustomLayerAdapter, type LayerState } from "maplibre
2727
import { CollapsedAttributionControl } from "./collapsed-attribution-control";
2828
import {
2929
circleLayerId,
30+
clusterCountLayerId,
31+
clusterLayerId,
3032
fillExtrusionLayerId,
3133
fillLayerId,
3234
getLayerBounds,
35+
heatmapLayerId,
3336
highlightCircleLayerId,
3437
highlightFillLayerId,
3538
highlightLineLayerId,
3639
highlightSourceId,
40+
labelLayerId,
3741
lineLayerId,
3842
markerLayerId,
3943
sourceId,
44+
textLayerId,
4045
} from "./geojson-loader";
4146
import {
4247
mbtilesStyleLayerIds,
@@ -2349,8 +2354,13 @@ export class MapController {
23492354
{ id: fillExtrusionLayerId(layer.id), suffix: "Extrusions" },
23502355
{ id: fillLayerId(layer.id), suffix: "Polygons" },
23512356
{ id: lineLayerId(layer.id), suffix: "Lines" },
2357+
{ id: heatmapLayerId(layer.id), suffix: "Heatmap" },
2358+
{ id: clusterLayerId(layer.id), suffix: "Clusters" },
2359+
{ id: clusterCountLayerId(layer.id), suffix: "Cluster counts" },
23522360
{ id: circleLayerId(layer.id), suffix: "Points" },
23532361
{ id: markerLayerId(layer.id), suffix: "Markers" },
2362+
{ id: textLayerId(layer.id), suffix: "Text" },
2363+
{ id: labelLayerId(layer.id), suffix: "Labels" },
23542364
];
23552365
}
23562366

packages/plugins/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"maplibre-gl-swipe": "^0.11.2",
6969
"maplibre-gl-time-slider": "^1.8.4",
7070
"maplibre-gl-usgs-lidar": "^0.11.1",
71-
"maplibre-gl-vector": "^0.10.11",
71+
"maplibre-gl-vector": "^0.10.12",
7272
"netcdfjs": "^4.0.0",
7373
"ngeohash": "^0.6.4",
7474
"open-location-code-typescript": "^1.5.0",

tests/map-controller.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ function controlVectorLayer(id: string, patch: Partial<GeoLibreLayer> = {}): Geo
285285
}
286286

287287
const circleId = (id: string) => `layer-${id}-circle`;
288+
const clusterId = (id: string) => `layer-${id}-cluster`;
289+
const clusterCountId = (id: string) => `layer-${id}-cluster-count`;
290+
const heatmapId = (id: string) => `layer-${id}-heatmap`;
288291
const markerId = (id: string) => `layer-${id}-marker`;
289292
const rasterId = (id: string) => `layer-${id}-raster`;
290293
const srcId = (id: string) => `source-${id}`;
@@ -419,6 +422,23 @@ describe("MapController.syncLayers reconciliation", () => {
419422
);
420423
});
421424

425+
it("keeps a lower heatmap beneath every companion of an upper clustered layer", () => {
426+
const { map, fake } = makeFakeMap();
427+
const controller = controllerWith(map);
428+
429+
controller.syncLayers([
430+
pointLayer("heat", {}, { pointRenderer: "heatmap" }),
431+
pointLayer("clusters", {}, { pointRenderer: "cluster" }),
432+
]);
433+
434+
const userOrder = fake.order.filter((id) => id !== "basemap-bg");
435+
const heatIndex = userOrder.indexOf(heatmapId("heat"));
436+
assert.ok(heatIndex !== -1, "heatmap layer exists");
437+
assert.ok(heatIndex < userOrder.indexOf(clusterId("clusters")), "beneath cluster bubbles");
438+
assert.ok(heatIndex < userOrder.indexOf(clusterCountId("clusters")), "beneath cluster counts");
439+
assert.ok(heatIndex < userOrder.indexOf(circleId("clusters")), "beneath unclustered points");
440+
});
441+
422442
it("restacks every layer in one pass when a control adds its style layers late", () => {
423443
const { map, fake } = makeFakeMap();
424444
const controller = controllerWith(map);

0 commit comments

Comments
 (0)