@mint-ui/map 1.1.1 → 1.2.0-test.1
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/.claude/settings.local.json +16 -0
- package/.vscode/settings.json +11 -0
- package/CLAUDE.md +100 -0
- package/README.md +190 -0
- package/dist/components/mint-map/core/MintMapController.d.ts +1 -1
- package/dist/components/mint-map/core/MintMapCore.js +4 -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/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/shared/context.d.ts +31 -0
- package/dist/components/mint-map/core/advanced/woongCanvas/shared/context.js +159 -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/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/util/geohash.d.ts +2 -0
- package/dist/components/mint-map/core/util/geohash.js +125 -0
- package/dist/components/mint-map/google/GoogleMintMapController.js +1 -0
- package/dist/components/mint-map/kakao/KakaoMintMapController.d.ts +58 -0
- package/dist/components/mint-map/kakao/KakaoMintMapController.js +1 -0
- package/dist/components/mint-map/naver/NaverMintMapController.js +1 -0
- package/dist/components/mint-map/types/CommonTypes.d.ts +11 -0
- package/dist/index.es.js +1863 -137
- package/dist/index.js +4 -0
- package/dist/index.umd.js +1862 -134
- package/dist/mock.d.ts +133 -0
- package/package.json +17 -4
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { __awaiter, __generator, __spreadArray, __assign,
|
|
2
|
-
import React, { createContext, useContext, useRef,
|
|
1
|
+
import { __extends, __awaiter, __generator, __spreadArray, __assign, __rest } from 'tslib';
|
|
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';
|
|
5
5
|
import { v4 } from 'uuid';
|
|
@@ -649,6 +649,286 @@ var css_248z$1 = ".MintMapCore-module_mint-map-root__SMfwn {\n position: relati
|
|
|
649
649
|
var styles$1 = {"mint-map-root":"MintMapCore-module_mint-map-root__SMfwn","mint-map-container":"MintMapCore-module_mint-map-container__8MIIr"};
|
|
650
650
|
styleInject(css_248z$1);
|
|
651
651
|
|
|
652
|
+
var Drawable =
|
|
653
|
+
/** @class */
|
|
654
|
+
function () {
|
|
655
|
+
function Drawable() {}
|
|
656
|
+
|
|
657
|
+
return Drawable;
|
|
658
|
+
}();
|
|
659
|
+
|
|
660
|
+
var Marker =
|
|
661
|
+
/** @class */
|
|
662
|
+
function (_super) {
|
|
663
|
+
__extends(Marker, _super);
|
|
664
|
+
/**
|
|
665
|
+
* 지도에 표시할 마커정보
|
|
666
|
+
*/
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
function Marker(options) {
|
|
670
|
+
var _this = _super.call(this) || this;
|
|
671
|
+
|
|
672
|
+
_this.options = options;
|
|
673
|
+
return _this;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return Marker;
|
|
677
|
+
}(Drawable);
|
|
678
|
+
|
|
679
|
+
var Polyline =
|
|
680
|
+
/** @class */
|
|
681
|
+
function (_super) {
|
|
682
|
+
__extends(Polyline, _super);
|
|
683
|
+
/**
|
|
684
|
+
* 지도에 표시할 폴리곤정보
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
function Polyline(options) {
|
|
689
|
+
var _this = _super.call(this) || this;
|
|
690
|
+
|
|
691
|
+
_this.options = options;
|
|
692
|
+
return _this;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return Polyline;
|
|
696
|
+
}(Drawable);
|
|
697
|
+
|
|
698
|
+
var Polygon =
|
|
699
|
+
/** @class */
|
|
700
|
+
function (_super) {
|
|
701
|
+
__extends(Polygon, _super);
|
|
702
|
+
/**
|
|
703
|
+
* 지도에 표시할 폴리곤정보
|
|
704
|
+
*/
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
function Polygon(options) {
|
|
708
|
+
var _this = _super.call(this) || this;
|
|
709
|
+
|
|
710
|
+
_this.options = options;
|
|
711
|
+
return _this;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* 폴리곤의 중점을 구한다.
|
|
715
|
+
*/
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
Polygon.prototype.getCenter = function () {
|
|
719
|
+
if (Array.isArray(this.options.position) && this.options.position.length > 0) {
|
|
720
|
+
var paths = this.options.position.map(function (elem) {
|
|
721
|
+
return elem instanceof Position ? elem : new Position(elem[0], elem[1]);
|
|
722
|
+
});
|
|
723
|
+
return PolygonCalculator.getCenter(paths);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
throw new Error('center 를 찾을 수 없습니다.');
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
return Polygon;
|
|
730
|
+
}(Drawable);
|
|
731
|
+
|
|
732
|
+
// export type MapEvent = 'bounds_changed'|'center_changed'|'idle'|'zoom_changed'|'zoomstart'
|
|
733
|
+
// export type MapUIEvent = 'click'|'dblclick'|''
|
|
734
|
+
var MapEvent =
|
|
735
|
+
/** @class */
|
|
736
|
+
function () {
|
|
737
|
+
function MapEvent() {
|
|
738
|
+
this.BOUNDS_CHANGED = 'bounds_changed';
|
|
739
|
+
this.CENTER_CHANGED = 'center_changed';
|
|
740
|
+
this.IDLE = 'idle';
|
|
741
|
+
this.ZOOM_CHANGED = 'zoom_changed';
|
|
742
|
+
this.ZOOMSTART = 'zoomstart';
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
MapEvent.prototype.get = function (eventName) {
|
|
746
|
+
var value = this[eventName];
|
|
747
|
+
|
|
748
|
+
if (typeof value === 'string') {
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
return MapEvent;
|
|
754
|
+
}();
|
|
755
|
+
|
|
756
|
+
var MapUIEvent =
|
|
757
|
+
/** @class */
|
|
758
|
+
function () {
|
|
759
|
+
function MapUIEvent() {
|
|
760
|
+
this.CLICK = 'click';
|
|
761
|
+
this.DBLCLICK = 'dblclick';
|
|
762
|
+
this.MOUSEDOWN = 'mousedown';
|
|
763
|
+
this.MOUSEUP = 'mouseup';
|
|
764
|
+
this.MOUSEOUT = 'mouseout';
|
|
765
|
+
this.MOUSEMOVE = 'mousemove';
|
|
766
|
+
this.MOUSEOVER = 'mouseover';
|
|
767
|
+
this.DRAG = 'drag';
|
|
768
|
+
this.DRAGSTART = 'dragstart';
|
|
769
|
+
this.DRAGEND = 'dragend';
|
|
770
|
+
this.RIGHTCLICK = 'rightclick';
|
|
771
|
+
this.CONTEXTMENU = 'contextmenu';
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
MapUIEvent.prototype.get = function (eventName) {
|
|
775
|
+
var value = this[eventName];
|
|
776
|
+
|
|
777
|
+
if (typeof value === 'string') {
|
|
778
|
+
return value;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
return MapUIEvent;
|
|
783
|
+
}();
|
|
784
|
+
|
|
785
|
+
var KonvaMarkerContext = createContext(null);
|
|
786
|
+
var KonvaMarkerProvider = function (_a) {
|
|
787
|
+
var children = _a.children;
|
|
788
|
+
var controller = useMintMapController(); // Refs
|
|
789
|
+
|
|
790
|
+
var componentsRef = useRef([]);
|
|
791
|
+
var currentHoveredRef = useRef(null);
|
|
792
|
+
var currentHoveredDataRef = useRef(null);
|
|
793
|
+
var draggingRef = useRef(false);
|
|
794
|
+
/**
|
|
795
|
+
* 컴포넌트 등록 (zIndex 내림차순 정렬)
|
|
796
|
+
* 높은 zIndex가 먼저 처리됨
|
|
797
|
+
*/
|
|
798
|
+
|
|
799
|
+
var registerComponent = useCallback(function (instance) {
|
|
800
|
+
componentsRef.current.push(instance);
|
|
801
|
+
componentsRef.current.sort(function (a, b) {
|
|
802
|
+
return b.zIndex - a.zIndex;
|
|
803
|
+
});
|
|
804
|
+
}, []);
|
|
805
|
+
/**
|
|
806
|
+
* 컴포넌트 등록 해제
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
var unregisterComponent = useCallback(function (instance) {
|
|
810
|
+
// Hover 중이던 컴포넌트면 초기화
|
|
811
|
+
if (currentHoveredRef.current === instance) {
|
|
812
|
+
currentHoveredRef.current = null;
|
|
813
|
+
currentHoveredDataRef.current = null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
componentsRef.current = componentsRef.current.filter(function (c) {
|
|
817
|
+
return c !== instance;
|
|
818
|
+
});
|
|
819
|
+
}, []);
|
|
820
|
+
/**
|
|
821
|
+
* 전역 클릭 핸들러 (zIndex 우선순위)
|
|
822
|
+
*/
|
|
823
|
+
|
|
824
|
+
var handleGlobalClick = useCallback(function (event) {
|
|
825
|
+
var _a;
|
|
826
|
+
|
|
827
|
+
if (!((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
828
|
+
var clickedOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회 (높은 것부터)
|
|
829
|
+
|
|
830
|
+
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
831
|
+
var component = _b[_i];
|
|
832
|
+
var data = component.findData(clickedOffset);
|
|
833
|
+
|
|
834
|
+
if (data) {
|
|
835
|
+
component.handleLocalClick(data);
|
|
836
|
+
|
|
837
|
+
if (component.onClick) {
|
|
838
|
+
component.onClick(data, component.getSelectedIds());
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return; // 첫 번째 히트만 처리
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}, [controller]);
|
|
845
|
+
/**
|
|
846
|
+
* 전역 마우스 이동 핸들러 (zIndex 우선순위)
|
|
847
|
+
*/
|
|
848
|
+
|
|
849
|
+
var handleGlobalMouseMove = useCallback(function (event) {
|
|
850
|
+
var _a;
|
|
851
|
+
|
|
852
|
+
if (draggingRef.current || !((_a = event === null || event === void 0 ? void 0 : event.param) === null || _a === void 0 ? void 0 : _a.position)) return;
|
|
853
|
+
var mouseOffset = controller.positionToOffset(event.param.position); // zIndex 순서대로 순회하여 Hover 대상 찾기
|
|
854
|
+
|
|
855
|
+
var newHoveredComponent = null;
|
|
856
|
+
var newHoveredData = null;
|
|
857
|
+
|
|
858
|
+
for (var _i = 0, _b = componentsRef.current; _i < _b.length; _i++) {
|
|
859
|
+
var component = _b[_i];
|
|
860
|
+
var data = component.findData(mouseOffset);
|
|
861
|
+
|
|
862
|
+
if (data) {
|
|
863
|
+
newHoveredComponent = component;
|
|
864
|
+
newHoveredData = data;
|
|
865
|
+
break; // 첫 번째 히트만 처리
|
|
866
|
+
}
|
|
867
|
+
} // Hover 상태 변경 감지 (최적화: 별도 ref로 직접 비교)
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
if (currentHoveredRef.current !== newHoveredComponent || currentHoveredDataRef.current !== newHoveredData) {
|
|
871
|
+
// 이전 hover 해제
|
|
872
|
+
if (currentHoveredRef.current) {
|
|
873
|
+
currentHoveredRef.current.setHovered(null);
|
|
874
|
+
|
|
875
|
+
if (currentHoveredRef.current.onMouseOut && currentHoveredDataRef.current) {
|
|
876
|
+
currentHoveredRef.current.onMouseOut(currentHoveredDataRef.current);
|
|
877
|
+
}
|
|
878
|
+
} // 새 hover 설정
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
if (newHoveredComponent && newHoveredData) {
|
|
882
|
+
newHoveredComponent.setHovered(newHoveredData);
|
|
883
|
+
|
|
884
|
+
if (newHoveredComponent.onMouseOver) {
|
|
885
|
+
newHoveredComponent.onMouseOver(newHoveredData);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
currentHoveredRef.current = newHoveredComponent;
|
|
890
|
+
currentHoveredDataRef.current = newHoveredData;
|
|
891
|
+
}
|
|
892
|
+
}, [controller]);
|
|
893
|
+
/**
|
|
894
|
+
* 줌/드래그 시작 (마우스 이동 이벤트 무시)
|
|
895
|
+
*/
|
|
896
|
+
|
|
897
|
+
var handleZoomStart = useCallback(function () {
|
|
898
|
+
draggingRef.current = true;
|
|
899
|
+
}, []);
|
|
900
|
+
/**
|
|
901
|
+
* 지도 idle (마우스 이동 이벤트 재개)
|
|
902
|
+
*/
|
|
903
|
+
|
|
904
|
+
var handleIdle = useCallback(function () {
|
|
905
|
+
draggingRef.current = false;
|
|
906
|
+
}, []); // 이벤트 리스너 등록
|
|
907
|
+
|
|
908
|
+
useEffect(function () {
|
|
909
|
+
controller.addEventListener('CLICK', handleGlobalClick);
|
|
910
|
+
controller.addEventListener('MOUSEMOVE', handleGlobalMouseMove);
|
|
911
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
912
|
+
controller.addEventListener('IDLE', handleIdle);
|
|
913
|
+
return function () {
|
|
914
|
+
controller.removeEventListener('CLICK', handleGlobalClick);
|
|
915
|
+
controller.removeEventListener('MOUSEMOVE', handleGlobalMouseMove);
|
|
916
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
917
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
918
|
+
};
|
|
919
|
+
}, [controller, handleGlobalClick, handleGlobalMouseMove, handleZoomStart, handleIdle]); // Context value 메모이제이션
|
|
920
|
+
|
|
921
|
+
var contextValue = useMemo(function () {
|
|
922
|
+
return {
|
|
923
|
+
registerComponent: registerComponent,
|
|
924
|
+
unregisterComponent: unregisterComponent
|
|
925
|
+
};
|
|
926
|
+
}, [registerComponent, unregisterComponent]);
|
|
927
|
+
return React.createElement(KonvaMarkerContext.Provider, {
|
|
928
|
+
value: contextValue
|
|
929
|
+
}, children);
|
|
930
|
+
};
|
|
931
|
+
|
|
652
932
|
var cn$3 = classNames.bind(styles$1);
|
|
653
933
|
function MintMapCore(_a) {
|
|
654
934
|
var _this = this;
|
|
@@ -731,7 +1011,7 @@ function MintMapCore(_a) {
|
|
|
731
1011
|
}, [center]);
|
|
732
1012
|
return React.createElement("div", {
|
|
733
1013
|
className: cn$3('mint-map-root')
|
|
734
|
-
}, mapInitialized && children, React.createElement("div", {
|
|
1014
|
+
}, mapInitialized && React.createElement(KonvaMarkerProvider, null, children), React.createElement("div", {
|
|
735
1015
|
className: cn$3('mint-map-container'),
|
|
736
1016
|
style: {
|
|
737
1017
|
visibility: visible ? 'inherit' : 'hidden'
|
|
@@ -1428,59 +1708,6 @@ function () {
|
|
|
1428
1708
|
return MapZoomInfo;
|
|
1429
1709
|
}();
|
|
1430
1710
|
|
|
1431
|
-
// export type MapEvent = 'bounds_changed'|'center_changed'|'idle'|'zoom_changed'|'zoomstart'
|
|
1432
|
-
// export type MapUIEvent = 'click'|'dblclick'|''
|
|
1433
|
-
var MapEvent =
|
|
1434
|
-
/** @class */
|
|
1435
|
-
function () {
|
|
1436
|
-
function MapEvent() {
|
|
1437
|
-
this.BOUNDS_CHANGED = 'bounds_changed';
|
|
1438
|
-
this.CENTER_CHANGED = 'center_changed';
|
|
1439
|
-
this.IDLE = 'idle';
|
|
1440
|
-
this.ZOOM_CHANGED = 'zoom_changed';
|
|
1441
|
-
this.ZOOMSTART = 'zoomstart';
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
MapEvent.prototype.get = function (eventName) {
|
|
1445
|
-
var value = this[eventName];
|
|
1446
|
-
|
|
1447
|
-
if (typeof value === 'string') {
|
|
1448
|
-
return value;
|
|
1449
|
-
}
|
|
1450
|
-
};
|
|
1451
|
-
|
|
1452
|
-
return MapEvent;
|
|
1453
|
-
}();
|
|
1454
|
-
|
|
1455
|
-
var MapUIEvent =
|
|
1456
|
-
/** @class */
|
|
1457
|
-
function () {
|
|
1458
|
-
function MapUIEvent() {
|
|
1459
|
-
this.CLICK = 'click';
|
|
1460
|
-
this.DBLCLICK = 'dblclick';
|
|
1461
|
-
this.MOUSEDOWN = 'mousedown';
|
|
1462
|
-
this.MOUSEUP = 'mouseup';
|
|
1463
|
-
this.MOUSEOUT = 'mouseout';
|
|
1464
|
-
this.MOUSEMOVE = 'mousemove';
|
|
1465
|
-
this.MOUSEOVER = 'mouseover';
|
|
1466
|
-
this.DRAG = 'drag';
|
|
1467
|
-
this.DRAGSTART = 'dragstart';
|
|
1468
|
-
this.DRAGEND = 'dragend';
|
|
1469
|
-
this.RIGHTCLICK = 'rightclick';
|
|
1470
|
-
this.CONTEXTMENU = 'contextmenu';
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
MapUIEvent.prototype.get = function (eventName) {
|
|
1474
|
-
var value = this[eventName];
|
|
1475
|
-
|
|
1476
|
-
if (typeof value === 'string') {
|
|
1477
|
-
return value;
|
|
1478
|
-
}
|
|
1479
|
-
};
|
|
1480
|
-
|
|
1481
|
-
return MapUIEvent;
|
|
1482
|
-
}();
|
|
1483
|
-
|
|
1484
1711
|
var MintMapCanvasRenderer =
|
|
1485
1712
|
/** @class */
|
|
1486
1713
|
function () {
|
|
@@ -1534,86 +1761,6 @@ function SVGCircle(_a) {
|
|
|
1534
1761
|
}, children));
|
|
1535
1762
|
}
|
|
1536
1763
|
|
|
1537
|
-
var Drawable =
|
|
1538
|
-
/** @class */
|
|
1539
|
-
function () {
|
|
1540
|
-
function Drawable() {}
|
|
1541
|
-
|
|
1542
|
-
return Drawable;
|
|
1543
|
-
}();
|
|
1544
|
-
|
|
1545
|
-
var Marker =
|
|
1546
|
-
/** @class */
|
|
1547
|
-
function (_super) {
|
|
1548
|
-
__extends(Marker, _super);
|
|
1549
|
-
/**
|
|
1550
|
-
* 지도에 표시할 마커정보
|
|
1551
|
-
*/
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
function Marker(options) {
|
|
1555
|
-
var _this = _super.call(this) || this;
|
|
1556
|
-
|
|
1557
|
-
_this.options = options;
|
|
1558
|
-
return _this;
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
return Marker;
|
|
1562
|
-
}(Drawable);
|
|
1563
|
-
|
|
1564
|
-
var Polyline =
|
|
1565
|
-
/** @class */
|
|
1566
|
-
function (_super) {
|
|
1567
|
-
__extends(Polyline, _super);
|
|
1568
|
-
/**
|
|
1569
|
-
* 지도에 표시할 폴리곤정보
|
|
1570
|
-
*/
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
function Polyline(options) {
|
|
1574
|
-
var _this = _super.call(this) || this;
|
|
1575
|
-
|
|
1576
|
-
_this.options = options;
|
|
1577
|
-
return _this;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
return Polyline;
|
|
1581
|
-
}(Drawable);
|
|
1582
|
-
|
|
1583
|
-
var Polygon =
|
|
1584
|
-
/** @class */
|
|
1585
|
-
function (_super) {
|
|
1586
|
-
__extends(Polygon, _super);
|
|
1587
|
-
/**
|
|
1588
|
-
* 지도에 표시할 폴리곤정보
|
|
1589
|
-
*/
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
function Polygon(options) {
|
|
1593
|
-
var _this = _super.call(this) || this;
|
|
1594
|
-
|
|
1595
|
-
_this.options = options;
|
|
1596
|
-
return _this;
|
|
1597
|
-
}
|
|
1598
|
-
/**
|
|
1599
|
-
* 폴리곤의 중점을 구한다.
|
|
1600
|
-
*/
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
Polygon.prototype.getCenter = function () {
|
|
1604
|
-
if (Array.isArray(this.options.position) && this.options.position.length > 0) {
|
|
1605
|
-
var paths = this.options.position.map(function (elem) {
|
|
1606
|
-
return elem instanceof Position ? elem : new Position(elem[0], elem[1]);
|
|
1607
|
-
});
|
|
1608
|
-
return PolygonCalculator.getCenter(paths);
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
throw new Error('center 를 찾을 수 없습니다.');
|
|
1612
|
-
};
|
|
1613
|
-
|
|
1614
|
-
return Polygon;
|
|
1615
|
-
}(Drawable);
|
|
1616
|
-
|
|
1617
1764
|
function SVGPolygon(_a) {
|
|
1618
1765
|
var path = _a.path,
|
|
1619
1766
|
_b = _a.innerPath,
|
|
@@ -2676,6 +2823,1585 @@ function CanvasMarker(_a) {
|
|
|
2676
2823
|
})), divElement);
|
|
2677
2824
|
}
|
|
2678
2825
|
|
|
2826
|
+
// Lightweight Geohash encoder and neighbor utilities
|
|
2827
|
+
var BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
|
|
2828
|
+
var NEIGHBORS = {
|
|
2829
|
+
n: {
|
|
2830
|
+
even: {
|
|
2831
|
+
border: 'prxz',
|
|
2832
|
+
neighbor: 'bc01fg45238967deuvhjyznpkmstqrwx'
|
|
2833
|
+
},
|
|
2834
|
+
odd: {
|
|
2835
|
+
border: 'bcfguvyz',
|
|
2836
|
+
neighbor: 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'
|
|
2837
|
+
}
|
|
2838
|
+
},
|
|
2839
|
+
s: {
|
|
2840
|
+
even: {
|
|
2841
|
+
border: '028b',
|
|
2842
|
+
neighbor: '238967debc01fg45kmstqrwxuvhjyznp'
|
|
2843
|
+
},
|
|
2844
|
+
odd: {
|
|
2845
|
+
border: '0145hjnp',
|
|
2846
|
+
neighbor: '14365h7k9dcfesgujnmqp0r2twvyx8zb'
|
|
2847
|
+
}
|
|
2848
|
+
},
|
|
2849
|
+
e: {
|
|
2850
|
+
even: {
|
|
2851
|
+
border: 'bcfguvyz',
|
|
2852
|
+
neighbor: '14365h7k9dcfesgujnmqp0r2twvyx8zb'
|
|
2853
|
+
},
|
|
2854
|
+
odd: {
|
|
2855
|
+
border: 'prxz',
|
|
2856
|
+
neighbor: 'bc01fg45238967deuvhjyznpkmstqrwx'
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
w: {
|
|
2860
|
+
even: {
|
|
2861
|
+
border: '0145hjnp',
|
|
2862
|
+
neighbor: '238967debc01fg45kmstqrwxuvhjyznp'
|
|
2863
|
+
},
|
|
2864
|
+
odd: {
|
|
2865
|
+
border: '028b',
|
|
2866
|
+
neighbor: 'p0r21436x8zb9dcf5h7kjnmqesgutwvy'
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
function geohashEncode(lat, lon, precision) {
|
|
2871
|
+
if (precision === void 0) {
|
|
2872
|
+
precision = 6;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
var idx = 0;
|
|
2876
|
+
var bit = 0;
|
|
2877
|
+
var evenBit = true;
|
|
2878
|
+
var geohash = '';
|
|
2879
|
+
var latMin = -90,
|
|
2880
|
+
latMax = 90;
|
|
2881
|
+
var lonMin = -180,
|
|
2882
|
+
lonMax = 180;
|
|
2883
|
+
|
|
2884
|
+
while (geohash.length < precision) {
|
|
2885
|
+
if (evenBit) {
|
|
2886
|
+
var lonMid = (lonMin + lonMax) / 2;
|
|
2887
|
+
|
|
2888
|
+
if (lon >= lonMid) {
|
|
2889
|
+
idx = idx * 2 + 1;
|
|
2890
|
+
lonMin = lonMid;
|
|
2891
|
+
} else {
|
|
2892
|
+
idx = idx * 2;
|
|
2893
|
+
lonMax = lonMid;
|
|
2894
|
+
}
|
|
2895
|
+
} else {
|
|
2896
|
+
var latMid = (latMin + latMax) / 2;
|
|
2897
|
+
|
|
2898
|
+
if (lat >= latMid) {
|
|
2899
|
+
idx = idx * 2 + 1;
|
|
2900
|
+
latMin = latMid;
|
|
2901
|
+
} else {
|
|
2902
|
+
idx = idx * 2;
|
|
2903
|
+
latMax = latMid;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
evenBit = !evenBit;
|
|
2908
|
+
|
|
2909
|
+
if (++bit == 5) {
|
|
2910
|
+
geohash += BASE32.charAt(idx);
|
|
2911
|
+
bit = 0;
|
|
2912
|
+
idx = 0;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
return geohash;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
function adjacent(hash, dir) {
|
|
2920
|
+
var lastChr = hash[hash.length - 1];
|
|
2921
|
+
var type = hash.length % 2 ? 'odd' : 'even'; // @ts-ignore
|
|
2922
|
+
|
|
2923
|
+
var border = NEIGHBORS[dir][type].border; // @ts-ignore
|
|
2924
|
+
|
|
2925
|
+
var neighbor = NEIGHBORS[dir][type].neighbor;
|
|
2926
|
+
var base = hash.substring(0, hash.length - 1);
|
|
2927
|
+
|
|
2928
|
+
if (border.indexOf(lastChr) !== -1) {
|
|
2929
|
+
base = adjacent(base, dir);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
var pos = neighbor.indexOf(lastChr);
|
|
2933
|
+
var nextChr = BASE32.charAt(pos);
|
|
2934
|
+
return base + nextChr;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
function geohashNeighbors(hash) {
|
|
2938
|
+
var n = adjacent(hash, 'n');
|
|
2939
|
+
var s = adjacent(hash, 's');
|
|
2940
|
+
var e = adjacent(hash, 'e');
|
|
2941
|
+
var w = adjacent(hash, 'w');
|
|
2942
|
+
return [hash, n, s, e, w, adjacent(n, 'e'), adjacent(n, 'w'), adjacent(s, 'e'), adjacent(s, 'w')];
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
function CanvasMarkerHanquf(_a) {
|
|
2946
|
+
var renderer = _a.renderer,
|
|
2947
|
+
data = _a.data,
|
|
2948
|
+
onMouseOver = _a.onMouseOver,
|
|
2949
|
+
onMouseOut = _a.onMouseOut,
|
|
2950
|
+
onClick = _a.onClick,
|
|
2951
|
+
options = __rest(_a, ["renderer", "data", "onMouseOver", "onMouseOut", "onClick"]); //controller
|
|
2952
|
+
|
|
2953
|
+
|
|
2954
|
+
var controller = useMintMapController(); //element
|
|
2955
|
+
|
|
2956
|
+
var divRef = useRef(document.createElement('div'));
|
|
2957
|
+
var divElement = divRef.current; //canvas container ref
|
|
2958
|
+
|
|
2959
|
+
var containerRef = useRef(null); //canvas ref
|
|
2960
|
+
|
|
2961
|
+
var canvasRef = useRef(null); //canvas context
|
|
2962
|
+
|
|
2963
|
+
var contextRef = useRef(); //marker
|
|
2964
|
+
|
|
2965
|
+
var markerRef = useRef(); // interaction states
|
|
2966
|
+
|
|
2967
|
+
var hoveredRef = useRef(null);
|
|
2968
|
+
var clickedRef = useRef(null); // geohash index: hash -> items
|
|
2969
|
+
|
|
2970
|
+
var geoIndexRef = useRef(new Map());
|
|
2971
|
+
var geoPrecisionRef = useRef(6); // drag translate state
|
|
2972
|
+
|
|
2973
|
+
var prevCenterOffsetRef = useRef(null);
|
|
2974
|
+
var accumTranslateRef = useRef({
|
|
2975
|
+
x: 0,
|
|
2976
|
+
y: 0
|
|
2977
|
+
});
|
|
2978
|
+
var draggingRef = useRef(false); //create object
|
|
2979
|
+
|
|
2980
|
+
useEffect(function () {
|
|
2981
|
+
divElement.style.width = 'fit-content';
|
|
2982
|
+
divElement.style.pointerEvents = 'none';
|
|
2983
|
+
return function () {
|
|
2984
|
+
if (markerRef.current) {
|
|
2985
|
+
controller.clearDrawable(markerRef.current);
|
|
2986
|
+
markerRef.current = undefined;
|
|
2987
|
+
}
|
|
2988
|
+
};
|
|
2989
|
+
}, []); //create / update object
|
|
2990
|
+
|
|
2991
|
+
useEffect(function () {
|
|
2992
|
+
if (options) {
|
|
2993
|
+
var bounds = controller.getCurrBounds();
|
|
2994
|
+
|
|
2995
|
+
var markerOptions = __assign({
|
|
2996
|
+
position: bounds.nw
|
|
2997
|
+
}, options);
|
|
2998
|
+
|
|
2999
|
+
if (markerRef.current) {
|
|
3000
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
3001
|
+
} else {
|
|
3002
|
+
markerRef.current = new Marker(markerOptions);
|
|
3003
|
+
markerRef.current.element = divElement;
|
|
3004
|
+
controller.createMarker(markerRef.current); //disablePointerEvent 처리
|
|
3005
|
+
|
|
3006
|
+
if (divElement.parentElement) {
|
|
3007
|
+
divElement.style.pointerEvents = 'none';
|
|
3008
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
3009
|
+
} //z-index 처리
|
|
3010
|
+
|
|
3011
|
+
|
|
3012
|
+
if (options.zIndex !== undefined) {
|
|
3013
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
}, [options]);
|
|
3018
|
+
|
|
3019
|
+
var handleIdle = function () {
|
|
3020
|
+
// 클리어
|
|
3021
|
+
clearRect(canvasRef.current, contextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
|
|
3022
|
+
|
|
3023
|
+
containerRef.current && (containerRef.current.style.visibility = ''); // 드래그 종료 처리: 변환 초기화 및 기준 위치 갱신
|
|
3024
|
+
|
|
3025
|
+
draggingRef.current = false;
|
|
3026
|
+
prevCenterOffsetRef.current = null;
|
|
3027
|
+
accumTranslateRef.current = {
|
|
3028
|
+
x: 0,
|
|
3029
|
+
y: 0
|
|
3030
|
+
};
|
|
3031
|
+
|
|
3032
|
+
if (containerRef.current) {
|
|
3033
|
+
containerRef.current.style.transform = '';
|
|
3034
|
+
} // 마커 이동
|
|
3035
|
+
|
|
3036
|
+
|
|
3037
|
+
var bounds = controller.getCurrBounds();
|
|
3038
|
+
|
|
3039
|
+
var markerOptions = __assign({
|
|
3040
|
+
position: bounds.nw
|
|
3041
|
+
}, options);
|
|
3042
|
+
|
|
3043
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 렌더링 (hover/click item last)
|
|
3044
|
+
|
|
3045
|
+
renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
var handleZoomStart = function () {
|
|
3049
|
+
containerRef.current && (containerRef.current.style.visibility = 'hidden');
|
|
3050
|
+
};
|
|
3051
|
+
|
|
3052
|
+
var handleZoomEnd = function () {
|
|
3053
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
3054
|
+
}; //initialize
|
|
3055
|
+
|
|
3056
|
+
|
|
3057
|
+
useEffect(function () {
|
|
3058
|
+
var resizeObserver; // hover/out 공통 처리기 (mouse 좌표 기반)
|
|
3059
|
+
|
|
3060
|
+
var updateHoverByMouseXY = function (mouseX, mouseY) {
|
|
3061
|
+
var hit = hitTest(mouseX, mouseY);
|
|
3062
|
+
|
|
3063
|
+
if ((hit === null || hit === void 0 ? void 0 : hit.item) !== hoveredRef.current) {
|
|
3064
|
+
if (hoveredRef.current && onMouseOut) {
|
|
3065
|
+
onMouseOut(hoveredRef.current);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
hoveredRef.current = (hit === null || hit === void 0 ? void 0 : hit.item) || null;
|
|
3069
|
+
|
|
3070
|
+
if ((hit === null || hit === void 0 ? void 0 : hit.item) && onMouseOver) {
|
|
3071
|
+
onMouseOver(hit.item);
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
clearRect(canvasRef.current, contextRef.current);
|
|
3075
|
+
renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
var handleMouseLeave = function () {
|
|
3080
|
+
console.log('handleMouseLeave');
|
|
3081
|
+
|
|
3082
|
+
if (hoveredRef.current && onMouseOut) {
|
|
3083
|
+
onMouseOut(hoveredRef.current);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
hoveredRef.current = null;
|
|
3087
|
+
clearRect(canvasRef.current, contextRef.current);
|
|
3088
|
+
renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3089
|
+
}; // 지도 이벤트 → 화면 좌표 변환
|
|
3090
|
+
|
|
3091
|
+
|
|
3092
|
+
var parseEventToOffset = function (e) {
|
|
3093
|
+
var mapType = controller.getMapType();
|
|
3094
|
+
var latlng = (e === null || e === void 0 ? void 0 : e.latlng) || (e === null || e === void 0 ? void 0 : e.latLng);
|
|
3095
|
+
|
|
3096
|
+
if (!latlng) {
|
|
3097
|
+
return null;
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
var pos = {
|
|
3101
|
+
lat: 0,
|
|
3102
|
+
lng: 0
|
|
3103
|
+
}; //@ts-ignore
|
|
3104
|
+
|
|
3105
|
+
if (mapType === 'naver') {
|
|
3106
|
+
pos.lat = latlng._lat;
|
|
3107
|
+
pos.lng = latlng._lng; //@ts-ignore
|
|
3108
|
+
} else if (mapType === 'google') {
|
|
3109
|
+
pos.lat = latlng.lat();
|
|
3110
|
+
pos.lng = latlng.lng(); //@ts-ignore
|
|
3111
|
+
} else if (mapType === 'kakao') {
|
|
3112
|
+
pos.lat = latlng.Ma;
|
|
3113
|
+
pos.lng = latlng.La;
|
|
3114
|
+
} else {
|
|
3115
|
+
return null;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
var offset = controller.positionToOffset(pos);
|
|
3119
|
+
return offset;
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3122
|
+
var handleMapMouseMove = function (e) {
|
|
3123
|
+
if (draggingRef.current) {
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
var offset = parseEventToOffset(e);
|
|
3128
|
+
if (!offset) return;
|
|
3129
|
+
updateHoverByMouseXY(offset.x, offset.y);
|
|
3130
|
+
};
|
|
3131
|
+
|
|
3132
|
+
var handleMapClick = function (e) {
|
|
3133
|
+
console.log('handleClick');
|
|
3134
|
+
var offset = parseEventToOffset(e);
|
|
3135
|
+
if (!offset) return;
|
|
3136
|
+
var hit = hitTest(offset.x, offset.y);
|
|
3137
|
+
|
|
3138
|
+
if (hit === null || hit === void 0 ? void 0 : hit.item) {
|
|
3139
|
+
clickedRef.current = hit.item;
|
|
3140
|
+
onClick && onClick(hit.item);
|
|
3141
|
+
clearRect(canvasRef.current, contextRef.current);
|
|
3142
|
+
renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
|
|
3146
|
+
if (canvasRef.current && containerRef.current) {
|
|
3147
|
+
// 리사이즈 처리
|
|
3148
|
+
resizeObserver = new ResizeObserver(function () {
|
|
3149
|
+
// 클리어
|
|
3150
|
+
clearRect(canvasRef.current, contextRef.current); // 스케일링
|
|
3151
|
+
|
|
3152
|
+
canvasRef.current && contextRef.current && scaleCanvas(controller, canvasRef.current, contextRef.current); // 렌더링 (respect hover/click ordering)
|
|
3153
|
+
|
|
3154
|
+
renderMain(controller, rendererRef.current, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3155
|
+
});
|
|
3156
|
+
resizeObserver.observe(controller.mapDivElement); // IDLE 이벤트 등록
|
|
3157
|
+
|
|
3158
|
+
controller.addEventListener('IDLE', handleIdle); // 줌 처리
|
|
3159
|
+
|
|
3160
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
3161
|
+
controller.addEventListener('ZOOM_CHANGED', handleZoomEnd); // 2d 컨텍스트
|
|
3162
|
+
|
|
3163
|
+
contextRef.current = canvasRef.current.getContext('2d'); // 스케일링
|
|
3164
|
+
|
|
3165
|
+
if (contextRef.current) {
|
|
3166
|
+
scaleCanvas(controller, canvasRef.current, contextRef.current);
|
|
3167
|
+
} // 지도 이벤트 구독 (부모 pointer-events:none 이어도 동작)
|
|
3168
|
+
|
|
3169
|
+
|
|
3170
|
+
var map = controller.getMap();
|
|
3171
|
+
|
|
3172
|
+
if (map) {
|
|
3173
|
+
//@ts-ignore
|
|
3174
|
+
map.addListener('mousemove', handleMapMouseMove); //@ts-ignore
|
|
3175
|
+
|
|
3176
|
+
map.addListener('click', handleMapClick); //@ts-ignore
|
|
3177
|
+
|
|
3178
|
+
map.addListener('center_changed', function () {
|
|
3179
|
+
// 드래그 중에는 리렌더 대신 transform 으로만 추종
|
|
3180
|
+
var center = controller.getCurrBounds().getCenter();
|
|
3181
|
+
var curr = controller.positionToOffset(center);
|
|
3182
|
+
var prev = prevCenterOffsetRef.current;
|
|
3183
|
+
|
|
3184
|
+
if (!prev) {
|
|
3185
|
+
prevCenterOffsetRef.current = {
|
|
3186
|
+
x: curr.x,
|
|
3187
|
+
y: curr.y
|
|
3188
|
+
};
|
|
3189
|
+
draggingRef.current = true;
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
var dx = prev.x - curr.x;
|
|
3194
|
+
var dy = prev.y - curr.y;
|
|
3195
|
+
accumTranslateRef.current = {
|
|
3196
|
+
x: accumTranslateRef.current.x + dx,
|
|
3197
|
+
y: accumTranslateRef.current.y + dy
|
|
3198
|
+
};
|
|
3199
|
+
prevCenterOffsetRef.current = {
|
|
3200
|
+
x: curr.x,
|
|
3201
|
+
y: curr.y
|
|
3202
|
+
};
|
|
3203
|
+
draggingRef.current = true;
|
|
3204
|
+
|
|
3205
|
+
if (containerRef.current) {
|
|
3206
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
3207
|
+
}
|
|
3208
|
+
}); //@ts-ignore
|
|
3209
|
+
|
|
3210
|
+
map.addListener('idle', handleIdle);
|
|
3211
|
+
} // 마우스가 지도 영역을 벗어나는 경우 처리
|
|
3212
|
+
|
|
3213
|
+
|
|
3214
|
+
controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
return function () {
|
|
3218
|
+
resizeObserver && resizeObserver.disconnect();
|
|
3219
|
+
controller.mapDivElement && controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
3220
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
3221
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
3222
|
+
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
3223
|
+
};
|
|
3224
|
+
}, []); // data ref
|
|
3225
|
+
|
|
3226
|
+
var dataRef = useRef(data);
|
|
3227
|
+
useEffect(function () {
|
|
3228
|
+
dataRef.current = data; // rebuild geohash index
|
|
3229
|
+
|
|
3230
|
+
var map = new Map();
|
|
3231
|
+
var precision = geoPrecisionRef.current;
|
|
3232
|
+
|
|
3233
|
+
for (var _i = 0, _a = data || []; _i < _a.length; _i++) {
|
|
3234
|
+
var item = _a[_i];
|
|
3235
|
+
var pos = item.position && item.position[0];
|
|
3236
|
+
if (!pos) continue;
|
|
3237
|
+
var hash = geohashEncode(pos.lat, pos.lng, precision);
|
|
3238
|
+
var list = map.get(hash) || [];
|
|
3239
|
+
list.push(item);
|
|
3240
|
+
map.set(hash, list);
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
geoIndexRef.current = map;
|
|
3244
|
+
}, [data]); // renderer ref
|
|
3245
|
+
|
|
3246
|
+
var rendererRef = useRef(renderer);
|
|
3247
|
+
useEffect(function () {
|
|
3248
|
+
rendererRef.current = renderer;
|
|
3249
|
+
}, [renderer]); // Build ordered list so hovered/clicked items render last (on top) within this canvas layer
|
|
3250
|
+
|
|
3251
|
+
var buildOrderedData = function () {
|
|
3252
|
+
var base = dataRef.current || [];
|
|
3253
|
+
var result = [];
|
|
3254
|
+
var hoverItem;
|
|
3255
|
+
var clickItem;
|
|
3256
|
+
|
|
3257
|
+
for (var _i = 0, base_1 = base; _i < base_1.length; _i++) {
|
|
3258
|
+
var item = base_1[_i];
|
|
3259
|
+
|
|
3260
|
+
if (clickedRef.current && item === clickedRef.current) {
|
|
3261
|
+
clickItem = item;
|
|
3262
|
+
} else if (hoveredRef.current && item === hoveredRef.current) {
|
|
3263
|
+
hoverItem = item;
|
|
3264
|
+
} else {
|
|
3265
|
+
result.push(item);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
if (hoverItem) result.push(hoverItem);
|
|
3270
|
+
if (clickItem) result.push(clickItem);
|
|
3271
|
+
return result;
|
|
3272
|
+
}; // Geohash-accelerated hit-test
|
|
3273
|
+
|
|
3274
|
+
|
|
3275
|
+
var hitTest = function (mouseX, mouseY) {
|
|
3276
|
+
// convert mouse offset -> lat/lng then geohash it
|
|
3277
|
+
var pos = controller.offsetToPosition({
|
|
3278
|
+
x: mouseX,
|
|
3279
|
+
y: mouseY
|
|
3280
|
+
});
|
|
3281
|
+
var precision = geoPrecisionRef.current;
|
|
3282
|
+
var baseHash = geohashEncode(pos.lat, pos.lng, precision);
|
|
3283
|
+
var buckets = geohashNeighbors(baseHash); // collect candidates from buckets (preserve ordering for top-most first)
|
|
3284
|
+
|
|
3285
|
+
var set = new Set();
|
|
3286
|
+
var ordered = buildOrderedData();
|
|
3287
|
+
|
|
3288
|
+
for (var _i = 0, buckets_1 = buckets; _i < buckets_1.length; _i++) {
|
|
3289
|
+
var b = buckets_1[_i];
|
|
3290
|
+
var list = geoIndexRef.current.get(b);
|
|
3291
|
+
if (!list) continue;
|
|
3292
|
+
|
|
3293
|
+
for (var _a = 0, list_1 = list; _a < list_1.length; _a++) {
|
|
3294
|
+
var it = list_1[_a];
|
|
3295
|
+
set.add(it);
|
|
3296
|
+
}
|
|
3297
|
+
} // iterate from top-most
|
|
3298
|
+
|
|
3299
|
+
|
|
3300
|
+
for (var i = ordered.length - 1; i >= 0; i--) {
|
|
3301
|
+
var item = ordered[i];
|
|
3302
|
+
if (!set.has(item)) continue;
|
|
3303
|
+
if (item.visible === false) continue;
|
|
3304
|
+
var p = item.position && item.position[0];
|
|
3305
|
+
if (!p) continue;
|
|
3306
|
+
var off = controller.positionToOffset(p);
|
|
3307
|
+
var r = Math.max(2, item.hitRadius || 20);
|
|
3308
|
+
var dx = mouseX - off.x;
|
|
3309
|
+
var dy = mouseY - off.y;
|
|
3310
|
+
|
|
3311
|
+
if (dx * dx + dy * dy <= r * r) {
|
|
3312
|
+
return {
|
|
3313
|
+
item: item
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
return null;
|
|
3319
|
+
}; // Initial render
|
|
3320
|
+
|
|
3321
|
+
|
|
3322
|
+
renderMain(controller, renderer, containerRef.current, canvasRef.current, contextRef.current, buildOrderedData());
|
|
3323
|
+
return createPortal(React.createElement("div", {
|
|
3324
|
+
ref: containerRef,
|
|
3325
|
+
style: {
|
|
3326
|
+
position: 'absolute',
|
|
3327
|
+
width: '100%',
|
|
3328
|
+
height: '100%',
|
|
3329
|
+
pointerEvents: 'none'
|
|
3330
|
+
}
|
|
3331
|
+
}, React.createElement("canvas", {
|
|
3332
|
+
ref: canvasRef,
|
|
3333
|
+
style: {
|
|
3334
|
+
pointerEvents: 'revert-layer'
|
|
3335
|
+
}
|
|
3336
|
+
})), divElement);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
var defaultPolygonRenderer = function (_a) {
|
|
3340
|
+
var context = _a.context,
|
|
3341
|
+
offsets = _a.offsets,
|
|
3342
|
+
innerOffsets = _a.innerOffsets,
|
|
3343
|
+
polygon = _a.polygon,
|
|
3344
|
+
isHovered = _a.isHovered,
|
|
3345
|
+
defaultBackground = _a.defaultBackground,
|
|
3346
|
+
defaultStrokeColor = _a.defaultStrokeColor,
|
|
3347
|
+
defaultStrokeWidth = _a.defaultStrokeWidth,
|
|
3348
|
+
hoverBackground = _a.hoverBackground,
|
|
3349
|
+
hoverStrokeColor = _a.hoverStrokeColor;
|
|
3350
|
+
if (offsets.length < 3) return; // 외부 폴리곤 그리기
|
|
3351
|
+
|
|
3352
|
+
context.beginPath();
|
|
3353
|
+
context.moveTo(offsets[0].x, offsets[0].y);
|
|
3354
|
+
|
|
3355
|
+
for (var i = 1; i < offsets.length; i++) {
|
|
3356
|
+
context.lineTo(offsets[i].x, offsets[i].y);
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
context.closePath(); // 내부 폴리곤 (구멍) 그리기
|
|
3360
|
+
|
|
3361
|
+
if (innerOffsets) {
|
|
3362
|
+
innerOffsets.forEach(function (innerOffset) {
|
|
3363
|
+
if (innerOffset.length < 3) return;
|
|
3364
|
+
context.moveTo(innerOffset[0].x, innerOffset[0].y);
|
|
3365
|
+
|
|
3366
|
+
for (var i = 1; i < innerOffset.length; i++) {
|
|
3367
|
+
context.lineTo(innerOffset[i].x, innerOffset[i].y);
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
context.closePath();
|
|
3371
|
+
});
|
|
3372
|
+
} // 스타일 설정
|
|
3373
|
+
|
|
3374
|
+
|
|
3375
|
+
var fillColor = isHovered ? hoverBackground : polygon.background || defaultBackground;
|
|
3376
|
+
var lineColor = isHovered ? hoverStrokeColor : polygon.strokeColor || defaultStrokeColor;
|
|
3377
|
+
var lineWidth = polygon.strokeWidth || defaultStrokeWidth;
|
|
3378
|
+
context.fillStyle = fillColor;
|
|
3379
|
+
context.strokeStyle = lineColor;
|
|
3380
|
+
context.lineWidth = lineWidth;
|
|
3381
|
+
context.fill('evenodd');
|
|
3382
|
+
context.stroke();
|
|
3383
|
+
};
|
|
3384
|
+
|
|
3385
|
+
var CanvasMarkerClaude = React.memo(function CanvasMarkerClaude(_a) {
|
|
3386
|
+
var _b = _a.polygons,
|
|
3387
|
+
polygons = _b === void 0 ? [] : _b,
|
|
3388
|
+
_c = _a.markers,
|
|
3389
|
+
markers = _c === void 0 ? [] : _c,
|
|
3390
|
+
_d = _a.background,
|
|
3391
|
+
background = _d === void 0 ? 'rgba(0, 100, 255, 0.3)' : _d,
|
|
3392
|
+
_e = _a.strokeColor,
|
|
3393
|
+
strokeColor = _e === void 0 ? 'blue' : _e,
|
|
3394
|
+
_f = _a.strokeWidth,
|
|
3395
|
+
strokeWidth = _f === void 0 ? 1 : _f,
|
|
3396
|
+
onPolygonClick = _a.onPolygonClick,
|
|
3397
|
+
onPolygonHover = _a.onPolygonHover,
|
|
3398
|
+
onPolygonLeave = _a.onPolygonLeave;
|
|
3399
|
+
_a.onMarkerClick;
|
|
3400
|
+
var onMarkerHover = _a.onMarkerHover,
|
|
3401
|
+
onMarkerLeave = _a.onMarkerLeave,
|
|
3402
|
+
onRenderComplete = _a.onRenderComplete,
|
|
3403
|
+
markerRenderer = _a.markerRenderer,
|
|
3404
|
+
options = __rest(_a, ["polygons", "markers", "background", "strokeColor", "strokeWidth", "onPolygonClick", "onPolygonHover", "onPolygonLeave", "onMarkerClick", "onMarkerHover", "onMarkerLeave", "onRenderComplete", "markerRenderer"]); // controller
|
|
3405
|
+
|
|
3406
|
+
|
|
3407
|
+
var controller = useMintMapController(); // element (CanvasMarker 스타일)
|
|
3408
|
+
|
|
3409
|
+
var divRef = useRef(document.createElement('div'));
|
|
3410
|
+
var divElement = divRef.current; // canvas container ref
|
|
3411
|
+
|
|
3412
|
+
var containerRef = useRef(null); // base canvas (모든 폴리곤 - IDLE 시에만 업데이트)
|
|
3413
|
+
|
|
3414
|
+
var baseCanvasRef = useRef(null);
|
|
3415
|
+
var baseContextRef = useRef(); // hover canvas (호버된 폴리곤만 - 마우스 이동 시 업데이트)
|
|
3416
|
+
|
|
3417
|
+
var hoverCanvasRef = useRef(null);
|
|
3418
|
+
var hoverContextRef = useRef(); // base marker canvas (기본 상태 마커 - IDLE 시 업데이트)
|
|
3419
|
+
|
|
3420
|
+
var baseMarkerCanvasRef = useRef(null);
|
|
3421
|
+
var baseMarkerContextRef = useRef(); // interactive marker canvas (호버/클릭 마커 - 마우스 이동 시 업데이트)
|
|
3422
|
+
|
|
3423
|
+
var interactiveMarkerCanvasRef = useRef(null);
|
|
3424
|
+
var interactiveMarkerContextRef = useRef(); // marker
|
|
3425
|
+
|
|
3426
|
+
var markerRef = useRef(); // interaction states
|
|
3427
|
+
|
|
3428
|
+
var hoveredPolygonRef = useRef(null);
|
|
3429
|
+
var clickedPolygonRef = useRef(null);
|
|
3430
|
+
var hoveredMarkerRef = useRef(null);
|
|
3431
|
+
var clickedMarkerRef = useRef(null); // 🚀 WoongCanvasMarker 방식: 단순한 data ref (geohash 제거)
|
|
3432
|
+
|
|
3433
|
+
var polygonsRef = useRef([]);
|
|
3434
|
+
var markersRef = useRef([]); // 마커 경계 정보 저장 (hit-test용)
|
|
3435
|
+
|
|
3436
|
+
var markerBoundsRef = useRef(new Map()); // 🚀 드래그 추적용 (WoongCanvasMarker 방식)
|
|
3437
|
+
|
|
3438
|
+
var draggingRef = useRef(false);
|
|
3439
|
+
var prevCenterOffsetRef = useRef(null);
|
|
3440
|
+
var accumTranslateRef = useRef({
|
|
3441
|
+
x: 0,
|
|
3442
|
+
y: 0
|
|
3443
|
+
}); // 🚀 Offset 캐싱 (ID 문자열 기반으로 변경하여 메모리 누수 방지)
|
|
3444
|
+
|
|
3445
|
+
var MAX_CACHE_SIZE = 50000; // 최대 캐시 크기 제한 (3만개 + 여유분)
|
|
3446
|
+
|
|
3447
|
+
var polygonOffsetCacheRef = useRef(new Map());
|
|
3448
|
+
var polygonInnerOffsetCacheRef = useRef(new Map()); // innerOffsets 캐시 추가
|
|
3449
|
+
|
|
3450
|
+
var markerOffsetCacheRef = useRef(new Map()); // create object (CanvasMarker 스타일)
|
|
3451
|
+
|
|
3452
|
+
useEffect(function () {
|
|
3453
|
+
divElement.style.width = 'fit-content';
|
|
3454
|
+
divElement.style.pointerEvents = 'none'; // CanvasMarkerHanquf 방식
|
|
3455
|
+
|
|
3456
|
+
return function () {
|
|
3457
|
+
if (markerRef.current) {
|
|
3458
|
+
controller.clearDrawable(markerRef.current);
|
|
3459
|
+
markerRef.current = undefined;
|
|
3460
|
+
}
|
|
3461
|
+
};
|
|
3462
|
+
}, []); // create / update object (CanvasMarker 스타일)
|
|
3463
|
+
|
|
3464
|
+
useEffect(function () {
|
|
3465
|
+
if (options) {
|
|
3466
|
+
var bounds = controller.getCurrBounds();
|
|
3467
|
+
|
|
3468
|
+
var markerOptions = __assign({
|
|
3469
|
+
position: bounds.nw
|
|
3470
|
+
}, options);
|
|
3471
|
+
|
|
3472
|
+
if (markerRef.current) {
|
|
3473
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
3474
|
+
} else {
|
|
3475
|
+
markerRef.current = new Marker(markerOptions);
|
|
3476
|
+
markerRef.current.element = divElement;
|
|
3477
|
+
controller.createMarker(markerRef.current); // CanvasMarkerHanquf 방식: pointer events 비활성화
|
|
3478
|
+
|
|
3479
|
+
if (divElement.parentElement) {
|
|
3480
|
+
divElement.style.pointerEvents = 'none';
|
|
3481
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
3482
|
+
} // z-index 처리
|
|
3483
|
+
|
|
3484
|
+
|
|
3485
|
+
if (options.zIndex !== undefined) {
|
|
3486
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}, [options]); // 베이스 폴리곤 렌더링 (모든 폴리곤, 호버 효과 없음)
|
|
3491
|
+
|
|
3492
|
+
var renderBasePolygons = function () {
|
|
3493
|
+
if (!baseContextRef.current || !polygonsRef.current || polygonsRef.current.length === 0) return;
|
|
3494
|
+
clearRect(baseCanvasRef.current, baseContextRef.current);
|
|
3495
|
+
var cacheMissCount = 0; // 캐시 미스 카운트
|
|
3496
|
+
// 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
|
|
3497
|
+
|
|
3498
|
+
polygonsRef.current.forEach(function (polygon) {
|
|
3499
|
+
if (polygon.visible === false) return; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3500
|
+
|
|
3501
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
3502
|
+
|
|
3503
|
+
if (!offsets) {
|
|
3504
|
+
cacheMissCount++;
|
|
3505
|
+
offsets = polygon.positions.map(function (pos) {
|
|
3506
|
+
return controller.positionToOffset(pos);
|
|
3507
|
+
}); // LRU: 캐시 크기 제한
|
|
3508
|
+
|
|
3509
|
+
if (polygonOffsetCacheRef.current.size >= MAX_CACHE_SIZE) {
|
|
3510
|
+
var firstKey = polygonOffsetCacheRef.current.keys().next().value;
|
|
3511
|
+
|
|
3512
|
+
if (firstKey) {
|
|
3513
|
+
polygonOffsetCacheRef.current.delete(firstKey);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
var innerOffsets;
|
|
3521
|
+
|
|
3522
|
+
if (polygon.innerPositions) {
|
|
3523
|
+
// innerOffsets도 캐시 사용
|
|
3524
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
3525
|
+
|
|
3526
|
+
if (!innerOffsets) {
|
|
3527
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
3528
|
+
return innerPositions.map(function (pos) {
|
|
3529
|
+
return controller.positionToOffset(pos);
|
|
3530
|
+
});
|
|
3531
|
+
});
|
|
3532
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
3533
|
+
}
|
|
3534
|
+
} // 렌더러 호출 (항상 기본 스타일)
|
|
3535
|
+
|
|
3536
|
+
|
|
3537
|
+
defaultPolygonRenderer({
|
|
3538
|
+
context: baseContextRef.current,
|
|
3539
|
+
offsets: offsets,
|
|
3540
|
+
innerOffsets: innerOffsets,
|
|
3541
|
+
polygon: polygon,
|
|
3542
|
+
isHovered: false,
|
|
3543
|
+
defaultBackground: background,
|
|
3544
|
+
defaultStrokeColor: strokeColor,
|
|
3545
|
+
defaultStrokeWidth: strokeWidth,
|
|
3546
|
+
hoverBackground: background,
|
|
3547
|
+
hoverStrokeColor: strokeColor
|
|
3548
|
+
});
|
|
3549
|
+
}); // 요약 로그만 출력 (캐시 미스가 있을 때만)
|
|
3550
|
+
|
|
3551
|
+
if (cacheMissCount > 0) {
|
|
3552
|
+
console.log("\uD83C\uDFA8 [renderBasePolygons] \uCE90\uC2DC \uBBF8\uC2A4: ".concat(cacheMissCount, "\uAC1C, \uD604\uC7AC \uCE90\uC2DC \uD06C\uAE30: ").concat(polygonOffsetCacheRef.current.size));
|
|
3553
|
+
} // 렌더링 완료 콜백 호출
|
|
3554
|
+
|
|
3555
|
+
|
|
3556
|
+
if (onRenderComplete) {
|
|
3557
|
+
onRenderComplete();
|
|
3558
|
+
}
|
|
3559
|
+
}; // 베이스 마커 렌더링 (기본 상태 마커만, IDLE 시 업데이트)
|
|
3560
|
+
|
|
3561
|
+
|
|
3562
|
+
var renderBaseMarkers = function () {
|
|
3563
|
+
if (!baseMarkerContextRef.current || !markerRenderer) return;
|
|
3564
|
+
if (!markersRef.current || markersRef.current.length === 0) return;
|
|
3565
|
+
clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current); // 마커 경계 정보 초기화
|
|
3566
|
+
|
|
3567
|
+
markerBoundsRef.current.clear(); // 🚀 WoongCanvasMarker 방식: 전체 데이터 순회 (필터링 제거)
|
|
3568
|
+
|
|
3569
|
+
markersRef.current.forEach(function (marker) {
|
|
3570
|
+
var _a, _b;
|
|
3571
|
+
|
|
3572
|
+
if (marker.visible === false) return; // 호버/클릭 상태인 마커는 스킵 (인터랙션 레이어에서 렌더링)
|
|
3573
|
+
|
|
3574
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) return;
|
|
3575
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) return; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3576
|
+
|
|
3577
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
3578
|
+
|
|
3579
|
+
if (!offset) {
|
|
3580
|
+
offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
|
|
3581
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
3582
|
+
} // 커스텀 마커 렌더러 호출 (기본 상태)
|
|
3583
|
+
|
|
3584
|
+
|
|
3585
|
+
var markerBounds = markerRenderer({
|
|
3586
|
+
context: baseMarkerContextRef.current,
|
|
3587
|
+
x: offset.x,
|
|
3588
|
+
y: offset.y,
|
|
3589
|
+
marker: marker,
|
|
3590
|
+
isHovered: false,
|
|
3591
|
+
isClicked: false
|
|
3592
|
+
}); // 마커 경계 정보 저장 (hit-test용)
|
|
3593
|
+
|
|
3594
|
+
markerBoundsRef.current.set(marker.id, {
|
|
3595
|
+
marker: marker,
|
|
3596
|
+
bounds: markerBounds
|
|
3597
|
+
});
|
|
3598
|
+
});
|
|
3599
|
+
}; // 인터랙션 마커 렌더링 (호버/클릭 마커만, 마우스 이동 시 업데이트)
|
|
3600
|
+
|
|
3601
|
+
|
|
3602
|
+
var renderInteractionMarkers = function () {
|
|
3603
|
+
if (!interactiveMarkerContextRef.current || !markerRenderer) return;
|
|
3604
|
+
clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current); // 클릭된 마커 먼저 그리기 (하단 레이어)
|
|
3605
|
+
|
|
3606
|
+
if (clickedMarkerRef.current) {
|
|
3607
|
+
var marker = clickedMarkerRef.current;
|
|
3608
|
+
|
|
3609
|
+
if (marker.visible !== false) {
|
|
3610
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3611
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
3612
|
+
|
|
3613
|
+
if (!offset) {
|
|
3614
|
+
offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
|
|
3615
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
var markerBounds = markerRenderer({
|
|
3619
|
+
context: interactiveMarkerContextRef.current,
|
|
3620
|
+
x: offset.x,
|
|
3621
|
+
y: offset.y,
|
|
3622
|
+
marker: marker,
|
|
3623
|
+
isHovered: false,
|
|
3624
|
+
isClicked: true
|
|
3625
|
+
}); // 마커 경계 정보 업데이트
|
|
3626
|
+
|
|
3627
|
+
markerBoundsRef.current.set(marker.id, {
|
|
3628
|
+
marker: marker,
|
|
3629
|
+
bounds: markerBounds
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
} // 호버된 마커 나중에 그리기 (최상단 레이어)
|
|
3633
|
+
|
|
3634
|
+
|
|
3635
|
+
if (hoveredMarkerRef.current) {
|
|
3636
|
+
var marker = hoveredMarkerRef.current;
|
|
3637
|
+
|
|
3638
|
+
if (marker.visible !== false) {
|
|
3639
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3640
|
+
var offset = markerOffsetCacheRef.current.get(marker.id);
|
|
3641
|
+
|
|
3642
|
+
if (!offset) {
|
|
3643
|
+
offset = controller.positionToOffset(new Position(marker.lat, marker.lng));
|
|
3644
|
+
markerOffsetCacheRef.current.set(marker.id, offset);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
var markerBounds = markerRenderer({
|
|
3648
|
+
context: interactiveMarkerContextRef.current,
|
|
3649
|
+
x: offset.x,
|
|
3650
|
+
y: offset.y,
|
|
3651
|
+
marker: marker,
|
|
3652
|
+
isHovered: true,
|
|
3653
|
+
isClicked: false
|
|
3654
|
+
}); // 마커 경계 정보 업데이트
|
|
3655
|
+
|
|
3656
|
+
markerBoundsRef.current.set(marker.id, {
|
|
3657
|
+
marker: marker,
|
|
3658
|
+
bounds: markerBounds
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
}; // 인터랙션 폴리곤 렌더링 (호버 + 클릭)
|
|
3663
|
+
|
|
3664
|
+
|
|
3665
|
+
var renderInteractionPolygons = function () {
|
|
3666
|
+
if (!hoverContextRef.current) return; // 인터랙션 캔버스 클리어
|
|
3667
|
+
|
|
3668
|
+
clearRect(hoverCanvasRef.current, hoverContextRef.current); // 1. 클릭된 폴리곤 먼저 그리기 (하단 레이어)
|
|
3669
|
+
|
|
3670
|
+
if (clickedPolygonRef.current) {
|
|
3671
|
+
var polygon = clickedPolygonRef.current;
|
|
3672
|
+
|
|
3673
|
+
if (polygon.visible !== false) {
|
|
3674
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3675
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
3676
|
+
|
|
3677
|
+
if (!offsets) {
|
|
3678
|
+
offsets = polygon.positions.map(function (pos) {
|
|
3679
|
+
return controller.positionToOffset(pos);
|
|
3680
|
+
});
|
|
3681
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
var innerOffsets = void 0;
|
|
3685
|
+
|
|
3686
|
+
if (polygon.innerPositions) {
|
|
3687
|
+
// innerOffsets도 캐시 사용
|
|
3688
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
3689
|
+
|
|
3690
|
+
if (!innerOffsets) {
|
|
3691
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
3692
|
+
return innerPositions.map(function (pos) {
|
|
3693
|
+
return controller.positionToOffset(pos);
|
|
3694
|
+
});
|
|
3695
|
+
});
|
|
3696
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
3697
|
+
}
|
|
3698
|
+
} // 클릭 스타일 (노란색)
|
|
3699
|
+
|
|
3700
|
+
|
|
3701
|
+
defaultPolygonRenderer({
|
|
3702
|
+
context: hoverContextRef.current,
|
|
3703
|
+
offsets: offsets,
|
|
3704
|
+
innerOffsets: innerOffsets,
|
|
3705
|
+
polygon: polygon,
|
|
3706
|
+
isHovered: true,
|
|
3707
|
+
defaultBackground: background,
|
|
3708
|
+
defaultStrokeColor: strokeColor,
|
|
3709
|
+
defaultStrokeWidth: strokeWidth,
|
|
3710
|
+
hoverBackground: 'rgba(255, 200, 0, 0.6)',
|
|
3711
|
+
hoverStrokeColor: 'orange' // 주황색 테두리
|
|
3712
|
+
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
} // 2. 호버된 폴리곤 나중에 그리기 (최상단 레이어)
|
|
3716
|
+
|
|
3717
|
+
|
|
3718
|
+
if (hoveredPolygonRef.current) {
|
|
3719
|
+
var polygon = hoveredPolygonRef.current;
|
|
3720
|
+
|
|
3721
|
+
if (polygon.visible !== false) {
|
|
3722
|
+
// 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3723
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
3724
|
+
|
|
3725
|
+
if (!offsets) {
|
|
3726
|
+
offsets = polygon.positions.map(function (pos) {
|
|
3727
|
+
return controller.positionToOffset(pos);
|
|
3728
|
+
});
|
|
3729
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
var innerOffsets = void 0;
|
|
3733
|
+
|
|
3734
|
+
if (polygon.innerPositions) {
|
|
3735
|
+
// innerOffsets도 캐시 사용
|
|
3736
|
+
innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
3737
|
+
|
|
3738
|
+
if (!innerOffsets) {
|
|
3739
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
3740
|
+
return innerPositions.map(function (pos) {
|
|
3741
|
+
return controller.positionToOffset(pos);
|
|
3742
|
+
});
|
|
3743
|
+
});
|
|
3744
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
3745
|
+
}
|
|
3746
|
+
} // 호버 스타일 (빨간색)
|
|
3747
|
+
|
|
3748
|
+
|
|
3749
|
+
defaultPolygonRenderer({
|
|
3750
|
+
context: hoverContextRef.current,
|
|
3751
|
+
offsets: offsets,
|
|
3752
|
+
innerOffsets: innerOffsets,
|
|
3753
|
+
polygon: polygon,
|
|
3754
|
+
isHovered: true,
|
|
3755
|
+
defaultBackground: background,
|
|
3756
|
+
defaultStrokeColor: strokeColor,
|
|
3757
|
+
defaultStrokeWidth: strokeWidth,
|
|
3758
|
+
hoverBackground: 'rgba(255, 0, 0, 0.5)',
|
|
3759
|
+
hoverStrokeColor: 'red' // 빨간색 테두리
|
|
3760
|
+
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
}; // point-in-polygon 테스트 (ray casting algorithm)
|
|
3765
|
+
|
|
3766
|
+
|
|
3767
|
+
var pointInPolygon = function (x, y, polygon) {
|
|
3768
|
+
if (polygon.length < 3) return false;
|
|
3769
|
+
var inside = false;
|
|
3770
|
+
|
|
3771
|
+
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
3772
|
+
var xi = polygon[i].x,
|
|
3773
|
+
yi = polygon[i].y;
|
|
3774
|
+
var xj = polygon[j].x,
|
|
3775
|
+
yj = polygon[j].y;
|
|
3776
|
+
|
|
3777
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
|
|
3778
|
+
inside = !inside;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
return inside;
|
|
3783
|
+
}; // 히트 테스트 함수 (마커 우선)
|
|
3784
|
+
|
|
3785
|
+
|
|
3786
|
+
var findPolygonAt = function (x, y) {
|
|
3787
|
+
var _a, _b;
|
|
3788
|
+
|
|
3789
|
+
if (!polygonsRef.current || polygonsRef.current.length === 0) return null; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
3790
|
+
|
|
3791
|
+
if (markersRef.current) {
|
|
3792
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
3793
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
3794
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
3795
|
+
|
|
3796
|
+
if (markerData) {
|
|
3797
|
+
var bounds = markerData.bounds;
|
|
3798
|
+
|
|
3799
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
3800
|
+
return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
3804
|
+
|
|
3805
|
+
|
|
3806
|
+
if (clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
3807
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
3808
|
+
|
|
3809
|
+
if (markerData) {
|
|
3810
|
+
var bounds = markerData.bounds;
|
|
3811
|
+
|
|
3812
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
3813
|
+
return null; // 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
3817
|
+
|
|
3818
|
+
|
|
3819
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
3820
|
+
var marker = markersRef.current[i];
|
|
3821
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
3822
|
+
|
|
3823
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
3824
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
3825
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
3826
|
+
if (!markerData) continue;
|
|
3827
|
+
var bounds = markerData.bounds; // 사각형 경계 체크
|
|
3828
|
+
|
|
3829
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
3830
|
+
// 마커 히트 시 null 반환 (폴리곤이 아니므로)
|
|
3831
|
+
return null;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크 - 🚀 전체 폴리곤 순회
|
|
3835
|
+
// 역순으로 체크 (위에 그려진 것부터)
|
|
3836
|
+
|
|
3837
|
+
|
|
3838
|
+
for (var i = polygonsRef.current.length - 1; i >= 0; i--) {
|
|
3839
|
+
var polygon = polygonsRef.current[i];
|
|
3840
|
+
if (polygon.visible === false) continue; // 🚀 캐시 확인 (ID 기반으로 변경)
|
|
3841
|
+
|
|
3842
|
+
var offsets = polygonOffsetCacheRef.current.get(polygon.id);
|
|
3843
|
+
|
|
3844
|
+
if (!offsets) {
|
|
3845
|
+
offsets = polygon.positions.map(function (pos) {
|
|
3846
|
+
return controller.positionToOffset(pos);
|
|
3847
|
+
});
|
|
3848
|
+
polygonOffsetCacheRef.current.set(polygon.id, offsets);
|
|
3849
|
+
} // 외부 폴리곤 체크
|
|
3850
|
+
|
|
3851
|
+
|
|
3852
|
+
if (pointInPolygon(x, y, offsets)) {
|
|
3853
|
+
// 도넛 폴리곤인 경우 내부 구멍도 체크
|
|
3854
|
+
if (polygon.innerPositions && polygon.innerPositions.length > 0) {
|
|
3855
|
+
var innerOffsets = polygonInnerOffsetCacheRef.current.get(polygon.id);
|
|
3856
|
+
|
|
3857
|
+
if (!innerOffsets) {
|
|
3858
|
+
innerOffsets = polygon.innerPositions.map(function (innerPositions) {
|
|
3859
|
+
return innerPositions.map(function (pos) {
|
|
3860
|
+
return controller.positionToOffset(pos);
|
|
3861
|
+
});
|
|
3862
|
+
});
|
|
3863
|
+
polygonInnerOffsetCacheRef.current.set(polygon.id, innerOffsets);
|
|
3864
|
+
} // 내부 구멍 중 하나라도 포함되면 이 폴리곤은 스킵하고 다음 폴리곤 체크
|
|
3865
|
+
|
|
3866
|
+
|
|
3867
|
+
var isInHole = false;
|
|
3868
|
+
|
|
3869
|
+
for (var _i = 0, innerOffsets_1 = innerOffsets; _i < innerOffsets_1.length; _i++) {
|
|
3870
|
+
var innerOffset = innerOffsets_1[_i];
|
|
3871
|
+
|
|
3872
|
+
if (pointInPolygon(x, y, innerOffset)) {
|
|
3873
|
+
isInHole = true;
|
|
3874
|
+
break;
|
|
3875
|
+
}
|
|
3876
|
+
} // 구멍 안에 있으면 이 폴리곤은 스킵하고 다음 폴리곤 계속 검색
|
|
3877
|
+
|
|
3878
|
+
|
|
3879
|
+
if (isInHole) {
|
|
3880
|
+
continue;
|
|
3881
|
+
}
|
|
3882
|
+
} // 외부 폴리곤 안에 있고, 구멍 안에는 없으면 히트
|
|
3883
|
+
|
|
3884
|
+
|
|
3885
|
+
return polygon;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
return null;
|
|
3890
|
+
}; // IDLE 이벤트 핸들러 (WoongCanvasMarker 최적화 적용)
|
|
3891
|
+
|
|
3892
|
+
|
|
3893
|
+
var handleIdle = function () {
|
|
3894
|
+
var beforeCacheSize = polygonOffsetCacheRef.current.size;
|
|
3895
|
+
clearRect(baseCanvasRef.current, baseContextRef.current); // 표시 복구 (드래그/줌 중 숨김 처리 복원)
|
|
3896
|
+
|
|
3897
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
3898
|
+
draggingRef.current = false;
|
|
3899
|
+
prevCenterOffsetRef.current = null;
|
|
3900
|
+
accumTranslateRef.current = {
|
|
3901
|
+
x: 0,
|
|
3902
|
+
y: 0
|
|
3903
|
+
};
|
|
3904
|
+
|
|
3905
|
+
if (containerRef.current) {
|
|
3906
|
+
containerRef.current.style.transform = '';
|
|
3907
|
+
} // 🚀 캐시 무효화 (지도 이동/줌으로 좌표가 바뀌므로 전체 clear 필요)
|
|
3908
|
+
|
|
3909
|
+
|
|
3910
|
+
polygonOffsetCacheRef.current.clear();
|
|
3911
|
+
polygonInnerOffsetCacheRef.current.clear();
|
|
3912
|
+
markerOffsetCacheRef.current.clear();
|
|
3913
|
+
markerBoundsRef.current.clear(); // 캐시 clear 로그
|
|
3914
|
+
|
|
3915
|
+
if (beforeCacheSize > 0) {
|
|
3916
|
+
console.log("\uD83E\uDDF9 [IDLE] \uCE90\uC2DC \uCD08\uAE30\uD654: ".concat(beforeCacheSize, "\uAC1C \uC81C\uAC70 (\uC88C\uD45C \uAC31\uC2E0 \uD544\uC694)"));
|
|
3917
|
+
} // 마커 이동
|
|
3918
|
+
|
|
3919
|
+
|
|
3920
|
+
var bounds = controller.getCurrBounds();
|
|
3921
|
+
|
|
3922
|
+
var markerOptions = __assign({
|
|
3923
|
+
position: bounds.nw
|
|
3924
|
+
}, options);
|
|
3925
|
+
|
|
3926
|
+
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 🚀 폴리곤 렌더링 (독립)
|
|
3927
|
+
|
|
3928
|
+
if (polygonsRef.current.length > 0) {
|
|
3929
|
+
renderBasePolygons();
|
|
3930
|
+
renderInteractionPolygons();
|
|
3931
|
+
} // 🚀 마커 렌더링 (독립)
|
|
3932
|
+
|
|
3933
|
+
|
|
3934
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
3935
|
+
renderBaseMarkers();
|
|
3936
|
+
renderInteractionMarkers();
|
|
3937
|
+
}
|
|
3938
|
+
};
|
|
3939
|
+
|
|
3940
|
+
var handleZoomStart = function () {
|
|
3941
|
+
containerRef.current && (containerRef.current.style.visibility = 'hidden');
|
|
3942
|
+
};
|
|
3943
|
+
|
|
3944
|
+
var handleZoomEnd = function () {
|
|
3945
|
+
containerRef.current && (containerRef.current.style.visibility = '');
|
|
3946
|
+
}; // 지도 이벤트를 화면 좌표로 변환 (CanvasMarkerHanquf 방식)
|
|
3947
|
+
|
|
3948
|
+
|
|
3949
|
+
var parseEventToOffset = function (event) {
|
|
3950
|
+
var mapDiv = controller.mapDivElement;
|
|
3951
|
+
var rect = mapDiv.getBoundingClientRect();
|
|
3952
|
+
return {
|
|
3953
|
+
x: event.clientX - rect.left,
|
|
3954
|
+
y: event.clientY - rect.top
|
|
3955
|
+
};
|
|
3956
|
+
}; // 🚀 지도 클릭 이벤트 핸들러 (WoongCanvasMarker 방식 - controller.addEventListener)
|
|
3957
|
+
|
|
3958
|
+
|
|
3959
|
+
var handleMapClick = function (event) {
|
|
3960
|
+
var _a, _b, _c;
|
|
3961
|
+
|
|
3962
|
+
var clickedOffset = controller.positionToOffset(event.param.position); // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
3963
|
+
|
|
3964
|
+
var hitMarker = null;
|
|
3965
|
+
|
|
3966
|
+
if (markersRef.current) {
|
|
3967
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
3968
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
3969
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
3970
|
+
|
|
3971
|
+
if (markerData) {
|
|
3972
|
+
var bounds = markerData.bounds;
|
|
3973
|
+
|
|
3974
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
3975
|
+
hitMarker = hoveredMarkerRef.current;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
3979
|
+
|
|
3980
|
+
|
|
3981
|
+
if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
3982
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
3983
|
+
|
|
3984
|
+
if (markerData) {
|
|
3985
|
+
var bounds = markerData.bounds;
|
|
3986
|
+
|
|
3987
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
3988
|
+
hitMarker = clickedMarkerRef.current;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
3992
|
+
|
|
3993
|
+
|
|
3994
|
+
if (!hitMarker) {
|
|
3995
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
3996
|
+
var marker = markersRef.current[i];
|
|
3997
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
3998
|
+
|
|
3999
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
4000
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
4001
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
4002
|
+
if (!markerData) continue;
|
|
4003
|
+
var bounds = markerData.bounds;
|
|
4004
|
+
|
|
4005
|
+
if (clickedOffset.x >= bounds.x && clickedOffset.x <= bounds.x + bounds.width && clickedOffset.y >= bounds.y && clickedOffset.y <= bounds.y + bounds.height) {
|
|
4006
|
+
hitMarker = marker;
|
|
4007
|
+
break;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크
|
|
4012
|
+
|
|
4013
|
+
|
|
4014
|
+
var hitPolygon = findPolygonAt(clickedOffset.x, clickedOffset.y); // 마커 클릭 해제
|
|
4015
|
+
|
|
4016
|
+
if (clickedMarkerRef.current) {
|
|
4017
|
+
clickedMarkerRef.current = null;
|
|
4018
|
+
renderInteractionMarkers();
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
if (hitPolygon) {
|
|
4022
|
+
// 같은 폴리곤 다시 클릭하면 선택 해제 (토글)
|
|
4023
|
+
if (((_c = clickedPolygonRef.current) === null || _c === void 0 ? void 0 : _c.id) === hitPolygon.id) {
|
|
4024
|
+
console.log('🖱️ Polygon deselected:', hitPolygon.id);
|
|
4025
|
+
clickedPolygonRef.current = null; // 선택 해제 시에도 콜백 호출 (null 전달)
|
|
4026
|
+
|
|
4027
|
+
if (onPolygonClick) {
|
|
4028
|
+
onPolygonClick(null);
|
|
4029
|
+
}
|
|
4030
|
+
} else {
|
|
4031
|
+
console.log('🖱️ Polygon selected:', hitPolygon.id);
|
|
4032
|
+
clickedPolygonRef.current = hitPolygon; // 선택 시 콜백 호출
|
|
4033
|
+
|
|
4034
|
+
if (onPolygonClick) {
|
|
4035
|
+
onPolygonClick(hitPolygon);
|
|
4036
|
+
}
|
|
4037
|
+
} // 인터랙션 레이어 재렌더링
|
|
4038
|
+
|
|
4039
|
+
|
|
4040
|
+
renderInteractionPolygons();
|
|
4041
|
+
}
|
|
4042
|
+
}; // 마우스 이벤트 RAF 스로틀링용
|
|
4043
|
+
|
|
4044
|
+
|
|
4045
|
+
var rafIdRef = useRef(); // 마우스 이벤트 핸들러 (requestAnimationFrame으로 최적화)
|
|
4046
|
+
|
|
4047
|
+
var handleMouseMove = function (e) {
|
|
4048
|
+
// 🚀 드래그 중이면 호버 처리 스킵 (WoongCanvasMarker 방식)
|
|
4049
|
+
if (draggingRef.current) return; // 이미 RAF가 대기 중이면 스킵
|
|
4050
|
+
|
|
4051
|
+
if (rafIdRef.current) return;
|
|
4052
|
+
rafIdRef.current = requestAnimationFrame(function () {
|
|
4053
|
+
var _a, _b, _c;
|
|
4054
|
+
|
|
4055
|
+
rafIdRef.current = undefined;
|
|
4056
|
+
|
|
4057
|
+
var _d = parseEventToOffset(e),
|
|
4058
|
+
x = _d.x,
|
|
4059
|
+
y = _d.y; // 1. 마커 먼저 체크 (우선순위: hover 중 → clicked 중 → 배열 역순)
|
|
4060
|
+
|
|
4061
|
+
|
|
4062
|
+
var hitMarker = null;
|
|
4063
|
+
|
|
4064
|
+
if (markersRef.current) {
|
|
4065
|
+
// 🎯 최우선: 현재 hover 중인 마커가 있으면 그것부터 체크
|
|
4066
|
+
if (hoveredMarkerRef.current && hoveredMarkerRef.current.visible !== false) {
|
|
4067
|
+
var markerData = markerBoundsRef.current.get(hoveredMarkerRef.current.id);
|
|
4068
|
+
|
|
4069
|
+
if (markerData) {
|
|
4070
|
+
var bounds = markerData.bounds;
|
|
4071
|
+
|
|
4072
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
4073
|
+
hitMarker = hoveredMarkerRef.current;
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
} // 🎯 두 번째: hover 중인 마커에 히트 안 되고, clicked 마커가 있으면 체크
|
|
4077
|
+
|
|
4078
|
+
|
|
4079
|
+
if (!hitMarker && clickedMarkerRef.current && clickedMarkerRef.current.visible !== false) {
|
|
4080
|
+
var markerData = markerBoundsRef.current.get(clickedMarkerRef.current.id);
|
|
4081
|
+
|
|
4082
|
+
if (markerData) {
|
|
4083
|
+
var bounds = markerData.bounds;
|
|
4084
|
+
|
|
4085
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
4086
|
+
hitMarker = clickedMarkerRef.current;
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
} // 🎯 세 번째: 나머지 마커들을 역순으로 체크 (위에 그려진 것부터)
|
|
4090
|
+
|
|
4091
|
+
|
|
4092
|
+
if (!hitMarker) {
|
|
4093
|
+
for (var i = markersRef.current.length - 1; i >= 0; i--) {
|
|
4094
|
+
var marker = markersRef.current[i];
|
|
4095
|
+
if (marker.visible === false) continue; // 이미 체크한 hover/clicked 마커는 스킵
|
|
4096
|
+
|
|
4097
|
+
if (marker.id === ((_a = hoveredMarkerRef.current) === null || _a === void 0 ? void 0 : _a.id)) continue;
|
|
4098
|
+
if (marker.id === ((_b = clickedMarkerRef.current) === null || _b === void 0 ? void 0 : _b.id)) continue;
|
|
4099
|
+
var markerData = markerBoundsRef.current.get(marker.id);
|
|
4100
|
+
if (!markerData) continue;
|
|
4101
|
+
var bounds = markerData.bounds;
|
|
4102
|
+
|
|
4103
|
+
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
|
|
4104
|
+
hitMarker = marker;
|
|
4105
|
+
break;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
} // 마커가 호버되었을 때
|
|
4110
|
+
|
|
4111
|
+
|
|
4112
|
+
if (hitMarker) {
|
|
4113
|
+
// 폴리곤 호버 해제
|
|
4114
|
+
if (hoveredPolygonRef.current) {
|
|
4115
|
+
hoveredPolygonRef.current = null;
|
|
4116
|
+
|
|
4117
|
+
if (onPolygonLeave) {
|
|
4118
|
+
onPolygonLeave();
|
|
4119
|
+
}
|
|
4120
|
+
} // 마커 호버 상태 업데이트
|
|
4121
|
+
|
|
4122
|
+
|
|
4123
|
+
if (hitMarker.id !== ((_c = hoveredMarkerRef.current) === null || _c === void 0 ? void 0 : _c.id)) {
|
|
4124
|
+
hoveredMarkerRef.current = hitMarker;
|
|
4125
|
+
console.log('🎯 Marker hovered:', hitMarker.id);
|
|
4126
|
+
|
|
4127
|
+
if (onMarkerHover) {
|
|
4128
|
+
onMarkerHover(hitMarker);
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
renderInteractionMarkers();
|
|
4132
|
+
renderInteractionPolygons();
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
return;
|
|
4136
|
+
} // 2. 마커에 히트 안 되면 폴리곤 체크
|
|
4137
|
+
|
|
4138
|
+
|
|
4139
|
+
var hitPolygon = findPolygonAt(x, y); // 마커 호버 해제
|
|
4140
|
+
|
|
4141
|
+
if (hoveredMarkerRef.current) {
|
|
4142
|
+
if (onMarkerLeave) {
|
|
4143
|
+
onMarkerLeave();
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
hoveredMarkerRef.current = null;
|
|
4147
|
+
renderInteractionMarkers();
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
if (hitPolygon !== hoveredPolygonRef.current) {
|
|
4151
|
+
// leave 이벤트
|
|
4152
|
+
if (hoveredPolygonRef.current && onPolygonLeave) {
|
|
4153
|
+
onPolygonLeave();
|
|
4154
|
+
} // hover 이벤트
|
|
4155
|
+
|
|
4156
|
+
|
|
4157
|
+
hoveredPolygonRef.current = hitPolygon;
|
|
4158
|
+
|
|
4159
|
+
if (hitPolygon && onPolygonHover) {
|
|
4160
|
+
onPolygonHover(hitPolygon);
|
|
4161
|
+
} // 인터랙션 레이어 재렌더링 (호버 + 클릭)
|
|
4162
|
+
|
|
4163
|
+
|
|
4164
|
+
renderInteractionPolygons();
|
|
4165
|
+
}
|
|
4166
|
+
});
|
|
4167
|
+
};
|
|
4168
|
+
|
|
4169
|
+
var handleCenterChanged = function () {
|
|
4170
|
+
// 드래그 중에는 리렌더 대신 transform 으로만 추종
|
|
4171
|
+
var center = controller.getCurrBounds().getCenter();
|
|
4172
|
+
var curr = controller.positionToOffset(center);
|
|
4173
|
+
var prev = prevCenterOffsetRef.current;
|
|
4174
|
+
|
|
4175
|
+
if (!prev) {
|
|
4176
|
+
prevCenterOffsetRef.current = {
|
|
4177
|
+
x: curr.x,
|
|
4178
|
+
y: curr.y
|
|
4179
|
+
};
|
|
4180
|
+
draggingRef.current = true;
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
var dx = prev.x - curr.x;
|
|
4185
|
+
var dy = prev.y - curr.y;
|
|
4186
|
+
accumTranslateRef.current = {
|
|
4187
|
+
x: accumTranslateRef.current.x + dx,
|
|
4188
|
+
y: accumTranslateRef.current.y + dy
|
|
4189
|
+
};
|
|
4190
|
+
prevCenterOffsetRef.current = {
|
|
4191
|
+
x: curr.x,
|
|
4192
|
+
y: curr.y
|
|
4193
|
+
};
|
|
4194
|
+
draggingRef.current = true;
|
|
4195
|
+
|
|
4196
|
+
if (containerRef.current) {
|
|
4197
|
+
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
4198
|
+
}
|
|
4199
|
+
};
|
|
4200
|
+
|
|
4201
|
+
var handleMouseLeave = function () {
|
|
4202
|
+
// RAF 취소
|
|
4203
|
+
if (rafIdRef.current) {
|
|
4204
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
4205
|
+
rafIdRef.current = undefined;
|
|
4206
|
+
}
|
|
4207
|
+
|
|
4208
|
+
if (hoveredPolygonRef.current && onPolygonLeave) {
|
|
4209
|
+
onPolygonLeave();
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
hoveredPolygonRef.current = null;
|
|
4213
|
+
hoveredMarkerRef.current = null;
|
|
4214
|
+
renderInteractionMarkers();
|
|
4215
|
+
renderInteractionPolygons();
|
|
4216
|
+
}; // 🚀 initialize - 폴리곤 캔버스 초기화 (독립)
|
|
4217
|
+
|
|
4218
|
+
|
|
4219
|
+
useEffect(function () {
|
|
4220
|
+
if (baseCanvasRef.current && hoverCanvasRef.current && containerRef.current) {
|
|
4221
|
+
// 2d 컨텍스트 설정 (폴리곤 캔버스만)
|
|
4222
|
+
baseContextRef.current = baseCanvasRef.current.getContext('2d');
|
|
4223
|
+
hoverContextRef.current = hoverCanvasRef.current.getContext('2d'); // 스케일링 (폴리곤 캔버스만)
|
|
4224
|
+
|
|
4225
|
+
if (baseContextRef.current) {
|
|
4226
|
+
scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4229
|
+
if (hoverContextRef.current) {
|
|
4230
|
+
scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
|
|
4231
|
+
} // 폴리곤 데이터가 있으면 렌더링
|
|
4232
|
+
|
|
4233
|
+
|
|
4234
|
+
if (polygonsRef.current.length > 0) {
|
|
4235
|
+
renderBasePolygons();
|
|
4236
|
+
renderInteractionPolygons();
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
}, []); // 🚀 initialize - 마커 캔버스 초기화 (독립)
|
|
4240
|
+
|
|
4241
|
+
useEffect(function () {
|
|
4242
|
+
if (baseMarkerCanvasRef.current && interactiveMarkerCanvasRef.current) {
|
|
4243
|
+
// 2d 컨텍스트 설정 (마커 캔버스만)
|
|
4244
|
+
baseMarkerContextRef.current = baseMarkerCanvasRef.current.getContext('2d');
|
|
4245
|
+
interactiveMarkerContextRef.current = interactiveMarkerCanvasRef.current.getContext('2d'); // 스케일링 (마커 캔버스만)
|
|
4246
|
+
|
|
4247
|
+
if (baseMarkerContextRef.current) {
|
|
4248
|
+
scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
if (interactiveMarkerContextRef.current) {
|
|
4252
|
+
scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
4253
|
+
} // 마커 데이터가 있으면 렌더링
|
|
4254
|
+
|
|
4255
|
+
|
|
4256
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
4257
|
+
renderBaseMarkers();
|
|
4258
|
+
renderInteractionMarkers();
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
}, [markerRenderer]); // 🚀 공통 초기화 - 이벤트 등록 및 리사이즈
|
|
4262
|
+
|
|
4263
|
+
useEffect(function () {
|
|
4264
|
+
var resizeObserver;
|
|
4265
|
+
|
|
4266
|
+
if (containerRef.current) {
|
|
4267
|
+
// 리사이즈 처리
|
|
4268
|
+
resizeObserver = new ResizeObserver(function () {
|
|
4269
|
+
// 폴리곤 캔버스 리사이즈
|
|
4270
|
+
if (baseCanvasRef.current && baseContextRef.current) {
|
|
4271
|
+
clearRect(baseCanvasRef.current, baseContextRef.current);
|
|
4272
|
+
scaleCanvas(controller, baseCanvasRef.current, baseContextRef.current);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
if (hoverCanvasRef.current && hoverContextRef.current) {
|
|
4276
|
+
clearRect(hoverCanvasRef.current, hoverContextRef.current);
|
|
4277
|
+
scaleCanvas(controller, hoverCanvasRef.current, hoverContextRef.current);
|
|
4278
|
+
} // 마커 캔버스 리사이즈
|
|
4279
|
+
|
|
4280
|
+
|
|
4281
|
+
if (baseMarkerCanvasRef.current && baseMarkerContextRef.current) {
|
|
4282
|
+
clearRect(baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
4283
|
+
scaleCanvas(controller, baseMarkerCanvasRef.current, baseMarkerContextRef.current);
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
if (interactiveMarkerCanvasRef.current && interactiveMarkerContextRef.current) {
|
|
4287
|
+
clearRect(interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
4288
|
+
scaleCanvas(controller, interactiveMarkerCanvasRef.current, interactiveMarkerContextRef.current);
|
|
4289
|
+
} // 폴리곤 렌더링
|
|
4290
|
+
|
|
4291
|
+
|
|
4292
|
+
if (polygonsRef.current.length > 0) {
|
|
4293
|
+
renderBasePolygons();
|
|
4294
|
+
renderInteractionPolygons();
|
|
4295
|
+
} // 마커 렌더링
|
|
4296
|
+
|
|
4297
|
+
|
|
4298
|
+
if (markersRef.current.length > 0 && markerRenderer) {
|
|
4299
|
+
renderBaseMarkers();
|
|
4300
|
+
renderInteractionMarkers();
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
resizeObserver.observe(controller.mapDivElement); // 🚀 이벤트 등록 (한 번만)
|
|
4304
|
+
|
|
4305
|
+
controller.addEventListener('IDLE', handleIdle);
|
|
4306
|
+
controller.addEventListener('ZOOMSTART', handleZoomStart);
|
|
4307
|
+
controller.addEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
4308
|
+
controller.addEventListener('CLICK', handleMapClick);
|
|
4309
|
+
controller.addEventListener('CENTER_CHANGED', handleCenterChanged); // 지도 div에 마우스 이벤트 등록
|
|
4310
|
+
|
|
4311
|
+
controller.mapDivElement.addEventListener('mousemove', handleMouseMove);
|
|
4312
|
+
controller.mapDivElement.addEventListener('mouseleave', handleMouseLeave);
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
return function () {
|
|
4316
|
+
// RAF 정리
|
|
4317
|
+
if (rafIdRef.current) {
|
|
4318
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
resizeObserver && resizeObserver.disconnect();
|
|
4322
|
+
controller.removeEventListener('IDLE', handleIdle);
|
|
4323
|
+
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
4324
|
+
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
4325
|
+
controller.removeEventListener('CLICK', handleMapClick);
|
|
4326
|
+
controller.removeEventListener('CENTER_CHANGED', handleCenterChanged);
|
|
4327
|
+
controller.mapDivElement.removeEventListener('mousemove', handleMouseMove);
|
|
4328
|
+
controller.mapDivElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
4329
|
+
};
|
|
4330
|
+
}, []); // 🚀 polygons 데이터 업데이트 (독립)
|
|
4331
|
+
|
|
4332
|
+
useEffect(function () {
|
|
4333
|
+
console.log('📦 [useEffect] polygons 변경:', polygons === null || polygons === void 0 ? void 0 : polygons.length, '개'); // refs 업데이트
|
|
4334
|
+
|
|
4335
|
+
polygonsRef.current = polygons || []; // 🚀 폴리곤만 렌더링 (마커와 독립)
|
|
4336
|
+
|
|
4337
|
+
if (baseContextRef.current) {
|
|
4338
|
+
renderBasePolygons();
|
|
4339
|
+
renderInteractionPolygons();
|
|
4340
|
+
}
|
|
4341
|
+
}, [polygons]); // 🚀 markers 데이터 업데이트 (독립)
|
|
4342
|
+
|
|
4343
|
+
useEffect(function () {
|
|
4344
|
+
console.log('📦 [useEffect] markers 변경:', markers === null || markers === void 0 ? void 0 : markers.length, '개'); // refs 업데이트
|
|
4345
|
+
|
|
4346
|
+
markersRef.current = markers || []; // 🚀 마커만 렌더링 (폴리곤과 독립)
|
|
4347
|
+
|
|
4348
|
+
if (baseMarkerContextRef.current && markerRenderer) {
|
|
4349
|
+
renderBaseMarkers();
|
|
4350
|
+
renderInteractionMarkers();
|
|
4351
|
+
}
|
|
4352
|
+
}, [markers, markerRenderer]); // CanvasMarker 스타일의 createPortal return
|
|
4353
|
+
|
|
4354
|
+
return createPortal(React.createElement("div", {
|
|
4355
|
+
ref: containerRef,
|
|
4356
|
+
style: {
|
|
4357
|
+
position: 'absolute',
|
|
4358
|
+
width: '100%',
|
|
4359
|
+
height: '100%',
|
|
4360
|
+
pointerEvents: 'none'
|
|
4361
|
+
}
|
|
4362
|
+
}, React.createElement("canvas", {
|
|
4363
|
+
ref: baseCanvasRef,
|
|
4364
|
+
style: {
|
|
4365
|
+
position: 'absolute',
|
|
4366
|
+
top: 0,
|
|
4367
|
+
left: 0,
|
|
4368
|
+
pointerEvents: 'revert-layer',
|
|
4369
|
+
width: '100%',
|
|
4370
|
+
height: '100%'
|
|
4371
|
+
}
|
|
4372
|
+
}), React.createElement("canvas", {
|
|
4373
|
+
ref: hoverCanvasRef,
|
|
4374
|
+
style: {
|
|
4375
|
+
position: 'absolute',
|
|
4376
|
+
top: 0,
|
|
4377
|
+
left: 0,
|
|
4378
|
+
pointerEvents: 'none',
|
|
4379
|
+
width: '100%',
|
|
4380
|
+
height: '100%'
|
|
4381
|
+
}
|
|
4382
|
+
}), React.createElement("canvas", {
|
|
4383
|
+
ref: baseMarkerCanvasRef,
|
|
4384
|
+
style: {
|
|
4385
|
+
position: 'absolute',
|
|
4386
|
+
top: 0,
|
|
4387
|
+
left: 0,
|
|
4388
|
+
pointerEvents: 'none',
|
|
4389
|
+
width: '100%',
|
|
4390
|
+
height: '100%'
|
|
4391
|
+
}
|
|
4392
|
+
}), React.createElement("canvas", {
|
|
4393
|
+
ref: interactiveMarkerCanvasRef,
|
|
4394
|
+
style: {
|
|
4395
|
+
position: 'absolute',
|
|
4396
|
+
top: 0,
|
|
4397
|
+
left: 0,
|
|
4398
|
+
pointerEvents: 'none',
|
|
4399
|
+
width: '100%',
|
|
4400
|
+
height: '100%'
|
|
4401
|
+
}
|
|
4402
|
+
})), divElement);
|
|
4403
|
+
});
|
|
4404
|
+
|
|
2679
4405
|
/**
|
|
2680
4406
|
* Mint Map 컴포넌트
|
|
2681
4407
|
*
|
|
@@ -5911,4 +7637,4 @@ function MintMap(_a) {
|
|
|
5911
7637
|
}), loading));
|
|
5912
7638
|
}
|
|
5913
7639
|
|
|
5914
|
-
export { AnimationPlayer, Bounds, CanvasMarker, CircleMarker, Drawable, GeoCalulator, GoogleMintMapController, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SVGCircle, SVGPolygon, SVGRect, Spacing, Status, getClusterInfo, getMapOfType, log, useMarkerMoving, useMintMapController, waiting };
|
|
7640
|
+
export { AnimationPlayer, Bounds, CanvasMarker, CanvasMarkerClaude, CanvasMarkerHanquf, CircleMarker, Drawable, GeoCalulator, GoogleMintMapController, MapBuildingProjection, MapCanvasMarkerWrapper, MapCanvasWrapper, MapControlWrapper, MapEvent, MapLoadingWithImage, MapMarkerWrapper, MapPolygonWrapper, MapPolylineWrapper, MapUIEvent, Marker, MintMap, MintMapCanvasRenderer, MintMapController, MintMapCore, MintMapProvider, NaverMintMapController, Offset, PointLoading, Polygon, PolygonCalculator, PolygonMarker, Polyline, Position, SVGCircle, SVGPolygon, SVGRect, Spacing, Status, getClusterInfo, getMapOfType, log, useMarkerMoving, useMintMapController, waiting };
|