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