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