isard 1.4.0 → 1.6.0

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 (135) hide show
  1. package/CHANGELOG.md +220 -2
  2. package/README.md +23 -7
  3. package/dist/basemap/furniture.d.ts +8 -0
  4. package/dist/basemap/furniture.js +26 -0
  5. package/dist/basemap/furniture.js.map +1 -0
  6. package/dist/basemap/trees.d.ts +35 -8
  7. package/dist/basemap/trees.js +105 -52
  8. package/dist/basemap/trees.js.map +1 -1
  9. package/dist/basemap/vectorstyle.d.ts +9 -3
  10. package/dist/basemap/vectorstyle.js +5 -6
  11. package/dist/basemap/vectorstyle.js.map +1 -1
  12. package/dist/basemap/vectortiles/context.d.ts +46 -0
  13. package/dist/basemap/vectortiles/context.js +6 -0
  14. package/dist/basemap/vectortiles/context.js.map +1 -0
  15. package/dist/basemap/vectortiles/extrusions.d.ts +15 -0
  16. package/dist/basemap/vectortiles/extrusions.js +61 -0
  17. package/dist/basemap/vectortiles/extrusions.js.map +1 -0
  18. package/dist/basemap/vectortiles/labels.d.ts +25 -0
  19. package/dist/basemap/vectortiles/labels.js +250 -0
  20. package/dist/basemap/vectortiles/labels.js.map +1 -0
  21. package/dist/basemap/vectortiles/lamps.d.ts +23 -0
  22. package/dist/basemap/vectortiles/lamps.js +90 -0
  23. package/dist/basemap/vectortiles/lamps.js.map +1 -0
  24. package/dist/basemap/vectortiles/requests.d.ts +28 -0
  25. package/dist/basemap/vectortiles/requests.js +74 -0
  26. package/dist/basemap/vectortiles/requests.js.map +1 -0
  27. package/dist/basemap/vectortiles/street-wind.d.ts +19 -0
  28. package/dist/basemap/vectortiles/street-wind.js +133 -0
  29. package/dist/basemap/vectortiles/street-wind.js.map +1 -0
  30. package/dist/basemap/vectortiles/style.d.ts +47 -0
  31. package/dist/basemap/vectortiles/style.js +141 -0
  32. package/dist/basemap/vectortiles/style.js.map +1 -0
  33. package/dist/basemap/vectortiles/tiles.d.ts +34 -0
  34. package/dist/basemap/vectortiles/tiles.js +62 -0
  35. package/dist/basemap/vectortiles/tiles.js.map +1 -0
  36. package/dist/basemap/vectortiles/trees.d.ts +28 -0
  37. package/dist/basemap/vectortiles/trees.js +178 -0
  38. package/dist/basemap/vectortiles/trees.js.map +1 -0
  39. package/dist/basemap/vectortiles/vectors.d.ts +37 -0
  40. package/dist/basemap/vectortiles/vectors.js +231 -0
  41. package/dist/basemap/vectortiles/vectors.js.map +1 -0
  42. package/dist/basemap/vectortiles.d.ts +49 -96
  43. package/dist/basemap/vectortiles.js +99 -774
  44. package/dist/basemap/vectortiles.js.map +1 -1
  45. package/dist/basemap/workerpool.js +25 -6
  46. package/dist/basemap/workerpool.js.map +1 -1
  47. package/dist/basemap.worker.js +2 -2
  48. package/dist/basemap.worker.js.map +14 -12
  49. package/dist/bind/layers.js +1 -0
  50. package/dist/bind/layers.js.map +1 -1
  51. package/dist/data/collada.d.ts +19 -0
  52. package/dist/data/collada.js +319 -0
  53. package/dist/data/collada.js.map +1 -0
  54. package/dist/data/ifc.d.ts +100 -0
  55. package/dist/data/ifc.js +372 -0
  56. package/dist/data/ifc.js.map +1 -0
  57. package/dist/decoder.worker.js +5 -4
  58. package/dist/decoder.worker.js.map +8 -6
  59. package/dist/engine/decoders.d.ts +4 -8
  60. package/dist/engine/decoders.js +29 -2
  61. package/dist/engine/decoders.js.map +1 -1
  62. package/dist/formats.d.ts +7 -2
  63. package/dist/formats.js +7 -2
  64. package/dist/formats.js.map +1 -1
  65. package/dist/index.d.ts +15 -2
  66. package/dist/index.js +7 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/isard_bg.wasm +0 -0
  69. package/dist/isard_core_bg.wasm +0 -0
  70. package/dist/isard_grid_bg.wasm +0 -0
  71. package/dist/isard_tess_bg.wasm +0 -0
  72. package/dist/layers/extrusions.d.ts +10 -0
  73. package/dist/layers/extrusions.js +28 -2
  74. package/dist/layers/extrusions.js.map +1 -1
  75. package/dist/layers/flow.d.ts +9 -2
  76. package/dist/layers/flow.js +51 -10
  77. package/dist/layers/flow.js.map +1 -1
  78. package/dist/layers/layers.d.ts +12 -0
  79. package/dist/layers/layers.js +44 -1
  80. package/dist/layers/layers.js.map +1 -1
  81. package/dist/layers/panoramas.d.ts +2 -0
  82. package/dist/layers/panoramas.js +27 -6
  83. package/dist/layers/panoramas.js.map +1 -1
  84. package/dist/layers/photos.d.ts +48 -0
  85. package/dist/layers/photos.js +140 -0
  86. package/dist/layers/photos.js.map +1 -0
  87. package/dist/layers/symbols.d.ts +5 -1
  88. package/dist/layers/symbols.js +111 -32
  89. package/dist/layers/symbols.js.map +1 -1
  90. package/dist/layers/trees.d.ts +28 -0
  91. package/dist/layers/trees.js +51 -0
  92. package/dist/layers/trees.js.map +1 -0
  93. package/dist/layers/vectors.d.ts +28 -2
  94. package/dist/layers/vectors.js +81 -7
  95. package/dist/layers/vectors.js.map +1 -1
  96. package/dist/map/layer-spec.d.ts +3 -0
  97. package/dist/map/layer-spec.js +2 -0
  98. package/dist/map/layer-spec.js.map +1 -1
  99. package/dist/map/workers.js +3 -0
  100. package/dist/map/workers.js.map +1 -1
  101. package/dist/map.d.ts +8 -0
  102. package/dist/map.js +15 -2
  103. package/dist/map.js.map +1 -1
  104. package/dist/panoramax.d.ts +49 -0
  105. package/dist/panoramax.js +71 -0
  106. package/dist/panoramax.js.map +1 -0
  107. package/dist/react/Layer.d.ts +1 -0
  108. package/dist/react/Layer.js.map +1 -1
  109. package/dist/react/VectorGeometry.d.ts +5 -0
  110. package/dist/react/VectorGeometry.js +12 -4
  111. package/dist/react/VectorGeometry.js.map +1 -1
  112. package/dist/util/gltf.d.ts +9 -0
  113. package/dist/util/gltf.js +27 -0
  114. package/dist/util/gltf.js.map +1 -0
  115. package/dist/util/read.d.ts +4 -0
  116. package/dist/util/read.js +28 -0
  117. package/dist/util/read.js.map +1 -0
  118. package/dist/util/roofs.d.ts +10 -0
  119. package/dist/util/roofs.js +30 -0
  120. package/dist/util/roofs.js.map +1 -0
  121. package/dist/wasm/isard.d.ts +74 -22
  122. package/dist/wasm/isard.js +153 -22
  123. package/dist/wasm/isard_core.d.ts +62 -22
  124. package/dist/wasm/isard_core.js +129 -22
  125. package/dist/wasm/isard_tess.d.ts +6 -3
  126. package/dist/wasm/isard_tess.js +27 -4
  127. package/dist/workers/basemap/geometry.d.ts +38 -22
  128. package/dist/workers/basemap/labels.d.ts +14 -1
  129. package/dist/workers/basemap/lamps.d.ts +10 -0
  130. package/dist/workers/basemap/street.d.ts +1 -1
  131. package/dist/workers/basemap/trees.d.ts +3 -1
  132. package/dist/workers/basemap.worker.d.ts +43 -3
  133. package/dist/workers/basemap.worker.js +14 -2
  134. package/dist/workers/basemap.worker.js.map +1 -1
  135. package/package.json +1 -1
@@ -109,25 +109,40 @@ export class Panoramas {
109
109
  if (!pano || !attitude)
110
110
  return;
111
111
  const shown = ++this.shown;
112
+ // The images of the panorama shown before: no longer downloaded nor decoded
113
+ const controller = new AbortController();
114
+ this.loading?.abort();
115
+ this.loading = controller;
112
116
  this.current = pano;
113
117
  const rotation = panoramaRotation(attitude, pano.lonLat[0], this.lambert93);
114
118
  const { url, backgroundDistance, opacity } = this.options;
115
119
  this.host.setPanorama(pano.lonLat[0], pano.lonLat[1], pano.height, Float64Array.from(rotation), Float64Array.from(this.cameras.flatMap(cameraNumbers)), backgroundDistance, opacity);
116
- await Promise.all(this.cameras.map(async (camera, k) => {
120
+ const images = Promise.all(this.cameras.map(async (camera, k) => {
117
121
  const src = url.replaceAll('{cameraId}', String(camera.id)).replaceAll('{panoId}', String(pano.id));
118
- const response = await fetch(src, withHeaders(src));
122
+ const response = await fetch(src, withHeaders(src, { signal: controller.signal }));
119
123
  if (!response.ok)
120
124
  throw new Error(`isard: panorama image ${src}: ${response.status}`);
121
- const image = await createImageBitmap(await response.blob(), { premultiplyAlpha: 'none' });
125
+ const blob = await response.blob();
126
+ if (controller.signal.aborted)
127
+ return;
128
+ const image = await createImageBitmap(blob, { premultiplyAlpha: 'none' });
122
129
  // Another panorama shown since: this image is not its
123
130
  if (shown === this.shown)
124
131
  this.host.setPanoramaImage(k, image);
125
132
  image.close();
126
133
  }));
134
+ // Another panorama shown, or this one hidden, meanwhile: not a failure
135
+ await images.catch((error) => {
136
+ if (!controller.signal.aborted)
137
+ throw error;
138
+ });
127
139
  }
140
+ /** The downloads of the panorama shown, aborted by the next one shown or by `hide` */
141
+ loading;
128
142
  /** Hides the panorama shown */
129
143
  hide() {
130
144
  this.shown++;
145
+ this.loading?.abort();
131
146
  this.current = undefined;
132
147
  this.host.clearPanorama();
133
148
  }
@@ -145,13 +160,13 @@ export class Panoramas {
145
160
  this.stopWalking = this.host.listen((lonLat) => {
146
161
  const target = lonLat && this.nearest(lonLat);
147
162
  if (target && target !== this.current)
148
- void this.go(target.index);
163
+ this.go(target.index).catch(report);
149
164
  }, (e) => {
150
165
  const step = { z: 1, Z: 1, PageUp: 1, s: -1, S: -1, PageDown: -1 }[e.key];
151
166
  if (!step || !this.current)
152
167
  return;
153
168
  e.preventDefault();
154
- void this.go((this.current.index + step + this.list.length) % this.list.length);
169
+ this.go((this.current.index + step + this.list.length) % this.list.length).catch(report);
155
170
  }, (e) => {
156
171
  // One step per notch, the next ones once it is shown
157
172
  if (this.moving || e.deltaY === 0)
@@ -159,7 +174,9 @@ export class Panoramas {
159
174
  const target = this.ahead(e.deltaY < 0);
160
175
  if (target) {
161
176
  this.moving = true;
162
- void this.go(target.index).finally(() => {
177
+ this.go(target.index)
178
+ .catch(report)
179
+ .finally(() => {
163
180
  this.moving = false;
164
181
  });
165
182
  }
@@ -253,4 +270,8 @@ export const loadPanoramas = async (host, options) => {
253
270
  opacity: options.opacity ?? 1,
254
271
  }, list, attitudes, cameras, lambert93);
255
272
  };
273
+ /** A step of the walk that failed (an image not found): told, the walk going on */
274
+ const report = (error) => {
275
+ console.warn('isard: panorama', error);
276
+ };
256
277
  //# sourceMappingURL=panoramas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"panoramas.js","sourceRoot":"","sources":["../../src/layers/panoramas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Street-level panoramas: the oriented images of a vehicle's cameras around each of its positions (Stereopolis, the\n * iTowns immersive examples), projected onto the scene around the panorama shown. Their positions and attitudes come\n * as GeoJSON points (Lambert-93 by default, altitudes NGF-IGN69 raised by the RAF20 geoid onto the IGN relief), the\n * calibration of the cameras as JSON (rotation and position in the panorama frame, intrinsics, MicMac's radial\n * distortion), the images from a URL template with `{cameraId}` and `{panoId}`.\n *\n * ```ts\n * const panoramas = await map.addPanoramas({ url, orientationsUrl, calibrationUrl });\n * await panoramas.show(panoramas.nearest([2.3345, 48.8505]).index);\n * ```\n */\nimport { getCrs } from '../data/crs.ts';\nimport { withHeaders } from '../engine/requests.ts';\nimport type { FirstPersonView, LonLat } from '../types.ts';\nimport { isNumbers, isRecord } from '../util/guards.ts';\n\nexport interface PanoramaOptions {\n /** URL of an image: `{cameraId}` and `{panoId}` replaced by a camera's id and a panorama's */\n url: string;\n /** GeoJSON points: each panorama's position, and its `id`, `heading`, `pitch`, `roll` (degrees) in properties */\n orientationsUrl: string;\n /** JSON array of the cameras: `id`, `rotation` (row-major), `position`, `projection`, `size`, `distortion` */\n calibrationUrl: string;\n /** CRS of the positions (default the GeoJSON's `crs` member, else `EPSG:2154`) */\n crs?: string;\n /** Altitudes above the geoid (NGF-IGN69), raised by the RAF20 geoid onto the IGN relief (default true in\n * Lambert-93) */\n geoid?: boolean;\n /** Radius of the sphere around the panorama its images are a backdrop on, metres (default 1000) */\n backgroundDistance?: number;\n /** 0-1 (default 1) */\n opacity?: number;\n}\n\n/** A panorama: where it is and which way the vehicle faced */\nexport interface Panorama {\n index: number;\n id: number | string;\n lonLat: LonLat;\n /** Metres above the ellipsoid */\n height: number;\n /** Degrees clockwise from north */\n heading: number;\n}\n\n/** What the map gives the panoramas */\nexport interface PanoramaHost {\n setPanorama(\n lon: number,\n lat: number,\n height: number,\n rotation: Float64Array,\n cameras: Float64Array,\n radius: number,\n opacity: number,\n ): void;\n setPanoramaImage(index: number, image: ImageBitmap): void;\n clearPanorama(): void;\n geoidHeight(lon: number, lat: number): number;\n /** The street-level eye, and setting it (undefined: back to the camera turning around a point) */\n firstPerson(): Readonly<Required<FirstPersonView>> | undefined;\n setFirstPerson(view: FirstPersonView | undefined): void;\n elevationAt(lonLat: LonLat): number | undefined;\n /** Follows the clicks on the map (with the ground clicked), the keys typed on it and its wheel (taken from the\n * map's own); returns what stops */\n listen(\n click: (lonLat: LonLat | undefined) => void,\n key: (e: KeyboardEvent) => void,\n wheel: (e: WheelEvent) => void,\n ): () => void;\n}\n\ninterface Calibration {\n id: number | string;\n rotation: number[];\n position: number[];\n projection: number[];\n size: number[];\n distortion?: { pps: number[]; poly357: number[]; limit?: number };\n}\n\ninterface Orientations {\n crs?: { properties?: { name?: string } };\n features: { geometry: { coordinates: number[] }; properties: Record<string, unknown> }[];\n}\n\nconst isOrientation = (value: unknown): value is Orientations['features'][number] =>\n isRecord(value) && isRecord(value.geometry) && isNumbers(value.geometry.coordinates) && isRecord(value.properties);\n\nconst isCrs = (value: unknown): value is Orientations['crs'] =>\n isRecord(value) &&\n (value.properties === undefined ||\n (isRecord(value.properties) && (value.properties.name === undefined || typeof value.properties.name === 'string')));\n\nconst isOrientations = (value: unknown): value is Orientations =>\n isRecord(value) &&\n (value.crs === undefined || isCrs(value.crs)) &&\n Array.isArray(value.features) &&\n value.features.every(isOrientation);\n\nconst isCalibration = (value: unknown): value is Calibration =>\n isRecord(value) &&\n (typeof value.id === 'number' || typeof value.id === 'string') &&\n isNumbers(value.rotation) &&\n isNumbers(value.position) &&\n isNumbers(value.projection) &&\n isNumbers(value.size) &&\n (value.distortion === undefined ||\n (isRecord(value.distortion) &&\n isNumbers(value.distortion.pps) &&\n isNumbers(value.distortion.poly357) &&\n (value.distortion.limit === undefined || typeof value.distortion.limit === 'number')));\n\nconst isCalibrations = (value: unknown): value is Calibration[] => Array.isArray(value) && value.every(isCalibration);\n\ninterface Attitude {\n heading: number;\n pitch: number;\n roll: number;\n}\n\nconst RAD = Math.PI / 180;\n\n/** 3 × 3 product, row-major */\nconst times = (a: number[], b: number[]): number[] =>\n Array.from({ length: 9 }, (_, i) => {\n const [r, c] = [Math.floor(i / 3), i % 3];\n\n return [0, 1, 2].reduce((sum, k) => sum + (a[r * 3 + k] ?? 0) * (b[k * 3 + c] ?? 0), 0);\n });\n\nconst rotX = (t: number) => [1, 0, 0, 0, Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t)];\nconst rotY = (t: number) => [Math.cos(t), 0, Math.sin(t), 0, 1, 0, -Math.sin(t), 0, Math.cos(t)];\nconst rotZ = (t: number) => [Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t), 0, 0, 0, 1];\n\n/**\n * The panorama frame to east, north, up (row-major): the vehicle's attitude in the CRS's grid (iTowns'\n * `quaternionFromRollPitchHeading`: Ry(-roll) · Rx(-pitch) · Rz(-heading)), turned by the meridian convergence of the\n * projection (Lambert-93: sin(46.5°) · (3° - longitude))\n */\nexport const panoramaRotation = (attitude: Attitude, lon: number, lambert93: boolean): number[] => {\n const vehicle = times(times(rotY(-attitude.roll * RAD), rotX(-attitude.pitch * RAD)), rotZ(-attitude.heading * RAD));\n const convergence = lambert93 ? Math.sin(46.5 * RAD) * (3 - lon) * RAD : 0;\n\n return times(rotZ(convergence), vehicle);\n};\n\n/** The 25 numbers of a camera for `setPanorama` */\nconst cameraNumbers = (c: Calibration): number[] => {\n const p = c.projection;\n const d = c.distortion;\n\n return [\n ...c.rotation.slice(0, 9),\n ...c.position.slice(0, 3),\n p[0] ?? 1,\n p[4] ?? 1,\n p[2] ?? 0,\n p[5] ?? 0,\n p[1] ?? 0,\n c.size[0] ?? 1,\n c.size[1] ?? 1,\n d?.pps[0] ?? 0,\n d?.pps[1] ?? 0,\n d?.poly357[0] ?? 0,\n d?.poly357[1] ?? 0,\n d?.poly357[2] ?? 0,\n d?.limit ?? 0,\n ];\n};\n\n/** The panoramas of a set, one shown at a time */\nexport class Panoramas {\n /** The panorama shown, if any */\n current: Panorama | undefined;\n private shown = 0;\n\n /** @internal */\n constructor(\n private readonly host: PanoramaHost,\n private readonly options: Required<Pick<PanoramaOptions, 'url' | 'backgroundDistance' | 'opacity'>>,\n readonly list: readonly Panorama[],\n private readonly attitudes: readonly Attitude[],\n private readonly cameras: readonly Calibration[],\n private readonly lambert93: boolean,\n ) {}\n\n /** The panorama nearest to a position */\n nearest(lonLat: LonLat): Panorama | undefined {\n const cos = Math.cos(lonLat[1] * RAD);\n\n return this.list.reduce<{ best?: Panorama; distance: number }>(\n (nearest, p) => {\n const d = ((p.lonLat[0] - lonLat[0]) * cos) ** 2 + (p.lonLat[1] - lonLat[1]) ** 2;\n\n return d < nearest.distance ? { best: p, distance: d } : nearest;\n },\n { distance: Number.POSITIVE_INFINITY },\n ).best;\n }\n\n /** Shows a panorama (its index in `list`): projected as soon as its first image arrives, the others after */\n async show(index: number): Promise<void> {\n const pano = this.list[index];\n const attitude = this.attitudes[index];\n\n if (!pano || !attitude) return;\n\n const shown = ++this.shown;\n\n this.current = pano;\n\n const rotation = panoramaRotation(attitude, pano.lonLat[0], this.lambert93);\n const { url, backgroundDistance, opacity } = this.options;\n\n this.host.setPanorama(\n pano.lonLat[0],\n pano.lonLat[1],\n pano.height,\n Float64Array.from(rotation),\n Float64Array.from(this.cameras.flatMap(cameraNumbers)),\n backgroundDistance,\n opacity,\n );\n await Promise.all(\n this.cameras.map(async (camera, k) => {\n const src = url.replaceAll('{cameraId}', String(camera.id)).replaceAll('{panoId}', String(pano.id));\n const response = await fetch(src, withHeaders(src));\n\n if (!response.ok) throw new Error(`isard: panorama image ${src}: ${response.status}`);\n\n const image = await createImageBitmap(await response.blob(), { premultiplyAlpha: 'none' });\n\n // Another panorama shown since: this image is not its\n if (shown === this.shown) this.host.setPanoramaImage(k, image);\n image.close();\n }),\n );\n }\n\n /** Hides the panorama shown */\n hide(): void {\n this.shown++;\n this.current = undefined;\n this.host.clearPanorama();\n }\n\n /** Stops the walk (`enter`) */\n private stopWalking: (() => void) | undefined;\n\n /**\n * Street-level walk from panorama to panorama (the iTowns immersive view): the eye on a panorama, the drag\n * turning it; a click goes to the panorama nearest to the clicked point, the wheel to the nearest one ahead (or\n * behind), Z and S (or Page Up and Page Down) to the next and the previous ones. Starts on the panorama `index`,\n * else the one nearest to `near`\n */\n async enter(start: { index?: number; near?: LonLat } = {}): Promise<void> {\n const first = start.index ?? (start.near && this.nearest(start.near)?.index) ?? 0;\n\n this.stopWalking?.();\n this.stopWalking = this.host.listen(\n (lonLat) => {\n const target = lonLat && this.nearest(lonLat);\n\n if (target && target !== this.current) void this.go(target.index);\n },\n (e) => {\n const step = { z: 1, Z: 1, PageUp: 1, s: -1, S: -1, PageDown: -1 }[e.key];\n\n if (!step || !this.current) return;\n e.preventDefault();\n void this.go((this.current.index + step + this.list.length) % this.list.length);\n },\n (e) => {\n // One step per notch, the next ones once it is shown\n if (this.moving || e.deltaY === 0) return;\n\n const target = this.ahead(e.deltaY < 0);\n\n if (target) {\n this.moving = true;\n void this.go(target.index).finally(() => {\n this.moving = false;\n });\n }\n },\n );\n await this.go(first, this.list[first]?.heading);\n }\n\n /** A step of the wheel under way */\n private moving = false;\n\n /** The nearest panorama ahead of the eye (within 60° of where it looks), or behind it */\n ahead(forward = true): Panorama | undefined {\n const eye = this.host.firstPerson();\n const here = this.current;\n\n if (!eye || !here) return undefined;\n\n const cos = Math.cos(here.lonLat[1] * RAD);\n const look = (eye.bearing + (forward ? 0 : 180)) * RAD;\n\n return this.list.reduce<{ best?: Panorama; distance: number }>(\n (nearest, p) => {\n if (p === here) return nearest;\n\n const [east, north] = [(p.lonLat[0] - here.lonLat[0]) * cos, p.lonLat[1] - here.lonLat[1]];\n const d = Math.hypot(east, north);\n const off = Math.acos(Math.max(-1, Math.min(1, (east * Math.sin(look) + north * Math.cos(look)) / (d || 1))));\n\n return off < 60 * RAD && d < nearest.distance ? { best: p, distance: d } : nearest;\n },\n { distance: Number.POSITIVE_INFINITY },\n ).best;\n }\n\n /** Goes to a panorama during the walk, the eye looking the same way (or `bearing`) */\n async go(index: number, bearing?: number): Promise<void> {\n const pano = this.list[index];\n\n if (!pano) return;\n\n const eye = this.host.firstPerson();\n // The eye at the panorama's cameras, above the ground there\n const ground = this.host.elevationAt(pano.lonLat);\n\n this.host.setFirstPerson({\n lonLat: pano.lonLat,\n height: ground === undefined ? 2.5 : Math.max(0.5, pano.height - ground),\n bearing: bearing ?? eye?.bearing ?? pano.heading,\n ...(eye ? { pitch: eye.pitch, fov: eye.fov } : {}),\n });\n await this.show(index);\n }\n\n /** Ends the walk: the panorama hidden, the camera turning around a point again, above where the eye stood */\n exit(): void {\n this.stopWalking?.();\n this.stopWalking = undefined;\n this.hide();\n this.host.setFirstPerson(undefined);\n }\n}\n\n/** Loads a set of panoramas: their positions and attitudes, the calibration of their cameras */\nexport const loadPanoramas = async (host: PanoramaHost, options: PanoramaOptions): Promise<Panoramas> => {\n const json = async <T>(src: string, is: (value: unknown) => value is T): Promise<T> => {\n const response = await fetch(src, withHeaders(src));\n\n if (!response.ok) throw new Error(`isard: ${src}: ${response.status}`);\n\n const value: unknown = await response.json();\n\n if (!is(value)) throw new Error(`isard: ${src}: unexpected content`);\n\n return value;\n };\n const [orientations, cameras] = await Promise.all([\n json(options.orientationsUrl, isOrientations),\n json(options.calibrationUrl, isCalibrations),\n ]);\n const crsName = options.crs ?? orientations.crs?.properties?.name ?? 'EPSG:2154';\n const lambert93 = /2154/.test(crsName);\n const crs = getCrs(crsName);\n const geoid = options.geoid ?? lambert93;\n const list: Panorama[] = [];\n const attitudes: Attitude[] = [];\n\n orientations.features.forEach((feature) => {\n const [x = 0, y = 0, z = 0] = feature.geometry.coordinates;\n const [lon = 0, lat = 0] = crs.inverse([x, y]);\n const p = feature.properties;\n const number = (key: string) => {\n const value = p[key];\n\n return typeof value === 'number' ? value : 0;\n };\n\n attitudes.push({ heading: number('heading'), pitch: number('pitch'), roll: number('roll') });\n list.push({\n index: list.length,\n id: typeof p.id === 'number' || typeof p.id === 'string' ? p.id : list.length,\n lonLat: [lon, lat],\n height: z + (geoid ? host.geoidHeight(lon, lat) : 0),\n heading: number('heading'),\n });\n });\n\n return new Panoramas(\n host,\n {\n url: options.url,\n backgroundDistance: options.backgroundDistance ?? 1000,\n opacity: options.opacity ?? 1,\n },\n list,\n attitudes,\n cameras,\n lambert93,\n );\n};\n"]}
1
+ {"version":3,"file":"panoramas.js","sourceRoot":"","sources":["../../src/layers/panoramas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Street-level panoramas: the oriented images of a vehicle's cameras around each of its positions (Stereopolis, the\n * iTowns immersive examples), projected onto the scene around the panorama shown. Their positions and attitudes come\n * as GeoJSON points (Lambert-93 by default, altitudes NGF-IGN69 raised by the RAF20 geoid onto the IGN relief), the\n * calibration of the cameras as JSON (rotation and position in the panorama frame, intrinsics, MicMac's radial\n * distortion), the images from a URL template with `{cameraId}` and `{panoId}`.\n *\n * ```ts\n * const panoramas = await map.addPanoramas({ url, orientationsUrl, calibrationUrl });\n * await panoramas.show(panoramas.nearest([2.3345, 48.8505]).index);\n * ```\n */\nimport { getCrs } from '../data/crs.ts';\nimport { withHeaders } from '../engine/requests.ts';\nimport type { FirstPersonView, LonLat } from '../types.ts';\nimport { isNumbers, isRecord } from '../util/guards.ts';\n\nexport interface PanoramaOptions {\n /** URL of an image: `{cameraId}` and `{panoId}` replaced by a camera's id and a panorama's */\n url: string;\n /** GeoJSON points: each panorama's position, and its `id`, `heading`, `pitch`, `roll` (degrees) in properties */\n orientationsUrl: string;\n /** JSON array of the cameras: `id`, `rotation` (row-major), `position`, `projection`, `size`, `distortion` */\n calibrationUrl: string;\n /** CRS of the positions (default the GeoJSON's `crs` member, else `EPSG:2154`) */\n crs?: string;\n /** Altitudes above the geoid (NGF-IGN69), raised by the RAF20 geoid onto the IGN relief (default true in\n * Lambert-93) */\n geoid?: boolean;\n /** Radius of the sphere around the panorama its images are a backdrop on, metres (default 1000) */\n backgroundDistance?: number;\n /** 0-1 (default 1) */\n opacity?: number;\n}\n\n/** A panorama: where it is and which way the vehicle faced */\nexport interface Panorama {\n index: number;\n id: number | string;\n lonLat: LonLat;\n /** Metres above the ellipsoid */\n height: number;\n /** Degrees clockwise from north */\n heading: number;\n}\n\n/** What the map gives the panoramas */\nexport interface PanoramaHost {\n setPanorama(\n lon: number,\n lat: number,\n height: number,\n rotation: Float64Array,\n cameras: Float64Array,\n radius: number,\n opacity: number,\n ): void;\n setPanoramaImage(index: number, image: ImageBitmap): void;\n clearPanorama(): void;\n geoidHeight(lon: number, lat: number): number;\n /** The street-level eye, and setting it (undefined: back to the camera turning around a point) */\n firstPerson(): Readonly<Required<FirstPersonView>> | undefined;\n setFirstPerson(view: FirstPersonView | undefined): void;\n elevationAt(lonLat: LonLat): number | undefined;\n /** Follows the clicks on the map (with the ground clicked), the keys typed on it and its wheel (taken from the\n * map's own); returns what stops */\n listen(\n click: (lonLat: LonLat | undefined) => void,\n key: (e: KeyboardEvent) => void,\n wheel: (e: WheelEvent) => void,\n ): () => void;\n}\n\ninterface Calibration {\n id: number | string;\n rotation: number[];\n position: number[];\n projection: number[];\n size: number[];\n distortion?: { pps: number[]; poly357: number[]; limit?: number };\n}\n\ninterface Orientations {\n crs?: { properties?: { name?: string } };\n features: { geometry: { coordinates: number[] }; properties: Record<string, unknown> }[];\n}\n\nconst isOrientation = (value: unknown): value is Orientations['features'][number] =>\n isRecord(value) && isRecord(value.geometry) && isNumbers(value.geometry.coordinates) && isRecord(value.properties);\n\nconst isCrs = (value: unknown): value is Orientations['crs'] =>\n isRecord(value) &&\n (value.properties === undefined ||\n (isRecord(value.properties) && (value.properties.name === undefined || typeof value.properties.name === 'string')));\n\nconst isOrientations = (value: unknown): value is Orientations =>\n isRecord(value) &&\n (value.crs === undefined || isCrs(value.crs)) &&\n Array.isArray(value.features) &&\n value.features.every(isOrientation);\n\nconst isCalibration = (value: unknown): value is Calibration =>\n isRecord(value) &&\n (typeof value.id === 'number' || typeof value.id === 'string') &&\n isNumbers(value.rotation) &&\n isNumbers(value.position) &&\n isNumbers(value.projection) &&\n isNumbers(value.size) &&\n (value.distortion === undefined ||\n (isRecord(value.distortion) &&\n isNumbers(value.distortion.pps) &&\n isNumbers(value.distortion.poly357) &&\n (value.distortion.limit === undefined || typeof value.distortion.limit === 'number')));\n\nconst isCalibrations = (value: unknown): value is Calibration[] => Array.isArray(value) && value.every(isCalibration);\n\ninterface Attitude {\n heading: number;\n pitch: number;\n roll: number;\n}\n\nconst RAD = Math.PI / 180;\n\n/** 3 × 3 product, row-major */\nconst times = (a: number[], b: number[]): number[] =>\n Array.from({ length: 9 }, (_, i) => {\n const [r, c] = [Math.floor(i / 3), i % 3];\n\n return [0, 1, 2].reduce((sum, k) => sum + (a[r * 3 + k] ?? 0) * (b[k * 3 + c] ?? 0), 0);\n });\n\nconst rotX = (t: number) => [1, 0, 0, 0, Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t)];\nconst rotY = (t: number) => [Math.cos(t), 0, Math.sin(t), 0, 1, 0, -Math.sin(t), 0, Math.cos(t)];\nconst rotZ = (t: number) => [Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t), 0, 0, 0, 1];\n\n/**\n * The panorama frame to east, north, up (row-major): the vehicle's attitude in the CRS's grid (iTowns'\n * `quaternionFromRollPitchHeading`: Ry(-roll) · Rx(-pitch) · Rz(-heading)), turned by the meridian convergence of the\n * projection (Lambert-93: sin(46.5°) · (3° - longitude))\n */\nexport const panoramaRotation = (attitude: Attitude, lon: number, lambert93: boolean): number[] => {\n const vehicle = times(times(rotY(-attitude.roll * RAD), rotX(-attitude.pitch * RAD)), rotZ(-attitude.heading * RAD));\n const convergence = lambert93 ? Math.sin(46.5 * RAD) * (3 - lon) * RAD : 0;\n\n return times(rotZ(convergence), vehicle);\n};\n\n/** The 25 numbers of a camera for `setPanorama` */\nconst cameraNumbers = (c: Calibration): number[] => {\n const p = c.projection;\n const d = c.distortion;\n\n return [\n ...c.rotation.slice(0, 9),\n ...c.position.slice(0, 3),\n p[0] ?? 1,\n p[4] ?? 1,\n p[2] ?? 0,\n p[5] ?? 0,\n p[1] ?? 0,\n c.size[0] ?? 1,\n c.size[1] ?? 1,\n d?.pps[0] ?? 0,\n d?.pps[1] ?? 0,\n d?.poly357[0] ?? 0,\n d?.poly357[1] ?? 0,\n d?.poly357[2] ?? 0,\n d?.limit ?? 0,\n ];\n};\n\n/** The panoramas of a set, one shown at a time */\nexport class Panoramas {\n /** The panorama shown, if any */\n current: Panorama | undefined;\n private shown = 0;\n\n /** @internal */\n constructor(\n private readonly host: PanoramaHost,\n private readonly options: Required<Pick<PanoramaOptions, 'url' | 'backgroundDistance' | 'opacity'>>,\n readonly list: readonly Panorama[],\n private readonly attitudes: readonly Attitude[],\n private readonly cameras: readonly Calibration[],\n private readonly lambert93: boolean,\n ) {}\n\n /** The panorama nearest to a position */\n nearest(lonLat: LonLat): Panorama | undefined {\n const cos = Math.cos(lonLat[1] * RAD);\n\n return this.list.reduce<{ best?: Panorama; distance: number }>(\n (nearest, p) => {\n const d = ((p.lonLat[0] - lonLat[0]) * cos) ** 2 + (p.lonLat[1] - lonLat[1]) ** 2;\n\n return d < nearest.distance ? { best: p, distance: d } : nearest;\n },\n { distance: Number.POSITIVE_INFINITY },\n ).best;\n }\n\n /** Shows a panorama (its index in `list`): projected as soon as its first image arrives, the others after */\n async show(index: number): Promise<void> {\n const pano = this.list[index];\n const attitude = this.attitudes[index];\n\n if (!pano || !attitude) return;\n\n const shown = ++this.shown;\n // The images of the panorama shown before: no longer downloaded nor decoded\n const controller = new AbortController();\n\n this.loading?.abort();\n this.loading = controller;\n this.current = pano;\n\n const rotation = panoramaRotation(attitude, pano.lonLat[0], this.lambert93);\n const { url, backgroundDistance, opacity } = this.options;\n\n this.host.setPanorama(\n pano.lonLat[0],\n pano.lonLat[1],\n pano.height,\n Float64Array.from(rotation),\n Float64Array.from(this.cameras.flatMap(cameraNumbers)),\n backgroundDistance,\n opacity,\n );\n const images = Promise.all(\n this.cameras.map(async (camera, k) => {\n const src = url.replaceAll('{cameraId}', String(camera.id)).replaceAll('{panoId}', String(pano.id));\n const response = await fetch(src, withHeaders(src, { signal: controller.signal }));\n\n if (!response.ok) throw new Error(`isard: panorama image ${src}: ${response.status}`);\n\n const blob = await response.blob();\n\n if (controller.signal.aborted) return;\n\n const image = await createImageBitmap(blob, { premultiplyAlpha: 'none' });\n\n // Another panorama shown since: this image is not its\n if (shown === this.shown) this.host.setPanoramaImage(k, image);\n image.close();\n }),\n );\n\n // Another panorama shown, or this one hidden, meanwhile: not a failure\n await images.catch((error: unknown) => {\n if (!controller.signal.aborted) throw error;\n });\n }\n\n /** The downloads of the panorama shown, aborted by the next one shown or by `hide` */\n private loading: AbortController | undefined;\n\n /** Hides the panorama shown */\n hide(): void {\n this.shown++;\n this.loading?.abort();\n this.current = undefined;\n this.host.clearPanorama();\n }\n\n /** Stops the walk (`enter`) */\n private stopWalking: (() => void) | undefined;\n\n /**\n * Street-level walk from panorama to panorama (the iTowns immersive view): the eye on a panorama, the drag\n * turning it; a click goes to the panorama nearest to the clicked point, the wheel to the nearest one ahead (or\n * behind), Z and S (or Page Up and Page Down) to the next and the previous ones. Starts on the panorama `index`,\n * else the one nearest to `near`\n */\n async enter(start: { index?: number; near?: LonLat } = {}): Promise<void> {\n const first = start.index ?? (start.near && this.nearest(start.near)?.index) ?? 0;\n\n this.stopWalking?.();\n this.stopWalking = this.host.listen(\n (lonLat) => {\n const target = lonLat && this.nearest(lonLat);\n\n if (target && target !== this.current) this.go(target.index).catch(report);\n },\n (e) => {\n const step = { z: 1, Z: 1, PageUp: 1, s: -1, S: -1, PageDown: -1 }[e.key];\n\n if (!step || !this.current) return;\n e.preventDefault();\n this.go((this.current.index + step + this.list.length) % this.list.length).catch(report);\n },\n (e) => {\n // One step per notch, the next ones once it is shown\n if (this.moving || e.deltaY === 0) return;\n\n const target = this.ahead(e.deltaY < 0);\n\n if (target) {\n this.moving = true;\n this.go(target.index)\n .catch(report)\n .finally(() => {\n this.moving = false;\n });\n }\n },\n );\n await this.go(first, this.list[first]?.heading);\n }\n\n /** A step of the wheel under way */\n private moving = false;\n\n /** The nearest panorama ahead of the eye (within 60° of where it looks), or behind it */\n ahead(forward = true): Panorama | undefined {\n const eye = this.host.firstPerson();\n const here = this.current;\n\n if (!eye || !here) return undefined;\n\n const cos = Math.cos(here.lonLat[1] * RAD);\n const look = (eye.bearing + (forward ? 0 : 180)) * RAD;\n\n return this.list.reduce<{ best?: Panorama; distance: number }>(\n (nearest, p) => {\n if (p === here) return nearest;\n\n const [east, north] = [(p.lonLat[0] - here.lonLat[0]) * cos, p.lonLat[1] - here.lonLat[1]];\n const d = Math.hypot(east, north);\n const off = Math.acos(Math.max(-1, Math.min(1, (east * Math.sin(look) + north * Math.cos(look)) / (d || 1))));\n\n return off < 60 * RAD && d < nearest.distance ? { best: p, distance: d } : nearest;\n },\n { distance: Number.POSITIVE_INFINITY },\n ).best;\n }\n\n /** Goes to a panorama during the walk, the eye looking the same way (or `bearing`) */\n async go(index: number, bearing?: number): Promise<void> {\n const pano = this.list[index];\n\n if (!pano) return;\n\n const eye = this.host.firstPerson();\n // The eye at the panorama's cameras, above the ground there\n const ground = this.host.elevationAt(pano.lonLat);\n\n this.host.setFirstPerson({\n lonLat: pano.lonLat,\n height: ground === undefined ? 2.5 : Math.max(0.5, pano.height - ground),\n bearing: bearing ?? eye?.bearing ?? pano.heading,\n ...(eye ? { pitch: eye.pitch, fov: eye.fov } : {}),\n });\n await this.show(index);\n }\n\n /** Ends the walk: the panorama hidden, the camera turning around a point again, above where the eye stood */\n exit(): void {\n this.stopWalking?.();\n this.stopWalking = undefined;\n this.hide();\n this.host.setFirstPerson(undefined);\n }\n}\n\n/** Loads a set of panoramas: their positions and attitudes, the calibration of their cameras */\nexport const loadPanoramas = async (host: PanoramaHost, options: PanoramaOptions): Promise<Panoramas> => {\n const json = async <T>(src: string, is: (value: unknown) => value is T): Promise<T> => {\n const response = await fetch(src, withHeaders(src));\n\n if (!response.ok) throw new Error(`isard: ${src}: ${response.status}`);\n\n const value: unknown = await response.json();\n\n if (!is(value)) throw new Error(`isard: ${src}: unexpected content`);\n\n return value;\n };\n const [orientations, cameras] = await Promise.all([\n json(options.orientationsUrl, isOrientations),\n json(options.calibrationUrl, isCalibrations),\n ]);\n const crsName = options.crs ?? orientations.crs?.properties?.name ?? 'EPSG:2154';\n const lambert93 = /2154/.test(crsName);\n const crs = getCrs(crsName);\n const geoid = options.geoid ?? lambert93;\n const list: Panorama[] = [];\n const attitudes: Attitude[] = [];\n\n orientations.features.forEach((feature) => {\n const [x = 0, y = 0, z = 0] = feature.geometry.coordinates;\n const [lon = 0, lat = 0] = crs.inverse([x, y]);\n const p = feature.properties;\n const number = (key: string) => {\n const value = p[key];\n\n return typeof value === 'number' ? value : 0;\n };\n\n attitudes.push({ heading: number('heading'), pitch: number('pitch'), roll: number('roll') });\n list.push({\n index: list.length,\n id: typeof p.id === 'number' || typeof p.id === 'string' ? p.id : list.length,\n lonLat: [lon, lat],\n height: z + (geoid ? host.geoidHeight(lon, lat) : 0),\n heading: number('heading'),\n });\n });\n\n return new Panoramas(\n host,\n {\n url: options.url,\n backgroundDistance: options.backgroundDistance ?? 1000,\n opacity: options.opacity ?? 1,\n },\n list,\n attitudes,\n cameras,\n lambert93,\n );\n};\n\n/** A step of the walk that failed (an image not found): told, the walk going on */\nconst report = (error: unknown): void => {\n console.warn('isard: panorama', error);\n};\n"]}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A photo standing where it was taken (a street-level photo: Panoramax's, a phone's), facing the way it looked, over
3
+ * the 3D map: a flat photo on a frame across its field of view, a 360° photo (equirectangular) on a band curved round
4
+ * its camera, each pixel where the camera saw it. Seen from where it was taken, it lies over the buildings it shows.
5
+ */
6
+ import type { LonLat } from '../types.ts';
7
+ import type { Layer, ModelLayer, ModelOptions } from './layers.ts';
8
+ /** The options of `GeoMap.addPhoto` */
9
+ export interface PhotoOptions {
10
+ /** The image (JPEG or PNG); or its bytes (`data`) */
11
+ url?: string;
12
+ data?: ArrayBuffer | ArrayBufferView<ArrayBuffer>;
13
+ /** Where it was taken */
14
+ lonLat: LonLat;
15
+ /** Degrees clockwise from the north of the photo's middle (0 by default) */
16
+ heading?: number;
17
+ /** `'flat'` (the default) or `'equirectangular'`: a 360° photo, its middle at `heading` */
18
+ projection?: 'flat' | 'equirectangular';
19
+ /** A flat photo's field of view across, degrees (70 by default) */
20
+ fov?: number;
21
+ /** Degrees of a 360° photo shown round its heading (120 by default) */
22
+ span?: number;
23
+ /** Metres of its camera above the ground (2 by default) */
24
+ height?: number;
25
+ /** Metres from its camera to the photo drawn (15 by default) */
26
+ distance?: number;
27
+ before?: Layer;
28
+ }
29
+ /** A photo's surface: positions (glTF: x left, y up, z ahead, metres from its foot), image coordinates, triangles */
30
+ export interface PhotoMesh {
31
+ positions: number[];
32
+ uvs: number[];
33
+ indices: number[];
34
+ }
35
+ /**
36
+ * The surface a photo is drawn on, `aspect` its width over its height: a flat photo on a frame `distance` ahead
37
+ * spanning its field of view; a 360° photo on a band `distance` round its camera, its columns at their bearings and its
38
+ * rows at their heights over the horizon, as the camera saw them
39
+ */
40
+ export declare const photoMesh: (options: Omit<PhotoOptions, "lonLat">, aspect: number) => PhotoMesh;
41
+ /** The width and height of a JPEG or a PNG from its header, undefined for another image */
42
+ export declare const imageSize: (bytes: Uint8Array) => [width: number, height: number] | undefined;
43
+ /** A photo as a glTF binary: its surface (`photoMesh`) textured by its image, unlit, both sides drawn */
44
+ export declare const photoModel: (image: Uint8Array, options?: Omit<PhotoOptions, "lonLat">) => Uint8Array<ArrayBuffer>;
45
+ /** The photo's layer (`addModels`: the map's), its image read */
46
+ export declare const addPhoto: (addModels: (options: ModelOptions & {
47
+ before?: Layer;
48
+ }) => ModelLayer, options: PhotoOptions) => Promise<ModelLayer>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * A photo standing where it was taken (a street-level photo: Panoramax's, a phone's), facing the way it looked, over
3
+ * the 3D map: a flat photo on a frame across its field of view, a 360° photo (equirectangular) on a band curved round
4
+ * its camera, each pixel where the camera saw it. Seen from where it was taken, it lies over the buildings it shows.
5
+ */
6
+ import { withHeaders } from "../engine/requests.js";
7
+ import { bounds, glbOf } from "../util/gltf.js";
8
+ /** Degrees between the rows and the columns of a 360° photo's band */
9
+ const STEP = 5;
10
+ /** Degrees of a 360° photo shown above and below the horizon */
11
+ const TILT = 40;
12
+ /** Degrees to radians */
13
+ const rad = (degrees) => (degrees * Math.PI) / 180;
14
+ /**
15
+ * The surface a photo is drawn on, `aspect` its width over its height: a flat photo on a frame `distance` ahead
16
+ * spanning its field of view; a 360° photo on a band `distance` round its camera, its columns at their bearings and its
17
+ * rows at their heights over the horizon, as the camera saw them
18
+ */
19
+ export const photoMesh = (options, aspect) => {
20
+ const { projection = 'flat', fov = 70, span = 120, height = 2, distance = 15 } = options;
21
+ // A grid of columns (left to right) and rows (top to bottom): its points and their image coordinates
22
+ const grid = (columns, rows, at) => {
23
+ const points = rows.flatMap((row) => columns.map((column) => at(column, row)));
24
+ const indices = rows.slice(1).flatMap((_, r) => columns.slice(1).flatMap((_, c) => {
25
+ const [a, b] = [r * columns.length + c, r * columns.length + c + 1];
26
+ const [d, e] = [a + columns.length, b + columns.length];
27
+ // Facing the camera
28
+ return [a, d, b, b, d, e];
29
+ }));
30
+ return {
31
+ positions: points.flatMap((p) => p.slice(0, 3)),
32
+ uvs: points.flatMap(([, , , u, v]) => [u, v]),
33
+ indices,
34
+ };
35
+ };
36
+ if (projection === 'flat') {
37
+ const half = distance * Math.tan(rad(Math.min(fov, 170) / 2));
38
+ // Its left (u 0) at +x, glTF's left; its top (v 0) up
39
+ return grid([0, 1], [0, 1], (u, v) => [half * (1 - 2 * u), height + (half / aspect) * (1 - 2 * v), distance, u, v]);
40
+ }
41
+ const range = (from, to) => Array.from({ length: Math.round((to - from) / STEP) + 1 }, (_, i) => from + i * STEP);
42
+ const half = Math.min(span, 360) / 2;
43
+ // Bearing `theta` right of the heading, `phi` above the horizon: in the image at u 0.5 + theta / 360, v 0.5 - phi / 180
44
+ return grid(range(-half, half), range(-TILT, TILT).reverse(), (theta, phi) => [
45
+ -distance * Math.sin(rad(theta)),
46
+ height + distance * Math.tan(rad(phi)),
47
+ distance * Math.cos(rad(theta)),
48
+ 0.5 + theta / 360,
49
+ 0.5 - phi / 180,
50
+ ]);
51
+ };
52
+ /** The width and height of a JPEG or a PNG from its header, undefined for another image */
53
+ export const imageSize = (bytes) => {
54
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
55
+ // PNG: its IHDR chunk first
56
+ if (bytes.length >= 24 && view.getUint32(0) === 0x89504e47)
57
+ return [view.getUint32(16), view.getUint32(20)];
58
+ if (bytes.length < 4 || view.getUint16(0) !== 0xffd8)
59
+ return undefined;
60
+ // JPEG: the segments up to a start of frame (C0 to CF, but C4, C8 and CC)
61
+ for (let at = 2; at + 9 < bytes.length;) {
62
+ const marker = view.getUint16(at);
63
+ const length = view.getUint16(at + 2);
64
+ if ((marker & 0xff00) !== 0xff00)
65
+ return undefined;
66
+ if (marker >= 0xffc0 && marker <= 0xffcf && ![0xffc4, 0xffc8, 0xffcc].includes(marker))
67
+ return [view.getUint16(at + 7), view.getUint16(at + 5)];
68
+ at += 2 + length;
69
+ }
70
+ return undefined;
71
+ };
72
+ /** A photo as a glTF binary: its surface (`photoMesh`) textured by its image, unlit, both sides drawn */
73
+ export const photoModel = (image, options = {}) => {
74
+ const [width, height] = imageSize(image) ?? [4, 3];
75
+ const mesh = photoMesh(options, width / height);
76
+ const floats = new Float32Array([...mesh.positions, ...mesh.uvs]);
77
+ const indices = new Uint32Array(mesh.indices);
78
+ const parts = [new Uint8Array(floats.buffer), new Uint8Array(indices.buffer), image];
79
+ // Each part 4-byte aligned in the buffer
80
+ const offsets = parts.map((_, i) => parts.slice(0, i).reduce((n, p) => n + Math.ceil(p.byteLength / 4) * 4, 0));
81
+ const bin = new Uint8Array((offsets.at(-1) ?? 0) + image.byteLength);
82
+ parts.forEach((p, i) => {
83
+ bin.set(p, offsets[i]);
84
+ });
85
+ const count = mesh.positions.length / 3;
86
+ const gltf = {
87
+ asset: { version: '2.0', generator: 'isard' },
88
+ extensionsUsed: ['KHR_materials_unlit'],
89
+ scene: 0,
90
+ scenes: [{ nodes: [0] }],
91
+ nodes: [{ mesh: 0 }],
92
+ meshes: [{ primitives: [{ attributes: { POSITION: 0, TEXCOORD_0: 1 }, indices: 2, material: 0 }] }],
93
+ materials: [
94
+ {
95
+ pbrMetallicRoughness: { baseColorTexture: { index: 0 }, metallicFactor: 0, roughnessFactor: 1 },
96
+ extensions: { KHR_materials_unlit: {} },
97
+ doubleSided: true,
98
+ },
99
+ ],
100
+ textures: [{ source: 0, sampler: 0 }],
101
+ samplers: [{ magFilter: 9729, minFilter: 9987, wrapS: 33071, wrapT: 33071 }],
102
+ images: [{ bufferView: 3, mimeType: image[0] === 0x89 ? 'image/png' : 'image/jpeg' }],
103
+ accessors: [
104
+ { bufferView: 0, componentType: 5126, count, type: 'VEC3', ...bounds(mesh.positions) },
105
+ { bufferView: 1, componentType: 5126, count, type: 'VEC2' },
106
+ { bufferView: 2, componentType: 5125, count: indices.length, type: 'SCALAR' },
107
+ ],
108
+ bufferViews: [
109
+ { buffer: 0, byteOffset: 0, byteLength: mesh.positions.length * 4, target: 34962 },
110
+ { buffer: 0, byteOffset: mesh.positions.length * 4, byteLength: mesh.uvs.length * 4, target: 34962 },
111
+ { buffer: 0, byteOffset: offsets[1], byteLength: indices.byteLength, target: 34963 },
112
+ { buffer: 0, byteOffset: offsets[2], byteLength: image.byteLength },
113
+ ],
114
+ buffers: [{ byteLength: bin.byteLength }],
115
+ };
116
+ return glbOf(gltf, bin);
117
+ };
118
+ /** The bytes of a photo's image: given, or read from its URL (with the map's request headers) */
119
+ const imageOf = async ({ url, data }) => {
120
+ if (data)
121
+ return ArrayBuffer.isView(data)
122
+ ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
123
+ : new Uint8Array(data);
124
+ if (!url)
125
+ throw new Error('isard: a photo needs a url or data');
126
+ const response = await fetch(url, withHeaders(url));
127
+ if (!response.ok)
128
+ throw new Error(`isard: ${url}: ${response.status}`);
129
+ return new Uint8Array(await response.arrayBuffer());
130
+ };
131
+ /** The photo's layer (`addModels`: the map's), its image read */
132
+ export const addPhoto = async (addModels, options) => {
133
+ const data = photoModel(await imageOf(options), options);
134
+ return addModels({
135
+ data,
136
+ instances: [{ lonLat: options.lonLat, heading: options.heading ?? 0 }],
137
+ ...(options.before && { before: options.before }),
138
+ });
139
+ };
140
+ //# sourceMappingURL=photos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"photos.js","sourceRoot":"","sources":["../../src/layers/photos.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A photo standing where it was taken (a street-level photo: Panoramax's, a phone's), facing the way it looked, over\n * the 3D map: a flat photo on a frame across its field of view, a 360° photo (equirectangular) on a band curved round\n * its camera, each pixel where the camera saw it. Seen from where it was taken, it lies over the buildings it shows.\n */\n\nimport { withHeaders } from '../engine/requests.ts';\nimport type { LonLat } from '../types.ts';\nimport { bounds, glbOf } from '../util/gltf.ts';\nimport type { Layer, ModelLayer, ModelOptions } from './layers.ts';\n\n/** The options of `GeoMap.addPhoto` */\nexport interface PhotoOptions {\n /** The image (JPEG or PNG); or its bytes (`data`) */\n url?: string;\n data?: ArrayBuffer | ArrayBufferView<ArrayBuffer>;\n /** Where it was taken */\n lonLat: LonLat;\n /** Degrees clockwise from the north of the photo's middle (0 by default) */\n heading?: number;\n /** `'flat'` (the default) or `'equirectangular'`: a 360° photo, its middle at `heading` */\n projection?: 'flat' | 'equirectangular';\n /** A flat photo's field of view across, degrees (70 by default) */\n fov?: number;\n /** Degrees of a 360° photo shown round its heading (120 by default) */\n span?: number;\n /** Metres of its camera above the ground (2 by default) */\n height?: number;\n /** Metres from its camera to the photo drawn (15 by default) */\n distance?: number;\n before?: Layer;\n}\n\n/** A photo's surface: positions (glTF: x left, y up, z ahead, metres from its foot), image coordinates, triangles */\nexport interface PhotoMesh {\n positions: number[];\n uvs: number[];\n indices: number[];\n}\n\n/** Degrees between the rows and the columns of a 360° photo's band */\nconst STEP = 5;\n/** Degrees of a 360° photo shown above and below the horizon */\nconst TILT = 40;\n\n/** Degrees to radians */\nconst rad = (degrees: number): number => (degrees * Math.PI) / 180;\n\n/**\n * The surface a photo is drawn on, `aspect` its width over its height: a flat photo on a frame `distance` ahead\n * spanning its field of view; a 360° photo on a band `distance` round its camera, its columns at their bearings and its\n * rows at their heights over the horizon, as the camera saw them\n */\nexport const photoMesh = (options: Omit<PhotoOptions, 'lonLat'>, aspect: number): PhotoMesh => {\n const { projection = 'flat', fov = 70, span = 120, height = 2, distance = 15 } = options;\n // A grid of columns (left to right) and rows (top to bottom): its points and their image coordinates\n const grid = (\n columns: number[],\n rows: number[],\n at: (column: number, row: number) => [x: number, y: number, z: number, u: number, v: number],\n ): PhotoMesh => {\n const points = rows.flatMap((row) => columns.map((column) => at(column, row)));\n const indices = rows.slice(1).flatMap((_, r) =>\n columns.slice(1).flatMap((_, c) => {\n const [a, b] = [r * columns.length + c, r * columns.length + c + 1];\n const [d, e] = [a + columns.length, b + columns.length];\n\n // Facing the camera\n return [a, d, b, b, d, e];\n }),\n );\n\n return {\n positions: points.flatMap((p) => p.slice(0, 3)),\n uvs: points.flatMap(([, , , u, v]) => [u, v]),\n indices,\n };\n };\n\n if (projection === 'flat') {\n const half = distance * Math.tan(rad(Math.min(fov, 170) / 2));\n\n // Its left (u 0) at +x, glTF's left; its top (v 0) up\n return grid([0, 1], [0, 1], (u, v) => [half * (1 - 2 * u), height + (half / aspect) * (1 - 2 * v), distance, u, v]);\n }\n\n const range = (from: number, to: number) =>\n Array.from({ length: Math.round((to - from) / STEP) + 1 }, (_, i) => from + i * STEP);\n const half = Math.min(span, 360) / 2;\n\n // Bearing `theta` right of the heading, `phi` above the horizon: in the image at u 0.5 + theta / 360, v 0.5 - phi / 180\n return grid(range(-half, half), range(-TILT, TILT).reverse(), (theta, phi) => [\n -distance * Math.sin(rad(theta)),\n height + distance * Math.tan(rad(phi)),\n distance * Math.cos(rad(theta)),\n 0.5 + theta / 360,\n 0.5 - phi / 180,\n ]);\n};\n\n/** The width and height of a JPEG or a PNG from its header, undefined for another image */\nexport const imageSize = (bytes: Uint8Array): [width: number, height: number] | undefined => {\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\n // PNG: its IHDR chunk first\n if (bytes.length >= 24 && view.getUint32(0) === 0x89504e47) return [view.getUint32(16), view.getUint32(20)];\n if (bytes.length < 4 || view.getUint16(0) !== 0xffd8) return undefined;\n\n // JPEG: the segments up to a start of frame (C0 to CF, but C4, C8 and CC)\n for (let at = 2; at + 9 < bytes.length; ) {\n const marker = view.getUint16(at);\n const length = view.getUint16(at + 2);\n\n if ((marker & 0xff00) !== 0xff00) return undefined;\n if (marker >= 0xffc0 && marker <= 0xffcf && ![0xffc4, 0xffc8, 0xffcc].includes(marker))\n return [view.getUint16(at + 7), view.getUint16(at + 5)];\n at += 2 + length;\n }\n\n return undefined;\n};\n\n/** A photo as a glTF binary: its surface (`photoMesh`) textured by its image, unlit, both sides drawn */\nexport const photoModel = (image: Uint8Array, options: Omit<PhotoOptions, 'lonLat'> = {}): Uint8Array<ArrayBuffer> => {\n const [width, height] = imageSize(image) ?? [4, 3];\n const mesh = photoMesh(options, width / height);\n const floats = new Float32Array([...mesh.positions, ...mesh.uvs]);\n const indices = new Uint32Array(mesh.indices);\n const parts = [new Uint8Array(floats.buffer), new Uint8Array(indices.buffer), image];\n // Each part 4-byte aligned in the buffer\n const offsets = parts.map((_, i) => parts.slice(0, i).reduce((n, p) => n + Math.ceil(p.byteLength / 4) * 4, 0));\n const bin = new Uint8Array((offsets.at(-1) ?? 0) + image.byteLength);\n\n parts.forEach((p, i) => {\n bin.set(p, offsets[i]);\n });\n\n const count = mesh.positions.length / 3;\n const gltf = {\n asset: { version: '2.0', generator: 'isard' },\n extensionsUsed: ['KHR_materials_unlit'],\n scene: 0,\n scenes: [{ nodes: [0] }],\n nodes: [{ mesh: 0 }],\n meshes: [{ primitives: [{ attributes: { POSITION: 0, TEXCOORD_0: 1 }, indices: 2, material: 0 }] }],\n materials: [\n {\n pbrMetallicRoughness: { baseColorTexture: { index: 0 }, metallicFactor: 0, roughnessFactor: 1 },\n extensions: { KHR_materials_unlit: {} },\n doubleSided: true,\n },\n ],\n textures: [{ source: 0, sampler: 0 }],\n samplers: [{ magFilter: 9729, minFilter: 9987, wrapS: 33071, wrapT: 33071 }],\n images: [{ bufferView: 3, mimeType: image[0] === 0x89 ? 'image/png' : 'image/jpeg' }],\n accessors: [\n { bufferView: 0, componentType: 5126, count, type: 'VEC3', ...bounds(mesh.positions) },\n { bufferView: 1, componentType: 5126, count, type: 'VEC2' },\n { bufferView: 2, componentType: 5125, count: indices.length, type: 'SCALAR' },\n ],\n bufferViews: [\n { buffer: 0, byteOffset: 0, byteLength: mesh.positions.length * 4, target: 34962 },\n { buffer: 0, byteOffset: mesh.positions.length * 4, byteLength: mesh.uvs.length * 4, target: 34962 },\n { buffer: 0, byteOffset: offsets[1], byteLength: indices.byteLength, target: 34963 },\n { buffer: 0, byteOffset: offsets[2], byteLength: image.byteLength },\n ],\n buffers: [{ byteLength: bin.byteLength }],\n };\n\n return glbOf(gltf, bin);\n};\n\n/** The bytes of a photo's image: given, or read from its URL (with the map's request headers) */\nconst imageOf = async ({ url, data }: PhotoOptions): Promise<Uint8Array> => {\n if (data)\n return ArrayBuffer.isView(data)\n ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n : new Uint8Array(data);\n if (!url) throw new Error('isard: a photo needs a url or data');\n\n const response = await fetch(url, withHeaders(url));\n\n if (!response.ok) throw new Error(`isard: ${url}: ${response.status}`);\n\n return new Uint8Array(await response.arrayBuffer());\n};\n\n/** The photo's layer (`addModels`: the map's), its image read */\nexport const addPhoto = async (\n addModels: (options: ModelOptions & { before?: Layer }) => ModelLayer,\n options: PhotoOptions,\n): Promise<ModelLayer> => {\n const data = photoModel(await imageOf(options), options);\n\n return addModels({\n data,\n instances: [{ lonLat: options.lonLat, heading: options.heading ?? 0 }],\n ...(options.before && { before: options.before }),\n });\n};\n"]}
@@ -13,6 +13,10 @@ import { Layer } from './layers.ts';
13
13
  export interface SymbolStyle<P = Properties> extends Omit<FeatureLayerStyle<P>, 'ramp'> {
14
14
  /** Metres over the ground the label stands at (default 0): on a floor of a building, over a roof */
15
15
  height: Styled<number, P>;
16
+ /** The labels hidden by what stands before them on a flat map too: the buildings, trees and models (default false:
17
+ * drawn over them; in relief, the hills hide them either way). A label in a building then needs its `height` on
18
+ * its roof */
19
+ occluded: boolean;
16
20
  /** The text: a string (`\n` breaks lines) or runs with their own scale and font; empty: no label */
17
21
  text: Styled<string | TextPart[], P>;
18
22
  /** A font of the map's glyphs (default `Noto Sans Regular`; also Bold and Italic with the default glyphs) */
@@ -100,7 +104,7 @@ export declare class SymbolLayer<P = Properties> extends Layer {
100
104
  private parts;
101
105
  /** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
102
106
  private update;
103
- /** The labels of a group of features, `base` the index of its first feature */
107
+ /** The labels of a group's features `from` to `to` (excluded), `base` the index of the group's first feature */
104
108
  private build;
105
109
  private send;
106
110
  private sendLayer;