@@ -39,11 +70,11 @@ export function MapFooter({
Pan map to view details
)}
- {mapInternals && (
+ {displayCoords && (
- Lat: {mapInternals.lat.toFixed(4)}
- Lng: {mapInternals.lng.toFixed(4)}
- Zoom: {mapInternals.zoom.toFixed(1)}
+ Lat: {displayCoords.lat.toFixed(4)}
+ Lng: {displayCoords.lng.toFixed(4)}
+ Zoom: {displayCoords.zoom.toFixed(1)}
)}
diff --git a/packages/ui/src/modules/places/components/map-layers/LiveSearchLayers.tsx b/packages/ui/src/modules/places/components/map-layers/LiveSearchLayers.tsx
index b3936dd7..1f9984a7 100644
--- a/packages/ui/src/modules/places/components/map-layers/LiveSearchLayers.tsx
+++ b/packages/ui/src/modules/places/components/map-layers/LiveSearchLayers.tsx
@@ -58,12 +58,14 @@ export function LiveSearchLayers({ map, liveAreas, liveRadii, liveNodes, isDarkS
}
return () => {
- map.off('style.load', initSources);
+ if (map && map.getStyle()) {
+ map.off('style.load', initSources);
+ }
};
}, [map]);
useEffect(() => {
- if (!map || !map.getSource('live-areas')) return;
+ if (!map || !map.getStyle() || !map.getSource('live-areas')) return;
const features = liveAreas.map(a => {
// Assuming the boundary comes as geojson
@@ -78,14 +80,16 @@ export function LiveSearchLayers({ map, liveAreas, liveRadii, liveNodes, isDarkS
return null;
}).filter(Boolean);
- (map.getSource('live-areas') as maplibregl.GeoJSONSource).setData({
- type: 'FeatureCollection',
- features: features as any
- });
+ try {
+ (map.getSource('live-areas') as maplibregl.GeoJSONSource).setData({
+ type: 'FeatureCollection',
+ features: features as any
+ });
+ } catch (e) {}
}, [map, liveAreas]);
useEffect(() => {
- if (!map || !map.getSource('live-nodes')) return;
+ if (!map || !map.getStyle() || !map.getSource('live-nodes')) return;
const features = liveNodes.map(n => {
// Resolve coordinates from multiple possible formats
@@ -116,10 +120,12 @@ export function LiveSearchLayers({ map, liveAreas, liveRadii, liveNodes, isDarkS
return null;
}).filter(Boolean);
- (map.getSource('live-nodes') as maplibregl.GeoJSONSource).setData({
- type: 'FeatureCollection',
- features: features as any
- });
+ try {
+ (map.getSource('live-nodes') as maplibregl.GeoJSONSource).setData({
+ type: 'FeatureCollection',
+ features: features as any
+ });
+ } catch (e) {}
}, [map, liveNodes]);
return null;
diff --git a/packages/ui/src/modules/places/components/map-layers/LocationLayers.tsx b/packages/ui/src/modules/places/components/map-layers/LocationLayers.tsx
index 252b91d1..54bdef75 100644
--- a/packages/ui/src/modules/places/components/map-layers/LocationLayers.tsx
+++ b/packages/ui/src/modules/places/components/map-layers/LocationLayers.tsx
@@ -41,7 +41,7 @@ export function LocationLayers({
const isDarkStyleRef = useRef(isDarkStyle);
isDarkStyleRef.current = isDarkStyle;
-
+
const onSelectRef = useRef(onSelect);
onSelectRef.current = onSelect;
@@ -157,13 +157,13 @@ export function LocationLayers({
const clusterId = features[0].properties.cluster_id;
const source = map.getSource('locations-source') as maplibregl.GeoJSONSource;
-
+
try {
// MapLibre v5+ getClusterExpansionZoom returns a Promise and doesn't take a callback
const zoom = await (source as any).getClusterExpansionZoom(clusterId);
map.easeTo({
center: (features[0].geometry as any).coordinates,
- zoom: zoom
+ //zoom: zoom
});
} catch (err) {
console.error('Error expanding cluster', err);
@@ -200,21 +200,23 @@ export function LocationLayers({
map.on('style.load', setupLayers);
return () => {
- map.off('styledata', setupLayers);
- map.off('style.load', setupLayers);
- if (map.getSource('locations-source')) {
- if (map.getLayer('clusters')) map.removeLayer('clusters');
- if (map.getLayer('cluster-count')) map.removeLayer('cluster-count');
- if (map.getLayer('unclustered-point-circle')) map.removeLayer('unclustered-point-circle');
- if (map.getLayer('unclustered-point-label')) map.removeLayer('unclustered-point-label');
- map.removeSource('locations-source');
+ if (map && map.getStyle()) {
+ map.off('styledata', setupLayers);
+ map.off('style.load', setupLayers);
+ if (map.getSource('locations-source')) {
+ if (map.getLayer('clusters')) map.removeLayer('clusters');
+ if (map.getLayer('cluster-count')) map.removeLayer('cluster-count');
+ if (map.getLayer('unclustered-point-circle')) map.removeLayer('unclustered-point-circle');
+ if (map.getLayer('unclustered-point-label')) map.removeLayer('unclustered-point-label');
+ map.removeSource('locations-source');
+ }
}
};
}, [map, competitors]);
// Update data when it changes
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
const source = map.getSource('locations-source') as maplibregl.GeoJSONSource;
if (source) {
source.setData(data);
@@ -223,7 +225,7 @@ export function LocationLayers({
// Update style-dependent properties
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
if (map.getLayer('unclustered-point-circle')) {
map.setPaintProperty('unclustered-point-circle', 'circle-color', [
'case',
diff --git a/packages/ui/src/modules/places/components/map-layers/RegionLayers.tsx b/packages/ui/src/modules/places/components/map-layers/RegionLayers.tsx
index 80415df3..5a0d666f 100644
--- a/packages/ui/src/modules/places/components/map-layers/RegionLayers.tsx
+++ b/packages/ui/src/modules/places/components/map-layers/RegionLayers.tsx
@@ -131,9 +131,9 @@ export function RegionLayers({
map.on('style.load', setupMapLayers);
return () => {
- map.off('styledata', setupMapLayers);
- map.off('style.load', setupMapLayers);
- if (map.getStyle()) {
+ if (map && map.getStyle()) {
+ map.off('styledata', setupMapLayers);
+ map.off('style.load', setupMapLayers);
if (map.getLayer('polygons-fill')) map.removeLayer('polygons-fill');
if (map.getLayer('polygons-line')) map.removeLayer('polygons-line');
if (map.getLayer('bboxes-fill')) map.removeLayer('bboxes-fill');
@@ -170,7 +170,7 @@ export function RegionLayers({
// Update GHS Centers Source
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
try {
if (showCenters && pickerPolygons && pickerPolygons.length > 0) {
const features: any[] = [];
@@ -226,7 +226,7 @@ export function RegionLayers({
// Update Bboxes and Polygons Features
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
if ((window as any).__MAP_PERF_DEBUG__) {
const fc = polygonsFeatureCollection as any;
console.log(`[MapPerf] RegionLayers setData() called — polygons features: ${fc?.features?.length ?? 0}`);
diff --git a/packages/ui/src/modules/places/components/map-layers/SimulatorLayers.tsx b/packages/ui/src/modules/places/components/map-layers/SimulatorLayers.tsx
index 71c338a5..b38e3b9a 100644
--- a/packages/ui/src/modules/places/components/map-layers/SimulatorLayers.tsx
+++ b/packages/ui/src/modules/places/components/map-layers/SimulatorLayers.tsx
@@ -204,13 +204,13 @@ export function SimulatorLayers({ map, isDarkStyle, simulatorData, simulatorPath
map.on('style.load', setupMapLayers);
return () => {
- map.off('styledata', setupMapLayers);
- map.off('style.load', setupMapLayers);
- if (scannerMarkerRef.current) {
- scannerMarkerRef.current.remove();
- scannerMarkerRef.current = null;
- }
- if (map.getStyle()) {
+ if (map && map.getStyle()) {
+ map.off('styledata', setupMapLayers);
+ map.off('style.load', setupMapLayers);
+ if (scannerMarkerRef.current) {
+ scannerMarkerRef.current.remove();
+ scannerMarkerRef.current = null;
+ }
if (map.getLayer('simulator-grid-fill')) map.removeLayer('simulator-grid-fill');
if (map.getLayer('simulator-path-line')) map.removeLayer('simulator-path-line');
if (map.getSource('simulator-grid')) map.removeSource('simulator-grid');
@@ -236,7 +236,7 @@ export function SimulatorLayers({ map, isDarkStyle, simulatorData, simulatorPath
// Update grid + path data
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
try {
if (map.getSource('simulator-grid')) (map.getSource('simulator-grid') as maplibregl.GeoJSONSource).setData(simulatorData || emptyFc as any);
if (map.getSource('simulator-path')) (map.getSource('simulator-path') as maplibregl.GeoJSONSource).setData(simulatorPath || emptyFc as any);
@@ -247,7 +247,7 @@ export function SimulatorLayers({ map, isDarkStyle, simulatorData, simulatorPath
// Update pacman DOM marker position + rotation
useEffect(() => {
- if (!map) return;
+ if (!map || !map.getStyle()) return;
const fc = simulatorScanner as any;
const feature = fc?.features?.[0];
diff --git a/packages/ui/src/modules/places/gadm-picker/GadmPicker.tsx b/packages/ui/src/modules/places/gadm-picker/GadmPicker.tsx
index ff32d5a1..5545f024 100644
--- a/packages/ui/src/modules/places/gadm-picker/GadmPicker.tsx
+++ b/packages/ui/src/modules/places/gadm-picker/GadmPicker.tsx
@@ -392,10 +392,18 @@ export function GadmPicker({ map, active, onClose, onSelectionChange, className
async function handleSelectRegion(region: any, forceLevel?: number, isMulti: boolean = false) {
const { levelOption, resolutionOption, enrich } = stateRef.current;
const gadmLevel = region.raw?.level ?? (region.level !== undefined ? region.level : levelOption);
- const gid = region.gid || region[`GID_${gadmLevel}`] || region.GID_0;
+
+ // Robust GID resolution: prioritization
+ // 1. region.gid (if explicitly passed)
+ // 2. region.GID_{gadmLevel} (specific level match)
+ // 3. Fallback to GID_0 ONLY if gadmLevel is 0, otherwise it's a failure (don't snap to country)
+ const gid = region.gid || region[`GID_${gadmLevel}`] || (gadmLevel === 0 ? region.GID_0 : undefined);
const name = region.gadmName || region.name || region[`NAME_${gadmLevel}`] || region.NAME_0;
- if (!gid) return;
+ if (!gid) {
+ console.warn('GadmPicker: Could not resolve gid for region', region, 'at level', gadmLevel);
+ return;
+ }
if (!isMulti) {
if (isFetchingSelectionRef.current) return;
@@ -589,9 +597,25 @@ export function GadmPicker({ map, active, onClose, onSelectionChange, className
// Import initialRegions prop (same logic as handleImportJson)
useEffect(() => {
- if (!initialRegions || initialRegions.length === 0) return;
+ if (!initialRegions || initialRegions.length === 0) {
+ if (active && importedInitialRef.current !== null) {
+ // If it became empty, we SHOULD clear
+ handleClearAll();
+ importedInitialRef.current = null;
+ }
+ return;
+ }
+
const key = initialRegions.map(r => r.gid).sort().join(',');
if (importedInitialRef.current === key) return;
+
+ // Check if current selectedRegions already contains exactly these gids
+ const currentGids = selectedRegions.map(r => r.gid).sort().join(',');
+ if (currentGids === key && importedInitialRef.current !== null) {
+ importedInitialRef.current = key;
+ return;
+ }
+
importedInitialRef.current = key;
// Full reset, same as import handler
@@ -603,7 +627,9 @@ export function GadmPicker({ map, active, onClose, onSelectionChange, className
setGeojsons({});
for (const item of initialRegions) {
- const raw = (item as any).raw ? { ...((item as any).raw), level: item.level } : { gid: item.gid, gadmName: item.name, level: item.level };
+ const raw = (item as any).raw
+ ? { ...((item as any).raw), gid: item.gid, level: item.level }
+ : { gid: item.gid, gadmName: item.name, level: item.level };
handleSelectRegion(raw, item.level, true);
}
}, [initialRegions]);
diff --git a/packages/ui/src/modules/places/gadm-picker/GadmRegionCollector.tsx b/packages/ui/src/modules/places/gadm-picker/GadmRegionCollector.tsx
index 98ccbeda..ee0e48b4 100644
--- a/packages/ui/src/modules/places/gadm-picker/GadmRegionCollector.tsx
+++ b/packages/ui/src/modules/places/gadm-picker/GadmRegionCollector.tsx
@@ -12,7 +12,7 @@ export interface GadmRegionCollectorProps {
}
export function GadmRegionCollector({
resolutions = {},
- onChangeResolution = () => {}
+ onChangeResolution = () => { }
}: GadmRegionCollectorProps) {
const {
roots, setRoots,
@@ -26,7 +26,7 @@ export function GadmRegionCollector({
const [suggestions, setSuggestions] = useState
([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [loadingSuggestions, setLoadingSuggestions] = useState(false);
-
+
const suggestionsWrapRef = useRef(null);
const inputRef = useRef(null);
const [isLocating, setIsLocating] = useState(false);
@@ -95,25 +95,27 @@ export function GadmRegionCollector({
if (data && !data.error && data.latitude && data.longitude) {
const hierarchy = await fetchRegionHierarchy(data.latitude, data.longitude);
if (hierarchy && hierarchy.length > 0) {
- const pathGids = hierarchy.map((d: any) => d.gid);
+
const l0 = hierarchy[0];
+
const rootNode: GadmNode = {
name: l0.gadmName || l0.name || l0.gid,
gid: l0.gid,
level: l0.level,
hasChildren: l0.level < 5,
- data: l0
+ data: l0
};
-
+ console.log('root node', rootNode)
+
setRoots(prev => {
if (prev.find(p => p.gid === rootNode.gid)) return prev;
return [...prev, rootNode];
});
setTimeout(() => {
- treeApiRef.current?.expandPath(pathGids);
+ // treeApiRef.current?.expandPath(pathGids);
}, 100);
-
+
const lastNode = hierarchy[hierarchy.length - 1];
setRegionQuery(lastNode.gadmName || lastNode.name || lastNode.gid);
}
@@ -135,12 +137,12 @@ export function GadmRegionCollector({
// Ensure tree path expands to existing selections (e.g. when returning from back-navigation)
useEffect(() => {
if (selectedNodes.length === 0 || roots.length === 0) return;
-
+
let expandedAnything = false;
for (const node of selectedNodes) {
const data = node.data;
if (!data) continue;
-
+
const pathGids: string[] = [];
for (let i = 0; i <= node.level; i++) {
if (data[`GID_${i}`]) {
@@ -156,7 +158,7 @@ export function GadmRegionCollector({
}
}, [roots.length]); // Relies on roots being loaded
-
+
useEffect(() => {
if (!regionQuery || regionQuery.length < 2) { setSuggestions([]); return; }
const id = setTimeout(async () => {
@@ -212,7 +214,7 @@ export function GadmRegionCollector({
{loadingSuggestions ? (
) : (
-
-
+