babyeditor-tool 0.0.8 → 0.0.10

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.
@@ -0,0 +1,329 @@
1
+ import { MeshBuilder, StandardMaterial, Texture, Color3 } from "@babylonjs/core";
2
+ import { Layer } from "../Layer";
3
+ import { Projection } from "../Projection";
4
+ import { TileLayer } from "./TileLayer";
5
+ /**
6
+ * 瓦片图层渲染器
7
+ */
8
+ export class TileLayerRenderer extends Layer {
9
+ /**
10
+ * 构造函数
11
+ * @param mapViewer 地图视图
12
+ */
13
+ constructor(mapViewer) {
14
+ super('TileLayerRenderer', mapViewer.scene);
15
+ /**
16
+ * 瓦片图层渲染器
17
+ */
18
+ this.tileLayers = [];
19
+ /**
20
+ * 当前缩放级别
21
+ */
22
+ this.currentZoom = 1;
23
+ /**
24
+ * 已渲染的瓦片集合,使用 "x-y-z" 作为键
25
+ */
26
+ this.renderedTiles = new Map();
27
+ /**
28
+ * 摄像机位置观察者
29
+ */
30
+ this.cameraObserver = null;
31
+ /**
32
+ * 是否启用自动更新
33
+ */
34
+ this.autoUpdate = false;
35
+ this.mapViewer = mapViewer;
36
+ this.maxBBox = mapViewer.projection === 'EPSG:3857' ? Projection.Max3857BBox : Projection.Max4326BBox;
37
+ // 将瓦片图层添加到地图根节点
38
+ this.layerRootNode.parent = mapViewer.rootNode;
39
+ this.tileLayers = [new TileLayer('aaa', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}')];
40
+ }
41
+ get projectionBBox() {
42
+ return this.mapViewer.getProjectionBbox();
43
+ }
44
+ /**
45
+ * 获取地图范围
46
+ * @returns [minX, minY, maxX, maxY] 地图范围
47
+ */
48
+ get bbox() {
49
+ return this.mapViewer.bbox;
50
+ }
51
+ /**
52
+ * 获取地图大小
53
+ * @returns [width, height] 地图大小
54
+ */
55
+ get size() {
56
+ return this.mapViewer.size;
57
+ }
58
+ /**
59
+ * 获取地图投影
60
+ * @returns 地图投影
61
+ */
62
+ get projection() {
63
+ return this.mapViewer.projection;
64
+ }
65
+ /**
66
+ * 根据地图投影坐标和缩放级别获取瓦片序号
67
+ * @param x 投影坐标X
68
+ * @param y 投影坐标Y
69
+ * @param z 缩放级别
70
+ * @returns [tileX, tileY] 瓦片序号
71
+ */
72
+ positionToTile(x, y, z) {
73
+ const [bboxMinX, bboxMinY, bboxMaxX, bboxMaxY] = this.maxBBox;
74
+ const width = bboxMaxX - bboxMinX;
75
+ const height = bboxMaxY - bboxMinY;
76
+ // 计算单个瓦片在当前缩放级别下的大小
77
+ const tileCount = Math.pow(2, z);
78
+ const tileWidth = width / tileCount;
79
+ const tileHeight = height / tileCount;
80
+ // 计算瓦片序号
81
+ const tileX = Math.floor((x - bboxMinX) / tileWidth);
82
+ const tileY = Math.floor((bboxMaxY - y) / tileHeight); // Y轴通常是从上到下
83
+ return [tileX, tileY];
84
+ }
85
+ /**
86
+ * 根据瓦片序号和缩放级别获取瓦片范围
87
+ * @param tileX 瓦片序号X
88
+ * @param tileY 瓦片序号Y
89
+ * @param z 缩放级别
90
+ * @returns [minX, minY, maxX, maxY] 瓦片范围
91
+ */
92
+ tileToBBox(tileX, tileY, z) {
93
+ const [minX, minY, maxX, maxY] = this.maxBBox;
94
+ const width = maxX - minX;
95
+ const height = maxY - minY;
96
+ // 计算单个瓦片在当前缩放级别下的大小
97
+ const tileCount = Math.pow(2, z);
98
+ const tileWidth = width / tileCount;
99
+ const tileHeight = height / tileCount;
100
+ // 计算瓦片范围
101
+ const tileMinX = minX + tileX * tileWidth;
102
+ const tileMinY = maxY - tileY * tileHeight - tileHeight;
103
+ const tileMaxX = tileMinX + tileWidth;
104
+ const tileMaxY = tileMinY + tileHeight;
105
+ return [tileMinX, tileMaxY, tileMaxX, tileMinY];
106
+ }
107
+ /**
108
+ * 根据瓦片序号和缩放级别获取瓦片中心点坐标
109
+ * @param tileX 瓦片序号X
110
+ * @param tileY 瓦片序号Y
111
+ * @param z 缩放级别
112
+ * @returns [centerX, centerY] 瓦片中心点坐标
113
+ */
114
+ tileToCenter(tileX, tileY, z) {
115
+ const [minX, minY, maxX, maxY] = this.tileToBBox(tileX, tileY, z);
116
+ const centerX = (minX + maxX) / 2;
117
+ const centerY = (minY + maxY) / 2;
118
+ return [centerX, centerY];
119
+ }
120
+ /**
121
+ * 根据瓦片序号和缩放级别获取瓦片在视野中的尺寸大小
122
+ * @param tileX 瓦片序号X
123
+ * @param tileY 瓦片序号Y
124
+ * @param z 缩放级别
125
+ * @returns [width, height] 瓦片在视野中的宽度和高度(像素)
126
+ */
127
+ tileToViewportSize(tileX, tileY, z) {
128
+ const [minX, minY, maxX, maxY] = this.tileToBBox(tileX, tileY, z);
129
+ // 获取瓦片左上角和右下角在视口中的坐标
130
+ const [viewportMinX, viewportMinY] = this.projectionToViewport(minX, maxY);
131
+ const [viewportMaxX, viewportMaxY] = this.projectionToViewport(maxX, minY);
132
+ // 计算视口尺寸
133
+ const viewportWidth = Math.abs(viewportMaxX - viewportMinX);
134
+ const viewportHeight = Math.abs(viewportMaxY - viewportMinY);
135
+ return [viewportWidth, viewportHeight];
136
+ }
137
+ /**
138
+ * 将投影坐标转换为地图视野相对坐标
139
+ * @param x 投影坐标X
140
+ * @param y 投影坐标Y
141
+ * @returns [relativeX, relativeY] 相对于地图视野的坐标
142
+ */
143
+ projectionToViewport(x, y) {
144
+ const [bboxMinX, bboxMinY, bboxMaxX, bboxMaxY] = this.projectionBBox;
145
+ const [width, height] = this.size;
146
+ // 计算投影坐标在地图范围内的相对位置(0-1)
147
+ const relativeX = (x - bboxMinX) / (bboxMaxX - bboxMinX);
148
+ const relativeY = (y - bboxMinY) / (bboxMaxY - bboxMinY);
149
+ // 转换为视口像素坐标
150
+ const viewportX = relativeX * width - this.size[0] / 2;
151
+ const viewportY = relativeY * height - this.size[1] / 2;
152
+ return [viewportX, viewportY];
153
+ }
154
+ /**
155
+ * 根据瓦片序号和缩放级别获取瓦片网格
156
+ * @param tileX 瓦片序号X
157
+ * @param tileY 瓦片序号Y
158
+ * @param z 缩放级别
159
+ * @returns 瓦片网格
160
+ */
161
+ getTileMesh(tileX, tileY, z) {
162
+ const tileMesh = MeshBuilder.CreateGround(`tile-${tileX}-${tileY}-${z}`, { width: 1, height: 1 }, this.mapViewer.scene);
163
+ const center = this.tileToCenter(tileX, tileY, z);
164
+ const worldPosition = this.projectionToViewport(center[0], center[1]);
165
+ const worldSize = this.tileToViewportSize(tileX, tileY, z);
166
+ tileMesh.position.x = worldPosition[0];
167
+ tileMesh.position.z = worldPosition[1];
168
+ tileMesh.scaling.x = worldSize[0];
169
+ tileMesh.scaling.z = worldSize[1];
170
+ tileMesh.parent = this.layerRootNode;
171
+ this.updateTileTexture(tileX, tileY, z, tileMesh);
172
+ return tileMesh;
173
+ }
174
+ /**
175
+ * 获取调试画布
176
+ * @returns 调试画布元素
177
+ */
178
+ getTileCanvas(tileX, tileY, z) {
179
+ const canvas = document.createElement('canvas');
180
+ canvas.width = 256;
181
+ canvas.height = 256;
182
+ const ctx = canvas.getContext('2d');
183
+ if (ctx) {
184
+ ctx.fillStyle = 'red';
185
+ ctx.font = '18px Arial';
186
+ ctx.textBaseline = 'middle';
187
+ ctx.textAlign = 'center';
188
+ ctx.fillText(`Tile: ${tileX}-${tileY}, ${z}`, canvas.width / 2, canvas.height / 2);
189
+ }
190
+ return canvas;
191
+ }
192
+ updateTileTexture(tileX, tileY, z, tileMesh) {
193
+ const canvas = document.createElement('canvas');
194
+ canvas.width = 256;
195
+ canvas.height = 256;
196
+ const ctx = canvas.getContext('2d');
197
+ if (ctx) {
198
+ ctx.fillStyle = 'red';
199
+ ctx.font = '18px Arial';
200
+ ctx.textBaseline = 'middle';
201
+ ctx.textAlign = 'center';
202
+ ctx.fillText(`Tile: ${tileX}-${tileY}, ${z}`, canvas.width / 2, canvas.height / 2);
203
+ }
204
+ let material = tileMesh.material;
205
+ if (!material) {
206
+ material = new StandardMaterial(`tile-${tileX}-${tileY}-${z}`, this.mapViewer.scene);
207
+ material.specularColor = new Color3(0, 0, 0);
208
+ tileMesh.material = material;
209
+ }
210
+ this.tileLayers.forEach((tileLayer) => {
211
+ // tileLayer.requestImage(tileX, tileY, z).then((image: HTMLImageElement) => {
212
+ // if (image) {
213
+ // if (ctx) ctx.drawImage(image, 0, 0, 256, 256);
214
+ // material.diffuseTexture = new Texture(canvas.toDataURL(), this.mapViewer.scene, false, true);
215
+ // }
216
+ // })
217
+ material.diffuseTexture = new Texture(tileLayer.getTileUrl(tileX, tileY, z), this.mapViewer.scene, false, true);
218
+ });
219
+ // material.diffuseTexture = new Texture(canvas.toDataURL(), this.mapViewer.scene, false, true);
220
+ }
221
+ /**
222
+ * 根据当前视野范围和缩放级别计算需要渲染的瓦片列表
223
+ * @param zoom 缩放级别
224
+ * @returns 需要渲染的瓦片坐标数组 [[tileX, tileY, z], ...]
225
+ */
226
+ calculateVisibleTiles(zoom) {
227
+ const [bboxMinX, bboxMinY, bboxMaxX, bboxMaxY] = this.projectionBBox;
228
+ // 获取视野范围的四个角点对应的瓦片坐标
229
+ const [minTileX, maxTileY] = this.positionToTile(bboxMinX, bboxMinY, zoom);
230
+ const [maxTileX, minTileY] = this.positionToTile(bboxMaxX, bboxMaxY, zoom);
231
+ const visibleTiles = [];
232
+ // 遍历视野范围内的所有瓦片
233
+ for (let tileY = minTileY; tileY <= maxTileY; tileY++) {
234
+ for (let tileX = minTileX; tileX <= maxTileX; tileX++) {
235
+ // 确保瓦片坐标在有效范围内
236
+ const center = this.tileToCenter(tileX, tileY, zoom);
237
+ if (center[0] >= bboxMinX && center[0] <= bboxMaxX && center[1] >= bboxMinY && center[1] <= bboxMaxY) {
238
+ visibleTiles.push([tileX, tileY, zoom]);
239
+ }
240
+ }
241
+ }
242
+ return visibleTiles;
243
+ }
244
+ /**
245
+ * 更新瓦片渲染,只渲染当前视野内的瓦片
246
+ * @param zoom 缩放级别
247
+ */
248
+ updateTiles(zoom) {
249
+ this.currentZoom = zoom;
250
+ const visibleTiles = this.calculateVisibleTiles(zoom);
251
+ // 创建当前应该存在的瓦片集合
252
+ const shouldExistTiles = new Set();
253
+ visibleTiles.forEach(([x, y, z]) => {
254
+ shouldExistTiles.add(`${x}-${y}-${z}`);
255
+ });
256
+ // 移除不在视野内的瓦片
257
+ this.renderedTiles.forEach((mesh, key) => {
258
+ if (!shouldExistTiles.has(key)) {
259
+ mesh.dispose();
260
+ this.renderedTiles.delete(key);
261
+ }
262
+ });
263
+ // 添加新的可见瓦片
264
+ visibleTiles.forEach(([tileX, tileY, z]) => {
265
+ const tileKey = `${tileX}-${tileY}-${z}`;
266
+ if (!this.renderedTiles.has(tileKey)) {
267
+ const tileMesh = this.getTileMesh(tileX, tileY, z);
268
+ this.renderedTiles.set(tileKey, tileMesh);
269
+ }
270
+ });
271
+ }
272
+ /**
273
+ * 启用自动更新模式,监听摄像机变化自动更新瓦片
274
+ * @param zoom 缩放级别
275
+ */
276
+ enableAutoUpdate(zoom = 0) {
277
+ this.autoUpdate = true;
278
+ this.currentZoom = zoom;
279
+ // 初始渲染
280
+ this.updateTiles(zoom);
281
+ // 监听场景渲染,检测视野变化
282
+ const scene = this.mapViewer.scene;
283
+ if (scene.activeCamera) {
284
+ this.cameraObserver = scene.onAfterCameraRenderObservable.add(() => {
285
+ if (this.autoUpdate) {
286
+ // 这里可以添加防抖逻辑,避免频繁更新
287
+ this.updateTiles(this.currentZoom);
288
+ }
289
+ });
290
+ }
291
+ }
292
+ /**
293
+ * 禁用自动更新模式
294
+ */
295
+ disableAutoUpdate() {
296
+ this.autoUpdate = false;
297
+ if (this.cameraObserver) {
298
+ this.mapViewer.scene.onAfterCameraRenderObservable.remove(this.cameraObserver);
299
+ this.cameraObserver = null;
300
+ }
301
+ }
302
+ /**
303
+ * 设置缩放级别并更新瓦片
304
+ * @param zoom 缩放级别
305
+ */
306
+ setZoom(zoom) {
307
+ if (zoom !== this.currentZoom) {
308
+ this.currentZoom = zoom;
309
+ this.updateTiles(zoom);
310
+ }
311
+ }
312
+ /**
313
+ * 清除所有已渲染的瓦片
314
+ */
315
+ clearTiles() {
316
+ this.renderedTiles.forEach((mesh) => {
317
+ mesh.dispose();
318
+ });
319
+ this.renderedTiles.clear();
320
+ }
321
+ /**
322
+ * 释放资源
323
+ */
324
+ dispose() {
325
+ this.disableAutoUpdate();
326
+ this.clearTiles();
327
+ super.dispose();
328
+ }
329
+ }