diff --git a/packages/common/business_components/src/ui/map/GisOverlay.js b/packages/common/business_components/src/ui/map/GisOverlay.js index ca22cf9c9..387f74c83 100644 --- a/packages/common/business_components/src/ui/map/GisOverlay.js +++ b/packages/common/business_components/src/ui/map/GisOverlay.js @@ -10,8 +10,8 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, const getCoordinateKey = buildCoordinateKeyFactory(coordPrecision); let currentFormatter = overlayFormatter; - const overlayMap = new Map(); // coordKey -> Overlay - const overlayElementMap = new Map(); // coordKey -> HTMLElement + const overlayMap = new Map(); // feature -> Overlay + const overlayElementMap = new Map(); // feature -> HTMLElement const vectorSource = vectorLayer?.getSource?.(); const SYNC_MIN_INTERVAL = 120; @@ -25,22 +25,22 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, const clearAllOverlaysAsync = (batch = 150) => { - const keys = Array.from(overlayMap.keys()); - if (!keys.length) return; + const features = Array.from(overlayMap.keys()); + if (!features.length) return; // 재귀로 사용하는 이유 한번에 다지운는ㄴ것이아닌 150개 이상 더있을경우를 대비해서 다시 호출하기 위해(지도가 계속 멈춤) const step = () => { let iNum = 0; - const end = Math.min(iNum + batch, keys.length); + const end = Math.min(iNum + batch, features.length); for (; iNum < end; iNum++) { - const k = keys[iNum]; - const ov = overlayMap.get(k); + const feat = features[iNum]; + const ov = overlayMap.get(feat); try { if (ov) map.removeOverlay(ov); } catch {} - overlayMap.delete(k); - const el = overlayElementMap.get(k); - if (el) { try { el.remove?.(); } catch {} overlayElementMap.delete(k); } + overlayMap.delete(feat); + const el = overlayElementMap.get(feat); + if (el) { try { el.remove?.(); } catch {} overlayElementMap.delete(feat); } } - if (iNum < keys.length) { + if (iNum < features.length) { (typeof requestAnimationFrame === 'function' ? requestAnimationFrame : setTimeout)(step, 0); } }; @@ -75,9 +75,8 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, const ensureOverlay = (feature, properties) => { const coord = getFeatureLabelCoordinate(feature); if (!Array.isArray(coord)) return null; - const key = getCoordinateKey(coord); - let el = overlayElementMap.get(key); + let el = overlayElementMap.get(feature); if (!el) { if (!currentFormatter || typeof currentFormatter !== 'function') return null; @@ -86,19 +85,19 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, // 1) 동기 HTMLElement if (maybeEl && typeof maybeEl === 'object' && (maybeEl.nodeType === 1 || maybeEl.tagName)) { el = maybeEl; - overlayElementMap.set(key, el); + overlayElementMap.set(feature, el); } // 2) 비동기 Promise else if (maybeEl && typeof maybeEl.then === 'function') { maybeEl.then((resolvedEl) => { if (!resolvedEl || !(resolvedEl.nodeType === 1 || resolvedEl.tagName)) return; // 이미 다른 사이클에서 만들어졌으면 덮어쓰지 않음 - if (!overlayElementMap.has(key)) overlayElementMap.set(key, resolvedEl); + if (!overlayElementMap.has(feature)) overlayElementMap.set(feature, resolvedEl); - let ov = overlayMap.get(key); + let ov = overlayMap.get(feature); if (!ov) { ov = new Overlay({ element: resolvedEl, positioning: 'bottom-center', offset: [0, -18], stopEvent: true }); - overlayMap.set(key, ov); + overlayMap.set(feature, ov); try { map.addOverlay(ov); } catch {} } else { try { ov.setElement(resolvedEl); } catch {} @@ -115,10 +114,10 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, } // 여기까지 왔으면 el은 HTMLElement - let ov = overlayMap.get(key); + let ov = overlayMap.get(feature); if (!ov) { ov = new Overlay({ element: el, positioning: 'bottom-center', offset: [0, -18], stopEvent: true }); - overlayMap.set(key, ov); + overlayMap.set(feature, ov); map.addOverlay(ov); } ov.setPosition(coord); @@ -132,52 +131,35 @@ export default function createOverlayManager(map, vectorLayer, overlayFormatter, const size = map.getSize?.(); const viewExtent = view && size ? view.calculateExtent(size) : null; - // gather features in viewport only and group by coordinate - const groups = new Map(); // coordKey -> feature[] - const addFeature = (f) => { - const c = getFeatureLabelCoordinate(f); - if (!Array.isArray(c)) return; - const k = getCoordinateKey(c); - const arr = groups.get(k); - if (arr) arr.push(f); else groups.set(k, [f]); - }; - + const features = []; + const pushFeature = (f) => features.push(f); if (vectorSource?.forEachFeatureInExtent && viewExtent) { - vectorSource.forEachFeatureInExtent(viewExtent, addFeature); + vectorSource.forEachFeatureInExtent(viewExtent, pushFeature); } else { - vectorSource?.forEachFeature(addFeature); + vectorSource?.forEachFeature(pushFeature); } - // cap overlays per viewport with simple uniform sampling - const keys = Array.from(groups.keys()); - let selectedKeys; - if (!isFinite(maxOverlaysPerViewport) || keys.length <= maxOverlaysPerViewport) { - selectedKeys = keys; - } else { - const step = Math.ceil(keys.length / maxOverlaysPerViewport); - selectedKeys = []; - for (let i = 0; i < keys.length && selectedKeys.length < maxOverlaysPerViewport; i += step) { - selectedKeys.push(keys[i]); + let selected = features; + if (isFinite(maxOverlaysPerViewport) && features.length > maxOverlaysPerViewport) { + const step = Math.ceil(features.length / maxOverlaysPerViewport); + selected = []; + for (let i = 0; i < features.length && selected.length < maxOverlaysPerViewport; i += step) { + selected.push(features[i]); } } - // ensure selected overlays - for (const k of selectedKeys) { - const featuresAtKey = groups.get(k); - const rep = featuresAtKey[0]; - const props = rep.getProperties?.() ?? {}; - if (featuresAtKey.length > 1) props.__groupCount = featuresAtKey.length; - ensureOverlay(rep, props); + for (const f of selected) { + const props = f.getProperties?.() ?? {}; + ensureOverlay(f, props); } - // remove non-selected overlays - const keep = new Set(selectedKeys); - for (const [k, ov] of overlayMap.entries()) { - if (!keep.has(k)) { + const keep = new Set(selected); + for (const [feat, ov] of overlayMap.entries()) { + if (!keep.has(feat)) { try { map.removeOverlay(ov); } catch {} - overlayMap.delete(k); - const el = overlayElementMap.get(k); - if (el) { try { el.remove?.(); } catch {} overlayElementMap.delete(k); } + overlayMap.delete(feat); + const el = overlayElementMap.get(feat); + if (el) { try { el.remove?.(); } catch {} overlayElementMap.delete(feat); } } } };