@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/components/mint-map/core/advanced/CanvasMarkerLayer/CanvasMarkerLayer.js +60 -32
- package/dist/components/mint-map/core/advanced/CanvasMarkerLayer/types.d.ts +43 -1
- package/dist/components/mint-map/core/advanced/CanvasPolygonLayer/CanvasPolygonLayer.js +52 -25
- package/dist/components/mint-map/core/advanced/CanvasPolygonLayer/types.d.ts +7 -1
- package/dist/components/mint-map/core/advanced/shared/context.d.ts +5 -0
- package/dist/components/mint-map/core/advanced/shared/context.js +12 -2
- package/dist/components/mint-map/core/advanced/shared/hooks.d.ts +1 -1
- package/dist/components/mint-map/core/advanced/shared/hooks.js +2 -2
- package/dist/components/mint-map/core/advanced/shared/performance.d.ts +8 -4
- package/dist/components/mint-map/core/advanced/shared/performance.js +24 -4
- package/dist/index.es.js +678 -594
- package/dist/index.umd.js +678 -594
- package/package.json +1 -1
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
|
-
|
|
653
|
+
/* eslint-disable max-classes-per-file */
|
|
654
|
+
|
|
655
|
+
/* eslint-disable no-restricted-syntax */
|
|
656
|
+
|
|
654
657
|
/**
|
|
655
|
-
*
|
|
658
|
+
* 공간 인덱스 그리드 셀 크기 (픽셀 단위)
|
|
656
659
|
*
|
|
657
|
-
*
|
|
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
|
-
|
|
683
|
+
var DEFAULT_CULLING_MARGIN = 100;
|
|
684
|
+
/**
|
|
685
|
+
* Queue 캐시 최대 항목 수
|
|
686
|
+
*
|
|
687
|
+
* @default 30000
|
|
688
|
+
*/
|
|
661
689
|
|
|
662
|
-
|
|
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
|
|
665
|
-
|
|
666
|
-
|
|
701
|
+
var QueueCache =
|
|
702
|
+
/** @class */
|
|
703
|
+
function () {
|
|
704
|
+
function QueueCache(maxSize) {
|
|
705
|
+
if (maxSize === void 0) {
|
|
706
|
+
maxSize = 10000;
|
|
707
|
+
}
|
|
667
708
|
|
|
668
|
-
|
|
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
|
-
|
|
673
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
});
|
|
691
|
-
}, []); // 전역 클릭 핸들러 (zIndex 우선순위)
|
|
727
|
+
if (this.cache.size >= this.maxSize) {
|
|
728
|
+
var firstKey = this.cache.keys().next().value;
|
|
692
729
|
|
|
693
|
-
|
|
694
|
-
|
|
730
|
+
if (firstKey !== undefined) {
|
|
731
|
+
this.cache.delete(firstKey);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
695
734
|
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
733
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
759
|
+
QueueCache.prototype.getAllEntries = function () {
|
|
760
|
+
return Array.from(this.cache.entries());
|
|
761
|
+
};
|
|
749
762
|
|
|
750
|
-
|
|
763
|
+
return QueueCache;
|
|
764
|
+
}();
|
|
765
|
+
/**
|
|
766
|
+
* Spatial Hash Grid (공간 해시 그리드)
|
|
767
|
+
*
|
|
768
|
+
* 빠른 Hit Test를 위한 그리드 기반 공간 인덱싱 자료구조
|
|
769
|
+
*
|
|
770
|
+
* @template T 인덱싱할 항목 타입
|
|
771
|
+
*/
|
|
751
772
|
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
873
|
-
|
|
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
|
-
|
|
876
|
-
|
|
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
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
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
|
-
|
|
917
|
-
|
|
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
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
|
|
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
|
-
|
|
948
|
-
|
|
949
|
-
containerRef.current.style.visibility = 'hidden';
|
|
950
|
-
}; // 줌 종료 시 처리 (다시 표시)
|
|
891
|
+
return Array.from(results);
|
|
892
|
+
}; // 항목 존재 여부 확인
|
|
951
893
|
|
|
952
894
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}; // 지도 중심 변경 시 처리 (transform으로 이동 추적, 캐시 유지)
|
|
895
|
+
SpatialHashGrid.prototype.has = function (item) {
|
|
896
|
+
return this.itemToCells.has(item);
|
|
897
|
+
}; // 전체 초기화
|
|
957
898
|
|
|
958
899
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
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
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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
|
-
|
|
925
|
+
SpatialHashGrid.prototype.getCellItems = function (cellKey) {
|
|
926
|
+
return this.grid.get(cellKey) || [];
|
|
991
927
|
};
|
|
992
928
|
|
|
993
|
-
|
|
994
|
-
|
|
929
|
+
return SpatialHashGrid;
|
|
930
|
+
}();
|
|
995
931
|
|
|
996
|
-
|
|
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
|
-
*
|
|
934
|
+
* CanvasProvider 컴포넌트
|
|
1007
935
|
*
|
|
1008
|
-
*
|
|
1009
|
-
* @param data 공간 인덱스에 삽입할 데이터 배열
|
|
1010
|
-
* @param spatialIndex Spatial Hash Grid 인스턴스
|
|
1011
|
-
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
936
|
+
* 다중 Canvas 인스턴스를 관리하고 zIndex 기반 이벤트 우선순위를 처리합니다.
|
|
1012
937
|
*/
|
|
1013
938
|
|
|
1014
|
-
|
|
1015
|
-
spatialIndex.clear();
|
|
939
|
+
/* eslint-disable no-restricted-syntax */
|
|
1016
940
|
|
|
1017
|
-
|
|
1018
|
-
var item = data_1[_i];
|
|
1019
|
-
var bbox = computeBoundingBox(item);
|
|
941
|
+
/* eslint-disable no-continue */
|
|
1020
942
|
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1052
|
-
|
|
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
|
|
1068
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1079
|
-
|
|
968
|
+
var unregisterComponent = useCallback(function (instance) {
|
|
969
|
+
if (currentHoveredRef.current === instance) {
|
|
970
|
+
currentHoveredRef.current = null;
|
|
971
|
+
currentHoveredDataRef.current = null;
|
|
972
|
+
}
|
|
1080
973
|
|
|
1081
|
-
|
|
974
|
+
componentsRef.current = componentsRef.current.filter(function (c) {
|
|
975
|
+
return c !== instance;
|
|
976
|
+
});
|
|
977
|
+
}, []); // 전역 클릭 핸들러 (zIndex 우선순위)
|
|
1082
978
|
|
|
1083
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1138
|
-
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
|
|
1148
|
-
var exists = this.cache.has(key);
|
|
1034
|
+
var newHoveredComponent = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1149
1035
|
|
|
1150
|
-
|
|
1151
|
-
this.cache.set(key, value);
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1036
|
+
var newHoveredData = null; // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
|
|
1154
1037
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1038
|
+
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
1039
|
+
var component = _b[_i];
|
|
1157
1040
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1041
|
+
try {
|
|
1042
|
+
if (component.isInteractionDisabled()) continue;
|
|
1043
|
+
var data = component.findData(mouseOffset);
|
|
1044
|
+
if (!data) continue; // 첫 번째로 찾은 항목만 hover 처리 (zIndex 우선순위)
|
|
1162
1045
|
|
|
1163
|
-
|
|
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
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1057
|
+
if (currentHoveredRef.current === newHoveredComponent && currentHoveredDataRef.current === newHoveredData) {
|
|
1058
|
+
return;
|
|
1059
|
+
} // 기존 hover 항목에 mouseOut 이벤트 발생
|
|
1170
1060
|
|
|
1171
1061
|
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1076
|
+
if (newHoveredComponent && newHoveredData) {
|
|
1077
|
+
try {
|
|
1078
|
+
newHoveredComponent.setHovered(newHoveredData);
|
|
1180
1079
|
|
|
1181
|
-
|
|
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
|
-
*
|
|
1139
|
+
* Canvas Context Hook
|
|
1185
1140
|
*
|
|
1186
|
-
*
|
|
1141
|
+
* @returns CanvasContextValue 또는 null (Provider 없으면)
|
|
1142
|
+
*/
|
|
1143
|
+
|
|
1144
|
+
var useCanvasContext = function () {
|
|
1145
|
+
return useContext(CanvasContext);
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* 이벤트 유효성 검증 및 좌표 변환
|
|
1187
1150
|
*
|
|
1188
|
-
* @
|
|
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
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1214
|
-
|
|
1211
|
+
clearCache();
|
|
1212
|
+
boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
|
|
1215
1213
|
|
|
1216
|
-
var
|
|
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
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1216
|
+
var markerOptions = __assign(__assign({}, options), {
|
|
1217
|
+
position: bounds.nw
|
|
1218
|
+
});
|
|
1226
1219
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1220
|
+
if (markerRef.current) {
|
|
1221
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
1222
|
+
} // transform 제거 전에 새 데이터로 즉시 렌더링 (transform 제거 시 잠깐 빈 화면이 보이는 것 방지)
|
|
1229
1223
|
|
|
1230
1224
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
-
|
|
1239
|
-
|
|
1231
|
+
renderAllImmediate();
|
|
1232
|
+
}; // 줌 시작 시 처리 (일시적으로 숨김)
|
|
1240
1233
|
|
|
1241
|
-
if (!this.grid.has(cell)) {
|
|
1242
|
-
this.grid.set(cell, []);
|
|
1243
|
-
}
|
|
1244
1234
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1235
|
+
var handleZoomStart = function () {
|
|
1236
|
+
if (!containerRef.current) return;
|
|
1237
|
+
containerRef.current.style.visibility = 'hidden';
|
|
1238
|
+
}; // 줌 종료 시 처리 (다시 표시)
|
|
1248
1239
|
|
|
1249
1240
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1247
|
+
var handleCenterChanged = function () {
|
|
1248
|
+
var center = controller.getCurrBounds().getCenter();
|
|
1249
|
+
var curr = controller.positionToOffset(center);
|
|
1250
|
+
var prev = prevCenterOffsetRef.current; // 첫 번째 호출 시 이전 위치 저장만 하고 종료
|
|
1260
1251
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1252
|
+
if (!prev) {
|
|
1253
|
+
prevCenterOffsetRef.current = {
|
|
1254
|
+
x: curr.x,
|
|
1255
|
+
y: curr.y
|
|
1256
|
+
};
|
|
1257
|
+
return;
|
|
1258
|
+
} // 이전 위치와 현재 위치의 차이 계산 (이동 거리)
|
|
1264
1259
|
|
|
1265
1260
|
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1278
|
-
|
|
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
|
-
|
|
1283
|
-
|
|
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
|
-
|
|
1288
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1333
|
+
if (currentItem) {
|
|
1334
|
+
newSelectedItemsMap.set(id, currentItem);
|
|
1335
|
+
} else {
|
|
1336
|
+
// 화면 밖이면 기존 데이터 유지
|
|
1337
|
+
var prevItem = selectedItemsMap.get(id);
|
|
1300
1338
|
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", "
|
|
3988
|
+
options = __rest(props, ["data", "cullingMargin", "onOptimizationDataUpdate", "renderBase"]); // renderEvent가 있는 경우에만 인터랙션 관련 props 추출
|
|
3961
3989
|
|
|
3962
3990
|
|
|
3963
|
-
var
|
|
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
|
-
|
|
3974
|
-
disableInteraction =
|
|
3975
|
-
onClick =
|
|
3976
|
-
onMouseOut =
|
|
3977
|
-
onMouseOver =
|
|
3978
|
-
renderEvent =
|
|
3979
|
-
externalSelectedItem =
|
|
3980
|
-
externalSelectedItems =
|
|
3981
|
-
rawTopStageZIndex =
|
|
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); //
|
|
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
|
-
}; // 마커 좌표 변환 (위경도 → 화면 좌표,
|
|
4062
|
+
}; // 마커 좌표 변환 (위경도 → 화면 좌표, 공유 캐시 사용)
|
|
4029
4063
|
|
|
4030
4064
|
|
|
4031
4065
|
var getOrComputeMarkerOffset = function (markerData) {
|
|
4032
|
-
var cached =
|
|
4033
|
-
if (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
|
-
|
|
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
|
|
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 =
|
|
4264
|
-
handleZoomStart =
|
|
4265
|
-
handleZoomEnd =
|
|
4266
|
-
handleCenterChangedShared =
|
|
4267
|
-
handleDragStartShared =
|
|
4268
|
-
handleDragEndShared =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5931
|
-
|
|
5986
|
+
onClick = props.onClick,
|
|
5987
|
+
onOptimizationDataUpdate = props.onOptimizationDataUpdate,
|
|
5932
5988
|
externalSelectedItems = props.selectedItems,
|
|
5933
5989
|
externalSelectedItem = props.selectedItem,
|
|
5934
|
-
|
|
5935
|
-
disableInteraction =
|
|
5936
|
-
options = __rest(props, ["data", "
|
|
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); //
|
|
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); // 폴리곤 좌표 변환 (위경도 → 화면 좌표,
|
|
6046
|
+
var viewportRef = useRef(null); // 폴리곤 좌표 변환 (위경도 → 화면 좌표, 공유 캐시 사용)
|
|
5985
6047
|
|
|
5986
6048
|
var getOrComputePolygonOffsets = function (polygonData) {
|
|
5987
|
-
var cached =
|
|
5988
|
-
if (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
|
-
|
|
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
|
|
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 =
|
|
6173
|
-
handleZoomStart =
|
|
6174
|
-
handleZoomEnd =
|
|
6175
|
-
handleCenterChanged =
|
|
6176
|
-
handleDragStartShared =
|
|
6177
|
-
handleDragEndShared =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6545
|
+
sharedPolygonCache.clear();
|
|
6462
6546
|
boundingBoxCacheRef.current.clear();
|
|
6463
6547
|
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
6464
6548
|
renderAllImmediate();
|