@mint-ui/map 1.2.0-test.71 → 1.2.0-test.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -650,692 +650,721 @@ var css_248z$1 = ".MintMapCore-module_mint-map-root__SMfwn {\n position: relati
650
650
  var styles$1 = {"mint-map-root":"MintMapCore-module_mint-map-root__SMfwn","mint-map-container":"MintMapCore-module_mint-map-container__8MIIr"};
651
651
  styleInject(css_248z$1);
652
652
 
653
- var CanvasContext = createContext(null);
653
+ /* eslint-disable max-classes-per-file */
654
+
655
+ /* eslint-disable no-restricted-syntax */
656
+
654
657
  /**
655
- * CanvasProvider 컴포넌트
658
+ * 공간 인덱스 그리드 셀 크기 (픽셀 단위)
656
659
  *
657
- * 다중 Canvas 인스턴스를 관리하고 zIndex 기반 이벤트 우선순위를 처리합니다.
660
+ * @default 200
661
+ *
662
+ * @remarks
663
+ * 셀 크기는 평균 마커 크기의 1.5~2배가 적절합니다.
664
+ * - 마커가 50px 이하: 50px 권장
665
+ * - 마커가 60-80px: 100px 권장
666
+ * - 마커가 100px 이상: 150-200px 권장 (현재 설정)
667
+ *
668
+ * 셀 크기가 너무 작으면:
669
+ * - 한 마커가 여러 셀에 등록되어 메모리 사용량 증가
670
+ * - 인덱스 빌드 비용 증가
671
+ *
672
+ * 셀 크기가 너무 크면:
673
+ * - 한 셀에 많은 마커가 들어가서 Hit Test 시 후보 항목이 많아짐
674
+ * - Hit Test 성능 저하
675
+ */
676
+ var SPATIAL_GRID_CELL_SIZE = 200;
677
+ /**
678
+ * 뷰포트 컬링 여유 공간 (픽셀 단위)
679
+ *
680
+ * @default 100
658
681
  */
659
682
 
660
- /* eslint-disable no-restricted-syntax */
683
+ var DEFAULT_CULLING_MARGIN = 100;
684
+ /**
685
+ * Queue 캐시 최대 항목 수
686
+ *
687
+ * @default 30000
688
+ */
661
689
 
662
- /* eslint-disable no-continue */
690
+ var DEFAULT_MAX_CACHE_SIZE = 30000;
691
+ /**
692
+ * Queue Cache (FIFO - First In First Out)
693
+ *
694
+ * 좌표 변환 결과를 캐싱하기 위한 캐시 구현
695
+ * FIFO 방식으로 가장 먼저 저장된 항목부터 제거합니다.
696
+ *
697
+ * @template K 캐시 키 타입
698
+ * @template V 캐시 값 타입
699
+ */
663
700
 
664
- var CanvasProvider = function (_a) {
665
- var children = _a.children;
666
- var controller = useMintMapController(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
701
+ var QueueCache =
702
+ /** @class */
703
+ function () {
704
+ function QueueCache(maxSize) {
705
+ if (maxSize === void 0) {
706
+ maxSize = 10000;
707
+ }
667
708
 
668
- var componentsRef = useRef([]); // eslint-disable-next-line @typescript-eslint/no-explicit-any
709
+ this.cache = new Map();
710
+ this.maxSize = maxSize;
711
+ } // 캐시에서 값 조회
669
712
 
670
- var currentHoveredRef = useRef(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any
671
713
 
672
- var currentHoveredDataRef = useRef(null);
673
- var draggingRef = useRef(false); // 컴포넌트 등록 (zIndex 내림차순 정렬)
714
+ QueueCache.prototype.get = function (key) {
715
+ return this.cache.get(key);
716
+ }; // 캐시에 값 저장 (FIFO eviction)
674
717
 
675
- var registerComponent = useCallback(function (instance) {
676
- componentsRef.current.push(instance);
677
- componentsRef.current.sort(function (a, b) {
678
- return b.zIndex - a.zIndex;
679
- });
680
- }, []); // 컴포넌트 등록 해제
681
718
 
682
- var unregisterComponent = useCallback(function (instance) {
683
- if (currentHoveredRef.current === instance) {
684
- currentHoveredRef.current = null;
685
- currentHoveredDataRef.current = null;
719
+ QueueCache.prototype.set = function (key, value) {
720
+ var exists = this.cache.has(key);
721
+
722
+ if (exists) {
723
+ this.cache.set(key, value);
724
+ return;
686
725
  }
687
726
 
688
- componentsRef.current = componentsRef.current.filter(function (c) {
689
- return c !== instance;
690
- });
691
- }, []); // 전역 클릭 핸들러 (zIndex 우선순위)
727
+ if (this.cache.size >= this.maxSize) {
728
+ var firstKey = this.cache.keys().next().value;
692
729
 
693
- var handleGlobalClick = useCallback(function (event) {
694
- var _a, _b;
730
+ if (firstKey !== undefined) {
731
+ this.cache.delete(firstKey);
732
+ }
733
+ }
695
734
 
696
- try {
697
- if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
698
- var clickedOffset = void 0;
735
+ this.cache.set(key, value);
736
+ }; // 캐시 초기화
699
737
 
700
- try {
701
- clickedOffset = controller.positionToOffset(event.param.position);
702
- } catch (error) {
703
- // positionToOffset 실패 시 조용히 종료 (기존 동작 보장)
704
- console.warn('[CanvasProvider] positionToOffset failed:', error);
705
- return;
706
- } // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
707
738
 
739
+ QueueCache.prototype.clear = function () {
740
+ this.cache.clear();
741
+ }; // 캐시 크기 반환
708
742
 
709
- for (var _i = 0, _c = componentsRef.current; _i < _c.length; _i++) {
710
- var component = _c[_i];
711
743
 
712
- try {
713
- if (component.isInteractionDisabled()) continue;
714
- var data = component.findData(clickedOffset);
715
- if (!data) continue; // 첫 번째로 찾은 항목만 처리하고 종료 (zIndex 우선순위)
744
+ QueueCache.prototype.size = function () {
745
+ return this.cache.size;
746
+ }; // 존재 여부 확인
716
747
 
717
- component.handleLocalClick(data);
718
- (_b = component.onClick) === null || _b === void 0 ? void 0 : _b.call(component, data, component.getSelectedIds());
719
- return;
720
- } catch (error) {
721
- // 개별 컴포넌트 처리 중 에러 발생 시 다음 컴포넌트로 계속 진행
722
- console.warn('[CanvasProvider] Component click handler error:', error);
723
- continue;
724
- }
725
- }
726
- } catch (error) {
727
- // 전체 핸들러 에러 시 조용히 종료 (앱 크래시 방지)
728
- console.error('[CanvasProvider] handleGlobalClick error:', error);
729
- }
730
- }, [controller]); // 전역 마우스 이동 핸들러 (zIndex 우선순위)
731
748
 
732
- var handleGlobalMouseMove = useCallback(function (event) {
733
- var _a;
749
+ QueueCache.prototype.has = function (key) {
750
+ return this.cache.has(key);
751
+ }; // 모든 캐시 키 반환 (디버깅/모니터링용)
734
752
 
735
- try {
736
- if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
737
- var mouseOffset = void 0;
738
753
 
739
- try {
740
- mouseOffset = controller.positionToOffset(event.param.position);
741
- } catch (error) {
742
- // positionToOffset 실패 시 조용히 종료 (기존 동작 보장)
743
- console.warn('[CanvasProvider] positionToOffset failed:', error);
744
- return;
745
- } // eslint-disable-next-line @typescript-eslint/no-explicit-any
754
+ QueueCache.prototype.getAllKeys = function () {
755
+ return Array.from(this.cache.keys());
756
+ }; // 모든 캐시 항목 반환 (디버깅/모니터링용)
746
757
 
747
758
 
748
- var newHoveredComponent = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any
759
+ QueueCache.prototype.getAllEntries = function () {
760
+ return Array.from(this.cache.entries());
761
+ };
749
762
 
750
- var newHoveredData = null; // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
763
+ return QueueCache;
764
+ }();
765
+ /**
766
+ * Spatial Hash Grid (공간 해시 그리드)
767
+ *
768
+ * 빠른 Hit Test를 위한 그리드 기반 공간 인덱싱 자료구조
769
+ *
770
+ * @template T 인덱싱할 항목 타입
771
+ */
751
772
 
752
- for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
753
- var component = _b[_i];
773
+ var SpatialHashGrid =
774
+ /** @class */
775
+ function () {
776
+ function SpatialHashGrid(cellSize) {
777
+ if (cellSize === void 0) {
778
+ cellSize = SPATIAL_GRID_CELL_SIZE;
779
+ }
754
780
 
755
- try {
756
- if (component.isInteractionDisabled()) continue;
757
- var data = component.findData(mouseOffset);
758
- if (!data) continue; // 번째로 찾은 항목만 hover 처리 (zIndex 우선순위)
781
+ this.cellSize = cellSize;
782
+ this.grid = new Map();
783
+ this.itemToCells = new Map();
784
+ } // 생성 (x, y 좌표 그리드 ID)
759
785
 
760
- newHoveredComponent = component;
761
- newHoveredData = data;
762
- break;
763
- } catch (error) {
764
- // 개별 컴포넌트 처리 중 에러 발생 시 다음 컴포넌트로 계속 진행
765
- console.warn('[CanvasProvider] Component mouse move handler error:', error);
766
- continue;
767
- }
768
- } // hover 상태가 변경되지 않았으면 종료 (불필요한 렌더링 방지)
769
786
 
787
+ SpatialHashGrid.prototype.getCellKey = function (x, y) {
788
+ // 좌표를 셀 크기로 나눈 몫으로 셀 인덱스 계산
789
+ var cellX = Math.floor(x / this.cellSize);
790
+ var cellY = Math.floor(y / this.cellSize);
791
+ return "".concat(cellX, ",").concat(cellY);
792
+ }; // 바운딩 박스가 걸치는 모든 셀 키 배열 반환
770
793
 
771
- if (currentHoveredRef.current === newHoveredComponent && currentHoveredDataRef.current === newHoveredData) {
772
- return;
773
- } // 기존 hover 항목에 mouseOut 이벤트 발생
774
794
 
795
+ SpatialHashGrid.prototype.getCellsForBounds = function (minX, minY, maxX, maxY) {
796
+ var cells = []; // 바운딩 박스가 걸치는 셀 범위 계산
775
797
 
776
- if (currentHoveredRef.current) {
777
- try {
778
- currentHoveredRef.current.setHovered(null);
798
+ var startCellX = Math.floor(minX / this.cellSize);
799
+ var startCellY = Math.floor(minY / this.cellSize);
800
+ var endCellX = Math.floor(maxX / this.cellSize);
801
+ var endCellY = Math.floor(maxY / this.cellSize); // 바운딩 박스가 걸치는 모든 셀을 배열에 추가
779
802
 
780
- if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
781
- currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
782
- }
783
- } catch (error) {
784
- // mouseOut 이벤트 에러 시 조용히 처리 (기존 동작 보장)
785
- console.warn('[CanvasProvider] mouseOut handler error:', error);
786
- }
787
- } // 새 hover 항목에 mouseOver 이벤트 발생
803
+ for (var x = startCellX; x <= endCellX; x++) {
804
+ for (var y = startCellY; y <= endCellY; y++) {
805
+ cells.push("".concat(x, ",").concat(y));
806
+ }
807
+ }
788
808
 
809
+ return cells;
810
+ }; // 항목 추가 (바운딩 박스 기반, 중복 삽입 방지)
789
811
 
790
- if (newHoveredComponent && newHoveredData) {
791
- try {
792
- newHoveredComponent.setHovered(newHoveredData);
793
812
 
794
- if (newHoveredComponent.onMouseOver) {
795
- newHoveredComponent.onMouseOver(newHoveredData);
796
- }
797
- } catch (error) {
798
- // mouseOver 이벤트 에러 조용히 처리 (기존 동작 보장)
799
- console.warn('[CanvasProvider] mouseOver handler error:', error);
800
- }
813
+ SpatialHashGrid.prototype.insert = function (item, minX, minY, maxX, maxY) {
814
+ // 기존 항목 제거 (중복 삽입 방지: 같은 항목을 여러 번 insert 해도 안전)
815
+ this.remove(item); // 바운딩 박스가 걸치는 모든 셀에 항목 등록
816
+
817
+ var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
818
+ this.itemToCells.set(item, cells); // 항목과 셀의 매핑 저장 (제거 시 필요)
819
+
820
+ for (var _i = 0, cells_1 = cells; _i < cells_1.length; _i++) {
821
+ var cell = cells_1[_i];
822
+
823
+ if (!this.grid.has(cell)) {
824
+ this.grid.set(cell, []);
801
825
  }
802
826
 
803
- currentHoveredRef.current = newHoveredComponent;
804
- currentHoveredDataRef.current = newHoveredData;
805
- } catch (error) {
806
- // 전체 핸들러 에러 시 조용히 종료 (앱 크래시 방지)
807
- console.error('[CanvasProvider] handleGlobalMouseMove error:', error);
827
+ this.grid.get(cell).push(item);
808
828
  }
809
- }, [controller]);
810
- var handleZoomStart = useCallback(function () {
811
- draggingRef.current = true;
812
- }, []);
813
- var handleIdle = useCallback(function () {
814
- draggingRef.current = false;
815
- }, []);
816
- useEffect(function () {
817
- try {
818
- controller.addEventListener('CLICK', handleGlobalClick);
819
- controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
820
- controller.addEventListener('ZOOMSTART', handleZoomStart);
821
- controller.addEventListener('IDLE', handleIdle);
822
- } catch (error) {
823
- // 이벤트 리스너 등록 실패 시 경고만 출력 (기존 동작 보장)
824
- console.warn('[CanvasProvider] Failed to add event listeners:', error);
825
- }
826
-
827
- return function () {
828
- try {
829
- controller.removeEventListener('CLICK', handleGlobalClick);
830
- controller.removeEventListener('MOUSEMOVE', handleGlobalMouseMove);
831
- controller.removeEventListener('ZOOMSTART', handleZoomStart);
832
- controller.removeEventListener('IDLE', handleIdle);
833
- } catch (error) {
834
- // cleanup 중 에러 발생 시 조용히 처리 (controller가 이미 destroy된 경우 등)
835
- // 에러를 무시해도 메모리 누수는 발생하지 않음 (이벤트 리스너는 자동으로 정리됨)
836
- console.warn('[CanvasProvider] Failed to remove event listeners:', error);
837
- }
838
- };
839
- }, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]);
840
- var contextValue = useMemo(function () {
841
- return {
842
- registerComponent: registerComponent,
843
- unregisterComponent: unregisterComponent
844
- };
845
- }, [registerComponent, unregisterComponent]);
846
- return React.createElement(CanvasContext.Provider, {
847
- value: contextValue
848
- }, children);
849
- };
850
- /**
851
- * Canvas Context Hook
852
- *
853
- * @returns CanvasContextValue 또는 null (Provider 없으면)
854
- */
829
+ }; // 항목 제거 (모든 셀에서 참조 제거)
855
830
 
856
- var useCanvasContext = function () {
857
- return useContext(CanvasContext);
858
- };
859
831
 
860
- /**
861
- * 이벤트 유효성 검증 및 좌표 변환
862
- *
863
- * @param event 이벤트 파라미터
864
- * @param context CanvasContext 인스턴스
865
- * @param controller MintMapController 인스턴스
866
- * @returns 유효한 화면 좌표 또는 null
867
- */
868
- var validateEvent = function (event, // eslint-disable-next-line @typescript-eslint/no-explicit-any
869
- context, controller) {
870
- var _a;
832
+ SpatialHashGrid.prototype.remove = function (item) {
833
+ var prevCells = this.itemToCells.get(item);
834
+ if (!prevCells) return; // 항목이 등록된 모든 셀에서 참조 제거 (메모리 누수 방지)
871
835
 
872
- if (context) return null;
873
- if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return null;
836
+ for (var _i = 0, prevCells_1 = prevCells; _i < prevCells_1.length; _i++) {
837
+ var cell = prevCells_1[_i];
838
+ var cellItems = this.grid.get(cell);
874
839
 
875
- try {
876
- return controller.positionToOffset(event.param.position);
877
- } catch (error) {
878
- console.error('[CanvasLayer] validateEvent error:', error);
879
- return null;
880
- }
881
- };
882
- /**
883
- * Map의 values를 배열로 변환
884
- *
885
- * @template T Map 값의 타입
886
- * @param map 변환할 Map
887
- * @returns Map의 값 배열
888
- */
840
+ if (cellItems) {
841
+ var index = cellItems.indexOf(item);
889
842
 
890
- var mapValuesToArray = function (map) {
891
- if (map.size === 0) return [];
892
- return Array.from(map.values());
893
- };
843
+ if (index !== -1) {
844
+ cellItems.splice(index, 1);
845
+ } // 빈 셀 정리 (메모리 효율: 사용하지 않는 셀 제거)
894
846
 
895
- /**
896
- * 지도 이벤트 핸들러 생성 함수
897
- *
898
- * @template T 마커/폴리곤 데이터의 추가 속성 타입
899
- * @param deps 이벤트 핸들러 생성에 필요한 의존성
900
- * @returns 지도 이벤트 핸들러 객체
901
- */
902
847
 
903
- /* eslint-disable no-restricted-syntax */
848
+ if (cellItems.length === 0) {
849
+ this.grid.delete(cell);
850
+ }
851
+ }
852
+ } // 항목과 셀의 매핑 제거
904
853
 
905
- var createMapEventHandlers = function (deps) {
906
- var accumTranslateRef = deps.accumTranslateRef,
907
- boundingBoxCacheRef = deps.boundingBoxCacheRef,
908
- containerRef = deps.containerRef,
909
- controller = deps.controller,
910
- markerRef = deps.markerRef,
911
- offsetCacheRef = deps.offsetCacheRef,
912
- options = deps.options,
913
- prevCenterOffsetRef = deps.prevCenterOffsetRef,
914
- renderAllImmediate = deps.renderAllImmediate; // 지도 이동/줌 완료 시 처리 (캐시 초기화 및 렌더링)
915
854
 
916
- var handleIdle = function () {
917
- prevCenterOffsetRef.current = null;
918
- accumTranslateRef.current = {
919
- x: 0,
920
- y: 0
921
- }; // 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
855
+ this.itemToCells.delete(item);
856
+ }; // 항목 위치 업데이트 (remove + insert)
922
857
 
923
- offsetCacheRef.current.clear();
924
- boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
925
858
 
926
- var bounds = controller.getCurrBounds();
859
+ SpatialHashGrid.prototype.update = function (item, minX, minY, maxX, maxY) {
860
+ this.insert(item, minX, minY, maxX, maxY);
861
+ }; // 점 주변의 항목 조회 (Hit Test용)
927
862
 
928
- var markerOptions = __assign(__assign({}, options), {
929
- position: bounds.nw
930
- });
931
863
 
932
- if (markerRef.current) {
933
- controller.updateMarker(markerRef.current, markerOptions);
934
- } // transform 제거 전에 새 데이터로 즉시 렌더링 (transform 제거 시 잠깐 빈 화면이 보이는 것 방지)
864
+ SpatialHashGrid.prototype.queryPoint = function (x, y) {
865
+ // 클릭 위치가 속한 셀의 모든 항목 조회 (O(1) 수준의 빠른 조회)
866
+ var cellKey = this.getCellKey(x, y);
867
+ var items = this.grid.get(cellKey); // 빈 배열 재사용 (메모리 할당 최소화)
935
868
 
869
+ return items || [];
870
+ }; // 영역 내 항목 조회 (Viewport Culling용)
936
871
 
937
- if (containerRef.current) {
938
- containerRef.current.style.transform = '';
939
- containerRef.current.style.visibility = '';
940
- } // 새 위치에서 렌더링 (캐시는 이미 초기화됨)
941
872
 
873
+ SpatialHashGrid.prototype.queryBounds = function (minX, minY, maxX, maxY) {
874
+ // 영역이 걸치는 모든 셀 찾기
875
+ var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
876
+ var results = new Set(); // 중복 제거를 위해 Set 사용
877
+ // 각 셀의 모든 항목을 결과에 추가
942
878
 
943
- renderAllImmediate();
944
- }; // 시작 시 처리 (일시적으로 숨김)
879
+ for (var _i = 0, cells_2 = cells; _i < cells_2.length; _i++) {
880
+ var cell = cells_2[_i];
881
+ var items = this.grid.get(cell);
945
882
 
883
+ if (items) {
884
+ for (var _a = 0, items_1 = items; _a < items_1.length; _a++) {
885
+ var item = items_1[_a];
886
+ results.add(item); // Set이므로 중복 자동 제거
887
+ }
888
+ }
889
+ }
946
890
 
947
- var handleZoomStart = function () {
948
- if (!containerRef.current) return;
949
- containerRef.current.style.visibility = 'hidden';
950
- }; // 줌 종료 시 처리 (다시 표시)
891
+ return Array.from(results);
892
+ }; // 항목 존재 여부 확인
951
893
 
952
894
 
953
- var handleZoomEnd = function () {
954
- if (!containerRef.current) return;
955
- containerRef.current.style.visibility = '';
956
- }; // 지도 중심 변경 시 처리 (transform으로 이동 추적, 캐시 유지)
895
+ SpatialHashGrid.prototype.has = function (item) {
896
+ return this.itemToCells.has(item);
897
+ }; // 전체 초기화
957
898
 
958
899
 
959
- var handleCenterChanged = function () {
960
- var center = controller.getCurrBounds().getCenter();
961
- var curr = controller.positionToOffset(center);
962
- var prev = prevCenterOffsetRef.current; // 번째 호출 시 이전 위치 저장만 하고 종료
900
+ SpatialHashGrid.prototype.clear = function () {
901
+ this.grid.clear();
902
+ this.itemToCells.clear();
903
+ }; // 통계 정보 반환
963
904
 
964
- if (!prev) {
965
- prevCenterOffsetRef.current = {
966
- x: curr.x,
967
- y: curr.y
968
- };
969
- return;
970
- } // 이전 위치와 현재 위치의 차이 계산 (이동 거리)
971
905
 
906
+ SpatialHashGrid.prototype.stats = function () {
907
+ var totalCellItems = 0;
908
+ this.grid.forEach(function (items) {
909
+ totalCellItems += items.length;
910
+ });
911
+ return {
912
+ // 고유 항목 수 (정확)
913
+ avgItemsPerCell: this.grid.size > 0 ? totalCellItems / this.grid.size : 0,
914
+ totalCells: this.grid.size,
915
+ totalItems: this.itemToCells.size
916
+ };
917
+ }; // 모든 그리드 셀 데이터 반환 (디버깅/모니터링용)
972
918
 
973
- var dx = prev.x - curr.x;
974
- var dy = prev.y - curr.y; // 누적 이동 거리 저장 (transform으로 화면만 이동, 캐시는 유지하여 성능 최적화)
975
919
 
976
- accumTranslateRef.current = {
977
- x: accumTranslateRef.current.x + dx,
978
- y: accumTranslateRef.current.y + dy
979
- };
980
- prevCenterOffsetRef.current = {
981
- x: curr.x,
982
- y: curr.y
983
- }; // CSS transform으로 컨테이너 이동 (캐시된 좌표는 그대로 유지)
920
+ SpatialHashGrid.prototype.getAllCells = function () {
921
+ return new Map(this.grid);
922
+ }; // 특정 셀의 항목 조회
984
923
 
985
- if (containerRef.current) {
986
- containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
987
- }
988
- };
989
924
 
990
- var handleDragStart = function () {// 커서는 각 컴포넌트에서 처리
925
+ SpatialHashGrid.prototype.getCellItems = function (cellKey) {
926
+ return this.grid.get(cellKey) || [];
991
927
  };
992
928
 
993
- var handleDragEnd = function () {// 커서는 각 컴포넌트에서 처리
994
- };
929
+ return SpatialHashGrid;
930
+ }();
995
931
 
996
- return {
997
- handleCenterChanged: handleCenterChanged,
998
- handleDragEnd: handleDragEnd,
999
- handleDragStart: handleDragStart,
1000
- handleIdle: handleIdle,
1001
- handleZoomEnd: handleZoomEnd,
1002
- handleZoomStart: handleZoomStart
1003
- };
1004
- };
932
+ var CanvasContext = createContext(null);
1005
933
  /**
1006
- * 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
934
+ * CanvasProvider 컴포넌트
1007
935
  *
1008
- * @template T 마커/폴리곤 데이터의 추가 속성 타입
1009
- * @param data 공간 인덱스에 삽입할 데이터 배열
1010
- * @param spatialIndex Spatial Hash Grid 인스턴스
1011
- * @param computeBoundingBox 바운딩 박스 계산 함수
936
+ * 다중 Canvas 인스턴스를 관리하고 zIndex 기반 이벤트 우선순위를 처리합니다.
1012
937
  */
1013
938
 
1014
- var buildSpatialIndex = function (data, spatialIndex, computeBoundingBox) {
1015
- spatialIndex.clear();
939
+ /* eslint-disable no-restricted-syntax */
1016
940
 
1017
- for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
1018
- var item = data_1[_i];
1019
- var bbox = computeBoundingBox(item);
941
+ /* eslint-disable no-continue */
1020
942
 
1021
- if (bbox) {
1022
- spatialIndex.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
1023
- }
1024
- }
1025
- };
1026
- /**
1027
- * 선택 상태 동기화 (화면 데이터도 선택 상태 유지)
1028
- *
1029
- * @template T 마커/폴리곤 데이터의 추가 속성 타입
1030
- * @param data 최신 데이터 배열
1031
- * @param selectedIds 선택된 항목 ID Set
1032
- * @param selectedItemsMap 현재 선택된 항목 Map
1033
- * @returns 업데이트된 선택된 항목 Map
1034
- */
943
+ var CanvasProvider = function (_a) {
944
+ var children = _a.children,
945
+ _b = _a.markerMaxCacheSize,
946
+ markerMaxCacheSize = _b === void 0 ? 5000 : _b,
947
+ _c = _a.polygonMaxCacheSize,
948
+ polygonMaxCacheSize = _c === void 0 ? 30000 : _c;
949
+ var controller = useMintMapController(); // eslint-disable-next-line @typescript-eslint/no-explicit-any
1035
950
 
1036
- var syncSelectedItems = function (data, selectedIds, selectedItemsMap) {
1037
- var dataMap = new Map(data.map(function (m) {
1038
- return [m.id, m];
1039
- }));
1040
- var newSelectedItemsMap = new Map();
1041
- selectedIds.forEach(function (id) {
1042
- // 현재 data에 있으면 최신 데이터 사용
1043
- var currentItem = dataMap.get(id);
951
+ var componentsRef = useRef([]); // eslint-disable-next-line @typescript-eslint/no-explicit-any
1044
952
 
1045
- if (currentItem) {
1046
- newSelectedItemsMap.set(id, currentItem);
1047
- } else {
1048
- // 화면 밖이면 기존 데이터 유지
1049
- var prevItem = selectedItemsMap.get(id);
953
+ var currentHoveredRef = useRef(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any
1050
954
 
1051
- if (prevItem) {
1052
- newSelectedItemsMap.set(id, prevItem);
1053
- }
1054
- }
1055
- });
1056
- return newSelectedItemsMap;
1057
- };
1058
- /**
1059
- * 외부 selectedItems를 내부 상태로 동기화
1060
- *
1061
- * @template T 마커/폴리곤 데이터의 추가 속성 타입
1062
- * @param externalSelectedItems 외부에서 전달된 선택된 항목 배열
1063
- * @param selectedIdsRef 선택된 ID Set ref
1064
- * @param selectedItemsMapRef 선택된 항목 Map ref
1065
- */
955
+ var currentHoveredDataRef = useRef(null);
956
+ var draggingRef = useRef(false); // 공유 캐시 (모든 레이어가 공유)
1066
957
 
1067
- var syncExternalSelectedItems = function (externalSelectedItems, selectedIdsRef, selectedItemsMapRef) {
1068
- if (externalSelectedItems === undefined) return;
1069
- var newSelectedIds = new Set();
1070
- var newSelectedItemsMap = new Map();
1071
- externalSelectedItems.forEach(function (item) {
1072
- newSelectedIds.add(item.id);
1073
- newSelectedItemsMap.set(item.id, item);
1074
- }); // eslint-disable-next-line no-param-reassign
958
+ var sharedMarkerCacheRef = useRef(new QueueCache(markerMaxCacheSize));
959
+ var sharedPolygonCacheRef = useRef(new QueueCache(polygonMaxCacheSize)); // 컴포넌트 등록 (zIndex 내림차순 정렬)
1075
960
 
1076
- selectedIdsRef.current = newSelectedIds; // eslint-disable-next-line no-param-reassign
961
+ var registerComponent = useCallback(function (instance) {
962
+ componentsRef.current.push(instance);
963
+ componentsRef.current.sort(function (a, b) {
964
+ return b.zIndex - a.zIndex;
965
+ });
966
+ }, []); // 컴포넌트 등록 해제
1077
967
 
1078
- selectedItemsMapRef.current = newSelectedItemsMap;
1079
- };
968
+ var unregisterComponent = useCallback(function (instance) {
969
+ if (currentHoveredRef.current === instance) {
970
+ currentHoveredRef.current = null;
971
+ currentHoveredDataRef.current = null;
972
+ }
1080
973
 
1081
- /* eslint-disable max-classes-per-file */
974
+ componentsRef.current = componentsRef.current.filter(function (c) {
975
+ return c !== instance;
976
+ });
977
+ }, []); // 전역 클릭 핸들러 (zIndex 우선순위)
1082
978
 
1083
- /* eslint-disable no-restricted-syntax */
979
+ var handleGlobalClick = useCallback(function (event) {
980
+ var _a, _b;
1084
981
 
1085
- /**
1086
- * 공간 인덱스 그리드 크기 (픽셀 단위)
1087
- *
1088
- * @default 100
1089
- *
1090
- * @remarks
1091
- * 셀 크기는 평균 마커 크기의 1.5~2배가 적절합니다.
1092
- * - 마커가 50px 이하: 50px 권장
1093
- * - 마커가 60-80px: 100px 권장 (현재 설정)
1094
- * - 마커가 100px 이상: 150-200px 권장
1095
- *
1096
- * 셀 크기가 너무 작으면:
1097
- * - 한 마커가 여러 셀에 등록되어 메모리 사용량 증가
1098
- * - 인덱스 빌드 비용 증가
1099
- *
1100
- * 셀 크기가 너무 크면:
1101
- * - 한 셀에 많은 마커가 들어가서 Hit Test 시 후보 항목이 많아짐
1102
- * - Hit Test 성능 저하
1103
- */
1104
- var SPATIAL_GRID_CELL_SIZE = 100;
1105
- /**
1106
- * 뷰포트 컬링 여유 공간 (픽셀 단위)
1107
- *
1108
- * @default 100
1109
- */
982
+ try {
983
+ if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
984
+ var clickedOffset = void 0;
1110
985
 
1111
- var DEFAULT_CULLING_MARGIN = 100;
1112
- /**
1113
- * Queue 캐시 최대 항목 수
1114
- *
1115
- * @default 30000
1116
- */
986
+ try {
987
+ clickedOffset = controller.positionToOffset(event.param.position);
988
+ } catch (error) {
989
+ // positionToOffset 실패 시 조용히 종료 (기존 동작 보장)
990
+ console.warn('[CanvasProvider] positionToOffset failed:', error);
991
+ return;
992
+ } // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
1117
993
 
1118
- var DEFAULT_MAX_CACHE_SIZE = 30000;
1119
- /**
1120
- * Queue Cache (FIFO - First In First Out)
1121
- *
1122
- * 좌표 변환 결과를 캐싱하기 위한 캐시 구현
1123
- * FIFO 방식으로 가장 먼저 저장된 항목부터 제거합니다.
1124
- *
1125
- * @template K 캐시 키 타입
1126
- * @template V 캐시 값 타입
1127
- */
1128
994
 
1129
- var QueueCache =
1130
- /** @class */
1131
- function () {
1132
- function QueueCache(maxSize) {
1133
- if (maxSize === void 0) {
1134
- maxSize = 10000;
995
+ for (var _i = 0, _c = componentsRef.current; _i < _c.length; _i++) {
996
+ var component = _c[_i];
997
+
998
+ try {
999
+ if (component.isInteractionDisabled()) continue;
1000
+ var data = component.findData(clickedOffset);
1001
+ if (!data) continue; // 첫 번째로 찾은 항목만 처리하고 종료 (zIndex 우선순위)
1002
+
1003
+ component.handleLocalClick(data);
1004
+ (_b = component.onClick) === null || _b === void 0 ? void 0 : _b.call(component, data, component.getSelectedIds());
1005
+ return;
1006
+ } catch (error) {
1007
+ // 개별 컴포넌트 처리 중 에러 발생 시 다음 컴포넌트로 계속 진행
1008
+ console.warn('[CanvasProvider] Component click handler error:', error);
1009
+ continue;
1010
+ }
1011
+ }
1012
+ } catch (error) {
1013
+ // 전체 핸들러 에러 시 조용히 종료 (앱 크래시 방지)
1014
+ console.error('[CanvasProvider] handleGlobalClick error:', error);
1135
1015
  }
1016
+ }, [controller]); // 전역 마우스 이동 핸들러 (zIndex 우선순위)
1136
1017
 
1137
- this.cache = new Map();
1138
- this.maxSize = maxSize;
1139
- } // 캐시에서 값 조회
1018
+ var handleGlobalMouseMove = useCallback(function (event) {
1019
+ var _a;
1140
1020
 
1021
+ try {
1022
+ if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
1023
+ var mouseOffset = void 0;
1141
1024
 
1142
- QueueCache.prototype.get = function (key) {
1143
- return this.cache.get(key);
1144
- }; // 캐시에 값 저장 (FIFO eviction)
1025
+ try {
1026
+ mouseOffset = controller.positionToOffset(event.param.position);
1027
+ } catch (error) {
1028
+ // positionToOffset 실패 시 조용히 종료 (기존 동작 보장)
1029
+ console.warn('[CanvasProvider] positionToOffset failed:', error);
1030
+ return;
1031
+ } // eslint-disable-next-line @typescript-eslint/no-explicit-any
1145
1032
 
1146
1033
 
1147
- QueueCache.prototype.set = function (key, value) {
1148
- var exists = this.cache.has(key);
1034
+ var newHoveredComponent = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any
1149
1035
 
1150
- if (exists) {
1151
- this.cache.set(key, value);
1152
- return;
1153
- }
1036
+ var newHoveredData = null; // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
1154
1037
 
1155
- if (this.cache.size >= this.maxSize) {
1156
- var firstKey = this.cache.keys().next().value;
1038
+ for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
1039
+ var component = _b[_i];
1157
1040
 
1158
- if (firstKey !== undefined) {
1159
- this.cache.delete(firstKey);
1160
- }
1161
- }
1041
+ try {
1042
+ if (component.isInteractionDisabled()) continue;
1043
+ var data = component.findData(mouseOffset);
1044
+ if (!data) continue; // 첫 번째로 찾은 항목만 hover 처리 (zIndex 우선순위)
1162
1045
 
1163
- this.cache.set(key, value);
1164
- }; // 캐시 초기화
1046
+ newHoveredComponent = component;
1047
+ newHoveredData = data;
1048
+ break;
1049
+ } catch (error) {
1050
+ // 개별 컴포넌트 처리 중 에러 발생 시 다음 컴포넌트로 계속 진행
1051
+ console.warn('[CanvasProvider] Component mouse move handler error:', error);
1052
+ continue;
1053
+ }
1054
+ } // hover 상태가 변경되지 않았으면 종료 (불필요한 렌더링 방지)
1165
1055
 
1166
1056
 
1167
- QueueCache.prototype.clear = function () {
1168
- this.cache.clear();
1169
- }; // 캐시 크기 반환
1057
+ if (currentHoveredRef.current === newHoveredComponent && currentHoveredDataRef.current === newHoveredData) {
1058
+ return;
1059
+ } // 기존 hover 항목에 mouseOut 이벤트 발생
1170
1060
 
1171
1061
 
1172
- QueueCache.prototype.size = function () {
1173
- return this.cache.size;
1174
- }; // 키 존재 여부 확인
1062
+ if (currentHoveredRef.current) {
1063
+ try {
1064
+ currentHoveredRef.current.setHovered(null);
1065
+
1066
+ if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
1067
+ currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
1068
+ }
1069
+ } catch (error) {
1070
+ // mouseOut 이벤트 에러 시 조용히 처리 (기존 동작 보장)
1071
+ console.warn('[CanvasProvider] mouseOut handler error:', error);
1072
+ }
1073
+ } // 새 hover 항목에 mouseOver 이벤트 발생
1175
1074
 
1176
1075
 
1177
- QueueCache.prototype.has = function (key) {
1178
- return this.cache.has(key);
1179
- };
1076
+ if (newHoveredComponent && newHoveredData) {
1077
+ try {
1078
+ newHoveredComponent.setHovered(newHoveredData);
1180
1079
 
1181
- return QueueCache;
1182
- }();
1080
+ if (newHoveredComponent.onMouseOver) {
1081
+ newHoveredComponent.onMouseOver(newHoveredData);
1082
+ }
1083
+ } catch (error) {
1084
+ // mouseOver 이벤트 에러 시 조용히 처리 (기존 동작 보장)
1085
+ console.warn('[CanvasProvider] mouseOver handler error:', error);
1086
+ }
1087
+ }
1088
+
1089
+ currentHoveredRef.current = newHoveredComponent;
1090
+ currentHoveredDataRef.current = newHoveredData;
1091
+ } catch (error) {
1092
+ // 전체 핸들러 에러 시 조용히 종료 (앱 크래시 방지)
1093
+ console.error('[CanvasProvider] handleGlobalMouseMove error:', error);
1094
+ }
1095
+ }, [controller]);
1096
+ var handleZoomStart = useCallback(function () {
1097
+ draggingRef.current = true;
1098
+ }, []);
1099
+ var handleIdle = useCallback(function () {
1100
+ draggingRef.current = false;
1101
+ }, []);
1102
+ useEffect(function () {
1103
+ try {
1104
+ controller.addEventListener('CLICK', handleGlobalClick);
1105
+ controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
1106
+ controller.addEventListener('ZOOMSTART', handleZoomStart);
1107
+ controller.addEventListener('IDLE', handleIdle);
1108
+ } catch (error) {
1109
+ // 이벤트 리스너 등록 실패 시 경고만 출력 (기존 동작 보장)
1110
+ console.warn('[CanvasProvider] Failed to add event listeners:', error);
1111
+ }
1112
+
1113
+ return function () {
1114
+ try {
1115
+ controller.removeEventListener('CLICK', handleGlobalClick);
1116
+ controller.removeEventListener('MOUSEMOVE', handleGlobalMouseMove);
1117
+ controller.removeEventListener('ZOOMSTART', handleZoomStart);
1118
+ controller.removeEventListener('IDLE', handleIdle);
1119
+ } catch (error) {
1120
+ // cleanup 중 에러 발생 시 조용히 처리 (controller가 이미 destroy된 경우 등)
1121
+ // 에러를 무시해도 메모리 누수는 발생하지 않음 (이벤트 리스너는 자동으로 정리됨)
1122
+ console.warn('[CanvasProvider] Failed to remove event listeners:', error);
1123
+ }
1124
+ };
1125
+ }, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]);
1126
+ var contextValue = useMemo(function () {
1127
+ return {
1128
+ registerComponent: registerComponent,
1129
+ sharedMarkerCache: sharedMarkerCacheRef.current,
1130
+ sharedPolygonCache: sharedPolygonCacheRef.current,
1131
+ unregisterComponent: unregisterComponent
1132
+ };
1133
+ }, [registerComponent, unregisterComponent]);
1134
+ return React.createElement(CanvasContext.Provider, {
1135
+ value: contextValue
1136
+ }, children);
1137
+ };
1183
1138
  /**
1184
- * Spatial Hash Grid (공간 해시 그리드)
1139
+ * Canvas Context Hook
1185
1140
  *
1186
- * 빠른 Hit Test를 위한 그리드 기반 공간 인덱싱 자료구조
1141
+ * @returns CanvasContextValue 또는 null (Provider 없으면)
1142
+ */
1143
+
1144
+ var useCanvasContext = function () {
1145
+ return useContext(CanvasContext);
1146
+ };
1147
+
1148
+ /**
1149
+ * 이벤트 유효성 검증 및 좌표 변환
1187
1150
  *
1188
- * @template T 인덱싱할 항목 타입
1151
+ * @param event 이벤트 파라미터
1152
+ * @param context CanvasContext 인스턴스
1153
+ * @param controller MintMapController 인스턴스
1154
+ * @returns 유효한 화면 좌표 또는 null
1189
1155
  */
1156
+ var validateEvent = function (event, // eslint-disable-next-line @typescript-eslint/no-explicit-any
1157
+ context, controller) {
1158
+ var _a;
1190
1159
 
1191
- var SpatialHashGrid =
1192
- /** @class */
1193
- function () {
1194
- function SpatialHashGrid(cellSize) {
1195
- if (cellSize === void 0) {
1196
- cellSize = SPATIAL_GRID_CELL_SIZE;
1197
- }
1160
+ if (context) return null;
1161
+ if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return null;
1198
1162
 
1199
- this.cellSize = cellSize;
1200
- this.grid = new Map();
1201
- this.itemToCells = new Map();
1202
- } // 셀 키 생성 (x, y 좌표 → 그리드 셀 ID)
1163
+ try {
1164
+ return controller.positionToOffset(event.param.position);
1165
+ } catch (error) {
1166
+ console.error('[CanvasLayer] validateEvent error:', error);
1167
+ return null;
1168
+ }
1169
+ };
1170
+ /**
1171
+ * Map의 values를 배열로 변환
1172
+ *
1173
+ * @template T Map 값의 타입
1174
+ * @param map 변환할 Map
1175
+ * @returns Map의 값 배열
1176
+ */
1203
1177
 
1178
+ var mapValuesToArray = function (map) {
1179
+ if (map.size === 0) return [];
1180
+ return Array.from(map.values());
1181
+ };
1204
1182
 
1205
- SpatialHashGrid.prototype.getCellKey = function (x, y) {
1206
- // 좌표를 크기로 나눈 몫으로 셀 인덱스 계산
1207
- var cellX = Math.floor(x / this.cellSize);
1208
- var cellY = Math.floor(y / this.cellSize);
1209
- return "".concat(cellX, ",").concat(cellY);
1210
- }; // 바운딩 박스가 걸치는 모든 셀 키 배열 반환
1183
+ /**
1184
+ * 지도 이벤트 핸들러 생성 함수
1185
+ *
1186
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
1187
+ * @param deps 이벤트 핸들러 생성에 필요한 의존성
1188
+ * @returns 지도 이벤트 핸들러 객체
1189
+ */
1190
+
1191
+ /* eslint-disable no-restricted-syntax */
1192
+
1193
+ var createMapEventHandlers = function (deps) {
1194
+ var accumTranslateRef = deps.accumTranslateRef,
1195
+ boundingBoxCacheRef = deps.boundingBoxCacheRef,
1196
+ clearCache = deps.clearCache,
1197
+ containerRef = deps.containerRef,
1198
+ controller = deps.controller,
1199
+ markerRef = deps.markerRef,
1200
+ options = deps.options,
1201
+ prevCenterOffsetRef = deps.prevCenterOffsetRef,
1202
+ renderAllImmediate = deps.renderAllImmediate; // 지도 이동/줌 완료 시 처리 (캐시 초기화 및 렌더링)
1211
1203
 
1204
+ var handleIdle = function () {
1205
+ prevCenterOffsetRef.current = null;
1206
+ accumTranslateRef.current = {
1207
+ x: 0,
1208
+ y: 0
1209
+ }; // 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
1212
1210
 
1213
- SpatialHashGrid.prototype.getCellsForBounds = function (minX, minY, maxX, maxY) {
1214
- var cells = []; // 바운딩 박스가 걸치는 셀 범위 계산
1211
+ clearCache();
1212
+ boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
1215
1213
 
1216
- var startCellX = Math.floor(minX / this.cellSize);
1217
- var startCellY = Math.floor(minY / this.cellSize);
1218
- var endCellX = Math.floor(maxX / this.cellSize);
1219
- var endCellY = Math.floor(maxY / this.cellSize); // 바운딩 박스가 걸치는 모든 셀을 배열에 추가
1214
+ var bounds = controller.getCurrBounds();
1220
1215
 
1221
- for (var x = startCellX; x <= endCellX; x++) {
1222
- for (var y = startCellY; y <= endCellY; y++) {
1223
- cells.push("".concat(x, ",").concat(y));
1224
- }
1225
- }
1216
+ var markerOptions = __assign(__assign({}, options), {
1217
+ position: bounds.nw
1218
+ });
1226
1219
 
1227
- return cells;
1228
- }; // 항목 추가 (바운딩 박스 기반, 중복 삽입 방지)
1220
+ if (markerRef.current) {
1221
+ controller.updateMarker(markerRef.current, markerOptions);
1222
+ } // transform 제거 전에 새 데이터로 즉시 렌더링 (transform 제거 시 잠깐 빈 화면이 보이는 것 방지)
1229
1223
 
1230
1224
 
1231
- SpatialHashGrid.prototype.insert = function (item, minX, minY, maxX, maxY) {
1232
- // 기존 항목 제거 (중복 삽입 방지: 같은 항목을 여러 번 insert 해도 안전)
1233
- this.remove(item); // 바운딩 박스가 걸치는 모든 셀에 항목 등록
1225
+ if (containerRef.current) {
1226
+ containerRef.current.style.transform = '';
1227
+ containerRef.current.style.visibility = '';
1228
+ } // 새 위치에서 렌더링 (캐시는 이미 초기화됨)
1234
1229
 
1235
- var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
1236
- this.itemToCells.set(item, cells); // 항목과 셀의 매핑 저장 (제거 시 필요)
1237
1230
 
1238
- for (var _i = 0, cells_1 = cells; _i < cells_1.length; _i++) {
1239
- var cell = cells_1[_i];
1231
+ renderAllImmediate();
1232
+ }; // 시작 시 처리 (일시적으로 숨김)
1240
1233
 
1241
- if (!this.grid.has(cell)) {
1242
- this.grid.set(cell, []);
1243
- }
1244
1234
 
1245
- this.grid.get(cell).push(item);
1246
- }
1247
- }; // 항목 제거 (모든 셀에서 참조 제거)
1235
+ var handleZoomStart = function () {
1236
+ if (!containerRef.current) return;
1237
+ containerRef.current.style.visibility = 'hidden';
1238
+ }; // 줌 종료 시 처리 (다시 표시)
1248
1239
 
1249
1240
 
1250
- SpatialHashGrid.prototype.remove = function (item) {
1251
- var prevCells = this.itemToCells.get(item);
1252
- if (!prevCells) return; // 항목이 등록된 모든 셀에서 참조 제거 (메모리 누수 방지)
1241
+ var handleZoomEnd = function () {
1242
+ if (!containerRef.current) return;
1243
+ containerRef.current.style.visibility = '';
1244
+ }; // 지도 중심 변경 시 처리 (transform으로 이동 추적, 캐시 유지)
1253
1245
 
1254
- for (var _i = 0, prevCells_1 = prevCells; _i < prevCells_1.length; _i++) {
1255
- var cell = prevCells_1[_i];
1256
- var cellItems = this.grid.get(cell);
1257
1246
 
1258
- if (cellItems) {
1259
- var index = cellItems.indexOf(item);
1247
+ var handleCenterChanged = function () {
1248
+ var center = controller.getCurrBounds().getCenter();
1249
+ var curr = controller.positionToOffset(center);
1250
+ var prev = prevCenterOffsetRef.current; // 첫 번째 호출 시 이전 위치 저장만 하고 종료
1260
1251
 
1261
- if (index !== -1) {
1262
- cellItems.splice(index, 1);
1263
- } // 빈 셀 정리 (메모리 효율: 사용하지 않는 셀 제거)
1252
+ if (!prev) {
1253
+ prevCenterOffsetRef.current = {
1254
+ x: curr.x,
1255
+ y: curr.y
1256
+ };
1257
+ return;
1258
+ } // 이전 위치와 현재 위치의 차이 계산 (이동 거리)
1264
1259
 
1265
1260
 
1266
- if (cellItems.length === 0) {
1267
- this.grid.delete(cell);
1268
- }
1269
- }
1270
- } // 항목과 셀의 매핑 제거
1261
+ var dx = prev.x - curr.x;
1262
+ var dy = prev.y - curr.y; // 누적 이동 거리 저장 (transform으로 화면만 이동, 캐시는 유지하여 성능 최적화)
1271
1263
 
1264
+ accumTranslateRef.current = {
1265
+ x: accumTranslateRef.current.x + dx,
1266
+ y: accumTranslateRef.current.y + dy
1267
+ };
1268
+ prevCenterOffsetRef.current = {
1269
+ x: curr.x,
1270
+ y: curr.y
1271
+ }; // CSS transform으로 컨테이너 이동 (캐시된 좌표는 그대로 유지)
1272
1272
 
1273
- this.itemToCells.delete(item);
1274
- }; // 항목 위치 업데이트 (remove + insert)
1273
+ if (containerRef.current) {
1274
+ containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
1275
+ }
1276
+ };
1275
1277
 
1278
+ var handleDragStart = function () {// 커서는 각 컴포넌트에서 처리
1279
+ };
1276
1280
 
1277
- SpatialHashGrid.prototype.update = function (item, minX, minY, maxX, maxY) {
1278
- this.insert(item, minX, minY, maxX, maxY);
1279
- }; // 점 주변의 항목 조회 (Hit Test용)
1281
+ var handleDragEnd = function () {// 커서는 컴포넌트에서 처리
1282
+ };
1280
1283
 
1284
+ return {
1285
+ handleCenterChanged: handleCenterChanged,
1286
+ handleDragEnd: handleDragEnd,
1287
+ handleDragStart: handleDragStart,
1288
+ handleIdle: handleIdle,
1289
+ handleZoomEnd: handleZoomEnd,
1290
+ handleZoomStart: handleZoomStart
1291
+ };
1292
+ };
1293
+ /**
1294
+ * 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
1295
+ *
1296
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
1297
+ * @param data 공간 인덱스에 삽입할 데이터 배열
1298
+ * @param spatialIndex Spatial Hash Grid 인스턴스
1299
+ * @param computeBoundingBox 바운딩 박스 계산 함수
1300
+ */
1281
1301
 
1282
- SpatialHashGrid.prototype.queryPoint = function (x, y) {
1283
- // 클릭 위치가 속한 셀의 모든 항목 조회 (O(1) 수준의 빠른 조회)
1284
- var cellKey = this.getCellKey(x, y);
1285
- var items = this.grid.get(cellKey); // 빈 배열 재사용 (메모리 할당 최소화)
1302
+ var buildSpatialIndex = function (data, spatialIndex, computeBoundingBox) {
1303
+ spatialIndex.clear();
1286
1304
 
1287
- return items || [];
1288
- }; // 영역 내 항목 조회 (Viewport Culling용)
1305
+ for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
1306
+ var item = data_1[_i];
1307
+ var bbox = computeBoundingBox(item);
1289
1308
 
1309
+ if (bbox) {
1310
+ spatialIndex.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
1311
+ }
1312
+ }
1313
+ };
1314
+ /**
1315
+ * 선택 상태 동기화 (화면 밖 데이터도 선택 상태 유지)
1316
+ *
1317
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
1318
+ * @param data 최신 데이터 배열
1319
+ * @param selectedIds 선택된 항목 ID Set
1320
+ * @param selectedItemsMap 현재 선택된 항목 Map
1321
+ * @returns 업데이트된 선택된 항목 Map
1322
+ */
1290
1323
 
1291
- SpatialHashGrid.prototype.queryBounds = function (minX, minY, maxX, maxY) {
1292
- // 영역이 걸치는 모든 찾기
1293
- var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
1294
- var results = new Set(); // 중복 제거를 위해 Set 사용
1295
- // 셀의 모든 항목을 결과에 추가
1324
+ var syncSelectedItems = function (data, selectedIds, selectedItemsMap) {
1325
+ var dataMap = new Map(data.map(function (m) {
1326
+ return [m.id, m];
1327
+ }));
1328
+ var newSelectedItemsMap = new Map();
1329
+ selectedIds.forEach(function (id) {
1330
+ // 현재 data에 있으면 최신 데이터 사용
1331
+ var currentItem = dataMap.get(id);
1296
1332
 
1297
- for (var _i = 0, cells_2 = cells; _i < cells_2.length; _i++) {
1298
- var cell = cells_2[_i];
1299
- var items = this.grid.get(cell);
1333
+ if (currentItem) {
1334
+ newSelectedItemsMap.set(id, currentItem);
1335
+ } else {
1336
+ // 화면 밖이면 기존 데이터 유지
1337
+ var prevItem = selectedItemsMap.get(id);
1300
1338
 
1301
- if (items) {
1302
- for (var _a = 0, items_1 = items; _a < items_1.length; _a++) {
1303
- var item = items_1[_a];
1304
- results.add(item); // Set이므로 중복 자동 제거
1305
- }
1339
+ if (prevItem) {
1340
+ newSelectedItemsMap.set(id, prevItem);
1306
1341
  }
1307
1342
  }
1343
+ });
1344
+ return newSelectedItemsMap;
1345
+ };
1346
+ /**
1347
+ * 외부 selectedItems를 내부 상태로 동기화
1348
+ *
1349
+ * @template T 마커/폴리곤 데이터의 추가 속성 타입
1350
+ * @param externalSelectedItems 외부에서 전달된 선택된 항목 배열
1351
+ * @param selectedIdsRef 선택된 ID Set ref
1352
+ * @param selectedItemsMapRef 선택된 항목 Map ref
1353
+ */
1308
1354
 
1309
- return Array.from(results);
1310
- }; // 항목 존재 여부 확인
1311
-
1312
-
1313
- SpatialHashGrid.prototype.has = function (item) {
1314
- return this.itemToCells.has(item);
1315
- }; // 전체 초기화
1316
-
1317
-
1318
- SpatialHashGrid.prototype.clear = function () {
1319
- this.grid.clear();
1320
- this.itemToCells.clear();
1321
- }; // 통계 정보 반환
1322
-
1355
+ var syncExternalSelectedItems = function (externalSelectedItems, selectedIdsRef, selectedItemsMapRef) {
1356
+ if (externalSelectedItems === undefined) return;
1357
+ var newSelectedIds = new Set();
1358
+ var newSelectedItemsMap = new Map();
1359
+ externalSelectedItems.forEach(function (item) {
1360
+ newSelectedIds.add(item.id);
1361
+ newSelectedItemsMap.set(item.id, item);
1362
+ }); // eslint-disable-next-line no-param-reassign
1323
1363
 
1324
- SpatialHashGrid.prototype.stats = function () {
1325
- var totalCellItems = 0;
1326
- this.grid.forEach(function (items) {
1327
- totalCellItems += items.length;
1328
- });
1329
- return {
1330
- // 고유 항목 수 (정확)
1331
- avgItemsPerCell: this.grid.size > 0 ? totalCellItems / this.grid.size : 0,
1332
- totalCells: this.grid.size,
1333
- totalItems: this.itemToCells.size
1334
- };
1335
- };
1364
+ selectedIdsRef.current = newSelectedIds; // eslint-disable-next-line no-param-reassign
1336
1365
 
1337
- return SpatialHashGrid;
1338
- }();
1366
+ selectedItemsMapRef.current = newSelectedItemsMap;
1367
+ };
1339
1368
 
1340
1369
  var Drawable =
1341
1370
  /** @class */
@@ -3954,13 +3983,12 @@ var CanvasMarkerLayer = function (props) {
3954
3983
  var data = props.data,
3955
3984
  _a = props.cullingMargin,
3956
3985
  cullingMargin = _a === void 0 ? DEFAULT_CULLING_MARGIN : _a,
3957
- _b = props.maxCacheSize,
3958
- maxCacheSize = _b === void 0 ? DEFAULT_MAX_CACHE_SIZE : _b,
3986
+ onOptimizationDataUpdate = props.onOptimizationDataUpdate,
3959
3987
  renderBase = props.renderBase,
3960
- options = __rest(props, ["data", "cullingMargin", "maxCacheSize", "renderBase"]); // renderEvent가 있는 경우에만 인터랙션 관련 props 추출
3988
+ options = __rest(props, ["data", "cullingMargin", "onOptimizationDataUpdate", "renderBase"]); // renderEvent가 있는 경우에만 인터랙션 관련 props 추출
3961
3989
 
3962
3990
 
3963
- var _c = hasRenderEvent && 'renderEvent' in props ? props : {
3991
+ var _b = hasRenderEvent && 'renderEvent' in props ? props : {
3964
3992
  disableInteraction: false,
3965
3993
  onClick: undefined,
3966
3994
  onMouseOut: undefined,
@@ -3970,15 +3998,15 @@ var CanvasMarkerLayer = function (props) {
3970
3998
  selectedItems: undefined,
3971
3999
  topStageZIndex: undefined
3972
4000
  },
3973
- _d = _c.disableInteraction,
3974
- disableInteraction = _d === void 0 ? false : _d,
3975
- onClick = _c.onClick,
3976
- onMouseOut = _c.onMouseOut,
3977
- onMouseOver = _c.onMouseOver,
3978
- renderEvent = _c.renderEvent,
3979
- externalSelectedItem = _c.selectedItem,
3980
- externalSelectedItems = _c.selectedItems,
3981
- rawTopStageZIndex = _c.topStageZIndex; // topStageZIndex가 있으면 hover 최상단 표시 활성화, 없으면 비활성화 (성능 우선)
4001
+ _c = _b.disableInteraction,
4002
+ disableInteraction = _c === void 0 ? false : _c,
4003
+ onClick = _b.onClick,
4004
+ onMouseOut = _b.onMouseOut,
4005
+ onMouseOver = _b.onMouseOver,
4006
+ renderEvent = _b.renderEvent,
4007
+ externalSelectedItem = _b.selectedItem,
4008
+ externalSelectedItems = _b.selectedItems,
4009
+ rawTopStageZIndex = _b.topStageZIndex; // topStageZIndex가 있으면 hover 최상단 표시 활성화, 없으면 비활성화 (성능 우선)
3982
4010
 
3983
4011
 
3984
4012
  var topStageZIndex = rawTopStageZIndex;
@@ -4016,24 +4044,30 @@ var CanvasMarkerLayer = function (props) {
4016
4044
  y: 0
4017
4045
  }); // 드래그 시작 시점의 hover 상태 저장 (드래그 중 hover 고정용)
4018
4046
 
4019
- var dragStartHoveredItemRef = useRef(null); // 성능 최적화 Refs
4047
+ var dragStartHoveredItemRef = useRef(null); // 공유 캐시 사용 (모든 마커 레이어가 공유)
4048
+
4049
+ var sharedMarkerCache = context === null || context === void 0 ? void 0 : context.sharedMarkerCache;
4050
+
4051
+ if (!sharedMarkerCache) {
4052
+ throw new Error('CanvasMarkerLayer must be used within CanvasProvider');
4053
+ } // 성능 최적화 Refs
4054
+
4020
4055
 
4021
- var offsetCacheRef = useRef(new QueueCache(maxCacheSize));
4022
4056
  var spatialIndexRef = useRef(new SpatialHashGrid(SPATIAL_GRID_CELL_SIZE));
4023
4057
  var boundingBoxCacheRef = useRef(new Map());
4024
4058
  var viewportRef = useRef(null); // 뷰포트 영역 계산 (Viewport Culling용)
4025
4059
 
4026
4060
  var updateViewport$1 = function () {
4027
4061
  updateViewport(stageRef.current, cullingMargin, viewportRef);
4028
- }; // 마커 좌표 변환 (위경도 → 화면 좌표, Queue 캐시 사용)
4062
+ }; // 마커 좌표 변환 (위경도 → 화면 좌표, 공유 캐시 사용)
4029
4063
 
4030
4064
 
4031
4065
  var getOrComputeMarkerOffset = function (markerData) {
4032
- var cached = offsetCacheRef.current.get(markerData.id);
4033
- if (cached && !Array.isArray(cached)) return cached;
4066
+ var cached = sharedMarkerCache.get(markerData.id);
4067
+ if (cached) return cached;
4034
4068
  var result = computeMarkerOffset(markerData, controller);
4035
4069
  if (!result) return null;
4036
- offsetCacheRef.current.set(markerData.id, result);
4070
+ sharedMarkerCache.set(markerData.id, result);
4037
4071
  return result;
4038
4072
  }; // 마커 바운딩 박스 계산 (Viewport Culling 및 Hit Test용, 오프셋 지원)
4039
4073
 
@@ -4230,6 +4264,24 @@ var CanvasMarkerLayer = function (props) {
4230
4264
  }
4231
4265
  });
4232
4266
  layer.batchDraw();
4267
+ }; // 최적화 데이터 업데이트 콜백 호출
4268
+
4269
+
4270
+ var notifyOptimizationData = function () {
4271
+ if (!onOptimizationDataUpdate) return; // 공유 캐시에서 마커 타입 항목만 필터링 (Offset 타입만)
4272
+
4273
+ var allCacheEntries = sharedMarkerCache.getAllEntries();
4274
+ var cacheEntries = allCacheEntries.map(function (_a) {
4275
+ var key = _a[0],
4276
+ value = _a[1];
4277
+ return [key, value];
4278
+ });
4279
+ var spatialGridCells = spatialIndexRef.current.getAllCells();
4280
+ onOptimizationDataUpdate({
4281
+ cacheEntries: cacheEntries,
4282
+ cacheSize: sharedMarkerCache.size(),
4283
+ spatialGridCells: spatialGridCells
4284
+ });
4233
4285
  }; // 전체 즉시 렌더링
4234
4286
 
4235
4287
 
@@ -4245,27 +4297,32 @@ var CanvasMarkerLayer = function (props) {
4245
4297
 
4246
4298
  if (topStageZIndex !== undefined) {
4247
4299
  doRenderTop();
4248
- }
4300
+ } // 최적화 데이터 업데이트 콜백 호출
4301
+
4302
+
4303
+ notifyOptimizationData();
4249
4304
  }; // 지도 이벤트 핸들러 생성
4250
4305
 
4251
4306
 
4252
- var _e = createMapEventHandlers({
4307
+ var _d = createMapEventHandlers({
4253
4308
  accumTranslateRef: accumTranslateRef,
4254
4309
  boundingBoxCacheRef: boundingBoxCacheRef,
4310
+ clearCache: function () {
4311
+ return sharedMarkerCache.clear();
4312
+ },
4255
4313
  containerRef: containerRef,
4256
4314
  controller: controller,
4257
4315
  markerRef: markerRef,
4258
- offsetCacheRef: offsetCacheRef,
4259
4316
  options: options,
4260
4317
  prevCenterOffsetRef: prevCenterOffsetRef,
4261
4318
  renderAllImmediate: renderAllImmediate
4262
4319
  }),
4263
- handleIdleShared = _e.handleIdle,
4264
- handleZoomStart = _e.handleZoomStart,
4265
- handleZoomEnd = _e.handleZoomEnd,
4266
- handleCenterChangedShared = _e.handleCenterChanged,
4267
- handleDragStartShared = _e.handleDragStart,
4268
- handleDragEndShared = _e.handleDragEnd; // handleIdle 래핑: topStage transform 제거 추가
4320
+ handleIdleShared = _d.handleIdle,
4321
+ handleZoomStart = _d.handleZoomStart,
4322
+ handleZoomEnd = _d.handleZoomEnd,
4323
+ handleCenterChangedShared = _d.handleCenterChanged,
4324
+ handleDragStartShared = _d.handleDragStart,
4325
+ handleDragEndShared = _d.handleDragEnd; // handleIdle 래핑: topStage transform 제거 추가
4269
4326
 
4270
4327
 
4271
4328
  var handleIdle = function () {
@@ -4530,7 +4587,7 @@ var CanvasMarkerLayer = function (props) {
4530
4587
  resizeRafId = requestAnimationFrame(function () {
4531
4588
  stage.width(mapDiv.offsetWidth);
4532
4589
  stage.height(mapDiv.offsetHeight);
4533
- offsetCacheRef.current.clear();
4590
+ sharedMarkerCache.clear();
4534
4591
  boundingBoxCacheRef.current.clear();
4535
4592
  updateViewport$1();
4536
4593
  renderAllImmediate();
@@ -4605,8 +4662,8 @@ var CanvasMarkerLayer = function (props) {
4605
4662
 
4606
4663
  baseLayer.destroyChildren();
4607
4664
  eventLayer.destroyChildren();
4608
- stage.destroy();
4609
- offsetCacheRef.current.clear();
4665
+ stage.destroy(); // 공유 캐시는 cleanup 시 clear하지 않음 (다른 레이어가 사용 중일 수 있음)
4666
+
4610
4667
  boundingBoxCacheRef.current.clear();
4611
4668
  spatialIndexRef.current.clear();
4612
4669
  };
@@ -4822,7 +4879,7 @@ var CanvasMarkerLayer = function (props) {
4822
4879
  x: 0,
4823
4880
  y: 0
4824
4881
  };
4825
- offsetCacheRef.current.clear();
4882
+ sharedMarkerCache.clear();
4826
4883
  boundingBoxCacheRef.current.clear();
4827
4884
  selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
4828
4885
  renderAllImmediate();
@@ -5922,18 +5979,17 @@ var renderPolygonEventWithHybridObject = function (customStyle, renderStyle) {
5922
5979
 
5923
5980
  var CanvasPolygonLayer = function (props) {
5924
5981
  var data = props.data,
5925
- onClick = props.onClick,
5926
5982
  _a = props.enableMultiSelect,
5927
5983
  enableMultiSelect = _a === void 0 ? false : _a,
5928
5984
  _b = props.cullingMargin,
5929
5985
  cullingMargin = _b === void 0 ? DEFAULT_CULLING_MARGIN : _b,
5930
- _c = props.maxCacheSize,
5931
- maxCacheSize = _c === void 0 ? DEFAULT_MAX_CACHE_SIZE : _c,
5986
+ onClick = props.onClick,
5987
+ onOptimizationDataUpdate = props.onOptimizationDataUpdate,
5932
5988
  externalSelectedItems = props.selectedItems,
5933
5989
  externalSelectedItem = props.selectedItem,
5934
- _d = props.disableInteraction,
5935
- disableInteraction = _d === void 0 ? false : _d,
5936
- options = __rest(props, ["data", "onClick", "enableMultiSelect", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction"]); // customStyle은 다른 방식과 함께 사용 가능
5990
+ _c = props.disableInteraction,
5991
+ disableInteraction = _c === void 0 ? false : _c,
5992
+ options = __rest(props, ["data", "enableMultiSelect", "cullingMargin", "onClick", "onOptimizationDataUpdate", "selectedItems", "selectedItem", "disableInteraction"]); // customStyle은 다른 방식과 함께 사용 가능
5937
5993
 
5938
5994
 
5939
5995
  var hasCustomStyle = 'customStyle' in props && props.customStyle !== undefined;
@@ -5976,19 +6032,25 @@ var CanvasPolygonLayer = function (props) {
5976
6032
  y: 0
5977
6033
  }); // 드래그 시작 시점의 hover 상태 저장 (드래그 중 hover 고정용)
5978
6034
 
5979
- var dragStartHoveredItemRef = useRef(null); // 성능 최적화 Refs
6035
+ var dragStartHoveredItemRef = useRef(null); // 공유 캐시 사용 (모든 폴리곤 레이어가 공유)
6036
+
6037
+ var sharedPolygonCache = context === null || context === void 0 ? void 0 : context.sharedPolygonCache;
6038
+
6039
+ if (!sharedPolygonCache) {
6040
+ throw new Error('CanvasPolygonLayer must be used within CanvasProvider');
6041
+ } // 성능 최적화 Refs
6042
+
5980
6043
 
5981
- var offsetCacheRef = useRef(new QueueCache(maxCacheSize));
5982
6044
  var spatialIndexRef = useRef(new SpatialHashGrid(SPATIAL_GRID_CELL_SIZE));
5983
6045
  var boundingBoxCacheRef = useRef(new Map());
5984
- var viewportRef = useRef(null); // 폴리곤 좌표 변환 (위경도 → 화면 좌표, Queue 캐시 사용)
6046
+ var viewportRef = useRef(null); // 폴리곤 좌표 변환 (위경도 → 화면 좌표, 공유 캐시 사용)
5985
6047
 
5986
6048
  var getOrComputePolygonOffsets = function (polygonData) {
5987
- var cached = offsetCacheRef.current.get(polygonData.id);
5988
- if (cached && Array.isArray(cached)) return cached;
6049
+ var cached = sharedPolygonCache.get(polygonData.id);
6050
+ if (cached) return cached;
5989
6051
  var result = computePolygonOffsets(polygonData, controller);
5990
6052
  if (!result) return null;
5991
- offsetCacheRef.current.set(polygonData.id, result);
6053
+ sharedPolygonCache.set(polygonData.id, result);
5992
6054
  return result;
5993
6055
  }; // 폴리곤 바운딩 박스 계산 (Viewport Culling 및 Hit Test용)
5994
6056
 
@@ -6147,6 +6209,24 @@ var CanvasPolygonLayer = function (props) {
6147
6209
  }
6148
6210
 
6149
6211
  layer.batchDraw();
6212
+ }; // 최적화 데이터 업데이트 콜백 호출
6213
+
6214
+
6215
+ var notifyOptimizationData = function () {
6216
+ if (!onOptimizationDataUpdate) return; // 공유 캐시에서 폴리곤 타입 항목만 필터링 (number[][][][] 타입만)
6217
+
6218
+ var allCacheEntries = sharedPolygonCache.getAllEntries();
6219
+ var cacheEntries = allCacheEntries.map(function (_a) {
6220
+ var key = _a[0],
6221
+ value = _a[1];
6222
+ return [key, value];
6223
+ });
6224
+ var spatialGridCells = spatialIndexRef.current.getAllCells();
6225
+ onOptimizationDataUpdate({
6226
+ cacheEntries: cacheEntries,
6227
+ cacheSize: sharedPolygonCache.size(),
6228
+ spatialGridCells: spatialGridCells
6229
+ });
6150
6230
  }; // 전체 즉시 렌더링
6151
6231
 
6152
6232
 
@@ -6154,27 +6234,31 @@ var CanvasPolygonLayer = function (props) {
6154
6234
  updateViewport$1();
6155
6235
  buildSpatialIndex$1();
6156
6236
  doRenderBase();
6157
- doRenderEvent();
6237
+ doRenderEvent(); // 최적화 데이터 업데이트 콜백 호출
6238
+
6239
+ notifyOptimizationData();
6158
6240
  }; // 지도 이벤트 핸들러 생성
6159
6241
 
6160
6242
 
6161
- var _e = createMapEventHandlers({
6243
+ var _d = createMapEventHandlers({
6162
6244
  accumTranslateRef: accumTranslateRef,
6163
6245
  boundingBoxCacheRef: boundingBoxCacheRef,
6246
+ clearCache: function () {
6247
+ return sharedPolygonCache.clear();
6248
+ },
6164
6249
  containerRef: containerRef,
6165
6250
  controller: controller,
6166
6251
  markerRef: markerRef,
6167
- offsetCacheRef: offsetCacheRef,
6168
6252
  options: options,
6169
6253
  prevCenterOffsetRef: prevCenterOffsetRef,
6170
6254
  renderAllImmediate: renderAllImmediate
6171
6255
  }),
6172
- handleIdle = _e.handleIdle,
6173
- handleZoomStart = _e.handleZoomStart,
6174
- handleZoomEnd = _e.handleZoomEnd,
6175
- handleCenterChanged = _e.handleCenterChanged,
6176
- handleDragStartShared = _e.handleDragStart,
6177
- handleDragEndShared = _e.handleDragEnd;
6256
+ handleIdle = _d.handleIdle,
6257
+ handleZoomStart = _d.handleZoomStart,
6258
+ handleZoomEnd = _d.handleZoomEnd,
6259
+ handleCenterChanged = _d.handleCenterChanged,
6260
+ handleDragStartShared = _d.handleDragStart,
6261
+ handleDragEndShared = _d.handleDragEnd;
6178
6262
 
6179
6263
  var handleDragStart = function () {
6180
6264
  handleDragStartShared(); // 드래그 시작 시점의 hover 상태 저장
@@ -6358,7 +6442,7 @@ var CanvasPolygonLayer = function (props) {
6358
6442
  resizeRafId = requestAnimationFrame(function () {
6359
6443
  stage.width(mapDiv.offsetWidth);
6360
6444
  stage.height(mapDiv.offsetHeight);
6361
- offsetCacheRef.current.clear();
6445
+ sharedPolygonCache.clear();
6362
6446
  boundingBoxCacheRef.current.clear();
6363
6447
  updateViewport$1();
6364
6448
  renderAllImmediate();
@@ -6421,8 +6505,8 @@ var CanvasPolygonLayer = function (props) {
6421
6505
 
6422
6506
  baseLayer.destroyChildren();
6423
6507
  eventLayer.destroyChildren();
6424
- stage.destroy();
6425
- offsetCacheRef.current.clear();
6508
+ stage.destroy(); // 공유 캐시는 cleanup 시 clear하지 않음 (다른 레이어가 사용 중일 수 있음)
6509
+
6426
6510
  boundingBoxCacheRef.current.clear();
6427
6511
  spatialIndexRef.current.clear();
6428
6512
  };
@@ -6458,7 +6542,7 @@ var CanvasPolygonLayer = function (props) {
6458
6542
  x: 0,
6459
6543
  y: 0
6460
6544
  };
6461
- offsetCacheRef.current.clear();
6545
+ sharedPolygonCache.clear();
6462
6546
  boundingBoxCacheRef.current.clear();
6463
6547
  selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
6464
6548
  renderAllImmediate();