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