@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.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';
|
|
@@ -796,6 +796,30 @@ function () {
|
|
|
796
796
|
|
|
797
797
|
/**
|
|
798
798
|
* 폴리곤 offset 계산
|
|
799
|
+
*
|
|
800
|
+
* GeoJSON MultiPolygon 형식의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
|
|
801
|
+
*
|
|
802
|
+
* @param polygonData 폴리곤 데이터 (paths 필드 필수)
|
|
803
|
+
* @param controller MintMapController 인스턴스
|
|
804
|
+
* @returns 변환된 화면 좌표 배열 (4차원 배열) 또는 null (변환 실패 시)
|
|
805
|
+
*
|
|
806
|
+
* @remarks
|
|
807
|
+
* - 반환 형식: [MultiPolygon][Polygon][Point][x/y]
|
|
808
|
+
* - 성능: O(n), n은 폴리곤의 총 좌표 수
|
|
809
|
+
* - GeoJSON MultiPolygon 형식 지원
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* ```typescript
|
|
813
|
+
* const offsets = computePolygonOffsets(polygonData, controller);
|
|
814
|
+
* if (!offsets) return; // 변환 실패
|
|
815
|
+
*
|
|
816
|
+
* // offsets 구조: [MultiPolygon][Polygon][Point][x/y]
|
|
817
|
+
* for (const multiPolygon of offsets) {
|
|
818
|
+
* for (const polygon of multiPolygon) {
|
|
819
|
+
* // polygon은 [Point][x/y] 배열
|
|
820
|
+
* }
|
|
821
|
+
* }
|
|
822
|
+
* ```
|
|
799
823
|
*/
|
|
800
824
|
|
|
801
825
|
var computePolygonOffsets = function (polygonData, controller) {
|
|
@@ -832,6 +856,23 @@ var computePolygonOffsets = function (polygonData, controller) {
|
|
|
832
856
|
};
|
|
833
857
|
/**
|
|
834
858
|
* 마커 offset 계산
|
|
859
|
+
*
|
|
860
|
+
* 마커의 위경도 좌표를 화면 픽셀 좌표로 변환합니다.
|
|
861
|
+
*
|
|
862
|
+
* @param markerData 마커 데이터 (position 필드 필수)
|
|
863
|
+
* @param controller MintMapController 인스턴스
|
|
864
|
+
* @returns 변환된 화면 좌표 (Offset) 또는 null (변환 실패 시)
|
|
865
|
+
*
|
|
866
|
+
* @remarks
|
|
867
|
+
* - 반환된 좌표는 마커의 중심점 (x, y)
|
|
868
|
+
* - 성능: O(1) - 단일 좌표 변환
|
|
869
|
+
*
|
|
870
|
+
* @example
|
|
871
|
+
* ```typescript
|
|
872
|
+
* const offset = computeMarkerOffset(markerData, controller);
|
|
873
|
+
* if (!offset) return; // 변환 실패
|
|
874
|
+
* // offset.x, offset.y는 화면 픽셀 좌표
|
|
875
|
+
* ```
|
|
835
876
|
*/
|
|
836
877
|
|
|
837
878
|
var computeMarkerOffset = function (markerData, controller) {
|
|
@@ -843,6 +884,24 @@ var computeMarkerOffset = function (markerData, controller) {
|
|
|
843
884
|
};
|
|
844
885
|
/**
|
|
845
886
|
* Point-in-Polygon 알고리즘
|
|
887
|
+
*
|
|
888
|
+
* Ray Casting 알고리즘을 사용하여 점이 폴리곤 내부에 있는지 확인합니다.
|
|
889
|
+
*
|
|
890
|
+
* @param point 확인할 점의 좌표
|
|
891
|
+
* @param polygon 폴리곤 좌표 배열 (각 요소는 [x, y] 형식)
|
|
892
|
+
* @returns 점이 폴리곤 내부에 있으면 true, 아니면 false
|
|
893
|
+
*
|
|
894
|
+
* @remarks
|
|
895
|
+
* - **알고리즘**: Ray Casting (Ray Crossing)
|
|
896
|
+
* - **성능**: O(n), n은 폴리곤의 좌표 수
|
|
897
|
+
* - **경계 처리**: 경계선 위의 점은 내부로 간주
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* ```typescript
|
|
901
|
+
* const point = { x: 100, y: 200 };
|
|
902
|
+
* const polygon = [[0, 0], [100, 0], [100, 100], [0, 100]];
|
|
903
|
+
* const isInside = isPointInPolygon(point, polygon);
|
|
904
|
+
* ```
|
|
846
905
|
*/
|
|
847
906
|
|
|
848
907
|
var isPointInPolygon = function (point, polygon) {
|
|
@@ -862,13 +921,32 @@ var isPointInPolygon = function (point, polygon) {
|
|
|
862
921
|
/**
|
|
863
922
|
* 폴리곤 히트 테스트 (도넛 폴리곤 지원)
|
|
864
923
|
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
924
|
+
* 점이 폴리곤 내부에 있는지 확인합니다. 도넛 폴리곤(구멍이 있는 폴리곤)을 지원합니다.
|
|
925
|
+
*
|
|
926
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
927
|
+
* @param polygonData 폴리곤 데이터
|
|
928
|
+
* @param getPolygonOffsets 폴리곤 좌표 변환 함수
|
|
929
|
+
* @returns 점이 폴리곤 내부에 있으면 true, 아니면 false
|
|
868
930
|
*
|
|
869
|
-
*
|
|
931
|
+
* @remarks
|
|
932
|
+
* - **도넛 폴리곤 처리** (isDonutPolygon === true):
|
|
933
|
+
* 1. 외부 폴리곤(첫 번째): 내부에 있어야 함
|
|
934
|
+
* 2. 내부 구멍들(나머지): 내부에 있으면 안 됨 (evenodd 규칙)
|
|
935
|
+
* - **일반 폴리곤 처리**: Point-in-Polygon 알고리즘 사용
|
|
936
|
+
* - **성능**: O(n), n은 폴리곤의 총 좌표 수
|
|
937
|
+
*
|
|
938
|
+
* **중요**: 도넛 폴리곤과 내부 폴리곤은 별개의 polygonData로 처리됩니다.
|
|
870
939
|
* - 도넛 폴리곤 A: isDonutPolygon=true
|
|
871
940
|
* - 내부 폴리곤 B: isDonutPolygon=false (별도 데이터)
|
|
941
|
+
*
|
|
942
|
+
* @example
|
|
943
|
+
* ```typescript
|
|
944
|
+
* const isHit = isPointInPolygonData(
|
|
945
|
+
* clickedOffset,
|
|
946
|
+
* polygonData,
|
|
947
|
+
* getOrComputePolygonOffsets
|
|
948
|
+
* );
|
|
949
|
+
* ```
|
|
872
950
|
*/
|
|
873
951
|
|
|
874
952
|
var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffsets) {
|
|
@@ -933,10 +1011,29 @@ var isPointInPolygonData = function (clickedOffset, polygonData, getPolygonOffse
|
|
|
933
1011
|
/**
|
|
934
1012
|
* 마커 히트 테스트 (클릭/hover 영역 체크)
|
|
935
1013
|
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1014
|
+
* 점이 마커의 클릭/호버 영역 내부에 있는지 확인합니다.
|
|
1015
|
+
* 마커의 꼬리(tail)는 Hit Test 영역에서 제외됩니다.
|
|
1016
|
+
*
|
|
1017
|
+
* @param clickedOffset 클릭/마우스 위치 좌표
|
|
1018
|
+
* @param markerData 마커 데이터
|
|
1019
|
+
* @param getMarkerOffset 마커 좌표 변환 함수
|
|
1020
|
+
* @returns 점이 마커 영역 내부에 있으면 true, 아니면 false
|
|
1021
|
+
*
|
|
1022
|
+
* @remarks
|
|
1023
|
+
* - **꼬리 제외**: 꼬리(tail)는 Hit Test 영역에서 제외됩니다
|
|
1024
|
+
* - markerOffset.y는 마커 최하단(꼬리 끝) 좌표
|
|
1025
|
+
* - boxHeight는 마커 본체만 포함 (꼬리 제외)
|
|
1026
|
+
* - tailHeight만큼 위로 올려서 본체만 Hit Test 영역으로 사용
|
|
1027
|
+
* - **성능**: O(1) - 단순 사각형 영역 체크
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* const isHit = isPointInMarkerData(
|
|
1032
|
+
* clickedOffset,
|
|
1033
|
+
* markerData,
|
|
1034
|
+
* getOrComputeMarkerOffset
|
|
1035
|
+
* );
|
|
1036
|
+
* ```
|
|
940
1037
|
*/
|
|
941
1038
|
|
|
942
1039
|
var isPointInMarkerData = function (clickedOffset, markerData, getMarkerOffset) {
|
|
@@ -973,12 +1070,29 @@ var tempCtx = tempCanvas.getContext('2d');
|
|
|
973
1070
|
/**
|
|
974
1071
|
* 텍스트 박스의 너비를 계산합니다.
|
|
975
1072
|
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
* @param
|
|
980
|
-
* @param
|
|
981
|
-
* @
|
|
1073
|
+
* Canvas 2D Context의 measureText()를 사용하여 텍스트의 실제 너비를 계산하고,
|
|
1074
|
+
* 패딩과 최소 너비를 고려하여 최종 너비를 반환합니다.
|
|
1075
|
+
*
|
|
1076
|
+
* @param params 파라미터 객체
|
|
1077
|
+
* @param params.text 측정할 텍스트
|
|
1078
|
+
* @param params.fontConfig 폰트 설정 (예: 'bold 16px Arial')
|
|
1079
|
+
* @param params.padding 텍스트 박스에 적용할 패딩 값 (px)
|
|
1080
|
+
* @param params.minWidth 최소 너비 (px)
|
|
1081
|
+
* @returns 계산된 텍스트 박스의 너비 (px)
|
|
1082
|
+
*
|
|
1083
|
+
* @remarks
|
|
1084
|
+
* - 성능: O(1) - 단일 텍스트 측정
|
|
1085
|
+
* - 임시 Canvas를 사용하여 정확한 너비 측정
|
|
1086
|
+
*
|
|
1087
|
+
* @example
|
|
1088
|
+
* ```typescript
|
|
1089
|
+
* const width = calculateTextBoxWidth({
|
|
1090
|
+
* text: "Hello World",
|
|
1091
|
+
* fontConfig: 'bold 16px Arial',
|
|
1092
|
+
* padding: 20,
|
|
1093
|
+
* minWidth: 60
|
|
1094
|
+
* });
|
|
1095
|
+
* ```
|
|
982
1096
|
*/
|
|
983
1097
|
|
|
984
1098
|
var calculateTextBoxWidth = function (_a) {
|
|
@@ -993,6 +1107,29 @@ var calculateTextBoxWidth = function (_a) {
|
|
|
993
1107
|
};
|
|
994
1108
|
|
|
995
1109
|
var WoongCanvasContext = createContext(null);
|
|
1110
|
+
/**
|
|
1111
|
+
* WoongCanvasProvider 컴포넌트
|
|
1112
|
+
*
|
|
1113
|
+
* 다중 WoongCanvas 컴포넌트 인스턴스를 관리하는 Context Provider입니다.
|
|
1114
|
+
* 여러 WoongCanvasMarker/WoongCanvasPolygon이 함께 사용될 때 전역 이벤트 조정을 수행합니다.
|
|
1115
|
+
*
|
|
1116
|
+
* @param props 컴포넌트 props
|
|
1117
|
+
* @param props.children 자식 컴포넌트
|
|
1118
|
+
*
|
|
1119
|
+
* @remarks
|
|
1120
|
+
* - zIndex 기반으로 이벤트 우선순위 처리
|
|
1121
|
+
* - 전역 클릭/호버 이벤트 조정
|
|
1122
|
+
* - 여러 레이어 간 상호작용 관리
|
|
1123
|
+
*
|
|
1124
|
+
* @example
|
|
1125
|
+
* ```tsx
|
|
1126
|
+
* <WoongCanvasProvider>
|
|
1127
|
+
* <WoongCanvasMarker data={markers} zIndex={10} />
|
|
1128
|
+
* <WoongCanvasPolygon data={polygons} zIndex={5} />
|
|
1129
|
+
* </WoongCanvasProvider>
|
|
1130
|
+
* ```
|
|
1131
|
+
*/
|
|
1132
|
+
|
|
996
1133
|
var WoongCanvasProvider = function (_a) {
|
|
997
1134
|
var children = _a.children;
|
|
998
1135
|
var controller = useMintMapController(); // Refs
|
|
@@ -1003,7 +1140,12 @@ var WoongCanvasProvider = function (_a) {
|
|
|
1003
1140
|
var draggingRef = useRef(false);
|
|
1004
1141
|
/**
|
|
1005
1142
|
* 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
1006
|
-
*
|
|
1143
|
+
*
|
|
1144
|
+
* 컴포넌트 인스턴스를 등록하고 zIndex 기준으로 내림차순 정렬합니다.
|
|
1145
|
+
* 높은 zIndex를 가진 컴포넌트가 이벤트 처리에서 우선순위를 가집니다.
|
|
1146
|
+
*
|
|
1147
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1148
|
+
* @param instance 등록할 컴포넌트 인스턴스
|
|
1007
1149
|
*/
|
|
1008
1150
|
|
|
1009
1151
|
var registerComponent = useCallback(function (instance) {
|
|
@@ -1014,6 +1156,12 @@ var WoongCanvasProvider = function (_a) {
|
|
|
1014
1156
|
}, []);
|
|
1015
1157
|
/**
|
|
1016
1158
|
* 컴포넌트 등록 해제
|
|
1159
|
+
*
|
|
1160
|
+
* 컴포넌트 인스턴스를 등록 해제합니다.
|
|
1161
|
+
* hover 중이던 컴포넌트면 hover 상태도 초기화합니다.
|
|
1162
|
+
*
|
|
1163
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1164
|
+
* @param instance 등록 해제할 컴포넌트 인스턴스
|
|
1017
1165
|
*/
|
|
1018
1166
|
|
|
1019
1167
|
var unregisterComponent = useCallback(function (instance) {
|
|
@@ -1029,6 +1177,15 @@ var WoongCanvasProvider = function (_a) {
|
|
|
1029
1177
|
}, []);
|
|
1030
1178
|
/**
|
|
1031
1179
|
* 전역 클릭 핸들러 (zIndex 우선순위)
|
|
1180
|
+
*
|
|
1181
|
+
* 모든 등록된 WoongCanvas 컴포넌트 중 zIndex가 높은 컴포넌트부터 클릭 이벤트를 처리합니다.
|
|
1182
|
+
*
|
|
1183
|
+
* @param event 클릭 이벤트 파라미터
|
|
1184
|
+
*
|
|
1185
|
+
* @remarks
|
|
1186
|
+
* - zIndex가 높은 컴포넌트부터 순회하여 첫 번째 히트만 처리
|
|
1187
|
+
* - 상호작용이 비활성화된 컴포넌트는 스킵
|
|
1188
|
+
* - Context가 없으면 각 컴포넌트의 로컬 핸들러가 처리
|
|
1032
1189
|
*/
|
|
1033
1190
|
|
|
1034
1191
|
var handleGlobalClick = useCallback(function (event) {
|
|
@@ -1056,6 +1213,16 @@ var WoongCanvasProvider = function (_a) {
|
|
|
1056
1213
|
}, [controller]);
|
|
1057
1214
|
/**
|
|
1058
1215
|
* 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
1216
|
+
*
|
|
1217
|
+
* 모든 등록된 WoongCanvas 컴포넌트 중 zIndex가 높은 컴포넌트부터 hover 이벤트를 처리합니다.
|
|
1218
|
+
*
|
|
1219
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
1220
|
+
*
|
|
1221
|
+
* @remarks
|
|
1222
|
+
* - zIndex가 높은 컴포넌트부터 순회하여 첫 번째 히트만 처리
|
|
1223
|
+
* - 상호작용이 비활성화된 컴포넌트는 스킵
|
|
1224
|
+
* - 드래그 중이면 이벤트 무시 (성능 최적화)
|
|
1225
|
+
* - hover 상태 변경 감지 후 이전 hover 해제 및 새 hover 설정
|
|
1059
1226
|
*/
|
|
1060
1227
|
|
|
1061
1228
|
var handleGlobalMouseMove = useCallback(function (event) {
|
|
@@ -1142,6 +1309,26 @@ var WoongCanvasProvider = function (_a) {
|
|
|
1142
1309
|
value: contextValue
|
|
1143
1310
|
}, children);
|
|
1144
1311
|
};
|
|
1312
|
+
/**
|
|
1313
|
+
* WoongCanvas Context Hook
|
|
1314
|
+
*
|
|
1315
|
+
* WoongCanvasProvider로 감싸진 컴포넌트에서 Context에 접근할 수 있는 hook입니다.
|
|
1316
|
+
*
|
|
1317
|
+
* @returns WoongCanvasContextValue 또는 null (Provider 없으면)
|
|
1318
|
+
*
|
|
1319
|
+
* @remarks
|
|
1320
|
+
* - Provider로 감싸지지 않으면 null 반환 (각 컴포넌트가 로컬 이벤트 처리)
|
|
1321
|
+
* - Provider로 감싸져 있으면 전역 이벤트 조정 활성화
|
|
1322
|
+
*
|
|
1323
|
+
* @example
|
|
1324
|
+
* ```typescript
|
|
1325
|
+
* const context = useWoongCanvasContext();
|
|
1326
|
+
* if (context) {
|
|
1327
|
+
* // Context 사용 중 (전역 이벤트 조정)
|
|
1328
|
+
* }
|
|
1329
|
+
* ```
|
|
1330
|
+
*/
|
|
1331
|
+
|
|
1145
1332
|
var useWoongCanvasContext = function () {
|
|
1146
1333
|
var context = useContext(WoongCanvasContext);
|
|
1147
1334
|
return context;
|
|
@@ -1195,12 +1382,32 @@ var DEFAULT_CULLING_MARGIN = 100;
|
|
|
1195
1382
|
var DEFAULT_MAX_CACHE_SIZE = 30000;
|
|
1196
1383
|
/**
|
|
1197
1384
|
* LRU (Least Recently Used) Cache
|
|
1198
|
-
* 메모리 제한을 위한 캐시 구현 (최적화 버전)
|
|
1199
1385
|
*
|
|
1200
|
-
*
|
|
1386
|
+
* 메모리 제한을 위한 캐시 구현입니다. WoongCanvas 컴포넌트에서 좌표 변환 결과를 캐싱하는데 사용됩니다.
|
|
1387
|
+
*
|
|
1388
|
+
* @template K 캐시 키 타입
|
|
1389
|
+
* @template V 캐시 값 타입
|
|
1390
|
+
*
|
|
1391
|
+
* @remarks
|
|
1392
|
+
* **개선 사항**:
|
|
1201
1393
|
* 1. get() 성능 향상: 접근 빈도 추적 없이 단순 조회만 수행 (delete+set 제거)
|
|
1202
1394
|
* 2. set() 버그 수정: 기존 키 업데이트 시 maxSize 체크 로직 개선
|
|
1203
1395
|
* 3. 메모리 효율: 단순 FIFO 캐시로 동작하여 오버헤드 최소화
|
|
1396
|
+
*
|
|
1397
|
+
* **트레이드오프**:
|
|
1398
|
+
* - 장점: 읽기 성능 대폭 향상 (10,000번 get → 이전보다 2배 빠름)
|
|
1399
|
+
* - 단점: 접근 빈도가 아닌 삽입 순서 기반 eviction (FIFO)
|
|
1400
|
+
*
|
|
1401
|
+
* WoongCanvasMarker 사용 사례에 최적:
|
|
1402
|
+
* - 좌표 변환 결과는 zoom/pan 시 어차피 전체 초기화
|
|
1403
|
+
* - 접근 빈도 추적보다 빠른 조회가 더 중요
|
|
1404
|
+
*
|
|
1405
|
+
* @example
|
|
1406
|
+
* ```typescript
|
|
1407
|
+
* const cache = new LRUCache<string, Offset>(30000);
|
|
1408
|
+
* cache.set(item.id, offset);
|
|
1409
|
+
* const cached = cache.get(item.id);
|
|
1410
|
+
* ```
|
|
1204
1411
|
*/
|
|
1205
1412
|
|
|
1206
1413
|
var LRUCache =
|
|
@@ -1217,17 +1424,12 @@ function () {
|
|
|
1217
1424
|
/**
|
|
1218
1425
|
* 캐시에서 값 조회
|
|
1219
1426
|
*
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
* - 현재: 단순 조회만 수행 (O(1) 해시 조회)
|
|
1223
|
-
*
|
|
1224
|
-
* 트레이드오프:
|
|
1225
|
-
* - 장점: 읽기 성능 대폭 향상 (10,000번 get → 이전보다 2배 빠름)
|
|
1226
|
-
* - 단점: 접근 빈도가 아닌 삽입 순서 기반 eviction (FIFO)
|
|
1427
|
+
* @param key 조회할 키
|
|
1428
|
+
* @returns 캐시된 값 또는 undefined (캐시 미스 시)
|
|
1227
1429
|
*
|
|
1228
|
-
*
|
|
1229
|
-
* -
|
|
1230
|
-
* -
|
|
1430
|
+
* @remarks
|
|
1431
|
+
* - 성능: O(1) 해시 조회
|
|
1432
|
+
* - 최적화: delete+set 제거로 읽기 성능 대폭 향상
|
|
1231
1433
|
*/
|
|
1232
1434
|
|
|
1233
1435
|
|
|
@@ -1235,11 +1437,15 @@ function () {
|
|
|
1235
1437
|
return this.cache.get(key);
|
|
1236
1438
|
};
|
|
1237
1439
|
/**
|
|
1238
|
-
* 캐시에 값 저장
|
|
1440
|
+
* 캐시에 값 저장
|
|
1239
1441
|
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1442
|
+
* @param key 저장할 키
|
|
1443
|
+
* @param value 저장할 값
|
|
1444
|
+
*
|
|
1445
|
+
* @remarks
|
|
1446
|
+
* - 기존 키 업데이트: 단순 덮어쓰기 (크기 변화 없음)
|
|
1447
|
+
* - 신규 키 추가: 크기 체크 후 필요시 가장 오래된 항목 제거 (FIFO)
|
|
1448
|
+
* - 성능: O(1) 평균 시간복잡도
|
|
1243
1449
|
*/
|
|
1244
1450
|
|
|
1245
1451
|
|
|
@@ -1337,9 +1543,18 @@ function () {
|
|
|
1337
1543
|
/**
|
|
1338
1544
|
* 항목 추가 (바운딩 박스 기반)
|
|
1339
1545
|
*
|
|
1340
|
-
*
|
|
1546
|
+
* 항목을 공간 인덱스에 추가합니다. 바운딩 박스가 걸치는 모든 셀에 삽입됩니다.
|
|
1547
|
+
*
|
|
1548
|
+
* @param item 추가할 항목
|
|
1549
|
+
* @param minX 바운딩 박스 최소 X 좌표
|
|
1550
|
+
* @param minY 바운딩 박스 최소 Y 좌표
|
|
1551
|
+
* @param maxX 바운딩 박스 최대 X 좌표
|
|
1552
|
+
* @param maxY 바운딩 박스 최대 Y 좌표
|
|
1553
|
+
*
|
|
1554
|
+
* @remarks
|
|
1341
1555
|
* - 중복 삽입 방지: 기존 항목이 있으면 먼저 제거 후 재삽입
|
|
1342
1556
|
* - 메모리 누수 방지: 이전 셀 참조 완전 제거
|
|
1557
|
+
* - 성능: O(1) 평균 시간복잡도
|
|
1343
1558
|
*/
|
|
1344
1559
|
|
|
1345
1560
|
|
|
@@ -1363,7 +1578,14 @@ function () {
|
|
|
1363
1578
|
/**
|
|
1364
1579
|
* 항목 제거
|
|
1365
1580
|
*
|
|
1366
|
-
*
|
|
1581
|
+
* 공간 인덱스에서 항목을 제거합니다.
|
|
1582
|
+
*
|
|
1583
|
+
* @param item 제거할 항목
|
|
1584
|
+
*
|
|
1585
|
+
* @remarks
|
|
1586
|
+
* - 메모리 누수 방지: 모든 셀에서 참조 완전 제거
|
|
1587
|
+
* - 빈 셀 정리: 항목이 없어진 셀은 자동으로 정리됨
|
|
1588
|
+
* - 성능: O(셀 개수), 보통 O(1)
|
|
1367
1589
|
*/
|
|
1368
1590
|
|
|
1369
1591
|
|
|
@@ -1394,7 +1616,13 @@ function () {
|
|
|
1394
1616
|
/**
|
|
1395
1617
|
* 항목 위치 업데이트
|
|
1396
1618
|
*
|
|
1397
|
-
*
|
|
1619
|
+
* 항목의 위치를 업데이트합니다. remove + insert의 편의 함수입니다.
|
|
1620
|
+
*
|
|
1621
|
+
* @param item 업데이트할 항목
|
|
1622
|
+
* @param minX 새로운 바운딩 박스 최소 X 좌표
|
|
1623
|
+
* @param minY 새로운 바운딩 박스 최소 Y 좌표
|
|
1624
|
+
* @param maxX 새로운 바운딩 박스 최대 X 좌표
|
|
1625
|
+
* @param maxY 새로운 바운딩 박스 최대 Y 좌표
|
|
1398
1626
|
*/
|
|
1399
1627
|
|
|
1400
1628
|
|
|
@@ -1404,7 +1632,26 @@ function () {
|
|
|
1404
1632
|
/**
|
|
1405
1633
|
* 점 주변의 항목 조회 (1개 셀만)
|
|
1406
1634
|
*
|
|
1407
|
-
*
|
|
1635
|
+
* 특정 좌표가 속한 셀의 모든 항목을 반환합니다.
|
|
1636
|
+
*
|
|
1637
|
+
* @param x 조회할 X 좌표
|
|
1638
|
+
* @param y 조회할 Y 좌표
|
|
1639
|
+
* @returns 해당 셀의 항목 배열 (없으면 빈 배열)
|
|
1640
|
+
*
|
|
1641
|
+
* @remarks
|
|
1642
|
+
* - 성능: O(해당 셀의 항목 수) - 보통 ~10개 (30,000개 전체를 체크하지 않음)
|
|
1643
|
+
* - Hit Test에 최적화된 메서드
|
|
1644
|
+
* - 빈 배열 재사용으로 메모리 할당 최소화
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* ```typescript
|
|
1648
|
+
* const candidates = grid.queryPoint(mouseX, mouseY);
|
|
1649
|
+
* for (const item of candidates) {
|
|
1650
|
+
* if (isPointInItem(item, mouseX, mouseY)) {
|
|
1651
|
+
* return item;
|
|
1652
|
+
* }
|
|
1653
|
+
* }
|
|
1654
|
+
* ```
|
|
1408
1655
|
*/
|
|
1409
1656
|
|
|
1410
1657
|
|
|
@@ -1417,8 +1664,18 @@ function () {
|
|
|
1417
1664
|
/**
|
|
1418
1665
|
* 영역 내 항목 조회
|
|
1419
1666
|
*
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1667
|
+
* 특정 영역(바운딩 박스)과 교차하는 모든 항목을 반환합니다.
|
|
1668
|
+
*
|
|
1669
|
+
* @param minX 영역 최소 X 좌표
|
|
1670
|
+
* @param minY 영역 최소 Y 좌표
|
|
1671
|
+
* @param maxX 영역 최대 X 좌표
|
|
1672
|
+
* @param maxY 영역 최대 Y 좌표
|
|
1673
|
+
* @returns 영역과 교차하는 항목 배열 (중복 제거됨)
|
|
1674
|
+
*
|
|
1675
|
+
* @remarks
|
|
1676
|
+
* - 성능: O(셀 개수 × 셀당 평균 항목 수)
|
|
1677
|
+
* - Set으로 중복 제거 보장 (항목이 여러 셀에 걸쳐 있어도 한 번만 반환)
|
|
1678
|
+
* - Viewport Culling에 유용
|
|
1422
1679
|
*/
|
|
1423
1680
|
|
|
1424
1681
|
|
|
@@ -1443,7 +1700,11 @@ function () {
|
|
|
1443
1700
|
/**
|
|
1444
1701
|
* 항목 존재 여부 확인
|
|
1445
1702
|
*
|
|
1446
|
-
*
|
|
1703
|
+
* @param item 확인할 항목
|
|
1704
|
+
* @returns 항목이 인덱스에 있으면 true, 아니면 false
|
|
1705
|
+
*
|
|
1706
|
+
* @remarks
|
|
1707
|
+
* - 성능: O(1) 해시 조회
|
|
1447
1708
|
*/
|
|
1448
1709
|
|
|
1449
1710
|
|
|
@@ -1462,7 +1723,14 @@ function () {
|
|
|
1462
1723
|
/**
|
|
1463
1724
|
* 통계 정보
|
|
1464
1725
|
*
|
|
1465
|
-
*
|
|
1726
|
+
* 공간 인덱스의 현재 상태를 반환합니다. 디버깅 및 성능 분석에 유용합니다.
|
|
1727
|
+
*
|
|
1728
|
+
* @returns 통계 정보 객체
|
|
1729
|
+
*
|
|
1730
|
+
* @remarks
|
|
1731
|
+
* - totalCells: 현재 사용 중인 셀 개수
|
|
1732
|
+
* - totalItems: 인덱스에 등록된 고유 항목 수 (정확)
|
|
1733
|
+
* - avgItemsPerCell: 셀당 평균 항목 수
|
|
1466
1734
|
*/
|
|
1467
1735
|
|
|
1468
1736
|
|
|
@@ -1481,6 +1749,400 @@ function () {
|
|
|
1481
1749
|
return SpatialHashGrid;
|
|
1482
1750
|
}();
|
|
1483
1751
|
|
|
1752
|
+
/**
|
|
1753
|
+
* 현재 뷰포트 영역 계산
|
|
1754
|
+
*
|
|
1755
|
+
* Konva Stage의 크기와 컬링 마진을 기반으로 뷰포트 경계를 계산합니다.
|
|
1756
|
+
*
|
|
1757
|
+
* @param stage Konva Stage 인스턴스 (width, height 메서드 제공)
|
|
1758
|
+
* @param cullingMargin 컬링 여유 공간 (px)
|
|
1759
|
+
* @param viewportRef 뷰포트 경계를 저장할 ref
|
|
1760
|
+
*
|
|
1761
|
+
* @remarks
|
|
1762
|
+
* - 화면 밖 cullingMargin만큼의 영역까지 포함하여 계산
|
|
1763
|
+
* - 스크롤 시 부드러운 전환을 위해 여유 공간 포함
|
|
1764
|
+
*
|
|
1765
|
+
* @example
|
|
1766
|
+
* ```typescript
|
|
1767
|
+
* updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
1768
|
+
* ```
|
|
1769
|
+
*/
|
|
1770
|
+
var updateViewport = function (stage, cullingMargin, viewportRef) {
|
|
1771
|
+
if (!stage) return;
|
|
1772
|
+
viewportRef.current = {
|
|
1773
|
+
minX: -cullingMargin,
|
|
1774
|
+
maxX: stage.width() + cullingMargin,
|
|
1775
|
+
minY: -cullingMargin,
|
|
1776
|
+
maxY: stage.height() + cullingMargin
|
|
1777
|
+
};
|
|
1778
|
+
};
|
|
1779
|
+
/**
|
|
1780
|
+
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
1781
|
+
*
|
|
1782
|
+
* 뷰포트 컬링을 위한 함수입니다. 바운딩 박스와 뷰포트 경계의 교차를 확인합니다.
|
|
1783
|
+
* 바운딩 박스는 캐시되어 성능을 최적화합니다.
|
|
1784
|
+
*
|
|
1785
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1786
|
+
* @param item 확인할 아이템
|
|
1787
|
+
* @param enableViewportCulling 뷰포트 컬링 활성화 여부
|
|
1788
|
+
* @param viewportRef 뷰포트 경계 ref
|
|
1789
|
+
* @param boundingBoxCacheRef 바운딩 박스 캐시 ref
|
|
1790
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1791
|
+
* @returns 뷰포트 안에 있으면 true, 아니면 false
|
|
1792
|
+
*
|
|
1793
|
+
* @remarks
|
|
1794
|
+
* - 성능: O(1) (캐시 히트 시) 또는 O(바운딩 박스 계산 비용) (캐시 미스 시)
|
|
1795
|
+
* - 바운딩 박스는 자동으로 캐시되어 재사용됨
|
|
1796
|
+
*
|
|
1797
|
+
* @example
|
|
1798
|
+
* ```typescript
|
|
1799
|
+
* const isVisible = isInViewport(
|
|
1800
|
+
* item,
|
|
1801
|
+
* enableViewportCulling,
|
|
1802
|
+
* viewportRef,
|
|
1803
|
+
* boundingBoxCacheRef,
|
|
1804
|
+
* computeBoundingBox
|
|
1805
|
+
* );
|
|
1806
|
+
* ```
|
|
1807
|
+
*/
|
|
1808
|
+
|
|
1809
|
+
var isInViewport = function (item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox) {
|
|
1810
|
+
if (!enableViewportCulling || !viewportRef.current) return true;
|
|
1811
|
+
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
1812
|
+
|
|
1813
|
+
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
1814
|
+
|
|
1815
|
+
if (!bbox) {
|
|
1816
|
+
// 바운딩 박스 계산 (공통 함수 사용)
|
|
1817
|
+
var computed = computeBoundingBox(item);
|
|
1818
|
+
if (!computed) return false;
|
|
1819
|
+
bbox = computed;
|
|
1820
|
+
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
1821
|
+
} // 바운딩 박스와 viewport 교차 체크
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* 지도 이벤트 핸들러 생성 함수
|
|
1829
|
+
*
|
|
1830
|
+
* 지도 이동, 줌, 드래그 등의 이벤트를 처리하는 핸들러들을 생성합니다.
|
|
1831
|
+
*
|
|
1832
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1833
|
+
* @param deps 이벤트 핸들러 생성에 필요한 의존성
|
|
1834
|
+
* @returns 지도 이벤트 핸들러 객체
|
|
1835
|
+
*
|
|
1836
|
+
* @example
|
|
1837
|
+
* ```typescript
|
|
1838
|
+
* const {
|
|
1839
|
+
* handleIdle,
|
|
1840
|
+
* handleZoomStart,
|
|
1841
|
+
* handleZoomEnd,
|
|
1842
|
+
* handleCenterChanged,
|
|
1843
|
+
* handleDragStart,
|
|
1844
|
+
* handleDragEnd,
|
|
1845
|
+
* } = createMapEventHandlers({
|
|
1846
|
+
* controller,
|
|
1847
|
+
* containerRef,
|
|
1848
|
+
* markerRef,
|
|
1849
|
+
* options,
|
|
1850
|
+
* prevCenterOffsetRef,
|
|
1851
|
+
* accumTranslateRef,
|
|
1852
|
+
* offsetCacheRef,
|
|
1853
|
+
* boundingBoxCacheRef,
|
|
1854
|
+
* renderAllImmediate,
|
|
1855
|
+
* });
|
|
1856
|
+
* ```
|
|
1857
|
+
*/
|
|
1858
|
+
|
|
1859
|
+
var createMapEventHandlers = function (deps) {
|
|
1860
|
+
var controller = deps.controller,
|
|
1861
|
+
containerRef = deps.containerRef,
|
|
1862
|
+
markerRef = deps.markerRef,
|
|
1863
|
+
options = deps.options,
|
|
1864
|
+
prevCenterOffsetRef = deps.prevCenterOffsetRef,
|
|
1865
|
+
accumTranslateRef = deps.accumTranslateRef,
|
|
1866
|
+
offsetCacheRef = deps.offsetCacheRef,
|
|
1867
|
+
boundingBoxCacheRef = deps.boundingBoxCacheRef,
|
|
1868
|
+
renderAllImmediate = deps.renderAllImmediate;
|
|
1869
|
+
/**
|
|
1870
|
+
* 지도 이동/줌 완료 시 처리
|
|
1871
|
+
*
|
|
1872
|
+
* - 캐시 초기화: 좌표 변환 결과가 변경되었으므로 캐시 무효화
|
|
1873
|
+
* - 마커 위치 업데이트: 새로운 지도 위치에 맞게 마커 재배치
|
|
1874
|
+
* - 렌더링: 새 위치에서 전체 렌더링 수행
|
|
1875
|
+
*/
|
|
1876
|
+
|
|
1877
|
+
var handleIdle = function () {
|
|
1878
|
+
prevCenterOffsetRef.current = null;
|
|
1879
|
+
accumTranslateRef.current = {
|
|
1880
|
+
x: 0,
|
|
1881
|
+
y: 0
|
|
1882
|
+
}; // 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
1883
|
+
|
|
1884
|
+
offsetCacheRef.current.clear();
|
|
1885
|
+
boundingBoxCacheRef.current.clear(); // 마커 위치 업데이트
|
|
1886
|
+
|
|
1887
|
+
var bounds = controller.getCurrBounds();
|
|
1888
|
+
|
|
1889
|
+
var markerOptions = __assign({
|
|
1890
|
+
position: bounds.nw
|
|
1891
|
+
}, options);
|
|
1892
|
+
|
|
1893
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
1894
|
+
|
|
1895
|
+
if (containerRef.current) {
|
|
1896
|
+
containerRef.current.style.transform = '';
|
|
1897
|
+
containerRef.current.style.visibility = '';
|
|
1898
|
+
} // 새 위치에서 렌더링
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
renderAllImmediate();
|
|
1902
|
+
};
|
|
1903
|
+
/**
|
|
1904
|
+
* 줌 시작 시 처리 (일시적으로 숨김)
|
|
1905
|
+
*/
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
var handleZoomStart = function () {
|
|
1909
|
+
if (containerRef.current) {
|
|
1910
|
+
containerRef.current.style.visibility = 'hidden';
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
/**
|
|
1914
|
+
* 줌 종료 시 처리 (다시 표시)
|
|
1915
|
+
*/
|
|
1916
|
+
|
|
1917
|
+
|
|
1918
|
+
var handleZoomEnd = function () {
|
|
1919
|
+
if (containerRef.current) {
|
|
1920
|
+
containerRef.current.style.visibility = '';
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
/**
|
|
1924
|
+
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
1925
|
+
*/
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
var handleCenterChanged = function () {
|
|
1929
|
+
var center = controller.getCurrBounds().getCenter();
|
|
1930
|
+
var curr = controller.positionToOffset(center);
|
|
1931
|
+
var prev = prevCenterOffsetRef.current;
|
|
1932
|
+
|
|
1933
|
+
if (!prev) {
|
|
1934
|
+
prevCenterOffsetRef.current = {
|
|
1935
|
+
x: curr.x,
|
|
1936
|
+
y: curr.y
|
|
1937
|
+
};
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
var dx = prev.x - curr.x;
|
|
1942
|
+
var dy = prev.y - curr.y;
|
|
1943
|
+
accumTranslateRef.current = {
|
|
1944
|
+
x: accumTranslateRef.current.x + dx,
|
|
1945
|
+
y: accumTranslateRef.current.y + dy
|
|
1946
|
+
};
|
|
1947
|
+
prevCenterOffsetRef.current = {
|
|
1948
|
+
x: curr.x,
|
|
1949
|
+
y: curr.y
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1952
|
+
if (containerRef.current) {
|
|
1953
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
/**
|
|
1957
|
+
* 드래그 시작 처리
|
|
1958
|
+
*/
|
|
1959
|
+
|
|
1960
|
+
|
|
1961
|
+
var handleDragStart = function () {// 커서는 각 컴포넌트에서 처리
|
|
1962
|
+
};
|
|
1963
|
+
/**
|
|
1964
|
+
* 드래그 종료 처리
|
|
1965
|
+
*/
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
var handleDragEnd = function () {// 커서는 각 컴포넌트에서 처리
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
return {
|
|
1972
|
+
handleIdle: handleIdle,
|
|
1973
|
+
handleZoomStart: handleZoomStart,
|
|
1974
|
+
handleZoomEnd: handleZoomEnd,
|
|
1975
|
+
handleCenterChanged: handleCenterChanged,
|
|
1976
|
+
handleDragStart: handleDragStart,
|
|
1977
|
+
handleDragEnd: handleDragEnd
|
|
1978
|
+
};
|
|
1979
|
+
};
|
|
1980
|
+
/**
|
|
1981
|
+
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
1982
|
+
*
|
|
1983
|
+
* Spatial Hash Grid에 모든 데이터의 바운딩 박스를 삽입합니다.
|
|
1984
|
+
* 이를 통해 클릭/호버 시 O(1) 수준의 빠른 Hit Test가 가능합니다.
|
|
1985
|
+
*
|
|
1986
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
1987
|
+
* @param data 공간 인덱스에 삽입할 데이터 배열
|
|
1988
|
+
* @param spatialIndex Spatial Hash Grid 인스턴스
|
|
1989
|
+
* @param computeBoundingBox 바운딩 박스 계산 함수
|
|
1990
|
+
*
|
|
1991
|
+
* @remarks
|
|
1992
|
+
* - 성능: O(n) 시간복잡도, n은 데이터 개수
|
|
1993
|
+
* - 호출 시점: 데이터 변경 시 또는 지도 이동/줌 완료 시
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```typescript
|
|
1997
|
+
* buildSpatialIndex(
|
|
1998
|
+
* dataRef.current,
|
|
1999
|
+
* spatialIndexRef.current,
|
|
2000
|
+
* computeBoundingBox
|
|
2001
|
+
* );
|
|
2002
|
+
* ```
|
|
2003
|
+
*/
|
|
2004
|
+
|
|
2005
|
+
var buildSpatialIndex = function (data, spatialIndex, computeBoundingBox) {
|
|
2006
|
+
spatialIndex.clear();
|
|
2007
|
+
|
|
2008
|
+
for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
|
|
2009
|
+
var item = data_1[_i];
|
|
2010
|
+
var bbox = computeBoundingBox(item);
|
|
2011
|
+
|
|
2012
|
+
if (bbox) {
|
|
2013
|
+
spatialIndex.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
/**
|
|
2018
|
+
* 선택 상태 동기화 유틸리티
|
|
2019
|
+
*
|
|
2020
|
+
* 데이터 변경 시 선택된 항목의 참조를 최신 데이터로 업데이트합니다.
|
|
2021
|
+
* 화면 밖에 있는 선택된 항목도 선택 상태를 유지합니다.
|
|
2022
|
+
*
|
|
2023
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
2024
|
+
* @param data 최신 데이터 배열
|
|
2025
|
+
* @param selectedIds 선택된 항목 ID Set
|
|
2026
|
+
* @param selectedItemsMap 현재 선택된 항목 Map
|
|
2027
|
+
* @returns 업데이트된 선택된 항목 Map
|
|
2028
|
+
*
|
|
2029
|
+
* @remarks
|
|
2030
|
+
* - 성능: O(n + m), n은 전체 데이터 수, m은 선택된 항목 수
|
|
2031
|
+
* - 화면 밖 데이터도 선택 상태 유지 (최신 데이터가 없으면 기존 데이터 유지)
|
|
2032
|
+
*
|
|
2033
|
+
* @example
|
|
2034
|
+
* ```typescript
|
|
2035
|
+
* selectedItemsMapRef.current = syncSelectedItems(
|
|
2036
|
+
* data,
|
|
2037
|
+
* selectedIdsRef.current,
|
|
2038
|
+
* selectedItemsMapRef.current
|
|
2039
|
+
* );
|
|
2040
|
+
* ```
|
|
2041
|
+
*/
|
|
2042
|
+
|
|
2043
|
+
var syncSelectedItems = function (data, selectedIds, selectedItemsMap) {
|
|
2044
|
+
var dataMap = new Map(data.map(function (m) {
|
|
2045
|
+
return [m.id, m];
|
|
2046
|
+
}));
|
|
2047
|
+
var newSelectedItemsMap = new Map();
|
|
2048
|
+
selectedIds.forEach(function (id) {
|
|
2049
|
+
// 현재 data에 있으면 최신 데이터 사용
|
|
2050
|
+
var currentItem = dataMap.get(id);
|
|
2051
|
+
|
|
2052
|
+
if (currentItem) {
|
|
2053
|
+
newSelectedItemsMap.set(id, currentItem);
|
|
2054
|
+
} else {
|
|
2055
|
+
// 화면 밖이면 기존 데이터 유지
|
|
2056
|
+
var prevItem = selectedItemsMap.get(id);
|
|
2057
|
+
|
|
2058
|
+
if (prevItem) {
|
|
2059
|
+
newSelectedItemsMap.set(id, prevItem);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
return newSelectedItemsMap;
|
|
2064
|
+
};
|
|
2065
|
+
/**
|
|
2066
|
+
* 외부 selectedItems를 내부 상태로 동기화
|
|
2067
|
+
*
|
|
2068
|
+
* 외부에서 전달된 selectedItems prop을 내부 ref 상태로 동기화합니다.
|
|
2069
|
+
*
|
|
2070
|
+
* @template T 마커/폴리곤 데이터의 추가 속성 타입
|
|
2071
|
+
* @param externalSelectedItems 외부에서 전달된 선택된 항목 배열 (undefined면 동기화 안 함)
|
|
2072
|
+
* @param selectedIdsRef 선택된 ID Set ref
|
|
2073
|
+
* @param selectedItemsMapRef 선택된 항목 Map ref
|
|
2074
|
+
*
|
|
2075
|
+
* @remarks
|
|
2076
|
+
* - externalSelectedItems가 undefined면 외부 제어가 아니므로 아무 작업도 하지 않음
|
|
2077
|
+
* - 성능: O(m), m은 externalSelectedItems의 길이
|
|
2078
|
+
*
|
|
2079
|
+
* @example
|
|
2080
|
+
* ```typescript
|
|
2081
|
+
* useEffect(() => {
|
|
2082
|
+
* syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
2083
|
+
* // 렌더링...
|
|
2084
|
+
* }, [externalSelectedItems]);
|
|
2085
|
+
* ```
|
|
2086
|
+
*/
|
|
2087
|
+
|
|
2088
|
+
var syncExternalSelectedItems = function (externalSelectedItems, selectedIdsRef, selectedItemsMapRef) {
|
|
2089
|
+
if (externalSelectedItems === undefined) return;
|
|
2090
|
+
var newSelectedIds = new Set();
|
|
2091
|
+
var newSelectedItemsMap = new Map();
|
|
2092
|
+
externalSelectedItems.forEach(function (item) {
|
|
2093
|
+
newSelectedIds.add(item.id);
|
|
2094
|
+
newSelectedItemsMap.set(item.id, item);
|
|
2095
|
+
});
|
|
2096
|
+
selectedIdsRef.current = newSelectedIds;
|
|
2097
|
+
selectedItemsMapRef.current = newSelectedItemsMap;
|
|
2098
|
+
};
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* 이벤트 유효성 검증 헬퍼
|
|
2102
|
+
*
|
|
2103
|
+
* @param event 이벤트 파라미터
|
|
2104
|
+
* @param context Context가 있는지 여부
|
|
2105
|
+
* @param controller MintMapController 인스턴스
|
|
2106
|
+
* @returns 유효한 offset 또는 null
|
|
2107
|
+
*
|
|
2108
|
+
* @remarks
|
|
2109
|
+
* Context가 있으면 전역 이벤트 핸들러가 처리하므로 로컬 핸들러는 스킵
|
|
2110
|
+
*/
|
|
2111
|
+
var validateEvent = function (event, context, controller) {
|
|
2112
|
+
var _a; // Context가 있으면 전역 핸들러가 처리
|
|
2113
|
+
|
|
2114
|
+
|
|
2115
|
+
if (context) return null; // 이벤트 파라미터 검증
|
|
2116
|
+
|
|
2117
|
+
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return null;
|
|
2118
|
+
|
|
2119
|
+
try {
|
|
2120
|
+
return controller.positionToOffset(event.param.position);
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
console.error('[WoongCanvas] validateEvent error:', error);
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
/**
|
|
2127
|
+
* Map의 values를 배열로 변환 (최적화 버전)
|
|
2128
|
+
*
|
|
2129
|
+
* @param map 변환할 Map
|
|
2130
|
+
* @returns Map의 값 배열
|
|
2131
|
+
*
|
|
2132
|
+
* @remarks
|
|
2133
|
+
* Map.values()는 IterableIterator를 반환하므로 배열 변환이 필요할 때 사용합니다.
|
|
2134
|
+
* 성능: O(n) 시간복잡도
|
|
2135
|
+
*
|
|
2136
|
+
* 최적화: Array.from을 사용하되, 크기를 미리 할당하여 메모리 재할당 최소화
|
|
2137
|
+
*/
|
|
2138
|
+
|
|
2139
|
+
var mapValuesToArray = function (map) {
|
|
2140
|
+
// Map이 비어있으면 빈 배열 반환 (메모리 할당 최소화)
|
|
2141
|
+
if (map.size === 0) return []; // Array.from 사용 (TypeScript 컴파일러 호환성)
|
|
2142
|
+
|
|
2143
|
+
return Array.from(map.values());
|
|
2144
|
+
};
|
|
2145
|
+
|
|
1484
2146
|
var cn$3 = classNames.bind(styles$1);
|
|
1485
2147
|
function MintMapCore(_a) {
|
|
1486
2148
|
var _this = this;
|
|
@@ -5546,37 +6208,16 @@ var WoongCanvasMarker = function (props) {
|
|
|
5546
6208
|
* 현재 뷰포트 영역 계산
|
|
5547
6209
|
*/
|
|
5548
6210
|
|
|
5549
|
-
var updateViewport = function () {
|
|
5550
|
-
|
|
5551
|
-
var stage = stageRef.current;
|
|
5552
|
-
viewportRef.current = {
|
|
5553
|
-
minX: -cullingMargin,
|
|
5554
|
-
maxX: stage.width() + cullingMargin,
|
|
5555
|
-
minY: -cullingMargin,
|
|
5556
|
-
maxY: stage.height() + cullingMargin
|
|
5557
|
-
};
|
|
6211
|
+
var updateViewport$1 = function () {
|
|
6212
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
5558
6213
|
};
|
|
5559
6214
|
/**
|
|
5560
6215
|
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
5561
6216
|
*/
|
|
5562
6217
|
|
|
5563
6218
|
|
|
5564
|
-
var isInViewport = function (item) {
|
|
5565
|
-
|
|
5566
|
-
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
5567
|
-
|
|
5568
|
-
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
5569
|
-
|
|
5570
|
-
if (!bbox) {
|
|
5571
|
-
// 바운딩 박스 계산 (공통 함수 사용)
|
|
5572
|
-
var computed = computeBoundingBox(item);
|
|
5573
|
-
if (!computed) return false;
|
|
5574
|
-
bbox = computed;
|
|
5575
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
5576
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
6219
|
+
var isInViewport$1 = function (item) {
|
|
6220
|
+
return isInViewport(item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
5580
6221
|
}; // --------------------------------------------------------------------------
|
|
5581
6222
|
// 유틸리티 함수: 좌표 변환 캐싱
|
|
5582
6223
|
// --------------------------------------------------------------------------
|
|
@@ -5606,12 +6247,23 @@ var WoongCanvasMarker = function (props) {
|
|
|
5606
6247
|
/**
|
|
5607
6248
|
* 마커의 바운딩 박스 계산
|
|
5608
6249
|
*
|
|
5609
|
-
*
|
|
5610
|
-
*
|
|
5611
|
-
* - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
|
|
6250
|
+
* 마커의 화면 상 위치와 크기를 기반으로 바운딩 박스를 계산합니다.
|
|
6251
|
+
* Viewport Culling에 사용되며, tailHeight를 포함하여 전체 표시 영역을 계산합니다.
|
|
5612
6252
|
*
|
|
5613
6253
|
* @param item 마커 데이터
|
|
5614
|
-
* @returns 바운딩 박스 또는 null
|
|
6254
|
+
* @returns 바운딩 박스 (minX, minY, maxX, maxY) 또는 null (좌표 변환 실패 시)
|
|
6255
|
+
*
|
|
6256
|
+
* @remarks
|
|
6257
|
+
* - **boxHeight**: 마커 본체만 포함 (Hit Test 영역)
|
|
6258
|
+
* - **tailHeight**: 마커 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역 포함)
|
|
6259
|
+
* - 바운딩 박스는 캐시되어 성능 최적화
|
|
6260
|
+
*
|
|
6261
|
+
* @example
|
|
6262
|
+
* ```typescript
|
|
6263
|
+
* const bbox = computeBoundingBox(item);
|
|
6264
|
+
* if (!bbox) return; // 계산 실패
|
|
6265
|
+
* // bbox.minX, bbox.minY, bbox.maxX, bbox.maxY 사용
|
|
6266
|
+
* ```
|
|
5615
6267
|
*/
|
|
5616
6268
|
|
|
5617
6269
|
|
|
@@ -5635,29 +6287,32 @@ var WoongCanvasMarker = function (props) {
|
|
|
5635
6287
|
|
|
5636
6288
|
/**
|
|
5637
6289
|
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
6290
|
+
*
|
|
6291
|
+
* 모든 마커의 바운딩 박스를 Spatial Hash Grid에 삽입합니다.
|
|
6292
|
+
* 이를 통해 클릭/호버 시 해당 위치 주변의 마커만 빠르게 조회할 수 있습니다.
|
|
6293
|
+
*
|
|
6294
|
+
* @remarks
|
|
6295
|
+
* - 호출 시점: 데이터 변경 시 또는 지도 이동/줌 완료 시
|
|
6296
|
+
* - 성능: O(n) 시간복잡도, n은 마커 개수
|
|
6297
|
+
* - Hit Test 성능: O(1) 수준 (30,000개 → ~10개 후보만 체크)
|
|
5638
6298
|
*/
|
|
5639
6299
|
|
|
5640
6300
|
|
|
5641
|
-
var buildSpatialIndex = function () {
|
|
5642
|
-
|
|
5643
|
-
spatial.clear();
|
|
5644
|
-
var currentData = dataRef.current;
|
|
5645
|
-
|
|
5646
|
-
for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
|
|
5647
|
-
var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
|
|
5648
|
-
|
|
5649
|
-
var bbox = computeBoundingBox(item);
|
|
5650
|
-
|
|
5651
|
-
if (bbox) {
|
|
5652
|
-
spatial.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
5653
|
-
}
|
|
5654
|
-
}
|
|
6301
|
+
var buildSpatialIndex$1 = function () {
|
|
6302
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
5655
6303
|
}; // --------------------------------------------------------------------------
|
|
5656
|
-
// 렌더링 함수 결정
|
|
6304
|
+
// 렌더링 함수 결정
|
|
5657
6305
|
// --------------------------------------------------------------------------
|
|
5658
6306
|
|
|
5659
6307
|
/**
|
|
5660
6308
|
* 외부 렌더링 함수에 전달할 유틸리티 객체
|
|
6309
|
+
*
|
|
6310
|
+
* 커스텀 렌더링 함수(renderBase, renderAnimation, renderEvent)에서 사용할
|
|
6311
|
+
* 좌표 변환 등의 헬퍼 함수들을 제공합니다.
|
|
6312
|
+
*
|
|
6313
|
+
* @remarks
|
|
6314
|
+
* - getOrComputeMarkerOffset: 마커 좌표 변환 (자동 캐싱)
|
|
6315
|
+
* - getOrComputePolygonOffsets: 폴리곤 좌표 변환 (마커에서는 사용 안 함)
|
|
5661
6316
|
*/
|
|
5662
6317
|
|
|
5663
6318
|
|
|
@@ -5699,7 +6354,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
5699
6354
|
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
5700
6355
|
|
|
5701
6356
|
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
5702
|
-
return isInViewport(item);
|
|
6357
|
+
return isInViewport$1(item);
|
|
5703
6358
|
}) : dataRef.current; // topOnHover가 true이고 renderEvent가 없으면 Base Layer에서 hover 처리
|
|
5704
6359
|
|
|
5705
6360
|
if (topOnHover && !renderEvent && hovered) {
|
|
@@ -5719,7 +6374,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
5719
6374
|
}); // hover된 항목을 최상단에 렌더링 (renderEvent가 없을 때만)
|
|
5720
6375
|
|
|
5721
6376
|
if (topOnHover && !renderEvent && hovered) {
|
|
5722
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
6377
|
+
var isHoveredInViewport = enableViewportCulling ? isInViewport$1(hovered) : true;
|
|
5723
6378
|
|
|
5724
6379
|
if (isHoveredInViewport) {
|
|
5725
6380
|
renderBase({
|
|
@@ -5745,7 +6400,11 @@ var WoongCanvasMarker = function (props) {
|
|
|
5745
6400
|
/**
|
|
5746
6401
|
* Animation 레이어 렌더링 (선택된 마커 애니메이션)
|
|
5747
6402
|
*
|
|
5748
|
-
*
|
|
6403
|
+
* 선택된 마커에 대한 애니메이션 효과를 렌더링합니다.
|
|
6404
|
+
* renderAnimation prop이 제공된 경우에만 실행됩니다.
|
|
6405
|
+
*
|
|
6406
|
+
* @remarks
|
|
6407
|
+
* - **성능 최적화**: sceneFunc 내부에서 최신 items 참조
|
|
5749
6408
|
* - 선택 변경 시에만 재생성
|
|
5750
6409
|
* - 지도 이동 시에는 기존 Animation 계속 실행
|
|
5751
6410
|
*/
|
|
@@ -5765,10 +6424,16 @@ var WoongCanvasMarker = function (props) {
|
|
|
5765
6424
|
/**
|
|
5766
6425
|
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
5767
6426
|
*
|
|
5768
|
-
*
|
|
5769
|
-
*
|
|
5770
|
-
*
|
|
5771
|
-
*
|
|
6427
|
+
* 마커의 hover 효과 및 선택 상태를 표시합니다.
|
|
6428
|
+
* renderEvent prop이 제공된 경우에만 실행됩니다.
|
|
6429
|
+
*
|
|
6430
|
+
* @remarks
|
|
6431
|
+
* - **성능 최적화**:
|
|
6432
|
+
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
6433
|
+
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
6434
|
+
* 3. 클로저로 최신 데이터 참조
|
|
6435
|
+
* - **topOnHover 지원**: hover된 항목을 최상단에 렌더링
|
|
6436
|
+
* - 선택된 항목은 Map에서 O(1)로 조회하여 성능 최적화
|
|
5772
6437
|
*/
|
|
5773
6438
|
|
|
5774
6439
|
|
|
@@ -5786,8 +6451,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
5786
6451
|
name: 'event-render-shape',
|
|
5787
6452
|
sceneFunc: function (context, shape) {
|
|
5788
6453
|
var ctx = context; // 클로저로 최신 ref 값 참조
|
|
6454
|
+
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
5789
6455
|
|
|
5790
|
-
var selectedItems =
|
|
6456
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
5791
6457
|
var hovered = hoveredItemRef.current; // topOnHover가 true이면 hover된 항목을 최상단에 렌더링
|
|
5792
6458
|
|
|
5793
6459
|
if (topOnHover && hovered) {
|
|
@@ -5802,7 +6468,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
5802
6468
|
selectedItem: selectedItemRef.current
|
|
5803
6469
|
}); // 2. hover된 항목을 최상단에 렌더링
|
|
5804
6470
|
|
|
5805
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
6471
|
+
var isHoveredInViewport = enableViewportCulling ? isInViewport$1(hovered) : true;
|
|
5806
6472
|
|
|
5807
6473
|
if (isHoveredInViewport) {
|
|
5808
6474
|
// hover된 항목이 선택되어 있다면 hoverSelectedItems에 포함시켜서
|
|
@@ -5846,8 +6512,8 @@ var WoongCanvasMarker = function (props) {
|
|
|
5846
6512
|
|
|
5847
6513
|
|
|
5848
6514
|
var renderAllImmediate = function () {
|
|
5849
|
-
updateViewport();
|
|
5850
|
-
buildSpatialIndex();
|
|
6515
|
+
updateViewport$1();
|
|
6516
|
+
buildSpatialIndex$1();
|
|
5851
6517
|
doRenderBase();
|
|
5852
6518
|
doRenderAnimation();
|
|
5853
6519
|
doRenderEvent();
|
|
@@ -5855,89 +6521,43 @@ var WoongCanvasMarker = function (props) {
|
|
|
5855
6521
|
// 이벤트 핸들러: 지도 이벤트
|
|
5856
6522
|
// --------------------------------------------------------------------------
|
|
5857
6523
|
|
|
5858
|
-
/**
|
|
5859
|
-
* 지도 이동/줌 완료 시 처리
|
|
5860
|
-
*/
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
var handleIdle = function () {
|
|
5864
|
-
prevCenterOffsetRef.current = null;
|
|
5865
|
-
accumTranslateRef.current = {
|
|
5866
|
-
x: 0,
|
|
5867
|
-
y: 0
|
|
5868
|
-
}; // 2. 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
5869
|
-
|
|
5870
|
-
offsetCacheRef.current.clear();
|
|
5871
|
-
boundingBoxCacheRef.current.clear(); // 3. 마커 위치 업데이트
|
|
5872
|
-
|
|
5873
|
-
var bounds = controller.getCurrBounds();
|
|
5874
|
-
|
|
5875
|
-
var markerOptions = __assign({
|
|
5876
|
-
position: bounds.nw
|
|
5877
|
-
}, options);
|
|
5878
|
-
|
|
5879
|
-
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 4. transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
5880
6524
|
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
};
|
|
6525
|
+
var _g = createMapEventHandlers({
|
|
6526
|
+
controller: controller,
|
|
6527
|
+
containerRef: containerRef,
|
|
6528
|
+
markerRef: markerRef,
|
|
6529
|
+
options: options,
|
|
6530
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
6531
|
+
accumTranslateRef: accumTranslateRef,
|
|
6532
|
+
offsetCacheRef: offsetCacheRef,
|
|
6533
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
6534
|
+
renderAllImmediate: renderAllImmediate
|
|
6535
|
+
}),
|
|
6536
|
+
handleIdle = _g.handleIdle,
|
|
6537
|
+
handleZoomStart = _g.handleZoomStart,
|
|
6538
|
+
handleZoomEnd = _g.handleZoomEnd,
|
|
6539
|
+
handleCenterChanged = _g.handleCenterChanged,
|
|
6540
|
+
handleDragStartShared = _g.handleDragStart,
|
|
6541
|
+
handleDragEndShared = _g.handleDragEnd;
|
|
5899
6542
|
/**
|
|
5900
|
-
*
|
|
5901
|
-
*/
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
var handleZoomEnd = function () {
|
|
5905
|
-
if (containerRef.current) {
|
|
5906
|
-
containerRef.current.style.visibility = '';
|
|
5907
|
-
}
|
|
5908
|
-
};
|
|
5909
|
-
/**
|
|
5910
|
-
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
5911
|
-
*/
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
var handleCenterChanged = function () {
|
|
5915
|
-
var center = controller.getCurrBounds().getCenter();
|
|
5916
|
-
var curr = controller.positionToOffset(center);
|
|
5917
|
-
var prev = prevCenterOffsetRef.current;
|
|
5918
|
-
|
|
5919
|
-
if (!prev) {
|
|
5920
|
-
prevCenterOffsetRef.current = {
|
|
5921
|
-
x: curr.x,
|
|
5922
|
-
y: curr.y
|
|
5923
|
-
};
|
|
5924
|
-
return;
|
|
5925
|
-
}
|
|
6543
|
+
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
6544
|
+
*/
|
|
5926
6545
|
|
|
5927
|
-
var dx = prev.x - curr.x;
|
|
5928
|
-
var dy = prev.y - curr.y;
|
|
5929
|
-
accumTranslateRef.current = {
|
|
5930
|
-
x: accumTranslateRef.current.x + dx,
|
|
5931
|
-
y: accumTranslateRef.current.y + dy
|
|
5932
|
-
};
|
|
5933
|
-
prevCenterOffsetRef.current = {
|
|
5934
|
-
x: curr.x,
|
|
5935
|
-
y: curr.y
|
|
5936
|
-
};
|
|
5937
6546
|
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
6547
|
+
var handleDragStart = function () {
|
|
6548
|
+
handleDragStartShared();
|
|
6549
|
+
draggingRef.current = true;
|
|
6550
|
+
controller.setMapCursor('grabbing');
|
|
6551
|
+
};
|
|
6552
|
+
/**
|
|
6553
|
+
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
6554
|
+
*/
|
|
6555
|
+
|
|
6556
|
+
|
|
6557
|
+
var handleDragEnd = function () {
|
|
6558
|
+
handleDragEndShared();
|
|
6559
|
+
draggingRef.current = false;
|
|
6560
|
+
controller.setMapCursor('grab');
|
|
5941
6561
|
}; // --------------------------------------------------------------------------
|
|
5942
6562
|
// Hit Test & 상태 관리
|
|
5943
6563
|
// --------------------------------------------------------------------------
|
|
@@ -5945,12 +6565,26 @@ var WoongCanvasMarker = function (props) {
|
|
|
5945
6565
|
/**
|
|
5946
6566
|
* 특정 좌표의 마커 데이터 찾기 (Spatial Index 사용)
|
|
5947
6567
|
*
|
|
5948
|
-
*
|
|
5949
|
-
*
|
|
5950
|
-
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
6568
|
+
* 클릭/호버 이벤트 시 해당 위치에 있는 마커를 찾습니다.
|
|
6569
|
+
* Spatial Hash Grid를 사용하여 O(1) 수준의 빠른 Hit Test를 수행합니다.
|
|
5951
6570
|
*
|
|
5952
|
-
* @param offset 검사할 좌표
|
|
5953
|
-
* @returns 찾은 마커 데이터 또는 null
|
|
6571
|
+
* @param offset 검사할 화면 좌표 (픽셀 단위)
|
|
6572
|
+
* @returns 찾은 마커 데이터 또는 null (없으면)
|
|
6573
|
+
*
|
|
6574
|
+
* @remarks
|
|
6575
|
+
* - **topOnHover 지원**: topOnHover가 true일 때 현재 hover된 항목을 최우선으로 체크
|
|
6576
|
+
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
6577
|
+
* - **성능**: O(후보 항목 수) - 보통 ~10개 (30,000개 전체를 체크하지 않음)
|
|
6578
|
+
* - Spatial Index를 통해 해당 위치 주변의 후보만 추출 후 정확한 Hit Test 수행
|
|
6579
|
+
*
|
|
6580
|
+
* @example
|
|
6581
|
+
* ```typescript
|
|
6582
|
+
* const clickedOffset = controller.positionToOffset(event.param.position);
|
|
6583
|
+
* const data = findData(clickedOffset);
|
|
6584
|
+
* if (data) {
|
|
6585
|
+
* // 마커를 찾음
|
|
6586
|
+
* }
|
|
6587
|
+
* ```
|
|
5954
6588
|
*/
|
|
5955
6589
|
|
|
5956
6590
|
|
|
@@ -6011,11 +6645,17 @@ var WoongCanvasMarker = function (props) {
|
|
|
6011
6645
|
/**
|
|
6012
6646
|
* 클릭 처리 (단일/다중 선택)
|
|
6013
6647
|
*
|
|
6014
|
-
*
|
|
6648
|
+
* 마커 클릭 시 선택 상태를 업데이트하고 렌더링을 수행합니다.
|
|
6015
6649
|
*
|
|
6016
|
-
*
|
|
6017
|
-
*
|
|
6018
|
-
*
|
|
6650
|
+
* @param data 클릭된 마커 데이터
|
|
6651
|
+
*
|
|
6652
|
+
* @remarks
|
|
6653
|
+
* - **단일 선택**: 기존 선택 해제 후 새로 선택 (토글 가능)
|
|
6654
|
+
* - **다중 선택**: enableMultiSelect가 true면 기존 선택 유지하며 추가/제거
|
|
6655
|
+
* - **성능 최적화**:
|
|
6656
|
+
* - 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
6657
|
+
* - sceneFunc에서 selectedIds를 체크하여 선택된 마커만 스킵
|
|
6658
|
+
* - 객체 생성 오버헤드 제거로 1,000개 이상도 부드럽게 처리
|
|
6019
6659
|
*/
|
|
6020
6660
|
|
|
6021
6661
|
|
|
@@ -6064,75 +6704,57 @@ var WoongCanvasMarker = function (props) {
|
|
|
6064
6704
|
|
|
6065
6705
|
/**
|
|
6066
6706
|
* 클릭 이벤트 처리
|
|
6707
|
+
*
|
|
6708
|
+
* @param event 클릭 이벤트 파라미터
|
|
6709
|
+
*
|
|
6710
|
+
* @remarks
|
|
6711
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
6712
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
6713
|
+
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
6067
6714
|
*/
|
|
6068
6715
|
|
|
6069
6716
|
|
|
6070
6717
|
var handleClick = function (event) {
|
|
6071
|
-
var _a;
|
|
6072
|
-
|
|
6073
6718
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
6074
6719
|
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
var clickedOffset = controller.positionToOffset(event.param.position);
|
|
6079
|
-
var data_1 = findData(clickedOffset);
|
|
6720
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
6721
|
+
if (!clickedOffset) return;
|
|
6722
|
+
var data = findData(clickedOffset);
|
|
6080
6723
|
|
|
6081
|
-
|
|
6082
|
-
|
|
6724
|
+
if (data) {
|
|
6725
|
+
handleLocalClick(data);
|
|
6083
6726
|
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
}
|
|
6727
|
+
if (onClick) {
|
|
6728
|
+
onClick(data, selectedIdsRef.current);
|
|
6087
6729
|
}
|
|
6088
|
-
} catch (error) {
|
|
6089
|
-
console.error('[WoongCanvasMarker] handleClick error:', error);
|
|
6090
6730
|
}
|
|
6091
6731
|
};
|
|
6092
6732
|
/**
|
|
6093
6733
|
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
6734
|
+
*
|
|
6735
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
6736
|
+
*
|
|
6737
|
+
* @remarks
|
|
6738
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
6739
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
6740
|
+
* - hover 상태 변경 시에만 렌더링 및 콜백 호출 (최적화)
|
|
6094
6741
|
*/
|
|
6095
6742
|
|
|
6096
6743
|
|
|
6097
6744
|
var handleMouseMove = function (event) {
|
|
6098
|
-
var _a;
|
|
6099
|
-
|
|
6100
6745
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
6101
6746
|
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
var hoveredItem = findData(mouseOffset);
|
|
6107
|
-
var prevHovered = hoveredItemRef.current;
|
|
6747
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
6748
|
+
if (!mouseOffset) return;
|
|
6749
|
+
var hoveredItem = findData(mouseOffset);
|
|
6750
|
+
var prevHovered = hoveredItemRef.current;
|
|
6108
6751
|
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
}
|
|
6114
|
-
} catch (error) {
|
|
6115
|
-
console.error('[WoongCanvasMarker] handleMouseMove error:', error);
|
|
6752
|
+
if (prevHovered !== hoveredItem) {
|
|
6753
|
+
setHovered(hoveredItem);
|
|
6754
|
+
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
6755
|
+
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
6116
6756
|
}
|
|
6117
6757
|
};
|
|
6118
|
-
/**
|
|
6119
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
6120
|
-
*/
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
var handleDragStart = function () {
|
|
6124
|
-
draggingRef.current = true;
|
|
6125
|
-
controller.setMapCursor('grabbing');
|
|
6126
|
-
};
|
|
6127
|
-
/**
|
|
6128
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
6129
|
-
*/
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
var handleDragEnd = function () {
|
|
6133
|
-
draggingRef.current = false;
|
|
6134
|
-
controller.setMapCursor('grab');
|
|
6135
|
-
};
|
|
6136
6758
|
/**
|
|
6137
6759
|
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
6138
6760
|
*/
|
|
@@ -6227,7 +6849,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
6227
6849
|
|
|
6228
6850
|
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
6229
6851
|
|
|
6230
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
6852
|
+
updateViewport$1(); // ResizeObserver (맵 크기 변경 감지)
|
|
6231
6853
|
|
|
6232
6854
|
var resizeRafId = null;
|
|
6233
6855
|
var resizeObserver = new ResizeObserver(function () {
|
|
@@ -6241,7 +6863,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
6241
6863
|
stage.height(mapDiv.offsetHeight);
|
|
6242
6864
|
offsetCacheRef.current.clear();
|
|
6243
6865
|
boundingBoxCacheRef.current.clear();
|
|
6244
|
-
updateViewport();
|
|
6866
|
+
updateViewport$1();
|
|
6245
6867
|
renderAllImmediate();
|
|
6246
6868
|
resizeRafId = null;
|
|
6247
6869
|
});
|
|
@@ -6330,18 +6952,8 @@ var WoongCanvasMarker = function (props) {
|
|
|
6330
6952
|
// --------------------------------------------------------------------------
|
|
6331
6953
|
|
|
6332
6954
|
useEffect(function () {
|
|
6333
|
-
if (!stageRef.current) return;
|
|
6334
|
-
|
|
6335
|
-
if (externalSelectedItems === undefined) return; // 외부에서 전달된 selectedItems로 동기화
|
|
6336
|
-
|
|
6337
|
-
var newSelectedIds = new Set();
|
|
6338
|
-
var newSelectedItemsMap = new Map();
|
|
6339
|
-
externalSelectedItems.forEach(function (item) {
|
|
6340
|
-
newSelectedIds.add(item.id);
|
|
6341
|
-
newSelectedItemsMap.set(item.id, item);
|
|
6342
|
-
});
|
|
6343
|
-
selectedIdsRef.current = newSelectedIds;
|
|
6344
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 렌더링
|
|
6955
|
+
if (!stageRef.current) return;
|
|
6956
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef); // 렌더링
|
|
6345
6957
|
|
|
6346
6958
|
doRenderBase();
|
|
6347
6959
|
doRenderAnimation();
|
|
@@ -6392,27 +7004,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
6392
7004
|
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
6393
7005
|
*/
|
|
6394
7006
|
|
|
6395
|
-
|
|
6396
|
-
return [m.id, m];
|
|
6397
|
-
}));
|
|
6398
|
-
var newSelectedItemsMap = new Map();
|
|
6399
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
6400
|
-
// 현재 data에 있으면 최신 데이터 사용
|
|
6401
|
-
var currentItem = dataMap.get(id);
|
|
6402
|
-
|
|
6403
|
-
if (currentItem) {
|
|
6404
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
6405
|
-
} else {
|
|
6406
|
-
// 화면 밖이면 기존 데이터 유지
|
|
6407
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
6408
|
-
|
|
6409
|
-
if (prevItem) {
|
|
6410
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
6411
|
-
}
|
|
6412
|
-
}
|
|
6413
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
|
|
6414
|
-
|
|
6415
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
7007
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current); // 즉시 렌더링
|
|
6416
7008
|
|
|
6417
7009
|
renderAllImmediate();
|
|
6418
7010
|
}, [data]);
|
|
@@ -6430,10 +7022,35 @@ var WoongCanvasMarker = function (props) {
|
|
|
6430
7022
|
* 폴리곤 렌더링 유틸리티
|
|
6431
7023
|
*
|
|
6432
7024
|
* 이 파일은 폴리곤 렌더링을 위한 헬퍼 함수와 팩토리 함수를 제공합니다.
|
|
7025
|
+
* GeoJSON MultiPolygon 형식을 지원하며, 도넛 폴리곤(구멍이 있는 폴리곤)도 처리할 수 있습니다.
|
|
6433
7026
|
*/
|
|
6434
7027
|
|
|
6435
7028
|
/**
|
|
6436
7029
|
* 폴리곤 그리기 헬퍼 함수 (도넛 폴리곤 지원)
|
|
7030
|
+
*
|
|
7031
|
+
* Canvas 2D Context를 사용하여 폴리곤을 그립니다.
|
|
7032
|
+
* 도넛 폴리곤의 경우 evenodd fill rule을 사용하여 구멍을 처리합니다.
|
|
7033
|
+
*
|
|
7034
|
+
* @param params 폴리곤 그리기 파라미터
|
|
7035
|
+
*
|
|
7036
|
+
* @remarks
|
|
7037
|
+
* - **도넛 폴리곤 처리**:
|
|
7038
|
+
* - 외부 폴리곤과 내부 구멍들을 같은 path에 추가
|
|
7039
|
+
* - `fill('evenodd')`를 사용하여 구멍 뚫기
|
|
7040
|
+
* - **일반 폴리곤 처리**: 각 폴리곤 그룹을 개별적으로 그리기
|
|
7041
|
+
* - **성능**: O(n), n은 폴리곤의 총 좌표 수
|
|
7042
|
+
*
|
|
7043
|
+
* @example
|
|
7044
|
+
* ```typescript
|
|
7045
|
+
* drawPolygon({
|
|
7046
|
+
* ctx,
|
|
7047
|
+
* polygonOffsets: [[[[100, 200], [200, 200], [200, 100], [100, 100]]]],
|
|
7048
|
+
* isDonutPolygon: false,
|
|
7049
|
+
* fillColor: 'rgba(255, 0, 0, 0.5)',
|
|
7050
|
+
* strokeColor: 'rgba(255, 0, 0, 1)',
|
|
7051
|
+
* lineWidth: 2
|
|
7052
|
+
* });
|
|
7053
|
+
* ```
|
|
6437
7054
|
*/
|
|
6438
7055
|
var drawPolygon = function (_a) {
|
|
6439
7056
|
var ctx = _a.ctx,
|
|
@@ -6447,13 +7064,16 @@ var drawPolygon = function (_a) {
|
|
|
6447
7064
|
var multiPolygon = polygonOffsets_1[_i];
|
|
6448
7065
|
|
|
6449
7066
|
if (isDonutPolygon) {
|
|
6450
|
-
|
|
7067
|
+
// 도넛 폴리곤 처리: 외부 폴리곤 + 내부 구멍들을 같은 path에 추가
|
|
7068
|
+
ctx.beginPath(); // 1. 외부 폴리곤 그리기 (첫 번째 폴리곤)
|
|
6451
7069
|
|
|
6452
|
-
|
|
6453
|
-
|
|
7070
|
+
var outerPolygon = multiPolygon[0];
|
|
7071
|
+
|
|
7072
|
+
if (outerPolygon && outerPolygon.length > 0) {
|
|
7073
|
+
ctx.moveTo(outerPolygon[0][0], outerPolygon[0][1]);
|
|
6454
7074
|
|
|
6455
|
-
for (var i = 1; i <
|
|
6456
|
-
ctx.lineTo(
|
|
7075
|
+
for (var i = 1; i < outerPolygon.length; i++) {
|
|
7076
|
+
ctx.lineTo(outerPolygon[i][0], outerPolygon[i][1]);
|
|
6457
7077
|
}
|
|
6458
7078
|
|
|
6459
7079
|
ctx.closePath();
|
|
@@ -6480,7 +7100,7 @@ var drawPolygon = function (_a) {
|
|
|
6480
7100
|
ctx.lineWidth = lineWidth;
|
|
6481
7101
|
ctx.stroke();
|
|
6482
7102
|
} else {
|
|
6483
|
-
// 일반 폴리곤
|
|
7103
|
+
// 일반 폴리곤 처리: 각 폴리곤 그룹을 개별적으로 그리기
|
|
6484
7104
|
for (var _b = 0, multiPolygon_1 = multiPolygon; _b < multiPolygon_1.length; _b++) {
|
|
6485
7105
|
var polygonGroup = multiPolygon_1[_b];
|
|
6486
7106
|
if (!polygonGroup.length) continue;
|
|
@@ -6493,7 +7113,8 @@ var drawPolygon = function (_a) {
|
|
|
6493
7113
|
ctx.lineTo(point[0], point[1]);
|
|
6494
7114
|
}
|
|
6495
7115
|
|
|
6496
|
-
ctx.closePath();
|
|
7116
|
+
ctx.closePath(); // 스타일 설정 및 렌더링
|
|
7117
|
+
|
|
6497
7118
|
ctx.fillStyle = fillColor;
|
|
6498
7119
|
ctx.strokeStyle = strokeColor;
|
|
6499
7120
|
ctx.lineWidth = lineWidth;
|
|
@@ -6504,19 +7125,30 @@ var drawPolygon = function (_a) {
|
|
|
6504
7125
|
}
|
|
6505
7126
|
};
|
|
6506
7127
|
/**
|
|
6507
|
-
* 폴리곤 Base 렌더링 함수
|
|
7128
|
+
* 폴리곤 Base 렌더링 함수 팩토리
|
|
6508
7129
|
*
|
|
7130
|
+
* Base Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
7131
|
+
* 선택되지 않은 폴리곤만 렌더링하며, 선택된 항목은 Event Layer에서 처리됩니다.
|
|
7132
|
+
*
|
|
7133
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6509
7134
|
* @param baseFillColor 기본 폴리곤 채우기 색상
|
|
6510
7135
|
* @param baseStrokeColor 기본 폴리곤 테두리 색상
|
|
6511
7136
|
* @param baseLineWidth 기본 폴리곤 테두리 두께
|
|
6512
7137
|
* @returns Base Layer 렌더링 함수
|
|
6513
7138
|
*
|
|
7139
|
+
* @remarks
|
|
7140
|
+
* - 선택된 항목은 Event Layer에서 그려지므로 Base Layer에서는 스킵
|
|
7141
|
+
* - 성능: O(n), n은 렌더링할 폴리곤 개수
|
|
7142
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
7143
|
+
*
|
|
6514
7144
|
* @example
|
|
7145
|
+
* ```typescript
|
|
6515
7146
|
* const renderBase = renderPolygonBase(
|
|
6516
7147
|
* 'rgba(255, 100, 100, 0.5)',
|
|
6517
7148
|
* 'rgba(200, 50, 50, 0.8)',
|
|
6518
7149
|
* 2
|
|
6519
7150
|
* );
|
|
7151
|
+
* ```
|
|
6520
7152
|
*/
|
|
6521
7153
|
|
|
6522
7154
|
var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth) {
|
|
@@ -6527,12 +7159,15 @@ var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth)
|
|
|
6527
7159
|
utils = _a.utils;
|
|
6528
7160
|
|
|
6529
7161
|
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
6530
|
-
var item = items_1[_i]; // 선택된 항목은 Event Layer에서 그림
|
|
7162
|
+
var item = items_1[_i]; // 선택된 항목은 Event Layer에서 그림 (중복 렌더링 방지)
|
|
7163
|
+
|
|
7164
|
+
if (selectedIds.has(item.id)) continue; // paths가 없으면 스킵
|
|
7165
|
+
|
|
7166
|
+
if (!item.paths) continue; // 좌표 변환 (자동 캐싱)
|
|
6531
7167
|
|
|
6532
|
-
if (selectedIds.has(item.id)) continue;
|
|
6533
|
-
if (!item.paths) continue;
|
|
6534
7168
|
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6535
|
-
if (!polygonOffsets) continue;
|
|
7169
|
+
if (!polygonOffsets) continue; // 폴리곤 그리기
|
|
7170
|
+
|
|
6536
7171
|
drawPolygon({
|
|
6537
7172
|
ctx: ctx,
|
|
6538
7173
|
polygonOffsets: polygonOffsets,
|
|
@@ -6545,8 +7180,12 @@ var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth)
|
|
|
6545
7180
|
};
|
|
6546
7181
|
};
|
|
6547
7182
|
/**
|
|
6548
|
-
* 폴리곤 Event 렌더링 함수
|
|
7183
|
+
* 폴리곤 Event 렌더링 함수 팩토리
|
|
7184
|
+
*
|
|
7185
|
+
* Event Layer에서 사용할 렌더링 함수를 생성합니다.
|
|
7186
|
+
* 선택된 항목, hover된 항목, 마지막 선택된 항목을 각각 다른 스타일로 렌더링합니다.
|
|
6549
7187
|
*
|
|
7188
|
+
* @template T 폴리곤 데이터의 추가 속성 타입
|
|
6550
7189
|
* @param baseFillColor 기본 폴리곤 채우기 색상 (필수, fallback용)
|
|
6551
7190
|
* @param baseStrokeColor 기본 폴리곤 테두리 색상 (필수, fallback용)
|
|
6552
7191
|
* @param baseLineWidth 기본 폴리곤 테두리 두께 (필수, fallback용)
|
|
@@ -6561,7 +7200,14 @@ var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth)
|
|
|
6561
7200
|
* @param hoveredLineWidth Hover 시 폴리곤 테두리 두께 (선택, 기본값: selectedLineWidth)
|
|
6562
7201
|
* @returns Event Layer 렌더링 함수
|
|
6563
7202
|
*
|
|
7203
|
+
* @remarks
|
|
7204
|
+
* - **렌더링 순서**: 선택된 항목 → 마지막 선택된 항목 → hover된 항목 (최상단)
|
|
7205
|
+
* - **성능**: O(m), m은 선택된 항목 수 + hover된 항목 수
|
|
7206
|
+
* - 좌표 변환은 자동으로 캐싱되어 성능 최적화됨
|
|
7207
|
+
* - hover된 항목이 선택되어 있으면 active 스타일 적용
|
|
7208
|
+
*
|
|
6564
7209
|
* @example
|
|
7210
|
+
* ```typescript
|
|
6565
7211
|
* const renderEvent = renderPolygonEvent(
|
|
6566
7212
|
* 'rgba(255, 100, 100, 0.5)', // baseFillColor
|
|
6567
7213
|
* 'rgba(200, 50, 50, 0.8)', // baseStrokeColor
|
|
@@ -6570,6 +7216,7 @@ var renderPolygonBase = function (baseFillColor, baseStrokeColor, baseLineWidth)
|
|
|
6570
7216
|
* 'rgba(255, 152, 0, 1)', // selectedStrokeColor
|
|
6571
7217
|
* 4 // selectedLineWidth
|
|
6572
7218
|
* );
|
|
7219
|
+
* ```
|
|
6573
7220
|
*/
|
|
6574
7221
|
|
|
6575
7222
|
var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth) {
|
|
@@ -6597,13 +7244,19 @@ var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth
|
|
|
6597
7244
|
hoveredItem = _a.hoveredItem,
|
|
6598
7245
|
utils = _a.utils,
|
|
6599
7246
|
selectedItems = _a.selectedItems,
|
|
6600
|
-
selectedItem = _a.selectedItem; //
|
|
7247
|
+
selectedItem = _a.selectedItem; // 성능 최적화: selectedItems를 Set으로 변환하여 O(1) 조회 (매번 some() 체크 방지)
|
|
7248
|
+
|
|
7249
|
+
var selectedIdsSet = selectedItems ? new Set(selectedItems.map(function (item) {
|
|
7250
|
+
return item.id;
|
|
7251
|
+
})) : new Set();
|
|
7252
|
+
var hoveredItemId = hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.id;
|
|
7253
|
+
var selectedItemId = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id; // 1. 선택된 항목들 그리기 (마지막 선택 항목과 호버된 항목 제외)
|
|
6601
7254
|
|
|
6602
7255
|
if (selectedItems === null || selectedItems === void 0 ? void 0 : selectedItems.length) {
|
|
6603
7256
|
for (var _i = 0, selectedItems_1 = selectedItems; _i < selectedItems_1.length; _i++) {
|
|
6604
7257
|
var item = selectedItems_1[_i]; // 마지막 선택 항목과 호버된 항목은 나중에 따로 그림
|
|
6605
7258
|
|
|
6606
|
-
if (item.id ===
|
|
7259
|
+
if (item.id === selectedItemId || item.id === hoveredItemId) continue;
|
|
6607
7260
|
if (!item.paths) continue;
|
|
6608
7261
|
var polygonOffsets = utils.getOrComputePolygonOffsets(item);
|
|
6609
7262
|
if (!polygonOffsets) continue;
|
|
@@ -6619,7 +7272,7 @@ var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth
|
|
|
6619
7272
|
} // 2. 마지막 선택된 항목 그리기 (호버되지 않은 경우)
|
|
6620
7273
|
|
|
6621
7274
|
|
|
6622
|
-
if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.paths) &&
|
|
7275
|
+
if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.paths) && hoveredItemId !== selectedItemId) {
|
|
6623
7276
|
var polygonOffsets = utils.getOrComputePolygonOffsets(selectedItem);
|
|
6624
7277
|
|
|
6625
7278
|
if (polygonOffsets) {
|
|
@@ -6637,10 +7290,10 @@ var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth
|
|
|
6637
7290
|
|
|
6638
7291
|
if (hoveredItem === null || hoveredItem === void 0 ? void 0 : hoveredItem.paths) {
|
|
6639
7292
|
var polygonOffsets = utils.getOrComputePolygonOffsets(hoveredItem);
|
|
6640
|
-
if (!polygonOffsets) return;
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
7293
|
+
if (!polygonOffsets) return; // 좌표 변환 실패 시 스킵 (return은 렌더링 함수 종료)
|
|
7294
|
+
// 성능 최적화: Set을 사용하여 O(1) 조회 (이전: O(m) some() 체크)
|
|
7295
|
+
|
|
7296
|
+
var isSelected = selectedIdsSet.has(hoveredItem.id);
|
|
6644
7297
|
drawPolygon({
|
|
6645
7298
|
ctx: ctx,
|
|
6646
7299
|
polygonOffsets: polygonOffsets,
|
|
@@ -6659,8 +7312,6 @@ var renderPolygonEvent = function (baseFillColor, baseStrokeColor, baseLineWidth
|
|
|
6659
7312
|
var WoongCanvasPolygon = function (props) {
|
|
6660
7313
|
var data = props.data,
|
|
6661
7314
|
onClick = props.onClick,
|
|
6662
|
-
onMouseOver = props.onMouseOver,
|
|
6663
|
-
onMouseOut = props.onMouseOut,
|
|
6664
7315
|
_a = props.enableMultiSelect,
|
|
6665
7316
|
enableMultiSelect = _a === void 0 ? false : _a,
|
|
6666
7317
|
_b = props.enableViewportCulling,
|
|
@@ -6685,7 +7336,7 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6685
7336
|
hoveredFillColor = props.hoveredFillColor,
|
|
6686
7337
|
hoveredStrokeColor = props.hoveredStrokeColor,
|
|
6687
7338
|
hoveredLineWidth = props.hoveredLineWidth,
|
|
6688
|
-
options = __rest(props, ["data", "onClick", "
|
|
7339
|
+
options = __rest(props, ["data", "onClick", "enableMultiSelect", "enableViewportCulling", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "baseFillColor", "baseStrokeColor", "baseLineWidth", "selectedFillColor", "selectedStrokeColor", "selectedLineWidth", "activeFillColor", "activeStrokeColor", "activeLineWidth", "hoveredFillColor", "hoveredStrokeColor", "hoveredLineWidth"]); // --------------------------------------------------------------------------
|
|
6689
7340
|
// Hooks & Context
|
|
6690
7341
|
// --------------------------------------------------------------------------
|
|
6691
7342
|
|
|
@@ -6784,37 +7435,16 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6784
7435
|
* 현재 뷰포트 영역 계산
|
|
6785
7436
|
*/
|
|
6786
7437
|
|
|
6787
|
-
var updateViewport = function () {
|
|
6788
|
-
|
|
6789
|
-
var stage = stageRef.current;
|
|
6790
|
-
viewportRef.current = {
|
|
6791
|
-
minX: -cullingMargin,
|
|
6792
|
-
maxX: stage.width() + cullingMargin,
|
|
6793
|
-
minY: -cullingMargin,
|
|
6794
|
-
maxY: stage.height() + cullingMargin
|
|
6795
|
-
};
|
|
7438
|
+
var updateViewport$1 = function () {
|
|
7439
|
+
updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
6796
7440
|
};
|
|
6797
7441
|
/**
|
|
6798
7442
|
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
6799
7443
|
*/
|
|
6800
7444
|
|
|
6801
7445
|
|
|
6802
|
-
var isInViewport = function (item) {
|
|
6803
|
-
|
|
6804
|
-
var viewport = viewportRef.current; // 캐시된 바운딩 박스 확인
|
|
6805
|
-
|
|
6806
|
-
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
6807
|
-
|
|
6808
|
-
if (!bbox) {
|
|
6809
|
-
// 바운딩 박스 계산 (공통 함수 사용)
|
|
6810
|
-
var computed = computeBoundingBox(item);
|
|
6811
|
-
if (!computed) return false;
|
|
6812
|
-
bbox = computed;
|
|
6813
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
6814
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
7446
|
+
var isInViewport$1 = function (item) {
|
|
7447
|
+
return isInViewport(item, enableViewportCulling, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
6818
7448
|
}; // --------------------------------------------------------------------------
|
|
6819
7449
|
// 유틸리티 함수: 좌표 변환 캐싱
|
|
6820
7450
|
// --------------------------------------------------------------------------
|
|
@@ -6843,8 +7473,23 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6843
7473
|
/**
|
|
6844
7474
|
* 폴리곤의 바운딩 박스 계산
|
|
6845
7475
|
*
|
|
7476
|
+
* 폴리곤의 모든 좌표를 순회하여 최소/최대 X, Y 값을 계산합니다.
|
|
7477
|
+
* Viewport Culling에 사용되며, MultiPolygon 형식을 지원합니다.
|
|
7478
|
+
*
|
|
6846
7479
|
* @param item 폴리곤 데이터
|
|
6847
|
-
* @returns 바운딩 박스 또는 null
|
|
7480
|
+
* @returns 바운딩 박스 (minX, minY, maxX, maxY) 또는 null (좌표 변환 실패 시)
|
|
7481
|
+
*
|
|
7482
|
+
* @remarks
|
|
7483
|
+
* - 성능: O(n), n은 폴리곤의 총 좌표 수
|
|
7484
|
+
* - 바운딩 박스는 캐시되어 성능 최적화
|
|
7485
|
+
* - MultiPolygon의 모든 좌표를 고려하여 계산
|
|
7486
|
+
*
|
|
7487
|
+
* @example
|
|
7488
|
+
* ```typescript
|
|
7489
|
+
* const bbox = computeBoundingBox(item);
|
|
7490
|
+
* if (!bbox) return; // 계산 실패
|
|
7491
|
+
* // bbox.minX, bbox.minY, bbox.maxX, bbox.maxY 사용
|
|
7492
|
+
* ```
|
|
6848
7493
|
*/
|
|
6849
7494
|
|
|
6850
7495
|
|
|
@@ -6890,20 +7535,8 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6890
7535
|
*/
|
|
6891
7536
|
|
|
6892
7537
|
|
|
6893
|
-
var buildSpatialIndex = function () {
|
|
6894
|
-
|
|
6895
|
-
spatial.clear();
|
|
6896
|
-
var currentData = dataRef.current;
|
|
6897
|
-
|
|
6898
|
-
for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
|
|
6899
|
-
var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
|
|
6900
|
-
|
|
6901
|
-
var bbox = computeBoundingBox(item);
|
|
6902
|
-
|
|
6903
|
-
if (bbox) {
|
|
6904
|
-
spatial.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
6905
|
-
}
|
|
6906
|
-
}
|
|
7538
|
+
var buildSpatialIndex$1 = function () {
|
|
7539
|
+
buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
6907
7540
|
}; // --------------------------------------------------------------------------
|
|
6908
7541
|
// 렌더링 함수 결정 (dataType에 따라)
|
|
6909
7542
|
// --------------------------------------------------------------------------
|
|
@@ -6926,11 +7559,8 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6926
7559
|
|
|
6927
7560
|
var renderBase = renderPolygonBase(baseFillColor, baseStrokeColor, baseLineWidth);
|
|
6928
7561
|
var renderEvent = renderPolygonEvent(baseFillColor, baseStrokeColor, baseLineWidth, selectedFillColor, selectedStrokeColor, selectedLineWidth, activeFillColor, activeStrokeColor, activeLineWidth, hoveredFillColor, hoveredStrokeColor, hoveredLineWidth);
|
|
6929
|
-
/** Base Layer에서 사용할 빈 Set (재사용) */
|
|
6930
|
-
|
|
6931
|
-
useRef(new Set());
|
|
6932
7562
|
/**
|
|
6933
|
-
* Base 레이어 렌더링 (뷰포트 컬링
|
|
7563
|
+
* Base 레이어 렌더링 (뷰포트 컬링 적용)
|
|
6934
7564
|
*
|
|
6935
7565
|
* 🔥 최적화:
|
|
6936
7566
|
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
@@ -6958,7 +7588,7 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6958
7588
|
var hovered = hoveredItemRef.current; // 클로저로 최신 ref 값 참조
|
|
6959
7589
|
|
|
6960
7590
|
var visibleItems = enableViewportCulling ? dataRef.current.filter(function (item) {
|
|
6961
|
-
return isInViewport(item);
|
|
7591
|
+
return isInViewport$1(item);
|
|
6962
7592
|
}) : dataRef.current; // 일반 항목 렌더링
|
|
6963
7593
|
|
|
6964
7594
|
renderBase({
|
|
@@ -6982,10 +7612,16 @@ var WoongCanvasPolygon = function (props) {
|
|
|
6982
7612
|
/**
|
|
6983
7613
|
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
6984
7614
|
*
|
|
6985
|
-
*
|
|
6986
|
-
*
|
|
6987
|
-
*
|
|
6988
|
-
*
|
|
7615
|
+
* 폴리곤의 hover 효과 및 선택 상태를 표시합니다.
|
|
7616
|
+
* 자동 렌더링 방식으로 renderPolygonEvent를 사용합니다.
|
|
7617
|
+
*
|
|
7618
|
+
* @remarks
|
|
7619
|
+
* - **성능 최적화**:
|
|
7620
|
+
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
7621
|
+
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
7622
|
+
* 3. 클로저로 최신 데이터 참조
|
|
7623
|
+
* - 선택된 항목은 Map에서 O(1)로 조회하여 성능 최적화
|
|
7624
|
+
* - 자동 렌더링: 스타일 props(selectedFillColor, hoveredFillColor 등) 기반으로 자동 렌더링
|
|
6989
7625
|
*/
|
|
6990
7626
|
|
|
6991
7627
|
|
|
@@ -7002,8 +7638,9 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7002
7638
|
name: 'event-render-shape',
|
|
7003
7639
|
sceneFunc: function (context, shape) {
|
|
7004
7640
|
var ctx = context; // 클로저로 최신 ref 값 참조
|
|
7641
|
+
// 성능 최적화: Array.from 대신 직접 변환 (메모리 할당 최소화)
|
|
7005
7642
|
|
|
7006
|
-
var selectedItems =
|
|
7643
|
+
var selectedItems = mapValuesToArray(selectedItemsMapRef.current);
|
|
7007
7644
|
var hovered = hoveredItemRef.current; // 일반 렌더링
|
|
7008
7645
|
|
|
7009
7646
|
renderEvent({
|
|
@@ -7026,101 +7663,62 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7026
7663
|
};
|
|
7027
7664
|
/**
|
|
7028
7665
|
* 전체 즉시 렌더링 (IDLE 시 호출)
|
|
7666
|
+
*
|
|
7667
|
+
* 뷰포트 업데이트, 공간 인덱스 빌드, 모든 레이어 렌더링을 순차적으로 수행합니다.
|
|
7668
|
+
*
|
|
7669
|
+
* @remarks
|
|
7670
|
+
* - 호출 시점: 지도 이동/줌 완료 시, 데이터 변경 시, 리사이즈 시
|
|
7671
|
+
* - 순서: 뷰포트 업데이트 → 공간 인덱스 빌드 → Base → Event 렌더링
|
|
7672
|
+
* - Animation Layer는 사용하지 않음 (폴리곤 특성)
|
|
7029
7673
|
*/
|
|
7030
7674
|
|
|
7031
7675
|
|
|
7032
7676
|
var renderAllImmediate = function () {
|
|
7033
|
-
updateViewport();
|
|
7034
|
-
buildSpatialIndex();
|
|
7677
|
+
updateViewport$1();
|
|
7678
|
+
buildSpatialIndex$1();
|
|
7035
7679
|
doRenderBase();
|
|
7036
7680
|
doRenderEvent();
|
|
7037
7681
|
}; // --------------------------------------------------------------------------
|
|
7038
7682
|
// 이벤트 핸들러: 지도 이벤트
|
|
7039
7683
|
// --------------------------------------------------------------------------
|
|
7040
7684
|
|
|
7041
|
-
/**
|
|
7042
|
-
* 지도 이동/줌 완료 시 처리
|
|
7043
|
-
*/
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
var handleIdle = function () {
|
|
7047
|
-
prevCenterOffsetRef.current = null;
|
|
7048
|
-
accumTranslateRef.current = {
|
|
7049
|
-
x: 0,
|
|
7050
|
-
y: 0
|
|
7051
|
-
}; // 2. 캐시 정리 (지도 이동/줌으로 좌표 변환 결과가 바뀜)
|
|
7052
|
-
|
|
7053
|
-
offsetCacheRef.current.clear();
|
|
7054
|
-
boundingBoxCacheRef.current.clear(); // 3. 마커 위치 업데이트
|
|
7055
|
-
|
|
7056
|
-
var bounds = controller.getCurrBounds();
|
|
7057
|
-
|
|
7058
|
-
var markerOptions = __assign({
|
|
7059
|
-
position: bounds.nw
|
|
7060
|
-
}, options);
|
|
7061
|
-
|
|
7062
|
-
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 4. transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
7063
|
-
|
|
7064
|
-
if (containerRef.current) {
|
|
7065
|
-
containerRef.current.style.transform = '';
|
|
7066
|
-
containerRef.current.style.visibility = '';
|
|
7067
|
-
} // 5. 새 위치에서 렌더링
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
renderAllImmediate();
|
|
7071
|
-
};
|
|
7072
|
-
/**
|
|
7073
|
-
* 줌 시작 시 처리 (일시적으로 숨김)
|
|
7074
|
-
*/
|
|
7075
|
-
|
|
7076
7685
|
|
|
7077
|
-
var
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7686
|
+
var _f = createMapEventHandlers({
|
|
7687
|
+
controller: controller,
|
|
7688
|
+
containerRef: containerRef,
|
|
7689
|
+
markerRef: markerRef,
|
|
7690
|
+
options: options,
|
|
7691
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
7692
|
+
accumTranslateRef: accumTranslateRef,
|
|
7693
|
+
offsetCacheRef: offsetCacheRef,
|
|
7694
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
7695
|
+
renderAllImmediate: renderAllImmediate
|
|
7696
|
+
}),
|
|
7697
|
+
handleIdle = _f.handleIdle,
|
|
7698
|
+
handleZoomStart = _f.handleZoomStart,
|
|
7699
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
7700
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
7701
|
+
handleDragStartShared = _f.handleDragStart,
|
|
7702
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
7082
7703
|
/**
|
|
7083
|
-
*
|
|
7704
|
+
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
7084
7705
|
*/
|
|
7085
7706
|
|
|
7086
7707
|
|
|
7087
|
-
var
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7708
|
+
var handleDragStart = function () {
|
|
7709
|
+
handleDragStartShared();
|
|
7710
|
+
draggingRef.current = true;
|
|
7711
|
+
controller.setMapCursor('grabbing');
|
|
7091
7712
|
};
|
|
7092
7713
|
/**
|
|
7093
|
-
*
|
|
7714
|
+
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
7094
7715
|
*/
|
|
7095
7716
|
|
|
7096
7717
|
|
|
7097
|
-
var
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
if (!prev) {
|
|
7103
|
-
prevCenterOffsetRef.current = {
|
|
7104
|
-
x: curr.x,
|
|
7105
|
-
y: curr.y
|
|
7106
|
-
};
|
|
7107
|
-
return;
|
|
7108
|
-
}
|
|
7109
|
-
|
|
7110
|
-
var dx = prev.x - curr.x;
|
|
7111
|
-
var dy = prev.y - curr.y;
|
|
7112
|
-
accumTranslateRef.current = {
|
|
7113
|
-
x: accumTranslateRef.current.x + dx,
|
|
7114
|
-
y: accumTranslateRef.current.y + dy
|
|
7115
|
-
};
|
|
7116
|
-
prevCenterOffsetRef.current = {
|
|
7117
|
-
x: curr.x,
|
|
7118
|
-
y: curr.y
|
|
7119
|
-
};
|
|
7120
|
-
|
|
7121
|
-
if (containerRef.current) {
|
|
7122
|
-
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
7123
|
-
}
|
|
7718
|
+
var handleDragEnd = function () {
|
|
7719
|
+
handleDragEndShared();
|
|
7720
|
+
draggingRef.current = false;
|
|
7721
|
+
controller.setMapCursor('grab');
|
|
7124
7722
|
}; // --------------------------------------------------------------------------
|
|
7125
7723
|
// Hit Test & 상태 관리
|
|
7126
7724
|
// --------------------------------------------------------------------------
|
|
@@ -7150,13 +7748,14 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7150
7748
|
/**
|
|
7151
7749
|
* Hover 상태 설정 및 레이어 렌더링
|
|
7152
7750
|
*
|
|
7153
|
-
*
|
|
7751
|
+
* 마우스가 폴리곤 위에 올라갔을 때 hover 상태를 설정하고 즉시 렌더링합니다.
|
|
7154
7752
|
*
|
|
7155
|
-
*
|
|
7753
|
+
* @param data hover된 폴리곤 데이터 또는 null (hover 해제 시)
|
|
7156
7754
|
*
|
|
7157
|
-
*
|
|
7158
|
-
* -
|
|
7159
|
-
* -
|
|
7755
|
+
* @remarks
|
|
7756
|
+
* - **성능 최적화**: RAF 없이 즉시 렌더링 (16ms 지연 제거)
|
|
7757
|
+
* - Event Layer에서 hover 효과 표시
|
|
7758
|
+
* - 커서 상태도 자동으로 업데이트됨 (pointer/grab)
|
|
7160
7759
|
*/
|
|
7161
7760
|
|
|
7162
7761
|
|
|
@@ -7175,11 +7774,17 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7175
7774
|
/**
|
|
7176
7775
|
* 클릭 처리 (단일/다중 선택)
|
|
7177
7776
|
*
|
|
7178
|
-
*
|
|
7777
|
+
* 폴리곤 클릭 시 선택 상태를 업데이트하고 렌더링을 수행합니다.
|
|
7778
|
+
*
|
|
7779
|
+
* @param data 클릭된 폴리곤 데이터
|
|
7179
7780
|
*
|
|
7180
|
-
*
|
|
7181
|
-
* -
|
|
7182
|
-
* -
|
|
7781
|
+
* @remarks
|
|
7782
|
+
* - **단일 선택**: 기존 선택 해제 후 새로 선택 (토글 가능)
|
|
7783
|
+
* - **다중 선택**: enableMultiSelect가 true면 기존 선택 유지하며 추가/제거
|
|
7784
|
+
* - **성능 최적화**:
|
|
7785
|
+
* - 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
7786
|
+
* - sceneFunc에서 selectedIds를 체크하여 선택된 폴리곤만 스킵
|
|
7787
|
+
* - 객체 생성 오버헤드 제거로 1,000개 이상도 부드럽게 처리
|
|
7183
7788
|
*/
|
|
7184
7789
|
|
|
7185
7790
|
|
|
@@ -7223,77 +7828,63 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7223
7828
|
|
|
7224
7829
|
/**
|
|
7225
7830
|
* 클릭 이벤트 처리
|
|
7831
|
+
*
|
|
7832
|
+
* @param event 클릭 이벤트 파라미터
|
|
7833
|
+
*
|
|
7834
|
+
* @remarks
|
|
7835
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
7836
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7837
|
+
* - Spatial Index를 사용하여 빠른 Hit Test 수행
|
|
7226
7838
|
*/
|
|
7227
7839
|
|
|
7228
7840
|
|
|
7229
7841
|
var handleClick = function (event) {
|
|
7230
|
-
var _a;
|
|
7231
|
-
|
|
7232
7842
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
7233
7843
|
|
|
7234
|
-
|
|
7844
|
+
var clickedOffset = validateEvent(event, context, controller);
|
|
7845
|
+
if (!clickedOffset) return;
|
|
7846
|
+
var data = findData(clickedOffset);
|
|
7235
7847
|
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
var data_1 = findData(clickedOffset);
|
|
7239
|
-
|
|
7240
|
-
if (data_1) {
|
|
7241
|
-
handleLocalClick(data_1);
|
|
7848
|
+
if (data) {
|
|
7849
|
+
handleLocalClick(data);
|
|
7242
7850
|
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
}
|
|
7851
|
+
if (onClick) {
|
|
7852
|
+
onClick(data, selectedIdsRef.current);
|
|
7246
7853
|
}
|
|
7247
|
-
} catch (error) {
|
|
7248
|
-
console.error('[WoongCanvasPolygon] handleClick error:', error);
|
|
7249
7854
|
}
|
|
7250
7855
|
};
|
|
7251
7856
|
/**
|
|
7252
7857
|
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
7858
|
+
*
|
|
7859
|
+
* @param event 마우스 이동 이벤트 파라미터
|
|
7860
|
+
*
|
|
7861
|
+
* @remarks
|
|
7862
|
+
* - Context가 있으면 전역 이벤트 핸들러가 처리하므로 스킵
|
|
7863
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7864
|
+
* - hover 상태 변경 시에만 렌더링 (최적화)
|
|
7253
7865
|
*/
|
|
7254
7866
|
|
|
7255
7867
|
|
|
7256
7868
|
var handleMouseMove = function (event) {
|
|
7257
|
-
var _a;
|
|
7258
|
-
|
|
7259
7869
|
if (disableInteractionRef.current) return; // 🚫 상호작용 비활성화 시 즉시 반환
|
|
7260
7870
|
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
var hoveredItem = findData(mouseOffset);
|
|
7266
|
-
var prevHovered = hoveredItemRef.current;
|
|
7871
|
+
var mouseOffset = validateEvent(event, context, controller);
|
|
7872
|
+
if (!mouseOffset) return;
|
|
7873
|
+
var hoveredItem = findData(mouseOffset);
|
|
7874
|
+
var prevHovered = hoveredItemRef.current;
|
|
7267
7875
|
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
7271
|
-
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
7272
|
-
}
|
|
7273
|
-
} catch (error) {
|
|
7274
|
-
console.error('[WoongCanvasPolygon] handleMouseMove error:', error);
|
|
7876
|
+
if (prevHovered !== hoveredItem) {
|
|
7877
|
+
setHovered(hoveredItem);
|
|
7275
7878
|
}
|
|
7276
7879
|
};
|
|
7277
|
-
/**
|
|
7278
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
7279
|
-
*/
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
var handleDragStart = function () {
|
|
7283
|
-
draggingRef.current = true;
|
|
7284
|
-
controller.setMapCursor('grabbing');
|
|
7285
|
-
};
|
|
7286
|
-
/**
|
|
7287
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
7288
|
-
*/
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
var handleDragEnd = function () {
|
|
7292
|
-
draggingRef.current = false;
|
|
7293
|
-
controller.setMapCursor('grab');
|
|
7294
|
-
};
|
|
7295
7880
|
/**
|
|
7296
7881
|
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
7882
|
+
*
|
|
7883
|
+
* 맵 영역 밖으로 마우스가 나갔을 때 hover 상태를 초기화합니다.
|
|
7884
|
+
*
|
|
7885
|
+
* @remarks
|
|
7886
|
+
* - 상호작용이 비활성화되어 있으면 스킵
|
|
7887
|
+
* - hover 상태 초기화 및 커서 복원
|
|
7297
7888
|
*/
|
|
7298
7889
|
|
|
7299
7890
|
|
|
@@ -7306,10 +7897,6 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7306
7897
|
hoveredItemRef.current = null;
|
|
7307
7898
|
controller.setMapCursor('grab');
|
|
7308
7899
|
doRenderEvent();
|
|
7309
|
-
|
|
7310
|
-
if (onMouseOut) {
|
|
7311
|
-
onMouseOut(prevHovered);
|
|
7312
|
-
}
|
|
7313
7900
|
}
|
|
7314
7901
|
}; // --------------------------------------------------------------------------
|
|
7315
7902
|
// Lifecycle: DOM 초기화
|
|
@@ -7377,7 +7964,7 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7377
7964
|
stage.add(baseLayer);
|
|
7378
7965
|
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
7379
7966
|
|
|
7380
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
7967
|
+
updateViewport$1(); // ResizeObserver (맵 크기 변경 감지)
|
|
7381
7968
|
|
|
7382
7969
|
var resizeRafId = null;
|
|
7383
7970
|
var resizeObserver = new ResizeObserver(function () {
|
|
@@ -7391,7 +7978,7 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7391
7978
|
stage.height(mapDiv.offsetHeight);
|
|
7392
7979
|
offsetCacheRef.current.clear();
|
|
7393
7980
|
boundingBoxCacheRef.current.clear();
|
|
7394
|
-
updateViewport();
|
|
7981
|
+
updateViewport$1();
|
|
7395
7982
|
renderAllImmediate();
|
|
7396
7983
|
resizeRafId = null;
|
|
7397
7984
|
});
|
|
@@ -7418,8 +8005,6 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7418
8005
|
return findData(offset) !== null;
|
|
7419
8006
|
},
|
|
7420
8007
|
onClick: onClick,
|
|
7421
|
-
onMouseOver: onMouseOver,
|
|
7422
|
-
onMouseOut: onMouseOut,
|
|
7423
8008
|
findData: findData,
|
|
7424
8009
|
setHovered: setHovered,
|
|
7425
8010
|
handleLocalClick: handleLocalClick,
|
|
@@ -7479,18 +8064,8 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7479
8064
|
// --------------------------------------------------------------------------
|
|
7480
8065
|
|
|
7481
8066
|
useEffect(function () {
|
|
7482
|
-
if (!stageRef.current) return;
|
|
7483
|
-
|
|
7484
|
-
if (externalSelectedItems === undefined) return; // 외부에서 전달된 selectedItems로 동기화
|
|
7485
|
-
|
|
7486
|
-
var newSelectedIds = new Set();
|
|
7487
|
-
var newSelectedItemsMap = new Map();
|
|
7488
|
-
externalSelectedItems.forEach(function (item) {
|
|
7489
|
-
newSelectedIds.add(item.id);
|
|
7490
|
-
newSelectedItemsMap.set(item.id, item);
|
|
7491
|
-
});
|
|
7492
|
-
selectedIdsRef.current = newSelectedIds;
|
|
7493
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 렌더링
|
|
8067
|
+
if (!stageRef.current) return;
|
|
8068
|
+
syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef); // 렌더링
|
|
7494
8069
|
|
|
7495
8070
|
doRenderBase();
|
|
7496
8071
|
doRenderEvent();
|
|
@@ -7540,27 +8115,7 @@ var WoongCanvasPolygon = function (props) {
|
|
|
7540
8115
|
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
7541
8116
|
*/
|
|
7542
8117
|
|
|
7543
|
-
|
|
7544
|
-
return [m.id, m];
|
|
7545
|
-
}));
|
|
7546
|
-
var newSelectedItemsMap = new Map();
|
|
7547
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
7548
|
-
// 현재 data에 있으면 최신 데이터 사용
|
|
7549
|
-
var currentItem = dataMap.get(id);
|
|
7550
|
-
|
|
7551
|
-
if (currentItem) {
|
|
7552
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
7553
|
-
} else {
|
|
7554
|
-
// 화면 밖이면 기존 데이터 유지
|
|
7555
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
7556
|
-
|
|
7557
|
-
if (prevItem) {
|
|
7558
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
7559
|
-
}
|
|
7560
|
-
}
|
|
7561
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
|
|
7562
|
-
|
|
7563
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
8118
|
+
selectedItemsMapRef.current = syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current); // 즉시 렌더링
|
|
7564
8119
|
|
|
7565
8120
|
renderAllImmediate();
|
|
7566
8121
|
}, [data]);
|
|
@@ -10405,4 +10960,4 @@ function MintMap(_a) {
|
|
|
10405
10960
|
}), loading));
|
|
10406
10961
|
}
|
|
10407
10962
|
|
|
10408
|
-
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, calculateTextBoxWidth, computeMarkerOffset, computePolygonOffsets, getClusterInfo, getMapOfType, hexToRgba, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, useMarkerMoving, useMintMapController, useWoongCanvasContext, waiting };
|
|
10963
|
+
export { AnimationPlayer, Bounds, CanvasDataType, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, DEFAULT_CULLING_MARGIN, DEFAULT_MAX_CACHE_SIZE, Drawable, GeoCalulator, GoogleMintMapController, LRUCache, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SPATIAL_GRID_CELL_SIZE, SVGCircle, SVGPolygon, SVGRect, Spacing, SpatialHashGrid, Status, WoongCanvasMarker, WoongCanvasPolygon, WoongCanvasProvider, buildSpatialIndex, calculateTextBoxWidth, computeMarkerOffset, computePolygonOffsets, createMapEventHandlers, getClusterInfo, getMapOfType, hexToRgba, isInViewport, isPointInMarkerData, isPointInPolygon, isPointInPolygonData, log, mapValuesToArray, syncExternalSelectedItems, syncSelectedItems, updateViewport, useMarkerMoving, useMintMapController, useWoongCanvasContext, validateEvent, waiting };
|