s3m-three 0.4.26 → 0.4.28

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 (77) hide show
  1. package/dist/browser/assets/s3mWorker-B2u-xt54.js +1 -0
  2. package/dist/browser/s3m-three.iife.ext-three.js +52 -40
  3. package/dist/browser/s3m-three.iife.js +52 -40
  4. package/dist/core/BakeMercator.d.ts +54 -0
  5. package/dist/core/BakeMercator.d.ts.map +1 -0
  6. package/dist/core/BakeMercator.js +65 -0
  7. package/dist/core/BakeMercator.js.map +1 -0
  8. package/dist/core/S3M1Parser.d.ts +19 -0
  9. package/dist/core/S3M1Parser.d.ts.map +1 -1
  10. package/dist/core/S3M1Parser.js +179 -8
  11. package/dist/core/S3M1Parser.js.map +1 -1
  12. package/dist/core/TileGeometry.d.ts +7 -0
  13. package/dist/core/TileGeometry.d.ts.map +1 -1
  14. package/dist/core/TileGeometry.js +20 -0
  15. package/dist/core/TileGeometry.js.map +1 -1
  16. package/dist/index.d.ts +4 -3
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/render/DatasetManager.d.ts +27 -0
  21. package/dist/render/DatasetManager.d.ts.map +1 -1
  22. package/dist/render/DatasetManager.js +18 -4
  23. package/dist/render/DatasetManager.js.map +1 -1
  24. package/dist/render/GeoAdapter.d.ts +49 -24
  25. package/dist/render/GeoAdapter.d.ts.map +1 -1
  26. package/dist/render/GeoAdapter.js +96 -8
  27. package/dist/render/GeoAdapter.js.map +1 -1
  28. package/dist/render/GeometryBuilder.d.ts +17 -0
  29. package/dist/render/GeometryBuilder.d.ts.map +1 -1
  30. package/dist/render/GeometryBuilder.js +31 -2
  31. package/dist/render/GeometryBuilder.js.map +1 -1
  32. package/dist/render/S3MViewer.d.ts +80 -2
  33. package/dist/render/S3MViewer.d.ts.map +1 -1
  34. package/dist/render/S3MViewer.js +299 -9
  35. package/dist/render/S3MViewer.js.map +1 -1
  36. package/dist/render/StyleMaterial.d.ts +5 -0
  37. package/dist/render/StyleMaterial.d.ts.map +1 -1
  38. package/dist/render/StyleMaterial.js +21 -0
  39. package/dist/render/StyleMaterial.js.map +1 -1
  40. package/dist/render/tilePlacement.d.ts +84 -0
  41. package/dist/render/tilePlacement.d.ts.map +1 -0
  42. package/dist/render/tilePlacement.js +71 -0
  43. package/dist/render/tilePlacement.js.map +1 -0
  44. package/dist/render/viewer/EdgeDecorator.d.ts +1 -0
  45. package/dist/render/viewer/EdgeDecorator.d.ts.map +1 -1
  46. package/dist/render/viewer/EdgeDecorator.js +3 -0
  47. package/dist/render/viewer/EdgeDecorator.js.map +1 -1
  48. package/dist/render/viewer/PickService.d.ts +13 -0
  49. package/dist/render/viewer/PickService.d.ts.map +1 -1
  50. package/dist/render/viewer/PickService.js +24 -15
  51. package/dist/render/viewer/PickService.js.map +1 -1
  52. package/dist/render/viewer/types.d.ts +53 -0
  53. package/dist/render/viewer/types.d.ts.map +1 -1
  54. package/dist/render/viewer/types.js.map +1 -1
  55. package/dist/tileset/S3MTileset.d.ts +43 -1
  56. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  57. package/dist/tileset/S3MTileset.js +217 -2
  58. package/dist/tileset/S3MTileset.js.map +1 -1
  59. package/dist/worker/s3mWorker.js +691 -508
  60. package/dist/worker/s3mWorker.js.map +1 -1
  61. package/docs/API.md +18 -0
  62. package/package.json +1 -1
  63. package/src/core/BakeMercator.ts +96 -0
  64. package/src/core/S3M1Parser.ts +179 -9
  65. package/src/core/TileGeometry.ts +26 -0
  66. package/src/index.ts +11 -1
  67. package/src/render/DatasetManager.ts +43 -5
  68. package/src/render/GeoAdapter.ts +139 -11
  69. package/src/render/GeometryBuilder.ts +53 -2
  70. package/src/render/S3MViewer.ts +335 -13
  71. package/src/render/StyleMaterial.ts +26 -0
  72. package/src/render/tilePlacement.ts +121 -0
  73. package/src/render/viewer/EdgeDecorator.ts +3 -0
  74. package/src/render/viewer/PickService.ts +44 -16
  75. package/src/render/viewer/types.ts +51 -0
  76. package/src/tileset/S3MTileset.ts +240 -3
  77. package/dist/browser/assets/s3mWorker-DHOVIrAU.js +0 -1
@@ -6,39 +6,151 @@
6
6
  * 这个文件只定义接口与默认实现,不依赖任何宿主:
7
7
  *
8
8
  * - `GeoAdapter` —— 宿主需要提供的两个转换函数
9
- * - `mercatorFromLngLat` / `lngLatFromMercator` —— 标准 Web 墨卡托换算
9
+ * - `mercatorFromLngLat` / `lngLatFromMercator` —— 标准 Web 墨卡托换算(WGS84 椭球)
10
10
  * - `worldFromLngLat` / `lngLatFromWorld` —— 带兜底的封装:
11
11
  * 有 adapter 用 adapter(自动适配 MapLibre v5+ 任意 CRS / 自定义投影),
12
12
  * 没有就平滑回退到标准 Web 墨卡托。
13
+ * - `enuToLngLat` / `lngLatToEnu` —— WGS84 椭球高精度局部坐标互转
13
14
  *
14
15
  * MapLibre / Mapbox 的 adapter 实现见 s3m-maplibre 包的 MapLibreGeoAdapter.ts。
15
16
  */
16
17
 
17
- export const EARTH_RADIUS = 6378137;
18
+ /** WGS84 椭球长半轴 a(米) */
19
+ export const WGS84_A = 6378137.0;
20
+ /** WGS84 椭球扁率 f */
21
+ export const WGS84_F = 1 / 298.257223563;
22
+ /** WGS84 椭球短半轴 b(米) */
23
+ export const WGS84_B = WGS84_A * (1 - WGS84_F);
24
+ /** WGS84 椭球第一偏心率平方 e² = 2f - f² ≈ 0.0066943799901413165 */
25
+ export const WGS84_E2 = 2 * WGS84_F - WGS84_F * WGS84_F;
26
+
27
+ /** 保持向下兼容:EARTH_RADIUS 对应 WGS84 长半轴 */
28
+ export const EARTH_RADIUS = WGS84_A;
29
+
30
+ /**
31
+ * 卯酉圈曲率半径 N(φ) = a / √(1 - e² sin²φ)(米)
32
+ */
33
+ export function primeVerticalRadius(latRad: number): number {
34
+ const sin = Math.sin(latRad);
35
+ return WGS84_A / Math.sqrt(1 - WGS84_E2 * sin * sin);
36
+ }
37
+
38
+ /**
39
+ * 子午圈曲率半径 M(φ) = a(1 - e²) / (1 - e² sin²φ)^(3/2)(米)
40
+ */
41
+ export function meridianRadius(latRad: number): number {
42
+ const sin = Math.sin(latRad);
43
+ const w2 = 1 - WGS84_E2 * sin * sin;
44
+ return (WGS84_A * (1 - WGS84_E2)) / (w2 * Math.sqrt(w2));
45
+ }
46
+
47
+ /**
48
+ * 局部 ENU 米坐标 (east, north) → 经纬度 [lon, lat] (度)
49
+ * 基于 WGS84 椭球中点曲率半径反解,在 100 km 范围内误差 < 1 cm。
50
+ */
51
+ export function enuToLngLat(
52
+ anchorLon: number,
53
+ anchorLat: number,
54
+ east: number,
55
+ north: number
56
+ ): [number, number] {
57
+ const lat0 = (anchorLat * Math.PI) / 180;
58
+ const M0 = meridianRadius(lat0);
59
+ // 中点迭代一步:在数百公里尺度即可将纬度子午线积分误差压到亚毫米
60
+ const dLatApprox = north / M0;
61
+ const latMid = lat0 + dLatApprox * 0.5;
62
+ const M_mid = meridianRadius(latMid);
63
+ const latRad = lat0 + north / M_mid;
64
+
65
+ const N_lat = primeVerticalRadius(latRad);
66
+ const cosLat = Math.cos(latRad);
67
+ const dLon = Math.abs(cosLat) < 1e-12 ? 0 : east / (N_lat * cosLat);
68
+
69
+ return [anchorLon + (dLon * 180) / Math.PI, (latRad * 180) / Math.PI];
70
+ }
71
+
72
+ /**
73
+ * 经纬度 (lon, lat) → 局部 ENU 米坐标 [east, north]
74
+ * enuToLngLat 的逆运算。
75
+ */
76
+ export function lngLatToEnu(
77
+ anchorLon: number,
78
+ anchorLat: number,
79
+ lon: number,
80
+ lat: number
81
+ ): [number, number] {
82
+ const lat0 = (anchorLat * Math.PI) / 180;
83
+ const lat1 = (lat * Math.PI) / 180;
84
+ const latMid = (lat0 + lat1) * 0.5;
85
+ const north = (lat1 - lat0) * meridianRadius(latMid);
86
+
87
+ const N_lat = primeVerticalRadius(lat1);
88
+ const cosLat = Math.cos(lat1);
89
+ const dLon = ((lon - anchorLon) * Math.PI) / 180;
90
+ const east = dLon * N_lat * cosLat;
91
+
92
+ return [east, north];
93
+ }
94
+
95
+ /** 坐标转换结果类型 */
96
+ export interface WorldCoordinateResult {
97
+ x: number;
98
+ y: number;
99
+ z: number;
100
+ /** 东向:1 米 = 多少世界单位 (Web Mercator 东西向尺度 1 / (2π N cosφ)) */
101
+ meterInMercatorEast: number;
102
+ /** 北向:1 米 = 多少世界单位 (Web Mercator 南北向尺度 1 / (2π M cosφ)) */
103
+ meterInMercatorNorth: number;
104
+ /** 竖向(高程):1 米 = 多少世界单位(当前取东向尺度 meterInMercatorEast,以保持局部长方体水平与竖直比例) */
105
+ meterInMercatorUp: number;
106
+ /** 兼容旧代码:等同于 meterInMercatorEast。注意:从原先的正球近似更新为更精准的 WGS84 椭球东向尺度 */
107
+ meterInMercator: number;
108
+ altitude: number;
109
+ }
18
110
 
19
111
  /** 宿主地理坐标系能力:S3M 渲染层用到的就只有这两个方向 */
20
112
  export interface GeoAdapter {
21
- /** 经纬度 + 高程 → 归一化世界坐标,并给出「1 米 = 多少世界单位」 */
113
+ /** 经纬度 + 高程 → 归一化世界坐标,并给出「1 米 = 多少世界单位」(含各向异性 East / North) */
22
114
  worldFromLngLat(
23
115
  lng: number,
24
116
  lat: number,
25
117
  altitude?: number
26
- ): { x: number; y: number; z: number; meterInMercator: number; altitude: number };
118
+ ): WorldCoordinateResult;
27
119
  /** 归一化世界坐标 → 经纬度 [lng, lat] */
28
120
  lngLatFromWorld(x: number, y: number): [number, number];
29
121
  }
30
122
 
31
- /** 经纬度 → 墨卡托归一化坐标(世界为 1×1)—— 默认墨卡托兜底 */
123
+ /** 经纬度 → 墨卡托归一化坐标(世界为 1×1)—— 默认墨卡托兜底(WGS84 椭球曲率半径与各向异性尺度) */
32
124
  export function mercatorFromLngLat(
33
125
  lon: number,
34
126
  lat: number,
35
127
  altitude = 0
36
- ): { x: number; y: number; z: number; meterInMercator: number; altitude: number } {
37
- const sinLat = Math.sin((lat * Math.PI) / 180);
128
+ ): WorldCoordinateResult {
129
+ const latRad = (lat * Math.PI) / 180;
130
+ const sinLat = Math.sin(latRad);
131
+ const cosLat = Math.cos(latRad);
132
+ const safeCosLat = Math.max(Math.abs(cosLat), 1e-12);
133
+
38
134
  const x = (180 + lon) / 360;
39
135
  const y = 0.5 - (0.25 * Math.log((1 + sinLat) / (1 - sinLat))) / Math.PI;
40
- const meterInMercator = 1 / (2 * Math.PI * EARTH_RADIUS) / Math.cos((lat * Math.PI) / 180);
41
- return { x, y, z: altitude * meterInMercator, meterInMercator, altitude };
136
+
137
+ const N = primeVerticalRadius(latRad);
138
+ const M = meridianRadius(latRad);
139
+
140
+ const meterInMercatorEast = 1 / (2 * Math.PI * N * safeCosLat);
141
+ const meterInMercatorNorth = 1 / (2 * Math.PI * M * safeCosLat);
142
+ const meterInMercatorUp = meterInMercatorEast;
143
+
144
+ return {
145
+ x,
146
+ y,
147
+ z: altitude * meterInMercatorUp,
148
+ meterInMercatorEast,
149
+ meterInMercatorNorth,
150
+ meterInMercatorUp,
151
+ meterInMercator: meterInMercatorEast,
152
+ altitude,
153
+ };
42
154
  }
43
155
 
44
156
  /** 墨卡托归一化坐标 → 经纬度 —— 默认墨卡托兜底 */
@@ -58,8 +170,24 @@ export function worldFromLngLat(
58
170
  lon: number,
59
171
  lat: number,
60
172
  altitude = 0
61
- ): { x: number; y: number; z: number; meterInMercator: number; altitude: number } {
62
- if (adapter) return adapter.worldFromLngLat(lon, lat, altitude);
173
+ ): WorldCoordinateResult {
174
+ if (adapter) {
175
+ const res = adapter.worldFromLngLat(lon, lat, altitude);
176
+ const east = res.meterInMercatorEast ?? res.meterInMercator;
177
+ const north = res.meterInMercatorNorth ?? res.meterInMercator ?? east;
178
+ const up = res.meterInMercatorUp ?? east;
179
+ const legacy = res.meterInMercator ?? east;
180
+ return {
181
+ x: res.x,
182
+ y: res.y,
183
+ z: res.z,
184
+ meterInMercatorEast: east,
185
+ meterInMercatorNorth: north,
186
+ meterInMercatorUp: up,
187
+ meterInMercator: legacy,
188
+ altitude: res.altitude,
189
+ };
190
+ }
63
191
  return mercatorFromLngLat(lon, lat, altitude);
64
192
  }
65
193
 
@@ -18,6 +18,8 @@ import { createSharedUniforms, createStyledMaterial } from './StyleMaterial.js';
18
18
  import { isTransparentMaterial, minVertexAlpha } from './materialAlpha.js';
19
19
  import type { StyledMaterial } from './StyleMaterial.js';
20
20
  import { assembleTileGeometry, LINE_PRIMITIVE, POINT_PRIMITIVE } from '../core/TileGeometry.js';
21
+ import { bakeMercatorOffsets } from '../core/BakeMercator.js';
22
+ import { mercatorFromLngLat } from './GeoAdapter.js';
21
23
  import type { TileGeometryData, PrimitiveData } from '../core/TileGeometry.js';
22
24
  import { decodeScale, decodeRotation } from '../core/GaussianSplat.js';
23
25
  import { createSplatMaterial, createQuadGeometry, splatRaycast } from './SplatMaterial.js';
@@ -378,6 +380,15 @@ export interface BuildTileOptions {
378
380
  * 不传 = 各自读模块级默认。同样由图层的渲染 profile 带下来。
379
381
  */
380
382
  textureOptions?: TextureCreateOptions;
383
+ /**
384
+ * 是否烘焙精确 Web 墨卡托偏移属性(消除全球尺度下的视差游动)。
385
+ * 透传给 assembleTileGeometry 和 createStyledMaterial。
386
+ */
387
+ bakeMercator?: boolean;
388
+ /** 瓦片锚点(经纬度),烘焙墨卡托时使用 */
389
+ tileAnchor?: { lon: number; lat: number; height?: number };
390
+ /** 瓦片中心偏移(ENU 米),烘焙墨卡托时使用 */
391
+ tileCenter?: { x: number; y: number; z?: number };
381
392
  }
382
393
 
383
394
  export interface BuildTileResult {
@@ -475,7 +486,8 @@ export async function buildTileObjects(
475
486
  code?: string,
476
487
  hasAtlasRect = false,
477
488
  hasTexCoordZ = false,
478
- vertexAlphaMin = 1
489
+ vertexAlphaMin = 1,
490
+ hasMercator = false
479
491
  ): StyledMaterial => {
480
492
  const entry = materialList.find((e) => materialName(unwrapMaterial(e)) === code);
481
493
  const m = unwrapMaterial(entry);
@@ -546,6 +558,19 @@ export async function buildTileObjects(
546
558
  ? Math.max((m?.alphaCutoff as number) ?? 0, 0.01)
547
559
  : 0;
548
560
 
561
+ let initialInvScale: [number, number, number] | undefined;
562
+ if (options.tileAnchor) {
563
+ const tileMerc = mercatorFromLngLat(
564
+ options.tileAnchor.lon,
565
+ options.tileAnchor.lat,
566
+ options.tileAnchor.height
567
+ );
568
+ const sEast = tileMerc.meterInMercatorEast ?? tileMerc.meterInMercator;
569
+ const sNorth = tileMerc.meterInMercatorNorth ?? tileMerc.meterInMercator;
570
+ const sUp = tileMerc.meterInMercatorUp ?? sEast;
571
+ initialInvScale = [1.0 / sEast, 1.0 / sNorth, 1.0 / sUp];
572
+ }
573
+
549
574
  const mat = createStyledMaterial({
550
575
  uniforms,
551
576
  map,
@@ -562,6 +587,9 @@ export async function buildTileObjects(
562
587
  // 图层 / 数据集的档位;undefined 时 createStyledMaterial 自己回落到模块级默认
563
588
  materialQuality: options.materialQuality,
564
589
  colorPipeline: options.colorPipeline,
590
+ bakeMercator: hasMercator || options.bakeMercator,
591
+ tilePivot: options.tileCenter ? new THREE.Vector3(options.tileCenter.x, options.tileCenter.y, options.tileCenter.z) : undefined,
592
+ invTileScale: initialInvScale ? new THREE.Vector3(...initialInvScale) : undefined,
565
593
  });
566
594
  // 用户配置的深度偏移(共面摩尔纹 / 重合面归属的人工定序,见 BuildTileOptions 注释)
567
595
  if (options.polygonOffset) {
@@ -574,11 +602,32 @@ export async function buildTileObjects(
574
602
 
575
603
  // 3) 纯数据几何组装:Worker 解析时已顺带产出就直接用,否则主线程现场组装
576
604
  // (core/TileGeometry 的同一份实现,两条路径产物一致)
605
+ if (options.assembled && options.bakeMercator && options.tileAnchor) {
606
+ for (const prim of options.assembled.primitives) {
607
+ if (!prim.attributes.aMercatorOffset && prim.attributes.position) {
608
+ const offsets = bakeMercatorOffsets(
609
+ prim.attributes.position.array,
610
+ prim.vertexCount,
611
+ options.tileAnchor,
612
+ options.tileCenter ?? { x: 0, y: 0, z: 0 }
613
+
614
+ );
615
+ prim.attributes.aMercatorOffset = {
616
+ array: offsets,
617
+ components: 3,
618
+ normalized: false,
619
+ };
620
+ }
621
+ }
622
+ }
577
623
  const assembled =
578
624
  options.assembled ??
579
625
  assembleTileGeometry(tile, {
580
626
  batching: options.batching !== false,
581
627
  dedupeCoincident: options.dedupeCoincident,
628
+ bakeMercator: options.bakeMercator,
629
+ tileAnchor: options.tileAnchor,
630
+ tileCenter: options.tileCenter,
582
631
  });
583
632
  stats.vertices = assembled.stats.vertices;
584
633
  stats.edgeLines = assembled.stats.edgeLines;
@@ -610,11 +659,13 @@ export async function buildTileObjects(
610
659
  const geometry = packPrimitive(prim);
611
660
  const geoRef = tile.geoPackage[prim.geoNames[0]] ?? null;
612
661
  const vp = geoRef?.vertexPackage;
662
+ const hasMercator = !!prim.attributes.aMercatorOffset;
613
663
  const material = makeMaterial(
614
664
  prim.passNames[0],
615
665
  !!prim.attributes.aAtlasRect,
616
666
  !!prim.attributes.aTexCoordZ,
617
- minVertexAlpha(prim.attributes.aColor)
667
+ minVertexAlpha(prim.attributes.aColor),
668
+ hasMercator
618
669
  );
619
670
  const matrix =
620
671
  prim.matrix && prim.matrix.length === 16 ? new THREE.Matrix4().fromArray(prim.matrix) : null;