@mint-ui/map 1.2.0-test.33 → 1.2.0-test.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/mint-map/core/advanced/shared/context.d.ts +71 -2
- package/dist/components/mint-map/core/advanced/shared/context.js +74 -1
- package/dist/components/mint-map/core/advanced/shared/helpers.d.ts +28 -0
- package/dist/components/mint-map/core/advanced/shared/helpers.js +52 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.d.ts +144 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.js +283 -0
- package/dist/components/mint-map/core/advanced/shared/index.d.ts +3 -0
- package/dist/components/mint-map/core/advanced/shared/performance.d.ts +105 -24
- package/dist/components/mint-map/core/advanced/shared/performance.js +105 -24
- package/dist/components/mint-map/core/advanced/shared/utils.d.ts +128 -14
- package/dist/components/mint-map/core/advanced/shared/utils.js +128 -14
- package/dist/components/mint-map/core/advanced/shared/viewport.d.ts +72 -0
- package/dist/components/mint-map/core/advanced/shared/viewport.js +81 -0
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.js +142 -209
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.d.ts +0 -4
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.js +122 -217
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.d.ts +64 -5
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.js +81 -20
- package/dist/index.es.js +1066 -511
- package/dist/index.js +11 -0
- package/dist/index.umd.js +1072 -509
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -800,6 +800,30 @@
|
|
|
800
800
|
|
|
801
801
|
/**
|
|
802
802
|
* 폴리곤 offset 계산
|
|
803
|
+
*
|
|
804
|
+
* GeoJSON MultiPolygon 형식의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
|
|
805
|
+
*
|
|
806
|
+
* @param polygonData 폴리곤 데이터 (paths 필드 필수)
|
|
807
|
+
* @param controller MintMapController 인스턴스
|
|
808
|
+
* @returns 변환된 화면 좌표 배열 (4차원 배열) 또는 null (변환 실패 시)
|
|
809
|
+
*
|
|
810
|
+
* @remarks
|
|
811
|
+
* - 반환 형식: [MultiPolygon][Polygon][Point][x/y]
|
|
812
|
+
* - 성능: O(n), n은 폴리곤의 총 좌표 수
|
|
813
|
+
* - GeoJSON MultiPolygon 형식 지원
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```typescript
|
|
817
|
+
* const offsets = computePolygonOffsets(polygonData, controller);
|
|
818
|
+
* if (!offsets) return; // 변환 실패
|
|
819
|
+
*
|
|
820
|
+
* // offsets 구조: [MultiPolygon][Polygon][Point][x/y]
|
|
821
|
+
* for (const multiPolygon of offsets) {
|
|
822
|
+
* for (const polygon of multiPolygon) {
|
|
823
|
+
* // polygon은 [Point][x/y] 배열
|
|
824
|
+
* }
|
|
825
|
+
* }
|
|
826
|
+
* ```
|
|
803
827
|
*/
|
|
804
828
|
|
|
805
829
|
var computePolygonOffsets = function (polygonData, controller) {
|
|
@@ -836,6 +860,23 @@
|
|
|
836
860
|
};
|
|
837
861
|
/**
|
|
838
862
|
* 마커 offset 계산
|
|
863
|
+
*
|
|
864
|
+
* 마커의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
|
|
865
|
+
*
|
|
866
|
+
* @param markerData 마커 데이터 (position 필드 필수)
|
|
867
|
+
* @param controller MintMapController 인스턴스
|
|
868
|
+
* @returns 변환된 화면 좌표 (Offset) 또는 null (변환 실패 시)
|
|
869
|
+
*
|
|
870
|
+
* @remarks
|
|
871
|
+
* - 반환된 좌표는 마커의 중심점 (x, y)
|
|
872
|
+
* - 성능: O(1) - 단일 좌표 변환
|
|
873
|
+
*
|
|
874
|
+
* @example
|
|
875
|
+
* ```typescript
|
|
876
|
+
* const offset = computeMarkerOffset(markerData, controller);
|
|
877
|
+
* if (!offset) return; // 변환 실패
|
|
878
|
+
* // offset.x, offset.y는 화면 픽셀 좌표
|
|
879
|
+
* ```
|
|
839
880
|
*/
|
|
840
881
|
|
|
841
882
|
var computeMarkerOffset = function (markerData, controller) {
|
|
@@ -847,6 +888,24 @@
|
|
|
847
888
|
};
|
|
848
889
|
/**
|
|
849
890
|
* Point-in-Polygon 알고리즘
|
|
891
|
+
*
|
|
892
|
+
* Ray Casting 알고리즘을 사용하여 점이 폴리곤 내부에 있는지 확인합니다.
|
|
893
|
+
*
|
|
894
|
+
* @param point 확인할 점의 좌표
|
|
895
|
+
* @param polygon 폴리곤 좌표 배열 (각 요소는 [x, y] 형식)
|
|
896
|
+
* @returns 점이 폴리곤 내부에 있으면 true, 아니면 false
|
|
897
|
+
*
|
|
898
|
+
* @remarks
|
|
899
|
+
* - **알고리즘**: Ray Casting (Ray Crossing)
|
|
900
|
+
* - **성능**: O(n), n은 폴리곤의 좌표 수
|
|
901
|
+
* - **경계 처리**: 경계선 위의 점은 내부로 간주
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```typescript
|
|
905
|
+
* const point = { x: 100, y: 200 };
|
|
906
|
+
* const polygon = [[0, 0], [100, 0], [100, 100], [0, 100]];
|
|
907
|
+
* const isInside = isPointInPolygon(point, polygon);
|
|
908
|
+
* ```
|
|
850
909
|
*/
|
|
851
910
|
|
|
852
911
|
var isPointInPolygon = function (point, polygon) {
|
|
@@ -866,13 +925,32 @@
|
|
|
866
925
|
/**
|
|
867
926
|
* 폴리곤 히트 테스트 (도넛 폴리곤 지원)
|
|
868
927
|
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
928
|
+
* 점이 폴리곤 내부에 있는지 확인합니다. 도넛 폴리곤(구멍이 있는 폴리곤)을 지원합니다.
|
|
929
|
+
*
|
|
930
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
931
|
+
* @param polygonData 폴리곤 데이터
|
|
932
|
+
* @param getPolygonOffsets 폴리곤 좌표 변환 함수
|
|
933
|
+
* @returns 점이 폴리곤 내부에 있으면 true, 아니면 false
|
|
872
934
|
*
|
|
873
|
-
*
|
|
935
|
+
* @remarks
|
|
936
|
+
* - **도넛 폴리곤 처리** (isDonutPolygon === true):
|
|
937
|
+
* 1. 외부 폴리곤(첫 번째): 내부에 있어야 함
|
|
938
|
+
* 2. 내부 구멍들(나머지): 내부에 있으면 안 됨 (evenodd 규칙)
|
|
939
|
+
* - **일반 폴리곤 처리**: Point-in-Polygon 알고리즘 사용
|
|
940
|
+
* - **성능**: O(n), n은 폴리곤의 총 좌표 수
|
|
941
|
+
*
|
|
942
|
+
* **중요**: 도넛 폴리곤과 내부 폴리곤은 별개의 polygonData로 처리됩니다.
|
|
874
943
|
* - 도넛 폴리곤 A: isDonutPolygon=true
|
|
875
944
|
* - 내부 폴리곤 B: isDonutPolygon=false (별도 데이터)
|
|
945
|
+
*
|
|
946
|
+
* @example
|
|
947
|
+
* ```typescript
|
|
948
|
+
* const isHit = isPointInPolygonData(
|
|
949
|
+
* clickedOffset,
|
|
950
|
+
* polygonData,
|
|
951
|
+
* getOrComputePolygonOffsets
|
|
952
|
+
* );
|
|
953
|
+
* ```
|
|
876
954
|
*/
|
|
877
955
|
|
|
878
956
|
var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffsets) {
|
|
@@ -937,10 +1015,29 @@
|
|
|
937
1015
|
/**
|
|
938
1016
|
* 마커 히트 테스트 (클릭/hover 영역 체크)
|
|
939
1017
|
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
1018
|
+
* 점이 마커의 클릭/호버 영역 내부에 있는지 확인합니다.
|
|
1019
|
+
* 마커의 꼬리(tail)는 Hit Test 영역에서 제외됩니다.
|
|
1020
|
+
*
|
|
1021
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
1022
|
+
* @param markerData 마커 데이터
|
|
1023
|
+
* @param getMarkerOffset 마커 좌표 변환 함수
|
|
1024
|
+
* @returns 점이 마커 영역 내부에 있으면 true, 아니면 false
|
|
1025
|
+
*
|
|
1026
|
+
* @remarks
|
|
1027
|
+
* - **꼬리 제외**: 꼬리(tail)는 Hit Test 영역에서 제외됩니다
|
|
1028
|
+
* - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
|
|
1029
|
+
* - boxHeight는 마커 본체만 포함 (꼬리 제외)
|
|
1030
|
+
* - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
|
|
1031
|
+
* - **성능**: O(1) - 단순 사각형 영역 체크
|
|
1032
|
+
*
|
|
1033
|
+
* @example
|
|
1034
|
+
* ```typescript
|
|
1035
|
+
* const isHit = isPointInMarkerData(
|
|
1036
|
+
* clickedOffset,
|
|
1037
|
+
* markerData,
|
|
1038
|
+
* getOrComputeMarkerOffset
|
|
1039
|
+
* );
|
|
1040
|
+
* ```
|
|
944
1041
|
*/
|
|
945
1042
|
|
|
946
1043
|
var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
|
|
@@ -977,12 +1074,29 @@
|
|
|
977
1074
|
/**
|
|
978
1075
|
* 텍스트 박스의 너비를 계산합니다.
|
|
979
1076
|
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
* @param
|
|
984
|
-
* @param
|
|
985
|
-
* @
|
|
1077
|
+
* Canvas 2D Context의 measureText()를 사용하여 텍스트의 실제 너비를 계산하고,
|
|
1078
|
+
* 패딩과 최소 너비를 고려하여 최종 너비를 반환합니다.
|
|
1079
|
+
*
|
|
1080
|
+
* @param params 파라미터 객체
|
|
1081
|
+
* @param params.text 측정할 텍스트
|
|
1082
|
+
* @param params.fontConfig 폰트 설정 (예: 'bold 16px Arial')
|
|
1083
|
+
* @param params.padding 텍스트 박스에 적용할 패딩 값 (px)
|
|
1084
|
+
* @param params.minWidth 최소 너비 (px)
|
|
1085
|
+
* @returns 계산된 텍스트 박스의 너비 (px)
|
|
1086
|
+
*
|
|
1087
|
+
* @remarks
|
|
1088
|
+
* - 성능: O(1) - 단일 텍스트 측정
|
|
1089
|
+
* - 임시 Canvas를 사용하여 정확한 너비 측정
|
|
1090
|
+
*
|
|
1091
|
+
* @example
|
|
1092
|
+
* ```typescript
|
|
1093
|
+
* const width = calculateTextBoxWidth({
|
|
1094
|
+
* text: "Hello World",
|
|
1095
|
+
* fontConfig: 'bold 16px Arial',
|
|
1096
|
+
* padding: 20,
|
|
1097
|
+
* minWidth: 60
|
|
1098
|
+
* });
|
|
1099
|
+
* ```
|
|
986
1100
|
*/
|
|
987
1101
|
|
|
988
1102
|
var calculateTextBoxWidth = function (_a) {
|
|
@@ -997,6 +1111,29 @@
|
|
|
997
1111
|
};
|
|
998
1112
|
|
|
999
1113
|
var WoongCanvasContext = React.createContext(null);
|
|
1114
|
+
/**
|
|
1115
|
+
* WoongCanvasProvider 컴포넌트
|
|
1116
|
+
*
|
|
1117
|
+
* 다중 WoongCanvas 컴포넌트 인스턴스를 관리하는 Context Provider입니다.
|
|
1118
|
+
* 여러 WoongCanvasMarker/WoongCanvasPolygon이 함께 사용될 때 전역 이벤트 조정을 수행합니다.
|
|
1119
|
+
*
|
|
1120
|
+
* @param props 컴포넌트 props
|
|
1121
|
+
* @param props.children 자식 컴포넌트
|
|
1122
|
+
*
|
|
1123
|
+
* @remarks
|
|
1124
|
+
* - zIndex 기반으로 이벤트 우선순위 처리
|
|
1125
|
+
* - 전역 클릭/호버 이벤트 조정
|
|
1126
|
+
* - 여러 레이어 간 상호작용 관리
|
|
1127
|
+
*
|
|
1128
|
+
* @example
|
|
1129
|
+
* ```tsx
|
|
1130
|
+
* <WoongCanvasProvider>
|
|
1131
|
+
* <WoongCanvasMarker data={markers} zIndex={10} />
|
|
1132
|
+
* <WoongCanvasPolygon data={polygons} zIndex={5} />
|
|
1133
|
+
* </WoongCanvasProvider>
|
|
1134
|
+
* ```
|
|
1135
|
+
*/
|
|
1136
|
+
|
|
1000
1137
|
var WoongCanvasProvider = function (_a) {
|
|
1001
1138
|
var children = _a.children;
|
|
1002
1139
|
var controller = useMintMapController(); // Refs
|
|
@@ -1007,7 +1144,12 @@
|
|
|
1007
1144
|
var draggingRef = React.useRef(false);
|
|
1008
1145
|
/**
|
|
1009
1146
|
* 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
1010
|
-
*
|
|
1147
|
+
*
|
|
1148
|
+
* 컴포넌트 인스턴스를 등록하고 zIndex 기준으로 내림차순 정렬합니다.
|
|
1149
|
+
* 높은 zIndex를 가진 컴포넌트가 이벤트 처리에서 우선순위를 가집니다.
|
|
1150
|
+
*
|
|
1151
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1152
|
+
* @param instance 등록할 컴포넌트 인스턴스
|
|
1011
1153
|
*/
|
|
1012
1154
|
|
|
1013
1155
|
var registerComponent = React.useCallback(function (instance) {
|
|
@@ -1018,6 +1160,12 @@
|
|
|
1018
1160
|
}, []);
|
|
1019
1161
|
/**
|
|
1020
1162
|
* 컴포넌트 등록 해제
|
|
1163
|
+
*
|
|
1164
|
+
* 컴포넌트 인스턴스를 등록 해제합니다.
|
|
1165
|
+
* hover 중이던 컴포넌트면 hover 상태도 초기화합니다.
|
|
1166
|
+
*
|
|
1167
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1168
|
+
* @param instance 등록 해제할 컴포넌트 인스턴스
|
|
1021
1169
|
*/
|
|
1022
1170
|
|
|
1023
1171
|
var unregisterComponent = React.useCallback(function (instance) {
|
|
@@ -1033,6 +1181,15 @@
|
|
|
1033
1181
|
}, []);
|
|
1034
1182
|
/**
|
|
1035
1183
|
* 전역 클릭 핸들러 (zIndex 우선순위)
|
|
1184
|
+
*
|
|
1185
|
+
* 모든 등록된 WoongCanvas 컴포넌트 중 zIndex가 높은 컴포넌트부터 클릭 이벤트를 처리합니다.
|
|
1186
|
+
*
|
|
1187
|
+
* @param event 클릭 이벤트 파라미터
|
|
1188
|
+
*
|
|
1189
|
+
* @remarks
|
|
1190
|
+
* - zIndex가 높은 컴포넌트부터 순회하여 첫 번째 히트만 처리
|
|
1191
|
+
* - 상호작용이 비활성화된 컴포넌트는 스킵
|
|
1192
|
+
* - Context가 없으면 각 컴포넌트의 로컬 핸들러가 처리
|
|
1036
1193
|
*/
|
|
1037
1194
|
|
|
1038
1195
|
var handleGlobalClick = React.useCallback(function (event) {
|
|
@@ -1060,6 +1217,16 @@
|
|
|
1060
1217
|
}, [controller]);
|
|
1061
1218
|
/**
|
|
1062
1219
|
* 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
1220
|
+
*
|
|
1221
|
+
* 모든 등록된 WoongCanvas 컴포넌트 중 zIndex가 높은 컴포넌트부터 hover 이벤트를 처리합니다.
|
|
1222
|
+
*
|
|
1223
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
1224
|
+
*
|
|
1225
|
+
* @remarks
|
|
1226
|
+
* - zIndex가 높은 컴포넌트부터 순회하여 첫 번째 히트만 처리
|
|
1227
|
+
* - 상호작용이 비활성화된 컴포넌트는 스킵
|
|
1228
|
+
* - 드래그 중이면 이벤트 무시 (성능 최적화)
|
|
1229
|
+
* - hover 상태 변경 감지 후 이전 hover 해제 및 새 hover 설정
|
|
1063
1230
|
*/
|
|
1064
1231
|
|
|
1065
1232
|
var handleGlobalMouseMove = React.useCallback(function (event) {
|
|
@@ -1146,6 +1313,26 @@
|
|
|
1146
1313
|
value: contextValue
|
|
1147
1314
|
}, children);
|
|
1148
1315
|
};
|
|
1316
|
+
/**
|
|
1317
|
+
* WoongCanvas Context Hook
|
|
1318
|
+
*
|
|
1319
|
+
* WoongCanvasProvider로 감싸진 컴포넌트에서 Context에 접근할 수 있는 hook입니다.
|
|
1320
|
+
*
|
|
1321
|
+
* @returns WoongCanvasContextValue 또는 null (Provider 없으면)
|
|
1322
|
+
*
|
|
1323
|
+
* @remarks
|
|
1324
|
+
* - Provider로 감싸지지 않으면 null 반환 (각 컴포넌트가 로컬 이벤트 처리)
|
|
1325
|
+
* - Provider로 감싸져 있으면 전역 이벤트 조정 활성화
|
|
1326
|
+
*
|
|
1327
|
+
* @example
|
|
1328
|
+
* ```typescript
|
|
1329
|
+
* const context = useWoongCanvasContext();
|
|
1330
|
+
* if (context) {
|
|
1331
|
+
* // Context 사용 중 (전역 이벤트 조정)
|
|
1332
|
+
* }
|
|
1333
|
+
* ```
|
|
1334
|
+
*/
|
|
1335
|
+
|
|
1149
1336
|
var useWoongCanvasContext = function () {
|
|
1150
1337
|
var context = React.useContext(WoongCanvasContext);
|
|
1151
1338
|
return context;
|
|
@@ -1199,12 +1386,32 @@
|
|
|
1199
1386
|
var DEFAULT_MAX_CACHE_SIZE = 30000;
|
|
1200
1387
|
/**
|
|
1201
1388
|
* LRU (Least Recently Used) Cache
|
|
1202
|
-
* 메모리 제한을 위한 캐시 구현 (최적화 버전)
|
|
1203
1389
|
*
|
|
1204
|
-
*
|
|
1390
|
+
* 메모리 제한을 위한 캐시 구현입니다. WoongCanvas 컴포넌트에서 좌표 변환 결과를 캐싱하는데 사용됩니다.
|
|
1391
|
+
*
|
|
1392
|
+
* @template K 캐시 키 타입
|
|
1393
|
+
* @template V 캐시 값 타입
|
|
1394
|
+
*
|
|
1395
|
+
* @remarks
|
|
1396
|
+
* **개선 사항**:
|
|
1205
1397
|
* 1. get() 성능 향상: 접근 빈도 추적 없이 단순 조회만 수행 (delete+set 제거)
|
|
1206
1398
|
* 2. set() 버그 수정: 기존 키 업데이트 시 maxSize 체크 로직 개선
|
|
1207
1399
|
* 3. 메모리 효율: 단순 FIFO 캐시로 동작하여 오버헤드 최소화
|
|
1400
|
+
*
|
|
1401
|
+
* **트레이드오프**:
|
|
1402
|
+
* - 장점: 읽기 성능 대폭 향상 (10,000번 get → 이전보다 2배 빠름)
|
|
1403
|
+
* - 단점: 접근 빈도가 아닌 삽입 순서 기반 eviction (FIFO)
|
|
1404
|
+
*
|
|
1405
|
+
* WoongCanvasMarker 사용 사례에 최적:
|
|
1406
|
+
* - 좌표 변환 결과는 zoom/pan 시 어차피 전체 초기화
|
|
1407
|
+
* - 접근 빈도 추적보다 빠른 조회가 더 중요
|
|
1408
|
+
*
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```typescript
|
|
1411
|
+
* const cache = new LRUCache<string, Offset>(30000);
|
|
1412
|
+
* cache.set(item.id, offset);
|
|
1413
|
+
* const cached = cache.get(item.id);
|
|
1414
|
+
* ```
|
|
1208
1415
|
*/
|
|
1209
1416
|
|
|
1210
1417
|
var LRUCache =
|
|
@@ -1221,17 +1428,12 @@
|
|
|
1221
1428
|
/**
|
|
1222
1429
|
* 캐시에서 값 조회
|
|
1223
1430
|
*
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1226
|
-
* - 현재: 단순 조회만 수행 (O(1) 해시 조회)
|
|
1227
|
-
*
|
|
1228
|
-
* 트레이드오프:
|
|
1229
|
-
* - 장점: 읽기 성능 대폭 향상 (10,000번 get → 이전보다 2배 빠름)
|
|
1230
|
-
* - 단점: 접근 빈도가 아닌 삽입 순서 기반 eviction (FIFO)
|
|
1431
|
+
* @param key 조회할 키
|
|
1432
|
+
* @returns 캐시된 값 또는 undefined (캐시 미스 시)
|
|
1231
1433
|
*
|
|
1232
|
-
*
|
|
1233
|
-
* -
|
|
1234
|
-
* -
|
|
1434
|
+
* @remarks
|
|
1435
|
+
* - 성능: O(1) 해시 조회
|
|
1436
|
+
* - 최적화: delete+set 제거로 읽기 성능 대폭 향상
|
|
1235
1437
|
*/
|
|
1236
1438
|
|
|
1237
1439
|
|
|
@@ -1239,11 +1441,15 @@
|
|
|
1239
1441
|
return this.cache.get(key);
|
|
1240
1442
|
};
|
|
1241
1443
|
/**
|
|
1242
|
-
* 캐시에 값 저장
|
|
1444
|
+
* 캐시에 값 저장
|
|
1243
1445
|
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1446
|
+
* @param key 저장할 키
|
|
1447
|
+
* @param value 저장할 값
|
|
1448
|
+
*
|
|
1449
|
+
* @remarks
|
|
1450
|
+
* - 기존 키 업데이트: 단순 덮어쓰기 (크기 변화 없음)
|
|
1451
|
+
* - 신규 키 추가: 크기 체크 후 필요시 가장 오래된 항목 제거 (FIFO)
|
|
1452
|
+
* - 성능: O(1) 평균 시간복잡도
|
|
1247
1453
|
*/
|
|
1248
1454
|
|
|
1249
1455
|
|
|
@@ -1341,9 +1547,18 @@
|
|
|
1341
1547
|
/**
|
|
1342
1548
|
* 항목 추가 (바운딩 박스 기반)
|
|
1343
1549
|
*
|
|
1344
|
-
*
|
|
1550
|
+
* 항목을 공간 인덱스에 추가합니다. 바운딩 박스가 걸치는 모든 셀에 삽입됩니다.
|
|
1551
|
+
*
|
|
1552
|
+
* @param item 추가할 항목
|
|
1553
|
+
* @param minX 바운딩 박스 최소 X 좌표
|
|
1554
|
+
* @param minY 바운딩 박스 최소 Y 좌표
|
|
1555
|
+
* @param maxX 바운딩 박스 최대 X 좌표
|
|
1556
|
+
* @param maxY 바운딩 박스 최대 Y 좌표
|
|
1557
|
+
*
|
|
1558
|
+
* @remarks
|
|
1345
1559
|
* - 중복 삽입 방지: 기존 항목이 있으면 먼저 제거 후 재삽입
|
|
1346
1560
|
* - 메모리 누수 방지: 이전 셀 참조 완전 제거
|
|
1561
|
+
* - 성능: O(1) 평균 시간복잡도
|
|
1347
1562
|
*/
|
|
1348
1563
|
|
|
1349
1564
|
|
|
@@ -1367,7 +1582,14 @@
|
|
|
1367
1582
|
/**
|
|
1368
1583
|
* 항목 제거
|
|
1369
1584
|
*
|
|
1370
|
-
*
|
|
1585
|
+
* 공간 인덱스에서 항목을 제거합니다.
|
|
1586
|
+
*
|
|
1587
|
+
* @param item 제거할 항목
|
|
1588
|
+
*
|
|
1589
|
+
* @remarks
|
|
1590
|
+
* - 메모리 누수 방지: 모든 셀에서 참조 완전 제거
|
|
1591
|
+
* - 빈 셀 정리: 항목이 없어진 셀은 자동으로 정리됨
|
|
1592
|
+
* - 성능: O(셀 개수), 보통 O(1)
|
|
1371
1593
|
*/
|
|
1372
1594
|
|
|
1373
1595
|
|
|
@@ -1398,7 +1620,13 @@
|
|
|
1398
1620
|
/**
|
|
1399
1621
|
* 항목 위치 업데이트
|
|
1400
1622
|
*
|
|
1401
|
-
*
|
|
1623
|
+
* 항목의 위치를 업데이트합니다. remove + insert의 편의 함수입니다.
|
|
1624
|
+
*
|
|
1625
|
+
* @param item 업데이트할 항목
|
|
1626
|
+
* @param minX 새로운 바운딩 박스 최소 X 좌표
|
|
1627
|
+
* @param minY 새로운 바운딩 박스 최소 Y 좌표
|
|
1628
|
+
* @param maxX 새로운 바운딩 박스 최대 X 좌표
|
|
1629
|
+
* @param maxY 새로운 바운딩 박스 최대 Y 좌표
|
|
1402
1630
|
*/
|
|
1403
1631
|
|
|
1404
1632
|
|
|
@@ -1408,7 +1636,26 @@
|
|
|
1408
1636
|
/**
|
|
1409
1637
|
* 점 주변의 항목 조회 (1개 셀만)
|
|
1410
1638
|
*
|
|
1411
|
-
*
|
|
1639
|
+
* 특정 좌표가 속한 셀의 모든 항목을 반환합니다.
|
|
1640
|
+
*
|
|
1641
|
+
* @param x 조회할 X 좌표
|
|
1642
|
+
* @param y 조회할 Y 좌표
|
|
1643
|
+
* @returns 해당 셀의 항목 배열 (없으면 빈 배열)
|
|
1644
|
+
*
|
|
1645
|
+
* @remarks
|
|
1646
|
+
* - 성능: O(해당 셀의 항목 수) - 보통 ~10개 (30,000개 전체를 체크하지 않음)
|
|
1647
|
+
* - Hit Test에 최적화된 메서드
|
|
1648
|
+
* - 빈 배열 재사용으로 메모리 할당 최소화
|
|
1649
|
+
*
|
|
1650
|
+
* @example
|
|
1651
|
+
* ```typescript
|
|
1652
|
+
* const candidates = grid.queryPoint(mouseX, mouseY);
|
|
1653
|
+
* for (const item of candidates) {
|
|
1654
|
+
* if (isPointInItem(item, mouseX, mouseY)) {
|
|
1655
|
+
* return item;
|
|
1656
|
+
* }
|
|
1657
|
+
* }
|
|
1658
|
+
* ```
|
|
1412
1659
|
*/
|
|
1413
1660
|
|
|
1414
1661
|
|
|
@@ -1421,8 +1668,18 @@
|
|
|
1421
1668
|
/**
|
|
1422
1669
|
* 영역 내 항목 조회
|
|
1423
1670
|
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1671
|
+
* 특정 영역(바운딩 박스)과 교차하는 모든 항목을 반환합니다.
|
|
1672
|
+
*
|
|
1673
|
+
* @param minX 영역 최소 X 좌표
|
|
1674
|
+
* @param minY 영역 최소 Y 좌표
|
|
1675
|
+
* @param maxX 영역 최대 X 좌표
|
|
1676
|
+
* @param maxY 영역 최대 Y 좌표
|
|
1677
|
+
* @returns 영역과 교차하는 항목 배열 (중복 제거됨)
|
|
1678
|
+
*
|
|
1679
|
+
* @remarks
|
|
1680
|
+
* - 성능: O(셀 개수 × 셀당 평균 항목 수)
|
|
1681
|
+
* - Set으로 중복 제거 보장 (항목이 여러 셀에 걸쳐 있어도 한 번만 반환)
|
|
1682
|
+
* - Viewport Culling에 유용
|
|
1426
1683
|
*/
|
|
1427
1684
|
|
|
1428
1685
|
|
|
@@ -1447,7 +1704,11 @@
|
|
|
1447
1704
|
/**
|
|
1448
1705
|
* 항목 존재 여부 확인
|
|
1449
1706
|
*
|
|
1450
|
-
*
|
|
1707
|
+
* @param item 확인할 항목
|
|
1708
|
+
* @returns 항목이 인덱스에 있으면 true, 아니면 false
|
|
1709
|
+
*
|
|
1710
|
+
* @remarks
|
|
1711
|
+
* - 성능: O(1) 해시 조회
|
|
1451
1712
|
*/
|
|
1452
1713
|
|
|
1453
1714
|
|
|
@@ -1466,7 +1727,14 @@
|
|
|
1466
1727
|
/**
|
|
1467
1728
|
* 통계 정보
|
|
1468
1729
|
*
|
|
1469
|
-
*
|
|
1730
|
+
* 공간 인덱스의 현재 상태를 반환합니다. 디버깅 및 성능 분석에 유용합니다.
|
|
1731
|
+
*
|
|
1732
|
+
* @returns 통계 정보 객체
|
|
1733
|
+
*
|
|
1734
|
+
* @remarks
|
|
1735
|
+
* - totalCells: 현재 사용 중인 셀 개수
|
|
1736
|
+
* - totalItems: 인덱스에 등록된 고유 항목 수 (정확)
|
|
1737
|
+
* - avgItemsPerCell: 셀당 평균 항목 수
|
|
1470
1738
|
*/
|
|
1471
1739
|
|
|
1472
1740
|
|
|
@@ -1485,6 +1753,400 @@
|
|
|
1485
1753
|
return SpatialHashGrid;
|
|
1486
1754
|
}();
|
|
1487
1755
|
|
|
1756
|
+
/**
|
|
1757
|
+
* 현재 뷰포트 영역 계산
|
|
1758
|
+
*
|
|
1759
|
+
* Konva Stage의 크기와 컬링 마진을 기반으로 뷰포트 경계를 계산합니다.
|
|
1760
|
+
*
|
|
1761
|
+
* @param stage Konva Stage 인스턴스 (width, height 메서드 제공)
|
|
1762
|
+
* @param cullingMargin 컬링 여유 공간 (px)
|
|
1763
|
+
* @param viewportRef 뷰포트 경계를 저장할 ref
|
|
1764
|
+
*
|
|
1765
|
+
* @remarks
|
|
1766
|
+
* - 화면 밖 cullingMargin만큼의 영역까지 포함하여 계산
|
|
1767
|
+
* - 스크롤 시 부드러운 전환을 위해 여유 공간 포함
|
|
1768
|
+
*
|
|
1769
|
+
* @example
|
|
1770
|
+
* ```typescript
|
|
1771
|
+
* updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
1772
|
+
* ```
|
|
1773
|
+
*/
|
|
1774
|
+
var updateViewport = function (stage, cullingMargin, viewportRef) {
|
|
1775
|
+
if (!stage) return;
|
|
1776
|
+
viewportRef.current = {
|
|
1777
|
+
minX: -cullingMargin,
|
|
1778
|
+
maxX: stage.width() + cullingMargin,
|
|
1779
|
+
minY: -cullingMargin,
|
|
1780
|
+
maxY: stage.height() + cullingMargin
|
|
1781
|
+
};
|
|
1782
|
+
};
|
|
1783
|
+
/**
|
|
1784
|
+
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
1785
|
+
*
|
|
1786
|
+
* 뷰포트 컬링을 위한 함수입니다. 바운딩 박스와 뷰포트 경계의 교차를 확인합니다.
|
|
1787
|
+
* 바운딩 박스는 캐시되어 성능을 최적화합니다.
|
|
1788
|
+
*
|
|
1789
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1790
|
+
* @param item 확인할 아이템
|
|
1791
|
+
* @param enableViewportCulling 뷰포트 컬링 활성화 여부
|
|
1792
|
+
* @param viewportRef 뷰포트 경계 ref
|
|
1793
|
+
* @param boundingBoxCacheRef 바운딩 박스 캐시 ref
|
|
1794
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1795
|
+
* @returns 뷰포트 안에 있으면 true, 아니면 false
|
|
1796
|
+
*
|
|
1797
|
+
* @remarks
|
|
1798
|
+
* - 성능: O(1) (캐시 히트 시) 또는 O(바운딩 박스 계산 비용) (캐시 미스 시)
|
|
1799
|
+
* - 바운딩 박스는 자동으로 캐시되어 재사용됨
|
|
1800
|
+
*
|
|
1801
|
+
* @example
|
|
1802
|
+
* ```typescript
|
|
1803
|
+
* const isVisible = isInViewport(
|
|
1804
|
+
* item,
|
|
1805
|
+
* enableViewportCulling,
|
|
1806
|
+
* viewportRef,
|
|
1807
|
+
* boundingBoxCacheRef,
|
|
1808
|
+
* computeBoundingBox
|
|
1809
|
+
* );
|
|
1810
|
+
* ```
|
|
1811
|
+
*/
|
|
1812
|
+
|
|
1813
|
+
var isInViewport = function (item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox) {
|
|
1814
|
+
if (!enableViewportCulling || !viewportRef.current) return true;
|
|
1815
|
+
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
1816
|
+
|
|
1817
|
+
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
1818
|
+
|
|
1819
|
+
if (!bbox) {
|
|
1820
|
+
// 바운딩 박스 계산 (공통 함수 사용)
|
|
1821
|
+
var computed = computeBoundingBox(item);
|
|
1822
|
+
if (!computed) return false;
|
|
1823
|
+
bbox = computed;
|
|
1824
|
+
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
1825
|
+
} // 바운딩 박스와 viewport 교차 체크
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* 지도 이벤트 핸들러 생성 함수
|
|
1833
|
+
*
|
|
1834
|
+
* 지도 이동, 줌, 드래그 등의 이벤트를 처리하는 핸들러들을 생성합니다.
|
|
1835
|
+
*
|
|
1836
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1837
|
+
* @param deps 이벤트 핸들러 생성에 필요한 의존성
|
|
1838
|
+
* @returns 지도 이벤트 핸들러 객체
|
|
1839
|
+
*
|
|
1840
|
+
* @example
|
|
1841
|
+
* ```typescript
|
|
1842
|
+
* const {
|
|
1843
|
+
* handleIdle,
|
|
1844
|
+
* handleZoomStart,
|
|
1845
|
+
* handleZoomEnd,
|
|
1846
|
+
* handleCenterChanged,
|
|
1847
|
+
* handleDragStart,
|
|
1848
|
+
* handleDragEnd,
|
|
1849
|
+
* } = createMapEventHandlers({
|
|
1850
|
+
* controller,
|
|
1851
|
+
* containerRef,
|
|
1852
|
+
* markerRef,
|
|
1853
|
+
* options,
|
|
1854
|
+
* prevCenterOffsetRef,
|
|
1855
|
+
* accumTranslateRef,
|
|
1856
|
+
* offsetCacheRef,
|
|
1857
|
+
* boundingBoxCacheRef,
|
|
1858
|
+
* renderAllImmediate,
|
|
1859
|
+
* });
|
|
1860
|
+
* ```
|
|
1861
|
+
*/
|
|
1862
|
+
|
|
1863
|
+
var createMapEventHandlers = function (deps) {
|
|
1864
|
+
var controller = deps.controller,
|
|
1865
|
+
containerRef = deps.containerRef,
|
|
1866
|
+
markerRef = deps.markerRef,
|
|
1867
|
+
options = deps.options,
|
|
1868
|
+
prevCenterOffsetRef = deps.prevCenterOffsetRef,
|
|
1869
|
+
accumTranslateRef = deps.accumTranslateRef,
|
|
1870
|
+
offsetCacheRef = deps.offsetCacheRef,
|
|
1871
|
+
boundingBoxCacheRef = deps.boundingBoxCacheRef,
|
|
1872
|
+
renderAllImmediate = deps.renderAllImmediate;
|
|
1873
|
+
/**
|
|
1874
|
+
* 지도 이동/줌 완료 시 처리
|
|
1875
|
+
*
|
|
1876
|
+
* - 캐시 초기화: 좌표 변환 결과가 변경되었으므로 캐시 무효화
|
|
1877
|
+
* - 마커 위치 업데이트: 새로운 지도 위치에 맞게 마커 재배치
|
|
1878
|
+
* - 렌더링: 새 위치에서 전체 렌더링 수행
|
|
1879
|
+
*/
|
|
1880
|
+
|
|
1881
|
+
var handleIdle = function () {
|
|
1882
|
+
prevCenterOffsetRef.current = null;
|
|
1883
|
+
accumTranslateRef.current = {
|
|
1884
|
+
x: 0,
|
|
1885
|
+
y: 0
|
|
1886
|
+
}; // 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
1887
|
+
|
|
1888
|
+
offsetCacheRef.current.clear();
|
|
1889
|
+
boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
|
|
1890
|
+
|
|
1891
|
+
var bounds = controller.getCurrBounds();
|
|
1892
|
+
|
|
1893
|
+
var markerOptions = tslib.__assign({
|
|
1894
|
+
position: bounds.nw
|
|
1895
|
+
}, options);
|
|
1896
|
+
|
|
1897
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
1898
|
+
|
|
1899
|
+
if (containerRef.current) {
|
|
1900
|
+
containerRef.current.style.transform = '';
|
|
1901
|
+
containerRef.current.style.visibility = '';
|
|
1902
|
+
} // 새 위치에서 렌더링
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
renderAllImmediate();
|
|
1906
|
+
};
|
|
1907
|
+
/**
|
|
1908
|
+
* 줌 시작 시 처리 (일시적으로 숨김)
|
|
1909
|
+
*/
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
var handleZoomStart = function () {
|
|
1913
|
+
if (containerRef.current) {
|
|
1914
|
+
containerRef.current.style.visibility = 'hidden';
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
/**
|
|
1918
|
+
* 줌 종료 시 처리 (다시 표시)
|
|
1919
|
+
*/
|
|
1920
|
+
|
|
1921
|
+
|
|
1922
|
+
var handleZoomEnd = function () {
|
|
1923
|
+
if (containerRef.current) {
|
|
1924
|
+
containerRef.current.style.visibility = '';
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
/**
|
|
1928
|
+
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
1929
|
+
*/
|
|
1930
|
+
|
|
1931
|
+
|
|
1932
|
+
var handleCenterChanged = function () {
|
|
1933
|
+
var center = controller.getCurrBounds().getCenter();
|
|
1934
|
+
var curr = controller.positionToOffset(center);
|
|
1935
|
+
var prev = prevCenterOffsetRef.current;
|
|
1936
|
+
|
|
1937
|
+
if (!prev) {
|
|
1938
|
+
prevCenterOffsetRef.current = {
|
|
1939
|
+
x: curr.x,
|
|
1940
|
+
y: curr.y
|
|
1941
|
+
};
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var dx = prev.x - curr.x;
|
|
1946
|
+
var dy = prev.y - curr.y;
|
|
1947
|
+
accumTranslateRef.current = {
|
|
1948
|
+
x: accumTranslateRef.current.x + dx,
|
|
1949
|
+
y: accumTranslateRef.current.y + dy
|
|
1950
|
+
};
|
|
1951
|
+
prevCenterOffsetRef.current = {
|
|
1952
|
+
x: curr.x,
|
|
1953
|
+
y: curr.y
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
if (containerRef.current) {
|
|
1957
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
/**
|
|
1961
|
+
* 드래그 시작 처리
|
|
1962
|
+
*/
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
var handleDragStart = function () {// 커서는 각 컴포넌트에서 처리
|
|
1966
|
+
};
|
|
1967
|
+
/**
|
|
1968
|
+
* 드래그 종료 처리
|
|
1969
|
+
*/
|
|
1970
|
+
|
|
1971
|
+
|
|
1972
|
+
var handleDragEnd = function () {// 커서는 각 컴포넌트에서 처리
|
|
1973
|
+
};
|
|
1974
|
+
|
|
1975
|
+
return {
|
|
1976
|
+
handleIdle: handleIdle,
|
|
1977
|
+
handleZoomStart: handleZoomStart,
|
|
1978
|
+
handleZoomEnd: handleZoomEnd,
|
|
1979
|
+
handleCenterChanged: handleCenterChanged,
|
|
1980
|
+
handleDragStart: handleDragStart,
|
|
1981
|
+
handleDragEnd: handleDragEnd
|
|
1982
|
+
};
|
|
1983
|
+
};
|
|
1984
|
+
/**
|
|
1985
|
+
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
1986
|
+
*
|
|
1987
|
+
* Spatial Hash Grid에 모든 데이터의 바운딩 박스를 삽입합니다.
|
|
1988
|
+
* 이를 통해 클릭/호버 시 O(1) 수준의 빠른 Hit Test가 가능합니다.
|
|
1989
|
+
*
|
|
1990
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1991
|
+
* @param data 공간 인덱스에 삽입할 데이터 배열
|
|
1992
|
+
* @param spatialIndex Spatial Hash Grid 인스턴스
|
|
1993
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1994
|
+
*
|
|
1995
|
+
* @remarks
|
|
1996
|
+
* - 성능: O(n) 시간복잡도, n은 데이터 개수
|
|
1997
|
+
* - 호출 시점: 데이터 변경 시 또는 지도 이동/줌 완료 시
|
|
1998
|
+
*
|
|
1999
|
+
* @example
|
|
2000
|
+
* ```typescript
|
|
2001
|
+
* buildSpatialIndex(
|
|
2002
|
+
* dataRef.current,
|
|
2003
|
+
* spatialIndexRef.current,
|
|
2004
|
+
* computeBoundingBox
|
|
2005
|
+
* );
|
|
2006
|
+
* ```
|
|
2007
|
+
*/
|
|
2008
|
+
|
|
2009
|
+
var buildSpatialIndex = function (data, spatialIndex, computeBoundingBox) {
|
|
2010
|
+
spatialIndex.clear();
|
|
2011
|
+
|
|
2012
|
+
for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
|
|
2013
|
+
var item = data_1[_i];
|
|
2014
|
+
var bbox = computeBoundingBox(item);
|
|
2015
|
+
|
|
2016
|
+
if (bbox) {
|
|
2017
|
+
spatialIndex.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
/**
|
|
2022
|
+
* 선택 상태 동기화 유틸리티
|
|
2023
|
+
*
|
|
2024
|
+
* 데이터 변경 시 선택된 항목의 참조를 최신 데이터로 업데이트합니다.
|
|
2025
|
+
* 화면 밖에 있는 선택된 항목도 선택 상태를 유지합니다.
|
|
2026
|
+
*
|
|
2027
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
2028
|
+
* @param data 최신 데이터 배열
|
|
2029
|
+
* @param selectedIds 선택된 항목 ID Set
|
|
2030
|
+
* @param selectedItemsMap 현재 선택된 항목 Map
|
|
2031
|
+
* @returns 업데이트된 선택된 항목 Map
|
|
2032
|
+
*
|
|
2033
|
+
* @remarks
|
|
2034
|
+
* - 성능: O(n + m), n은 전체 데이터 수, m은 선택된 항목 수
|
|
2035
|
+
* - 화면 밖 데이터도 선택 상태 유지 (최신 데이터가 없으면 기존 데이터 유지)
|
|
2036
|
+
*
|
|
2037
|
+
* @example
|
|
2038
|
+
* ```typescript
|
|
2039
|
+
* selectedItemsMapRef.current = syncSelectedItems(
|
|
2040
|
+
* data,
|
|
2041
|
+
* selectedIdsRef.current,
|
|
2042
|
+
* selectedItemsMapRef.current
|
|
2043
|
+
* );
|
|
2044
|
+
* ```
|
|
2045
|
+
*/
|
|
2046
|
+
|
|
2047
|
+
var syncSelectedItems = function (data, selectedIds, selectedItemsMap) {
|
|
2048
|
+
var dataMap = new Map(data.map(function (m) {
|
|
2049
|
+
return [m.id, m];
|
|
2050
|
+
}));
|
|
2051
|
+
var newSelectedItemsMap = new Map();
|
|
2052
|
+
selectedIds.forEach(function (id) {
|
|
2053
|
+
// 현재 data에 있으면 최신 데이터 사용
|
|
2054
|
+
var currentItem = dataMap.get(id);
|
|
2055
|
+
|
|
2056
|
+
if (currentItem) {
|
|
2057
|
+
newSelectedItemsMap.set(id, currentItem);
|
|
2058
|
+
} else {
|
|
2059
|
+
// 화면 밖이면 기존 데이터 유지
|
|
2060
|
+
var prevItem = selectedItemsMap.get(id);
|
|
2061
|
+
|
|
2062
|
+
if (prevItem) {
|
|
2063
|
+
newSelectedItemsMap.set(id, prevItem);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
return newSelectedItemsMap;
|
|
2068
|
+
};
|
|
2069
|
+
/**
|
|
2070
|
+
* 외부 selectedItems를 내부 상태로 동기화
|
|
2071
|
+
*
|
|
2072
|
+
* 외부에서 전달된 selectedItems prop을 내부 ref 상태로 동기화합니다.
|
|
2073
|
+
*
|
|
2074
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
2075
|
+
* @param externalSelectedItems 외부에서 전달된 선택된 항목 배열 (undefined면 동기화 안 함)
|
|
2076
|
+
* @param selectedIdsRef 선택된 ID Set ref
|
|
2077
|
+
* @param selectedItemsMapRef 선택된 항목 Map ref
|
|
2078
|
+
*
|
|
2079
|
+
* @remarks
|
|
2080
|
+
* - externalSelectedItems가 undefined면 외부 제어가 아니므로 아무 작업도 하지 않음
|
|
2081
|
+
* - 성능: O(m), m은 externalSelectedItems의 길이
|
|
2082
|
+
*
|
|
2083
|
+
* @example
|
|
2084
|
+
* ```typescript
|
|
2085
|
+
* useEffect(() => {
|
|
2086
|
+
* syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
2087
|
+
* // 렌더링...
|
|
2088
|
+
* }, [externalSelectedItems]);
|
|
2089
|
+
* ```
|
|
2090
|
+
*/
|
|
2091
|
+
|
|
2092
|
+
var syncExternalSelectedItems = function (externalSelectedItems, selectedIdsRef, selectedItemsMapRef) {
|
|
2093
|
+
if (externalSelectedItems === undefined) return;
|
|
2094
|
+
var newSelectedIds = new Set();
|
|
2095
|
+
var newSelectedItemsMap = new Map();
|
|
2096
|
+
externalSelectedItems.forEach(function (item) {
|
|
2097
|
+
newSelectedIds.add(item.id);
|
|
2098
|
+
newSelectedItemsMap.set(item.id, item);
|
|
2099
|
+
});
|
|
2100
|
+
selectedIdsRef.current = newSelectedIds;
|
|
2101
|
+
selectedItemsMapRef.current = newSelectedItemsMap;
|
|
2102
|
+
};
|
|
2103
|
+
|
|
2104
|
+
/**
|
|
2105
|
+
* 이벤트 유효성 검증 헬퍼
|
|
2106
|
+
*
|
|
2107
|
+
* @param event 이벤트 파라미터
|
|
2108
|
+
* @param context Context가 있는지 여부
|
|
2109
|
+
* @param controller MintMapController 인스턴스
|
|
2110
|
+
* @returns 유효한 offset 또는 null
|
|
2111
|
+
*
|
|
2112
|
+
* @remarks
|
|
2113
|
+
* Context가 있으면 전역 이벤트 핸들러가 처리하므로 로컬 핸들러는 스킵
|
|
2114
|
+
*/
|
|
2115
|
+
var validateEvent = function (event, context, controller) {
|
|
2116
|
+
var _a; // Context가 있으면 전역 핸들러가 처리
|
|
2117
|
+
|
|
2118
|
+
|
|
2119
|
+
if (context) return null; // 이벤트 파라미터 검증
|
|
2120
|
+
|
|
2121
|
+
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return null;
|
|
2122
|
+
|
|
2123
|
+
try {
|
|
2124
|
+
return controller.positionToOffset(event.param.position);
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
console.error('[WoongCanvas] validateEvent error:', error);
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
/**
|
|
2131
|
+
* Map의 values를 배열로 변환 (최적화 버전)
|
|
2132
|
+
*
|
|
2133
|
+
* @param map 변환할 Map
|
|
2134
|
+
* @returns Map의 값 배열
|
|
2135
|
+
*
|
|
2136
|
+
* @remarks
|
|
2137
|
+
* Map.values()는 IterableIterator를 반환하므로 배열 변환이 필요할 때 사용합니다.
|
|
2138
|
+
* 성능: O(n) 시간복잡도
|
|
2139
|
+
*
|
|
2140
|
+
* 최적화: Array.from을 사용하되, 크기를 미리 할당하여 메모리 재할당 최소화
|
|
2141
|
+
*/
|
|
2142
|
+
|
|
2143
|
+
var mapValuesToArray = function (map) {
|
|
2144
|
+
// Map이 비어있으면 빈 배열 반환 (메모리 할당 최소화)
|
|
2145
|
+
if (map.size === 0) return []; // Array.from 사용 (TypeScript 컴파일러 호환성)
|
|
2146
|
+
|
|
2147
|
+
return Array.from(map.values());
|
|
2148
|
+
};
|
|
2149
|
+
|
|
1488
2150
|
var cn$3 = classNames__default["default"].bind(styles$1);
|
|
1489
2151
|
function MintMapCore(_a) {
|
|
1490
2152
|
var _this = this;
|
|
@@ -5550,37 +6212,16 @@
|
|
|
5550
6212
|
* 현재 뷰포트 영역 계산
|
|
5551
6213
|
*/
|
|
5552
6214
|
|
|
5553
|
-
var updateViewport = function () {
|
|
5554
|
-
|
|
5555
|
-
var stage = stageRef.current;
|
|
5556
|
-
viewportRef.current = {
|
|
5557
|
-
minX: -cullingMargin,
|
|
5558
|
-
maxX: stage.width() + cullingMargin,
|
|
5559
|
-
minY: -cullingMargin,
|
|
5560
|
-
maxY: stage.height() + cullingMargin
|
|
5561
|
-
};
|
|
6215
|
+
var updateViewport$1 = function () {
|
|
6216
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
5562
6217
|
};
|
|
5563
6218
|
/**
|
|
5564
6219
|
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
5565
6220
|
*/
|
|
5566
6221
|
|
|
5567
6222
|
|
|
5568
|
-
var isInViewport = function (item) {
|
|
5569
|
-
|
|
5570
|
-
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
5571
|
-
|
|
5572
|
-
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
5573
|
-
|
|
5574
|
-
if (!bbox) {
|
|
5575
|
-
// 바운딩 박스 계산 (공통 함수 사용)
|
|
5576
|
-
var computed = computeBoundingBox(item);
|
|
5577
|
-
if (!computed) return false;
|
|
5578
|
-
bbox = computed;
|
|
5579
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
5580
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
6223
|
+
var isInViewport$1 = function (item) {
|
|
6224
|
+
return isInViewport(item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
5584
6225
|
}; // --------------------------------------------------------------------------
|
|
5585
6226
|
// 유틸리티 함수: 좌표 변환 캐싱
|
|
5586
6227
|
// --------------------------------------------------------------------------
|
|
@@ -5610,12 +6251,23 @@
|
|
|
5610
6251
|
/**
|
|
5611
6252
|
* 마커의 바운딩 박스 계산
|
|
5612
6253
|
*
|
|
5613
|
-
*
|
|
5614
|
-
*
|
|
5615
|
-
* - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
|
|
6254
|
+
* 마커의 화면 상 위치와 크기를 기반으로 바운딩 박스를 계산합니다.
|
|
6255
|
+
* Viewport Culling에 사용되며, tailHeight를 포함하여 전체 표시 영역을 계산합니다.
|
|
5616
6256
|
*
|
|
5617
6257
|
* @param item 마커 데이터
|
|
5618
|
-
* @returns 바운딩 박스 또는 null
|
|
6258
|
+
* @returns 바운딩 박스 (minX, minY, maxX, maxY) 또는 null (좌표 변환 실패 시)
|
|
6259
|
+
*
|
|
6260
|
+
* @remarks
|
|
6261
|
+
* - **boxHeight**: 마커 본체만 포함 (Hit Test 영역)
|
|
6262
|
+
* - **tailHeight**: 마커 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역 포함)
|
|
6263
|
+
* - 바운딩 박스는 캐시되어 성능 최적화
|
|
6264
|
+
*
|
|
6265
|
+
* @example
|
|
6266
|
+
* ```typescript
|
|
6267
|
+
* const bbox = computeBoundingBox(item);
|
|
6268
|
+
* if (!bbox) return; // 계산 실패
|
|
6269
|
+
* // bbox.minX, bbox.minY, bbox.maxX, bbox.maxY 사용
|
|
6270
|
+
* ```
|
|
5619
6271
|
*/
|
|
5620
6272
|
|
|
5621
6273
|
|
|
@@ -5639,29 +6291,32 @@
|
|
|
5639
6291
|
|
|
5640
6292
|
/**
|
|
5641
6293
|
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
6294
|
+
*
|
|
6295
|
+
* 모든 마커의 바운딩 박스를 Spatial Hash Grid에 삽입합니다.
|
|
6296
|
+
* 이를 통해 클릭/호버 시 해당 위치 주변의 마커만 빠르게 조회할 수 있습니다.
|
|
6297
|
+
*
|
|
6298
|
+
* @remarks
|
|
6299
|
+
* - 호출 시점: 데이터 변경 시 또는 지도 이동/줌 완료 시
|
|
6300
|
+
* - 성능: O(n) 시간복잡도, n은 마커 개수
|
|
6301
|
+
* - Hit Test 성능: O(1) 수준 (30,000개 → ~10개 후보만 체크)
|
|
5642
6302
|
*/
|
|
5643
6303
|
|
|
5644
6304
|
|
|
5645
|
-
var buildSpatialIndex = function () {
|
|
5646
|
-
|
|
5647
|
-
spatial.clear();
|
|
5648
|
-
var currentData = dataRef.current;
|
|
5649
|
-
|
|
5650
|
-
for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
|
|
5651
|
-
var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
|
|
5652
|
-
|
|
5653
|
-
var bbox = computeBoundingBox(item);
|
|
5654
|
-
|
|
5655
|
-
if (bbox) {
|
|
5656
|
-
spatial.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
5657
|
-
}
|
|
5658
|
-
}
|
|
6305
|
+
var buildSpatialIndex$1 = function () {
|
|
6306
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
5659
6307
|
}; // --------------------------------------------------------------------------
|
|
5660
|
-
// 렌더링 함수 결정
|
|
6308
|
+
// 렌더링 함수 결정
|
|
5661
6309
|
// --------------------------------------------------------------------------
|
|
5662
6310
|
|
|
5663
6311
|
/**
|
|
5664
6312
|
* 외부 렌더링 함수에 전달할 유틸리티 객체
|
|
6313
|
+
*
|
|
6314
|
+
* 커스텀 렌더링 함수(renderBase, renderAnimation, renderEvent)에서 사용할
|
|
6315
|
+
* 좌표 변환 등의 헬퍼 함수들을 제공합니다.
|
|
6316
|
+
*
|
|
6317
|
+
* @remarks
|
|
6318
|
+
* - getOrComputeMarkerOffset: 마커 좌표 변환 (자동 캐싱)
|
|
6319
|
+
* - getOrComputePolygonOffsets: 폴리곤 좌표 변환 (마커에서는 사용 안 함)
|
|
5665
6320
|
*/
|
|
5666
6321
|
|
|
5667
6322
|
|
|
@@ -5703,7 +6358,7 @@
|
|
|
5703
6358
|
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
5704
6359
|
|
|
5705
6360
|
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
5706
|
-
return isInViewport(item);
|
|
6361
|
+
return isInViewport$1(item);
|
|
5707
6362
|
}) : dataRef.current; // topOnHover가 true이고 renderEvent가 없으면 Base Layer에서 hover 처리
|
|
5708
6363
|
|
|
5709
6364
|
if (topOnHover && !renderEvent && hovered) {
|
|
@@ -5723,7 +6378,7 @@
|
|
|
5723
6378
|
}); // hover된 항목을 최상단에 렌더링 (renderEvent가 없을 때만)
|
|
5724
6379
|
|
|
5725
6380
|
if (topOnHover && !renderEvent && hovered) {
|
|
5726
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
6381
|
+
var isHoveredInViewport = enableViewportCulling ? isInViewport$1(hovered) : true;
|
|
5727
6382
|
|
|
5728
6383
|
if (isHoveredInViewport) {
|
|
5729
6384
|
renderBase({
|
|
@@ -5749,7 +6404,11 @@
|
|
|
5749
6404
|
/**
|
|
5750
6405
|
* Animation 레이어 렌더링 (선택된 마커 애니메이션)
|
|
5751
6406
|
*
|
|
5752
|
-
*
|
|
6407
|
+
* 선택된 마커에 대한 애니메이션 효과를 렌더링합니다.
|
|
6408
|
+
* renderAnimation prop이 제공된 경우에만 실행됩니다.
|
|
6409
|
+
*
|
|
6410
|
+
* @remarks
|
|
6411
|
+
* - **성능 최적화**: sceneFunc 내부에서 최신 items 참조
|
|
5753
6412
|
* - 선택 변경 시에만 재생성
|
|
5754
6413
|
* - 지도 이동 시에는 기존 Animation 계속 실행
|
|
5755
6414
|
*/
|
|
@@ -5769,10 +6428,16 @@
|
|
|
5769
6428
|
/**
|
|
5770
6429
|
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
5771
6430
|
*
|
|
5772
|
-
*
|
|
5773
|
-
*
|
|
5774
|
-
*
|
|
5775
|
-
*
|
|
6431
|
+
* 마커의 hover 효과 및 선택 상태를 표시합니다.
|
|
6432
|
+
* renderEvent prop이 제공된 경우에만 실행됩니다.
|
|
6433
|
+
*
|
|
6434
|
+
* @remarks
|
|
6435
|
+
* - **성능 최적화**:
|
|
6436
|
+
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
6437
|
+
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
6438
|
+
* 3. 클로저로 최신 데이터 참조
|
|
6439
|
+
* - **topOnHover 지원**: hover된 항목을 최상단에 렌더링
|
|
6440
|
+
* - 선택된 항목은 Map에서 O(1)로 조회하여 성능 최적화
|
|
5776
6441
|
*/
|
|
5777
6442
|
|
|
5778
6443
|
|
|
@@ -5790,8 +6455,9 @@
|
|
|
5790
6455
|
name: 'event-render-shape',
|
|
5791
6456
|
sceneFunc: function (context, shape) {
|
|
5792
6457
|
var ctx = context; // 클로저로 최신 ref 값 참조
|
|
6458
|
+
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
5793
6459
|
|
|
5794
|
-
var selectedItems =
|
|
6460
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
5795
6461
|
var hovered = hoveredItemRef.current; // topOnHover가 true이면 hover된 항목을 최상단에 렌더링
|
|
5796
6462
|
|
|
5797
6463
|
if (topOnHover && hovered) {
|
|
@@ -5806,7 +6472,7 @@
|
|
|
5806
6472
|
selectedItem: selectedItemRef.current
|
|
5807
6473
|
}); // 2. hover된 항목을 최상단에 렌더링
|
|
5808
6474
|
|
|
5809
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
6475
|
+
var isHoveredInViewport = enableViewportCulling ? isInViewport$1(hovered) : true;
|
|
5810
6476
|
|
|
5811
6477
|
if (isHoveredInViewport) {
|
|
5812
6478
|
// hover된 항목이 선택되어 있다면 hoverSelectedItems에 포함시켜서
|
|
@@ -5850,8 +6516,8 @@
|
|
|
5850
6516
|
|
|
5851
6517
|
|
|
5852
6518
|
var renderAllImmediate = function () {
|
|
5853
|
-
updateViewport();
|
|
5854
|
-
buildSpatialIndex();
|
|
6519
|
+
updateViewport$1();
|
|
6520
|
+
buildSpatialIndex$1();
|
|
5855
6521
|
doRenderBase();
|
|
5856
6522
|
doRenderAnimation();
|
|
5857
6523
|
doRenderEvent();
|
|
@@ -5859,89 +6525,43 @@
|
|
|
5859
6525
|
// 이벤트 핸들러: 지도 이벤트
|
|
5860
6526
|
// --------------------------------------------------------------------------
|
|
5861
6527
|
|
|
5862
|
-
/**
|
|
5863
|
-
* 지도 이동/줌 완료 시 처리
|
|
5864
|
-
*/
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
var handleIdle = function () {
|
|
5868
|
-
prevCenterOffsetRef.current = null;
|
|
5869
|
-
accumTranslateRef.current = {
|
|
5870
|
-
x: 0,
|
|
5871
|
-
y: 0
|
|
5872
|
-
}; // 2. 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
5873
|
-
|
|
5874
|
-
offsetCacheRef.current.clear();
|
|
5875
|
-
boundingBoxCacheRef.current.clear(); // 3. 마커 위치 업데이트
|
|
5876
|
-
|
|
5877
|
-
var bounds = controller.getCurrBounds();
|
|
5878
|
-
|
|
5879
|
-
var markerOptions = tslib.__assign({
|
|
5880
|
-
position: bounds.nw
|
|
5881
|
-
}, options);
|
|
5882
|
-
|
|
5883
|
-
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 4. transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
5884
6528
|
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
};
|
|
6529
|
+
var _g = createMapEventHandlers({
|
|
6530
|
+
controller: controller,
|
|
6531
|
+
containerRef: containerRef,
|
|
6532
|
+
markerRef: markerRef,
|
|
6533
|
+
options: options,
|
|
6534
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
6535
|
+
accumTranslateRef: accumTranslateRef,
|
|
6536
|
+
offsetCacheRef: offsetCacheRef,
|
|
6537
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
6538
|
+
renderAllImmediate: renderAllImmediate
|
|
6539
|
+
}),
|
|
6540
|
+
handleIdle = _g.handleIdle,
|
|
6541
|
+
handleZoomStart = _g.handleZoomStart,
|
|
6542
|
+
handleZoomEnd = _g.handleZoomEnd,
|
|
6543
|
+
handleCenterChanged = _g.handleCenterChanged,
|
|
6544
|
+
handleDragStartShared = _g.handleDragStart,
|
|
6545
|
+
handleDragEndShared = _g.handleDragEnd;
|
|
5903
6546
|
/**
|
|
5904
|
-
*
|
|
5905
|
-
*/
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
var handleZoomEnd = function () {
|
|
5909
|
-
if (containerRef.current) {
|
|
5910
|
-
containerRef.current.style.visibility = '';
|
|
5911
|
-
}
|
|
5912
|
-
};
|
|
5913
|
-
/**
|
|
5914
|
-
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
5915
|
-
*/
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
var handleCenterChanged = function () {
|
|
5919
|
-
var center = controller.getCurrBounds().getCenter();
|
|
5920
|
-
var curr = controller.positionToOffset(center);
|
|
5921
|
-
var prev = prevCenterOffsetRef.current;
|
|
5922
|
-
|
|
5923
|
-
if (!prev) {
|
|
5924
|
-
prevCenterOffsetRef.current = {
|
|
5925
|
-
x: curr.x,
|
|
5926
|
-
y: curr.y
|
|
5927
|
-
};
|
|
5928
|
-
return;
|
|
5929
|
-
}
|
|
6547
|
+
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
6548
|
+
*/
|
|
5930
6549
|
|
|
5931
|
-
var dx = prev.x - curr.x;
|
|
5932
|
-
var dy = prev.y - curr.y;
|
|
5933
|
-
accumTranslateRef.current = {
|
|
5934
|
-
x: accumTranslateRef.current.x + dx,
|
|
5935
|
-
y: accumTranslateRef.current.y + dy
|
|
5936
|
-
};
|
|
5937
|
-
prevCenterOffsetRef.current = {
|
|
5938
|
-
x: curr.x,
|
|
5939
|
-
y: curr.y
|
|
5940
|
-
};
|
|
5941
6550
|
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
6551
|
+
var handleDragStart = function () {
|
|
6552
|
+
handleDragStartShared();
|
|
6553
|
+
draggingRef.current = true;
|
|
6554
|
+
controller.setMapCursor('grabbing');
|
|
6555
|
+
};
|
|
6556
|
+
/**
|
|
6557
|
+
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
6558
|
+
*/
|
|
6559
|
+
|
|
6560
|
+
|
|
6561
|
+
var handleDragEnd = function () {
|
|
6562
|
+
handleDragEndShared();
|
|
6563
|
+
draggingRef.current = false;
|
|
6564
|
+
controller.setMapCursor('grab');
|
|
5945
6565
|
}; // --------------------------------------------------------------------------
|
|
5946
6566
|
// Hit Test & 상태 관리
|
|
5947
6567
|
// --------------------------------------------------------------------------
|
|
@@ -5949,12 +6569,26 @@
|
|
|
5949
6569
|
/**
|
|
5950
6570
|
* 특정 좌표의 마커 데이터 찾기 (Spatial Index 사용)
|
|
5951
6571
|
*
|
|
5952
|
-
*
|
|
5953
|
-
*
|
|
5954
|
-
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
6572
|
+
* 클릭/호버 이벤트 시 해당 위치에 있는 마커를 찾습니다.
|
|
6573
|
+
* Spatial Hash Grid를 사용하여 O(1) 수준의 빠른 Hit Test를 수행합니다.
|
|
5955
6574
|
*
|
|
5956
|
-
* @param offset 검사할 좌표
|
|
5957
|
-
* @returns 찾은 마커 데이터 또는 null
|
|
6575
|
+
* @param offset 검사할 화면 좌표 (픽셀 단위)
|
|
6576
|
+
* @returns 찾은 마커 데이터 또는 null (없으면)
|
|
6577
|
+
*
|
|
6578
|
+
* @remarks
|
|
6579
|
+
* - **topOnHover 지원**: topOnHover가 true일 때 현재 hover된 항목을 최우선으로 체크
|
|
6580
|
+
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
6581
|
+
* - **성능**: O(후보 항목 수) - 보통 ~10개 (30,000개 전체를 체크하지 않음)
|
|
6582
|
+
* - Spatial Index를 통해 해당 위치 주변의 후보만 추출 후 정확한 Hit Test 수행
|
|
6583
|
+
*
|
|
6584
|
+
* @example
|
|
6585
|
+
* ```typescript
|
|
6586
|
+
* const clickedOffset = controller.positionToOffset(event.param.position);
|
|
6587
|
+
* const data = findData(clickedOffset);
|
|
6588
|
+
* if (data) {
|
|
6589
|
+
* // 마커를 찾음
|
|
6590
|
+
* }
|
|
6591
|
+
* ```
|
|
5958
6592
|
*/
|
|
5959
6593
|
|
|
5960
6594
|
|
|
@@ -6015,11 +6649,17 @@
|
|
|
6015
6649
|
/**
|
|
6016
6650
|
* 클릭 처리 (단일/다중 선택)
|
|
6017
6651
|
*
|
|
6018
|
-
*
|
|
6652
|
+
* 마커 클릭 시 선택 상태를 업데이트하고 렌더링을 수행합니다.
|
|
6019
6653
|
*
|
|
6020
|
-
*
|
|
6021
|
-
*
|
|
6022
|
-
*
|
|
6654
|
+
* @param data 클릭된 마커 데이터
|
|
6655
|
+
*
|
|
6656
|
+
* @remarks
|
|
6657
|
+
* - **단일 선택**: 기존 선택 해제 후 새로 선택 (토글 가능)
|
|
6658
|
+
* - **다중 선택**: enableMultiSelect가 true면 기존 선택 유지하며 추가/제거
|
|
6659
|
+
* - **성능 최적화**:
|
|
6660
|
+
* - 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
6661
|
+
* - sceneFunc에서 selectedIds를 체크하여 선택된 마커만 스킵
|
|
6662
|
+
* - 객체 생성 오버헤드 제거로 1,000개 이상도 부드럽게 처리
|
|
6023
6663
|
*/
|
|
6024
6664
|
|
|
6025
6665
|
|
|
@@ -6068,75 +6708,57 @@
|
|
|
6068
6708
|
|
|
6069
6709
|
/**
|
|
6070
6710
|
* 클릭 이벤트 처리
|
|
6711
|
+
*
|
|
6712
|
+
* @param event 클릭 이벤트 파라미터
|
|
6713
|
+
*
|
|
6714
|
+
* @remarks
|
|
6715
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
6716
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
6717
|
+
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
6071
6718
|
*/
|
|
6072
6719
|
|
|
6073
6720
|
|
|
6074
6721
|
var handleClick = function (event) {
|
|
6075
|
-
var _a;
|
|
6076
|
-
|
|
6077
6722
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
6078
6723
|
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
var clickedOffset = controller.positionToOffset(event.param.position);
|
|
6083
|
-
var data_1 = findData(clickedOffset);
|
|
6724
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
6725
|
+
if (!clickedOffset) return;
|
|
6726
|
+
var data = findData(clickedOffset);
|
|
6084
6727
|
|
|
6085
|
-
|
|
6086
|
-
|
|
6728
|
+
if (data) {
|
|
6729
|
+
handleLocalClick(data);
|
|
6087
6730
|
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
}
|
|
6731
|
+
if (onClick) {
|
|
6732
|
+
onClick(data, selectedIdsRef.current);
|
|
6091
6733
|
}
|
|
6092
|
-
} catch (error) {
|
|
6093
|
-
console.error('[WoongCanvasMarker] handleClick error:', error);
|
|
6094
6734
|
}
|
|
6095
6735
|
};
|
|
6096
6736
|
/**
|
|
6097
6737
|
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
6738
|
+
*
|
|
6739
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
6740
|
+
*
|
|
6741
|
+
* @remarks
|
|
6742
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
6743
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
6744
|
+
* - hover 상태 변경 시에만 렌더링 및 콜백 호출 (최적화)
|
|
6098
6745
|
*/
|
|
6099
6746
|
|
|
6100
6747
|
|
|
6101
6748
|
var handleMouseMove = function (event) {
|
|
6102
|
-
var _a;
|
|
6103
|
-
|
|
6104
6749
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
6105
6750
|
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
var hoveredItem = findData(mouseOffset);
|
|
6111
|
-
var prevHovered = hoveredItemRef.current;
|
|
6751
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
6752
|
+
if (!mouseOffset) return;
|
|
6753
|
+
var hoveredItem = findData(mouseOffset);
|
|
6754
|
+
var prevHovered = hoveredItemRef.current;
|
|
6112
6755
|
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
}
|
|
6118
|
-
} catch (error) {
|
|
6119
|
-
console.error('[WoongCanvasMarker] handleMouseMove error:', error);
|
|
6756
|
+
if (prevHovered !== hoveredItem) {
|
|
6757
|
+
setHovered(hoveredItem);
|
|
6758
|
+
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
6759
|
+
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
6120
6760
|
}
|
|
6121
6761
|
};
|
|
6122
|
-
/**
|
|
6123
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
6124
|
-
*/
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
var handleDragStart = function () {
|
|
6128
|
-
draggingRef.current = true;
|
|
6129
|
-
controller.setMapCursor('grabbing');
|
|
6130
|
-
};
|
|
6131
|
-
/**
|
|
6132
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
6133
|
-
*/
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
var handleDragEnd = function () {
|
|
6137
|
-
draggingRef.current = false;
|
|
6138
|
-
controller.setMapCursor('grab');
|
|
6139
|
-
};
|
|
6140
6762
|
/**
|
|
6141
6763
|
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
6142
6764
|
*/
|
|
@@ -6231,7 +6853,7 @@
|
|
|
6231
6853
|
|
|
6232
6854
|
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
6233
6855
|
|
|
6234
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
6856
|
+
updateViewport$1(); // ResizeObserver (맵 크기 변경 감지)
|
|
6235
6857
|
|
|
6236
6858
|
var resizeRafId = null;
|
|
6237
6859
|
var resizeObserver = new ResizeObserver(function () {
|
|
@@ -6245,7 +6867,7 @@
|
|
|
6245
6867
|
stage.height(mapDiv.offsetHeight);
|
|
6246
6868
|
offsetCacheRef.current.clear();
|
|
6247
6869
|
boundingBoxCacheRef.current.clear();
|
|
6248
|
-
updateViewport();
|
|
6870
|
+
updateViewport$1();
|
|
6249
6871
|
renderAllImmediate();
|
|
6250
6872
|
resizeRafId = null;
|
|
6251
6873
|
});
|
|
@@ -6334,18 +6956,8 @@
|
|
|
6334
6956
|
// --------------------------------------------------------------------------
|
|
6335
6957
|
|
|
6336
6958
|
React.useEffect(function () {
|
|
6337
|
-
if (!stageRef.current) return;
|
|
6338
|
-
|
|
6339
|
-
if (externalSelectedItems === undefined) return; // 외부에서 전달된 selectedItems로 동기화
|
|
6340
|
-
|
|
6341
|
-
var newSelectedIds = new Set();
|
|
6342
|
-
var newSelectedItemsMap = new Map();
|
|
6343
|
-
externalSelectedItems.forEach(function (item) {
|
|
6344
|
-
newSelectedIds.add(item.id);
|
|
6345
|
-
newSelectedItemsMap.set(item.id, item);
|
|
6346
|
-
});
|
|
6347
|
-
selectedIdsRef.current = newSelectedIds;
|
|
6348
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 렌더링
|
|
6959
|
+
if (!stageRef.current) return;
|
|
6960
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef); // 렌더링
|
|
6349
6961
|
|
|
6350
6962
|
doRenderBase();
|
|
6351
6963
|
doRenderAnimation();
|
|
@@ -6396,27 +7008,7 @@
|
|
|
6396
7008
|
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
6397
7009
|
*/
|
|
6398
7010
|
|
|
6399
|
-
|
|
6400
|
-
return [m.id, m];
|
|
6401
|
-
}));
|
|
6402
|
-
var newSelectedItemsMap = new Map();
|
|
6403
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
6404
|
-
// 현재 data에 있으면 최신 데이터 사용
|
|
6405
|
-
var currentItem = dataMap.get(id);
|
|
6406
|
-
|
|
6407
|
-
if (currentItem) {
|
|
6408
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
6409
|
-
} else {
|
|
6410
|
-
// 화면 밖이면 기존 데이터 유지
|
|
6411
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
6412
|
-
|
|
6413
|
-
if (prevItem) {
|
|
6414
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
6415
|
-
}
|
|
6416
|
-
}
|
|
6417
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
|
|
6418
|
-
|
|
6419
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
7011
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current); // 즉시 렌더링
|
|
6420
7012
|
|
|
6421
7013
|
renderAllImmediate();
|
|
6422
7014
|
}, [data]);
|
|
@@ -6434,10 +7026,35 @@
|
|
|
6434
7026
|
* 폴리곤 렌더링 유틸리티
|
|
6435
7027
|
*
|
|
6436
7028
|
* 이 파일은 폴리곤 렌더링을 위한 헬퍼 함수와 팩토리 함수를 제공합니다.
|
|
7029
|
+
* GeoJSON MultiPolygon 형식을 지원하며, 도넛 폴리곤(구멍이 있는 폴리곤)도 처리할 수 있습니다.
|
|
6437
7030
|
*/
|
|
6438
7031
|
|
|
6439
7032
|
/**
|
|
6440
7033
|
* 폴리곤 그리기 헬퍼 함수 (도넛 폴리곤 지원)
|
|
7034
|
+
*
|
|
7035
|
+
* Canvas 2D Context를 사용하여 폴리곤을 그립니다.
|
|
7036
|
+
* 도넛 폴리곤의 경우 evenodd fill rule을 사용하여 구멍을 처리합니다.
|
|
7037
|
+
*
|
|
7038
|
+
* @param params 폴리곤 그리기 파라미터
|
|
7039
|
+
*
|
|
7040
|
+
* @remarks
|
|
7041
|
+
* - **도넛 폴리곤 처리**:
|
|
7042
|
+
* - 외부 폴리곤과 내부 구멍들을 같은 path에 추가
|
|
7043
|
+
* - `fill('evenodd')`를 사용하여 구멍 뚫기
|
|
7044
|
+
* - **일반 폴리곤 처리**: 각 폴리곤 그룹을 개별적으로 그리기
|
|
7045
|
+
* - **성능**: O(n), n은 폴리곤의 총 좌표 수
|
|
7046
|
+
*
|
|
7047
|
+
* @example
|
|
7048
|
+
* ```typescript
|
|
7049
|
+
* drawPolygon({
|
|
7050
|
+
* ctx,
|
|
7051
|
+
* polygonOffsets: [[[[100, 200], [200, 200], [200, 100], [100, 100]]]],
|
|
7052
|
+
* isDonutPolygon: false,
|
|
7053
|
+
* fillColor: 'rgba(255, 0, 0, 0.5)',
|
|
7054
|
+
* strokeColor: 'rgba(255, 0, 0, 1)',
|
|
7055
|
+
* lineWidth: 2
|
|
7056
|
+
* });
|
|
7057
|
+
* ```
|
|
6441
7058
|
*/
|
|
6442
7059
|
var drawPolygon = function (_a) {
|
|
6443
7060
|
var ctx = _a.ctx,
|
|
@@ -6451,13 +7068,16 @@
|
|
|
6451
7068
|
var multiPolygon = polygonOffsets_1[_i];
|
|
6452
7069
|
|
|
6453
7070
|
if (isDonutPolygon) {
|
|
6454
|
-
|
|
7071
|
+
// 도넛 폴리곤 처리: 외부 폴리곤 + 내부 구멍들을 같은 path에 추가
|
|
7072
|
+
ctx.beginPath(); // 1. 외부 폴리곤 그리기 (첫 번째 폴리곤)
|
|
6455
7073
|
|
|
6456
|
-
|
|
6457
|
-
|
|
7074
|
+
var outerPolygon = multiPolygon[0];
|
|
7075
|
+
|
|
7076
|
+
if (outerPolygon && outerPolygon.length > 0) {
|
|
7077
|
+
ctx.moveTo(outerPolygon[0][0], outerPolygon[0][1]);
|
|
6458
7078
|
|
|
6459
|
-
for (var i = 1; i <
|
|
6460
|
-
ctx.lineTo(
|
|
7079
|
+
for (var i = 1; i < outerPolygon.length; i++) {
|
|
7080
|
+
ctx.lineTo(outerPolygon[i][0], outerPolygon[i][1]);
|
|
6461
7081
|
}
|
|
6462
7082
|
|
|
6463
7083
|
ctx.closePath();
|
|
@@ -6484,7 +7104,7 @@
|
|
|
6484
7104
|
ctx.lineWidth = lineWidth;
|
|
6485
7105
|
ctx.stroke();
|
|
6486
7106
|
} else {
|
|
6487
|
-
// 일반 폴리곤
|
|
7107
|
+
// 일반 폴리곤 처리: 각 폴리곤 그룹을 개별적으로 그리기
|
|
6488
7108
|
for (var _b = 0, multiPolygon_1 = multiPolygon; _b < multiPolygon_1.length; _b++) {
|
|
6489
7109
|
var polygonGroup = multiPolygon_1[_b];
|
|
6490
7110
|
if (!polygonGroup.length) continue;
|
|
@@ -6497,7 +7117,8 @@
|
|
|
6497
7117
|
ctx.lineTo(point[0], point[1]);
|
|
6498
7118
|
}
|
|
6499
7119
|
|
|
6500
|
-
ctx.closePath();
|
|
7120
|
+
ctx.closePath(); // 스타일 설정 및 렌더링
|
|
7121
|
+
|
|
6501
7122
|
ctx.fillStyle = fillColor;
|
|
6502
7123
|
ctx.strokeStyle = strokeColor;
|
|
6503
7124
|
ctx.lineWidth = lineWidth;
|
|
@@ -6508,19 +7129,30 @@
|
|
|
6508
7129
|
}
|
|
6509
7130
|
};
|
|
6510
7131
|
/**
|
|
6511
|
-
* 폴리곤 Base 렌더링 함수
|
|
7132
|
+
* 폴리곤 Base 렌더링 함수 팩토리
|
|
6512
7133
|
*
|
|
7134
|
+
* Base Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
7135
|
+
* 선택되지 않은 폴리곤만 렌더링하며, 선택된 항목은 Event Layer에서 처리됩니다.
|
|
7136
|
+
*
|
|
7137
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6513
7138
|
* @param baseFillColor 기본 폴리곤 채우기 색상
|
|
6514
7139
|
* @param baseStrokeColor 기본 폴리곤 테두리 색상
|
|
6515
7140
|
* @param baseLineWidth 기본 폴리곤 테두리 두께
|
|
6516
7141
|
* @returns Base Layer 렌더링 함수
|
|
6517
7142
|
*
|
|
7143
|
+
* @remarks
|
|
7144
|
+
* - 선택된 항목은 Event Layer에서 그려지므로 Base Layer에서는 스킵
|
|
7145
|
+
* - 성능: O(n), n은 렌더링할 폴리곤 개수
|
|
7146
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
7147
|
+
*
|
|
6518
7148
|
* @example
|
|
7149
|
+
* ```typescript
|
|
6519
7150
|
* const renderBase = renderPolygonBase(
|
|
6520
7151
|
* 'rgba(255, 100, 100, 0.5)',
|
|
6521
7152
|
* 'rgba(200, 50, 50, 0.8)',
|
|
6522
7153
|
* 2
|
|
6523
7154
|
* );
|
|
7155
|
+
* ```
|
|
6524
7156
|
*/
|
|
6525
7157
|
|
|
6526
7158
|
var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth) {
|
|
@@ -6531,12 +7163,15 @@
|
|
|
6531
7163
|
utils = _a.utils;
|
|
6532
7164
|
|
|
6533
7165
|
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
6534
|
-
var item = items_1[_i]; // 선택된 항목은 Event Layer에서 그림
|
|
7166
|
+
var item = items_1[_i]; // 선택된 항목은 Event Layer에서 그림 (중복 렌더링 방지)
|
|
7167
|
+
|
|
7168
|
+
if (selectedIds.has(item.id)) continue; // paths가 없으면 스킵
|
|
7169
|
+
|
|
7170
|
+
if (!item.paths) continue; // 좌표 변환 (자동 캐싱)
|
|
6535
7171
|
|
|
6536
|
-
if (selectedIds.has(item.id)) continue;
|
|
6537
|
-
if (!item.paths) continue;
|
|
6538
7172
|
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6539
|
-
if (!polygonOffsets) continue;
|
|
7173
|
+
if (!polygonOffsets) continue; // 폴리곤 그리기
|
|
7174
|
+
|
|
6540
7175
|
drawPolygon({
|
|
6541
7176
|
ctx: ctx,
|
|
6542
7177
|
polygonOffsets: polygonOffsets,
|
|
@@ -6549,8 +7184,12 @@
|
|
|
6549
7184
|
};
|
|
6550
7185
|
};
|
|
6551
7186
|
/**
|
|
6552
|
-
* 폴리곤 Event 렌더링 함수
|
|
7187
|
+
* 폴리곤 Event 렌더링 함수 팩토리
|
|
7188
|
+
*
|
|
7189
|
+
* Event Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
7190
|
+
* 선택된 항목, hover된 항목, 마지막 선택된 항목을 각각 다른 스타일로 렌더링합니다.
|
|
6553
7191
|
*
|
|
7192
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6554
7193
|
* @param baseFillColor 기본 폴리곤 채우기 색상 (필수, fallback용)
|
|
6555
7194
|
* @param baseStrokeColor 기본 폴리곤 테두리 색상 (필수, fallback용)
|
|
6556
7195
|
* @param baseLineWidth 기본 폴리곤 테두리 두께 (필수, fallback용)
|
|
@@ -6565,7 +7204,14 @@
|
|
|
6565
7204
|
* @param hoveredLineWidth Hover 시 폴리곤 테두리 두께 (선택, 기본값: selectedLineWidth)
|
|
6566
7205
|
* @returns Event Layer 렌더링 함수
|
|
6567
7206
|
*
|
|
7207
|
+
* @remarks
|
|
7208
|
+
* - **렌더링 순서**: 선택된 항목 → 마지막 선택된 항목 → hover된 항목 (최상단)
|
|
7209
|
+
* - **성능**: O(m), m은 선택된 항목 수 + hover된 항목 수
|
|
7210
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
7211
|
+
* - hover된 항목이 선택되어 있으면 active 스타일 적용
|
|
7212
|
+
*
|
|
6568
7213
|
* @example
|
|
7214
|
+
* ```typescript
|
|
6569
7215
|
* const renderEvent = renderPolygonEvent(
|
|
6570
7216
|
* 'rgba(255, 100, 100, 0.5)', // baseFillColor
|
|
6571
7217
|
* 'rgba(200, 50, 50, 0.8)', // baseStrokeColor
|
|
@@ -6574,6 +7220,7 @@
|
|
|
6574
7220
|
* 'rgba(255, 152, 0, 1)', // selectedStrokeColor
|
|
6575
7221
|
* 4 // selectedLineWidth
|
|
6576
7222
|
* );
|
|
7223
|
+
* ```
|
|
6577
7224
|
*/
|
|
6578
7225
|
|
|
6579
7226
|
var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth) {
|
|
@@ -6601,13 +7248,19 @@
|
|
|
6601
7248
|
hoveredItem = _a.hoveredItem,
|
|
6602
7249
|
utils = _a.utils,
|
|
6603
7250
|
selectedItems = _a.selectedItems,
|
|
6604
|
-
selectedItem = _a.selectedItem; //
|
|
7251
|
+
selectedItem = _a.selectedItem; // 성능 최적화: selectedItems를 Set으로 변환하여 O(1) 조회 (매번 some() 체크 방지)
|
|
7252
|
+
|
|
7253
|
+
var selectedIdsSet = selectedItems ? new Set(selectedItems.map(function (item) {
|
|
7254
|
+
return item.id;
|
|
7255
|
+
})) : new Set();
|
|
7256
|
+
var hoveredItemId = hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.id;
|
|
7257
|
+
var selectedItemId = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id; // 1. 선택된 항목들 그리기 (마지막 선택 항목과 호버된 항목 제외)
|
|
6605
7258
|
|
|
6606
7259
|
if (selectedItems === null || selectedItems === void 0 ? void 0 : selectedItems.length) {
|
|
6607
7260
|
for (var _i = 0, selectedItems_1 = selectedItems; _i < selectedItems_1.length; _i++) {
|
|
6608
7261
|
var item = selectedItems_1[_i]; // 마지막 선택 항목과 호버된 항목은 나중에 따로 그림
|
|
6609
7262
|
|
|
6610
|
-
if (item.id ===
|
|
7263
|
+
if (item.id === selectedItemId || item.id === hoveredItemId) continue;
|
|
6611
7264
|
if (!item.paths) continue;
|
|
6612
7265
|
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6613
7266
|
if (!polygonOffsets) continue;
|
|
@@ -6623,7 +7276,7 @@
|
|
|
6623
7276
|
} // 2. 마지막 선택된 항목 그리기 (호버되지 않은 경우)
|
|
6624
7277
|
|
|
6625
7278
|
|
|
6626
|
-
if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.paths) &&
|
|
7279
|
+
if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.paths) && hoveredItemId !== selectedItemId) {
|
|
6627
7280
|
var polygonOffsets = utils.getOrComputePolygonOffsets(selectedItem);
|
|
6628
7281
|
|
|
6629
7282
|
if (polygonOffsets) {
|
|
@@ -6641,10 +7294,10 @@
|
|
|
6641
7294
|
|
|
6642
7295
|
if (hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.paths) {
|
|
6643
7296
|
var polygonOffsets = utils.getOrComputePolygonOffsets(hoveredItem);
|
|
6644
|
-
if (!polygonOffsets) return;
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
7297
|
+
if (!polygonOffsets) return; // 좌표 변환 실패 시 스킵 (return은 렌더링 함수 종료)
|
|
7298
|
+
// 성능 최적화: Set을 사용하여 O(1) 조회 (이전: O(m) some() 체크)
|
|
7299
|
+
|
|
7300
|
+
var isSelected = selectedIdsSet.has(hoveredItem.id);
|
|
6648
7301
|
drawPolygon({
|
|
6649
7302
|
ctx: ctx,
|
|
6650
7303
|
polygonOffsets: polygonOffsets,
|
|
@@ -6663,8 +7316,6 @@
|
|
|
6663
7316
|
var WoongCanvasPolygon = function (props) {
|
|
6664
7317
|
var data = props.data,
|
|
6665
7318
|
onClick = props.onClick,
|
|
6666
|
-
onMouseOver = props.onMouseOver,
|
|
6667
|
-
onMouseOut = props.onMouseOut,
|
|
6668
7319
|
_a = props.enableMultiSelect,
|
|
6669
7320
|
enableMultiSelect = _a === void 0 ? false : _a,
|
|
6670
7321
|
_b = props.enableViewportCulling,
|
|
@@ -6689,7 +7340,7 @@
|
|
|
6689
7340
|
hoveredFillColor = props.hoveredFillColor,
|
|
6690
7341
|
hoveredStrokeColor = props.hoveredStrokeColor,
|
|
6691
7342
|
hoveredLineWidth = props.hoveredLineWidth,
|
|
6692
|
-
options = tslib.__rest(props, ["data", "onClick", "
|
|
7343
|
+
options = tslib.__rest(props, ["data", "onClick", "enableMultiSelect", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "baseFillColor", "baseStrokeColor", "baseLineWidth", "selectedFillColor", "selectedStrokeColor", "selectedLineWidth", "activeFillColor", "activeStrokeColor", "activeLineWidth", "hoveredFillColor", "hoveredStrokeColor", "hoveredLineWidth"]); // --------------------------------------------------------------------------
|
|
6693
7344
|
// Hooks & Context
|
|
6694
7345
|
// --------------------------------------------------------------------------
|
|
6695
7346
|
|
|
@@ -6788,37 +7439,16 @@
|
|
|
6788
7439
|
* 현재 뷰포트 영역 계산
|
|
6789
7440
|
*/
|
|
6790
7441
|
|
|
6791
|
-
var updateViewport = function () {
|
|
6792
|
-
|
|
6793
|
-
var stage = stageRef.current;
|
|
6794
|
-
viewportRef.current = {
|
|
6795
|
-
minX: -cullingMargin,
|
|
6796
|
-
maxX: stage.width() + cullingMargin,
|
|
6797
|
-
minY: -cullingMargin,
|
|
6798
|
-
maxY: stage.height() + cullingMargin
|
|
6799
|
-
};
|
|
7442
|
+
var updateViewport$1 = function () {
|
|
7443
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
6800
7444
|
};
|
|
6801
7445
|
/**
|
|
6802
7446
|
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
6803
7447
|
*/
|
|
6804
7448
|
|
|
6805
7449
|
|
|
6806
|
-
var isInViewport = function (item) {
|
|
6807
|
-
|
|
6808
|
-
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
6809
|
-
|
|
6810
|
-
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
6811
|
-
|
|
6812
|
-
if (!bbox) {
|
|
6813
|
-
// 바운딩 박스 계산 (공통 함수 사용)
|
|
6814
|
-
var computed = computeBoundingBox(item);
|
|
6815
|
-
if (!computed) return false;
|
|
6816
|
-
bbox = computed;
|
|
6817
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
6818
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
7450
|
+
var isInViewport$1 = function (item) {
|
|
7451
|
+
return isInViewport(item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
6822
7452
|
}; // --------------------------------------------------------------------------
|
|
6823
7453
|
// 유틸리티 함수: 좌표 변환 캐싱
|
|
6824
7454
|
// --------------------------------------------------------------------------
|
|
@@ -6847,8 +7477,23 @@
|
|
|
6847
7477
|
/**
|
|
6848
7478
|
* 폴리곤의 바운딩 박스 계산
|
|
6849
7479
|
*
|
|
7480
|
+
* 폴리곤의 모든 좌표를 순회하여 최소/최대 X, Y 값을 계산합니다.
|
|
7481
|
+
* Viewport Culling에 사용되며, MultiPolygon 형식을 지원합니다.
|
|
7482
|
+
*
|
|
6850
7483
|
* @param item 폴리곤 데이터
|
|
6851
|
-
* @returns 바운딩 박스 또는 null
|
|
7484
|
+
* @returns 바운딩 박스 (minX, minY, maxX, maxY) 또는 null (좌표 변환 실패 시)
|
|
7485
|
+
*
|
|
7486
|
+
* @remarks
|
|
7487
|
+
* - 성능: O(n), n은 폴리곤의 총 좌표 수
|
|
7488
|
+
* - 바운딩 박스는 캐시되어 성능 최적화
|
|
7489
|
+
* - MultiPolygon의 모든 좌표를 고려하여 계산
|
|
7490
|
+
*
|
|
7491
|
+
* @example
|
|
7492
|
+
* ```typescript
|
|
7493
|
+
* const bbox = computeBoundingBox(item);
|
|
7494
|
+
* if (!bbox) return; // 계산 실패
|
|
7495
|
+
* // bbox.minX, bbox.minY, bbox.maxX, bbox.maxY 사용
|
|
7496
|
+
* ```
|
|
6852
7497
|
*/
|
|
6853
7498
|
|
|
6854
7499
|
|
|
@@ -6894,20 +7539,8 @@
|
|
|
6894
7539
|
*/
|
|
6895
7540
|
|
|
6896
7541
|
|
|
6897
|
-
var buildSpatialIndex = function () {
|
|
6898
|
-
|
|
6899
|
-
spatial.clear();
|
|
6900
|
-
var currentData = dataRef.current;
|
|
6901
|
-
|
|
6902
|
-
for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
|
|
6903
|
-
var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
|
|
6904
|
-
|
|
6905
|
-
var bbox = computeBoundingBox(item);
|
|
6906
|
-
|
|
6907
|
-
if (bbox) {
|
|
6908
|
-
spatial.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
7542
|
+
var buildSpatialIndex$1 = function () {
|
|
7543
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
6911
7544
|
}; // --------------------------------------------------------------------------
|
|
6912
7545
|
// 렌더링 함수 결정 (dataType에 따라)
|
|
6913
7546
|
// --------------------------------------------------------------------------
|
|
@@ -6930,11 +7563,8 @@
|
|
|
6930
7563
|
|
|
6931
7564
|
var renderBase = renderPolygonBase(baseFillColor, baseStrokeColor, baseLineWidth);
|
|
6932
7565
|
var renderEvent = renderPolygonEvent(baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth);
|
|
6933
|
-
/** Base Layer에서 사용할 빈 Set (재사용) */
|
|
6934
|
-
|
|
6935
|
-
React.useRef(new Set());
|
|
6936
7566
|
/**
|
|
6937
|
-
* Base 레이어 렌더링 (뷰포트 컬링
|
|
7567
|
+
* Base 레이어 렌더링 (뷰포트 컬링 적용)
|
|
6938
7568
|
*
|
|
6939
7569
|
* 🔥 최적화:
|
|
6940
7570
|
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
@@ -6962,7 +7592,7 @@
|
|
|
6962
7592
|
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
6963
7593
|
|
|
6964
7594
|
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
6965
|
-
return isInViewport(item);
|
|
7595
|
+
return isInViewport$1(item);
|
|
6966
7596
|
}) : dataRef.current; // 일반 항목 렌더링
|
|
6967
7597
|
|
|
6968
7598
|
renderBase({
|
|
@@ -6986,10 +7616,16 @@
|
|
|
6986
7616
|
/**
|
|
6987
7617
|
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
6988
7618
|
*
|
|
6989
|
-
*
|
|
6990
|
-
*
|
|
6991
|
-
*
|
|
6992
|
-
*
|
|
7619
|
+
* 폴리곤의 hover 효과 및 선택 상태를 표시합니다.
|
|
7620
|
+
* 자동 렌더링 방식으로 renderPolygonEvent를 사용합니다.
|
|
7621
|
+
*
|
|
7622
|
+
* @remarks
|
|
7623
|
+
* - **성능 최적화**:
|
|
7624
|
+
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
7625
|
+
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
7626
|
+
* 3. 클로저로 최신 데이터 참조
|
|
7627
|
+
* - 선택된 항목은 Map에서 O(1)로 조회하여 성능 최적화
|
|
7628
|
+
* - 자동 렌더링: 스타일 props(selectedFillColor, hoveredFillColor 등) 기반으로 자동 렌더링
|
|
6993
7629
|
*/
|
|
6994
7630
|
|
|
6995
7631
|
|
|
@@ -7006,8 +7642,9 @@
|
|
|
7006
7642
|
name: 'event-render-shape',
|
|
7007
7643
|
sceneFunc: function (context, shape) {
|
|
7008
7644
|
var ctx = context; // 클로저로 최신 ref 값 참조
|
|
7645
|
+
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
7009
7646
|
|
|
7010
|
-
var selectedItems =
|
|
7647
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
7011
7648
|
var hovered = hoveredItemRef.current; // 일반 렌더링
|
|
7012
7649
|
|
|
7013
7650
|
renderEvent({
|
|
@@ -7030,101 +7667,62 @@
|
|
|
7030
7667
|
};
|
|
7031
7668
|
/**
|
|
7032
7669
|
* 전체 즉시 렌더링 (IDLE 시 호출)
|
|
7670
|
+
*
|
|
7671
|
+
* 뷰포트 업데이트, 공간 인덱스 빌드, 모든 레이어 렌더링을 순차적으로 수행합니다.
|
|
7672
|
+
*
|
|
7673
|
+
* @remarks
|
|
7674
|
+
* - 호출 시점: 지도 이동/줌 완료 시, 데이터 변경 시, 리사이즈 시
|
|
7675
|
+
* - 순서: 뷰포트 업데이트 → 공간 인덱스 빌드 → Base → Event 렌더링
|
|
7676
|
+
* - Animation Layer는 사용하지 않음 (폴리곤 특성)
|
|
7033
7677
|
*/
|
|
7034
7678
|
|
|
7035
7679
|
|
|
7036
7680
|
var renderAllImmediate = function () {
|
|
7037
|
-
updateViewport();
|
|
7038
|
-
buildSpatialIndex();
|
|
7681
|
+
updateViewport$1();
|
|
7682
|
+
buildSpatialIndex$1();
|
|
7039
7683
|
doRenderBase();
|
|
7040
7684
|
doRenderEvent();
|
|
7041
7685
|
}; // --------------------------------------------------------------------------
|
|
7042
7686
|
// 이벤트 핸들러: 지도 이벤트
|
|
7043
7687
|
// --------------------------------------------------------------------------
|
|
7044
7688
|
|
|
7045
|
-
/**
|
|
7046
|
-
* 지도 이동/줌 완료 시 처리
|
|
7047
|
-
*/
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
var handleIdle = function () {
|
|
7051
|
-
prevCenterOffsetRef.current = null;
|
|
7052
|
-
accumTranslateRef.current = {
|
|
7053
|
-
x: 0,
|
|
7054
|
-
y: 0
|
|
7055
|
-
}; // 2. 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
7056
|
-
|
|
7057
|
-
offsetCacheRef.current.clear();
|
|
7058
|
-
boundingBoxCacheRef.current.clear(); // 3. 마커 위치 업데이트
|
|
7059
|
-
|
|
7060
|
-
var bounds = controller.getCurrBounds();
|
|
7061
|
-
|
|
7062
|
-
var markerOptions = tslib.__assign({
|
|
7063
|
-
position: bounds.nw
|
|
7064
|
-
}, options);
|
|
7065
|
-
|
|
7066
|
-
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 4. transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
7067
|
-
|
|
7068
|
-
if (containerRef.current) {
|
|
7069
|
-
containerRef.current.style.transform = '';
|
|
7070
|
-
containerRef.current.style.visibility = '';
|
|
7071
|
-
} // 5. 새 위치에서 렌더링
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
renderAllImmediate();
|
|
7075
|
-
};
|
|
7076
|
-
/**
|
|
7077
|
-
* 줌 시작 시 처리 (일시적으로 숨김)
|
|
7078
|
-
*/
|
|
7079
|
-
|
|
7080
7689
|
|
|
7081
|
-
var
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7690
|
+
var _f = createMapEventHandlers({
|
|
7691
|
+
controller: controller,
|
|
7692
|
+
containerRef: containerRef,
|
|
7693
|
+
markerRef: markerRef,
|
|
7694
|
+
options: options,
|
|
7695
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
7696
|
+
accumTranslateRef: accumTranslateRef,
|
|
7697
|
+
offsetCacheRef: offsetCacheRef,
|
|
7698
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
7699
|
+
renderAllImmediate: renderAllImmediate
|
|
7700
|
+
}),
|
|
7701
|
+
handleIdle = _f.handleIdle,
|
|
7702
|
+
handleZoomStart = _f.handleZoomStart,
|
|
7703
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
7704
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
7705
|
+
handleDragStartShared = _f.handleDragStart,
|
|
7706
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
7086
7707
|
/**
|
|
7087
|
-
*
|
|
7708
|
+
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
7088
7709
|
*/
|
|
7089
7710
|
|
|
7090
7711
|
|
|
7091
|
-
var
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7712
|
+
var handleDragStart = function () {
|
|
7713
|
+
handleDragStartShared();
|
|
7714
|
+
draggingRef.current = true;
|
|
7715
|
+
controller.setMapCursor('grabbing');
|
|
7095
7716
|
};
|
|
7096
7717
|
/**
|
|
7097
|
-
*
|
|
7718
|
+
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
7098
7719
|
*/
|
|
7099
7720
|
|
|
7100
7721
|
|
|
7101
|
-
var
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
if (!prev) {
|
|
7107
|
-
prevCenterOffsetRef.current = {
|
|
7108
|
-
x: curr.x,
|
|
7109
|
-
y: curr.y
|
|
7110
|
-
};
|
|
7111
|
-
return;
|
|
7112
|
-
}
|
|
7113
|
-
|
|
7114
|
-
var dx = prev.x - curr.x;
|
|
7115
|
-
var dy = prev.y - curr.y;
|
|
7116
|
-
accumTranslateRef.current = {
|
|
7117
|
-
x: accumTranslateRef.current.x + dx,
|
|
7118
|
-
y: accumTranslateRef.current.y + dy
|
|
7119
|
-
};
|
|
7120
|
-
prevCenterOffsetRef.current = {
|
|
7121
|
-
x: curr.x,
|
|
7122
|
-
y: curr.y
|
|
7123
|
-
};
|
|
7124
|
-
|
|
7125
|
-
if (containerRef.current) {
|
|
7126
|
-
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
7127
|
-
}
|
|
7722
|
+
var handleDragEnd = function () {
|
|
7723
|
+
handleDragEndShared();
|
|
7724
|
+
draggingRef.current = false;
|
|
7725
|
+
controller.setMapCursor('grab');
|
|
7128
7726
|
}; // --------------------------------------------------------------------------
|
|
7129
7727
|
// Hit Test & 상태 관리
|
|
7130
7728
|
// --------------------------------------------------------------------------
|
|
@@ -7154,13 +7752,14 @@
|
|
|
7154
7752
|
/**
|
|
7155
7753
|
* Hover 상태 설정 및 레이어 렌더링
|
|
7156
7754
|
*
|
|
7157
|
-
*
|
|
7755
|
+
* 마우스가 폴리곤 위에 올라갔을 때 hover 상태를 설정하고 즉시 렌더링합니다.
|
|
7158
7756
|
*
|
|
7159
|
-
*
|
|
7757
|
+
* @param data hover된 폴리곤 데이터 또는 null (hover 해제 시)
|
|
7160
7758
|
*
|
|
7161
|
-
*
|
|
7162
|
-
* -
|
|
7163
|
-
* -
|
|
7759
|
+
* @remarks
|
|
7760
|
+
* - **성능 최적화**: RAF 없이 즉시 렌더링 (16ms 지연 제거)
|
|
7761
|
+
* - Event Layer에서 hover 효과 표시
|
|
7762
|
+
* - 커서 상태도 자동으로 업데이트됨 (pointer/grab)
|
|
7164
7763
|
*/
|
|
7165
7764
|
|
|
7166
7765
|
|
|
@@ -7179,11 +7778,17 @@
|
|
|
7179
7778
|
/**
|
|
7180
7779
|
* 클릭 처리 (단일/다중 선택)
|
|
7181
7780
|
*
|
|
7182
|
-
*
|
|
7781
|
+
* 폴리곤 클릭 시 선택 상태를 업데이트하고 렌더링을 수행합니다.
|
|
7782
|
+
*
|
|
7783
|
+
* @param data 클릭된 폴리곤 데이터
|
|
7183
7784
|
*
|
|
7184
|
-
*
|
|
7185
|
-
* -
|
|
7186
|
-
* -
|
|
7785
|
+
* @remarks
|
|
7786
|
+
* - **단일 선택**: 기존 선택 해제 후 새로 선택 (토글 가능)
|
|
7787
|
+
* - **다중 선택**: enableMultiSelect가 true면 기존 선택 유지하며 추가/제거
|
|
7788
|
+
* - **성능 최적화**:
|
|
7789
|
+
* - 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
7790
|
+
* - sceneFunc에서 selectedIds를 체크하여 선택된 폴리곤만 스킵
|
|
7791
|
+
* - 객체 생성 오버헤드 제거로 1,000개 이상도 부드럽게 처리
|
|
7187
7792
|
*/
|
|
7188
7793
|
|
|
7189
7794
|
|
|
@@ -7227,77 +7832,63 @@
|
|
|
7227
7832
|
|
|
7228
7833
|
/**
|
|
7229
7834
|
* 클릭 이벤트 처리
|
|
7835
|
+
*
|
|
7836
|
+
* @param event 클릭 이벤트 파라미터
|
|
7837
|
+
*
|
|
7838
|
+
* @remarks
|
|
7839
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
7840
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7841
|
+
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
7230
7842
|
*/
|
|
7231
7843
|
|
|
7232
7844
|
|
|
7233
7845
|
var handleClick = function (event) {
|
|
7234
|
-
var _a;
|
|
7235
|
-
|
|
7236
7846
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
7237
7847
|
|
|
7238
|
-
|
|
7848
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
7849
|
+
if (!clickedOffset) return;
|
|
7850
|
+
var data = findData(clickedOffset);
|
|
7239
7851
|
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
var data_1 = findData(clickedOffset);
|
|
7243
|
-
|
|
7244
|
-
if (data_1) {
|
|
7245
|
-
handleLocalClick(data_1);
|
|
7852
|
+
if (data) {
|
|
7853
|
+
handleLocalClick(data);
|
|
7246
7854
|
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
}
|
|
7855
|
+
if (onClick) {
|
|
7856
|
+
onClick(data, selectedIdsRef.current);
|
|
7250
7857
|
}
|
|
7251
|
-
} catch (error) {
|
|
7252
|
-
console.error('[WoongCanvasPolygon] handleClick error:', error);
|
|
7253
7858
|
}
|
|
7254
7859
|
};
|
|
7255
7860
|
/**
|
|
7256
7861
|
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
7862
|
+
*
|
|
7863
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
7864
|
+
*
|
|
7865
|
+
* @remarks
|
|
7866
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
7867
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7868
|
+
* - hover 상태 변경 시에만 렌더링 (최적화)
|
|
7257
7869
|
*/
|
|
7258
7870
|
|
|
7259
7871
|
|
|
7260
7872
|
var handleMouseMove = function (event) {
|
|
7261
|
-
var _a;
|
|
7262
|
-
|
|
7263
7873
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
7264
7874
|
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
var hoveredItem = findData(mouseOffset);
|
|
7270
|
-
var prevHovered = hoveredItemRef.current;
|
|
7875
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
7876
|
+
if (!mouseOffset) return;
|
|
7877
|
+
var hoveredItem = findData(mouseOffset);
|
|
7878
|
+
var prevHovered = hoveredItemRef.current;
|
|
7271
7879
|
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
7275
|
-
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
7276
|
-
}
|
|
7277
|
-
} catch (error) {
|
|
7278
|
-
console.error('[WoongCanvasPolygon] handleMouseMove error:', error);
|
|
7880
|
+
if (prevHovered !== hoveredItem) {
|
|
7881
|
+
setHovered(hoveredItem);
|
|
7279
7882
|
}
|
|
7280
7883
|
};
|
|
7281
|
-
/**
|
|
7282
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
7283
|
-
*/
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
var handleDragStart = function () {
|
|
7287
|
-
draggingRef.current = true;
|
|
7288
|
-
controller.setMapCursor('grabbing');
|
|
7289
|
-
};
|
|
7290
|
-
/**
|
|
7291
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
7292
|
-
*/
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
var handleDragEnd = function () {
|
|
7296
|
-
draggingRef.current = false;
|
|
7297
|
-
controller.setMapCursor('grab');
|
|
7298
|
-
};
|
|
7299
7884
|
/**
|
|
7300
7885
|
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
7886
|
+
*
|
|
7887
|
+
* 맵 영역 밖으로 마우스가 나갔을 때 hover 상태를 초기화합니다.
|
|
7888
|
+
*
|
|
7889
|
+
* @remarks
|
|
7890
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7891
|
+
* - hover 상태 초기화 및 커서 복원
|
|
7301
7892
|
*/
|
|
7302
7893
|
|
|
7303
7894
|
|
|
@@ -7310,10 +7901,6 @@
|
|
|
7310
7901
|
hoveredItemRef.current = null;
|
|
7311
7902
|
controller.setMapCursor('grab');
|
|
7312
7903
|
doRenderEvent();
|
|
7313
|
-
|
|
7314
|
-
if (onMouseOut) {
|
|
7315
|
-
onMouseOut(prevHovered);
|
|
7316
|
-
}
|
|
7317
7904
|
}
|
|
7318
7905
|
}; // --------------------------------------------------------------------------
|
|
7319
7906
|
// Lifecycle: DOM 초기화
|
|
@@ -7381,7 +7968,7 @@
|
|
|
7381
7968
|
stage.add(baseLayer);
|
|
7382
7969
|
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
7383
7970
|
|
|
7384
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
7971
|
+
updateViewport$1(); // ResizeObserver (맵 크기 변경 감지)
|
|
7385
7972
|
|
|
7386
7973
|
var resizeRafId = null;
|
|
7387
7974
|
var resizeObserver = new ResizeObserver(function () {
|
|
@@ -7395,7 +7982,7 @@
|
|
|
7395
7982
|
stage.height(mapDiv.offsetHeight);
|
|
7396
7983
|
offsetCacheRef.current.clear();
|
|
7397
7984
|
boundingBoxCacheRef.current.clear();
|
|
7398
|
-
updateViewport();
|
|
7985
|
+
updateViewport$1();
|
|
7399
7986
|
renderAllImmediate();
|
|
7400
7987
|
resizeRafId = null;
|
|
7401
7988
|
});
|
|
@@ -7422,8 +8009,6 @@
|
|
|
7422
8009
|
return findData(offset) !== null;
|
|
7423
8010
|
},
|
|
7424
8011
|
onClick: onClick,
|
|
7425
|
-
onMouseOver: onMouseOver,
|
|
7426
|
-
onMouseOut: onMouseOut,
|
|
7427
8012
|
findData: findData,
|
|
7428
8013
|
setHovered: setHovered,
|
|
7429
8014
|
handleLocalClick: handleLocalClick,
|
|
@@ -7483,18 +8068,8 @@
|
|
|
7483
8068
|
// --------------------------------------------------------------------------
|
|
7484
8069
|
|
|
7485
8070
|
React.useEffect(function () {
|
|
7486
|
-
if (!stageRef.current) return;
|
|
7487
|
-
|
|
7488
|
-
if (externalSelectedItems === undefined) return; // 외부에서 전달된 selectedItems로 동기화
|
|
7489
|
-
|
|
7490
|
-
var newSelectedIds = new Set();
|
|
7491
|
-
var newSelectedItemsMap = new Map();
|
|
7492
|
-
externalSelectedItems.forEach(function (item) {
|
|
7493
|
-
newSelectedIds.add(item.id);
|
|
7494
|
-
newSelectedItemsMap.set(item.id, item);
|
|
7495
|
-
});
|
|
7496
|
-
selectedIdsRef.current = newSelectedIds;
|
|
7497
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 렌더링
|
|
8071
|
+
if (!stageRef.current) return;
|
|
8072
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef); // 렌더링
|
|
7498
8073
|
|
|
7499
8074
|
doRenderBase();
|
|
7500
8075
|
doRenderEvent();
|
|
@@ -7544,27 +8119,7 @@
|
|
|
7544
8119
|
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
7545
8120
|
*/
|
|
7546
8121
|
|
|
7547
|
-
|
|
7548
|
-
return [m.id, m];
|
|
7549
|
-
}));
|
|
7550
|
-
var newSelectedItemsMap = new Map();
|
|
7551
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
7552
|
-
// 현재 data에 있으면 최신 데이터 사용
|
|
7553
|
-
var currentItem = dataMap.get(id);
|
|
7554
|
-
|
|
7555
|
-
if (currentItem) {
|
|
7556
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
7557
|
-
} else {
|
|
7558
|
-
// 화면 밖이면 기존 데이터 유지
|
|
7559
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
7560
|
-
|
|
7561
|
-
if (prevItem) {
|
|
7562
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
7563
|
-
}
|
|
7564
|
-
}
|
|
7565
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
|
|
7566
|
-
|
|
7567
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
8122
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current); // 즉시 렌더링
|
|
7568
8123
|
|
|
7569
8124
|
renderAllImmediate();
|
|
7570
8125
|
}, [data]);
|
|
@@ -10455,19 +11010,27 @@
|
|
|
10455
11010
|
exports.WoongCanvasMarker = WoongCanvasMarker;
|
|
10456
11011
|
exports.WoongCanvasPolygon = WoongCanvasPolygon;
|
|
10457
11012
|
exports.WoongCanvasProvider = WoongCanvasProvider;
|
|
11013
|
+
exports.buildSpatialIndex = buildSpatialIndex;
|
|
10458
11014
|
exports.calculateTextBoxWidth = calculateTextBoxWidth;
|
|
10459
11015
|
exports.computeMarkerOffset = computeMarkerOffset;
|
|
10460
11016
|
exports.computePolygonOffsets = computePolygonOffsets;
|
|
11017
|
+
exports.createMapEventHandlers = createMapEventHandlers;
|
|
10461
11018
|
exports.getClusterInfo = getClusterInfo;
|
|
10462
11019
|
exports.getMapOfType = getMapOfType;
|
|
10463
11020
|
exports.hexToRgba = hexToRgba;
|
|
11021
|
+
exports.isInViewport = isInViewport;
|
|
10464
11022
|
exports.isPointInMarkerData = isPointInMarkerData;
|
|
10465
11023
|
exports.isPointInPolygon = isPointInPolygon;
|
|
10466
11024
|
exports.isPointInPolygonData = isPointInPolygonData;
|
|
10467
11025
|
exports.log = log;
|
|
11026
|
+
exports.mapValuesToArray = mapValuesToArray;
|
|
11027
|
+
exports.syncExternalSelectedItems = syncExternalSelectedItems;
|
|
11028
|
+
exports.syncSelectedItems = syncSelectedItems;
|
|
11029
|
+
exports.updateViewport = updateViewport;
|
|
10468
11030
|
exports.useMarkerMoving = useMarkerMoving;
|
|
10469
11031
|
exports.useMintMapController = useMintMapController;
|
|
10470
11032
|
exports.useWoongCanvasContext = useWoongCanvasContext;
|
|
11033
|
+
exports.validateEvent = validateEvent;
|
|
10471
11034
|
exports.waiting = waiting;
|
|
10472
11035
|
|
|
10473
11036
|
Object.defineProperty(exports, '__esModule', { value: true });
|