@mint-ui/map 1.1.3 → 1.2.0-test.2
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/.vscode/settings.json +3 -3
- package/CLAUDE.md +100 -0
- package/dist/components/mint-map/core/MintMapController.d.ts +0 -1
- package/dist/components/mint-map/core/MintMapCore.js +5 -1
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.d.ts +63 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerClaude.js +1084 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.d.ts +22 -0
- package/dist/components/mint-map/core/advanced/canvas/CanvasMarkerHanquf.js +413 -0
- package/dist/components/mint-map/core/advanced/canvas/index.d.ts +2 -0
- package/dist/components/mint-map/core/advanced/index.d.ts +4 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/ClusterMarker.d.ts +11 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.d.ts +48 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/WoongKonvaMarker.js +1032 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/index.d.ts +2 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.d.ts +31 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.js +164 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/index.d.ts +4 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.d.ts +161 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/performance.js +343 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/types.d.ts +121 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.d.ts +23 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/utils.js +115 -0
- package/dist/components/mint-map/core/util/geohash.d.ts +2 -0
- package/dist/components/mint-map/core/util/geohash.js +125 -0
- package/dist/components/mint-map/core/wrapper/MapMarkerWrapper.js +1 -22
- package/dist/components/mint-map/google/GoogleMintMapController.d.ts +0 -1
- package/dist/components/mint-map/google/GoogleMintMapController.js +4 -4
- package/dist/components/mint-map/kakao/KakaoMintMapController.d.ts +0 -1
- package/dist/components/mint-map/kakao/KakaoMintMapController.js +4 -4
- package/dist/components/mint-map/naver/NaverMintMapController.d.ts +0 -3
- package/dist/components/mint-map/naver/NaverMintMapController.js +7 -32
- package/dist/index.es.js +5185 -2069
- package/dist/index.js +25 -13
- package/dist/index.umd.js +5193 -2070
- package/dist/mock.d.ts +133 -0
- package/package.json +17 -4
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var tslib = require('tslib');
|
|
6
|
+
var React = require('react');
|
|
7
|
+
var MintMapProvider = require('../../provider/MintMapProvider.js');
|
|
8
|
+
var MapDrawables = require('../../../types/MapDrawables.js');
|
|
9
|
+
var MapTypes = require('../../../types/MapTypes.js');
|
|
10
|
+
require('../../../types/MapEventTypes.js');
|
|
11
|
+
var reactDom = require('react-dom');
|
|
12
|
+
var canvasUtil = require('./draw/canvas-util.js');
|
|
13
|
+
|
|
14
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
15
|
+
|
|
16
|
+
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
17
|
+
|
|
18
|
+
var defaultPolygonRenderer = function (_a) {
|
|
19
|
+
var context = _a.context,
|
|
20
|
+
offsets = _a.offsets,
|
|
21
|
+
innerOffsets = _a.innerOffsets,
|
|
22
|
+
polygon = _a.polygon,
|
|
23
|
+
isHovered = _a.isHovered,
|
|
24
|
+
defaultBackground = _a.defaultBackground,
|
|
25
|
+
defaultStrokeColor = _a.defaultStrokeColor,
|
|
26
|
+
defaultStrokeWidth = _a.defaultStrokeWidth,
|
|
27
|
+
hoverBackground = _a.hoverBackground,
|
|
28
|
+
hoverStrokeColor = _a.hoverStrokeColor;
|
|
29
|
+
if (offsets.length < 3) return; // 외부 폴리곤 그리기
|
|
30
|
+
|
|
31
|
+
context.beginPath();
|
|
32
|
+
context.moveTo(offsets[0].x, offsets[0].y);
|
|
33
|
+
|
|
34
|
+
for (var i = 1; i < offsets.length; i++) {
|
|
35
|
+
context.lineTo(offsets[i].x, offsets[i].y);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
context.closePath(); // 내부 폴리곤 (구멍) 그리기
|
|
39
|
+
|
|
40
|
+
if (innerOffsets) {
|
|
41
|
+
innerOffsets.forEach(function (innerOffset) {
|
|
42
|
+
if (innerOffset.length < 3) return;
|
|
43
|
+
context.moveTo(innerOffset[0].x, innerOffset[0].y);
|
|
44
|
+
|
|
45
|
+
for (var i = 1; i < innerOffset.length; i++) {
|
|
46
|
+
context.lineTo(innerOffset[i].x, innerOffset[i].y);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
context.closePath();
|
|
50
|
+
});
|
|
51
|
+
} // 스타일 설정
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
var fillColor = isHovered ? hoverBackground : polygon.background || defaultBackground;
|
|
55
|
+
var lineColor = isHovered ? hoverStrokeColor : polygon.strokeColor || defaultStrokeColor;
|
|
56
|
+
var lineWidth = polygon.strokeWidth || defaultStrokeWidth;
|
|
57
|
+
context.fillStyle = fillColor;
|
|
58
|
+
context.strokeStyle = lineColor;
|
|
59
|
+
context.lineWidth = lineWidth;
|
|
60
|
+
context.fill('evenodd');
|
|
61
|
+
context.stroke();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
var CanvasMarkerClaude = React__default["default"].memo(function CanvasMarkerClaude(_a) {
|
|
65
|
+
var _b = _a.polygons,
|
|
66
|
+
polygons = _b === void 0 ? [] : _b,
|
|
67
|
+
_c = _a.markers,
|
|
68
|
+
markers = _c === void 0 ? [] : _c,
|
|
69
|
+
_d = _a.background,
|
|
70
|
+
background = _d === void 0 ? 'rgba(0, 100, 255, 0.3)' : _d,
|
|
71
|
+
_e = _a.strokeColor,
|
|
72
|
+
strokeColor = _e === void 0 ? 'blue' : _e,
|
|
73
|
+
_f = _a.strokeWidth,
|
|
74
|
+
strokeWidth = _f === void 0 ? 1 : _f,
|
|
75
|
+
onPolygonClick = _a.onPolygonClick,
|
|
76
|
+
onPolygonHover = _a.onPolygonHover,
|
|
77
|
+
onPolygonLeave = _a.onPolygonLeave;
|
|
78
|
+
_a.onMarkerClick;
|
|
79
|
+
var onMarkerHover = _a.onMarkerHover,
|
|
80
|
+
onMarkerLeave = _a.onMarkerLeave,
|
|
81
|
+
onRenderComplete = _a.onRenderComplete,
|
|
82
|
+
markerRenderer = _a.markerRenderer,
|
|
83
|
+
options = tslib.__rest(_a, ["polygons", "markers", "background", "strokeColor", "strokeWidth", "onPolygonClick", "onPolygonHover", "onPolygonLeave", "onMarkerClick", "onMarkerHover", "onMarkerLeave", "onRenderComplete", "markerRenderer"]); // controller
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
var controller = MintMapProvider.useMintMapController(); // element (CanvasMarker 스타일)
|
|
87
|
+
|
|
88
|
+
var divRef = React.useRef(document.createElement('div'));
|
|
89
|
+
var divElement = divRef.current; // canvas container ref
|
|
90
|
+
|
|
91
|
+
var containerRef = React.useRef(null); // base canvas (모든 폴리곤 - IDLE 시에만 업데이트)
|
|
92
|
+
|
|
93
|
+
var baseCanvasRef = React.useRef(null);
|
|
94
|
+
var baseContextRef = React.useRef(); // hover canvas (호버된 폴리곤만 - 마우스 이동 시 업데이트)
|
|
95
|
+
|
|
96
|
+
var hoverCanvasRef = React.useRef(null);
|
|
97
|
+
var hoverContextRef = React.useRef(); // base marker canvas (기본 상태 마커 - IDLE 시 업데이트)
|
|
98
|
+
|
|
99
|
+
var baseMarkerCanvasRef = React.useRef(null);
|
|
100
|
+
var baseMarkerContextRef = React.useRef(); // interactive marker canvas (호버/클릭 마커 - 마우스 이동 시 업데이트)
|
|
101
|
+
|
|
102
|
+
var interactiveMarkerCanvasRef = React.useRef(null);
|
|
103
|
+
var interactiveMarkerContextRef = React.useRef(); // marker
|
|
104
|
+
|
|
105
|
+
var markerRef = React.useRef(); // interaction states
|
|
106
|
+
|
|
107
|
+
var hoveredPolygonRef = React.useRef(null);
|
|
108
|
+
var clickedPolygonRef = React.useRef(null);
|
|
109
|
+
var hoveredMarkerRef = React.useRef(null);
|
|
110
|
+
var clickedMarkerRef = React.useRef(null); // 🚀 WoongCanvasMarker 방식: 단순한 data ref (geohash 제거)
|
|
111
|
+
|
|
112
|
+
var polygonsRef = React.useRef([]);
|
|
113
|
+
var markersRef = React.useRef([]); // 마커 경계 정보 저장 (hit-test용)
|
|
114
|
+
|
|
115
|
+
var markerBoundsRef = React.useRef(new Map()); // 🚀 드래그 추적용 (WoongCanvasMarker 방식)
|
|
116
|
+
|
|
117
|
+
var draggingRef = React.useRef(false);
|
|
118
|
+
var prevCenterOffsetRef = React.useRef(null);
|
|
119
|
+
var accumTranslateRef = React.useRef({
|
|
120
|
+
x: 0,
|
|
121
|
+
y: 0
|
|
122
|
+
}); // 🚀 Offset 캐싱 (ID 문자열 기반으로 변경하여 메모리 누수 방지)
|
|
123
|
+
|
|
124
|
+
var MAX_CACHE_SIZE = 50000; // 최대 캐시 크기 제한 (3만개 + 여유분)
|
|
125
|
+
|
|
126
|
+
var polygonOffsetCacheRef = React.useRef(new Map());
|
|
127
|
+
var polygonInnerOffsetCacheRef = React.useRef(new Map()); // innerOffsets 캐시 추가
|
|
128
|
+
|
|
129
|
+
var markerOffsetCacheRef = React.useRef(new Map()); // create object (CanvasMarker 스타일)
|
|
130
|
+
|
|
131
|
+
React.useEffect(function () {
|
|
132
|
+
divElement.style.width = 'fit-content';
|
|
133
|
+
divElement.style.pointerEvents = 'none'; // CanvasMarkerHanquf 방식
|
|
134
|
+
|
|
135
|
+
return function () {
|
|
136
|
+
if (markerRef.current) {
|
|
137
|
+
controller.clearDrawable(markerRef.current);
|
|
138
|
+
markerRef.current = undefined;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}, []); // create / update object (CanvasMarker 스타일)
|
|
142
|
+
|
|
143
|
+
React.useEffect(function () {
|
|
144
|
+
if (options) {
|
|
145
|
+
var bounds = controller.getCurrBounds();
|
|
146
|
+
|
|
147
|
+
var markerOptions = tslib.__assign({
|
|
148
|
+
position: bounds.nw
|
|
149
|
+
}, options);
|
|
150
|
+
|
|
151
|
+
if (markerRef.current) {
|
|
152
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
153
|
+
} else {
|
|
154
|
+
markerRef.current = new MapDrawables.Marker(markerOptions);
|
|
155
|
+
markerRef.current.element = divElement;
|
|
156
|
+
controller.createMarker(markerRef.current); // CanvasMarkerHanquf 방식: pointer events 비활성화
|
|
157
|
+
|
|
158
|
+
if (divElement.parentElement) {
|
|
159
|
+
divElement.style.pointerEvents = 'none';
|
|
160
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
161
|
+
} // z-index 처리
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if (options.zIndex !== undefined) {
|
|
165
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, [options]); // 베이스 폴리곤 렌더링 (모든 폴리곤, 호버 효과 없음)
|
|
170
|
+
|
|
171
|
+
var renderBasePolygons = function () {
|
|
172
|
+
if (!baseContextRef.current || !polygonsRef.current || polygonsRef.current.length === 0) return;
|
|
173
|
+
canvasUtil.clearRect(baseCanvasRef.current, baseContextRef.current);
|
|
174
|
+
var cacheMissCount = 0; // 캐시 미스 카운트
|
|
175
|
+
// 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
|
|
176
|
+
|
|
177
|
+
polygonsRef.current.forEach(function (polygon) {
|
|
178
|
+
if (polygon.visible === false) return; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
179
|
+
|
|
180
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
181
|
+
|
|
182
|
+
if (!offsets) {
|
|
183
|
+
cacheMissCount++;
|
|
184
|
+
offsets = polygon.positions.map(function (pos) {
|
|
185
|
+
return controller.positionToOffset(pos);
|
|
186
|
+
}); // LRU: 캐시 크기 제한
|
|
187
|
+
|
|
188
|
+
if (polygonOffsetCacheRef.current.size >= MAX_CACHE_SIZE) {
|
|
189
|
+
var firstKey = polygonOffsetCacheRef.current.keys().next().value;
|
|
190
|
+
|
|
191
|
+
if (firstKey) {
|
|
192
|
+
polygonOffsetCacheRef.current.delete(firstKey);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
var innerOffsets;
|
|
200
|
+
|
|
201
|
+
if (polygon.innerPositions) {
|
|
202
|
+
// innerOffsets도 캐시 사용
|
|
203
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
204
|
+
|
|
205
|
+
if (!innerOffsets) {
|
|
206
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
207
|
+
return innerPositions.map(function (pos) {
|
|
208
|
+
return controller.positionToOffset(pos);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
212
|
+
}
|
|
213
|
+
} // 렌더러 호출 (항상 기본 스타일)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
defaultPolygonRenderer({
|
|
217
|
+
context: baseContextRef.current,
|
|
218
|
+
offsets: offsets,
|
|
219
|
+
innerOffsets: innerOffsets,
|
|
220
|
+
polygon: polygon,
|
|
221
|
+
isHovered: false,
|
|
222
|
+
defaultBackground: background,
|
|
223
|
+
defaultStrokeColor: strokeColor,
|
|
224
|
+
defaultStrokeWidth: strokeWidth,
|
|
225
|
+
hoverBackground: background,
|
|
226
|
+
hoverStrokeColor: strokeColor
|
|
227
|
+
});
|
|
228
|
+
}); // 요약 로그만 출력 (캐시 미스가 있을 때만)
|
|
229
|
+
|
|
230
|
+
if (cacheMissCount > 0) {
|
|
231
|
+
console.log("\uD83C\uDFA8 [renderBasePolygons] \uCE90\uC2DC \uBBF8\uC2A4: ".concat(cacheMissCount, "\uAC1C, \uD604\uC7AC \uCE90\uC2DC \uD06C\uAE30: ").concat(polygonOffsetCacheRef.current.size));
|
|
232
|
+
} // 렌더링 완료 콜백 호출
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if (onRenderComplete) {
|
|
236
|
+
onRenderComplete();
|
|
237
|
+
}
|
|
238
|
+
}; // 베이스 마커 렌더링 (기본 상태 마커만, IDLE 시 업데이트)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
var renderBaseMarkers = function () {
|
|
242
|
+
if (!baseMarkerContextRef.current || !markerRenderer) return;
|
|
243
|
+
if (!markersRef.current || markersRef.current.length === 0) return;
|
|
244
|
+
canvasUtil.clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current); // 마커 경계 정보 초기화
|
|
245
|
+
|
|
246
|
+
markerBoundsRef.current.clear(); // 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
|
|
247
|
+
|
|
248
|
+
markersRef.current.forEach(function (marker) {
|
|
249
|
+
var _a, _b;
|
|
250
|
+
|
|
251
|
+
if (marker.visible === false) return; // 호버/클릭 상태인 마커는 스킵 (인터랙션 레이어에서 렌더링)
|
|
252
|
+
|
|
253
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) return;
|
|
254
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) return; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
255
|
+
|
|
256
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
257
|
+
|
|
258
|
+
if (!offset) {
|
|
259
|
+
offset = controller.positionToOffset(new MapTypes.Position(marker.lat, marker.lng));
|
|
260
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
261
|
+
} // 커스텀 마커 렌더러 호출 (기본 상태)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
var markerBounds = markerRenderer({
|
|
265
|
+
context: baseMarkerContextRef.current,
|
|
266
|
+
x: offset.x,
|
|
267
|
+
y: offset.y,
|
|
268
|
+
marker: marker,
|
|
269
|
+
isHovered: false,
|
|
270
|
+
isClicked: false
|
|
271
|
+
}); // 마커 경계 정보 저장 (hit-test용)
|
|
272
|
+
|
|
273
|
+
markerBoundsRef.current.set(marker.id, {
|
|
274
|
+
marker: marker,
|
|
275
|
+
bounds: markerBounds
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}; // 인터랙션 마커 렌더링 (호버/클릭 마커만, 마우스 이동 시 업데이트)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
var renderInteractionMarkers = function () {
|
|
282
|
+
if (!interactiveMarkerContextRef.current || !markerRenderer) return;
|
|
283
|
+
canvasUtil.clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current); // 클릭된 마커 먼저 그리기 (하단 레이어)
|
|
284
|
+
|
|
285
|
+
if (clickedMarkerRef.current) {
|
|
286
|
+
var marker = clickedMarkerRef.current;
|
|
287
|
+
|
|
288
|
+
if (marker.visible !== false) {
|
|
289
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
290
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
291
|
+
|
|
292
|
+
if (!offset) {
|
|
293
|
+
offset = controller.positionToOffset(new MapTypes.Position(marker.lat, marker.lng));
|
|
294
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
var markerBounds = markerRenderer({
|
|
298
|
+
context: interactiveMarkerContextRef.current,
|
|
299
|
+
x: offset.x,
|
|
300
|
+
y: offset.y,
|
|
301
|
+
marker: marker,
|
|
302
|
+
isHovered: false,
|
|
303
|
+
isClicked: true
|
|
304
|
+
}); // 마커 경계 정보 업데이트
|
|
305
|
+
|
|
306
|
+
markerBoundsRef.current.set(marker.id, {
|
|
307
|
+
marker: marker,
|
|
308
|
+
bounds: markerBounds
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
} // 호버된 마커 나중에 그리기 (최상단 레이어)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if (hoveredMarkerRef.current) {
|
|
315
|
+
var marker = hoveredMarkerRef.current;
|
|
316
|
+
|
|
317
|
+
if (marker.visible !== false) {
|
|
318
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
319
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
320
|
+
|
|
321
|
+
if (!offset) {
|
|
322
|
+
offset = controller.positionToOffset(new MapTypes.Position(marker.lat, marker.lng));
|
|
323
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
var markerBounds = markerRenderer({
|
|
327
|
+
context: interactiveMarkerContextRef.current,
|
|
328
|
+
x: offset.x,
|
|
329
|
+
y: offset.y,
|
|
330
|
+
marker: marker,
|
|
331
|
+
isHovered: true,
|
|
332
|
+
isClicked: false
|
|
333
|
+
}); // 마커 경계 정보 업데이트
|
|
334
|
+
|
|
335
|
+
markerBoundsRef.current.set(marker.id, {
|
|
336
|
+
marker: marker,
|
|
337
|
+
bounds: markerBounds
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}; // 인터랙션 폴리곤 렌더링 (호버 + 클릭)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
var renderInteractionPolygons = function () {
|
|
345
|
+
if (!hoverContextRef.current) return; // 인터랙션 캔버스 클리어
|
|
346
|
+
|
|
347
|
+
canvasUtil.clearRect(hoverCanvasRef.current, hoverContextRef.current); // 1. 클릭된 폴리곤 먼저 그리기 (하단 레이어)
|
|
348
|
+
|
|
349
|
+
if (clickedPolygonRef.current) {
|
|
350
|
+
var polygon = clickedPolygonRef.current;
|
|
351
|
+
|
|
352
|
+
if (polygon.visible !== false) {
|
|
353
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
354
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
355
|
+
|
|
356
|
+
if (!offsets) {
|
|
357
|
+
offsets = polygon.positions.map(function (pos) {
|
|
358
|
+
return controller.positionToOffset(pos);
|
|
359
|
+
});
|
|
360
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
var innerOffsets = void 0;
|
|
364
|
+
|
|
365
|
+
if (polygon.innerPositions) {
|
|
366
|
+
// innerOffsets도 캐시 사용
|
|
367
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
368
|
+
|
|
369
|
+
if (!innerOffsets) {
|
|
370
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
371
|
+
return innerPositions.map(function (pos) {
|
|
372
|
+
return controller.positionToOffset(pos);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
376
|
+
}
|
|
377
|
+
} // 클릭 스타일 (노란색)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
defaultPolygonRenderer({
|
|
381
|
+
context: hoverContextRef.current,
|
|
382
|
+
offsets: offsets,
|
|
383
|
+
innerOffsets: innerOffsets,
|
|
384
|
+
polygon: polygon,
|
|
385
|
+
isHovered: true,
|
|
386
|
+
defaultBackground: background,
|
|
387
|
+
defaultStrokeColor: strokeColor,
|
|
388
|
+
defaultStrokeWidth: strokeWidth,
|
|
389
|
+
hoverBackground: 'rgba(255, 200, 0, 0.6)',
|
|
390
|
+
hoverStrokeColor: 'orange' // 주황색 테두리
|
|
391
|
+
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
} // 2. 호버된 폴리곤 나중에 그리기 (최상단 레이어)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
if (hoveredPolygonRef.current) {
|
|
398
|
+
var polygon = hoveredPolygonRef.current;
|
|
399
|
+
|
|
400
|
+
if (polygon.visible !== false) {
|
|
401
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
402
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
403
|
+
|
|
404
|
+
if (!offsets) {
|
|
405
|
+
offsets = polygon.positions.map(function (pos) {
|
|
406
|
+
return controller.positionToOffset(pos);
|
|
407
|
+
});
|
|
408
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
var innerOffsets = void 0;
|
|
412
|
+
|
|
413
|
+
if (polygon.innerPositions) {
|
|
414
|
+
// innerOffsets도 캐시 사용
|
|
415
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
416
|
+
|
|
417
|
+
if (!innerOffsets) {
|
|
418
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
419
|
+
return innerPositions.map(function (pos) {
|
|
420
|
+
return controller.positionToOffset(pos);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
424
|
+
}
|
|
425
|
+
} // 호버 스타일 (빨간색)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
defaultPolygonRenderer({
|
|
429
|
+
context: hoverContextRef.current,
|
|
430
|
+
offsets: offsets,
|
|
431
|
+
innerOffsets: innerOffsets,
|
|
432
|
+
polygon: polygon,
|
|
433
|
+
isHovered: true,
|
|
434
|
+
defaultBackground: background,
|
|
435
|
+
defaultStrokeColor: strokeColor,
|
|
436
|
+
defaultStrokeWidth: strokeWidth,
|
|
437
|
+
hoverBackground: 'rgba(255, 0, 0, 0.5)',
|
|
438
|
+
hoverStrokeColor: 'red' // 빨간색 테두리
|
|
439
|
+
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}; // point-in-polygon 테스트 (ray casting algorithm)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
var pointInPolygon = function (x, y, polygon) {
|
|
447
|
+
if (polygon.length < 3) return false;
|
|
448
|
+
var inside = false;
|
|
449
|
+
|
|
450
|
+
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
451
|
+
var xi = polygon[i].x,
|
|
452
|
+
yi = polygon[i].y;
|
|
453
|
+
var xj = polygon[j].x,
|
|
454
|
+
yj = polygon[j].y;
|
|
455
|
+
|
|
456
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
|
|
457
|
+
inside = !inside;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return inside;
|
|
462
|
+
}; // 히트 테스트 함수 (마커 우선)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
var findPolygonAt = function (x, y) {
|
|
466
|
+
var _a, _b;
|
|
467
|
+
|
|
468
|
+
if (!polygonsRef.current || polygonsRef.current.length === 0) return null; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
469
|
+
|
|
470
|
+
if (markersRef.current) {
|
|
471
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
472
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
473
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
474
|
+
|
|
475
|
+
if (markerData) {
|
|
476
|
+
var bounds = markerData.bounds;
|
|
477
|
+
|
|
478
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
479
|
+
return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
if (clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
486
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
487
|
+
|
|
488
|
+
if (markerData) {
|
|
489
|
+
var bounds = markerData.bounds;
|
|
490
|
+
|
|
491
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
492
|
+
return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
499
|
+
var marker = markersRef.current[i];
|
|
500
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
501
|
+
|
|
502
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
503
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
504
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
505
|
+
if (!markerData) continue;
|
|
506
|
+
var bounds = markerData.bounds; // 사각형 경계 체크
|
|
507
|
+
|
|
508
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
509
|
+
// 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크 - 🚀 전체 폴리곤 순회
|
|
514
|
+
// 역순으로 체크 (위에 그려진 것부터)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
for (var i = polygonsRef.current.length - 1; i >= 0; i--) {
|
|
518
|
+
var polygon = polygonsRef.current[i];
|
|
519
|
+
if (polygon.visible === false) continue; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
520
|
+
|
|
521
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
522
|
+
|
|
523
|
+
if (!offsets) {
|
|
524
|
+
offsets = polygon.positions.map(function (pos) {
|
|
525
|
+
return controller.positionToOffset(pos);
|
|
526
|
+
});
|
|
527
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
528
|
+
} // 외부 폴리곤 체크
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
if (pointInPolygon(x, y, offsets)) {
|
|
532
|
+
// 도넛 폴리곤인 경우 내부 구멍도 체크
|
|
533
|
+
if (polygon.innerPositions && polygon.innerPositions.length > 0) {
|
|
534
|
+
var innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
535
|
+
|
|
536
|
+
if (!innerOffsets) {
|
|
537
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
538
|
+
return innerPositions.map(function (pos) {
|
|
539
|
+
return controller.positionToOffset(pos);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
543
|
+
} // 내부 구멍 중 하나라도 포함되면 이 폴리곤은 스킵하고 다음 폴리곤 체크
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
var isInHole = false;
|
|
547
|
+
|
|
548
|
+
for (var _i = 0, innerOffsets_1 = innerOffsets; _i < innerOffsets_1.length; _i++) {
|
|
549
|
+
var innerOffset = innerOffsets_1[_i];
|
|
550
|
+
|
|
551
|
+
if (pointInPolygon(x, y, innerOffset)) {
|
|
552
|
+
isInHole = true;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
} // 구멍 안에 있으면 이 폴리곤은 스킵하고 다음 폴리곤 계속 검색
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
if (isInHole) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
} // 외부 폴리곤 안에 있고, 구멍 안에는 없으면 히트
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
return polygon;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return null;
|
|
569
|
+
}; // IDLE 이벤트 핸들러 (WoongCanvasMarker 최적화 적용)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
var handleIdle = function () {
|
|
573
|
+
var beforeCacheSize = polygonOffsetCacheRef.current.size;
|
|
574
|
+
canvasUtil.clearRect(baseCanvasRef.current, baseContextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
|
|
575
|
+
|
|
576
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
577
|
+
draggingRef.current = false;
|
|
578
|
+
prevCenterOffsetRef.current = null;
|
|
579
|
+
accumTranslateRef.current = {
|
|
580
|
+
x: 0,
|
|
581
|
+
y: 0
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
if (containerRef.current) {
|
|
585
|
+
containerRef.current.style.transform = '';
|
|
586
|
+
} // 🚀 캐시 무효화 (지도 이동/줌으로 좌표가 바뀌므로 전체 clear 필요)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
polygonOffsetCacheRef.current.clear();
|
|
590
|
+
polygonInnerOffsetCacheRef.current.clear();
|
|
591
|
+
markerOffsetCacheRef.current.clear();
|
|
592
|
+
markerBoundsRef.current.clear(); // 캐시 clear 로그
|
|
593
|
+
|
|
594
|
+
if (beforeCacheSize > 0) {
|
|
595
|
+
console.log("\uD83E\uDDF9 [IDLE] \uCE90\uC2DC \uCD08\uAE30\uD654: ".concat(beforeCacheSize, "\uAC1C \uC81C\uAC70 (\uC88C\uD45C \uAC31\uC2E0 \uD544\uC694)"));
|
|
596
|
+
} // 마커 이동
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
var bounds = controller.getCurrBounds();
|
|
600
|
+
|
|
601
|
+
var markerOptions = tslib.__assign({
|
|
602
|
+
position: bounds.nw
|
|
603
|
+
}, options);
|
|
604
|
+
|
|
605
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 🚀 폴리곤 렌더링 (독립)
|
|
606
|
+
|
|
607
|
+
if (polygonsRef.current.length > 0) {
|
|
608
|
+
renderBasePolygons();
|
|
609
|
+
renderInteractionPolygons();
|
|
610
|
+
} // 🚀 마커 렌더링 (독립)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
614
|
+
renderBaseMarkers();
|
|
615
|
+
renderInteractionMarkers();
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
var handleZoomStart = function () {
|
|
620
|
+
containerRef.current && (containerRef.current.style.visibility = 'hidden');
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
var handleZoomEnd = function () {
|
|
624
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
625
|
+
}; // 지도 이벤트를 화면 좌표로 변환 (CanvasMarkerHanquf 방식)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
var parseEventToOffset = function (event) {
|
|
629
|
+
var mapDiv = controller.mapDivElement;
|
|
630
|
+
var rect = mapDiv.getBoundingClientRect();
|
|
631
|
+
return {
|
|
632
|
+
x: event.clientX - rect.left,
|
|
633
|
+
y: event.clientY - rect.top
|
|
634
|
+
};
|
|
635
|
+
}; // 🚀 지도 클릭 이벤트 핸들러 (WoongCanvasMarker 방식 - controller.addEventListener)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
var handleMapClick = function (event) {
|
|
639
|
+
var _a, _b, _c;
|
|
640
|
+
|
|
641
|
+
var clickedOffset = controller.positionToOffset(event.param.position); // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
642
|
+
|
|
643
|
+
var hitMarker = null;
|
|
644
|
+
|
|
645
|
+
if (markersRef.current) {
|
|
646
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
647
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
648
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
649
|
+
|
|
650
|
+
if (markerData) {
|
|
651
|
+
var bounds = markerData.bounds;
|
|
652
|
+
|
|
653
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
654
|
+
hitMarker = hoveredMarkerRef.current;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
661
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
662
|
+
|
|
663
|
+
if (markerData) {
|
|
664
|
+
var bounds = markerData.bounds;
|
|
665
|
+
|
|
666
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
667
|
+
hitMarker = clickedMarkerRef.current;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
if (!hitMarker) {
|
|
674
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
675
|
+
var marker = markersRef.current[i];
|
|
676
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
677
|
+
|
|
678
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
679
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
680
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
681
|
+
if (!markerData) continue;
|
|
682
|
+
var bounds = markerData.bounds;
|
|
683
|
+
|
|
684
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
685
|
+
hitMarker = marker;
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
var hitPolygon = findPolygonAt(clickedOffset.x, clickedOffset.y); // 마커 클릭 해제
|
|
694
|
+
|
|
695
|
+
if (clickedMarkerRef.current) {
|
|
696
|
+
clickedMarkerRef.current = null;
|
|
697
|
+
renderInteractionMarkers();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (hitPolygon) {
|
|
701
|
+
// 같은 폴리곤 다시 클릭하면 선택 해제 (토글)
|
|
702
|
+
if (((_c = clickedPolygonRef.current) === null || _c === void 0 ? void 0 : _c.id) === hitPolygon.id) {
|
|
703
|
+
console.log('🖱️ Polygon deselected:', hitPolygon.id);
|
|
704
|
+
clickedPolygonRef.current = null; // 선택 해제 시에도 콜백 호출 (null 전달)
|
|
705
|
+
|
|
706
|
+
if (onPolygonClick) {
|
|
707
|
+
onPolygonClick(null);
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
console.log('🖱️ Polygon selected:', hitPolygon.id);
|
|
711
|
+
clickedPolygonRef.current = hitPolygon; // 선택 시 콜백 호출
|
|
712
|
+
|
|
713
|
+
if (onPolygonClick) {
|
|
714
|
+
onPolygonClick(hitPolygon);
|
|
715
|
+
}
|
|
716
|
+
} // 인터랙션 레이어 재렌더링
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
renderInteractionPolygons();
|
|
720
|
+
}
|
|
721
|
+
}; // 마우스 이벤트 RAF 스로틀링용
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
var rafIdRef = React.useRef(); // 마우스 이벤트 핸들러 (requestAnimationFrame으로 최적화)
|
|
725
|
+
|
|
726
|
+
var handleMouseMove = function (e) {
|
|
727
|
+
// 🚀 드래그 중이면 호버 처리 스킵 (WoongCanvasMarker 방식)
|
|
728
|
+
if (draggingRef.current) return; // 이미 RAF가 대기 중이면 스킵
|
|
729
|
+
|
|
730
|
+
if (rafIdRef.current) return;
|
|
731
|
+
rafIdRef.current = requestAnimationFrame(function () {
|
|
732
|
+
var _a, _b, _c;
|
|
733
|
+
|
|
734
|
+
rafIdRef.current = undefined;
|
|
735
|
+
|
|
736
|
+
var _d = parseEventToOffset(e),
|
|
737
|
+
x = _d.x,
|
|
738
|
+
y = _d.y; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
var hitMarker = null;
|
|
742
|
+
|
|
743
|
+
if (markersRef.current) {
|
|
744
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
745
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
746
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
747
|
+
|
|
748
|
+
if (markerData) {
|
|
749
|
+
var bounds = markerData.bounds;
|
|
750
|
+
|
|
751
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
752
|
+
hitMarker = hoveredMarkerRef.current;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
759
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
760
|
+
|
|
761
|
+
if (markerData) {
|
|
762
|
+
var bounds = markerData.bounds;
|
|
763
|
+
|
|
764
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
765
|
+
hitMarker = clickedMarkerRef.current;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
if (!hitMarker) {
|
|
772
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
773
|
+
var marker = markersRef.current[i];
|
|
774
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
775
|
+
|
|
776
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
777
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
778
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
779
|
+
if (!markerData) continue;
|
|
780
|
+
var bounds = markerData.bounds;
|
|
781
|
+
|
|
782
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
783
|
+
hitMarker = marker;
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
} // 마커가 호버되었을 때
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
if (hitMarker) {
|
|
792
|
+
// 폴리곤 호버 해제
|
|
793
|
+
if (hoveredPolygonRef.current) {
|
|
794
|
+
hoveredPolygonRef.current = null;
|
|
795
|
+
|
|
796
|
+
if (onPolygonLeave) {
|
|
797
|
+
onPolygonLeave();
|
|
798
|
+
}
|
|
799
|
+
} // 마커 호버 상태 업데이트
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
if (hitMarker.id !== ((_c = hoveredMarkerRef.current) === null || _c === void 0 ? void 0 : _c.id)) {
|
|
803
|
+
hoveredMarkerRef.current = hitMarker;
|
|
804
|
+
console.log('🎯 Marker hovered:', hitMarker.id);
|
|
805
|
+
|
|
806
|
+
if (onMarkerHover) {
|
|
807
|
+
onMarkerHover(hitMarker);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
renderInteractionMarkers();
|
|
811
|
+
renderInteractionPolygons();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return;
|
|
815
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
var hitPolygon = findPolygonAt(x, y); // 마커 호버 해제
|
|
819
|
+
|
|
820
|
+
if (hoveredMarkerRef.current) {
|
|
821
|
+
if (onMarkerLeave) {
|
|
822
|
+
onMarkerLeave();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
hoveredMarkerRef.current = null;
|
|
826
|
+
renderInteractionMarkers();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (hitPolygon !== hoveredPolygonRef.current) {
|
|
830
|
+
// leave 이벤트
|
|
831
|
+
if (hoveredPolygonRef.current && onPolygonLeave) {
|
|
832
|
+
onPolygonLeave();
|
|
833
|
+
} // hover 이벤트
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
hoveredPolygonRef.current = hitPolygon;
|
|
837
|
+
|
|
838
|
+
if (hitPolygon && onPolygonHover) {
|
|
839
|
+
onPolygonHover(hitPolygon);
|
|
840
|
+
} // 인터랙션 레이어 재렌더링 (호버 + 클릭)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
renderInteractionPolygons();
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
var handleCenterChanged = function () {
|
|
849
|
+
// 드래그 중에는 리렌더 대신 transform 으로만 추종
|
|
850
|
+
var center = controller.getCurrBounds().getCenter();
|
|
851
|
+
var curr = controller.positionToOffset(center);
|
|
852
|
+
var prev = prevCenterOffsetRef.current;
|
|
853
|
+
|
|
854
|
+
if (!prev) {
|
|
855
|
+
prevCenterOffsetRef.current = {
|
|
856
|
+
x: curr.x,
|
|
857
|
+
y: curr.y
|
|
858
|
+
};
|
|
859
|
+
draggingRef.current = true;
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
var dx = prev.x - curr.x;
|
|
864
|
+
var dy = prev.y - curr.y;
|
|
865
|
+
accumTranslateRef.current = {
|
|
866
|
+
x: accumTranslateRef.current.x + dx,
|
|
867
|
+
y: accumTranslateRef.current.y + dy
|
|
868
|
+
};
|
|
869
|
+
prevCenterOffsetRef.current = {
|
|
870
|
+
x: curr.x,
|
|
871
|
+
y: curr.y
|
|
872
|
+
};
|
|
873
|
+
draggingRef.current = true;
|
|
874
|
+
|
|
875
|
+
if (containerRef.current) {
|
|
876
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
var handleMouseLeave = function () {
|
|
881
|
+
// RAF 취소
|
|
882
|
+
if (rafIdRef.current) {
|
|
883
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
884
|
+
rafIdRef.current = undefined;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (hoveredPolygonRef.current && onPolygonLeave) {
|
|
888
|
+
onPolygonLeave();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
hoveredPolygonRef.current = null;
|
|
892
|
+
hoveredMarkerRef.current = null;
|
|
893
|
+
renderInteractionMarkers();
|
|
894
|
+
renderInteractionPolygons();
|
|
895
|
+
}; // 🚀 initialize - 폴리곤 캔버스 초기화 (독립)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
React.useEffect(function () {
|
|
899
|
+
if (baseCanvasRef.current && hoverCanvasRef.current && containerRef.current) {
|
|
900
|
+
// 2d 컨텍스트 설정 (폴리곤 캔버스만)
|
|
901
|
+
baseContextRef.current = baseCanvasRef.current.getContext('2d');
|
|
902
|
+
hoverContextRef.current = hoverCanvasRef.current.getContext('2d'); // 스케일링 (폴리곤 캔버스만)
|
|
903
|
+
|
|
904
|
+
if (baseContextRef.current) {
|
|
905
|
+
canvasUtil.scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (hoverContextRef.current) {
|
|
909
|
+
canvasUtil.scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
|
|
910
|
+
} // 폴리곤 데이터가 있으면 렌더링
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
if (polygonsRef.current.length > 0) {
|
|
914
|
+
renderBasePolygons();
|
|
915
|
+
renderInteractionPolygons();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}, []); // 🚀 initialize - 마커 캔버스 초기화 (독립)
|
|
919
|
+
|
|
920
|
+
React.useEffect(function () {
|
|
921
|
+
if (baseMarkerCanvasRef.current && interactiveMarkerCanvasRef.current) {
|
|
922
|
+
// 2d 컨텍스트 설정 (마커 캔버스만)
|
|
923
|
+
baseMarkerContextRef.current = baseMarkerCanvasRef.current.getContext('2d');
|
|
924
|
+
interactiveMarkerContextRef.current = interactiveMarkerCanvasRef.current.getContext('2d'); // 스케일링 (마커 캔버스만)
|
|
925
|
+
|
|
926
|
+
if (baseMarkerContextRef.current) {
|
|
927
|
+
canvasUtil.scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (interactiveMarkerContextRef.current) {
|
|
931
|
+
canvasUtil.scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
932
|
+
} // 마커 데이터가 있으면 렌더링
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
936
|
+
renderBaseMarkers();
|
|
937
|
+
renderInteractionMarkers();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}, [markerRenderer]); // 🚀 공통 초기화 - 이벤트 등록 및 리사이즈
|
|
941
|
+
|
|
942
|
+
React.useEffect(function () {
|
|
943
|
+
var resizeObserver;
|
|
944
|
+
|
|
945
|
+
if (containerRef.current) {
|
|
946
|
+
// 리사이즈 처리
|
|
947
|
+
resizeObserver = new ResizeObserver(function () {
|
|
948
|
+
// 폴리곤 캔버스 리사이즈
|
|
949
|
+
if (baseCanvasRef.current && baseContextRef.current) {
|
|
950
|
+
canvasUtil.clearRect(baseCanvasRef.current, baseContextRef.current);
|
|
951
|
+
canvasUtil.scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (hoverCanvasRef.current && hoverContextRef.current) {
|
|
955
|
+
canvasUtil.clearRect(hoverCanvasRef.current, hoverContextRef.current);
|
|
956
|
+
canvasUtil.scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
|
|
957
|
+
} // 마커 캔버스 리사이즈
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
if (baseMarkerCanvasRef.current && baseMarkerContextRef.current) {
|
|
961
|
+
canvasUtil.clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
962
|
+
canvasUtil.scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (interactiveMarkerCanvasRef.current && interactiveMarkerContextRef.current) {
|
|
966
|
+
canvasUtil.clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
967
|
+
canvasUtil.scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
968
|
+
} // 폴리곤 렌더링
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
if (polygonsRef.current.length > 0) {
|
|
972
|
+
renderBasePolygons();
|
|
973
|
+
renderInteractionPolygons();
|
|
974
|
+
} // 마커 렌더링
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
978
|
+
renderBaseMarkers();
|
|
979
|
+
renderInteractionMarkers();
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
resizeObserver.observe(controller.mapDivElement); // 🚀 이벤트 등록 (한 번만)
|
|
983
|
+
|
|
984
|
+
controller.addEventListener('IDLE', handleIdle);
|
|
985
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
986
|
+
controller.addEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
987
|
+
controller.addEventListener('CLICK', handleMapClick);
|
|
988
|
+
controller.addEventListener('CENTER_CHANGED', handleCenterChanged); // 지도 div에 마우스 이벤트 등록
|
|
989
|
+
|
|
990
|
+
controller.mapDivElement.addEventListener('mousemove', handleMouseMove);
|
|
991
|
+
controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return function () {
|
|
995
|
+
// RAF 정리
|
|
996
|
+
if (rafIdRef.current) {
|
|
997
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
resizeObserver && resizeObserver.disconnect();
|
|
1001
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
1002
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
1003
|
+
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
1004
|
+
controller.removeEventListener('CLICK', handleMapClick);
|
|
1005
|
+
controller.removeEventListener('CENTER_CHANGED', handleCenterChanged);
|
|
1006
|
+
controller.mapDivElement.removeEventListener('mousemove', handleMouseMove);
|
|
1007
|
+
controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
1008
|
+
};
|
|
1009
|
+
}, []); // 🚀 polygons 데이터 업데이트 (독립)
|
|
1010
|
+
|
|
1011
|
+
React.useEffect(function () {
|
|
1012
|
+
console.log('📦 [useEffect] polygons 변경:', polygons === null || polygons === void 0 ? void 0 : polygons.length, '개'); // refs 업데이트
|
|
1013
|
+
|
|
1014
|
+
polygonsRef.current = polygons || []; // 🚀 폴리곤만 렌더링 (마커와 독립)
|
|
1015
|
+
|
|
1016
|
+
if (baseContextRef.current) {
|
|
1017
|
+
renderBasePolygons();
|
|
1018
|
+
renderInteractionPolygons();
|
|
1019
|
+
}
|
|
1020
|
+
}, [polygons]); // 🚀 markers 데이터 업데이트 (독립)
|
|
1021
|
+
|
|
1022
|
+
React.useEffect(function () {
|
|
1023
|
+
console.log('📦 [useEffect] markers 변경:', markers === null || markers === void 0 ? void 0 : markers.length, '개'); // refs 업데이트
|
|
1024
|
+
|
|
1025
|
+
markersRef.current = markers || []; // 🚀 마커만 렌더링 (폴리곤과 독립)
|
|
1026
|
+
|
|
1027
|
+
if (baseMarkerContextRef.current && markerRenderer) {
|
|
1028
|
+
renderBaseMarkers();
|
|
1029
|
+
renderInteractionMarkers();
|
|
1030
|
+
}
|
|
1031
|
+
}, [markers, markerRenderer]); // CanvasMarker 스타일의 createPortal return
|
|
1032
|
+
|
|
1033
|
+
return reactDom.createPortal(React__default["default"].createElement("div", {
|
|
1034
|
+
ref: containerRef,
|
|
1035
|
+
style: {
|
|
1036
|
+
position: 'absolute',
|
|
1037
|
+
width: '100%',
|
|
1038
|
+
height: '100%',
|
|
1039
|
+
pointerEvents: 'none'
|
|
1040
|
+
}
|
|
1041
|
+
}, React__default["default"].createElement("canvas", {
|
|
1042
|
+
ref: baseCanvasRef,
|
|
1043
|
+
style: {
|
|
1044
|
+
position: 'absolute',
|
|
1045
|
+
top: 0,
|
|
1046
|
+
left: 0,
|
|
1047
|
+
pointerEvents: 'revert-layer',
|
|
1048
|
+
width: '100%',
|
|
1049
|
+
height: '100%'
|
|
1050
|
+
}
|
|
1051
|
+
}), React__default["default"].createElement("canvas", {
|
|
1052
|
+
ref: hoverCanvasRef,
|
|
1053
|
+
style: {
|
|
1054
|
+
position: 'absolute',
|
|
1055
|
+
top: 0,
|
|
1056
|
+
left: 0,
|
|
1057
|
+
pointerEvents: 'none',
|
|
1058
|
+
width: '100%',
|
|
1059
|
+
height: '100%'
|
|
1060
|
+
}
|
|
1061
|
+
}), React__default["default"].createElement("canvas", {
|
|
1062
|
+
ref: baseMarkerCanvasRef,
|
|
1063
|
+
style: {
|
|
1064
|
+
position: 'absolute',
|
|
1065
|
+
top: 0,
|
|
1066
|
+
left: 0,
|
|
1067
|
+
pointerEvents: 'none',
|
|
1068
|
+
width: '100%',
|
|
1069
|
+
height: '100%'
|
|
1070
|
+
}
|
|
1071
|
+
}), React__default["default"].createElement("canvas", {
|
|
1072
|
+
ref: interactiveMarkerCanvasRef,
|
|
1073
|
+
style: {
|
|
1074
|
+
position: 'absolute',
|
|
1075
|
+
top: 0,
|
|
1076
|
+
left: 0,
|
|
1077
|
+
pointerEvents: 'none',
|
|
1078
|
+
width: '100%',
|
|
1079
|
+
height: '100%'
|
|
1080
|
+
}
|
|
1081
|
+
})), divElement);
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
exports.CanvasMarkerClaude = CanvasMarkerClaude;
|