@mint-ui/map 1.2.0-test.4 → 1.2.0-test.40
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/MintMapCore.js +5 -6
- package/dist/components/mint-map/core/advanced/index.d.ts +2 -1
- package/dist/components/mint-map/core/advanced/shared/context.d.ts +39 -0
- package/dist/components/mint-map/core/advanced/{woongCanvas/shared → shared}/context.js +62 -79
- package/dist/components/mint-map/core/advanced/shared/helpers.d.ts +20 -0
- package/dist/components/mint-map/core/advanced/shared/helpers.js +40 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.d.ts +74 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.js +189 -0
- package/dist/components/mint-map/core/advanced/{woongCanvas/shared → shared}/index.d.ts +3 -0
- package/dist/components/mint-map/core/advanced/shared/performance.d.ts +77 -0
- package/dist/components/mint-map/core/advanced/shared/performance.js +262 -0
- package/dist/components/mint-map/core/advanced/shared/types.d.ts +111 -0
- package/dist/components/mint-map/core/advanced/{woongCanvas/shared → shared}/types.js +0 -1
- package/dist/components/mint-map/core/advanced/shared/utils.d.ts +62 -0
- package/dist/components/mint-map/core/advanced/shared/utils.js +221 -0
- package/dist/components/mint-map/core/advanced/shared/viewport.d.ts +42 -0
- package/dist/components/mint-map/core/advanced/shared/viewport.js +51 -0
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.d.ts +47 -0
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.js +620 -0
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/index.d.ts +3 -0
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.d.ts +61 -0
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.js +582 -0
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/index.d.ts +3 -0
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.d.ts +120 -0
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.js +295 -0
- package/dist/components/mint-map/google/GoogleMintMapController.js +5 -4
- package/dist/components/mint-map/kakao/KakaoMintMapController.js +5 -4
- package/dist/components/mint-map/naver/NaverMintMapController.js +5 -4
- package/dist/index.es.js +1711 -1056
- package/dist/index.js +23 -8
- package/dist/index.umd.js +1723 -1057
- package/package.json +1 -1
- package/dist/components/mint-map/core/advanced/woongCanvas/ClusterMarker.d.ts +0 -11
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.d.ts +0 -50
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.js +0 -1065
- package/dist/components/mint-map/core/advanced/woongCanvas/index.d.ts +0 -3
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.d.ts +0 -31
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.d.ts +0 -161
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.js +0 -343
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +0 -131
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +0 -23
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.js +0 -115
package/dist/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __extends, __awaiter, __generator, __spreadArray,
|
|
1
|
+
import { __extends, __assign, __awaiter, __generator, __spreadArray, __rest } from 'tslib';
|
|
2
2
|
import React, { createContext, useContext, useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import classNames from 'classnames/bind';
|
|
4
4
|
import styleInject from 'style-inject';
|
|
@@ -652,7 +652,6 @@ styleInject(css_248z$1);
|
|
|
652
652
|
|
|
653
653
|
/**
|
|
654
654
|
* 캔버스 데이터 타입 Enum
|
|
655
|
-
* 마커인지 폴리곤인지 구분하는 상수
|
|
656
655
|
*/
|
|
657
656
|
var CanvasDataType;
|
|
658
657
|
|
|
@@ -795,7 +794,11 @@ function () {
|
|
|
795
794
|
}();
|
|
796
795
|
|
|
797
796
|
/**
|
|
798
|
-
* 폴리곤
|
|
797
|
+
* 폴리곤 좌표 변환 (위경도 → 화면 좌표)
|
|
798
|
+
*
|
|
799
|
+
* @param polygonData 폴리곤 데이터
|
|
800
|
+
* @param controller MintMapController 인스턴스
|
|
801
|
+
* @returns 변환된 화면 좌표 배열 (4차원 배열) 또는 null
|
|
799
802
|
*/
|
|
800
803
|
|
|
801
804
|
var computePolygonOffsets = function (polygonData, controller) {
|
|
@@ -805,7 +808,7 @@ var computePolygonOffsets = function (polygonData, controller) {
|
|
|
805
808
|
return null;
|
|
806
809
|
}
|
|
807
810
|
|
|
808
|
-
var result = [];
|
|
811
|
+
var result = []; // GeoJSON MultiPolygon 구조: [MultiPolygon][PolygonGroup][Coordinate][lng, lat]
|
|
809
812
|
|
|
810
813
|
for (var _i = 0, _a = paths.coordinates; _i < _a.length; _i++) {
|
|
811
814
|
var multiPolygon = _a[_i];
|
|
@@ -816,7 +819,8 @@ var computePolygonOffsets = function (polygonData, controller) {
|
|
|
816
819
|
var polygonOffsets = [];
|
|
817
820
|
|
|
818
821
|
for (var _c = 0, polygonGroup_1 = polygonGroup; _c < polygonGroup_1.length; _c++) {
|
|
819
|
-
var coord = polygonGroup_1[_c];
|
|
822
|
+
var coord = polygonGroup_1[_c]; // GeoJSON은 [lng, lat] 순서이지만 Position은 [lat, lng] 순서
|
|
823
|
+
|
|
820
824
|
var pos = new Position(coord[1], coord[0]);
|
|
821
825
|
var offset = controller.positionToOffset(pos);
|
|
822
826
|
polygonOffsets.push([offset.x, offset.y]);
|
|
@@ -831,47 +835,97 @@ var computePolygonOffsets = function (polygonData, controller) {
|
|
|
831
835
|
return result;
|
|
832
836
|
};
|
|
833
837
|
/**
|
|
834
|
-
* 마커
|
|
838
|
+
* 마커 좌표 변환 (위경도 → 화면 좌표)
|
|
839
|
+
*
|
|
840
|
+
* @param markerData 마커 데이터
|
|
841
|
+
* @param controller MintMapController 인스턴스
|
|
842
|
+
* @returns 변환된 화면 좌표 또는 null
|
|
835
843
|
*/
|
|
836
844
|
|
|
837
845
|
var computeMarkerOffset = function (markerData, controller) {
|
|
838
|
-
if (!markerData.position)
|
|
839
|
-
return null;
|
|
840
|
-
}
|
|
841
|
-
|
|
846
|
+
if (!markerData.position) return null;
|
|
842
847
|
return controller.positionToOffset(markerData.position);
|
|
843
848
|
};
|
|
844
849
|
/**
|
|
845
|
-
* Point-in-Polygon 알고리즘
|
|
850
|
+
* Point-in-Polygon 알고리즘 (Ray Casting)
|
|
851
|
+
*
|
|
852
|
+
* @param point 확인할 점의 좌표
|
|
853
|
+
* @param polygon 폴리곤 좌표 배열
|
|
854
|
+
* @returns 점이 폴리곤 내부에 있으면 true
|
|
846
855
|
*/
|
|
847
856
|
|
|
848
857
|
var isPointInPolygon = function (point, polygon) {
|
|
858
|
+
// Ray Casting 알고리즘: 점에서 오른쪽으로 무한히 뻗은 선과 폴리곤 변의 교차 횟수로 판단
|
|
849
859
|
var inside = false;
|
|
850
860
|
|
|
851
861
|
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
852
862
|
var xi = polygon[i][0],
|
|
853
863
|
yi = polygon[i][1];
|
|
854
864
|
var xj = polygon[j][0],
|
|
855
|
-
yj = polygon[j][1];
|
|
865
|
+
yj = polygon[j][1]; // 점의 y 좌표가 변의 양 끝점 사이에 있고, 교차점의 x 좌표가 점의 x 좌표보다 큰지 확인
|
|
866
|
+
|
|
856
867
|
var intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi;
|
|
857
|
-
if (intersect) inside = !inside;
|
|
868
|
+
if (intersect) inside = !inside; // 교차할 때마다 inside 상태 토글
|
|
858
869
|
}
|
|
859
870
|
|
|
860
871
|
return inside;
|
|
861
872
|
};
|
|
862
873
|
/**
|
|
863
|
-
* 폴리곤 히트 테스트
|
|
874
|
+
* 폴리곤 히트 테스트 (도넛 폴리곤 지원)
|
|
875
|
+
*
|
|
876
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
877
|
+
* @param polygonData 폴리곤 데이터
|
|
878
|
+
* @param getPolygonOffsets 폴리곤 좌표 변환 함수
|
|
879
|
+
* @returns 점이 폴리곤 내부에 있으면 true
|
|
864
880
|
*/
|
|
865
881
|
|
|
866
882
|
var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffsets) {
|
|
867
883
|
var polygonOffsets = getPolygonOffsets(polygonData);
|
|
868
|
-
if (!polygonOffsets) return false;
|
|
884
|
+
if (!polygonOffsets) return false; // 도넛 폴리곤 처리: 외부 폴리곤 내부에 있으면서 구멍(hole) 내부에 있지 않아야 함
|
|
869
885
|
|
|
870
|
-
|
|
871
|
-
var
|
|
886
|
+
if (polygonData.isDonutPolygon) {
|
|
887
|
+
for (var _i = 0, polygonOffsets_1 = polygonOffsets; _i < polygonOffsets_1.length; _i++) {
|
|
888
|
+
var multiPolygon = polygonOffsets_1[_i];
|
|
889
|
+
if (multiPolygon.length === 0) continue; // 구멍이 없는 경우 일반 폴리곤과 동일
|
|
890
|
+
|
|
891
|
+
if (multiPolygon.length === 1) {
|
|
892
|
+
if (isPointInPolygon(clickedOffset, multiPolygon[0])) {
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
continue;
|
|
897
|
+
} // 외부 폴리곤 내부에 있는지 확인
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
var outerPolygon = multiPolygon[0];
|
|
872
901
|
|
|
873
|
-
|
|
874
|
-
|
|
902
|
+
if (!isPointInPolygon(clickedOffset, outerPolygon)) {
|
|
903
|
+
continue;
|
|
904
|
+
} // 구멍 내부에 있으면 false (도넛의 빈 공간)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
for (var i = 1; i < multiPolygon.length; i++) {
|
|
908
|
+
var hole = multiPolygon[i];
|
|
909
|
+
|
|
910
|
+
if (isPointInPolygon(clickedOffset, hole)) {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
} // 외부 폴리곤 내부에 있으면서 모든 구멍 밖에 있으면 true
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return false;
|
|
920
|
+
} // 일반 폴리곤 처리
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
for (var _a = 0, polygonOffsets_2 = polygonOffsets; _a < polygonOffsets_2.length; _a++) {
|
|
924
|
+
var multiPolygon = polygonOffsets_2[_a];
|
|
925
|
+
|
|
926
|
+
for (var _b = 0, multiPolygon_2 = multiPolygon; _b < multiPolygon_2.length; _b++) {
|
|
927
|
+
var polygonGroup = multiPolygon_2[_b];
|
|
928
|
+
if (polygonGroup.length === 0) continue;
|
|
875
929
|
|
|
876
930
|
if (isPointInPolygon(clickedOffset, polygonGroup)) {
|
|
877
931
|
return true;
|
|
@@ -882,7 +936,12 @@ var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffse
|
|
|
882
936
|
return false;
|
|
883
937
|
};
|
|
884
938
|
/**
|
|
885
|
-
* 마커 히트 테스트
|
|
939
|
+
* 마커 히트 테스트 (꼬리 제외, 오프셋 지원)
|
|
940
|
+
*
|
|
941
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
942
|
+
* @param markerData 마커 데이터
|
|
943
|
+
* @param getMarkerOffset 마커 좌표 변환 함수
|
|
944
|
+
* @returns 점이 마커 영역 내부에 있으면 true
|
|
886
945
|
*/
|
|
887
946
|
|
|
888
947
|
var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
|
|
@@ -890,38 +949,79 @@ var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset)
|
|
|
890
949
|
if (!markerOffset) return false;
|
|
891
950
|
var boxWidth = markerData.boxWidth || 50;
|
|
892
951
|
var boxHeight = markerData.boxHeight || 28;
|
|
893
|
-
var tailHeight =
|
|
894
|
-
var
|
|
895
|
-
var
|
|
952
|
+
var tailHeight = markerData.tailHeight || 0;
|
|
953
|
+
var offsetX = markerData.offsetX || 0;
|
|
954
|
+
var offsetY = markerData.offsetY || 0; // 오프셋을 적용한 마커 중심점 기준으로 박스 영역 계산 (꼬리는 제외)
|
|
955
|
+
|
|
956
|
+
var x = markerOffset.x + offsetX - boxWidth / 2;
|
|
957
|
+
var y = markerOffset.y + offsetY - boxHeight - tailHeight; // 클릭 위치가 박스 영역 내부에 있는지 확인
|
|
958
|
+
|
|
896
959
|
return clickedOffset.x >= x && clickedOffset.x <= x + boxWidth && clickedOffset.y >= y && clickedOffset.y <= y + boxHeight;
|
|
960
|
+
}; // Hex 색상을 RGBA로 변환
|
|
961
|
+
|
|
962
|
+
var hexToRgba = function (hexColor, alpha) {
|
|
963
|
+
if (alpha === void 0) {
|
|
964
|
+
alpha = 1;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
var hex = hexColor.replace('#', '');
|
|
968
|
+
|
|
969
|
+
if (hex.length !== 6) {
|
|
970
|
+
throw new Error('Invalid hex color format');
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
var r = parseInt(hex.substring(0, 2), 16);
|
|
974
|
+
var g = parseInt(hex.substring(2, 4), 16);
|
|
975
|
+
var b = parseInt(hex.substring(4, 6), 16);
|
|
976
|
+
return "rgba(".concat(r, ", ").concat(g, ", ").concat(b, ", ").concat(alpha, ")");
|
|
977
|
+
};
|
|
978
|
+
var tempCanvas = document.createElement('canvas');
|
|
979
|
+
var tempCtx = tempCanvas.getContext('2d');
|
|
980
|
+
/**
|
|
981
|
+
* 텍스트 박스 너비 계산
|
|
982
|
+
*
|
|
983
|
+
* @param params 파라미터 객체
|
|
984
|
+
* @param params.text 측정할 텍스트
|
|
985
|
+
* @param params.fontConfig 폰트 설정
|
|
986
|
+
* @param params.padding 패딩 값 (px)
|
|
987
|
+
* @param params.minWidth 최소 너비 (px)
|
|
988
|
+
* @returns 계산된 텍스트 박스 너비 (px)
|
|
989
|
+
*/
|
|
990
|
+
|
|
991
|
+
var calculateTextBoxWidth = function (_a) {
|
|
992
|
+
var text = _a.text,
|
|
993
|
+
fontConfig = _a.fontConfig,
|
|
994
|
+
padding = _a.padding,
|
|
995
|
+
minWidth = _a.minWidth;
|
|
996
|
+
if (!tempCtx) return 0;
|
|
997
|
+
tempCtx.font = fontConfig;
|
|
998
|
+
var textWidth = tempCtx.measureText(text).width;
|
|
999
|
+
return Math.max(minWidth, textWidth + padding);
|
|
897
1000
|
};
|
|
898
1001
|
|
|
899
|
-
var
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1002
|
+
var WoongCanvasContext = createContext(null);
|
|
1003
|
+
/**
|
|
1004
|
+
* WoongCanvasProvider 컴포넌트
|
|
1005
|
+
*
|
|
1006
|
+
* 다중 WoongCanvas 인스턴스를 관리하고 zIndex 기반 이벤트 우선순위를 처리합니다.
|
|
1007
|
+
*/
|
|
903
1008
|
|
|
1009
|
+
var WoongCanvasProvider = function (_a) {
|
|
1010
|
+
var children = _a.children;
|
|
1011
|
+
var controller = useMintMapController();
|
|
904
1012
|
var componentsRef = useRef([]);
|
|
905
1013
|
var currentHoveredRef = useRef(null);
|
|
906
1014
|
var currentHoveredDataRef = useRef(null);
|
|
907
|
-
var draggingRef = useRef(false);
|
|
908
|
-
/**
|
|
909
|
-
* 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
910
|
-
* 높은 zIndex가 먼저 처리됨
|
|
911
|
-
*/
|
|
1015
|
+
var draggingRef = useRef(false); // 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
912
1016
|
|
|
913
1017
|
var registerComponent = useCallback(function (instance) {
|
|
914
1018
|
componentsRef.current.push(instance);
|
|
915
1019
|
componentsRef.current.sort(function (a, b) {
|
|
916
1020
|
return b.zIndex - a.zIndex;
|
|
917
1021
|
});
|
|
918
|
-
}, []);
|
|
919
|
-
/**
|
|
920
|
-
* 컴포넌트 등록 해제
|
|
921
|
-
*/
|
|
1022
|
+
}, []); // 컴포넌트 등록 해제
|
|
922
1023
|
|
|
923
1024
|
var unregisterComponent = useCallback(function (instance) {
|
|
924
|
-
// Hover 중이던 컴포넌트면 초기화
|
|
925
1025
|
if (currentHoveredRef.current === instance) {
|
|
926
1026
|
currentHoveredRef.current = null;
|
|
927
1027
|
currentHoveredDataRef.current = null;
|
|
@@ -930,95 +1030,77 @@ var KonvaMarkerProvider = function (_a) {
|
|
|
930
1030
|
componentsRef.current = componentsRef.current.filter(function (c) {
|
|
931
1031
|
return c !== instance;
|
|
932
1032
|
});
|
|
933
|
-
}, []);
|
|
934
|
-
/**
|
|
935
|
-
* 전역 클릭 핸들러 (zIndex 우선순위)
|
|
936
|
-
*/
|
|
1033
|
+
}, []); // 전역 클릭 핸들러 (zIndex 우선순위)
|
|
937
1034
|
|
|
938
1035
|
var handleGlobalClick = useCallback(function (event) {
|
|
939
|
-
var _a;
|
|
1036
|
+
var _a, _b;
|
|
940
1037
|
|
|
941
1038
|
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
942
|
-
var clickedOffset = controller.positionToOffset(event.param.position); // zIndex
|
|
1039
|
+
var clickedOffset = controller.positionToOffset(event.param.position); // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
|
|
943
1040
|
|
|
944
|
-
for (var _i = 0,
|
|
945
|
-
var component =
|
|
1041
|
+
for (var _i = 0, _c = componentsRef.current; _i < _c.length; _i++) {
|
|
1042
|
+
var component = _c[_i];
|
|
1043
|
+
if (component.isInteractionDisabled()) continue;
|
|
946
1044
|
var data = component.findData(clickedOffset);
|
|
1045
|
+
if (!data) continue; // 첫 번째로 찾은 항목만 처리하고 종료 (zIndex 우선순위)
|
|
947
1046
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (component.onClick) {
|
|
952
|
-
component.onClick(data, component.getSelectedIds());
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
return; // 첫 번째 히트만 처리
|
|
956
|
-
}
|
|
1047
|
+
component.handleLocalClick(data);
|
|
1048
|
+
(_b = component.onClick) === null || _b === void 0 ? void 0 : _b.call(component, data, component.getSelectedIds());
|
|
1049
|
+
return;
|
|
957
1050
|
}
|
|
958
|
-
}, [controller]);
|
|
959
|
-
/**
|
|
960
|
-
* 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
961
|
-
*/
|
|
1051
|
+
}, [controller]); // 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
962
1052
|
|
|
963
1053
|
var handleGlobalMouseMove = useCallback(function (event) {
|
|
964
1054
|
var _a;
|
|
965
1055
|
|
|
966
1056
|
if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
967
|
-
var mouseOffset = controller.positionToOffset(event.param.position);
|
|
968
|
-
|
|
1057
|
+
var mouseOffset = controller.positionToOffset(event.param.position);
|
|
969
1058
|
var newHoveredComponent = null;
|
|
970
|
-
var newHoveredData = null;
|
|
1059
|
+
var newHoveredData = null; // zIndex 내림차순으로 정렬된 컴포넌트 순회 (높은 zIndex가 먼저 처리)
|
|
971
1060
|
|
|
972
1061
|
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
973
1062
|
var component = _b[_i];
|
|
1063
|
+
if (component.isInteractionDisabled()) continue;
|
|
974
1064
|
var data = component.findData(mouseOffset);
|
|
1065
|
+
if (!data) continue; // 첫 번째로 찾은 항목만 hover 처리 (zIndex 우선순위)
|
|
975
1066
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
} // Hover 상태 변경 감지 (최적화: 별도 ref로 직접 비교)
|
|
982
|
-
|
|
1067
|
+
newHoveredComponent = component;
|
|
1068
|
+
newHoveredData = data;
|
|
1069
|
+
break;
|
|
1070
|
+
} // hover 상태가 변경되지 않았으면 종료 (불필요한 렌더링 방지)
|
|
983
1071
|
|
|
984
|
-
if (currentHoveredRef.current !== newHoveredComponent || currentHoveredDataRef.current !== newHoveredData) {
|
|
985
|
-
// 이전 hover 해제
|
|
986
|
-
if (currentHoveredRef.current) {
|
|
987
|
-
currentHoveredRef.current.setHovered(null);
|
|
988
1072
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
} // 새 hover 설정
|
|
1073
|
+
if (currentHoveredRef.current === newHoveredComponent && currentHoveredDataRef.current === newHoveredData) {
|
|
1074
|
+
return;
|
|
1075
|
+
} // 기존 hover 항목에 mouseOut 이벤트 발생
|
|
993
1076
|
|
|
994
1077
|
|
|
995
|
-
|
|
996
|
-
|
|
1078
|
+
if (currentHoveredRef.current) {
|
|
1079
|
+
currentHoveredRef.current.setHovered(null);
|
|
997
1080
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
}
|
|
1081
|
+
if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
|
|
1082
|
+
currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
|
|
1001
1083
|
}
|
|
1084
|
+
} // 새 hover 항목에 mouseOver 이벤트 발생
|
|
1002
1085
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1086
|
+
|
|
1087
|
+
if (newHoveredComponent && newHoveredData) {
|
|
1088
|
+
newHoveredComponent.setHovered(newHoveredData);
|
|
1089
|
+
|
|
1090
|
+
if (newHoveredComponent.onMouseOver) {
|
|
1091
|
+
newHoveredComponent.onMouseOver(newHoveredData);
|
|
1092
|
+
}
|
|
1005
1093
|
}
|
|
1006
|
-
}, [controller]);
|
|
1007
|
-
/**
|
|
1008
|
-
* 줌/드래그 시작 (마우스 이동 이벤트 무시)
|
|
1009
|
-
*/
|
|
1010
1094
|
|
|
1095
|
+
currentHoveredRef.current = newHoveredComponent;
|
|
1096
|
+
currentHoveredDataRef.current = newHoveredData;
|
|
1097
|
+
}, [controller]);
|
|
1011
1098
|
var handleZoomStart = useCallback(function () {
|
|
1012
1099
|
draggingRef.current = true;
|
|
1013
1100
|
}, []);
|
|
1014
|
-
/**
|
|
1015
|
-
* 지도 idle (마우스 이동 이벤트 재개)
|
|
1016
|
-
*/
|
|
1017
|
-
|
|
1018
1101
|
var handleIdle = useCallback(function () {
|
|
1019
1102
|
draggingRef.current = false;
|
|
1020
|
-
}, []);
|
|
1021
|
-
|
|
1103
|
+
}, []);
|
|
1022
1104
|
useEffect(function () {
|
|
1023
1105
|
controller.addEventListener('CLICK', handleGlobalClick);
|
|
1024
1106
|
controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
|
|
@@ -1030,77 +1112,68 @@ var KonvaMarkerProvider = function (_a) {
|
|
|
1030
1112
|
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
1031
1113
|
controller.removeEventListener('IDLE', handleIdle);
|
|
1032
1114
|
};
|
|
1033
|
-
}, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]);
|
|
1034
|
-
|
|
1115
|
+
}, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]);
|
|
1035
1116
|
var contextValue = useMemo(function () {
|
|
1036
1117
|
return {
|
|
1037
1118
|
registerComponent: registerComponent,
|
|
1038
1119
|
unregisterComponent: unregisterComponent
|
|
1039
1120
|
};
|
|
1040
1121
|
}, [registerComponent, unregisterComponent]);
|
|
1041
|
-
return React.createElement(
|
|
1122
|
+
return React.createElement(WoongCanvasContext.Provider, {
|
|
1042
1123
|
value: contextValue
|
|
1043
1124
|
}, children);
|
|
1044
1125
|
};
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1126
|
+
/**
|
|
1127
|
+
* WoongCanvas Context Hook
|
|
1128
|
+
*
|
|
1129
|
+
* @returns WoongCanvasContextValue 또는 null (Provider 없으면)
|
|
1130
|
+
*/
|
|
1049
1131
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1132
|
+
var useWoongCanvasContext = function () {
|
|
1133
|
+
return useContext(WoongCanvasContext);
|
|
1134
|
+
};
|
|
1053
1135
|
|
|
1054
1136
|
/**
|
|
1055
|
-
* 공간 인덱스 그리드 셀 크기 (
|
|
1137
|
+
* 공간 인덱스 그리드 셀 크기 (픽셀 단위)
|
|
1056
1138
|
*
|
|
1057
|
-
*
|
|
1058
|
-
* - 목표: 클릭 시 셀당 10~30개 항목만 체크 (빠른 Hit Test)
|
|
1059
|
-
* - 화면 크기: 1920×1080 기준
|
|
1060
|
-
* - 30,000개 항목 → 50px 셀 크기 = 약 800개 셀 = 셀당 ~37개
|
|
1139
|
+
* @default 100
|
|
1061
1140
|
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
* - 50px
|
|
1141
|
+
* @remarks
|
|
1142
|
+
* 셀 크기는 평균 마커 크기의 1.5~2배가 적절합니다.
|
|
1143
|
+
* - 마커가 50px 이하: 50px 권장
|
|
1144
|
+
* - 마커가 60-80px: 100px 권장 (현재 설정)
|
|
1145
|
+
* - 마커가 100px 이상: 150-200px 권장
|
|
1065
1146
|
*
|
|
1066
|
-
*
|
|
1067
|
-
* -
|
|
1068
|
-
* -
|
|
1147
|
+
* 셀 크기가 너무 작으면:
|
|
1148
|
+
* - 한 마커가 여러 셀에 등록되어 메모리 사용량 증가
|
|
1149
|
+
* - 인덱스 빌드 비용 증가
|
|
1150
|
+
*
|
|
1151
|
+
* 셀 크기가 너무 크면:
|
|
1152
|
+
* - 한 셀에 많은 마커가 들어가서 Hit Test 시 후보 항목이 많아짐
|
|
1153
|
+
* - Hit Test 성능 저하
|
|
1069
1154
|
*/
|
|
1070
|
-
var SPATIAL_GRID_CELL_SIZE =
|
|
1155
|
+
var SPATIAL_GRID_CELL_SIZE = 100;
|
|
1071
1156
|
/**
|
|
1072
|
-
* 뷰포트 컬링 여유 공간 (
|
|
1157
|
+
* 뷰포트 컬링 여유 공간 (픽셀 단위)
|
|
1073
1158
|
*
|
|
1074
|
-
*
|
|
1075
|
-
* 30,000개 중 실제 렌더링: 화면에 보이는 1,000~3,000개만
|
|
1159
|
+
* @default 100
|
|
1076
1160
|
*/
|
|
1077
1161
|
|
|
1078
1162
|
var DEFAULT_CULLING_MARGIN = 100;
|
|
1079
1163
|
/**
|
|
1080
1164
|
* LRU 캐시 최대 항목 수
|
|
1081
1165
|
*
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1084
|
-
* 최적값 계산:
|
|
1085
|
-
* - 전체 항목: 30,000개
|
|
1086
|
-
* - 캐시 크기: 30,000개 → 100% 히트율 (메모리: ~2.4MB)
|
|
1087
|
-
*
|
|
1088
|
-
* 메모리 사용량 (항목당 ~80 bytes):
|
|
1089
|
-
* - 10,000개: ~800KB → 캐시 히트율 33% ❌
|
|
1090
|
-
* - 30,000개: ~2.4MB → 캐시 히트율 100% ✅
|
|
1091
|
-
*
|
|
1092
|
-
* zoom/pan 시 어차피 clear() 호출되므로 메모리 누적 없음
|
|
1166
|
+
* @default 30000
|
|
1093
1167
|
*/
|
|
1094
1168
|
|
|
1095
1169
|
var DEFAULT_MAX_CACHE_SIZE = 30000;
|
|
1096
1170
|
/**
|
|
1097
|
-
* LRU (Least Recently Used)
|
|
1098
|
-
* 메모리 제한을 위한 캐시 구현 (최적화 버전)
|
|
1171
|
+
* LRU Cache (Least Recently Used)
|
|
1099
1172
|
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1173
|
+
* 좌표 변환 결과를 캐싱하기 위한 캐시 구현
|
|
1174
|
+
*
|
|
1175
|
+
* @template K 캐시 키 타입
|
|
1176
|
+
* @template V 캐시 값 타입
|
|
1104
1177
|
*/
|
|
1105
1178
|
|
|
1106
1179
|
var LRUCache =
|
|
@@ -1113,64 +1186,43 @@ function () {
|
|
|
1113
1186
|
|
|
1114
1187
|
this.cache = new Map();
|
|
1115
1188
|
this.maxSize = maxSize;
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* 캐시에서 값 조회
|
|
1119
|
-
*
|
|
1120
|
-
* 최적화: delete+set 제거
|
|
1121
|
-
* - 이전: 매번 delete+set으로 LRU 갱신 (해시 재계산 비용)
|
|
1122
|
-
* - 현재: 단순 조회만 수행 (O(1) 해시 조회)
|
|
1123
|
-
*
|
|
1124
|
-
* 트레이드오프:
|
|
1125
|
-
* - 장점: 읽기 성능 대폭 향상 (10,000번 get → 이전보다 2배 빠름)
|
|
1126
|
-
* - 단점: 접근 빈도가 아닌 삽입 순서 기반 eviction (FIFO)
|
|
1127
|
-
*
|
|
1128
|
-
* WoongKonvaMarker 사용 사례에 최적:
|
|
1129
|
-
* - 좌표 변환 결과는 zoom/pan 시 어차피 전체 초기화
|
|
1130
|
-
* - 접근 빈도 추적보다 빠른 조회가 더 중요
|
|
1131
|
-
*/
|
|
1189
|
+
} // 캐시에서 값 조회
|
|
1132
1190
|
|
|
1133
1191
|
|
|
1134
1192
|
LRUCache.prototype.get = function (key) {
|
|
1135
1193
|
return this.cache.get(key);
|
|
1136
|
-
};
|
|
1137
|
-
/**
|
|
1138
|
-
* 캐시에 값 저장 (버그 수정 + 최적화)
|
|
1139
|
-
*
|
|
1140
|
-
* 수정 사항:
|
|
1141
|
-
* 1. 기존 키 업데이트 시 크기 체크 누락 버그 수정
|
|
1142
|
-
* 2. 로직 명확화: 기존 항목/신규 항목 분리 처리
|
|
1143
|
-
*/
|
|
1194
|
+
}; // 캐시에 값 저장 (FIFO eviction)
|
|
1144
1195
|
|
|
1145
1196
|
|
|
1146
1197
|
LRUCache.prototype.set = function (key, value) {
|
|
1147
1198
|
var exists = this.cache.has(key);
|
|
1148
1199
|
|
|
1149
1200
|
if (exists) {
|
|
1150
|
-
// 기존 항목 업데이트: 단순 덮어쓰기 (크기 변화 없음)
|
|
1151
1201
|
this.cache.set(key, value);
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (this.cache.size >= this.maxSize) {
|
|
1155
|
-
// 가장 오래된 항목 제거 (Map의 첫 번째 항목)
|
|
1156
|
-
var firstKey = this.cache.keys().next().value;
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1157
1204
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1205
|
+
if (this.cache.size >= this.maxSize) {
|
|
1206
|
+
var firstKey = this.cache.keys().next().value;
|
|
1162
1207
|
|
|
1163
|
-
|
|
1208
|
+
if (firstKey !== undefined) {
|
|
1209
|
+
this.cache.delete(firstKey);
|
|
1210
|
+
}
|
|
1164
1211
|
}
|
|
1165
|
-
|
|
1212
|
+
|
|
1213
|
+
this.cache.set(key, value);
|
|
1214
|
+
}; // 캐시 초기화
|
|
1215
|
+
|
|
1166
1216
|
|
|
1167
1217
|
LRUCache.prototype.clear = function () {
|
|
1168
1218
|
this.cache.clear();
|
|
1169
|
-
};
|
|
1219
|
+
}; // 캐시 크기 반환
|
|
1220
|
+
|
|
1170
1221
|
|
|
1171
1222
|
LRUCache.prototype.size = function () {
|
|
1172
1223
|
return this.cache.size;
|
|
1173
|
-
};
|
|
1224
|
+
}; // 키 존재 여부 확인
|
|
1225
|
+
|
|
1174
1226
|
|
|
1175
1227
|
LRUCache.prototype.has = function (key) {
|
|
1176
1228
|
return this.cache.has(key);
|
|
@@ -1180,16 +1232,10 @@ function () {
|
|
|
1180
1232
|
}();
|
|
1181
1233
|
/**
|
|
1182
1234
|
* Spatial Hash Grid (공간 해시 그리드)
|
|
1183
|
-
* 공간 인덱싱을 위한 그리드 기반 자료구조 (개선 버전)
|
|
1184
1235
|
*
|
|
1185
|
-
*
|
|
1186
|
-
* 1. 중복 삽입 방지: 같은 항목을 여러 번 insert 해도 안전
|
|
1187
|
-
* 2. 메모리 누수 방지: 기존 항목 자동 제거
|
|
1188
|
-
* 3. 성능 최적화: 불필요한 배열 생성 최소화
|
|
1236
|
+
* 빠른 Hit Test를 위한 그리드 기반 공간 인덱싱 자료구조
|
|
1189
1237
|
*
|
|
1190
|
-
*
|
|
1191
|
-
* - 빠른 Hit Test (마우스 클릭 시 어떤 마커/폴리곤인지 찾기)
|
|
1192
|
-
* - 30,000개 항목 → 클릭 위치 주변 ~10개만 체크 (3,000배 빠름)
|
|
1238
|
+
* @template T 인덱싱할 항목 타입
|
|
1193
1239
|
*/
|
|
1194
1240
|
|
|
1195
1241
|
var SpatialHashGrid =
|
|
@@ -1197,34 +1243,30 @@ var SpatialHashGrid =
|
|
|
1197
1243
|
function () {
|
|
1198
1244
|
function SpatialHashGrid(cellSize) {
|
|
1199
1245
|
if (cellSize === void 0) {
|
|
1200
|
-
cellSize =
|
|
1246
|
+
cellSize = SPATIAL_GRID_CELL_SIZE;
|
|
1201
1247
|
}
|
|
1202
1248
|
|
|
1203
1249
|
this.cellSize = cellSize;
|
|
1204
1250
|
this.grid = new Map();
|
|
1205
1251
|
this.itemToCells = new Map();
|
|
1206
|
-
}
|
|
1207
|
-
/**
|
|
1208
|
-
* 셀 키 생성 (x, y 좌표 → 그리드 셀 ID)
|
|
1209
|
-
*/
|
|
1252
|
+
} // 셀 키 생성 (x, y 좌표 → 그리드 셀 ID)
|
|
1210
1253
|
|
|
1211
1254
|
|
|
1212
1255
|
SpatialHashGrid.prototype.getCellKey = function (x, y) {
|
|
1256
|
+
// 좌표를 셀 크기로 나눈 몫으로 셀 인덱스 계산
|
|
1213
1257
|
var cellX = Math.floor(x / this.cellSize);
|
|
1214
1258
|
var cellY = Math.floor(y / this.cellSize);
|
|
1215
1259
|
return "".concat(cellX, ",").concat(cellY);
|
|
1216
|
-
};
|
|
1217
|
-
/**
|
|
1218
|
-
* 바운딩 박스가 걸치는 모든 셀 키 배열 반환
|
|
1219
|
-
*/
|
|
1260
|
+
}; // 바운딩 박스가 걸치는 모든 셀 키 배열 반환
|
|
1220
1261
|
|
|
1221
1262
|
|
|
1222
1263
|
SpatialHashGrid.prototype.getCellsForBounds = function (minX, minY, maxX, maxY) {
|
|
1223
|
-
var cells = [];
|
|
1264
|
+
var cells = []; // 바운딩 박스가 걸치는 셀 범위 계산
|
|
1265
|
+
|
|
1224
1266
|
var startCellX = Math.floor(minX / this.cellSize);
|
|
1225
1267
|
var startCellY = Math.floor(minY / this.cellSize);
|
|
1226
1268
|
var endCellX = Math.floor(maxX / this.cellSize);
|
|
1227
|
-
var endCellY = Math.floor(maxY / this.cellSize);
|
|
1269
|
+
var endCellY = Math.floor(maxY / this.cellSize); // 바운딩 박스가 걸치는 모든 셀을 배열에 추가
|
|
1228
1270
|
|
|
1229
1271
|
for (var x = startCellX; x <= endCellX; x++) {
|
|
1230
1272
|
for (var y = startCellY; y <= endCellY; y++) {
|
|
@@ -1233,22 +1275,15 @@ function () {
|
|
|
1233
1275
|
}
|
|
1234
1276
|
|
|
1235
1277
|
return cells;
|
|
1236
|
-
};
|
|
1237
|
-
/**
|
|
1238
|
-
* 항목 추가 (바운딩 박스 기반)
|
|
1239
|
-
*
|
|
1240
|
-
* 개선 사항:
|
|
1241
|
-
* - 중복 삽입 방지: 기존 항목이 있으면 먼저 제거 후 재삽입
|
|
1242
|
-
* - 메모리 누수 방지: 이전 셀 참조 완전 제거
|
|
1243
|
-
*/
|
|
1278
|
+
}; // 항목 추가 (바운딩 박스 기반, 중복 삽입 방지)
|
|
1244
1279
|
|
|
1245
1280
|
|
|
1246
1281
|
SpatialHashGrid.prototype.insert = function (item, minX, minY, maxX, maxY) {
|
|
1247
|
-
//
|
|
1248
|
-
this.remove(item); //
|
|
1282
|
+
// 기존 항목 제거 (중복 삽입 방지: 같은 항목을 여러 번 insert 해도 안전)
|
|
1283
|
+
this.remove(item); // 바운딩 박스가 걸치는 모든 셀에 항목 등록
|
|
1249
1284
|
|
|
1250
1285
|
var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
|
|
1251
|
-
this.itemToCells.set(item, cells);
|
|
1286
|
+
this.itemToCells.set(item, cells); // 항목과 셀의 매핑 저장 (제거 시 필요)
|
|
1252
1287
|
|
|
1253
1288
|
for (var _i = 0, cells_1 = cells; _i < cells_1.length; _i++) {
|
|
1254
1289
|
var cell = cells_1[_i];
|
|
@@ -1259,17 +1294,12 @@ function () {
|
|
|
1259
1294
|
|
|
1260
1295
|
this.grid.get(cell).push(item);
|
|
1261
1296
|
}
|
|
1262
|
-
};
|
|
1263
|
-
/**
|
|
1264
|
-
* 항목 제거
|
|
1265
|
-
*
|
|
1266
|
-
* 추가된 메서드: 메모리 누수 방지 및 업데이트 지원
|
|
1267
|
-
*/
|
|
1297
|
+
}; // 항목 제거 (모든 셀에서 참조 제거)
|
|
1268
1298
|
|
|
1269
1299
|
|
|
1270
1300
|
SpatialHashGrid.prototype.remove = function (item) {
|
|
1271
1301
|
var prevCells = this.itemToCells.get(item);
|
|
1272
|
-
if (!prevCells) return; //
|
|
1302
|
+
if (!prevCells) return; // 항목이 등록된 모든 셀에서 참조 제거 (메모리 누수 방지)
|
|
1273
1303
|
|
|
1274
1304
|
for (var _i = 0, prevCells_1 = prevCells; _i < prevCells_1.length; _i++) {
|
|
1275
1305
|
var cell = prevCells_1[_i];
|
|
@@ -1280,51 +1310,39 @@ function () {
|
|
|
1280
1310
|
|
|
1281
1311
|
if (index !== -1) {
|
|
1282
1312
|
cellItems.splice(index, 1);
|
|
1283
|
-
} // 빈 셀 정리 (메모리
|
|
1313
|
+
} // 빈 셀 정리 (메모리 효율: 사용하지 않는 셀 제거)
|
|
1284
1314
|
|
|
1285
1315
|
|
|
1286
1316
|
if (cellItems.length === 0) {
|
|
1287
1317
|
this.grid.delete(cell);
|
|
1288
1318
|
}
|
|
1289
1319
|
}
|
|
1290
|
-
}
|
|
1320
|
+
} // 항목과 셀의 매핑 제거
|
|
1321
|
+
|
|
1291
1322
|
|
|
1292
1323
|
this.itemToCells.delete(item);
|
|
1293
|
-
};
|
|
1294
|
-
/**
|
|
1295
|
-
* 항목 위치 업데이트
|
|
1296
|
-
*
|
|
1297
|
-
* 추가된 메서드: remove + insert의 편의 함수
|
|
1298
|
-
*/
|
|
1324
|
+
}; // 항목 위치 업데이트 (remove + insert)
|
|
1299
1325
|
|
|
1300
1326
|
|
|
1301
1327
|
SpatialHashGrid.prototype.update = function (item, minX, minY, maxX, maxY) {
|
|
1302
1328
|
this.insert(item, minX, minY, maxX, maxY);
|
|
1303
|
-
};
|
|
1304
|
-
/**
|
|
1305
|
-
* 점 주변의 항목 조회 (1개 셀만)
|
|
1306
|
-
*
|
|
1307
|
-
* 성능: O(해당 셀의 항목 수) - 보통 ~10개
|
|
1308
|
-
*/
|
|
1329
|
+
}; // 점 주변의 항목 조회 (Hit Test용)
|
|
1309
1330
|
|
|
1310
1331
|
|
|
1311
1332
|
SpatialHashGrid.prototype.queryPoint = function (x, y) {
|
|
1333
|
+
// 클릭 위치가 속한 셀의 모든 항목 조회 (O(1) 수준의 빠른 조회)
|
|
1312
1334
|
var cellKey = this.getCellKey(x, y);
|
|
1313
1335
|
var items = this.grid.get(cellKey); // 빈 배열 재사용 (메모리 할당 최소화)
|
|
1314
1336
|
|
|
1315
1337
|
return items || [];
|
|
1316
|
-
};
|
|
1317
|
-
/**
|
|
1318
|
-
* 영역 내 항목 조회
|
|
1319
|
-
*
|
|
1320
|
-
* 성능: O(셀 개수 × 셀당 평균 항목 수)
|
|
1321
|
-
* Set으로 중복 제거 보장
|
|
1322
|
-
*/
|
|
1338
|
+
}; // 영역 내 항목 조회 (Viewport Culling용)
|
|
1323
1339
|
|
|
1324
1340
|
|
|
1325
1341
|
SpatialHashGrid.prototype.queryBounds = function (minX, minY, maxX, maxY) {
|
|
1342
|
+
// 영역이 걸치는 모든 셀 찾기
|
|
1326
1343
|
var cells = this.getCellsForBounds(minX, minY, maxX, maxY);
|
|
1327
|
-
var results = new Set();
|
|
1344
|
+
var results = new Set(); // 중복 제거를 위해 Set 사용
|
|
1345
|
+
// 각 셀의 모든 항목을 결과에 추가
|
|
1328
1346
|
|
|
1329
1347
|
for (var _i = 0, cells_2 = cells; _i < cells_2.length; _i++) {
|
|
1330
1348
|
var cell = cells_2[_i];
|
|
@@ -1333,37 +1351,24 @@ function () {
|
|
|
1333
1351
|
if (items) {
|
|
1334
1352
|
for (var _a = 0, items_1 = items; _a < items_1.length; _a++) {
|
|
1335
1353
|
var item = items_1[_a];
|
|
1336
|
-
results.add(item);
|
|
1354
|
+
results.add(item); // Set이므로 중복 자동 제거
|
|
1337
1355
|
}
|
|
1338
1356
|
}
|
|
1339
1357
|
}
|
|
1340
1358
|
|
|
1341
1359
|
return Array.from(results);
|
|
1342
|
-
};
|
|
1343
|
-
/**
|
|
1344
|
-
* 항목 존재 여부 확인
|
|
1345
|
-
*
|
|
1346
|
-
* 추가된 메서드: 빠른 존재 여부 체크
|
|
1347
|
-
*/
|
|
1360
|
+
}; // 항목 존재 여부 확인
|
|
1348
1361
|
|
|
1349
1362
|
|
|
1350
1363
|
SpatialHashGrid.prototype.has = function (item) {
|
|
1351
1364
|
return this.itemToCells.has(item);
|
|
1352
|
-
};
|
|
1353
|
-
/**
|
|
1354
|
-
* 전체 초기화
|
|
1355
|
-
*/
|
|
1365
|
+
}; // 전체 초기화
|
|
1356
1366
|
|
|
1357
1367
|
|
|
1358
1368
|
SpatialHashGrid.prototype.clear = function () {
|
|
1359
1369
|
this.grid.clear();
|
|
1360
1370
|
this.itemToCells.clear();
|
|
1361
|
-
};
|
|
1362
|
-
/**
|
|
1363
|
-
* 통계 정보
|
|
1364
|
-
*
|
|
1365
|
-
* 개선: totalItems는 실제 고유 항목 수를 정확히 반환
|
|
1366
|
-
*/
|
|
1371
|
+
}; // 통계 정보 반환
|
|
1367
1372
|
|
|
1368
1373
|
|
|
1369
1374
|
SpatialHashGrid.prototype.stats = function () {
|
|
@@ -1381,89 +1386,347 @@ function () {
|
|
|
1381
1386
|
return SpatialHashGrid;
|
|
1382
1387
|
}();
|
|
1383
1388
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1389
|
+
/**
|
|
1390
|
+
* 현재 뷰포트 영역 계산
|
|
1391
|
+
*
|
|
1392
|
+
* @param stage Konva Stage 인스턴스
|
|
1393
|
+
* @param cullingMargin 컬링 여유 공간 (px)
|
|
1394
|
+
* @param viewportRef 뷰포트 경계를 저장할 ref
|
|
1395
|
+
*/
|
|
1396
|
+
var updateViewport = function (stage, cullingMargin, viewportRef) {
|
|
1397
|
+
if (!stage) return;
|
|
1398
|
+
viewportRef.current = {
|
|
1399
|
+
minX: -cullingMargin,
|
|
1400
|
+
maxX: stage.width() + cullingMargin,
|
|
1401
|
+
minY: -cullingMargin,
|
|
1402
|
+
maxY: stage.height() + cullingMargin
|
|
1403
|
+
};
|
|
1404
|
+
};
|
|
1405
|
+
/**
|
|
1406
|
+
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
1407
|
+
*
|
|
1408
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1409
|
+
* @param item 확인할 아이템
|
|
1410
|
+
* @param viewportRef 뷰포트 경계 ref
|
|
1411
|
+
* @param boundingBoxCacheRef 바운딩 박스 캐시 ref
|
|
1412
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1413
|
+
* @returns 뷰포트 안에 있으면 true
|
|
1414
|
+
*/
|
|
1387
1415
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
zoomLevel = _a.zoomLevel,
|
|
1392
|
-
center = _a.center,
|
|
1393
|
-
_c = _a.centerMoveWithPanning,
|
|
1394
|
-
centerMoveWithPanning = _c === void 0 ? false : _c,
|
|
1395
|
-
children = _a.children; //controller
|
|
1416
|
+
var isInViewport = function (item, viewportRef, boundingBoxCacheRef, computeBoundingBox) {
|
|
1417
|
+
if (!viewportRef.current) return true;
|
|
1418
|
+
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
1396
1419
|
|
|
1397
|
-
var
|
|
1420
|
+
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
1398
1421
|
|
|
1399
|
-
|
|
1422
|
+
if (!bbox) {
|
|
1423
|
+
// 바운딩 박스 계산 (공통 함수 사용)
|
|
1424
|
+
var computed = computeBoundingBox(item);
|
|
1425
|
+
if (!computed) return false;
|
|
1426
|
+
bbox = computed;
|
|
1427
|
+
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
1428
|
+
} // 바운딩 박스와 viewport 교차 체크
|
|
1400
1429
|
|
|
1401
|
-
var _d = useState(false),
|
|
1402
|
-
mapInitialized = _d[0],
|
|
1403
|
-
setMapInitialized = _d[1];
|
|
1404
1430
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
(function () {
|
|
1408
|
-
return __awaiter(_this, void 0, void 0, function () {
|
|
1409
|
-
var map_1;
|
|
1410
|
-
return __generator(this, function (_a) {
|
|
1411
|
-
switch (_a.label) {
|
|
1412
|
-
case 0:
|
|
1413
|
-
if (!(elementRef && elementRef.current)) return [3
|
|
1414
|
-
/*break*/
|
|
1415
|
-
, 2];
|
|
1416
|
-
return [4
|
|
1417
|
-
/*yield*/
|
|
1418
|
-
, controller.initializingMap(elementRef.current)];
|
|
1431
|
+
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
1432
|
+
};
|
|
1419
1433
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1434
|
+
/**
|
|
1435
|
+
* 지도 이벤트 핸들러 생성 함수
|
|
1436
|
+
*
|
|
1437
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1438
|
+
* @param deps 이벤트 핸들러 생성에 필요한 의존성
|
|
1439
|
+
* @returns 지도 이벤트 핸들러 객체
|
|
1440
|
+
*/
|
|
1422
1441
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1442
|
+
var createMapEventHandlers = function (deps) {
|
|
1443
|
+
var controller = deps.controller,
|
|
1444
|
+
containerRef = deps.containerRef,
|
|
1445
|
+
markerRef = deps.markerRef,
|
|
1446
|
+
options = deps.options,
|
|
1447
|
+
prevCenterOffsetRef = deps.prevCenterOffsetRef,
|
|
1448
|
+
accumTranslateRef = deps.accumTranslateRef,
|
|
1449
|
+
offsetCacheRef = deps.offsetCacheRef,
|
|
1450
|
+
boundingBoxCacheRef = deps.boundingBoxCacheRef,
|
|
1451
|
+
renderAllImmediate = deps.renderAllImmediate; // 지도 이동/줌 완료 시 처리 (캐시 초기화 및 렌더링)
|
|
1425
1452
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1453
|
+
var handleIdle = function () {
|
|
1454
|
+
prevCenterOffsetRef.current = null;
|
|
1455
|
+
accumTranslateRef.current = {
|
|
1456
|
+
x: 0,
|
|
1457
|
+
y: 0
|
|
1458
|
+
}; // 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
1432
1459
|
|
|
1433
|
-
|
|
1460
|
+
offsetCacheRef.current.clear();
|
|
1461
|
+
boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
|
|
1434
1462
|
|
|
1435
|
-
|
|
1436
|
-
return [2
|
|
1437
|
-
/*return*/
|
|
1438
|
-
];
|
|
1439
|
-
}
|
|
1440
|
-
});
|
|
1441
|
-
});
|
|
1442
|
-
})();
|
|
1443
|
-
}, [controller, elementRef]); //줌레벨
|
|
1463
|
+
var bounds = controller.getCurrBounds();
|
|
1444
1464
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1465
|
+
var markerOptions = __assign({
|
|
1466
|
+
position: bounds.nw
|
|
1467
|
+
}, options);
|
|
1448
1468
|
|
|
1449
|
-
|
|
1450
|
-
controller === null || controller === void 0 ? void 0 : controller.setZoomLevel(zoomLevel);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
}, [zoomLevel]); //센터
|
|
1469
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // transform 제거 전에 새 데이터로 즉시 렌더링 (transform 제거 시 잠깐 빈 화면이 보이는 것 방지)
|
|
1454
1470
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1471
|
+
if (containerRef.current) {
|
|
1472
|
+
containerRef.current.style.transform = '';
|
|
1473
|
+
containerRef.current.style.visibility = '';
|
|
1474
|
+
} // 새 위치에서 렌더링 (캐시는 이미 초기화됨)
|
|
1458
1475
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1476
|
+
|
|
1477
|
+
renderAllImmediate();
|
|
1478
|
+
}; // 줌 시작 시 처리 (일시적으로 숨김)
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
var handleZoomStart = function () {
|
|
1482
|
+
if (!containerRef.current) return;
|
|
1483
|
+
containerRef.current.style.visibility = 'hidden';
|
|
1484
|
+
}; // 줌 종료 시 처리 (다시 표시)
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
var handleZoomEnd = function () {
|
|
1488
|
+
if (!containerRef.current) return;
|
|
1489
|
+
containerRef.current.style.visibility = '';
|
|
1490
|
+
}; // 지도 중심 변경 시 처리 (transform으로 이동 추적, 캐시 유지)
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
var handleCenterChanged = function () {
|
|
1494
|
+
var center = controller.getCurrBounds().getCenter();
|
|
1495
|
+
var curr = controller.positionToOffset(center);
|
|
1496
|
+
var prev = prevCenterOffsetRef.current; // 첫 번째 호출 시 이전 위치 저장만 하고 종료
|
|
1497
|
+
|
|
1498
|
+
if (!prev) {
|
|
1499
|
+
prevCenterOffsetRef.current = {
|
|
1500
|
+
x: curr.x,
|
|
1501
|
+
y: curr.y
|
|
1502
|
+
};
|
|
1503
|
+
return;
|
|
1504
|
+
} // 이전 위치와 현재 위치의 차이 계산 (이동 거리)
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
var dx = prev.x - curr.x;
|
|
1508
|
+
var dy = prev.y - curr.y; // 누적 이동 거리 저장 (transform으로 화면만 이동, 캐시는 유지하여 성능 최적화)
|
|
1509
|
+
|
|
1510
|
+
accumTranslateRef.current = {
|
|
1511
|
+
x: accumTranslateRef.current.x + dx,
|
|
1512
|
+
y: accumTranslateRef.current.y + dy
|
|
1513
|
+
};
|
|
1514
|
+
prevCenterOffsetRef.current = {
|
|
1515
|
+
x: curr.x,
|
|
1516
|
+
y: curr.y
|
|
1517
|
+
}; // CSS transform으로 컨테이너 이동 (캐시된 좌표는 그대로 유지)
|
|
1518
|
+
|
|
1519
|
+
if (containerRef.current) {
|
|
1520
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
var handleDragStart = function () {// 커서는 각 컴포넌트에서 처리
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
var handleDragEnd = function () {// 커서는 각 컴포넌트에서 처리
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
return {
|
|
1531
|
+
handleIdle: handleIdle,
|
|
1532
|
+
handleZoomStart: handleZoomStart,
|
|
1533
|
+
handleZoomEnd: handleZoomEnd,
|
|
1534
|
+
handleCenterChanged: handleCenterChanged,
|
|
1535
|
+
handleDragStart: handleDragStart,
|
|
1536
|
+
handleDragEnd: handleDragEnd
|
|
1537
|
+
};
|
|
1538
|
+
};
|
|
1539
|
+
/**
|
|
1540
|
+
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
1541
|
+
*
|
|
1542
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1543
|
+
* @param data 공간 인덱스에 삽입할 데이터 배열
|
|
1544
|
+
* @param spatialIndex Spatial Hash Grid 인스턴스
|
|
1545
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1546
|
+
*/
|
|
1547
|
+
|
|
1548
|
+
var buildSpatialIndex = function (data, spatialIndex, computeBoundingBox) {
|
|
1549
|
+
spatialIndex.clear();
|
|
1550
|
+
|
|
1551
|
+
for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
|
|
1552
|
+
var item = data_1[_i];
|
|
1553
|
+
var bbox = computeBoundingBox(item);
|
|
1554
|
+
|
|
1555
|
+
if (bbox) {
|
|
1556
|
+
spatialIndex.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
/**
|
|
1561
|
+
* 선택 상태 동기화 (화면 밖 데이터도 선택 상태 유지)
|
|
1562
|
+
*
|
|
1563
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1564
|
+
* @param data 최신 데이터 배열
|
|
1565
|
+
* @param selectedIds 선택된 항목 ID Set
|
|
1566
|
+
* @param selectedItemsMap 현재 선택된 항목 Map
|
|
1567
|
+
* @returns 업데이트된 선택된 항목 Map
|
|
1568
|
+
*/
|
|
1569
|
+
|
|
1570
|
+
var syncSelectedItems = function (data, selectedIds, selectedItemsMap) {
|
|
1571
|
+
var dataMap = new Map(data.map(function (m) {
|
|
1572
|
+
return [m.id, m];
|
|
1573
|
+
}));
|
|
1574
|
+
var newSelectedItemsMap = new Map();
|
|
1575
|
+
selectedIds.forEach(function (id) {
|
|
1576
|
+
// 현재 data에 있으면 최신 데이터 사용
|
|
1577
|
+
var currentItem = dataMap.get(id);
|
|
1578
|
+
|
|
1579
|
+
if (currentItem) {
|
|
1580
|
+
newSelectedItemsMap.set(id, currentItem);
|
|
1581
|
+
} else {
|
|
1582
|
+
// 화면 밖이면 기존 데이터 유지
|
|
1583
|
+
var prevItem = selectedItemsMap.get(id);
|
|
1584
|
+
|
|
1585
|
+
if (prevItem) {
|
|
1586
|
+
newSelectedItemsMap.set(id, prevItem);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
return newSelectedItemsMap;
|
|
1591
|
+
};
|
|
1592
|
+
/**
|
|
1593
|
+
* 외부 selectedItems를 내부 상태로 동기화
|
|
1594
|
+
*
|
|
1595
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1596
|
+
* @param externalSelectedItems 외부에서 전달된 선택된 항목 배열
|
|
1597
|
+
* @param selectedIdsRef 선택된 ID Set ref
|
|
1598
|
+
* @param selectedItemsMapRef 선택된 항목 Map ref
|
|
1599
|
+
*/
|
|
1600
|
+
|
|
1601
|
+
var syncExternalSelectedItems = function (externalSelectedItems, selectedIdsRef, selectedItemsMapRef) {
|
|
1602
|
+
if (externalSelectedItems === undefined) return;
|
|
1603
|
+
var newSelectedIds = new Set();
|
|
1604
|
+
var newSelectedItemsMap = new Map();
|
|
1605
|
+
externalSelectedItems.forEach(function (item) {
|
|
1606
|
+
newSelectedIds.add(item.id);
|
|
1607
|
+
newSelectedItemsMap.set(item.id, item);
|
|
1608
|
+
});
|
|
1609
|
+
selectedIdsRef.current = newSelectedIds;
|
|
1610
|
+
selectedItemsMapRef.current = newSelectedItemsMap;
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* 이벤트 유효성 검증 및 좌표 변환
|
|
1615
|
+
*
|
|
1616
|
+
* @param event 이벤트 파라미터
|
|
1617
|
+
* @param context WoongCanvasContext 인스턴스
|
|
1618
|
+
* @param controller MintMapController 인스턴스
|
|
1619
|
+
* @returns 유효한 화면 좌표 또는 null
|
|
1620
|
+
*/
|
|
1621
|
+
var validateEvent = function (event, context, controller) {
|
|
1622
|
+
var _a;
|
|
1623
|
+
|
|
1624
|
+
if (context) return null;
|
|
1625
|
+
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return null;
|
|
1626
|
+
|
|
1627
|
+
try {
|
|
1628
|
+
return controller.positionToOffset(event.param.position);
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
console.error('[WoongCanvas] validateEvent error:', error);
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
/**
|
|
1635
|
+
* Map의 values를 배열로 변환
|
|
1636
|
+
*
|
|
1637
|
+
* @template T Map 값의 타입
|
|
1638
|
+
* @param map 변환할 Map
|
|
1639
|
+
* @returns Map의 값 배열
|
|
1640
|
+
*/
|
|
1641
|
+
|
|
1642
|
+
var mapValuesToArray = function (map) {
|
|
1643
|
+
if (map.size === 0) return [];
|
|
1644
|
+
return Array.from(map.values());
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
var cn$3 = classNames.bind(styles$1);
|
|
1648
|
+
function MintMapCore(_a) {
|
|
1649
|
+
var _this = this;
|
|
1650
|
+
|
|
1651
|
+
var onLoad = _a.onLoad,
|
|
1652
|
+
_b = _a.visible,
|
|
1653
|
+
visible = _b === void 0 ? true : _b,
|
|
1654
|
+
zoomLevel = _a.zoomLevel,
|
|
1655
|
+
center = _a.center,
|
|
1656
|
+
_c = _a.centerMoveWithPanning,
|
|
1657
|
+
centerMoveWithPanning = _c === void 0 ? false : _c,
|
|
1658
|
+
children = _a.children; //controller
|
|
1659
|
+
|
|
1660
|
+
var controller = useMintMapController(); //맵 초기화
|
|
1661
|
+
|
|
1662
|
+
var elementRef = useRef(null);
|
|
1663
|
+
|
|
1664
|
+
var _d = useState(false),
|
|
1665
|
+
mapInitialized = _d[0],
|
|
1666
|
+
setMapInitialized = _d[1];
|
|
1667
|
+
|
|
1668
|
+
var currMapInitialized = useRef(false);
|
|
1669
|
+
useEffect(function () {
|
|
1670
|
+
(function () {
|
|
1671
|
+
return __awaiter(_this, void 0, void 0, function () {
|
|
1672
|
+
var map_1;
|
|
1673
|
+
return __generator(this, function (_a) {
|
|
1674
|
+
switch (_a.label) {
|
|
1675
|
+
case 0:
|
|
1676
|
+
if (!(elementRef && elementRef.current)) return [3
|
|
1677
|
+
/*break*/
|
|
1678
|
+
, 2];
|
|
1679
|
+
return [4
|
|
1680
|
+
/*yield*/
|
|
1681
|
+
, controller.initializingMap(elementRef.current)];
|
|
1682
|
+
|
|
1683
|
+
case 1:
|
|
1684
|
+
map_1 = _a.sent();
|
|
1685
|
+
|
|
1686
|
+
if (!currMapInitialized.current) {
|
|
1687
|
+
currMapInitialized.current = true; //onload callback (setTimeout 으로 맵이 초기화 될 텀을 준다. 특히 google map..)
|
|
1688
|
+
|
|
1689
|
+
setTimeout(function () {
|
|
1690
|
+
// console.log('setMapInitialized true');
|
|
1691
|
+
setMapInitialized(true);
|
|
1692
|
+
onLoad && onLoad(map_1, controller);
|
|
1693
|
+
}, 100);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
_a.label = 2;
|
|
1697
|
+
|
|
1698
|
+
case 2:
|
|
1699
|
+
return [2
|
|
1700
|
+
/*return*/
|
|
1701
|
+
];
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
});
|
|
1705
|
+
})();
|
|
1706
|
+
}, [controller, elementRef]); //줌레벨
|
|
1707
|
+
|
|
1708
|
+
useEffect(function () {
|
|
1709
|
+
if (zoomLevel && controller && mapInitialized) {
|
|
1710
|
+
var prevZoomLevel = controller === null || controller === void 0 ? void 0 : controller.getZoomLevel();
|
|
1711
|
+
|
|
1712
|
+
if (prevZoomLevel !== zoomLevel) {
|
|
1713
|
+
controller === null || controller === void 0 ? void 0 : controller.setZoomLevel(zoomLevel);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}, [zoomLevel]); //센터
|
|
1717
|
+
|
|
1718
|
+
useEffect(function () {
|
|
1719
|
+
if (center && controller && mapInitialized) {
|
|
1720
|
+
var prevCenter = controller.getCenter();
|
|
1721
|
+
|
|
1722
|
+
if (!Position.equals(prevCenter, center)) {
|
|
1723
|
+
centerMoveWithPanning ? controller === null || controller === void 0 ? void 0 : controller.panningTo(center) : controller === null || controller === void 0 ? void 0 : controller.setCenter(center);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}, [center]);
|
|
1727
|
+
return React.createElement("div", {
|
|
1728
|
+
className: cn$3('mint-map-root')
|
|
1729
|
+
}, mapInitialized && React.createElement(WoongCanvasProvider, null, children), React.createElement("div", {
|
|
1467
1730
|
className: cn$3('mint-map-container'),
|
|
1468
1731
|
style: {
|
|
1469
1732
|
visibility: visible ? 'inherit' : 'hidden'
|
|
@@ -5321,602 +5584,1138 @@ function LoadingImage(_a) {
|
|
|
5321
5584
|
}))));
|
|
5322
5585
|
}
|
|
5323
5586
|
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
renderAnimation = _a.renderAnimation,
|
|
5347
|
-
renderEvent = _a.renderEvent,
|
|
5348
|
-
_b = _a.enableMultiSelect,
|
|
5349
|
-
enableMultiSelect = _b === void 0 ? false : _b,
|
|
5350
|
-
_c = _a.topOnHover,
|
|
5351
|
-
topOnHover = _c === void 0 ? false : _c,
|
|
5352
|
-
_d = _a.enableViewportCulling,
|
|
5353
|
-
enableViewportCulling = _d === void 0 ? true : _d,
|
|
5354
|
-
_e = _a.cullingMargin,
|
|
5355
|
-
cullingMargin = _e === void 0 ? DEFAULT_CULLING_MARGIN : _e,
|
|
5356
|
-
_f = _a.maxCacheSize,
|
|
5357
|
-
maxCacheSize = _f === void 0 ? DEFAULT_MAX_CACHE_SIZE : _f,
|
|
5358
|
-
externalSelectedItems = _a.selectedItems,
|
|
5359
|
-
options = __rest(_a, ["markers", "dataType", "onClick", "onMouseOver", "onMouseOut", "renderBase", "renderAnimation", "renderEvent", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems"]); // --------------------------------------------------------------------------
|
|
5360
|
-
// Hooks & Context
|
|
5361
|
-
// --------------------------------------------------------------------------
|
|
5362
|
-
|
|
5587
|
+
var WoongCanvasMarker = function (props) {
|
|
5588
|
+
var data = props.data,
|
|
5589
|
+
onClick = props.onClick,
|
|
5590
|
+
onMouseOver = props.onMouseOver,
|
|
5591
|
+
onMouseOut = props.onMouseOut,
|
|
5592
|
+
_a = props.enableMultiSelect,
|
|
5593
|
+
enableMultiSelect = _a === void 0 ? false : _a,
|
|
5594
|
+
_b = props.topOnHover,
|
|
5595
|
+
topOnHover = _b === void 0 ? false : _b,
|
|
5596
|
+
_c = props.enableViewportCulling,
|
|
5597
|
+
enableViewportCulling = _c === void 0 ? false : _c,
|
|
5598
|
+
_d = props.cullingMargin,
|
|
5599
|
+
cullingMargin = _d === void 0 ? DEFAULT_CULLING_MARGIN : _d,
|
|
5600
|
+
_e = props.maxCacheSize,
|
|
5601
|
+
maxCacheSize = _e === void 0 ? DEFAULT_MAX_CACHE_SIZE : _e,
|
|
5602
|
+
externalSelectedItems = props.selectedItems,
|
|
5603
|
+
externalSelectedItem = props.selectedItem,
|
|
5604
|
+
_f = props.disableInteraction,
|
|
5605
|
+
disableInteraction = _f === void 0 ? false : _f,
|
|
5606
|
+
renderBase = props.renderBase,
|
|
5607
|
+
renderEvent = props.renderEvent,
|
|
5608
|
+
options = __rest(props, ["data", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "renderBase", "renderEvent"]);
|
|
5363
5609
|
|
|
5364
5610
|
var controller = useMintMapController();
|
|
5365
|
-
var context =
|
|
5366
|
-
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; //
|
|
5367
|
-
// DOM Refs
|
|
5368
|
-
// --------------------------------------------------------------------------
|
|
5611
|
+
var context = useWoongCanvasContext();
|
|
5612
|
+
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // DOM Refs
|
|
5369
5613
|
|
|
5370
5614
|
var divRef = useRef(document.createElement('div'));
|
|
5371
5615
|
var divElement = divRef.current;
|
|
5372
5616
|
var containerRef = useRef(null);
|
|
5373
|
-
var markerRef = useRef(); //
|
|
5374
|
-
// Konva Refs
|
|
5375
|
-
// --------------------------------------------------------------------------
|
|
5617
|
+
var markerRef = useRef(); // Konva Refs
|
|
5376
5618
|
|
|
5377
5619
|
var stageRef = useRef(null);
|
|
5378
5620
|
var baseLayerRef = useRef(null);
|
|
5379
|
-
var
|
|
5380
|
-
var eventLayerRef = useRef(null); // --------------------------------------------------------------------------
|
|
5381
|
-
// Data Refs - 선택 및 Hover 상태 관리
|
|
5382
|
-
// --------------------------------------------------------------------------
|
|
5383
|
-
|
|
5384
|
-
/** markers prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
5385
|
-
|
|
5386
|
-
var markersRef = useRef(markers); // --------------------------------------------------------------------------
|
|
5387
|
-
// State Refs - 선택 및 Hover 상태 관리
|
|
5388
|
-
// --------------------------------------------------------------------------
|
|
5389
|
-
|
|
5390
|
-
/** 현재 Hover 중인 항목 */
|
|
5621
|
+
var eventLayerRef = useRef(null); // 상태 관리 Refs (React 리렌더링 최소화)
|
|
5391
5622
|
|
|
5623
|
+
var dataRef = useRef(data);
|
|
5624
|
+
var disableInteractionRef = useRef(disableInteraction);
|
|
5625
|
+
var enableViewportCullingRef = useRef(enableViewportCulling);
|
|
5392
5626
|
var hoveredItemRef = useRef(null);
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
var lastClickedItemRef = useRef(null);
|
|
5396
|
-
/**
|
|
5397
|
-
* 선택된 항목의 ID Set
|
|
5398
|
-
*
|
|
5399
|
-
* 용도:
|
|
5400
|
-
* 1. onClick 콜백에 전달 - onClick(data, selectedIdsRef.current)
|
|
5401
|
-
* 2. 선택 여부 빠른 체크 - selectedIdsRef.current.has(id)
|
|
5402
|
-
* 3. 메모리 효율 - ID만 저장 (작음)
|
|
5403
|
-
*
|
|
5404
|
-
* selectedItemsMapRef와 차이:
|
|
5405
|
-
* - selectedIdsRef: ID만 저장 { "id1", "id2" }
|
|
5406
|
-
* - selectedItemsMapRef: 전체 객체 저장 { id1: {...}, id2: {...} }
|
|
5407
|
-
*
|
|
5408
|
-
* 둘 다 필요: ID만 필요한 곳은 이것, 전체 데이터 필요한 곳은 Map
|
|
5409
|
-
*/
|
|
5410
|
-
|
|
5627
|
+
var selectedItemRef = useRef(externalSelectedItem);
|
|
5411
5628
|
var selectedIdsRef = useRef(new Set());
|
|
5412
|
-
|
|
5413
|
-
* 선택된 항목의 실제 데이터 Map (핵심 성능 최적화!)
|
|
5414
|
-
*
|
|
5415
|
-
* 목적: doRenderEvent에서 filter() 순회 제거
|
|
5416
|
-
* - 이전: markersRef.current.filter() → O(전체 마커 수)
|
|
5417
|
-
* - 현재: Map.values() → O(선택된 항목 수)
|
|
5418
|
-
*
|
|
5419
|
-
* 성능 개선: 10,000개 중 1개 선택 시
|
|
5420
|
-
* - 이전: 10,000번 체크
|
|
5421
|
-
* - 현재: 1번 접근 (10,000배 빠름!)
|
|
5422
|
-
*/
|
|
5423
|
-
|
|
5424
|
-
var selectedItemsMapRef = useRef(new Map()); // --------------------------------------------------------------------------
|
|
5425
|
-
// Drag Refs
|
|
5426
|
-
// --------------------------------------------------------------------------
|
|
5629
|
+
var selectedItemsMapRef = useRef(new Map()); // 드래그 상태 Refs
|
|
5427
5630
|
|
|
5428
5631
|
var draggingRef = useRef(false);
|
|
5429
5632
|
var prevCenterOffsetRef = useRef(null);
|
|
5430
5633
|
var accumTranslateRef = useRef({
|
|
5431
5634
|
x: 0,
|
|
5432
5635
|
y: 0
|
|
5433
|
-
}); //
|
|
5434
|
-
// Performance Refs (캐싱 & 최적화)
|
|
5435
|
-
// --------------------------------------------------------------------------
|
|
5436
|
-
|
|
5437
|
-
/** 좌표 변환 결과 LRU 캐시 */
|
|
5636
|
+
}); // 성능 최적화 Refs
|
|
5438
5637
|
|
|
5439
5638
|
var offsetCacheRef = useRef(new LRUCache(maxCacheSize));
|
|
5440
|
-
/** 공간 인덱스 (빠른 Hit Test) */
|
|
5441
|
-
|
|
5442
5639
|
var spatialIndexRef = useRef(new SpatialHashGrid(SPATIAL_GRID_CELL_SIZE));
|
|
5443
|
-
/** 바운딩 박스 캐시 (Viewport Culling 최적화) */
|
|
5444
|
-
|
|
5445
5640
|
var boundingBoxCacheRef = useRef(new Map());
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
var viewportRef = useRef(null); // --------------------------------------------------------------------------
|
|
5449
|
-
// 유틸리티 함수: 뷰포트 관리
|
|
5450
|
-
// --------------------------------------------------------------------------
|
|
5451
|
-
|
|
5452
|
-
/**
|
|
5453
|
-
* 현재 뷰포트 영역 계산
|
|
5454
|
-
*/
|
|
5455
|
-
|
|
5456
|
-
var updateViewport = function () {
|
|
5457
|
-
if (!stageRef.current) return;
|
|
5458
|
-
var stage = stageRef.current;
|
|
5459
|
-
viewportRef.current = {
|
|
5460
|
-
minX: -cullingMargin,
|
|
5461
|
-
maxX: stage.width() + cullingMargin,
|
|
5462
|
-
minY: -cullingMargin,
|
|
5463
|
-
maxY: stage.height() + cullingMargin
|
|
5464
|
-
};
|
|
5465
|
-
};
|
|
5466
|
-
/**
|
|
5467
|
-
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
5468
|
-
*/
|
|
5641
|
+
var viewportRef = useRef(null); // 뷰포트 영역 계산 (Viewport Culling용)
|
|
5469
5642
|
|
|
5643
|
+
var updateViewport$1 = function () {
|
|
5644
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
5645
|
+
}; // 뷰포트 내부 여부 확인 (바운딩 박스 캐싱)
|
|
5470
5646
|
|
|
5471
|
-
var isInViewport = function (item) {
|
|
5472
|
-
if (!enableViewportCulling || !viewportRef.current) return true;
|
|
5473
|
-
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
5474
5647
|
|
|
5475
|
-
|
|
5648
|
+
var isInViewport$1 = function (item) {
|
|
5649
|
+
return isInViewport(item, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
5650
|
+
}; // 마커 좌표 변환 (위경도 → 화면 좌표, LRU 캐시 사용)
|
|
5476
5651
|
|
|
5477
|
-
if (!bbox) {
|
|
5478
|
-
// 폴리곤인 경우
|
|
5479
|
-
if (dataType === CanvasDataType.POLYGON) {
|
|
5480
|
-
var offsets = getOrComputePolygonOffsets(item);
|
|
5481
|
-
if (!offsets) return false; // 바운딩 박스 계산 (최적화: 직접 비교)
|
|
5482
5652
|
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5653
|
+
var getOrComputeMarkerOffset = function (markerData) {
|
|
5654
|
+
var cached = offsetCacheRef.current.get(markerData.id);
|
|
5655
|
+
if (cached && !Array.isArray(cached)) return cached;
|
|
5656
|
+
var result = computeMarkerOffset(markerData, controller);
|
|
5657
|
+
if (!result) return null;
|
|
5658
|
+
offsetCacheRef.current.set(markerData.id, result);
|
|
5659
|
+
return result;
|
|
5660
|
+
}; // 마커 바운딩 박스 계산 (Viewport Culling 및 Hit Test용, 오프셋 지원)
|
|
5487
5661
|
|
|
5488
|
-
for (var _i = 0, offsets_1 = offsets; _i < offsets_1.length; _i++) {
|
|
5489
|
-
var multiPolygon = offsets_1[_i];
|
|
5490
5662
|
|
|
5491
|
-
|
|
5492
|
-
|
|
5663
|
+
var computeBoundingBox = function (item) {
|
|
5664
|
+
var offset = getOrComputeMarkerOffset(item);
|
|
5665
|
+
if (!offset) return null;
|
|
5666
|
+
var boxWidth = item.boxWidth || 50;
|
|
5667
|
+
var boxHeight = item.boxHeight || 28;
|
|
5668
|
+
var tailHeight = item.tailHeight || 0;
|
|
5669
|
+
var offsetX = item.offsetX || 0;
|
|
5670
|
+
var offsetY = item.offsetY || 0; // 오프셋을 적용한 마커 중심점 기준으로 바운딩 박스 계산
|
|
5493
5671
|
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
if (y > maxY) maxY = y;
|
|
5502
|
-
}
|
|
5503
|
-
}
|
|
5504
|
-
}
|
|
5672
|
+
return {
|
|
5673
|
+
minX: offset.x + offsetX - boxWidth / 2,
|
|
5674
|
+
minY: offset.y + offsetY - boxHeight - tailHeight,
|
|
5675
|
+
maxX: offset.x + offsetX + boxWidth / 2,
|
|
5676
|
+
maxY: offset.y + offsetY
|
|
5677
|
+
};
|
|
5678
|
+
}; // 공간 인덱스 빌드 (빠른 Hit Test용)
|
|
5505
5679
|
|
|
5506
|
-
bbox = {
|
|
5507
|
-
minX: minX,
|
|
5508
|
-
minY: minY,
|
|
5509
|
-
maxX: maxX,
|
|
5510
|
-
maxY: maxY
|
|
5511
|
-
};
|
|
5512
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
5513
|
-
} // 마커인 경우
|
|
5514
|
-
else {
|
|
5515
|
-
var offset = getOrComputeMarkerOffset(item);
|
|
5516
|
-
if (!offset) return false;
|
|
5517
|
-
var boxWidth = item.boxWidth || 50;
|
|
5518
|
-
var boxHeight = item.boxHeight || 28;
|
|
5519
|
-
bbox = {
|
|
5520
|
-
minX: offset.x - boxWidth / 2,
|
|
5521
|
-
minY: offset.y - boxHeight - 6,
|
|
5522
|
-
maxX: offset.x + boxWidth / 2,
|
|
5523
|
-
maxY: offset.y
|
|
5524
|
-
};
|
|
5525
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
5526
|
-
}
|
|
5527
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
5528
5680
|
|
|
5681
|
+
var buildSpatialIndex$1 = function () {
|
|
5682
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
5683
|
+
}; // 렌더링 유틸리티 객체
|
|
5529
5684
|
|
|
5530
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
5531
|
-
}; // --------------------------------------------------------------------------
|
|
5532
|
-
// 유틸리티 함수: 좌표 변환 캐싱
|
|
5533
|
-
// --------------------------------------------------------------------------
|
|
5534
5685
|
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5686
|
+
var renderUtils = {
|
|
5687
|
+
getOrComputePolygonOffsets: function () {
|
|
5688
|
+
return null;
|
|
5689
|
+
},
|
|
5690
|
+
getOrComputeMarkerOffset: getOrComputeMarkerOffset
|
|
5691
|
+
}; // Base Layer 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
5540
5692
|
|
|
5693
|
+
var doRenderBase = function () {
|
|
5694
|
+
var layer = baseLayerRef.current;
|
|
5695
|
+
if (!layer) return;
|
|
5696
|
+
var shape = layer.findOne('.base-render-shape');
|
|
5541
5697
|
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5698
|
+
if (!shape) {
|
|
5699
|
+
shape = new Konva.Shape({
|
|
5700
|
+
name: 'base-render-shape',
|
|
5701
|
+
sceneFunc: function (context, shape) {
|
|
5702
|
+
var ctx = context;
|
|
5703
|
+
var hovered = hoveredItemRef.current; // 뷰포트 컬링: 화면에 보이는 항목만 필터링
|
|
5546
5704
|
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5705
|
+
var visibleItems = enableViewportCullingRef.current ? dataRef.current.filter(function (item) {
|
|
5706
|
+
return isInViewport$1(item);
|
|
5707
|
+
}) : dataRef.current; // topOnHover 옵션: hover된 항목을 나중에 그려서 최상위에 표시
|
|
5550
5708
|
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
* @returns 변환된 좌표 또는 null
|
|
5557
|
-
*/
|
|
5709
|
+
if (topOnHover && !renderEvent && hovered) {
|
|
5710
|
+
visibleItems = visibleItems.filter(function (item) {
|
|
5711
|
+
return item.id !== hovered.id;
|
|
5712
|
+
});
|
|
5713
|
+
} // 일반 항목들 먼저 렌더링
|
|
5558
5714
|
|
|
5559
5715
|
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5716
|
+
renderBase({
|
|
5717
|
+
ctx: ctx,
|
|
5718
|
+
items: visibleItems,
|
|
5719
|
+
selectedIds: selectedIdsRef.current,
|
|
5720
|
+
hoveredItem: hovered,
|
|
5721
|
+
utils: renderUtils
|
|
5722
|
+
}); // hover된 항목을 마지막에 렌더링하여 최상위에 표시
|
|
5723
|
+
|
|
5724
|
+
if (topOnHover && !renderEvent && hovered) {
|
|
5725
|
+
if (!enableViewportCullingRef.current || isInViewport$1(hovered)) {
|
|
5726
|
+
renderBase({
|
|
5727
|
+
ctx: ctx,
|
|
5728
|
+
items: [hovered],
|
|
5729
|
+
selectedIds: selectedIdsRef.current,
|
|
5730
|
+
hoveredItem: hovered,
|
|
5731
|
+
utils: renderUtils
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
},
|
|
5736
|
+
perfectDrawEnabled: false,
|
|
5737
|
+
listening: false,
|
|
5738
|
+
hitStrokeWidth: 0
|
|
5739
|
+
});
|
|
5740
|
+
layer.add(shape);
|
|
5741
|
+
}
|
|
5742
|
+
|
|
5743
|
+
layer.batchDraw();
|
|
5744
|
+
}; // Event Layer 렌더링 (hover 효과 및 선택 상태 표시)
|
|
5564
5745
|
|
|
5565
|
-
|
|
5566
|
-
|
|
5746
|
+
|
|
5747
|
+
var doRenderEvent = function () {
|
|
5748
|
+
var layer = eventLayerRef.current;
|
|
5749
|
+
if (!layer || !renderEvent) return;
|
|
5750
|
+
var shape = layer.findOne('.event-render-shape');
|
|
5751
|
+
|
|
5752
|
+
if (!shape) {
|
|
5753
|
+
shape = new Konva.Shape({
|
|
5754
|
+
name: 'event-render-shape',
|
|
5755
|
+
sceneFunc: function (context, shape) {
|
|
5756
|
+
var ctx = context;
|
|
5757
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
5758
|
+
var hovered = hoveredItemRef.current;
|
|
5759
|
+
|
|
5760
|
+
if (topOnHover && hovered) {
|
|
5761
|
+
renderEvent({
|
|
5762
|
+
ctx: ctx,
|
|
5763
|
+
hoveredItem: null,
|
|
5764
|
+
utils: renderUtils,
|
|
5765
|
+
selectedItems: selectedItems.filter(function (item) {
|
|
5766
|
+
return item.id !== hovered.id;
|
|
5767
|
+
}),
|
|
5768
|
+
selectedItem: selectedItemRef.current
|
|
5769
|
+
});
|
|
5770
|
+
|
|
5771
|
+
if (!enableViewportCullingRef.current || isInViewport$1(hovered)) {
|
|
5772
|
+
var hoveredIsSelected = selectedItems.some(function (item) {
|
|
5773
|
+
return item.id === hovered.id;
|
|
5774
|
+
});
|
|
5775
|
+
var hoverSelectedItems = hoveredIsSelected ? [hovered] : [];
|
|
5776
|
+
renderEvent({
|
|
5777
|
+
ctx: ctx,
|
|
5778
|
+
hoveredItem: hovered,
|
|
5779
|
+
utils: renderUtils,
|
|
5780
|
+
selectedItems: hoverSelectedItems,
|
|
5781
|
+
selectedItem: selectedItemRef.current
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
} else {
|
|
5785
|
+
renderEvent({
|
|
5786
|
+
ctx: ctx,
|
|
5787
|
+
hoveredItem: hovered,
|
|
5788
|
+
utils: renderUtils,
|
|
5789
|
+
selectedItems: selectedItems,
|
|
5790
|
+
selectedItem: selectedItemRef.current
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
},
|
|
5794
|
+
perfectDrawEnabled: false,
|
|
5795
|
+
listening: false,
|
|
5796
|
+
hitStrokeWidth: 0
|
|
5797
|
+
});
|
|
5798
|
+
layer.add(shape);
|
|
5567
5799
|
}
|
|
5568
5800
|
|
|
5569
|
-
|
|
5570
|
-
}; //
|
|
5571
|
-
// 유틸리티 함수: 공간 인덱싱
|
|
5572
|
-
// --------------------------------------------------------------------------
|
|
5801
|
+
layer.batchDraw();
|
|
5802
|
+
}; // 전체 즉시 렌더링
|
|
5573
5803
|
|
|
5574
|
-
/**
|
|
5575
|
-
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
5576
|
-
*/
|
|
5577
5804
|
|
|
5805
|
+
var renderAllImmediate = function () {
|
|
5806
|
+
if (enableViewportCullingRef.current) {
|
|
5807
|
+
updateViewport$1();
|
|
5808
|
+
}
|
|
5578
5809
|
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5810
|
+
buildSpatialIndex$1();
|
|
5811
|
+
doRenderBase();
|
|
5812
|
+
doRenderEvent();
|
|
5813
|
+
}; // 지도 이벤트 핸들러 생성
|
|
5814
|
+
|
|
5815
|
+
|
|
5816
|
+
var _g = createMapEventHandlers({
|
|
5817
|
+
controller: controller,
|
|
5818
|
+
containerRef: containerRef,
|
|
5819
|
+
markerRef: markerRef,
|
|
5820
|
+
options: options,
|
|
5821
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
5822
|
+
accumTranslateRef: accumTranslateRef,
|
|
5823
|
+
offsetCacheRef: offsetCacheRef,
|
|
5824
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
5825
|
+
renderAllImmediate: renderAllImmediate
|
|
5826
|
+
}),
|
|
5827
|
+
handleIdle = _g.handleIdle,
|
|
5828
|
+
handleZoomStart = _g.handleZoomStart,
|
|
5829
|
+
handleZoomEnd = _g.handleZoomEnd,
|
|
5830
|
+
handleCenterChanged = _g.handleCenterChanged,
|
|
5831
|
+
handleDragStartShared = _g.handleDragStart,
|
|
5832
|
+
handleDragEndShared = _g.handleDragEnd;
|
|
5583
5833
|
|
|
5584
|
-
|
|
5585
|
-
|
|
5834
|
+
var handleDragStart = function () {
|
|
5835
|
+
handleDragStartShared();
|
|
5836
|
+
draggingRef.current = true;
|
|
5837
|
+
controller.setMapCursor('grabbing');
|
|
5838
|
+
};
|
|
5586
5839
|
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5840
|
+
var handleDragEnd = function () {
|
|
5841
|
+
handleDragEndShared();
|
|
5842
|
+
draggingRef.current = false;
|
|
5843
|
+
controller.setMapCursor('grab');
|
|
5844
|
+
}; // Hit Test: 특정 좌표의 마커 찾기
|
|
5590
5845
|
|
|
5591
|
-
if (offsets) {
|
|
5592
|
-
var minX = Infinity,
|
|
5593
|
-
minY = Infinity,
|
|
5594
|
-
maxX = -Infinity,
|
|
5595
|
-
maxY = -Infinity;
|
|
5596
5846
|
|
|
5597
|
-
|
|
5598
|
-
|
|
5847
|
+
var findData = function (offset) {
|
|
5848
|
+
// topOnHover 옵션이 켜져 있으면 hover된 항목을 최우선으로 확인
|
|
5849
|
+
if (topOnHover && hoveredItemRef.current) {
|
|
5850
|
+
var hovered = hoveredItemRef.current;
|
|
5599
5851
|
|
|
5600
|
-
|
|
5601
|
-
|
|
5852
|
+
if (isPointInMarkerData(offset, hovered, getOrComputeMarkerOffset)) {
|
|
5853
|
+
return hovered;
|
|
5854
|
+
}
|
|
5855
|
+
} // 공간 인덱스에서 후보 항목 조회 (O(1) 수준의 빠른 조회)
|
|
5602
5856
|
|
|
5603
|
-
for (var _c = 0, polygonGroup_2 = polygonGroup; _c < polygonGroup_2.length; _c++) {
|
|
5604
|
-
var _d = polygonGroup_2[_c],
|
|
5605
|
-
x = _d[0],
|
|
5606
|
-
y = _d[1];
|
|
5607
|
-
if (x < minX) minX = x;
|
|
5608
|
-
if (y < minY) minY = y;
|
|
5609
|
-
if (x > maxX) maxX = x;
|
|
5610
|
-
if (y > maxY) maxY = y;
|
|
5611
|
-
}
|
|
5612
|
-
}
|
|
5613
|
-
}
|
|
5614
5857
|
|
|
5615
|
-
|
|
5616
|
-
|
|
5858
|
+
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 역순 순회: 나중에 추가된 항목(최상위)이 먼저 선택되도록
|
|
5859
|
+
|
|
5860
|
+
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
5861
|
+
var item = candidates[i];
|
|
5862
|
+
|
|
5863
|
+
if (isPointInMarkerData(offset, item, getOrComputeMarkerOffset)) {
|
|
5864
|
+
return item;
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
|
|
5868
|
+
return null;
|
|
5869
|
+
}; // Hover 상태 설정 및 렌더링
|
|
5870
|
+
|
|
5871
|
+
|
|
5872
|
+
var setHovered = function (data) {
|
|
5873
|
+
hoveredItemRef.current = data;
|
|
5874
|
+
|
|
5875
|
+
if (draggingRef.current) {
|
|
5876
|
+
controller.setMapCursor('grabbing');
|
|
5877
|
+
} else {
|
|
5878
|
+
controller.setMapCursor(data ? 'pointer' : 'grab');
|
|
5879
|
+
}
|
|
5880
|
+
|
|
5881
|
+
if (renderEvent) {
|
|
5882
|
+
doRenderEvent();
|
|
5883
|
+
} else if (topOnHover) {
|
|
5884
|
+
doRenderBase();
|
|
5885
|
+
}
|
|
5886
|
+
}; // 클릭 처리: 선택 상태 업데이트
|
|
5887
|
+
|
|
5888
|
+
|
|
5889
|
+
var handleLocalClick = function (data) {
|
|
5890
|
+
if (enableMultiSelect) {
|
|
5891
|
+
var newSelected = new Set(selectedIdsRef.current);
|
|
5892
|
+
|
|
5893
|
+
if (newSelected.has(data.id)) {
|
|
5894
|
+
newSelected.delete(data.id);
|
|
5895
|
+
selectedItemsMapRef.current.delete(data.id);
|
|
5617
5896
|
} else {
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
if (offset) {
|
|
5622
|
-
var boxWidth = item.boxWidth || 50;
|
|
5623
|
-
var boxHeight = item.boxHeight || 28;
|
|
5624
|
-
var tailHeight = 6;
|
|
5625
|
-
var minX = offset.x - boxWidth / 2;
|
|
5626
|
-
var minY = offset.y - boxHeight - tailHeight;
|
|
5627
|
-
var maxX = offset.x + boxWidth / 2;
|
|
5628
|
-
var maxY = offset.y;
|
|
5629
|
-
spatial.insert(item, minX, minY, maxX, maxY);
|
|
5630
|
-
}
|
|
5897
|
+
newSelected.add(data.id);
|
|
5898
|
+
selectedItemsMapRef.current.set(data.id, data);
|
|
5631
5899
|
}
|
|
5900
|
+
|
|
5901
|
+
selectedIdsRef.current = newSelected;
|
|
5902
|
+
} else {
|
|
5903
|
+
var newSelected = new Set();
|
|
5904
|
+
|
|
5905
|
+
if (!selectedIdsRef.current.has(data.id)) {
|
|
5906
|
+
newSelected.add(data.id);
|
|
5907
|
+
selectedItemsMapRef.current.clear();
|
|
5908
|
+
selectedItemsMapRef.current.set(data.id, data);
|
|
5909
|
+
} else {
|
|
5910
|
+
selectedItemsMapRef.current.clear();
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5913
|
+
selectedIdsRef.current = newSelected;
|
|
5632
5914
|
}
|
|
5633
|
-
}; // --------------------------------------------------------------------------
|
|
5634
|
-
// 렌더링 함수
|
|
5635
|
-
// --------------------------------------------------------------------------
|
|
5636
5915
|
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5916
|
+
doRenderBase();
|
|
5917
|
+
doRenderEvent();
|
|
5918
|
+
}; // 클릭 이벤트 핸들러
|
|
5640
5919
|
|
|
5641
5920
|
|
|
5642
|
-
var
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5921
|
+
var handleClick = function (event) {
|
|
5922
|
+
if (disableInteractionRef.current) return;
|
|
5923
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
5924
|
+
if (!clickedOffset) return;
|
|
5925
|
+
var data = findData(clickedOffset);
|
|
5926
|
+
if (!data) return;
|
|
5927
|
+
handleLocalClick(data);
|
|
5928
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(data, selectedIdsRef.current);
|
|
5929
|
+
}; // 마우스 이동 이벤트 핸들러 (hover 감지)
|
|
5647
5930
|
|
|
5648
|
-
useRef(new Set());
|
|
5649
|
-
/**
|
|
5650
|
-
* Base 레이어 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
5651
|
-
*
|
|
5652
|
-
* 🔥 최적화:
|
|
5653
|
-
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
5654
|
-
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
5655
|
-
* 3. 클로저로 최신 데이터 참조
|
|
5656
|
-
*/
|
|
5657
5931
|
|
|
5658
|
-
var
|
|
5659
|
-
|
|
5660
|
-
|
|
5932
|
+
var handleMouseMove = function (event) {
|
|
5933
|
+
if (disableInteractionRef.current) return;
|
|
5934
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
5935
|
+
if (!mouseOffset) return;
|
|
5936
|
+
var hoveredItem = findData(mouseOffset);
|
|
5937
|
+
var prevHovered = hoveredItemRef.current;
|
|
5938
|
+
if (prevHovered === hoveredItem) return;
|
|
5939
|
+
setHovered(hoveredItem);
|
|
5940
|
+
if (prevHovered) onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
5941
|
+
if (hoveredItem) onMouseOver === null || onMouseOver === void 0 ? void 0 : onMouseOver(hoveredItem);
|
|
5942
|
+
}; // 마우스가 맵 영역을 벗어날 때 hover 상태 초기화
|
|
5661
5943
|
|
|
5662
|
-
var shape = layer.findOne('.base-render-shape');
|
|
5663
5944
|
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5945
|
+
var handleMouseLeave = function () {
|
|
5946
|
+
if (disableInteractionRef.current) return;
|
|
5947
|
+
var prevHovered = hoveredItemRef.current;
|
|
5948
|
+
if (!prevHovered) return;
|
|
5949
|
+
hoveredItemRef.current = null;
|
|
5950
|
+
controller.setMapCursor('grab');
|
|
5951
|
+
doRenderEvent();
|
|
5952
|
+
onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
5953
|
+
}; // DOM 초기화
|
|
5671
5954
|
|
|
5672
|
-
var visibleMarkers = enableViewportCulling ? markersRef.current.filter(function (item) {
|
|
5673
|
-
return isInViewport(item);
|
|
5674
|
-
}) : markersRef.current;
|
|
5675
|
-
renderBase({
|
|
5676
|
-
ctx: ctx,
|
|
5677
|
-
items: visibleMarkers,
|
|
5678
|
-
selectedIds: selectedIdsRef.current,
|
|
5679
|
-
utils: renderUtils
|
|
5680
|
-
});
|
|
5681
|
-
},
|
|
5682
|
-
perfectDrawEnabled: false,
|
|
5683
|
-
listening: false,
|
|
5684
|
-
hitStrokeWidth: 0
|
|
5685
|
-
});
|
|
5686
|
-
layer.add(shape);
|
|
5687
|
-
} // sceneFunc는 이미 설정되어 있으므로 다시 그리기만
|
|
5688
5955
|
|
|
5956
|
+
useEffect(function () {
|
|
5957
|
+
divElement.style.width = 'fit-content';
|
|
5958
|
+
return function () {
|
|
5959
|
+
if (!markerRef.current) return;
|
|
5960
|
+
controller.clearDrawable(markerRef.current);
|
|
5961
|
+
markerRef.current = undefined;
|
|
5962
|
+
};
|
|
5963
|
+
}, []); // 마커 생성/업데이트
|
|
5689
5964
|
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
* - 지도 이동 시에는 기존 Animation 계속 실행
|
|
5698
|
-
*/
|
|
5965
|
+
useEffect(function () {
|
|
5966
|
+
if (!options) return;
|
|
5967
|
+
var bounds = controller.getCurrBounds();
|
|
5968
|
+
|
|
5969
|
+
var markerOptions = __assign({
|
|
5970
|
+
position: bounds.nw
|
|
5971
|
+
}, options);
|
|
5699
5972
|
|
|
5973
|
+
if (markerRef.current) {
|
|
5974
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
5975
|
+
return;
|
|
5976
|
+
}
|
|
5700
5977
|
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5978
|
+
markerRef.current = new Marker(markerOptions);
|
|
5979
|
+
markerRef.current.element = divElement;
|
|
5980
|
+
controller.createMarker(markerRef.current);
|
|
5981
|
+
|
|
5982
|
+
if (divElement.parentElement) {
|
|
5983
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5986
|
+
if (options.zIndex !== undefined) {
|
|
5987
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
5988
|
+
}
|
|
5989
|
+
}, [options]); // Konva 초기화 및 이벤트 리스너 등록
|
|
5990
|
+
|
|
5991
|
+
useEffect(function () {
|
|
5992
|
+
var mapDiv = controller.mapDivElement;
|
|
5993
|
+
var stage = new Konva.Stage({
|
|
5994
|
+
container: containerRef.current,
|
|
5995
|
+
width: mapDiv.offsetWidth,
|
|
5996
|
+
height: mapDiv.offsetHeight
|
|
5710
5997
|
});
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5998
|
+
stageRef.current = stage;
|
|
5999
|
+
var baseLayer = new Konva.Layer({
|
|
6000
|
+
listening: false
|
|
6001
|
+
});
|
|
6002
|
+
var eventLayer = new Konva.Layer({
|
|
6003
|
+
listening: false
|
|
6004
|
+
});
|
|
6005
|
+
baseLayerRef.current = baseLayer;
|
|
6006
|
+
eventLayerRef.current = eventLayer;
|
|
6007
|
+
stage.add(baseLayer);
|
|
6008
|
+
stage.add(eventLayer);
|
|
5720
6009
|
|
|
6010
|
+
if (enableViewportCulling) {
|
|
6011
|
+
updateViewport$1();
|
|
6012
|
+
} // ResizeObserver: 맵 크기 변경 감지 (RAF로 debounce)
|
|
5721
6013
|
|
|
5722
|
-
var doRenderEvent = function () {
|
|
5723
|
-
if (!renderEvent) return;
|
|
5724
|
-
var layer = eventLayerRef.current;
|
|
5725
|
-
if (!layer) return; // 🔥 Shape 재사용: 이미 존재하면 재사용, 없으면 생성
|
|
5726
6014
|
|
|
5727
|
-
var
|
|
6015
|
+
var resizeRafId = null;
|
|
6016
|
+
var resizeObserver = new ResizeObserver(function () {
|
|
6017
|
+
if (resizeRafId !== null) {
|
|
6018
|
+
cancelAnimationFrame(resizeRafId);
|
|
6019
|
+
}
|
|
5728
6020
|
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
sceneFunc: function (context, shape) {
|
|
5735
|
-
var ctx = context; // 클로저로 최신 ref 값 참조
|
|
6021
|
+
resizeRafId = requestAnimationFrame(function () {
|
|
6022
|
+
stage.width(mapDiv.offsetWidth);
|
|
6023
|
+
stage.height(mapDiv.offsetHeight);
|
|
6024
|
+
offsetCacheRef.current.clear();
|
|
6025
|
+
boundingBoxCacheRef.current.clear();
|
|
5736
6026
|
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
lastClickedItem: lastClickedItemRef.current
|
|
5744
|
-
});
|
|
5745
|
-
},
|
|
5746
|
-
perfectDrawEnabled: false,
|
|
5747
|
-
listening: false,
|
|
5748
|
-
hitStrokeWidth: 0
|
|
6027
|
+
if (enableViewportCullingRef.current) {
|
|
6028
|
+
updateViewport$1();
|
|
6029
|
+
}
|
|
6030
|
+
|
|
6031
|
+
renderAllImmediate();
|
|
6032
|
+
resizeRafId = null;
|
|
5749
6033
|
});
|
|
5750
|
-
|
|
5751
|
-
|
|
6034
|
+
});
|
|
6035
|
+
resizeObserver.observe(mapDiv);
|
|
6036
|
+
controller.addEventListener('IDLE', handleIdle);
|
|
6037
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
6038
|
+
controller.addEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
6039
|
+
controller.addEventListener('CENTER_CHANGED', handleCenterChanged);
|
|
6040
|
+
controller.addEventListener('CLICK', handleClick);
|
|
6041
|
+
controller.addEventListener('MOUSEMOVE', handleMouseMove);
|
|
6042
|
+
controller.addEventListener('DRAGSTART', handleDragStart);
|
|
6043
|
+
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
6044
|
+
mapDiv.addEventListener('mouseleave', handleMouseLeave);
|
|
6045
|
+
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
5752
6046
|
|
|
6047
|
+
var componentInstance = null;
|
|
5753
6048
|
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
6049
|
+
if (context) {
|
|
6050
|
+
componentInstance = {
|
|
6051
|
+
zIndex: currentZIndex,
|
|
6052
|
+
hitTest: function (offset) {
|
|
6053
|
+
return findData(offset) !== null;
|
|
6054
|
+
},
|
|
6055
|
+
onClick: onClick,
|
|
6056
|
+
onMouseOver: onMouseOver,
|
|
6057
|
+
onMouseOut: onMouseOut,
|
|
6058
|
+
findData: findData,
|
|
6059
|
+
setHovered: setHovered,
|
|
6060
|
+
handleLocalClick: handleLocalClick,
|
|
6061
|
+
getSelectedIds: function () {
|
|
6062
|
+
return selectedIdsRef.current;
|
|
6063
|
+
},
|
|
6064
|
+
isInteractionDisabled: function () {
|
|
6065
|
+
return disableInteractionRef.current;
|
|
6066
|
+
}
|
|
6067
|
+
};
|
|
6068
|
+
context.registerComponent(componentInstance);
|
|
6069
|
+
}
|
|
5759
6070
|
|
|
6071
|
+
return function () {
|
|
6072
|
+
if (resizeRafId !== null) {
|
|
6073
|
+
cancelAnimationFrame(resizeRafId);
|
|
6074
|
+
}
|
|
5760
6075
|
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
6076
|
+
resizeObserver.disconnect();
|
|
6077
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
6078
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
6079
|
+
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
6080
|
+
controller.removeEventListener('CENTER_CHANGED', handleCenterChanged);
|
|
6081
|
+
controller.removeEventListener('CLICK', handleClick);
|
|
6082
|
+
controller.removeEventListener('MOUSEMOVE', handleMouseMove);
|
|
6083
|
+
controller.removeEventListener('DRAGSTART', handleDragStart);
|
|
6084
|
+
controller.removeEventListener('DRAGEND', handleDragEnd);
|
|
6085
|
+
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
6086
|
+
|
|
6087
|
+
if (context && componentInstance) {
|
|
6088
|
+
context.unregisterComponent(componentInstance);
|
|
6089
|
+
}
|
|
6090
|
+
|
|
6091
|
+
baseLayer.destroyChildren();
|
|
6092
|
+
eventLayer.destroyChildren();
|
|
6093
|
+
stage.destroy();
|
|
6094
|
+
offsetCacheRef.current.clear();
|
|
6095
|
+
boundingBoxCacheRef.current.clear();
|
|
6096
|
+
spatialIndexRef.current.clear();
|
|
6097
|
+
};
|
|
6098
|
+
}, []); // disableInteraction 동기화
|
|
6099
|
+
|
|
6100
|
+
useEffect(function () {
|
|
6101
|
+
disableInteractionRef.current = disableInteraction;
|
|
6102
|
+
}, [disableInteraction]); // enableViewportCulling 동기화
|
|
6103
|
+
|
|
6104
|
+
useEffect(function () {
|
|
6105
|
+
enableViewportCullingRef.current = enableViewportCulling;
|
|
6106
|
+
|
|
6107
|
+
if (stageRef.current) {
|
|
6108
|
+
// 뷰포트 컬링 설정이 변경되면 shape 재생성 필요
|
|
6109
|
+
var baseLayer = baseLayerRef.current;
|
|
6110
|
+
|
|
6111
|
+
if (baseLayer) {
|
|
6112
|
+
var shape = baseLayer.findOne('.base-render-shape');
|
|
6113
|
+
|
|
6114
|
+
if (shape) {
|
|
6115
|
+
shape.destroy();
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
|
|
6119
|
+
var eventLayer = eventLayerRef.current;
|
|
6120
|
+
|
|
6121
|
+
if (eventLayer) {
|
|
6122
|
+
var shape = eventLayer.findOne('.event-render-shape');
|
|
6123
|
+
|
|
6124
|
+
if (shape) {
|
|
6125
|
+
shape.destroy();
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
|
|
6129
|
+
renderAllImmediate();
|
|
6130
|
+
}
|
|
6131
|
+
}, [enableViewportCulling]); // 외부 selectedItems 동기화
|
|
6132
|
+
|
|
6133
|
+
useEffect(function () {
|
|
6134
|
+
if (!stageRef.current) return;
|
|
6135
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
5764
6136
|
doRenderBase();
|
|
5765
|
-
doRenderAnimation();
|
|
5766
6137
|
doRenderEvent();
|
|
5767
|
-
}; //
|
|
5768
|
-
// 이벤트 핸들러: 지도 이벤트
|
|
5769
|
-
// --------------------------------------------------------------------------
|
|
6138
|
+
}, [externalSelectedItems]); // 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
5770
6139
|
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6140
|
+
useEffect(function () {
|
|
6141
|
+
if (!stageRef.current) return;
|
|
6142
|
+
selectedItemRef.current = externalSelectedItem;
|
|
6143
|
+
doRenderEvent();
|
|
6144
|
+
}, [externalSelectedItem]); // 데이터 변경 시 렌더링 (캐시 정리 및 선택 상태 동기화)
|
|
5774
6145
|
|
|
6146
|
+
useEffect(function () {
|
|
6147
|
+
if (!stageRef.current) return;
|
|
6148
|
+
dataRef.current = data;
|
|
6149
|
+
|
|
6150
|
+
if (containerRef.current) {
|
|
6151
|
+
containerRef.current.style.transform = '';
|
|
6152
|
+
}
|
|
5775
6153
|
|
|
5776
|
-
var handleIdle = function () {
|
|
5777
6154
|
prevCenterOffsetRef.current = null;
|
|
5778
6155
|
accumTranslateRef.current = {
|
|
5779
6156
|
x: 0,
|
|
5780
6157
|
y: 0
|
|
5781
|
-
};
|
|
5782
|
-
|
|
6158
|
+
};
|
|
5783
6159
|
offsetCacheRef.current.clear();
|
|
5784
|
-
boundingBoxCacheRef.current.clear();
|
|
6160
|
+
boundingBoxCacheRef.current.clear();
|
|
6161
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
6162
|
+
renderAllImmediate();
|
|
6163
|
+
}, [data]);
|
|
6164
|
+
return createPortal(React.createElement("div", {
|
|
6165
|
+
ref: containerRef,
|
|
6166
|
+
style: {
|
|
6167
|
+
position: 'absolute',
|
|
6168
|
+
width: '100%',
|
|
6169
|
+
height: '100%'
|
|
6170
|
+
}
|
|
6171
|
+
}), divElement);
|
|
6172
|
+
};
|
|
5785
6173
|
|
|
5786
|
-
|
|
6174
|
+
/**
|
|
6175
|
+
* 폴리곤 렌더링 유틸리티
|
|
6176
|
+
*
|
|
6177
|
+
* 이 파일은 폴리곤 렌더링을 위한 헬퍼 함수와 팩토리 함수를 제공합니다.
|
|
6178
|
+
* GeoJSON MultiPolygon 형식을 지원하며, 도넛 폴리곤(구멍이 있는 폴리곤)도 처리할 수 있습니다.
|
|
6179
|
+
*/
|
|
5787
6180
|
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
6181
|
+
/**
|
|
6182
|
+
* 폴리곤 그리기 헬퍼 함수 (도넛 폴리곤 지원)
|
|
6183
|
+
*
|
|
6184
|
+
* Canvas 2D Context를 사용하여 폴리곤을 그립니다.
|
|
6185
|
+
* 도넛 폴리곤의 경우 evenodd fill rule을 사용하여 구멍을 처리합니다.
|
|
6186
|
+
*
|
|
6187
|
+
* @param params 폴리곤 그리기 파라미터
|
|
6188
|
+
*
|
|
6189
|
+
* @remarks
|
|
6190
|
+
* - **도넛 폴리곤 처리**:
|
|
6191
|
+
* - 외부 폴리곤과 내부 구멍들을 같은 path에 추가
|
|
6192
|
+
* - `fill('evenodd')`를 사용하여 구멍 뚫기
|
|
6193
|
+
* - **일반 폴리곤 처리**: 각 폴리곤 그룹을 개별적으로 그리기
|
|
6194
|
+
* - **성능**: O(n), n은 폴리곤의 총 좌표 수
|
|
6195
|
+
*
|
|
6196
|
+
* @example
|
|
6197
|
+
* ```typescript
|
|
6198
|
+
* drawPolygon({
|
|
6199
|
+
* ctx,
|
|
6200
|
+
* polygonOffsets: [[[[100, 200], [200, 200], [200, 100], [100, 100]]]],
|
|
6201
|
+
* isDonutPolygon: false,
|
|
6202
|
+
* fillColor: 'rgba(255, 0, 0, 0.5)',
|
|
6203
|
+
* strokeColor: 'rgba(255, 0, 0, 1)',
|
|
6204
|
+
* lineWidth: 2
|
|
6205
|
+
* });
|
|
6206
|
+
* ```
|
|
6207
|
+
*/
|
|
6208
|
+
var drawPolygon = function (_a) {
|
|
6209
|
+
var ctx = _a.ctx,
|
|
6210
|
+
polygonOffsets = _a.polygonOffsets,
|
|
6211
|
+
isDonutPolygon = _a.isDonutPolygon,
|
|
6212
|
+
fillColor = _a.fillColor,
|
|
6213
|
+
strokeColor = _a.strokeColor,
|
|
6214
|
+
lineWidth = _a.lineWidth;
|
|
5791
6215
|
|
|
5792
|
-
|
|
6216
|
+
for (var _i = 0, polygonOffsets_1 = polygonOffsets; _i < polygonOffsets_1.length; _i++) {
|
|
6217
|
+
var multiPolygon = polygonOffsets_1[_i];
|
|
5793
6218
|
|
|
5794
|
-
if (
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
} // 5. 새 위치에서 렌더링
|
|
6219
|
+
if (isDonutPolygon) {
|
|
6220
|
+
// 도넛 폴리곤 처리: 외부 폴리곤 + 내부 구멍들을 같은 path에 추가
|
|
6221
|
+
ctx.beginPath(); // 1. 외부 폴리곤 그리기 (첫 번째 폴리곤)
|
|
5798
6222
|
|
|
6223
|
+
var outerPolygon = multiPolygon[0];
|
|
5799
6224
|
|
|
5800
|
-
|
|
6225
|
+
if (outerPolygon && outerPolygon.length > 0) {
|
|
6226
|
+
ctx.moveTo(outerPolygon[0][0], outerPolygon[0][1]);
|
|
6227
|
+
|
|
6228
|
+
for (var i = 1; i < outerPolygon.length; i++) {
|
|
6229
|
+
ctx.lineTo(outerPolygon[i][0], outerPolygon[i][1]);
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
ctx.closePath();
|
|
6233
|
+
} // 2. 내부 폴리곤 (구멍들) 그리기 - 같은 path에 추가
|
|
6234
|
+
|
|
6235
|
+
|
|
6236
|
+
for (var j = 1; j < multiPolygon.length; j++) {
|
|
6237
|
+
var innerPolygon = multiPolygon[j];
|
|
6238
|
+
if (innerPolygon.length === 0) continue;
|
|
6239
|
+
ctx.moveTo(innerPolygon[0][0], innerPolygon[0][1]);
|
|
6240
|
+
|
|
6241
|
+
for (var i = 1; i < innerPolygon.length; i++) {
|
|
6242
|
+
ctx.lineTo(innerPolygon[i][0], innerPolygon[i][1]);
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6245
|
+
ctx.closePath();
|
|
6246
|
+
} // 3. evenodd fill rule로 구멍 뚫기
|
|
6247
|
+
|
|
6248
|
+
|
|
6249
|
+
ctx.fillStyle = fillColor;
|
|
6250
|
+
ctx.fill('evenodd'); // 4. 외곽선 그리기
|
|
6251
|
+
|
|
6252
|
+
ctx.strokeStyle = strokeColor;
|
|
6253
|
+
ctx.lineWidth = lineWidth;
|
|
6254
|
+
ctx.stroke();
|
|
6255
|
+
} else {
|
|
6256
|
+
// 일반 폴리곤 처리: 각 폴리곤 그룹을 개별적으로 그리기
|
|
6257
|
+
for (var _b = 0, multiPolygon_1 = multiPolygon; _b < multiPolygon_1.length; _b++) {
|
|
6258
|
+
var polygonGroup = multiPolygon_1[_b];
|
|
6259
|
+
if (!polygonGroup.length) continue;
|
|
6260
|
+
ctx.beginPath();
|
|
6261
|
+
var firstPoint = polygonGroup[0];
|
|
6262
|
+
ctx.moveTo(firstPoint[0], firstPoint[1]);
|
|
6263
|
+
|
|
6264
|
+
for (var i = 1; i < polygonGroup.length; i++) {
|
|
6265
|
+
var point = polygonGroup[i];
|
|
6266
|
+
ctx.lineTo(point[0], point[1]);
|
|
6267
|
+
}
|
|
6268
|
+
|
|
6269
|
+
ctx.closePath(); // 스타일 설정 및 렌더링
|
|
6270
|
+
|
|
6271
|
+
ctx.fillStyle = fillColor;
|
|
6272
|
+
ctx.strokeStyle = strokeColor;
|
|
6273
|
+
ctx.lineWidth = lineWidth;
|
|
6274
|
+
ctx.fill();
|
|
6275
|
+
ctx.stroke();
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
};
|
|
6280
|
+
/**
|
|
6281
|
+
* 폴리곤 Base 렌더링 함수 팩토리
|
|
6282
|
+
*
|
|
6283
|
+
* Base Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
6284
|
+
* 선택되지 않은 폴리곤만 렌더링하며, 선택된 항목은 Event Layer에서 처리됩니다.
|
|
6285
|
+
*
|
|
6286
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6287
|
+
* @param baseFillColor 기본 폴리곤 채우기 색상
|
|
6288
|
+
* @param baseStrokeColor 기본 폴리곤 테두리 색상
|
|
6289
|
+
* @param baseLineWidth 기본 폴리곤 테두리 두께
|
|
6290
|
+
* @returns Base Layer 렌더링 함수
|
|
6291
|
+
*
|
|
6292
|
+
* @remarks
|
|
6293
|
+
* - 선택된 항목은 Event Layer에서 그려지므로 Base Layer에서는 스킵
|
|
6294
|
+
* - 성능: O(n), n은 렌더링할 폴리곤 개수
|
|
6295
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
6296
|
+
*
|
|
6297
|
+
* @example
|
|
6298
|
+
* ```typescript
|
|
6299
|
+
* const renderBase = renderPolygonBase(
|
|
6300
|
+
* 'rgba(255, 100, 100, 0.5)',
|
|
6301
|
+
* 'rgba(200, 50, 50, 0.8)',
|
|
6302
|
+
* 2
|
|
6303
|
+
* );
|
|
6304
|
+
* ```
|
|
6305
|
+
*/
|
|
6306
|
+
|
|
6307
|
+
var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth) {
|
|
6308
|
+
return function (_a) {
|
|
6309
|
+
var ctx = _a.ctx,
|
|
6310
|
+
items = _a.items,
|
|
6311
|
+
selectedIds = _a.selectedIds,
|
|
6312
|
+
utils = _a.utils;
|
|
6313
|
+
|
|
6314
|
+
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
6315
|
+
var item = items_1[_i]; // 선택된 항목은 Event Layer에서 그림 (중복 렌더링 방지)
|
|
6316
|
+
|
|
6317
|
+
if (selectedIds.has(item.id)) continue; // paths가 없으면 스킵
|
|
6318
|
+
|
|
6319
|
+
if (!item.paths) continue; // 좌표 변환 (자동 캐싱)
|
|
6320
|
+
|
|
6321
|
+
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6322
|
+
if (!polygonOffsets) continue; // 폴리곤 그리기
|
|
6323
|
+
|
|
6324
|
+
drawPolygon({
|
|
6325
|
+
ctx: ctx,
|
|
6326
|
+
polygonOffsets: polygonOffsets,
|
|
6327
|
+
isDonutPolygon: item.isDonutPolygon || false,
|
|
6328
|
+
fillColor: baseFillColor,
|
|
6329
|
+
strokeColor: baseStrokeColor,
|
|
6330
|
+
lineWidth: baseLineWidth
|
|
6331
|
+
});
|
|
6332
|
+
}
|
|
5801
6333
|
};
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
6334
|
+
};
|
|
6335
|
+
/**
|
|
6336
|
+
* 폴리곤 Event 렌더링 함수 팩토리
|
|
6337
|
+
*
|
|
6338
|
+
* Event Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
6339
|
+
* 선택된 항목, hover된 항목, 마지막 선택된 항목을 각각 다른 스타일로 렌더링합니다.
|
|
6340
|
+
*
|
|
6341
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6342
|
+
* @param baseFillColor 기본 폴리곤 채우기 색상 (필수, fallback용)
|
|
6343
|
+
* @param baseStrokeColor 기본 폴리곤 테두리 색상 (필수, fallback용)
|
|
6344
|
+
* @param baseLineWidth 기본 폴리곤 테두리 두께 (필수, fallback용)
|
|
6345
|
+
* @param selectedFillColor 선택된 폴리곤 채우기 색상 (선택, 기본값: baseFillColor)
|
|
6346
|
+
* @param selectedStrokeColor 선택된 폴리곤 테두리 색상 (선택, 기본값: baseStrokeColor)
|
|
6347
|
+
* @param selectedLineWidth 선택된 폴리곤 테두리 두께 (선택, 기본값: baseLineWidth)
|
|
6348
|
+
* @param activeFillColor 마지막 선택된 폴리곤 채우기 색상 (선택, 기본값: selectedFillColor)
|
|
6349
|
+
* @param activeStrokeColor 마지막 선택된 폴리곤 테두리 색상 (선택, 기본값: selectedStrokeColor)
|
|
6350
|
+
* @param activeLineWidth 마지막 선택된 폴리곤 테두리 두께 (선택, 기본값: selectedLineWidth)
|
|
6351
|
+
* @param hoveredFillColor Hover 시 폴리곤 채우기 색상 (선택, 기본값: selectedFillColor)
|
|
6352
|
+
* @param hoveredStrokeColor Hover 시 폴리곤 테두리 색상 (선택, 기본값: selectedStrokeColor)
|
|
6353
|
+
* @param hoveredLineWidth Hover 시 폴리곤 테두리 두께 (선택, 기본값: selectedLineWidth)
|
|
6354
|
+
* @returns Event Layer 렌더링 함수
|
|
6355
|
+
*
|
|
6356
|
+
* @remarks
|
|
6357
|
+
* - **렌더링 순서**: 선택된 항목 → 마지막 선택된 항목 → hover된 항목 (최상단)
|
|
6358
|
+
* - **성능**: O(m), m은 선택된 항목 수 + hover된 항목 수
|
|
6359
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
6360
|
+
* - hover된 항목이 선택되어 있으면 active 스타일 적용
|
|
6361
|
+
*
|
|
6362
|
+
* @example
|
|
6363
|
+
* ```typescript
|
|
6364
|
+
* const renderEvent = renderPolygonEvent(
|
|
6365
|
+
* 'rgba(255, 100, 100, 0.5)', // baseFillColor
|
|
6366
|
+
* 'rgba(200, 50, 50, 0.8)', // baseStrokeColor
|
|
6367
|
+
* 2, // baseLineWidth
|
|
6368
|
+
* 'rgba(255, 193, 7, 0.7)', // selectedFillColor
|
|
6369
|
+
* 'rgba(255, 152, 0, 1)', // selectedStrokeColor
|
|
6370
|
+
* 4 // selectedLineWidth
|
|
6371
|
+
* );
|
|
6372
|
+
* ```
|
|
6373
|
+
*/
|
|
6374
|
+
|
|
6375
|
+
var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth) {
|
|
6376
|
+
// 기본값 설정 (base 기준)
|
|
6377
|
+
var _selectedFillColor = selectedFillColor || baseFillColor;
|
|
6378
|
+
|
|
6379
|
+
var _selectedStrokeColor = selectedStrokeColor || baseStrokeColor;
|
|
6380
|
+
|
|
6381
|
+
var _selectedLineWidth = selectedLineWidth || baseLineWidth;
|
|
6382
|
+
|
|
6383
|
+
var _activeFillColor = activeFillColor || _selectedFillColor;
|
|
6384
|
+
|
|
6385
|
+
var _activeStrokeColor = activeStrokeColor || _selectedStrokeColor;
|
|
6386
|
+
|
|
6387
|
+
var _activeLineWidth = activeLineWidth || _selectedLineWidth;
|
|
6388
|
+
|
|
6389
|
+
var _hoveredFillColor = hoveredFillColor || _selectedFillColor;
|
|
6390
|
+
|
|
6391
|
+
var _hoveredStrokeColor = hoveredStrokeColor || _selectedStrokeColor;
|
|
6392
|
+
|
|
6393
|
+
var _hoveredLineWidth = hoveredLineWidth || _selectedLineWidth;
|
|
6394
|
+
|
|
6395
|
+
return function (_a) {
|
|
6396
|
+
var ctx = _a.ctx,
|
|
6397
|
+
hoveredItem = _a.hoveredItem,
|
|
6398
|
+
utils = _a.utils,
|
|
6399
|
+
selectedItems = _a.selectedItems,
|
|
6400
|
+
selectedItem = _a.selectedItem; // 성능 최적화: selectedItems를 Set으로 변환하여 O(1) 조회 (매번 some() 체크 방지)
|
|
6401
|
+
|
|
6402
|
+
var selectedIdsSet = selectedItems ? new Set(selectedItems.map(function (item) {
|
|
6403
|
+
return item.id;
|
|
6404
|
+
})) : new Set();
|
|
6405
|
+
var hoveredItemId = hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.id;
|
|
6406
|
+
var selectedItemId = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id; // 1. 선택된 항목들 그리기 (마지막 선택 항목과 호버된 항목 제외)
|
|
6407
|
+
|
|
6408
|
+
if (selectedItems === null || selectedItems === void 0 ? void 0 : selectedItems.length) {
|
|
6409
|
+
for (var _i = 0, selectedItems_1 = selectedItems; _i < selectedItems_1.length; _i++) {
|
|
6410
|
+
var item = selectedItems_1[_i]; // 마지막 선택 항목과 호버된 항목은 나중에 따로 그림
|
|
6411
|
+
|
|
6412
|
+
if (item.id === selectedItemId || item.id === hoveredItemId) continue;
|
|
6413
|
+
if (!item.paths) continue;
|
|
6414
|
+
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6415
|
+
if (!polygonOffsets) continue;
|
|
6416
|
+
drawPolygon({
|
|
6417
|
+
ctx: ctx,
|
|
6418
|
+
polygonOffsets: polygonOffsets,
|
|
6419
|
+
isDonutPolygon: item.isDonutPolygon || false,
|
|
6420
|
+
fillColor: _selectedFillColor,
|
|
6421
|
+
strokeColor: _selectedStrokeColor,
|
|
6422
|
+
lineWidth: _selectedLineWidth
|
|
6423
|
+
});
|
|
6424
|
+
}
|
|
6425
|
+
} // 2. 마지막 선택된 항목 그리기 (호버되지 않은 경우)
|
|
6426
|
+
|
|
6427
|
+
|
|
6428
|
+
if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.paths) && hoveredItemId !== selectedItemId) {
|
|
6429
|
+
var polygonOffsets = utils.getOrComputePolygonOffsets(selectedItem);
|
|
6430
|
+
|
|
6431
|
+
if (polygonOffsets) {
|
|
6432
|
+
drawPolygon({
|
|
6433
|
+
ctx: ctx,
|
|
6434
|
+
polygonOffsets: polygonOffsets,
|
|
6435
|
+
isDonutPolygon: selectedItem.isDonutPolygon || false,
|
|
6436
|
+
fillColor: _activeFillColor,
|
|
6437
|
+
strokeColor: _activeStrokeColor,
|
|
6438
|
+
lineWidth: _activeLineWidth
|
|
6439
|
+
});
|
|
6440
|
+
}
|
|
6441
|
+
} // 3. 호버된 항목 그리기 (가장 위에 표시)
|
|
6442
|
+
|
|
6443
|
+
|
|
6444
|
+
if (hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.paths) {
|
|
6445
|
+
var polygonOffsets = utils.getOrComputePolygonOffsets(hoveredItem);
|
|
6446
|
+
if (!polygonOffsets) return; // 좌표 변환 실패 시 스킵 (return은 렌더링 함수 종료)
|
|
6447
|
+
// 성능 최적화: Set을 사용하여 O(1) 조회 (이전: O(m) some() 체크)
|
|
6448
|
+
|
|
6449
|
+
var isSelected = selectedIdsSet.has(hoveredItem.id);
|
|
6450
|
+
drawPolygon({
|
|
6451
|
+
ctx: ctx,
|
|
6452
|
+
polygonOffsets: polygonOffsets,
|
|
6453
|
+
isDonutPolygon: hoveredItem.isDonutPolygon || false,
|
|
6454
|
+
fillColor: isSelected ? _activeFillColor : _hoveredFillColor,
|
|
6455
|
+
strokeColor: isSelected ? _activeStrokeColor : _hoveredStrokeColor,
|
|
6456
|
+
lineWidth: isSelected ? _activeLineWidth : _hoveredLineWidth
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
};
|
|
6460
|
+
};
|
|
6461
|
+
|
|
6462
|
+
var WoongCanvasPolygon = function (props) {
|
|
6463
|
+
var data = props.data,
|
|
6464
|
+
onClick = props.onClick,
|
|
6465
|
+
_a = props.enableMultiSelect,
|
|
6466
|
+
enableMultiSelect = _a === void 0 ? false : _a,
|
|
6467
|
+
_b = props.enableViewportCulling,
|
|
6468
|
+
enableViewportCulling = _b === void 0 ? false : _b,
|
|
6469
|
+
_c = props.cullingMargin,
|
|
6470
|
+
cullingMargin = _c === void 0 ? DEFAULT_CULLING_MARGIN : _c,
|
|
6471
|
+
_d = props.maxCacheSize,
|
|
6472
|
+
maxCacheSize = _d === void 0 ? DEFAULT_MAX_CACHE_SIZE : _d,
|
|
6473
|
+
externalSelectedItems = props.selectedItems,
|
|
6474
|
+
externalSelectedItem = props.selectedItem,
|
|
6475
|
+
_e = props.disableInteraction,
|
|
6476
|
+
disableInteraction = _e === void 0 ? false : _e,
|
|
6477
|
+
baseFillColor = props.baseFillColor,
|
|
6478
|
+
baseStrokeColor = props.baseStrokeColor,
|
|
6479
|
+
baseLineWidth = props.baseLineWidth,
|
|
6480
|
+
selectedFillColor = props.selectedFillColor,
|
|
6481
|
+
selectedStrokeColor = props.selectedStrokeColor,
|
|
6482
|
+
selectedLineWidth = props.selectedLineWidth,
|
|
6483
|
+
activeFillColor = props.activeFillColor,
|
|
6484
|
+
activeStrokeColor = props.activeStrokeColor,
|
|
6485
|
+
activeLineWidth = props.activeLineWidth,
|
|
6486
|
+
hoveredFillColor = props.hoveredFillColor,
|
|
6487
|
+
hoveredStrokeColor = props.hoveredStrokeColor,
|
|
6488
|
+
hoveredLineWidth = props.hoveredLineWidth,
|
|
6489
|
+
options = __rest(props, ["data", "onClick", "enableMultiSelect", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "baseFillColor", "baseStrokeColor", "baseLineWidth", "selectedFillColor", "selectedStrokeColor", "selectedLineWidth", "activeFillColor", "activeStrokeColor", "activeLineWidth", "hoveredFillColor", "hoveredStrokeColor", "hoveredLineWidth"]); // --------------------------------------------------------------------------
|
|
6490
|
+
// Hooks & Context
|
|
6491
|
+
// --------------------------------------------------------------------------
|
|
6492
|
+
|
|
6493
|
+
|
|
6494
|
+
var controller = useMintMapController();
|
|
6495
|
+
var context = useWoongCanvasContext();
|
|
6496
|
+
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // DOM Refs
|
|
6497
|
+
|
|
6498
|
+
var divRef = useRef(document.createElement('div'));
|
|
6499
|
+
var divElement = divRef.current;
|
|
6500
|
+
var containerRef = useRef(null);
|
|
6501
|
+
var markerRef = useRef(); // Konva Refs
|
|
6502
|
+
|
|
6503
|
+
var stageRef = useRef(null);
|
|
6504
|
+
var baseLayerRef = useRef(null);
|
|
6505
|
+
var eventLayerRef = useRef(null); // 상태 관리 Refs (React 리렌더링 최소화)
|
|
6506
|
+
|
|
6507
|
+
var dataRef = useRef(data);
|
|
6508
|
+
var disableInteractionRef = useRef(disableInteraction);
|
|
6509
|
+
var enableViewportCullingRef = useRef(enableViewportCulling);
|
|
6510
|
+
var hoveredItemRef = useRef(null);
|
|
6511
|
+
var selectedItemRef = useRef(externalSelectedItem);
|
|
6512
|
+
var selectedIdsRef = useRef(new Set());
|
|
6513
|
+
var selectedItemsMapRef = useRef(new Map()); // 드래그 상태 Refs
|
|
6514
|
+
|
|
6515
|
+
var draggingRef = useRef(false);
|
|
6516
|
+
var prevCenterOffsetRef = useRef(null);
|
|
6517
|
+
var accumTranslateRef = useRef({
|
|
6518
|
+
x: 0,
|
|
6519
|
+
y: 0
|
|
6520
|
+
}); // 성능 최적화 Refs
|
|
6521
|
+
|
|
6522
|
+
var offsetCacheRef = useRef(new LRUCache(maxCacheSize));
|
|
6523
|
+
var spatialIndexRef = useRef(new SpatialHashGrid(SPATIAL_GRID_CELL_SIZE));
|
|
6524
|
+
var boundingBoxCacheRef = useRef(new Map());
|
|
6525
|
+
var viewportRef = useRef(null); // 뷰포트 영역 계산 (Viewport Culling용)
|
|
6526
|
+
|
|
6527
|
+
var updateViewport$1 = function () {
|
|
6528
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
6529
|
+
}; // 뷰포트 내부 여부 확인 (바운딩 박스 캐싱)
|
|
6530
|
+
|
|
6531
|
+
|
|
6532
|
+
var isInViewport$1 = function (item) {
|
|
6533
|
+
return isInViewport(item, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
6534
|
+
}; // 폴리곤 좌표 변환 (위경도 → 화면 좌표, LRU 캐시 사용)
|
|
6535
|
+
|
|
6536
|
+
|
|
6537
|
+
var getOrComputePolygonOffsets = function (polygonData) {
|
|
6538
|
+
var cached = offsetCacheRef.current.get(polygonData.id);
|
|
6539
|
+
if (cached && Array.isArray(cached)) return cached;
|
|
6540
|
+
var result = computePolygonOffsets(polygonData, controller);
|
|
6541
|
+
if (!result) return null;
|
|
6542
|
+
offsetCacheRef.current.set(polygonData.id, result);
|
|
6543
|
+
return result;
|
|
6544
|
+
}; // 폴리곤 바운딩 박스 계산 (Viewport Culling 및 Hit Test용)
|
|
6545
|
+
|
|
6546
|
+
|
|
6547
|
+
var computeBoundingBox = function (item) {
|
|
6548
|
+
var offsets = getOrComputePolygonOffsets(item);
|
|
6549
|
+
if (!offsets) return null; // 모든 좌표를 순회하며 최소/최대값 찾기
|
|
6550
|
+
|
|
6551
|
+
var minX = Infinity,
|
|
6552
|
+
minY = Infinity,
|
|
6553
|
+
maxX = -Infinity,
|
|
6554
|
+
maxY = -Infinity;
|
|
6555
|
+
|
|
6556
|
+
for (var _i = 0, offsets_1 = offsets; _i < offsets_1.length; _i++) {
|
|
6557
|
+
var multiPolygon = offsets_1[_i];
|
|
6558
|
+
|
|
6559
|
+
for (var _a = 0, multiPolygon_1 = multiPolygon; _a < multiPolygon_1.length; _a++) {
|
|
6560
|
+
var polygonGroup = multiPolygon_1[_a];
|
|
6561
|
+
|
|
6562
|
+
for (var _b = 0, polygonGroup_1 = polygonGroup; _b < polygonGroup_1.length; _b++) {
|
|
6563
|
+
var _c = polygonGroup_1[_b],
|
|
6564
|
+
x = _c[0],
|
|
6565
|
+
y = _c[1];
|
|
6566
|
+
if (x < minX) minX = x;
|
|
6567
|
+
if (y < minY) minY = y;
|
|
6568
|
+
if (x > maxX) maxX = x;
|
|
6569
|
+
if (y > maxY) maxY = y;
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
return {
|
|
6575
|
+
minX: minX,
|
|
6576
|
+
minY: minY,
|
|
6577
|
+
maxX: maxX,
|
|
6578
|
+
maxY: maxY
|
|
6579
|
+
};
|
|
6580
|
+
}; // 공간 인덱스 빌드 (빠른 Hit Test용)
|
|
6581
|
+
|
|
6582
|
+
|
|
6583
|
+
var buildSpatialIndex$1 = function () {
|
|
6584
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
6585
|
+
}; // 렌더링 유틸리티 객체
|
|
6586
|
+
|
|
6587
|
+
|
|
6588
|
+
var renderUtils = {
|
|
6589
|
+
getOrComputePolygonOffsets: getOrComputePolygonOffsets,
|
|
6590
|
+
getOrComputeMarkerOffset: function () {
|
|
6591
|
+
return null;
|
|
6592
|
+
}
|
|
6593
|
+
}; // 렌더링 함수 생성
|
|
6594
|
+
|
|
6595
|
+
var renderBase = renderPolygonBase(baseFillColor, baseStrokeColor, baseLineWidth);
|
|
6596
|
+
var renderEvent = renderPolygonEvent(baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth); // Base Layer 렌더링 (뷰포트 컬링 적용)
|
|
6597
|
+
|
|
6598
|
+
var doRenderBase = function () {
|
|
6599
|
+
var layer = baseLayerRef.current;
|
|
6600
|
+
if (!layer) return;
|
|
6601
|
+
var shape = layer.findOne('.base-render-shape');
|
|
6602
|
+
|
|
6603
|
+
if (!shape) {
|
|
6604
|
+
shape = new Konva.Shape({
|
|
6605
|
+
name: 'base-render-shape',
|
|
6606
|
+
sceneFunc: function (context, shape) {
|
|
6607
|
+
var ctx = context;
|
|
6608
|
+
var hovered = hoveredItemRef.current; // 뷰포트 컬링: 화면에 보이는 항목만 필터링
|
|
6609
|
+
|
|
6610
|
+
var visibleItems = enableViewportCullingRef.current ? dataRef.current.filter(function (item) {
|
|
6611
|
+
return isInViewport$1(item);
|
|
6612
|
+
}) : dataRef.current;
|
|
6613
|
+
renderBase({
|
|
6614
|
+
ctx: ctx,
|
|
6615
|
+
items: visibleItems,
|
|
6616
|
+
selectedIds: selectedIdsRef.current,
|
|
6617
|
+
hoveredItem: hovered,
|
|
6618
|
+
utils: renderUtils
|
|
6619
|
+
});
|
|
6620
|
+
},
|
|
6621
|
+
perfectDrawEnabled: false,
|
|
6622
|
+
listening: false,
|
|
6623
|
+
hitStrokeWidth: 0
|
|
6624
|
+
});
|
|
6625
|
+
layer.add(shape);
|
|
6626
|
+
}
|
|
5805
6627
|
|
|
6628
|
+
layer.batchDraw();
|
|
6629
|
+
}; // Event Layer 렌더링 (hover 효과 및 선택 상태 표시)
|
|
5806
6630
|
|
|
5807
|
-
var handleZoomStart = function () {
|
|
5808
|
-
if (containerRef.current) {
|
|
5809
|
-
containerRef.current.style.visibility = 'hidden';
|
|
5810
|
-
}
|
|
5811
|
-
};
|
|
5812
|
-
/**
|
|
5813
|
-
* 줌 종료 시 처리 (다시 표시)
|
|
5814
|
-
*/
|
|
5815
6631
|
|
|
6632
|
+
var doRenderEvent = function () {
|
|
6633
|
+
var layer = eventLayerRef.current;
|
|
6634
|
+
if (!layer) return;
|
|
6635
|
+
var shape = layer.findOne('.event-render-shape');
|
|
5816
6636
|
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
6637
|
+
if (!shape) {
|
|
6638
|
+
shape = new Konva.Shape({
|
|
6639
|
+
name: 'event-render-shape',
|
|
6640
|
+
sceneFunc: function (context, shape) {
|
|
6641
|
+
var ctx = context;
|
|
6642
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
6643
|
+
var hovered = hoveredItemRef.current;
|
|
6644
|
+
renderEvent({
|
|
6645
|
+
ctx: ctx,
|
|
6646
|
+
hoveredItem: hovered,
|
|
6647
|
+
utils: renderUtils,
|
|
6648
|
+
selectedItems: selectedItems,
|
|
6649
|
+
selectedItem: selectedItemRef.current
|
|
6650
|
+
});
|
|
6651
|
+
},
|
|
6652
|
+
perfectDrawEnabled: false,
|
|
6653
|
+
listening: false,
|
|
6654
|
+
hitStrokeWidth: 0
|
|
6655
|
+
});
|
|
6656
|
+
layer.add(shape);
|
|
5820
6657
|
}
|
|
5821
|
-
};
|
|
5822
|
-
/**
|
|
5823
|
-
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
5824
|
-
*/
|
|
5825
6658
|
|
|
6659
|
+
layer.batchDraw();
|
|
6660
|
+
}; // 전체 즉시 렌더링
|
|
5826
6661
|
|
|
5827
|
-
var handleCenterChanged = function () {
|
|
5828
|
-
var center = controller.getCurrBounds().getCenter();
|
|
5829
|
-
var curr = controller.positionToOffset(center);
|
|
5830
|
-
var prev = prevCenterOffsetRef.current;
|
|
5831
6662
|
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
y: curr.y
|
|
5836
|
-
};
|
|
5837
|
-
return;
|
|
6663
|
+
var renderAllImmediate = function () {
|
|
6664
|
+
if (enableViewportCullingRef.current) {
|
|
6665
|
+
updateViewport$1();
|
|
5838
6666
|
}
|
|
5839
6667
|
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
6668
|
+
buildSpatialIndex$1();
|
|
6669
|
+
doRenderBase();
|
|
6670
|
+
doRenderEvent();
|
|
6671
|
+
}; // 지도 이벤트 핸들러 생성
|
|
6672
|
+
|
|
6673
|
+
|
|
6674
|
+
var _f = createMapEventHandlers({
|
|
6675
|
+
controller: controller,
|
|
6676
|
+
containerRef: containerRef,
|
|
6677
|
+
markerRef: markerRef,
|
|
6678
|
+
options: options,
|
|
6679
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
6680
|
+
accumTranslateRef: accumTranslateRef,
|
|
6681
|
+
offsetCacheRef: offsetCacheRef,
|
|
6682
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
6683
|
+
renderAllImmediate: renderAllImmediate
|
|
6684
|
+
}),
|
|
6685
|
+
handleIdle = _f.handleIdle,
|
|
6686
|
+
handleZoomStart = _f.handleZoomStart,
|
|
6687
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
6688
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
6689
|
+
handleDragStartShared = _f.handleDragStart,
|
|
6690
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
5850
6691
|
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
// --------------------------------------------------------------------------
|
|
6692
|
+
var handleDragStart = function () {
|
|
6693
|
+
handleDragStartShared();
|
|
6694
|
+
draggingRef.current = true;
|
|
6695
|
+
controller.setMapCursor('grabbing');
|
|
6696
|
+
};
|
|
5857
6697
|
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
5864
|
-
*
|
|
5865
|
-
* @param offset 검사할 좌표
|
|
5866
|
-
* @returns 찾은 마커/폴리곤 데이터 또는 null
|
|
5867
|
-
*/
|
|
6698
|
+
var handleDragEnd = function () {
|
|
6699
|
+
handleDragEndShared();
|
|
6700
|
+
draggingRef.current = false;
|
|
6701
|
+
controller.setMapCursor('grab');
|
|
6702
|
+
}; // Hit Test: 특정 좌표의 폴리곤 찾기
|
|
5868
6703
|
|
|
5869
6704
|
|
|
5870
6705
|
var findData = function (offset) {
|
|
5871
|
-
//
|
|
5872
|
-
|
|
5873
|
-
var hovered = hoveredItemRef.current; // 폴리곤인 경우
|
|
5874
|
-
|
|
5875
|
-
if (dataType === CanvasDataType.POLYGON) {
|
|
5876
|
-
if (isPointInPolygonData(offset, hovered, getOrComputePolygonOffsets)) {
|
|
5877
|
-
return hovered; // 여전히 hover된 항목 위에 있음
|
|
5878
|
-
}
|
|
5879
|
-
} // 마커인 경우
|
|
5880
|
-
else {
|
|
5881
|
-
if (isPointInMarkerData(offset, hovered, getOrComputeMarkerOffset)) {
|
|
5882
|
-
return hovered; // 여전히 hover된 항목 위에 있음
|
|
5883
|
-
}
|
|
5884
|
-
}
|
|
5885
|
-
} // Spatial Index로 후보 항목만 빠르게 추출 (30,000개 → ~10개)
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 데이터 타입에 따라 적절한 히트 테스트 수행
|
|
5889
|
-
|
|
5890
|
-
if (dataType === CanvasDataType.MARKER) {
|
|
5891
|
-
// 마커 체크
|
|
5892
|
-
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
5893
|
-
var item = candidates[i];
|
|
6706
|
+
// 공간 인덱스에서 후보 항목 조회 (O(1) 수준의 빠른 조회)
|
|
6707
|
+
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 역순 순회: 나중에 추가된 항목(최상위)이 먼저 선택되도록
|
|
5894
6708
|
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
}
|
|
5898
|
-
}
|
|
5899
|
-
} else {
|
|
5900
|
-
// 폴리곤 체크
|
|
5901
|
-
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
5902
|
-
var item = candidates[i];
|
|
6709
|
+
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
6710
|
+
var item = candidates[i]; // 정확한 Hit Test: Ray Casting 알고리즘으로 폴리곤 내부 여부 확인
|
|
5903
6711
|
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
}
|
|
6712
|
+
if (isPointInPolygonData(offset, item, getOrComputePolygonOffsets)) {
|
|
6713
|
+
return item;
|
|
5907
6714
|
}
|
|
5908
6715
|
}
|
|
5909
6716
|
|
|
5910
6717
|
return null;
|
|
5911
|
-
};
|
|
5912
|
-
/**
|
|
5913
|
-
* Hover 상태 설정 및 Event 레이어 렌더링
|
|
5914
|
-
*
|
|
5915
|
-
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
5916
|
-
*
|
|
5917
|
-
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
5918
|
-
* topOnHover가 true일 때는 Base 레이어도 다시 그려서 z-order 변경
|
|
5919
|
-
*/
|
|
6718
|
+
}; // Hover 상태 설정 및 렌더링
|
|
5920
6719
|
|
|
5921
6720
|
|
|
5922
6721
|
var setHovered = function (data) {
|
|
@@ -5926,29 +6725,14 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
5926
6725
|
controller.setMapCursor('grabbing');
|
|
5927
6726
|
} else {
|
|
5928
6727
|
controller.setMapCursor(data ? 'pointer' : 'grab');
|
|
5929
|
-
}
|
|
5930
|
-
// topOnHover는 Event Layer에서만 처리 (Base Layer 재렌더링 제거로 성능 향상)
|
|
5931
|
-
|
|
6728
|
+
}
|
|
5932
6729
|
|
|
5933
6730
|
doRenderEvent();
|
|
5934
|
-
};
|
|
5935
|
-
/**
|
|
5936
|
-
* 클릭 처리 (단일/다중 선택)
|
|
5937
|
-
*
|
|
5938
|
-
* @param data 클릭된 마커/폴리곤 데이터
|
|
5939
|
-
*
|
|
5940
|
-
* 🔥 최적화: 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
5941
|
-
* - sceneFunc에서 selectedIds를 체크하여 선택된 마커만 스킵
|
|
5942
|
-
* - 객체 생성 오버헤드 제거로 1000개 이상도 부드럽게 처리
|
|
5943
|
-
*/
|
|
6731
|
+
}; // 클릭 처리: 선택 상태 업데이트
|
|
5944
6732
|
|
|
5945
6733
|
|
|
5946
6734
|
var handleLocalClick = function (data) {
|
|
5947
|
-
// 0. 마지막 클릭 항목 저장
|
|
5948
|
-
lastClickedItemRef.current = data; // 1. 선택 상태 업데이트
|
|
5949
|
-
|
|
5950
6735
|
if (enableMultiSelect) {
|
|
5951
|
-
// 다중 선택: Set과 Map 동시 업데이트
|
|
5952
6736
|
var newSelected = new Set(selectedIdsRef.current);
|
|
5953
6737
|
|
|
5954
6738
|
if (newSelected.has(data.id)) {
|
|
@@ -5961,7 +6745,6 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
5961
6745
|
|
|
5962
6746
|
selectedIdsRef.current = newSelected;
|
|
5963
6747
|
} else {
|
|
5964
|
-
// 단일 선택: 토글
|
|
5965
6748
|
var newSelected = new Set();
|
|
5966
6749
|
|
|
5967
6750
|
if (!selectedIdsRef.current.has(data.id)) {
|
|
@@ -5975,144 +6758,77 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
5975
6758
|
selectedIdsRef.current = newSelected;
|
|
5976
6759
|
}
|
|
5977
6760
|
|
|
5978
|
-
|
|
5979
|
-
// 2. Base Layer 재렌더링 (단일 Shape로 최적화되어 빠름)
|
|
5980
|
-
doRenderBase(); // 3. Animation Layer 렌더링 (선택된 마커 애니메이션)
|
|
5981
|
-
|
|
5982
|
-
doRenderAnimation();
|
|
5983
|
-
} // 4. Event Layer 렌더링 (hover 처리)
|
|
5984
|
-
|
|
5985
|
-
|
|
6761
|
+
doRenderBase();
|
|
5986
6762
|
doRenderEvent();
|
|
5987
|
-
}; //
|
|
5988
|
-
// 이벤트 핸들러: UI 이벤트
|
|
5989
|
-
// --------------------------------------------------------------------------
|
|
5990
|
-
|
|
5991
|
-
/**
|
|
5992
|
-
* 클릭 이벤트 처리
|
|
5993
|
-
*/
|
|
6763
|
+
}; // 클릭 이벤트 핸들러
|
|
5994
6764
|
|
|
5995
6765
|
|
|
5996
6766
|
var handleClick = function (event) {
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
if (
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
if (data) {
|
|
6006
|
-
handleLocalClick(data);
|
|
6007
|
-
if (onClick) onClick(data, selectedIdsRef.current);
|
|
6008
|
-
}
|
|
6009
|
-
} catch (error) {
|
|
6010
|
-
console.error('[WoongKonvaMarker] handleClick error:', error);
|
|
6011
|
-
}
|
|
6012
|
-
};
|
|
6013
|
-
/**
|
|
6014
|
-
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
6015
|
-
*/
|
|
6767
|
+
if (disableInteractionRef.current) return;
|
|
6768
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
6769
|
+
if (!clickedOffset) return;
|
|
6770
|
+
var data = findData(clickedOffset);
|
|
6771
|
+
if (!data) return;
|
|
6772
|
+
handleLocalClick(data);
|
|
6773
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(data, selectedIdsRef.current);
|
|
6774
|
+
}; // 마우스 이동 이벤트 핸들러 (hover 감지)
|
|
6016
6775
|
|
|
6017
6776
|
|
|
6018
6777
|
var handleMouseMove = function (event) {
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
if (
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
if (prevHovered !== hoveredItem) {
|
|
6029
|
-
setHovered(hoveredItem);
|
|
6030
|
-
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
6031
|
-
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
6032
|
-
}
|
|
6033
|
-
} catch (error) {
|
|
6034
|
-
console.error('[WoongKonvaMarker] handleMouseMove error:', error);
|
|
6035
|
-
}
|
|
6036
|
-
};
|
|
6037
|
-
/**
|
|
6038
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
6039
|
-
*/
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
var handleDragStart = function () {
|
|
6043
|
-
draggingRef.current = true;
|
|
6044
|
-
controller.setMapCursor('grabbing');
|
|
6045
|
-
};
|
|
6046
|
-
/**
|
|
6047
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
6048
|
-
*/
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
var handleDragEnd = function () {
|
|
6052
|
-
draggingRef.current = false;
|
|
6053
|
-
controller.setMapCursor('grab');
|
|
6054
|
-
};
|
|
6055
|
-
/**
|
|
6056
|
-
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
6057
|
-
*/
|
|
6778
|
+
if (disableInteractionRef.current) return;
|
|
6779
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
6780
|
+
if (!mouseOffset) return;
|
|
6781
|
+
var hoveredItem = findData(mouseOffset);
|
|
6782
|
+
var prevHovered = hoveredItemRef.current;
|
|
6783
|
+
if (prevHovered === hoveredItem) return;
|
|
6784
|
+
setHovered(hoveredItem);
|
|
6785
|
+
}; // 마우스가 맵 영역을 벗어날 때 hover 상태 초기화
|
|
6058
6786
|
|
|
6059
6787
|
|
|
6060
6788
|
var handleMouseLeave = function () {
|
|
6789
|
+
if (disableInteractionRef.current) return;
|
|
6061
6790
|
var prevHovered = hoveredItemRef.current;
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
if (onMouseOut) {
|
|
6069
|
-
onMouseOut(prevHovered);
|
|
6070
|
-
}
|
|
6071
|
-
}
|
|
6072
|
-
}; // --------------------------------------------------------------------------
|
|
6073
|
-
// Lifecycle: DOM 초기화
|
|
6074
|
-
// --------------------------------------------------------------------------
|
|
6791
|
+
if (!prevHovered) return;
|
|
6792
|
+
hoveredItemRef.current = null;
|
|
6793
|
+
controller.setMapCursor('grab');
|
|
6794
|
+
doRenderEvent();
|
|
6795
|
+
}; // DOM 초기화
|
|
6075
6796
|
|
|
6076
6797
|
|
|
6077
6798
|
useEffect(function () {
|
|
6078
6799
|
divElement.style.width = 'fit-content';
|
|
6079
6800
|
return function () {
|
|
6080
|
-
if (markerRef.current)
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
}
|
|
6801
|
+
if (!markerRef.current) return;
|
|
6802
|
+
controller.clearDrawable(markerRef.current);
|
|
6803
|
+
markerRef.current = undefined;
|
|
6084
6804
|
};
|
|
6085
|
-
}, []); //
|
|
6086
|
-
// Lifecycle: 마커 생성/업데이트
|
|
6087
|
-
// --------------------------------------------------------------------------
|
|
6805
|
+
}, []); // 마커 생성/업데이트
|
|
6088
6806
|
|
|
6089
6807
|
useEffect(function () {
|
|
6090
|
-
if (options)
|
|
6091
|
-
|
|
6808
|
+
if (!options) return;
|
|
6809
|
+
var bounds = controller.getCurrBounds();
|
|
6092
6810
|
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6811
|
+
var markerOptions = __assign({
|
|
6812
|
+
position: bounds.nw
|
|
6813
|
+
}, options);
|
|
6096
6814
|
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
markerRef.current.element = divElement;
|
|
6102
|
-
controller.createMarker(markerRef.current);
|
|
6815
|
+
if (markerRef.current) {
|
|
6816
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
6817
|
+
return;
|
|
6818
|
+
}
|
|
6103
6819
|
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6820
|
+
markerRef.current = new Marker(markerOptions);
|
|
6821
|
+
markerRef.current.element = divElement;
|
|
6822
|
+
controller.createMarker(markerRef.current);
|
|
6107
6823
|
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
}
|
|
6111
|
-
}
|
|
6824
|
+
if (divElement.parentElement) {
|
|
6825
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
6112
6826
|
}
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6827
|
+
|
|
6828
|
+
if (options.zIndex !== undefined) {
|
|
6829
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
6830
|
+
}
|
|
6831
|
+
}, [options]); // Konva 초기화 및 이벤트 리스너 등록
|
|
6116
6832
|
|
|
6117
6833
|
useEffect(function () {
|
|
6118
6834
|
var mapDiv = controller.mapDivElement;
|
|
@@ -6121,34 +6837,25 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6121
6837
|
width: mapDiv.offsetWidth,
|
|
6122
6838
|
height: mapDiv.offsetHeight
|
|
6123
6839
|
});
|
|
6124
|
-
stageRef.current = stage;
|
|
6125
|
-
|
|
6840
|
+
stageRef.current = stage;
|
|
6126
6841
|
var baseLayer = new Konva.Layer({
|
|
6127
|
-
listening: false // 이벤트 리스닝 비활성화로 성능 향상
|
|
6128
|
-
|
|
6129
|
-
});
|
|
6130
|
-
var animationLayer = new Konva.Layer({
|
|
6131
6842
|
listening: false
|
|
6132
6843
|
});
|
|
6133
6844
|
var eventLayer = new Konva.Layer({
|
|
6134
6845
|
listening: false
|
|
6135
6846
|
});
|
|
6136
6847
|
baseLayerRef.current = baseLayer;
|
|
6137
|
-
animationLayerRef.current = animationLayer;
|
|
6138
6848
|
eventLayerRef.current = eventLayer;
|
|
6139
6849
|
stage.add(baseLayer);
|
|
6850
|
+
stage.add(eventLayer);
|
|
6140
6851
|
|
|
6141
|
-
if (
|
|
6142
|
-
|
|
6143
|
-
}
|
|
6144
|
-
|
|
6145
|
-
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
6852
|
+
if (enableViewportCulling) {
|
|
6853
|
+
updateViewport$1();
|
|
6854
|
+
} // ResizeObserver: 맵 크기 변경 감지 (RAF로 debounce)
|
|
6146
6855
|
|
|
6147
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
6148
6856
|
|
|
6149
6857
|
var resizeRafId = null;
|
|
6150
6858
|
var resizeObserver = new ResizeObserver(function () {
|
|
6151
|
-
// RAF로 다음 프레임에 한 번만 실행 (debounce 효과)
|
|
6152
6859
|
if (resizeRafId !== null) {
|
|
6153
6860
|
cancelAnimationFrame(resizeRafId);
|
|
6154
6861
|
}
|
|
@@ -6158,7 +6865,11 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6158
6865
|
stage.height(mapDiv.offsetHeight);
|
|
6159
6866
|
offsetCacheRef.current.clear();
|
|
6160
6867
|
boundingBoxCacheRef.current.clear();
|
|
6161
|
-
|
|
6868
|
+
|
|
6869
|
+
if (enableViewportCullingRef.current) {
|
|
6870
|
+
updateViewport$1();
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6162
6873
|
renderAllImmediate();
|
|
6163
6874
|
resizeRafId = null;
|
|
6164
6875
|
});
|
|
@@ -6171,10 +6882,9 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6171
6882
|
controller.addEventListener('CLICK', handleClick);
|
|
6172
6883
|
controller.addEventListener('MOUSEMOVE', handleMouseMove);
|
|
6173
6884
|
controller.addEventListener('DRAGSTART', handleDragStart);
|
|
6174
|
-
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
6175
|
-
|
|
6885
|
+
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
6176
6886
|
mapDiv.addEventListener('mouseleave', handleMouseLeave);
|
|
6177
|
-
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
6887
|
+
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
6178
6888
|
|
|
6179
6889
|
var componentInstance = null;
|
|
6180
6890
|
|
|
@@ -6185,28 +6895,25 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6185
6895
|
return findData(offset) !== null;
|
|
6186
6896
|
},
|
|
6187
6897
|
onClick: onClick,
|
|
6188
|
-
onMouseOver: onMouseOver,
|
|
6189
|
-
onMouseOut: onMouseOut,
|
|
6190
6898
|
findData: findData,
|
|
6191
6899
|
setHovered: setHovered,
|
|
6192
6900
|
handleLocalClick: handleLocalClick,
|
|
6193
6901
|
getSelectedIds: function () {
|
|
6194
6902
|
return selectedIdsRef.current;
|
|
6903
|
+
},
|
|
6904
|
+
isInteractionDisabled: function () {
|
|
6905
|
+
return disableInteractionRef.current;
|
|
6195
6906
|
}
|
|
6196
6907
|
};
|
|
6197
6908
|
context.registerComponent(componentInstance);
|
|
6198
|
-
}
|
|
6199
|
-
|
|
6909
|
+
}
|
|
6200
6910
|
|
|
6201
6911
|
return function () {
|
|
6202
|
-
// RAF 정리
|
|
6203
6912
|
if (resizeRafId !== null) {
|
|
6204
6913
|
cancelAnimationFrame(resizeRafId);
|
|
6205
|
-
}
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
resizeObserver.disconnect(); // 이벤트 리스너 정리
|
|
6914
|
+
}
|
|
6209
6915
|
|
|
6916
|
+
resizeObserver.disconnect();
|
|
6210
6917
|
controller.removeEventListener('IDLE', handleIdle);
|
|
6211
6918
|
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
6212
6919
|
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
@@ -6215,52 +6922,70 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6215
6922
|
controller.removeEventListener('MOUSEMOVE', handleMouseMove);
|
|
6216
6923
|
controller.removeEventListener('DRAGSTART', handleDragStart);
|
|
6217
6924
|
controller.removeEventListener('DRAGEND', handleDragEnd);
|
|
6218
|
-
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
6925
|
+
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
6219
6926
|
|
|
6220
6927
|
if (context && componentInstance) {
|
|
6221
6928
|
context.unregisterComponent(componentInstance);
|
|
6222
|
-
}
|
|
6223
|
-
|
|
6929
|
+
}
|
|
6224
6930
|
|
|
6225
6931
|
baseLayer.destroyChildren();
|
|
6226
|
-
animationLayer.destroyChildren();
|
|
6227
6932
|
eventLayer.destroyChildren();
|
|
6228
|
-
stage.destroy();
|
|
6229
|
-
|
|
6933
|
+
stage.destroy();
|
|
6230
6934
|
offsetCacheRef.current.clear();
|
|
6231
6935
|
boundingBoxCacheRef.current.clear();
|
|
6232
6936
|
spatialIndexRef.current.clear();
|
|
6233
6937
|
};
|
|
6234
|
-
}, []); //
|
|
6235
|
-
|
|
6236
|
-
|
|
6938
|
+
}, []); // disableInteraction 동기화
|
|
6939
|
+
|
|
6940
|
+
useEffect(function () {
|
|
6941
|
+
disableInteractionRef.current = disableInteraction;
|
|
6942
|
+
}, [disableInteraction]); // enableViewportCulling 동기화
|
|
6237
6943
|
|
|
6238
6944
|
useEffect(function () {
|
|
6239
|
-
|
|
6945
|
+
enableViewportCullingRef.current = enableViewportCulling;
|
|
6240
6946
|
|
|
6241
|
-
if (
|
|
6947
|
+
if (stageRef.current) {
|
|
6948
|
+
// 뷰포트 컬링 설정이 변경되면 shape 재생성 필요
|
|
6949
|
+
var baseLayer = baseLayerRef.current;
|
|
6242
6950
|
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6951
|
+
if (baseLayer) {
|
|
6952
|
+
var shape = baseLayer.findOne('.base-render-shape');
|
|
6953
|
+
|
|
6954
|
+
if (shape) {
|
|
6955
|
+
shape.destroy();
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
|
|
6959
|
+
var eventLayer = eventLayerRef.current;
|
|
6960
|
+
|
|
6961
|
+
if (eventLayer) {
|
|
6962
|
+
var shape = eventLayer.findOne('.event-render-shape');
|
|
6963
|
+
|
|
6964
|
+
if (shape) {
|
|
6965
|
+
shape.destroy();
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
renderAllImmediate();
|
|
6970
|
+
}
|
|
6971
|
+
}, [enableViewportCulling]); // 외부 selectedItems 동기화
|
|
6251
6972
|
|
|
6973
|
+
useEffect(function () {
|
|
6974
|
+
if (!stageRef.current) return;
|
|
6975
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
6252
6976
|
doRenderBase();
|
|
6253
|
-
doRenderAnimation();
|
|
6254
6977
|
doRenderEvent();
|
|
6255
|
-
}, [externalSelectedItems]); //
|
|
6256
|
-
// --------------------------------------------------------------------------
|
|
6257
|
-
// Lifecycle: 마커 데이터 변경 시 렌더링
|
|
6258
|
-
// --------------------------------------------------------------------------
|
|
6978
|
+
}, [externalSelectedItems]); // 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
6259
6979
|
|
|
6260
6980
|
useEffect(function () {
|
|
6261
|
-
if (!stageRef.current) return;
|
|
6981
|
+
if (!stageRef.current) return;
|
|
6982
|
+
selectedItemRef.current = externalSelectedItem;
|
|
6983
|
+
doRenderEvent();
|
|
6984
|
+
}, [externalSelectedItem]); // 데이터 변경 시 렌더링 (캐시 정리 및 선택 상태 동기화)
|
|
6262
6985
|
|
|
6263
|
-
|
|
6986
|
+
useEffect(function () {
|
|
6987
|
+
if (!stageRef.current) return;
|
|
6988
|
+
dataRef.current = data;
|
|
6264
6989
|
|
|
6265
6990
|
if (containerRef.current) {
|
|
6266
6991
|
containerRef.current.style.transform = '';
|
|
@@ -6270,48 +6995,12 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6270
6995
|
accumTranslateRef.current = {
|
|
6271
6996
|
x: 0,
|
|
6272
6997
|
y: 0
|
|
6273
|
-
};
|
|
6274
|
-
|
|
6998
|
+
};
|
|
6275
6999
|
offsetCacheRef.current.clear();
|
|
6276
7000
|
boundingBoxCacheRef.current.clear();
|
|
6277
|
-
|
|
6278
|
-
* 선택 상태 동기화 (최적화 버전)
|
|
6279
|
-
*
|
|
6280
|
-
* markers가 변경되면 selectedItemsMapRef도 업데이트 필요
|
|
6281
|
-
* (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
|
|
6282
|
-
*
|
|
6283
|
-
* 🔥 중요: 화면 밖 마커도 선택 상태 유지!
|
|
6284
|
-
* - 현재 markers에 있으면 최신 데이터로 업데이트
|
|
6285
|
-
* - 없으면 기존 selectedItemsMapRef의 데이터 유지
|
|
6286
|
-
*
|
|
6287
|
-
* 최적화: markers를 Map으로 먼저 변환하여 find() 순회 제거
|
|
6288
|
-
* - O(전체 마커 수 + 선택된 개수) - 매우 효율적
|
|
6289
|
-
*/
|
|
6290
|
-
|
|
6291
|
-
var markersMap = new Map(markers.map(function (m) {
|
|
6292
|
-
return [m.id, m];
|
|
6293
|
-
}));
|
|
6294
|
-
var newSelectedItemsMap = new Map();
|
|
6295
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
6296
|
-
// 현재 markers에 있으면 최신 데이터 사용
|
|
6297
|
-
var currentItem = markersMap.get(id);
|
|
6298
|
-
|
|
6299
|
-
if (currentItem) {
|
|
6300
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
6301
|
-
} else {
|
|
6302
|
-
// 화면 밖이면 기존 데이터 유지
|
|
6303
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
6304
|
-
|
|
6305
|
-
if (prevItem) {
|
|
6306
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
6307
|
-
}
|
|
6308
|
-
}
|
|
6309
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 마커도 선택 상태 유지)
|
|
6310
|
-
|
|
6311
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
6312
|
-
|
|
7001
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
6313
7002
|
renderAllImmediate();
|
|
6314
|
-
}, [
|
|
7003
|
+
}, [data]);
|
|
6315
7004
|
return createPortal(React.createElement("div", {
|
|
6316
7005
|
ref: containerRef,
|
|
6317
7006
|
style: {
|
|
@@ -6321,40 +7010,6 @@ var WoongKonvaMarkerComponent = function (_a) {
|
|
|
6321
7010
|
}
|
|
6322
7011
|
}), divElement);
|
|
6323
7012
|
};
|
|
6324
|
-
/**
|
|
6325
|
-
* 🔥 React.memo 최적화: 마커 배열과 selectedItems 변경 체크
|
|
6326
|
-
*
|
|
6327
|
-
* 비교 전략:
|
|
6328
|
-
* 1. markers 배열 비교
|
|
6329
|
-
* 2. selectedItems 배열 비교 (외부 제어)
|
|
6330
|
-
*
|
|
6331
|
-
* 주의: JSON.stringify() 사용 금지! (매우 느림)
|
|
6332
|
-
*/
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
var WoongKonvaMarker = React.memo(WoongKonvaMarkerComponent, function (prevProps, nextProps) {
|
|
6336
|
-
// 1. markers 비교
|
|
6337
|
-
var prevMarkers = prevProps.markers;
|
|
6338
|
-
var nextMarkers = nextProps.markers; // 참조가 같으면 스킵
|
|
6339
|
-
|
|
6340
|
-
if (prevMarkers !== nextMarkers) {
|
|
6341
|
-
// 길이가 다르면 변경됨
|
|
6342
|
-
if (prevMarkers.length !== nextMarkers.length) return false; // 각 마커의 ID 비교
|
|
6343
|
-
|
|
6344
|
-
for (var i = 0; i < prevMarkers.length; i++) {
|
|
6345
|
-
if (prevMarkers[i].id !== nextMarkers[i].id) {
|
|
6346
|
-
return false; // 변경됨 → 리렌더링
|
|
6347
|
-
}
|
|
6348
|
-
}
|
|
6349
|
-
} // 2. selectedItems 비교 (참조만 비교)
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
if (prevProps.selectedItems !== nextProps.selectedItems) {
|
|
6353
|
-
return false; // 변경됨 → 리렌더링
|
|
6354
|
-
}
|
|
6355
|
-
|
|
6356
|
-
return true; // 같음 → 리렌더링 스킵
|
|
6357
|
-
});
|
|
6358
7013
|
|
|
6359
7014
|
var css_248z = ".MintMapWrapper-module_mint-map-control-wrapper-container__DONh7 {\n position: absolute;\n width: 100%;\n height: 100%;\n display: flex;\n pointer-events: none;\n z-index: 101;\n}\n\n.MintMapWrapper-module_mint-map-overlay-wrapper__Jn4wV {\n position: absolute;\n z-index: 1;\n}";
|
|
6360
7015
|
var styles = {"mint-map-control-wrapper-container":"MintMapWrapper-module_mint-map-control-wrapper-container__DONh7","mint-map-overlay-wrapper":"MintMapWrapper-module_mint-map-overlay-wrapper__Jn4wV"};
|
|
@@ -9187,4 +9842,4 @@ function MintMap(_a) {
|
|
|
9187
9842
|
}), loading));
|
|
9188
9843
|
}
|
|
9189
9844
|
|
|
9190
|
-
export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController,
|
|
9845
|
+
export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasMarker, WoongCanvasPolygon, WoongCanvasProvider, buildSpatialIndex, calculateTextBoxWidth, computeMarkerOffset, computePolygonOffsets, createMapEventHandlers, getClusterInfo, getMapOfType, hexToRgba, isInViewport, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, mapValuesToArray, syncExternalSelectedItems, syncSelectedItems, updateViewport, useMarkerMoving, useMintMapController, useWoongCanvasContext, validateEvent, waiting };
|