@hzab/map-combine 0.4.2-alpha.1 → 0.4.2-alpha.12

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +5 -5
  3. package/src/amap/AMap.ts +318 -306
  4. package/src/amap/AMapEvent.ts +110 -54
  5. package/src/amap/AMapLayer.ts +241 -0
  6. package/src/amap/AMapPoint.ts +140 -143
  7. package/src/amap/AMapPolygon.ts +214 -195
  8. package/src/amap/AMapPolyline.ts +199 -185
  9. package/src/basic/BasicBusiness.ts +5 -5
  10. package/src/basic/BasicModel.ts +49 -0
  11. package/src/basic/Layer.ts +154 -0
  12. package/src/basic/MapCombine.ts +105 -98
  13. package/src/basic/MapElement.ts +2 -1
  14. package/src/basic/MapEvent.ts +5 -0
  15. package/src/basic/Point.ts +1 -0
  16. package/src/basic/PointAggregation.tsx +22 -11
  17. package/src/basic/ReactPopup.tsx +3 -0
  18. package/src/basic/Tile3D.ts +0 -1
  19. package/src/cesium/CesiumEvent.ts +97 -84
  20. package/src/cesium/CesiumLayer.ts +365 -0
  21. package/src/cesium/CesiumMap.ts +363 -347
  22. package/src/cesium/CesiumModel.ts +64 -0
  23. package/src/cesium/CesiumPoint.ts +3 -0
  24. package/src/cesium/CesiumPolygon.ts +2 -0
  25. package/src/cesium/CesiumPolyline.ts +185 -47
  26. package/src/cesium/CesiumTile3D.ts +222 -225
  27. package/src/mine/MineEvent.ts +70 -46
  28. package/src/mine/MineMap.ts +217 -125
  29. package/src/mine/MinePoint.ts +7 -1
  30. package/src/mine/MinePolygon.ts +6 -1
  31. package/src/mine/MinePolyline.ts +6 -1
  32. package/src/openlayer/OpenlayerEvent.ts +109 -87
  33. package/src/openlayer/OpenlayerMap.ts +33 -6
  34. package/src/openlayer/OpenlayerPoint.ts +6 -1
  35. package/src/openlayer/OpenlayerPolygon.ts +6 -1
  36. package/src/openlayer/OpenlayerPolyline.ts +8 -1
  37. package/src/utils/color.ts +49 -0
  38. package/src/utils/index.ts +3 -0
  39. package/src/utils/points-viewer.ts +27 -1
  40. package/src/utils/turfjs-utils.ts +52 -0
  41. package/src/utils/types.d.ts +12 -0
@@ -1,54 +1,110 @@
1
- import { AMap } from "./AMap";
2
-
3
- /**
4
- * 高德地图事件
5
- * 高德地图 元素事件由各自元素处理
6
- * @param map
7
- */
8
- export function bindEvent(map: AMap) {
9
- const { event, viewer, container } = map;
10
-
11
- const frame = () => {
12
- if (map.isAlive) {
13
- requestAnimationFrame(frame);
14
- event.trigger("view-preRender");
15
- }
16
- };
17
- frame();
18
-
19
- viewer.on("moveend", (e) => {
20
- event.trigger("view-moveEnd");
21
- event.trigger("view-change");
22
- });
23
-
24
- container.addEventListener("mousedown", (e) => {
25
- event.trigger("pointer-down");
26
- });
27
-
28
- container.addEventListener("mouseup", (e) => {
29
- event.trigger("pointer-up");
30
- });
31
-
32
- // 解决 ReactPoint 点击事件无法触发的问题
33
- container.addEventListener("click", (e) => {
34
- event.trigger("left-click");
35
- });
36
-
37
- viewer.on("mousemove", (e) => {
38
- event.trigger("pointer-move");
39
- event.canvas = [e.pixel.x, e.pixel.y];
40
- event.geography = [e.lnglat.lng, e.lnglat.lat];
41
- });
42
-
43
- viewer.on("mapmove", () => {
44
- event.trigger("view-change");
45
- });
46
-
47
- viewer.on("click", (e) => {
48
- event.trigger("left-click");
49
- });
50
-
51
- viewer.on("dblclick", (e) => {
52
- map.event.trigger("double-click");
53
- });
54
- }
1
+ import { pointToLineDistance } from "@turf/turf";
2
+
3
+ import { AMap } from "./AMap";
4
+
5
+ /**
6
+ * 高德地图事件
7
+ * 高德地图 元素事件由各自元素处理
8
+ * @param map
9
+ */
10
+ export function bindEvent(map: AMap) {
11
+ const { event, viewer, container } = map;
12
+
13
+ const frame = () => {
14
+ if (map.isAlive) {
15
+ requestAnimationFrame(frame);
16
+ event.trigger("view-preRender");
17
+ map.bussinesses.forEach((e) => e.show && e.onFrame?.());
18
+ }
19
+ };
20
+ frame();
21
+
22
+ viewer.on("moveend", (e) => {
23
+ event.trigger("view-moveEnd");
24
+ event.trigger("view-change");
25
+ });
26
+
27
+ container.addEventListener("mousedown", (e) => {
28
+ event.trigger("pointer-down");
29
+ });
30
+
31
+ container.addEventListener("mouseup", (e) => {
32
+ event.trigger("pointer-up");
33
+ });
34
+
35
+ // 解决 ReactPoint 点击事件无法触发的问题
36
+ container.addEventListener("click", (e) => {
37
+ event.trigger("left-click");
38
+ });
39
+
40
+ viewer.on("mousemove", (e) => {
41
+ event.trigger("pointer-move");
42
+ event.canvas = [e.pixel.x, e.pixel.y];
43
+ event.geography = [e.lnglat.lng, e.lnglat.lat];
44
+ });
45
+
46
+ viewer.on("mapmove", () => {
47
+ event.trigger("view-change");
48
+ });
49
+
50
+ viewer.on("click", (e) => {
51
+ event.trigger("left-click");
52
+
53
+ // "drill-pick" 获取点击位置的元素
54
+ const clickPosition = e.lnglat;
55
+ const position = [clickPosition.lng, clickPosition.lat];
56
+ const allOverlays = viewer.getAllOverlays(); // 获取所有覆盖物
57
+ const pickedObjects = [];
58
+
59
+ // 遍历所有覆盖物,检查点击位置是否在覆盖物内
60
+ allOverlays.forEach((overlay) => {
61
+ // 判断是否为多边形
62
+ if (overlay instanceof map.LbsAMap.Polygon) {
63
+ // 使用 contains 方法判断点是否在多边形内
64
+ if (overlay.contains(clickPosition)) {
65
+ pickedObjects.push(overlay);
66
+ }
67
+ }
68
+ // 判断是否为折线
69
+ else if (overlay instanceof map.LbsAMap.Polyline) {
70
+ // 使用点线距离计算
71
+ const path = overlay.getPath();
72
+ const coords = path.map((p) => [p.getLng(), p.getLat()]);
73
+ const distance = pointToLineDistance(position, coords, { units: "meters" });
74
+
75
+ // 获取当前地图的分辨率(米/像素) 每像素代表多少米
76
+ const resolution = viewer.getResolution();
77
+ // 计算线宽对应的地理距离(单边)
78
+ const halfWidthInMeters = (overlay.getOptions().strokeWeight / 2) * resolution;
79
+
80
+ // 根据距离判断是否点击
81
+ if (distance < halfWidthInMeters + 2) {
82
+ pickedObjects.push(overlay);
83
+ }
84
+ }
85
+ // 判断是否为 Marker
86
+ else if (overlay instanceof map.LbsAMap.Marker) {
87
+ const position = overlay.getPosition();
88
+ const distance = map.LbsAMap.GeometryUtil.distance(position, clickPosition);
89
+ if (distance < 20) {
90
+ // 点击位置距离 Marker 小于 20 像素
91
+ pickedObjects.push(overlay);
92
+ }
93
+ }
94
+ });
95
+
96
+ // 按 zIndex 从高到低排序
97
+ pickedObjects.sort((a, b) => (b.getzIndex?.() || 0) - (a.getzIndex?.() || 0));
98
+
99
+ event.trigger("drill-pick", {
100
+ event: e,
101
+ pickedTargets: pickedObjects.map((it) => it.getOptions()?.extData?.target),
102
+ sourceObjects: pickedObjects,
103
+ position: position,
104
+ });
105
+ });
106
+
107
+ viewer.on("dblclick", (e) => {
108
+ map.event.trigger("double-click");
109
+ });
110
+ }
@@ -0,0 +1,241 @@
1
+ export function drawLayer(map, option) {
2
+ console.log("option.url", option.url);
3
+ const urlTemplate = option.url || "";
4
+ const self = map;
5
+
6
+ // 1. 获取地图容器
7
+ const container = map.viewer.getContainer();
8
+
9
+ // 2. 手动创建 canvas 并添加到容器
10
+ const canvas = document.createElement("canvas");
11
+ const mapSize = map.viewer.getSize();
12
+ canvas.width = mapSize.width;
13
+ canvas.height = mapSize.height;
14
+ canvas.style.cssText = `
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ height: 100%;
20
+ z-index: 1000;
21
+ pointer-events: none;
22
+ `;
23
+
24
+ container.appendChild(canvas);
25
+ console.log("Canvas 已手动添加到容器,尺寸:", canvas.width, canvas.height);
26
+
27
+ // 3. 瓦片缓存
28
+ const tileCache = new Map();
29
+
30
+ // 4. 经纬度转瓦片坐标
31
+ function lngLatToTile(lng: number, lat: number, zoom: number) {
32
+ const n = Math.pow(2, zoom);
33
+ const x = ((lng + 180) / 360) * n;
34
+ const y = ((1 - Math.log(Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)) / Math.PI) / 2) * n;
35
+ return { x: Math.floor(x), y: Math.floor(y) };
36
+ }
37
+
38
+ // 5. 瓦片坐标转经纬度
39
+ function tileToLngLat(tileX: number, tileY: number, zoom: number) {
40
+ const n = Math.pow(2, zoom);
41
+ const lng = (tileX / n) * 360 - 180;
42
+ const lat = (Math.atan(Math.sinh(Math.PI * (1 - (2 * tileY) / n))) * 180) / Math.PI;
43
+ return { lng, lat };
44
+ }
45
+
46
+ // 6. 获取当前视口的瓦片范围(增加预加载扩展)
47
+ function getTileRange() {
48
+ const zoom = self.viewer.getZoom();
49
+ const level = Math.floor(zoom);
50
+
51
+ const bounds = self.viewer.getBounds();
52
+ if (!bounds) {
53
+ return null;
54
+ }
55
+
56
+ const sw = bounds.getSouthWest();
57
+ const ne = bounds.getNorthEast();
58
+
59
+ // 扩展范围,预加载周边瓦片(移动时更流畅)
60
+ const expandFactor = 0.3;
61
+ const lngMin = sw.getLng() - (ne.getLng() - sw.getLng()) * expandFactor;
62
+ const lngMax = ne.getLng() + (ne.getLng() - sw.getLng()) * expandFactor;
63
+ const latMin = sw.getLat() - (ne.getLat() - sw.getLat()) * expandFactor;
64
+ const latMax = ne.getLat() + (ne.getLat() - sw.getLat()) * expandFactor;
65
+
66
+ const topLeft = lngLatToTile(lngMin, latMax, level);
67
+ const bottomRight = lngLatToTile(lngMax, latMin, level);
68
+
69
+ return {
70
+ minX: topLeft.x,
71
+ maxX: bottomRight.x,
72
+ minY: topLeft.y,
73
+ maxY: bottomRight.y,
74
+ level: level,
75
+ };
76
+ }
77
+
78
+ // 7. 绘制单张瓦片
79
+ function drawSingleTile(
80
+ ctx: CanvasRenderingContext2D,
81
+ img: HTMLImageElement,
82
+ tileX: number,
83
+ tileY: number,
84
+ zoom: number,
85
+ ) {
86
+ const nw = tileToLngLat(tileX, tileY, zoom);
87
+ const se = tileToLngLat(tileX + 1, tileY + 1, zoom);
88
+
89
+ const pNW = self.viewer.lngLatToContainer(new self.LbsAMap.LngLat(nw.lng, nw.lat));
90
+ const pSE = self.viewer.lngLatToContainer(new self.LbsAMap.LngLat(se.lng, se.lat));
91
+
92
+ const minX = Math.min(pNW.x, pSE.x);
93
+ const maxX = Math.max(pNW.x, pSE.x);
94
+ const minY = Math.min(pNW.y, pSE.y);
95
+ const maxY = Math.max(pNW.y, pSE.y);
96
+
97
+ const width = maxX - minX;
98
+ const height = maxY - minY;
99
+
100
+ const padding = 100;
101
+ if (maxX < -padding || minX > canvas.width + padding || maxY < -padding || minY > canvas.height + padding) {
102
+ return;
103
+ }
104
+
105
+ ctx.imageSmoothingEnabled = true;
106
+ ctx.imageSmoothingQuality = "high";
107
+ ctx.drawImage(img, minX, minY, width, height);
108
+ }
109
+
110
+ // 8. 主加载函数
111
+ let isLoading = false;
112
+ let pendingTiles = 0;
113
+ let animationId: number | null = null;
114
+
115
+ function loadAndDrawTiles() {
116
+ if (animationId !== null) {
117
+ cancelAnimationFrame(animationId);
118
+ animationId = null;
119
+ }
120
+
121
+ animationId = requestAnimationFrame(() => {
122
+ const ctx = canvas.getContext("2d");
123
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
124
+
125
+ const range = getTileRange();
126
+ if (!range) {
127
+ console.warn("无法获取地图范围");
128
+ return;
129
+ }
130
+
131
+ const { minX, maxX, minY, maxY, level } = range;
132
+
133
+ if (!isLoading) {
134
+ isLoading = true;
135
+ pendingTiles = 0;
136
+
137
+ // 先绘制已缓存的瓦片
138
+ for (let x = minX; x <= maxX; x++) {
139
+ for (let y = minY; y <= maxY; y++) {
140
+ const cacheKey = `${level}/${x}/${y}`;
141
+ if (tileCache.has(cacheKey)) {
142
+ const cachedImg = tileCache.get(cacheKey);
143
+ drawSingleTile(ctx, cachedImg, x, y, level);
144
+ }
145
+ }
146
+ }
147
+
148
+ // 加载新的瓦片
149
+ let newTilesCount = 0;
150
+ for (let x = minX; x <= maxX; x++) {
151
+ for (let y = minY; y <= maxY; y++) {
152
+ const cacheKey = `${level}/${x}/${y}`;
153
+
154
+ if (tileCache.has(cacheKey)) {
155
+ continue;
156
+ }
157
+
158
+ const url = urlTemplate
159
+ .replace(/\{z\}/g, String(level))
160
+ .replace(/\{x\}/g, String(x))
161
+ .replace(/\{y\}/g, String(y));
162
+
163
+ newTilesCount++;
164
+ pendingTiles++;
165
+ const img = new Image();
166
+ img.crossOrigin = "anonymous";
167
+
168
+ img.onload = () => {
169
+ tileCache.set(cacheKey, img);
170
+ drawSingleTile(ctx, img, x, y, level);
171
+ pendingTiles--;
172
+ if (pendingTiles === 0) {
173
+ isLoading = false;
174
+ console.log("所有瓦片加载完成");
175
+ }
176
+ };
177
+
178
+ img.onerror = () => {
179
+ console.warn("瓦片加载失败:", url);
180
+ pendingTiles--;
181
+ if (pendingTiles === 0) {
182
+ isLoading = false;
183
+ }
184
+ };
185
+
186
+ img.src = url;
187
+ }
188
+ }
189
+
190
+ if (newTilesCount === 0) {
191
+ isLoading = false;
192
+ } else {
193
+ console.log(`正在加载 ${newTilesCount} 个新瓦片`);
194
+ }
195
+ }
196
+
197
+ animationId = null;
198
+ });
199
+ }
200
+
201
+ // 9. 绑定事件
202
+ let renderTimer: any = null;
203
+
204
+ function scheduleRender() {
205
+ if (renderTimer) {
206
+ clearTimeout(renderTimer);
207
+ }
208
+ renderTimer = setTimeout(() => {
209
+ loadAndDrawTiles();
210
+ renderTimer = null;
211
+ }, 16);
212
+ }
213
+
214
+ map.viewer.on("moving", scheduleRender);
215
+ map.viewer.on("moveend", () => {
216
+ if (renderTimer) {
217
+ clearTimeout(renderTimer);
218
+ renderTimer = null;
219
+ }
220
+ loadAndDrawTiles();
221
+ });
222
+ map.viewer.on("zoomend", () => {
223
+ if (renderTimer) {
224
+ clearTimeout(renderTimer);
225
+ renderTimer = null;
226
+ }
227
+ loadAndDrawTiles();
228
+ });
229
+ map.viewer.on("resize", function () {
230
+ const newSize = self.viewer.getSize();
231
+ canvas.width = newSize.width;
232
+ canvas.height = newSize.height;
233
+ loadAndDrawTiles();
234
+ });
235
+
236
+ setTimeout(() => {
237
+ loadAndDrawTiles();
238
+ }, 500);
239
+
240
+ return canvas;
241
+ }