refactor: path's label viewed also line

This commit is contained in:
taDuc
2026-05-12 20:48:13 +07:00
parent 51f432f0fe
commit 72d7073c40
9 changed files with 161 additions and 153 deletions
+18
View File
@@ -28,6 +28,7 @@
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"maplibre-gl": "^5.20.2", "maplibre-gl": "^5.20.2",
"next": "^16.1.6", "next": "^16.1.6",
"polylabel": "^2.0.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
@@ -47,6 +48,7 @@
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@types/node": "^20.19.25", "@types/node": "^20.19.25",
"@types/polylabel": "^1.1.3",
"@types/react": "^19.2.1", "@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.1",
"@types/react-transition-group": "^4.4.12", "@types/react-transition-group": "^4.4.12",
@@ -4139,6 +4141,13 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/polylabel": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.1.3.tgz",
"integrity": "sha512-9Zw2KoDpi+T4PZz2G6pO2xArE0m/GSMTW1MIxF2s8ZY8x9XDO6fv9um0ydRGvcbkFLlaq8yNK6eZxnmMZtDgWQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.14", "version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@@ -8724,6 +8733,15 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/polylabel": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/polylabel/-/polylabel-2.0.1.tgz",
"integrity": "sha512-B6Yu+Bdl/8SGtjVhyUfZzD3DwciCS9SPVtHiNdt8idHHatvTHp5Ss8XGDRmQFtfF1ZQnfK+Cj5dXdpkUXBbXgA==",
"license": "ISC",
"dependencies": {
"tinyqueue": "^3.0.0"
}
},
"node_modules/possible-typed-array-names": { "node_modules/possible-typed-array-names": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+3 -1
View File
@@ -29,6 +29,7 @@
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"maplibre-gl": "^5.20.2", "maplibre-gl": "^5.20.2",
"next": "^16.1.6", "next": "^16.1.6",
"polylabel": "^2.0.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
@@ -37,17 +38,18 @@
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
"react-quill-new": "^3.8.3", "react-quill-new": "^3.8.3",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"uuid": "^13.0.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"sweetalert2": "^11.26.24", "sweetalert2": "^11.26.24",
"swiper": "^11.2.10", "swiper": "^11.2.10",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"uuid": "^13.0.0",
"yet-another-react-lightbox": "^3.30.1" "yet-another-react-lightbox": "^3.30.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@types/node": "^20.19.25", "@types/node": "^20.19.25",
"@types/polylabel": "^1.1.3",
"@types/react": "^19.2.1", "@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1", "@types/react-dom": "^19.2.1",
"@types/react-transition-group": "^4.4.12", "@types/react-transition-group": "^4.4.12",
+1
View File
@@ -1236,6 +1236,7 @@ export default function Page() {
<Map <Map
mode={mode} mode={mode}
draft={timelineVisibleDraft} draft={timelineVisibleDraft}
labelContextDraft={editor.draft}
selectedFeatureIds={selectedFeatureIds} selectedFeatureIds={selectedFeatureIds}
onSelectFeatureIds={setSelectedFeatureIds} onSelectFeatureIds={setSelectedFeatureIds}
onCreateFeature={handleCreateFeature} onCreateFeature={handleCreateFeature}
+3
View File
@@ -26,6 +26,7 @@ type MapProps = {
geometryVisibility?: Record<string, boolean>; geometryVisibility?: Record<string, boolean>;
selectedFeatureIds: (string | number)[]; selectedFeatureIds: (string | number)[];
onSelectFeatureIds: (ids: (string | number)[]) => void; onSelectFeatureIds: (ids: (string | number)[]) => void;
labelContextDraft?: FeatureCollection;
onCreateFeature?: (feature: FeatureCollection["features"][number]) => void; onCreateFeature?: (feature: FeatureCollection["features"][number]) => void;
onDeleteFeature?: (id: string | number) => void; onDeleteFeature?: (id: string | number) => void;
onUpdateFeature?: (id: string | number, geometry: Geometry) => void; onUpdateFeature?: (id: string | number, geometry: Geometry) => void;
@@ -48,6 +49,7 @@ export default function Map({
geometryVisibility, geometryVisibility,
selectedFeatureIds, selectedFeatureIds,
onSelectFeatureIds, onSelectFeatureIds,
labelContextDraft,
onCreateFeature, onCreateFeature,
onDeleteFeature, onDeleteFeature,
onUpdateFeature, onUpdateFeature,
@@ -117,6 +119,7 @@ export default function Map({
} = useMapSync({ } = useMapSync({
mapRef, mapRef,
draft, draft,
labelContextDraft,
backgroundVisibility, backgroundVisibility,
geometryVisibility, geometryVisibility,
selectedFeatureIds, selectedFeatureIds,
+120 -146
View File
@@ -1,4 +1,5 @@
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
import polylabel from "polylabel";
import { BACKGROUND_LAYER_OPTIONS, BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers"; import { BACKGROUND_LAYER_OPTIONS, BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
import { Feature, FeatureCollection, Geometry } from "@/uhm/lib/editor/state/useEditorState"; import { Feature, FeatureCollection, Geometry } from "@/uhm/lib/editor/state/useEditorState";
import { import {
@@ -15,12 +16,9 @@ import { newId } from "@/uhm/lib/utils/id";
type Coordinate = [number, number]; type Coordinate = [number, number];
type PolygonCoordinates = Coordinate[][]; type PolygonCoordinates = Coordinate[][];
type LabelCell = { type FeatureLabelInfo = {
x: number; entityId: string;
y: number; label: string;
h: number;
d: number;
max: number;
}; };
export function applyBackgroundLayerVisibility( export function applyBackgroundLayerVisibility(
@@ -196,24 +194,40 @@ export function splitDraftFeatures(fc: FeatureCollection) {
return { polygons, points }; return { polygons, points };
} }
export function decoratePointFeaturesWithLabels(fc: FeatureCollection): FeatureCollection { export function decoratePointFeaturesWithLabels(fc: FeatureCollection, labelContext: FeatureCollection = fc): FeatureCollection {
const getLabel = createFeatureLabelResolver(labelContext);
return { return {
...fc, ...fc,
features: fc.features.map((feature) => ({ features: fc.features.map((feature) => ({
...feature, ...feature,
properties: { properties: {
...feature.properties, ...feature.properties,
point_label: getSingleEntityFeatureLabel(feature), point_label: getLabel(feature),
}, },
})), })),
}; };
} }
export function buildPolygonLabelFeatureCollection(fc: FeatureCollection): FeatureCollection { export function decorateLineFeaturesWithLabels(fc: FeatureCollection, labelContext: FeatureCollection = fc): FeatureCollection {
const getLabel = createFeatureLabelResolver(labelContext);
return {
...fc,
features: fc.features.map((feature) => ({
...feature,
properties: {
...feature.properties,
line_label: isLineGeometry(feature.geometry) ? getLabel(feature) : null,
},
})),
};
}
export function buildPolygonLabelFeatureCollection(fc: FeatureCollection, labelContext: FeatureCollection = fc): FeatureCollection {
const getLabel = createFeatureLabelResolver(labelContext);
const features: Feature[] = []; const features: Feature[] = [];
for (const feature of fc.features) { for (const feature of fc.features) {
const label = getSingleEntityFeatureLabel(feature); const label = getLabel(feature);
if (!label) continue; if (!label) continue;
const labelPoint = getPolygonLabelPoint(feature.geometry); const labelPoint = getPolygonLabelPoint(feature.geometry);
@@ -595,27 +609,106 @@ export function roundZoom(value: number): number {
return Math.round(value * 10) / 10; return Math.round(value * 10) / 10;
} }
function getSingleEntityFeatureLabel(feature: Feature): string | null { function createFeatureLabelResolver(fc: FeatureCollection): (feature: Feature) => string | null {
const rawEntityIds = Array.isArray(feature.properties.entity_ids) const directLabelsByFeatureId = new Map<string, FeatureLabelInfo>();
const inheritedLabelsByChildId = new Map<string, FeatureLabelInfo | null>();
for (const feature of fc.features) {
const labelInfo = getSingleEntityFeatureLabelInfo(feature);
if (!labelInfo) continue;
directLabelsByFeatureId.set(String(feature.properties.id), labelInfo);
}
for (const feature of fc.features) {
const parentLabel = directLabelsByFeatureId.get(String(feature.properties.id));
const featureId = String(feature.properties.id);
const bindingIds = normalizeBindingIds(feature.properties.binding);
if (parentLabel) {
for (const childId of bindingIds) {
mergeInheritedFeatureLabel(inheritedLabelsByChildId, childId, parentLabel);
}
}
for (const parentId of bindingIds) {
const linkedParentLabel = directLabelsByFeatureId.get(parentId);
if (linkedParentLabel) {
mergeInheritedFeatureLabel(inheritedLabelsByChildId, featureId, linkedParentLabel);
}
}
}
return (feature) => {
const featureId = String(feature.properties.id);
const directEntityIds = getFeatureEntityIds(feature);
if (directEntityIds.length > 0) {
return directLabelsByFeatureId.get(featureId)?.label || null;
}
return inheritedLabelsByChildId.get(featureId)?.label || null;
};
}
function mergeInheritedFeatureLabel(
labelsByFeatureId: Map<string, FeatureLabelInfo | null>,
targetFeatureId: string,
labelInfo: FeatureLabelInfo
) {
const current = labelsByFeatureId.get(targetFeatureId);
if (current === undefined) {
labelsByFeatureId.set(targetFeatureId, labelInfo);
} else if (current && current.entityId === labelInfo.entityId) {
labelsByFeatureId.set(targetFeatureId, current);
} else {
labelsByFeatureId.set(targetFeatureId, null);
}
}
function getSingleEntityFeatureLabelInfo(feature: Feature): FeatureLabelInfo | null {
const entityIds = getFeatureEntityIds(feature);
if (entityIds.length !== 1) return null;
const label = getSingleEntityName(feature);
if (!label) return null;
return { entityId: entityIds[0], label };
}
function getFeatureEntityIds(feature: Feature): string[] {
const rawEntityIds: unknown[] = Array.isArray(feature.properties.entity_ids)
? feature.properties.entity_ids ? feature.properties.entity_ids
: (typeof feature.properties.entity_id === "string" && feature.properties.entity_id.trim().length > 0 : (typeof feature.properties.entity_id === "string" || typeof feature.properties.entity_id === "number"
? [feature.properties.entity_id] ? [feature.properties.entity_id]
: []); : []);
const entityIds = Array.from(new Set( return Array.from(new Set(
rawEntityIds rawEntityIds
.filter((id): id is string => typeof id === "string") .filter((id): id is string | number => typeof id === "string" || typeof id === "number")
.map((id) => id.trim()) .map((id) => String(id).trim())
.filter((id) => id.length > 0) .filter((id) => id.length > 0)
)); ));
}
if (entityIds.length !== 1) return null; function getSingleEntityName(feature: Feature): string | null {
const directName = typeof feature.properties.entity_name === "string"
const name = typeof feature.properties.entity_name === "string"
? feature.properties.entity_name.trim() ? feature.properties.entity_name.trim()
: ""; : "";
if (directName.length > 0) return directName;
return name.length ? name : null; const names = Array.isArray(feature.properties.entity_names)
? Array.from(new Set(
feature.properties.entity_names
.filter((name): name is string => typeof name === "string")
.map((name) => name.trim())
.filter((name) => name.length > 0)
))
: [];
return names.length === 1 ? names[0] : null;
}
function isLineGeometry(geometry: Geometry): boolean {
return geometry.type === "LineString" || geometry.type === "MultiLineString";
} }
function getPolygonLabelPoint(geometry: Geometry): Coordinate | null { function getPolygonLabelPoint(geometry: Geometry): Coordinate | null {
@@ -648,67 +741,20 @@ function getPolygonLabelCandidate(polygon: PolygonCoordinates): { point: Coordin
const width = bbox.maxX - bbox.minX; const width = bbox.maxX - bbox.minX;
const height = bbox.maxY - bbox.minY; const height = bbox.maxY - bbox.minY;
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
const fallback = getRingCentroid(outerRing) || [bbox.minX, bbox.minY] as Coordinate; const fallback: Coordinate = [bbox.minX, bbox.minY];
return { point: fallback, distance: 0 }; return { point: fallback, distance: 0 };
} }
const cellSize = Math.min(width, height);
const h = cellSize / 2;
const cells: LabelCell[] = [];
for (let x = bbox.minX; x < bbox.maxX; x += cellSize) {
for (let y = bbox.minY; y < bbox.maxY; y += cellSize) {
cells.push(createLabelCell(x + h, y + h, h, polygon));
}
}
let best = createLabelCell(bbox.minX + width / 2, bbox.minY + height / 2, 0, polygon);
const centroid = getRingCentroid(outerRing);
if (centroid) {
const centroidCell = createLabelCell(centroid[0], centroid[1], 0, polygon);
if (centroidCell.d > best.d) {
best = centroidCell;
}
}
const precision = Math.max(Math.max(width, height) / 100, 0.0001); const precision = Math.max(Math.max(width, height) / 100, 0.0001);
let iterations = 0; const result = polylabel(polygon, precision);
while (cells.length && iterations < 4096) { const x = result[0];
cells.sort((a, b) => b.max - a.max); const y = result[1];
const cell = cells.shift();
if (!cell) break;
iterations += 1;
if (cell.d > best.d) { if (!Number.isFinite(x) || !Number.isFinite(y)) {
best = cell; return { point: [bbox.minX + width / 2, bbox.minY + height / 2], distance: 0 };
}
if (cell.max - best.d <= precision) continue;
const nextH = cell.h / 2;
cells.push(createLabelCell(cell.x - nextH, cell.y - nextH, nextH, polygon));
cells.push(createLabelCell(cell.x + nextH, cell.y - nextH, nextH, polygon));
cells.push(createLabelCell(cell.x - nextH, cell.y + nextH, nextH, polygon));
cells.push(createLabelCell(cell.x + nextH, cell.y + nextH, nextH, polygon));
} }
if (best.d < 0) { return { point: [x, y], distance: Number.isFinite(result.distance) ? result.distance : 0 };
const fallback = centroid || [bbox.minX + width / 2, bbox.minY + height / 2] as Coordinate;
return { point: fallback, distance: 0 };
}
return { point: [best.x, best.y], distance: best.d };
}
function createLabelCell(x: number, y: number, h: number, polygon: PolygonCoordinates): LabelCell {
const d = pointToPolygonDistance([x, y], polygon);
return {
x,
y,
h,
d,
max: d + h * Math.SQRT2,
};
} }
function getRingBbox(ring: Coordinate[]): { minX: number; minY: number; maxX: number; maxY: number } | null { function getRingBbox(ring: Coordinate[]): { minX: number; minY: number; maxX: number; maxY: number } | null {
@@ -733,78 +779,6 @@ function getRingBbox(ring: Coordinate[]): { minX: number; minY: number; maxX: nu
return { minX, minY, maxX, maxY }; return { minX, minY, maxX, maxY };
} }
function getRingCentroid(ring: Coordinate[]): Coordinate | null {
let area = 0;
let x = 0;
let y = 0;
for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
const a = ring[i];
const b = ring[j];
const f = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * f;
y += (a[1] + b[1]) * f;
area += f * 3;
}
if (area === 0) return null;
return [x / area, y / area];
}
function pointToPolygonDistance(point: Coordinate, polygon: PolygonCoordinates): number {
const inside = isPointInRing(point, polygon[0]) && !polygon.slice(1).some((ring) => isPointInRing(point, ring));
let minDistSq = Number.POSITIVE_INFINITY;
for (const ring of polygon) {
for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
minDistSq = Math.min(minDistSq, getSegmentDistanceSquared(point, ring[j], ring[i]));
}
}
const distance = Math.sqrt(minDistSq);
return inside ? distance : -distance;
}
function isPointInRing(point: Coordinate, ring: Coordinate[] | undefined): boolean {
if (!ring || ring.length < 3) return false;
const [x, y] = point;
let inside = false;
for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
const xi = ring[i][0];
const yi = ring[i][1];
const xj = ring[j][0];
const yj = ring[j][1];
const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersects) inside = !inside;
}
return inside;
}
function getSegmentDistanceSquared(point: Coordinate, a: Coordinate, b: Coordinate): number {
let x = a[0];
let y = a[1];
let dx = b[0] - x;
let dy = b[1] - y;
if (dx !== 0 || dy !== 0) {
const t = ((point[0] - x) * dx + (point[1] - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = b[0];
y = b[1];
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = point[0] - x;
dy = point[1] - y;
return dx * dx + dy * dy;
}
export function buildClientFeatureId(): string { export function buildClientFeatureId(): string {
return newId(); return newId();
} }
+1 -1
View File
@@ -338,7 +338,7 @@ export function setupMapLayers(
map.addLayer(layer); map.addLayer(layer);
} }
const geotypeLabelLayers = getAllGeotypeLabelLayers(POLYGON_LABEL_SOURCE_ID); const geotypeLabelLayers = getAllGeotypeLabelLayers(POLYGON_LABEL_SOURCE_ID, "countries");
for (const layer of geotypeLabelLayers) { for (const layer of geotypeLabelLayers) {
map.addLayer(layer); map.addLayer(layer);
} }
+11 -4
View File
@@ -8,6 +8,7 @@ import {
applyBackgroundLayerVisibility, applyBackgroundLayerVisibility,
buildPolygonLabelFeatureCollection, buildPolygonLabelFeatureCollection,
buildPathArrowFeatureCollection, buildPathArrowFeatureCollection,
decorateLineFeaturesWithLabels,
decoratePointFeaturesWithLabels, decoratePointFeaturesWithLabels,
filterDraftByBinding, filterDraftByBinding,
filterDraftByGeometryVisibility, filterDraftByGeometryVisibility,
@@ -19,6 +20,7 @@ import {
type UseMapSyncProps = { type UseMapSyncProps = {
mapRef: React.MutableRefObject<maplibregl.Map | null>; mapRef: React.MutableRefObject<maplibregl.Map | null>;
draft: FeatureCollection; draft: FeatureCollection;
labelContextDraft?: FeatureCollection;
backgroundVisibility: BackgroundLayerVisibility; backgroundVisibility: BackgroundLayerVisibility;
geometryVisibility?: Record<string, boolean>; geometryVisibility?: Record<string, boolean>;
selectedFeatureIds: (string | number)[]; selectedFeatureIds: (string | number)[];
@@ -40,6 +42,7 @@ type UseMapSyncProps = {
export function useMapSync({ export function useMapSync({
mapRef, mapRef,
draft, draft,
labelContextDraft,
backgroundVisibility, backgroundVisibility,
geometryVisibility, geometryVisibility,
selectedFeatureIds, selectedFeatureIds,
@@ -55,6 +58,7 @@ export function useMapSync({
geolocationCenteredRef, geolocationCenteredRef,
}: UseMapSyncProps) { }: UseMapSyncProps) {
const draftRef = useRef<FeatureCollection>(draft); const draftRef = useRef<FeatureCollection>(draft);
const labelContextDraftRef = useRef<FeatureCollection | undefined>(labelContextDraft);
const backgroundVisibilityRef = useRef<BackgroundLayerVisibility>(backgroundVisibility); const backgroundVisibilityRef = useRef<BackgroundLayerVisibility>(backgroundVisibility);
const geometryVisibilityRef = useRef<Record<string, boolean> | undefined>(geometryVisibility); const geometryVisibilityRef = useRef<Record<string, boolean> | undefined>(geometryVisibility);
const selectedFeatureIdsRef = useRef<(string | number)[]>(selectedFeatureIds); const selectedFeatureIdsRef = useRef<(string | number)[]>(selectedFeatureIds);
@@ -68,6 +72,7 @@ export function useMapSync({
const fitBoundsAppliedRef = useRef(false); const fitBoundsAppliedRef = useRef(false);
useEffect(() => { draftRef.current = draft; }, [draft]); useEffect(() => { draftRef.current = draft; }, [draft]);
useEffect(() => { labelContextDraftRef.current = labelContextDraft; }, [labelContextDraft]);
useEffect(() => { backgroundVisibilityRef.current = backgroundVisibility; }, [backgroundVisibility]); useEffect(() => { backgroundVisibilityRef.current = backgroundVisibility; }, [backgroundVisibility]);
useEffect(() => { geometryVisibilityRef.current = geometryVisibility; }, [geometryVisibility]); useEffect(() => { geometryVisibilityRef.current = geometryVisibility; }, [geometryVisibility]);
useEffect(() => { selectedFeatureIdsRef.current = selectedFeatureIds; }, [selectedFeatureIds]); useEffect(() => { selectedFeatureIdsRef.current = selectedFeatureIds; }, [selectedFeatureIds]);
@@ -102,12 +107,14 @@ export function useMapSync({
? filterDraftByBinding(fc, selectedFeatureIdsRef.current, highlightFeaturesRef.current) ? filterDraftByBinding(fc, selectedFeatureIdsRef.current, highlightFeaturesRef.current)
: fc; : fc;
const visibleDraft = filterDraftByGeometryVisibility(visibleDraftRaw, geometryVisibilityRef.current); const visibleDraft = filterDraftByGeometryVisibility(visibleDraftRaw, geometryVisibilityRef.current);
const labelContext = labelContextDraftRef.current || fc;
const { polygons, points } = splitDraftFeatures(visibleDraft); const { polygons, points } = splitDraftFeatures(visibleDraft);
const labeledPoints = decoratePointFeaturesWithLabels(points); const labeledGeometries = decorateLineFeaturesWithLabels(polygons, labelContext);
const polygonLabels = buildPolygonLabelFeatureCollection(polygons); const labeledPoints = decoratePointFeaturesWithLabels(points, labelContext);
const polygonLabels = buildPolygonLabelFeatureCollection(polygons, labelContext);
const pathArrowShapes = buildPathArrowFeatureCollection(visibleDraft); const pathArrowShapes = buildPathArrowFeatureCollection(visibleDraft);
countriesSource.setData(polygons); countriesSource.setData(labeledGeometries);
placesSource.setData(labeledPoints); placesSource.setData(labeledPoints);
polygonLabelSource.setData(polygonLabels); polygonLabelSource.setData(polygonLabels);
(map.getSource(PATH_ARROW_SOURCE_ID) as maplibregl.GeoJSONSource | undefined)?.setData(pathArrowShapes); (map.getSource(PATH_ARROW_SOURCE_ID) as maplibregl.GeoJSONSource | undefined)?.setData(pathArrowShapes);
@@ -183,7 +190,7 @@ export function useMapSync({
editingEngineRef.current?.clearEditing(); editingEngineRef.current?.clearEditing();
} }
} }
}, [allowGeometryEditing, draft, selectedFeatureIds, applyDraftToMap, editingEngineRef]); }, [allowGeometryEditing, draft, labelContextDraft, selectedFeatureIds, applyDraftToMap, editingEngineRef]);
useEffect(() => { useEffect(() => {
if (focusRequestKey === null || focusRequestKey === undefined) return; if (focusRequestKey === null || focusRequestKey === undefined) return;
+3 -1
View File
@@ -29,6 +29,7 @@ import { getCastleLayers } from "./castle";
import { getRuinLayers } from "./ruin"; import { getRuinLayers } from "./ruin";
import { getPortLayers } from "./port"; import { getPortLayers } from "./port";
import { getBridgeLayers } from "./bridge"; import { getBridgeLayers } from "./bridge";
import { getLineLabelLayers } from "./lineLabels";
import { getPolygonLabelLayers } from "./polygonLabels"; import { getPolygonLabelLayers } from "./polygonLabels";
import { LayerSpecification } from "maplibre-gl"; import { LayerSpecification } from "maplibre-gl";
@@ -65,8 +66,9 @@ export function getAllGeotypeLayers(sourceId: string, pathArrowSourceId?: string
]; ];
} }
export function getAllGeotypeLabelLayers(polygonLabelSourceId: string): LayerSpecification[] { export function getAllGeotypeLabelLayers(polygonLabelSourceId: string, lineSourceId: string): LayerSpecification[] {
return [ return [
...getPolygonLabelLayers(polygonLabelSourceId), ...getPolygonLabelLayers(polygonLabelSourceId),
...getLineLabelLayers(lineSourceId),
]; ];
} }
+1
View File
@@ -23,6 +23,7 @@ export type FeatureProperties = {
entity_names?: string[]; entity_names?: string[];
entity_type_id?: string | null; entity_type_id?: string | null;
point_label?: string | null; point_label?: string | null;
line_label?: string | null;
polygon_label?: string | null; polygon_label?: string | null;
}; };