isard 0.7.0 → 0.8.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.
- package/CHANGELOG.md +47 -0
- package/dist/basemap.worker.d.ts +7 -1
- package/dist/basemap.worker.js +2 -2
- package/dist/basemap.worker.js.map +8 -7
- package/dist/decoder.worker.js +4 -4
- package/dist/decoder.worker.js.map +6 -5
- package/dist/engine-loader.d.ts +1 -1
- package/dist/engine-loader.js.map +1 -1
- package/dist/geoplateforme.js +2 -1
- package/dist/geoplateforme.js.map +1 -1
- package/dist/glyphs.js +2 -1
- package/dist/glyphs.js.map +1 -1
- package/dist/icons.js +5 -1
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +2 -0
- package/dist/interaction.js +29 -2
- package/dist/interaction.js.map +1 -1
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
- package/dist/layers.d.ts +2 -0
- package/dist/layers.js +22 -4
- package/dist/layers.js.map +1 -1
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +10 -0
- package/dist/loader.js.map +1 -1
- package/dist/map.d.ts +38 -1
- package/dist/map.js +153 -2
- package/dist/map.js.map +1 -1
- package/dist/overlay.d.ts +40 -0
- package/dist/overlay.js +119 -0
- package/dist/overlay.js.map +1 -1
- package/dist/panoramas.d.ts +94 -0
- package/dist/panoramas.js +245 -0
- package/dist/panoramas.js.map +1 -0
- package/dist/pmtiles.js +3 -2
- package/dist/pmtiles.js.map +1 -1
- package/dist/pointclouds.js +1 -1
- package/dist/pointclouds.js.map +1 -1
- package/dist/protocol.d.ts +7 -1
- package/dist/protocol.js.map +1 -1
- package/dist/requests.d.ts +14 -0
- package/dist/requests.js +48 -0
- package/dist/requests.js.map +1 -0
- package/dist/resample.js +2 -1
- package/dist/resample.js.map +1 -1
- package/dist/style.js +1 -0
- package/dist/style.js.map +1 -1
- package/dist/terrain.d.ts +0 -5
- package/dist/terrain.js +2 -1
- package/dist/terrain.js.map +1 -1
- package/dist/types.d.ts +29 -1
- package/dist/types.js.map +1 -1
- package/dist/vectortiles.js +12 -1
- package/dist/vectortiles.js.map +1 -1
- package/dist/wasm/isard.d.ts +79 -5
- package/dist/wasm/isard.js +165 -7
- package/dist/wasm/isard_core.d.ts +38 -0
- package/dist/wasm/isard_core.js +99 -0
- package/dist/wmts.js +2 -1
- package/dist/wmts.js.map +1 -1
- package/package.json +1 -1
package/dist/overlay.js
CHANGED
|
@@ -390,4 +390,123 @@ export class Popup {
|
|
|
390
390
|
place(this.map, this.container, this.lonLat, this.height, this.anchor, this.offset, size);
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
|
+
/** `a × b`, 4 × 4 column-major */
|
|
394
|
+
function multiply(a, b) {
|
|
395
|
+
const out = new Array(16).fill(0);
|
|
396
|
+
for (let c = 0; c < 4; c++) {
|
|
397
|
+
for (let r = 0; r < 4; r++) {
|
|
398
|
+
let sum = 0;
|
|
399
|
+
for (let k = 0; k < 4; k++)
|
|
400
|
+
sum += (a[k * 4 + r] ?? 0) * (b[c * 4 + k] ?? 0);
|
|
401
|
+
out[c * 4 + r] = sum;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* An element standing in 3D at a position: a video, a web page, a sign, `width` metres wide on the ground, facing
|
|
408
|
+
* `heading`. Placed after each frame by a CSS 3D transform (the element is not drawn by WebGL: the scene does not
|
|
409
|
+
* hide it); hidden when its position is behind the camera
|
|
410
|
+
*/
|
|
411
|
+
export class Panel {
|
|
412
|
+
element;
|
|
413
|
+
lonLat = [0, 0];
|
|
414
|
+
width;
|
|
415
|
+
heading;
|
|
416
|
+
pitch;
|
|
417
|
+
height;
|
|
418
|
+
map;
|
|
419
|
+
/** Its CSS size, read once it is on the page (`resize` reads it again) */
|
|
420
|
+
size;
|
|
421
|
+
constructor(options) {
|
|
422
|
+
this.element = options.element;
|
|
423
|
+
this.width = options.width;
|
|
424
|
+
this.heading = options.heading ?? 0;
|
|
425
|
+
this.pitch = options.pitch ?? 0;
|
|
426
|
+
this.height = options.height ?? 0;
|
|
427
|
+
this.element.classList.add('isard-panel');
|
|
428
|
+
if (options.className)
|
|
429
|
+
this.element.classList.add(...options.className.split(/\s+/).filter(Boolean));
|
|
430
|
+
}
|
|
431
|
+
getElement() {
|
|
432
|
+
return this.element;
|
|
433
|
+
}
|
|
434
|
+
getLonLat() {
|
|
435
|
+
return [...this.lonLat];
|
|
436
|
+
}
|
|
437
|
+
setLonLat(lonLat) {
|
|
438
|
+
this.lonLat = [lonLat[0], lonLat[1]];
|
|
439
|
+
this.map?.positionOverlay(this);
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
/** Width (metres), heading and pitch (degrees), height of its bottom edge (metres), as in `PanelOptions` */
|
|
443
|
+
setPlacement(placement) {
|
|
444
|
+
this.width = placement.width ?? this.width;
|
|
445
|
+
this.heading = placement.heading ?? this.heading;
|
|
446
|
+
this.pitch = placement.pitch ?? this.pitch;
|
|
447
|
+
this.height = placement.height ?? this.height;
|
|
448
|
+
this.map?.positionOverlay(this);
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
/** Reads the element's CSS size again (after it changed) */
|
|
452
|
+
resize() {
|
|
453
|
+
this.size = undefined;
|
|
454
|
+
this.map?.positionOverlay(this);
|
|
455
|
+
return this;
|
|
456
|
+
}
|
|
457
|
+
addTo(map) {
|
|
458
|
+
if (this.map === map)
|
|
459
|
+
return this;
|
|
460
|
+
this.remove();
|
|
461
|
+
this.map = map;
|
|
462
|
+
map.attachOverlay(this, this.element);
|
|
463
|
+
return this;
|
|
464
|
+
}
|
|
465
|
+
remove() {
|
|
466
|
+
this.map?.detachOverlay(this, this.element);
|
|
467
|
+
this.map = undefined;
|
|
468
|
+
this.size = undefined;
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
/** @internal */
|
|
472
|
+
position(viewport) {
|
|
473
|
+
const map = this.map;
|
|
474
|
+
if (!map)
|
|
475
|
+
return;
|
|
476
|
+
this.size ??= [this.element.offsetWidth || 300, this.element.offsetHeight || 150];
|
|
477
|
+
const [w, h] = this.size;
|
|
478
|
+
const ground = map.elevationAt(this.lonLat) ?? 0;
|
|
479
|
+
const local = map.engine.localMatrix(this.lonLat[0], this.lonLat[1], ground);
|
|
480
|
+
// The element's pixels to the local frame (metres, x east, y north, z up): its bottom edge centered on the
|
|
481
|
+
// position, `height` up, facing `heading`, leant back by `pitch`
|
|
482
|
+
const rad = Math.PI / 180;
|
|
483
|
+
const [hd, pt] = [this.heading * rad, this.pitch * rad];
|
|
484
|
+
const s = this.width / w;
|
|
485
|
+
// Across it (left to right as read from the side it faces), and up it
|
|
486
|
+
const [ax, ay] = [-Math.cos(hd), Math.sin(hd)];
|
|
487
|
+
const [ux, uy, uz] = [-Math.sin(hd) * Math.sin(pt), -Math.cos(hd) * Math.sin(pt), Math.cos(pt)];
|
|
488
|
+
const top = h * s;
|
|
489
|
+
const origin = [-ax * (this.width / 2) + ux * top, -ay * (this.width / 2) + uy * top, this.height + uz * top];
|
|
490
|
+
const toLocal = [ax * s, ay * s, 0, 0, -ux * s, -uy * s, -uz * s, 0, 0, 0, 0, 0, ...origin, 1];
|
|
491
|
+
// Clip space to the map's CSS pixels; the depth flattened
|
|
492
|
+
const [vw, vh] = viewport;
|
|
493
|
+
const toScreen = [vw / 2, 0, 0, 0, 0, -vh / 2, 0, 0, 0, 0, 0, 0, vw / 2, vh / 2, 0, 1];
|
|
494
|
+
const clip = multiply(local, toLocal);
|
|
495
|
+
// Behind the camera (its center): hidden
|
|
496
|
+
const centerW = (clip[3] ?? 0) * (w / 2) + (clip[7] ?? 0) * (h / 2) + (clip[15] ?? 0);
|
|
497
|
+
// Hidden without touching its display (the page's layout of its content)
|
|
498
|
+
if (!(centerW > 0)) {
|
|
499
|
+
this.element.style.visibility = 'hidden';
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
this.element.style.visibility = '';
|
|
503
|
+
// Its depth axis the identity, out of the rest: its points have no depth, and a transform that cannot be
|
|
504
|
+
// inverted is not drawn
|
|
505
|
+
const m = multiply(toScreen, clip);
|
|
506
|
+
m.splice(8, 4, 0, 0, 1, 0);
|
|
507
|
+
for (const k of [2, 6, 14])
|
|
508
|
+
m[k] = 0;
|
|
509
|
+
this.element.style.transform = `matrix3d(${m.join(',')})`;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
393
512
|
//# sourceMappingURL=overlay.js.map
|
package/dist/overlay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.js","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DOM overlays tied to a position: markers and popups. The map places them all in one pass after each frame it draws\n * (camera moves, finer relief arriving), with a CSS transform from `map.project`: the browser only composites them,\n * nothing is laid out again.\n */\nimport type { GeoMap } from './map.ts';\nimport type { LonLat, Point } from './types.ts';\n\n/** Which part of the element sits on the position */\nexport type Anchor =\n | 'center'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right';\n\nconst ANCHOR_TRANSLATE: Record<Anchor, string> = {\n center: 'translate(-50%, -50%)',\n top: 'translate(-50%, 0)',\n bottom: 'translate(-50%, -100%)',\n left: 'translate(0, -50%)',\n right: 'translate(-100%, -50%)',\n 'top-left': 'translate(0, 0)',\n 'top-right': 'translate(-100%, 0)',\n 'bottom-left': 'translate(0, -100%)',\n 'bottom-right': 'translate(-100%, -100%)',\n};\n\n/** Where the anchor lies within the element, CSS percentages: the stem of a raised marker starts there */\nconst ANCHOR_POINT: Record<Anchor, [string, string]> = {\n center: ['50%', '50%'],\n top: ['50%', '0'],\n bottom: ['50%', '100%'],\n left: ['0', '50%'],\n right: ['100%', '50%'],\n 'top-left': ['0', '0'],\n 'top-right': ['100%', '0'],\n 'bottom-left': ['0', '100%'],\n 'bottom-right': ['100%', '100%'],\n};\n\n/** Overlays further than this out of the canvas (CSS pixels) are hidden: no part of them can show, and huge\n * transforms near the horizon would only cost the compositor */\nconst MARGIN = 500;\n/** A press on a draggable marker that moves less than this (CSS pixels) is a click */\nconst DRAG_TOLERANCE = 3;\n\n/** @internal What the map places after each frame */\nexport interface Positioned {\n /** Places the overlay for the current camera; `size` is the canvas size in CSS pixels */\n position(size: Point): void;\n}\n\n/** Typed listeners of an overlay */\nclass Emitter<E> {\n private readonly listeners: { [K in keyof E]?: Set<(event: E[K]) => void> } = {};\n\n on<K extends keyof E>(type: K, listener: (event: E[K]) => void): () => void {\n const set = this.listeners[type] ?? new Set();\n this.listeners[type] = set;\n set.add(listener);\n return () => set.delete(listener);\n }\n\n emit<K extends keyof E>(type: K, event: E[K]): void {\n for (const listener of this.listeners[type] ?? []) listener(event);\n }\n}\n\n/** Moves `element` so that its anchor sits on `lonLat`, `height` metres above the ground, or hides it when the\n * position is out of sight; returns the screen point */\nfunction place(\n map: GeoMap,\n element: HTMLElement,\n lonLat: LonLat,\n height: number,\n anchor: Anchor,\n offset: Point,\n size: Point,\n): Point | undefined {\n const point = map.project(lonLat, height);\n const visible =\n point !== undefined &&\n point[0] > -MARGIN &&\n point[0] < size[0] + MARGIN &&\n point[1] > -MARGIN &&\n point[1] < size[1] + MARGIN;\n if (!visible) {\n element.style.display = 'none';\n return undefined;\n }\n element.style.display = '';\n // Whole pixels: a fractional translation blurs text and icons\n const x = Math.round(point[0] + offset[0]);\n const y = Math.round(point[1] + offset[1]);\n element.style.transform = `translate(${x}px, ${y}px) ${ANCHOR_TRANSLATE[anchor]}`;\n return point;\n}\n\n/** Round pin, its tip at the bottom: the default marker */\nfunction pin(): HTMLElement {\n const element = document.createElement('div');\n element.innerHTML =\n '<svg class=\"isard-marker-pin\" width=\"25\" height=\"34\" viewBox=\"0 0 25 34\" aria-hidden=\"true\">' +\n '<path fill=\"currentColor\" stroke=\"rgb(0 0 0 / 0.25)\" d=\"M12.5 33.5C12.5 33.5 .5 20.6 .5 12.5a12 12 0 0 1 24 0' +\n 'c0 8.1-12 21-12 21z\"/><circle cx=\"12.5\" cy=\"12.5\" r=\"4.5\" fill=\"#fff\"/></svg>';\n return element;\n}\n\nexport interface MarkerOptions {\n /** The element to show (default: a round pin) */\n element?: HTMLElement;\n /** Which part of the element sits on the position (default 'center' with an element of your own, 'bottom' for the\n * default pin) */\n anchor?: Anchor;\n /** Shift from the anchor, CSS pixels (default [0, 0]) */\n offset?: Point;\n /** The marker can be dragged over the map, on the relief (default false) */\n draggable?: boolean;\n /** Metres above the ground (default 0): a raised marker has a stem down to its position on the ground */\n height?: number;\n /** Classes added to the element, space separated */\n className?: string;\n}\n\nexport interface MarkerEvent {\n target: Marker;\n lonLat: LonLat;\n}\n\nexport interface MarkerEvents {\n dragstart: MarkerEvent;\n drag: MarkerEvent;\n dragend: MarkerEvent;\n}\n\n/** An element at a position on the map, optionally draggable */\nexport class Marker {\n private readonly element: HTMLElement;\n private readonly anchor: Anchor;\n private readonly events = new Emitter<MarkerEvents>();\n private offset: Point;\n private draggable: boolean;\n private lonLat: LonLat = [0, 0];\n private height: number;\n /** Metres above the ground of what the marker stands on, where its stem ends */\n private base = 0;\n /** The line from a raised marker down to the ground */\n private readonly stem: HTMLElement;\n private map: GeoMap | undefined;\n private popup: Popup | undefined;\n /** While pressed: the press point and the gap between the pointer and the marker's position */\n private grab: { start: Point; gap: Point } | undefined;\n private dragging = false;\n /** The click that ends a drag is not a click on the marker */\n private swallowClick = false;\n\n constructor(options: MarkerOptions = {}) {\n this.element = options.element ?? pin();\n this.anchor = options.anchor ?? (options.element ? 'center' : 'bottom');\n this.offset = options.offset ?? [0, 0];\n this.draggable = options.draggable ?? false;\n this.height = options.height ?? 0;\n this.stem = document.createElement('div');\n this.stem.className = 'isard-marker-stem';\n [this.stem.style.left, this.stem.style.top] = ANCHOR_POINT[this.anchor];\n this.element.append(this.stem);\n this.element.classList.add('isard-marker');\n if (options.className) this.element.classList.add(...options.className.split(/\\s+/).filter(Boolean));\n this.element.classList.toggle('isard-marker-draggable', this.draggable);\n this.element.addEventListener('pointerdown', (e) => this.down(e));\n this.element.addEventListener('pointermove', (e) => this.move(e));\n this.element.addEventListener('pointerup', (e) => this.up(e));\n this.element.addEventListener('pointercancel', (e) => this.up(e));\n this.element.addEventListener('click', (e) => this.click(e), { capture: true });\n }\n\n getElement(): HTMLElement {\n return this.element;\n }\n\n getLonLat(): LonLat {\n return [...this.lonLat];\n }\n\n setLonLat(lonLat: LonLat): this {\n this.lonLat = [lonLat[0], lonLat[1]];\n this.map?.positionOverlay(this);\n // An open popup of the marker goes with it\n if (this.popup?.isOpen()) this.popup.setLonLat(this.lonLat);\n return this;\n }\n\n getHeight(): number {\n return this.height;\n }\n\n /** Metres above the ground: a raised marker has a stem down to `base` metres, what it stands on (a roof, a mesh;\n * the ground by default); its popup is raised too */\n setHeight(height: number, base = 0): this {\n this.height = Number.isFinite(height) ? height : 0;\n this.base = Number.isFinite(base) ? Math.min(base, this.height) : 0;\n this.popup?.setHeight(this.height);\n this.map?.positionOverlay(this);\n return this;\n }\n\n setOffset(offset: Point): this {\n this.offset = [offset[0], offset[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n setDraggable(draggable: boolean): this {\n this.draggable = draggable;\n this.element.classList.toggle('isard-marker-draggable', draggable);\n return this;\n }\n\n isDraggable(): boolean {\n return this.draggable;\n }\n\n /** A popup that a click on the marker opens and closes, at the marker's position; give it an offset clearing the\n * marker (with the default pin, [0, -34]) */\n setPopup(popup: Popup | undefined): this {\n this.popup?.remove();\n this.popup = popup;\n popup?.setHeight(this.height);\n return this;\n }\n\n getPopup(): Popup | undefined {\n return this.popup;\n }\n\n /** Opens the marker's popup, or closes it if open */\n togglePopup(): this {\n if (!this.popup || !this.map) return this;\n if (this.popup.isOpen()) this.popup.remove();\n else this.popup.setLonLat(this.lonLat).addTo(this.map);\n return this;\n }\n\n /** Listens to the drag of the marker; returns the function that stops listening */\n on<K extends keyof MarkerEvents>(type: K, listener: (event: MarkerEvents[K]) => void): () => void {\n return this.events.on(type, listener);\n }\n\n addTo(map: GeoMap): this {\n if (this.map === map) return this;\n this.remove();\n this.map = map;\n map.attachOverlay(this, this.element);\n return this;\n }\n\n remove(): this {\n this.popup?.remove();\n this.map?.detachOverlay(this, this.element);\n this.map = undefined;\n this.grab = undefined;\n this.dragging = false;\n this.element.classList.remove('isard-marker-dragging');\n return this;\n }\n\n /** @internal */\n position(size: Point): void {\n if (!this.map) return;\n const top = place(this.map, this.element, this.lonLat, this.height, this.anchor, this.offset, size);\n const ground = top && this.height !== this.base ? this.map.project(this.lonLat, this.base) : undefined;\n if (!top || !ground) {\n this.stem.style.display = 'none';\n return;\n }\n // From the anchor down to what it stands on, a line turned toward it\n const [dx, dy] = [ground[0] - top[0], ground[1] - top[1]];\n this.stem.style.display = '';\n this.stem.style.height = `${Math.hypot(dx, dy)}px`;\n this.stem.style.transform = `rotate(${Math.atan2(dy, dx) - Math.PI / 2}rad)`;\n }\n\n private local(e: PointerEvent): Point | undefined {\n const rect = this.map?.getCanvas().getBoundingClientRect();\n return rect && [e.clientX - rect.left, e.clientY - rect.top];\n }\n\n private down(e: PointerEvent): void {\n this.swallowClick = false;\n if (!this.draggable || !this.map || e.button !== 0) return;\n // The gap to the position on the ground, below a raised marker\n const point = this.map.project(this.lonLat);\n const at = this.local(e);\n if (!point || !at) return;\n // The map must not start panning under the marker\n e.stopPropagation();\n e.preventDefault();\n this.element.setPointerCapture(e.pointerId);\n // The grabbed spot of the marker stays under the pointer, wherever the element was pressed\n this.grab = { start: at, gap: [at[0] - point[0], at[1] - point[1]] };\n this.dragging = false;\n }\n\n private move(e: PointerEvent): void {\n const at = this.local(e);\n if (!this.grab || !this.map || !at) return;\n e.stopPropagation();\n const { start, gap } = this.grab;\n if (!this.dragging && Math.hypot(at[0] - start[0], at[1] - start[1]) < DRAG_TOLERANCE) return;\n // On the relief: the ground under the point, read back from the picking pass\n const lonLat = this.map.unproject([at[0] - gap[0], at[1] - gap[1]]);\n if (!lonLat) return;\n if (!this.dragging) {\n this.dragging = true;\n this.element.classList.add('isard-marker-dragging');\n this.events.emit('dragstart', { target: this, lonLat: this.getLonLat() });\n }\n this.setLonLat(lonLat);\n this.events.emit('drag', { target: this, lonLat: this.getLonLat() });\n }\n\n private up(e: PointerEvent): void {\n if (!this.grab) return;\n e.stopPropagation();\n this.grab = undefined;\n if (this.element.hasPointerCapture(e.pointerId)) this.element.releasePointerCapture(e.pointerId);\n if (!this.dragging) return;\n this.dragging = false;\n this.swallowClick = true;\n this.element.classList.remove('isard-marker-dragging');\n this.events.emit('dragend', { target: this, lonLat: this.getLonLat() });\n }\n\n private click(e: MouseEvent): void {\n if (this.swallowClick) {\n this.swallowClick = false;\n e.stopImmediatePropagation();\n e.preventDefault();\n return;\n }\n if (this.popup) this.togglePopup();\n }\n}\n\nexport interface PopupOptions {\n /** A × button in the corner (default true) */\n closeButton?: boolean;\n /** A click on the map elsewhere closes the popup (default true) */\n closeOnClick?: boolean;\n /** CSS max-width of the popup (default '240px') */\n maxWidth?: string;\n /** Classes added to the popup, space separated */\n className?: string;\n /** Which part of the popup sits on the position, its tip there (default 'bottom': the popup is above the point) */\n anchor?: Anchor;\n /** Shift from the anchor, CSS pixels (default [0, 0]) */\n offset?: Point;\n}\n\nexport interface PopupEvents {\n open: { target: Popup };\n close: { target: Popup };\n}\n\n/** A box of content at a position, its tip pointing at it */\nexport class Popup {\n private readonly container: HTMLElement;\n private readonly content: HTMLElement;\n private readonly closeButton: HTMLButtonElement | undefined;\n private readonly anchor: Anchor;\n private readonly closeOnClick: boolean;\n private readonly events = new Emitter<PopupEvents>();\n private offset: Point;\n private lonLat: LonLat = [0, 0];\n private height = 0;\n private map: GeoMap | undefined;\n private stopListening: (() => void) | undefined;\n\n constructor(options: PopupOptions = {}) {\n this.anchor = options.anchor ?? 'bottom';\n this.offset = options.offset ?? [0, 0];\n this.closeOnClick = options.closeOnClick ?? true;\n this.container = document.createElement('div');\n this.container.className = `isard-popup isard-popup-anchor-${this.anchor}`;\n if (options.className) this.container.classList.add(...options.className.split(/\\s+/).filter(Boolean));\n this.container.style.maxWidth = options.maxWidth ?? '240px';\n const tip = document.createElement('div');\n tip.className = 'isard-popup-tip';\n this.content = document.createElement('div');\n this.content.className = 'isard-popup-content';\n if (options.closeButton ?? true) {\n this.container.classList.add('isard-popup-has-close');\n this.closeButton = document.createElement('button');\n this.closeButton.type = 'button';\n this.closeButton.className = 'isard-popup-close';\n this.closeButton.setAttribute('aria-label', 'Close popup');\n this.closeButton.textContent = '×';\n this.closeButton.addEventListener('click', () => this.remove());\n this.content.append(this.closeButton);\n }\n this.container.append(tip, this.content);\n }\n\n getElement(): HTMLElement {\n return this.container;\n }\n\n getLonLat(): LonLat {\n return [...this.lonLat];\n }\n\n setLonLat(lonLat: LonLat): this {\n this.lonLat = [lonLat[0], lonLat[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n setOffset(offset: Point): this {\n this.offset = [offset[0], offset[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n getHeight(): number {\n return this.height;\n }\n\n /** Metres above the ground (default 0); a marker's popup follows the marker's height */\n setHeight(height: number): this {\n this.height = Number.isFinite(height) ? height : 0;\n this.map?.positionOverlay(this);\n return this;\n }\n\n /** Content from an HTML string: only for trusted HTML (use `setText` for user or remote data) */\n setHTML(html: string): this {\n const template = document.createElement('template');\n template.innerHTML = html;\n return this.setDOMContent(template.content);\n }\n\n setText(text: string): this {\n return this.setDOMContent(document.createTextNode(text));\n }\n\n setDOMContent(node: Node): this {\n this.content.replaceChildren(node);\n if (this.closeButton) this.content.append(this.closeButton);\n this.map?.positionOverlay(this);\n return this;\n }\n\n isOpen(): boolean {\n return this.map !== undefined;\n }\n\n /** Listens to the popup opening or closing; returns the function that stops listening */\n on<K extends keyof PopupEvents>(type: K, listener: (event: PopupEvents[K]) => void): () => void {\n return this.events.on(type, listener);\n }\n\n addTo(map: GeoMap): this {\n if (this.map === map) return this;\n this.remove();\n this.map = map;\n map.attachOverlay(this, this.container);\n if (this.closeOnClick) {\n // From the next task on: a popup opened by a map click must not be closed by that same click\n queueMicrotask(() => {\n if (this.map === map && !this.stopListening) this.stopListening = map.on('click', () => this.remove());\n });\n }\n this.events.emit('open', { target: this });\n return this;\n }\n\n remove(): this {\n if (!this.map) return this;\n this.stopListening?.();\n this.stopListening = undefined;\n this.map.detachOverlay(this, this.container);\n this.map = undefined;\n this.events.emit('close', { target: this });\n return this;\n }\n\n /** @internal */\n position(size: Point): void {\n if (this.map) place(this.map, this.container, this.lonLat, this.height, this.anchor, this.offset, size);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"overlay.js","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DOM overlays tied to a position: markers and popups. The map places them all in one pass after each frame it draws\n * (camera moves, finer relief arriving), with a CSS transform from `map.project`: the browser only composites them,\n * nothing is laid out again.\n */\nimport type { GeoMap } from './map.ts';\nimport type { LonLat, Point } from './types.ts';\n\n/** Which part of the element sits on the position */\nexport type Anchor =\n | 'center'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right';\n\nconst ANCHOR_TRANSLATE: Record<Anchor, string> = {\n center: 'translate(-50%, -50%)',\n top: 'translate(-50%, 0)',\n bottom: 'translate(-50%, -100%)',\n left: 'translate(0, -50%)',\n right: 'translate(-100%, -50%)',\n 'top-left': 'translate(0, 0)',\n 'top-right': 'translate(-100%, 0)',\n 'bottom-left': 'translate(0, -100%)',\n 'bottom-right': 'translate(-100%, -100%)',\n};\n\n/** Where the anchor lies within the element, CSS percentages: the stem of a raised marker starts there */\nconst ANCHOR_POINT: Record<Anchor, [string, string]> = {\n center: ['50%', '50%'],\n top: ['50%', '0'],\n bottom: ['50%', '100%'],\n left: ['0', '50%'],\n right: ['100%', '50%'],\n 'top-left': ['0', '0'],\n 'top-right': ['100%', '0'],\n 'bottom-left': ['0', '100%'],\n 'bottom-right': ['100%', '100%'],\n};\n\n/** Overlays further than this out of the canvas (CSS pixels) are hidden: no part of them can show, and huge\n * transforms near the horizon would only cost the compositor */\nconst MARGIN = 500;\n/** A press on a draggable marker that moves less than this (CSS pixels) is a click */\nconst DRAG_TOLERANCE = 3;\n\n/** @internal What the map places after each frame */\nexport interface Positioned {\n /** Places the overlay for the current camera; `size` is the canvas size in CSS pixels */\n position(size: Point): void;\n}\n\n/** Typed listeners of an overlay */\nclass Emitter<E> {\n private readonly listeners: { [K in keyof E]?: Set<(event: E[K]) => void> } = {};\n\n on<K extends keyof E>(type: K, listener: (event: E[K]) => void): () => void {\n const set = this.listeners[type] ?? new Set();\n this.listeners[type] = set;\n set.add(listener);\n return () => set.delete(listener);\n }\n\n emit<K extends keyof E>(type: K, event: E[K]): void {\n for (const listener of this.listeners[type] ?? []) listener(event);\n }\n}\n\n/** Moves `element` so that its anchor sits on `lonLat`, `height` metres above the ground, or hides it when the\n * position is out of sight; returns the screen point */\nfunction place(\n map: GeoMap,\n element: HTMLElement,\n lonLat: LonLat,\n height: number,\n anchor: Anchor,\n offset: Point,\n size: Point,\n): Point | undefined {\n const point = map.project(lonLat, height);\n const visible =\n point !== undefined &&\n point[0] > -MARGIN &&\n point[0] < size[0] + MARGIN &&\n point[1] > -MARGIN &&\n point[1] < size[1] + MARGIN;\n if (!visible) {\n element.style.display = 'none';\n return undefined;\n }\n element.style.display = '';\n // Whole pixels: a fractional translation blurs text and icons\n const x = Math.round(point[0] + offset[0]);\n const y = Math.round(point[1] + offset[1]);\n element.style.transform = `translate(${x}px, ${y}px) ${ANCHOR_TRANSLATE[anchor]}`;\n return point;\n}\n\n/** Round pin, its tip at the bottom: the default marker */\nfunction pin(): HTMLElement {\n const element = document.createElement('div');\n element.innerHTML =\n '<svg class=\"isard-marker-pin\" width=\"25\" height=\"34\" viewBox=\"0 0 25 34\" aria-hidden=\"true\">' +\n '<path fill=\"currentColor\" stroke=\"rgb(0 0 0 / 0.25)\" d=\"M12.5 33.5C12.5 33.5 .5 20.6 .5 12.5a12 12 0 0 1 24 0' +\n 'c0 8.1-12 21-12 21z\"/><circle cx=\"12.5\" cy=\"12.5\" r=\"4.5\" fill=\"#fff\"/></svg>';\n return element;\n}\n\nexport interface MarkerOptions {\n /** The element to show (default: a round pin) */\n element?: HTMLElement;\n /** Which part of the element sits on the position (default 'center' with an element of your own, 'bottom' for the\n * default pin) */\n anchor?: Anchor;\n /** Shift from the anchor, CSS pixels (default [0, 0]) */\n offset?: Point;\n /** The marker can be dragged over the map, on the relief (default false) */\n draggable?: boolean;\n /** Metres above the ground (default 0): a raised marker has a stem down to its position on the ground */\n height?: number;\n /** Classes added to the element, space separated */\n className?: string;\n}\n\nexport interface MarkerEvent {\n target: Marker;\n lonLat: LonLat;\n}\n\nexport interface MarkerEvents {\n dragstart: MarkerEvent;\n drag: MarkerEvent;\n dragend: MarkerEvent;\n}\n\n/** An element at a position on the map, optionally draggable */\nexport class Marker {\n private readonly element: HTMLElement;\n private readonly anchor: Anchor;\n private readonly events = new Emitter<MarkerEvents>();\n private offset: Point;\n private draggable: boolean;\n private lonLat: LonLat = [0, 0];\n private height: number;\n /** Metres above the ground of what the marker stands on, where its stem ends */\n private base = 0;\n /** The line from a raised marker down to the ground */\n private readonly stem: HTMLElement;\n private map: GeoMap | undefined;\n private popup: Popup | undefined;\n /** While pressed: the press point and the gap between the pointer and the marker's position */\n private grab: { start: Point; gap: Point } | undefined;\n private dragging = false;\n /** The click that ends a drag is not a click on the marker */\n private swallowClick = false;\n\n constructor(options: MarkerOptions = {}) {\n this.element = options.element ?? pin();\n this.anchor = options.anchor ?? (options.element ? 'center' : 'bottom');\n this.offset = options.offset ?? [0, 0];\n this.draggable = options.draggable ?? false;\n this.height = options.height ?? 0;\n this.stem = document.createElement('div');\n this.stem.className = 'isard-marker-stem';\n [this.stem.style.left, this.stem.style.top] = ANCHOR_POINT[this.anchor];\n this.element.append(this.stem);\n this.element.classList.add('isard-marker');\n if (options.className) this.element.classList.add(...options.className.split(/\\s+/).filter(Boolean));\n this.element.classList.toggle('isard-marker-draggable', this.draggable);\n this.element.addEventListener('pointerdown', (e) => this.down(e));\n this.element.addEventListener('pointermove', (e) => this.move(e));\n this.element.addEventListener('pointerup', (e) => this.up(e));\n this.element.addEventListener('pointercancel', (e) => this.up(e));\n this.element.addEventListener('click', (e) => this.click(e), { capture: true });\n }\n\n getElement(): HTMLElement {\n return this.element;\n }\n\n getLonLat(): LonLat {\n return [...this.lonLat];\n }\n\n setLonLat(lonLat: LonLat): this {\n this.lonLat = [lonLat[0], lonLat[1]];\n this.map?.positionOverlay(this);\n // An open popup of the marker goes with it\n if (this.popup?.isOpen()) this.popup.setLonLat(this.lonLat);\n return this;\n }\n\n getHeight(): number {\n return this.height;\n }\n\n /** Metres above the ground: a raised marker has a stem down to `base` metres, what it stands on (a roof, a mesh;\n * the ground by default); its popup is raised too */\n setHeight(height: number, base = 0): this {\n this.height = Number.isFinite(height) ? height : 0;\n this.base = Number.isFinite(base) ? Math.min(base, this.height) : 0;\n this.popup?.setHeight(this.height);\n this.map?.positionOverlay(this);\n return this;\n }\n\n setOffset(offset: Point): this {\n this.offset = [offset[0], offset[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n setDraggable(draggable: boolean): this {\n this.draggable = draggable;\n this.element.classList.toggle('isard-marker-draggable', draggable);\n return this;\n }\n\n isDraggable(): boolean {\n return this.draggable;\n }\n\n /** A popup that a click on the marker opens and closes, at the marker's position; give it an offset clearing the\n * marker (with the default pin, [0, -34]) */\n setPopup(popup: Popup | undefined): this {\n this.popup?.remove();\n this.popup = popup;\n popup?.setHeight(this.height);\n return this;\n }\n\n getPopup(): Popup | undefined {\n return this.popup;\n }\n\n /** Opens the marker's popup, or closes it if open */\n togglePopup(): this {\n if (!this.popup || !this.map) return this;\n if (this.popup.isOpen()) this.popup.remove();\n else this.popup.setLonLat(this.lonLat).addTo(this.map);\n return this;\n }\n\n /** Listens to the drag of the marker; returns the function that stops listening */\n on<K extends keyof MarkerEvents>(type: K, listener: (event: MarkerEvents[K]) => void): () => void {\n return this.events.on(type, listener);\n }\n\n addTo(map: GeoMap): this {\n if (this.map === map) return this;\n this.remove();\n this.map = map;\n map.attachOverlay(this, this.element);\n return this;\n }\n\n remove(): this {\n this.popup?.remove();\n this.map?.detachOverlay(this, this.element);\n this.map = undefined;\n this.grab = undefined;\n this.dragging = false;\n this.element.classList.remove('isard-marker-dragging');\n return this;\n }\n\n /** @internal */\n position(size: Point): void {\n if (!this.map) return;\n const top = place(this.map, this.element, this.lonLat, this.height, this.anchor, this.offset, size);\n const ground = top && this.height !== this.base ? this.map.project(this.lonLat, this.base) : undefined;\n if (!top || !ground) {\n this.stem.style.display = 'none';\n return;\n }\n // From the anchor down to what it stands on, a line turned toward it\n const [dx, dy] = [ground[0] - top[0], ground[1] - top[1]];\n this.stem.style.display = '';\n this.stem.style.height = `${Math.hypot(dx, dy)}px`;\n this.stem.style.transform = `rotate(${Math.atan2(dy, dx) - Math.PI / 2}rad)`;\n }\n\n private local(e: PointerEvent): Point | undefined {\n const rect = this.map?.getCanvas().getBoundingClientRect();\n return rect && [e.clientX - rect.left, e.clientY - rect.top];\n }\n\n private down(e: PointerEvent): void {\n this.swallowClick = false;\n if (!this.draggable || !this.map || e.button !== 0) return;\n // The gap to the position on the ground, below a raised marker\n const point = this.map.project(this.lonLat);\n const at = this.local(e);\n if (!point || !at) return;\n // The map must not start panning under the marker\n e.stopPropagation();\n e.preventDefault();\n this.element.setPointerCapture(e.pointerId);\n // The grabbed spot of the marker stays under the pointer, wherever the element was pressed\n this.grab = { start: at, gap: [at[0] - point[0], at[1] - point[1]] };\n this.dragging = false;\n }\n\n private move(e: PointerEvent): void {\n const at = this.local(e);\n if (!this.grab || !this.map || !at) return;\n e.stopPropagation();\n const { start, gap } = this.grab;\n if (!this.dragging && Math.hypot(at[0] - start[0], at[1] - start[1]) < DRAG_TOLERANCE) return;\n // On the relief: the ground under the point, read back from the picking pass\n const lonLat = this.map.unproject([at[0] - gap[0], at[1] - gap[1]]);\n if (!lonLat) return;\n if (!this.dragging) {\n this.dragging = true;\n this.element.classList.add('isard-marker-dragging');\n this.events.emit('dragstart', { target: this, lonLat: this.getLonLat() });\n }\n this.setLonLat(lonLat);\n this.events.emit('drag', { target: this, lonLat: this.getLonLat() });\n }\n\n private up(e: PointerEvent): void {\n if (!this.grab) return;\n e.stopPropagation();\n this.grab = undefined;\n if (this.element.hasPointerCapture(e.pointerId)) this.element.releasePointerCapture(e.pointerId);\n if (!this.dragging) return;\n this.dragging = false;\n this.swallowClick = true;\n this.element.classList.remove('isard-marker-dragging');\n this.events.emit('dragend', { target: this, lonLat: this.getLonLat() });\n }\n\n private click(e: MouseEvent): void {\n if (this.swallowClick) {\n this.swallowClick = false;\n e.stopImmediatePropagation();\n e.preventDefault();\n return;\n }\n if (this.popup) this.togglePopup();\n }\n}\n\nexport interface PopupOptions {\n /** A × button in the corner (default true) */\n closeButton?: boolean;\n /** A click on the map elsewhere closes the popup (default true) */\n closeOnClick?: boolean;\n /** CSS max-width of the popup (default '240px') */\n maxWidth?: string;\n /** Classes added to the popup, space separated */\n className?: string;\n /** Which part of the popup sits on the position, its tip there (default 'bottom': the popup is above the point) */\n anchor?: Anchor;\n /** Shift from the anchor, CSS pixels (default [0, 0]) */\n offset?: Point;\n}\n\nexport interface PopupEvents {\n open: { target: Popup };\n close: { target: Popup };\n}\n\n/** A box of content at a position, its tip pointing at it */\nexport class Popup {\n private readonly container: HTMLElement;\n private readonly content: HTMLElement;\n private readonly closeButton: HTMLButtonElement | undefined;\n private readonly anchor: Anchor;\n private readonly closeOnClick: boolean;\n private readonly events = new Emitter<PopupEvents>();\n private offset: Point;\n private lonLat: LonLat = [0, 0];\n private height = 0;\n private map: GeoMap | undefined;\n private stopListening: (() => void) | undefined;\n\n constructor(options: PopupOptions = {}) {\n this.anchor = options.anchor ?? 'bottom';\n this.offset = options.offset ?? [0, 0];\n this.closeOnClick = options.closeOnClick ?? true;\n this.container = document.createElement('div');\n this.container.className = `isard-popup isard-popup-anchor-${this.anchor}`;\n if (options.className) this.container.classList.add(...options.className.split(/\\s+/).filter(Boolean));\n this.container.style.maxWidth = options.maxWidth ?? '240px';\n const tip = document.createElement('div');\n tip.className = 'isard-popup-tip';\n this.content = document.createElement('div');\n this.content.className = 'isard-popup-content';\n if (options.closeButton ?? true) {\n this.container.classList.add('isard-popup-has-close');\n this.closeButton = document.createElement('button');\n this.closeButton.type = 'button';\n this.closeButton.className = 'isard-popup-close';\n this.closeButton.setAttribute('aria-label', 'Close popup');\n this.closeButton.textContent = '×';\n this.closeButton.addEventListener('click', () => this.remove());\n this.content.append(this.closeButton);\n }\n this.container.append(tip, this.content);\n }\n\n getElement(): HTMLElement {\n return this.container;\n }\n\n getLonLat(): LonLat {\n return [...this.lonLat];\n }\n\n setLonLat(lonLat: LonLat): this {\n this.lonLat = [lonLat[0], lonLat[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n setOffset(offset: Point): this {\n this.offset = [offset[0], offset[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n getHeight(): number {\n return this.height;\n }\n\n /** Metres above the ground (default 0); a marker's popup follows the marker's height */\n setHeight(height: number): this {\n this.height = Number.isFinite(height) ? height : 0;\n this.map?.positionOverlay(this);\n return this;\n }\n\n /** Content from an HTML string: only for trusted HTML (use `setText` for user or remote data) */\n setHTML(html: string): this {\n const template = document.createElement('template');\n template.innerHTML = html;\n return this.setDOMContent(template.content);\n }\n\n setText(text: string): this {\n return this.setDOMContent(document.createTextNode(text));\n }\n\n setDOMContent(node: Node): this {\n this.content.replaceChildren(node);\n if (this.closeButton) this.content.append(this.closeButton);\n this.map?.positionOverlay(this);\n return this;\n }\n\n isOpen(): boolean {\n return this.map !== undefined;\n }\n\n /** Listens to the popup opening or closing; returns the function that stops listening */\n on<K extends keyof PopupEvents>(type: K, listener: (event: PopupEvents[K]) => void): () => void {\n return this.events.on(type, listener);\n }\n\n addTo(map: GeoMap): this {\n if (this.map === map) return this;\n this.remove();\n this.map = map;\n map.attachOverlay(this, this.container);\n if (this.closeOnClick) {\n // From the next task on: a popup opened by a map click must not be closed by that same click\n queueMicrotask(() => {\n if (this.map === map && !this.stopListening) this.stopListening = map.on('click', () => this.remove());\n });\n }\n this.events.emit('open', { target: this });\n return this;\n }\n\n remove(): this {\n if (!this.map) return this;\n this.stopListening?.();\n this.stopListening = undefined;\n this.map.detachOverlay(this, this.container);\n this.map = undefined;\n this.events.emit('close', { target: this });\n return this;\n }\n\n /** @internal */\n position(size: Point): void {\n if (this.map) place(this.map, this.container, this.lonLat, this.height, this.anchor, this.offset, size);\n }\n}\n\nexport interface PanelOptions {\n /** The element shown in 3D (a video, an iframe, an image…), laid out at its CSS size */\n element: HTMLElement;\n /** Its width on the ground, metres; its height follows its CSS aspect */\n width: number;\n /** Degrees clockwise from north the side it faces (default 0: it faces north, read from the north) */\n heading?: number;\n /** Degrees it leans back from upright (default 0: standing; 90: lying flat) */\n pitch?: number;\n /** Metres of its bottom edge above the ground (the relief; default 0) */\n height?: number;\n /** Classes added to the element, space separated */\n className?: string;\n}\n\n/** `a × b`, 4 × 4 column-major */\nfunction multiply(a: ArrayLike<number>, b: ArrayLike<number>): number[] {\n const out = new Array<number>(16).fill(0);\n for (let c = 0; c < 4; c++) {\n for (let r = 0; r < 4; r++) {\n let sum = 0;\n for (let k = 0; k < 4; k++) sum += (a[k * 4 + r] ?? 0) * (b[c * 4 + k] ?? 0);\n out[c * 4 + r] = sum;\n }\n }\n return out;\n}\n\n/**\n * An element standing in 3D at a position: a video, a web page, a sign, `width` metres wide on the ground, facing\n * `heading`. Placed after each frame by a CSS 3D transform (the element is not drawn by WebGL: the scene does not\n * hide it); hidden when its position is behind the camera\n */\nexport class Panel {\n private readonly element: HTMLElement;\n private lonLat: LonLat = [0, 0];\n private width: number;\n private heading: number;\n private pitch: number;\n private height: number;\n private map: GeoMap | undefined;\n /** Its CSS size, read once it is on the page (`resize` reads it again) */\n private size: Point | undefined;\n\n constructor(options: PanelOptions) {\n this.element = options.element;\n this.width = options.width;\n this.heading = options.heading ?? 0;\n this.pitch = options.pitch ?? 0;\n this.height = options.height ?? 0;\n this.element.classList.add('isard-panel');\n if (options.className) this.element.classList.add(...options.className.split(/\\s+/).filter(Boolean));\n }\n\n getElement(): HTMLElement {\n return this.element;\n }\n\n getLonLat(): LonLat {\n return [...this.lonLat];\n }\n\n setLonLat(lonLat: LonLat): this {\n this.lonLat = [lonLat[0], lonLat[1]];\n this.map?.positionOverlay(this);\n return this;\n }\n\n /** Width (metres), heading and pitch (degrees), height of its bottom edge (metres), as in `PanelOptions` */\n setPlacement(placement: Partial<Pick<PanelOptions, 'width' | 'heading' | 'pitch' | 'height'>>): this {\n this.width = placement.width ?? this.width;\n this.heading = placement.heading ?? this.heading;\n this.pitch = placement.pitch ?? this.pitch;\n this.height = placement.height ?? this.height;\n this.map?.positionOverlay(this);\n return this;\n }\n\n /** Reads the element's CSS size again (after it changed) */\n resize(): this {\n this.size = undefined;\n this.map?.positionOverlay(this);\n return this;\n }\n\n addTo(map: GeoMap): this {\n if (this.map === map) return this;\n this.remove();\n this.map = map;\n map.attachOverlay(this, this.element);\n return this;\n }\n\n remove(): this {\n this.map?.detachOverlay(this, this.element);\n this.map = undefined;\n this.size = undefined;\n return this;\n }\n\n /** @internal */\n position(viewport: Point): void {\n const map = this.map;\n if (!map) return;\n this.size ??= [this.element.offsetWidth || 300, this.element.offsetHeight || 150];\n const [w, h] = this.size;\n const ground = map.elevationAt(this.lonLat) ?? 0;\n const local = map.engine.localMatrix(this.lonLat[0], this.lonLat[1], ground);\n // The element's pixels to the local frame (metres, x east, y north, z up): its bottom edge centered on the\n // position, `height` up, facing `heading`, leant back by `pitch`\n const rad = Math.PI / 180;\n const [hd, pt] = [this.heading * rad, this.pitch * rad];\n const s = this.width / w;\n // Across it (left to right as read from the side it faces), and up it\n const [ax, ay] = [-Math.cos(hd), Math.sin(hd)];\n const [ux, uy, uz] = [-Math.sin(hd) * Math.sin(pt), -Math.cos(hd) * Math.sin(pt), Math.cos(pt)];\n const top = h * s;\n const origin = [-ax * (this.width / 2) + ux * top, -ay * (this.width / 2) + uy * top, this.height + uz * top];\n const toLocal = [ax * s, ay * s, 0, 0, -ux * s, -uy * s, -uz * s, 0, 0, 0, 0, 0, ...origin, 1];\n // Clip space to the map's CSS pixels; the depth flattened\n const [vw, vh] = viewport;\n const toScreen = [vw / 2, 0, 0, 0, 0, -vh / 2, 0, 0, 0, 0, 0, 0, vw / 2, vh / 2, 0, 1];\n const clip = multiply(local, toLocal);\n // Behind the camera (its center): hidden\n const centerW = (clip[3] ?? 0) * (w / 2) + (clip[7] ?? 0) * (h / 2) + (clip[15] ?? 0);\n // Hidden without touching its display (the page's layout of its content)\n if (!(centerW > 0)) {\n this.element.style.visibility = 'hidden';\n return;\n }\n this.element.style.visibility = '';\n // Its depth axis the identity, out of the rest: its points have no depth, and a transform that cannot be\n // inverted is not drawn\n const m = multiply(toScreen, clip);\n m.splice(8, 4, 0, 0, 1, 0);\n for (const k of [2, 6, 14]) m[k] = 0;\n this.element.style.transform = `matrix3d(${m.join(',')})`;\n }\n}\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FirstPersonView, LonLat } from './types.ts';
|
|
2
|
+
export interface PanoramaOptions {
|
|
3
|
+
/** URL of an image: `{cameraId}` and `{panoId}` replaced by a camera's id and a panorama's */
|
|
4
|
+
url: string;
|
|
5
|
+
/** GeoJSON points: each panorama's position, and its `id`, `heading`, `pitch`, `roll` (degrees) in properties */
|
|
6
|
+
orientationsUrl: string;
|
|
7
|
+
/** JSON array of the cameras: `id`, `rotation` (row-major), `position`, `projection`, `size`, `distortion` */
|
|
8
|
+
calibrationUrl: string;
|
|
9
|
+
/** CRS of the positions (default the GeoJSON's `crs` member, else `EPSG:2154`) */
|
|
10
|
+
crs?: string;
|
|
11
|
+
/** Altitudes above the geoid (NGF-IGN69), raised by the RAF20 geoid onto the IGN relief (default true in
|
|
12
|
+
* Lambert-93) */
|
|
13
|
+
geoid?: boolean;
|
|
14
|
+
/** Radius of the sphere around the panorama its images are a backdrop on, metres (default 1000) */
|
|
15
|
+
backgroundDistance?: number;
|
|
16
|
+
/** 0-1 (default 1) */
|
|
17
|
+
opacity?: number;
|
|
18
|
+
}
|
|
19
|
+
/** A panorama: where it is and which way the vehicle faced */
|
|
20
|
+
export interface Panorama {
|
|
21
|
+
index: number;
|
|
22
|
+
id: number | string;
|
|
23
|
+
lonLat: LonLat;
|
|
24
|
+
/** Metres above the ellipsoid */
|
|
25
|
+
height: number;
|
|
26
|
+
/** Degrees clockwise from north */
|
|
27
|
+
heading: number;
|
|
28
|
+
}
|
|
29
|
+
/** What the map gives the panoramas */
|
|
30
|
+
export interface PanoramaHost {
|
|
31
|
+
setPanorama(lon: number, lat: number, height: number, rotation: Float64Array, cameras: Float64Array, radius: number, opacity: number): void;
|
|
32
|
+
setPanoramaImage(index: number, image: ImageBitmap): void;
|
|
33
|
+
clearPanorama(): void;
|
|
34
|
+
geoidHeight(lon: number, lat: number): number;
|
|
35
|
+
/** The street-level eye, and setting it (undefined: back to the camera turning around a point) */
|
|
36
|
+
firstPerson(): Readonly<Required<FirstPersonView>> | undefined;
|
|
37
|
+
setFirstPerson(view: FirstPersonView | undefined): void;
|
|
38
|
+
elevationAt(lonLat: LonLat): number | undefined;
|
|
39
|
+
/** Follows the clicks on the map (with the ground clicked), the keys typed on it and its wheel (taken from the
|
|
40
|
+
* map's own); returns what stops */
|
|
41
|
+
listen(click: (lonLat: LonLat | undefined) => void, key: (e: KeyboardEvent) => void, wheel: (e: WheelEvent) => void): () => void;
|
|
42
|
+
}
|
|
43
|
+
interface Attitude {
|
|
44
|
+
heading: number;
|
|
45
|
+
pitch: number;
|
|
46
|
+
roll: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The panorama frame to east, north, up (row-major): the vehicle's attitude in the CRS's grid (iTowns'
|
|
50
|
+
* `quaternionFromRollPitchHeading`: Ry(-roll) · Rx(-pitch) · Rz(-heading)), turned by the meridian convergence of the
|
|
51
|
+
* projection (Lambert-93: sin(46.5°) · (3° - longitude))
|
|
52
|
+
*/
|
|
53
|
+
export declare function panoramaRotation(attitude: Attitude, lon: number, lambert93: boolean): number[];
|
|
54
|
+
/** The panoramas of a set, one shown at a time */
|
|
55
|
+
export declare class Panoramas {
|
|
56
|
+
private readonly host;
|
|
57
|
+
private readonly options;
|
|
58
|
+
readonly list: readonly Panorama[];
|
|
59
|
+
private readonly attitudes;
|
|
60
|
+
private readonly cameras;
|
|
61
|
+
private readonly lambert93;
|
|
62
|
+
/** The panorama shown, if any */
|
|
63
|
+
current: Panorama | undefined;
|
|
64
|
+
private shown;
|
|
65
|
+
/** The panorama nearest to a position */
|
|
66
|
+
nearest(lonLat: LonLat): Panorama | undefined;
|
|
67
|
+
/** Shows a panorama (its index in `list`): projected as soon as its first image arrives, the others after */
|
|
68
|
+
show(index: number): Promise<void>;
|
|
69
|
+
/** Hides the panorama shown */
|
|
70
|
+
hide(): void;
|
|
71
|
+
/** Stops the walk (`enter`) */
|
|
72
|
+
private stopWalking;
|
|
73
|
+
/**
|
|
74
|
+
* Street-level walk from panorama to panorama (the iTowns immersive view): the eye on a panorama, the drag
|
|
75
|
+
* turning it; a click goes to the panorama nearest to the clicked point, the wheel to the nearest one ahead (or
|
|
76
|
+
* behind), Z and S (or Page Up and Page Down) to the next and the previous ones. Starts on the panorama `index`,
|
|
77
|
+
* else the one nearest to `near`
|
|
78
|
+
*/
|
|
79
|
+
enter(start?: {
|
|
80
|
+
index?: number;
|
|
81
|
+
near?: LonLat;
|
|
82
|
+
}): Promise<void>;
|
|
83
|
+
/** A step of the wheel under way */
|
|
84
|
+
private moving;
|
|
85
|
+
/** The nearest panorama ahead of the eye (within 60° of where it looks), or behind it */
|
|
86
|
+
ahead(forward?: boolean): Panorama | undefined;
|
|
87
|
+
/** Goes to a panorama during the walk, the eye looking the same way (or `bearing`) */
|
|
88
|
+
go(index: number, bearing?: number): Promise<void>;
|
|
89
|
+
/** Ends the walk: the panorama hidden, the camera turning around a point again, above where the eye stood */
|
|
90
|
+
exit(): void;
|
|
91
|
+
}
|
|
92
|
+
/** Loads a set of panoramas: their positions and attitudes, the calibration of their cameras */
|
|
93
|
+
export declare function loadPanoramas(host: PanoramaHost, options: PanoramaOptions): Promise<Panoramas>;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Street-level panoramas: the oriented images of a vehicle's cameras around each of its positions (Stereopolis, the
|
|
3
|
+
* iTowns immersive examples), projected onto the scene around the panorama shown. Their positions and attitudes come
|
|
4
|
+
* as GeoJSON points (Lambert-93 by default, altitudes NGF-IGN69 raised by the RAF20 geoid onto the IGN relief), the
|
|
5
|
+
* calibration of the cameras as JSON (rotation and position in the panorama frame, intrinsics, MicMac's radial
|
|
6
|
+
* distortion), the images from a URL template with `{cameraId}` and `{panoId}`.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const panoramas = await map.addPanoramas({ url, orientationsUrl, calibrationUrl });
|
|
10
|
+
* await panoramas.show(panoramas.nearest([2.3345, 48.8505]).index);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { getCrs } from "./crs.js";
|
|
14
|
+
import { withHeaders } from "./requests.js";
|
|
15
|
+
const RAD = Math.PI / 180;
|
|
16
|
+
/** 3 × 3 product, row-major */
|
|
17
|
+
function times(a, b) {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (let r = 0; r < 3; r++) {
|
|
20
|
+
for (let c = 0; c < 3; c++) {
|
|
21
|
+
let sum = 0;
|
|
22
|
+
for (let k = 0; k < 3; k++)
|
|
23
|
+
sum += (a[r * 3 + k] ?? 0) * (b[k * 3 + c] ?? 0);
|
|
24
|
+
out.push(sum);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
const rotX = (t) => [1, 0, 0, 0, Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t)];
|
|
30
|
+
const rotY = (t) => [Math.cos(t), 0, Math.sin(t), 0, 1, 0, -Math.sin(t), 0, Math.cos(t)];
|
|
31
|
+
const rotZ = (t) => [Math.cos(t), -Math.sin(t), 0, Math.sin(t), Math.cos(t), 0, 0, 0, 1];
|
|
32
|
+
/**
|
|
33
|
+
* The panorama frame to east, north, up (row-major): the vehicle's attitude in the CRS's grid (iTowns'
|
|
34
|
+
* `quaternionFromRollPitchHeading`: Ry(-roll) · Rx(-pitch) · Rz(-heading)), turned by the meridian convergence of the
|
|
35
|
+
* projection (Lambert-93: sin(46.5°) · (3° - longitude))
|
|
36
|
+
*/
|
|
37
|
+
export function panoramaRotation(attitude, lon, lambert93) {
|
|
38
|
+
const vehicle = times(times(rotY(-attitude.roll * RAD), rotX(-attitude.pitch * RAD)), rotZ(-attitude.heading * RAD));
|
|
39
|
+
const convergence = lambert93 ? Math.sin(46.5 * RAD) * (3 - lon) * RAD : 0;
|
|
40
|
+
return times(rotZ(convergence), vehicle);
|
|
41
|
+
}
|
|
42
|
+
/** The 25 numbers of a camera for `setPanorama` */
|
|
43
|
+
function cameraNumbers(c) {
|
|
44
|
+
const p = c.projection;
|
|
45
|
+
const d = c.distortion;
|
|
46
|
+
return [
|
|
47
|
+
...c.rotation.slice(0, 9),
|
|
48
|
+
...c.position.slice(0, 3),
|
|
49
|
+
p[0] ?? 1,
|
|
50
|
+
p[4] ?? 1,
|
|
51
|
+
p[2] ?? 0,
|
|
52
|
+
p[5] ?? 0,
|
|
53
|
+
p[1] ?? 0,
|
|
54
|
+
c.size[0] ?? 1,
|
|
55
|
+
c.size[1] ?? 1,
|
|
56
|
+
d?.pps[0] ?? 0,
|
|
57
|
+
d?.pps[1] ?? 0,
|
|
58
|
+
d?.poly357[0] ?? 0,
|
|
59
|
+
d?.poly357[1] ?? 0,
|
|
60
|
+
d?.poly357[2] ?? 0,
|
|
61
|
+
d?.limit ?? 0,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
/** The panoramas of a set, one shown at a time */
|
|
65
|
+
export class Panoramas {
|
|
66
|
+
host;
|
|
67
|
+
options;
|
|
68
|
+
list;
|
|
69
|
+
attitudes;
|
|
70
|
+
cameras;
|
|
71
|
+
lambert93;
|
|
72
|
+
/** The panorama shown, if any */
|
|
73
|
+
current;
|
|
74
|
+
shown = 0;
|
|
75
|
+
/** @internal */
|
|
76
|
+
constructor(host, options, list, attitudes, cameras, lambert93) {
|
|
77
|
+
this.host = host;
|
|
78
|
+
this.options = options;
|
|
79
|
+
this.list = list;
|
|
80
|
+
this.attitudes = attitudes;
|
|
81
|
+
this.cameras = cameras;
|
|
82
|
+
this.lambert93 = lambert93;
|
|
83
|
+
}
|
|
84
|
+
/** The panorama nearest to a position */
|
|
85
|
+
nearest(lonLat) {
|
|
86
|
+
const cos = Math.cos(lonLat[1] * RAD);
|
|
87
|
+
let best;
|
|
88
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
89
|
+
for (const p of this.list) {
|
|
90
|
+
const d = ((p.lonLat[0] - lonLat[0]) * cos) ** 2 + (p.lonLat[1] - lonLat[1]) ** 2;
|
|
91
|
+
if (d < distance)
|
|
92
|
+
[best, distance] = [p, d];
|
|
93
|
+
}
|
|
94
|
+
return best;
|
|
95
|
+
}
|
|
96
|
+
/** Shows a panorama (its index in `list`): projected as soon as its first image arrives, the others after */
|
|
97
|
+
async show(index) {
|
|
98
|
+
const pano = this.list[index];
|
|
99
|
+
const attitude = this.attitudes[index];
|
|
100
|
+
if (!pano || !attitude)
|
|
101
|
+
return;
|
|
102
|
+
const shown = ++this.shown;
|
|
103
|
+
this.current = pano;
|
|
104
|
+
const rotation = panoramaRotation(attitude, pano.lonLat[0], this.lambert93);
|
|
105
|
+
const { url, backgroundDistance, opacity } = this.options;
|
|
106
|
+
this.host.setPanorama(pano.lonLat[0], pano.lonLat[1], pano.height, Float64Array.from(rotation), Float64Array.from(this.cameras.flatMap(cameraNumbers)), backgroundDistance, opacity);
|
|
107
|
+
await Promise.all(this.cameras.map(async (camera, k) => {
|
|
108
|
+
const src = url.replaceAll('{cameraId}', String(camera.id)).replaceAll('{panoId}', String(pano.id));
|
|
109
|
+
const response = await fetch(src, withHeaders(src));
|
|
110
|
+
if (!response.ok)
|
|
111
|
+
throw new Error(`isard: panorama image ${src}: ${response.status}`);
|
|
112
|
+
const image = await createImageBitmap(await response.blob(), { premultiplyAlpha: 'none' });
|
|
113
|
+
// Another panorama shown since: this image is not its
|
|
114
|
+
if (shown === this.shown)
|
|
115
|
+
this.host.setPanoramaImage(k, image);
|
|
116
|
+
image.close();
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/** Hides the panorama shown */
|
|
120
|
+
hide() {
|
|
121
|
+
this.shown++;
|
|
122
|
+
this.current = undefined;
|
|
123
|
+
this.host.clearPanorama();
|
|
124
|
+
}
|
|
125
|
+
/** Stops the walk (`enter`) */
|
|
126
|
+
stopWalking;
|
|
127
|
+
/**
|
|
128
|
+
* Street-level walk from panorama to panorama (the iTowns immersive view): the eye on a panorama, the drag
|
|
129
|
+
* turning it; a click goes to the panorama nearest to the clicked point, the wheel to the nearest one ahead (or
|
|
130
|
+
* behind), Z and S (or Page Up and Page Down) to the next and the previous ones. Starts on the panorama `index`,
|
|
131
|
+
* else the one nearest to `near`
|
|
132
|
+
*/
|
|
133
|
+
async enter(start = {}) {
|
|
134
|
+
const first = start.index ?? (start.near && this.nearest(start.near)?.index) ?? 0;
|
|
135
|
+
this.stopWalking?.();
|
|
136
|
+
this.stopWalking = this.host.listen((lonLat) => {
|
|
137
|
+
const target = lonLat && this.nearest(lonLat);
|
|
138
|
+
if (target && target !== this.current)
|
|
139
|
+
void this.go(target.index);
|
|
140
|
+
}, (e) => {
|
|
141
|
+
const step = { z: 1, Z: 1, PageUp: 1, s: -1, S: -1, PageDown: -1 }[e.key];
|
|
142
|
+
if (!step || !this.current)
|
|
143
|
+
return;
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
void this.go((this.current.index + step + this.list.length) % this.list.length);
|
|
146
|
+
}, (e) => {
|
|
147
|
+
// One step per notch, the next ones once it is shown
|
|
148
|
+
if (this.moving || e.deltaY === 0)
|
|
149
|
+
return;
|
|
150
|
+
const target = this.ahead(e.deltaY < 0);
|
|
151
|
+
if (target) {
|
|
152
|
+
this.moving = true;
|
|
153
|
+
void this.go(target.index).finally(() => {
|
|
154
|
+
this.moving = false;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
await this.go(first, this.list[first]?.heading);
|
|
159
|
+
}
|
|
160
|
+
/** A step of the wheel under way */
|
|
161
|
+
moving = false;
|
|
162
|
+
/** The nearest panorama ahead of the eye (within 60° of where it looks), or behind it */
|
|
163
|
+
ahead(forward = true) {
|
|
164
|
+
const eye = this.host.firstPerson();
|
|
165
|
+
const here = this.current;
|
|
166
|
+
if (!eye || !here)
|
|
167
|
+
return undefined;
|
|
168
|
+
const cos = Math.cos(here.lonLat[1] * RAD);
|
|
169
|
+
const look = (eye.bearing + (forward ? 0 : 180)) * RAD;
|
|
170
|
+
let best;
|
|
171
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
172
|
+
for (const p of this.list) {
|
|
173
|
+
if (p === here)
|
|
174
|
+
continue;
|
|
175
|
+
const [east, north] = [(p.lonLat[0] - here.lonLat[0]) * cos, p.lonLat[1] - here.lonLat[1]];
|
|
176
|
+
const d = Math.hypot(east, north);
|
|
177
|
+
const off = Math.acos(Math.max(-1, Math.min(1, (east * Math.sin(look) + north * Math.cos(look)) / (d || 1))));
|
|
178
|
+
if (off < 60 * RAD && d < distance)
|
|
179
|
+
[best, distance] = [p, d];
|
|
180
|
+
}
|
|
181
|
+
return best;
|
|
182
|
+
}
|
|
183
|
+
/** Goes to a panorama during the walk, the eye looking the same way (or `bearing`) */
|
|
184
|
+
async go(index, bearing) {
|
|
185
|
+
const pano = this.list[index];
|
|
186
|
+
if (!pano)
|
|
187
|
+
return;
|
|
188
|
+
const eye = this.host.firstPerson();
|
|
189
|
+
// The eye at the panorama's cameras, above the ground there
|
|
190
|
+
const ground = this.host.elevationAt(pano.lonLat);
|
|
191
|
+
this.host.setFirstPerson({
|
|
192
|
+
lonLat: pano.lonLat,
|
|
193
|
+
height: ground === undefined ? 2.5 : Math.max(0.5, pano.height - ground),
|
|
194
|
+
bearing: bearing ?? eye?.bearing ?? pano.heading,
|
|
195
|
+
...(eye ? { pitch: eye.pitch, fov: eye.fov } : {}),
|
|
196
|
+
});
|
|
197
|
+
await this.show(index);
|
|
198
|
+
}
|
|
199
|
+
/** Ends the walk: the panorama hidden, the camera turning around a point again, above where the eye stood */
|
|
200
|
+
exit() {
|
|
201
|
+
this.stopWalking?.();
|
|
202
|
+
this.stopWalking = undefined;
|
|
203
|
+
this.hide();
|
|
204
|
+
this.host.setFirstPerson(undefined);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Loads a set of panoramas: their positions and attitudes, the calibration of their cameras */
|
|
208
|
+
export async function loadPanoramas(host, options) {
|
|
209
|
+
const json = async (src) => {
|
|
210
|
+
const response = await fetch(src, withHeaders(src));
|
|
211
|
+
if (!response.ok)
|
|
212
|
+
throw new Error(`isard: ${src}: ${response.status}`);
|
|
213
|
+
return (await response.json());
|
|
214
|
+
};
|
|
215
|
+
const [orientations, cameras] = await Promise.all([
|
|
216
|
+
json(options.orientationsUrl),
|
|
217
|
+
json(options.calibrationUrl),
|
|
218
|
+
]);
|
|
219
|
+
const crsName = options.crs ?? orientations.crs?.properties?.name ?? 'EPSG:2154';
|
|
220
|
+
const lambert93 = /2154/.test(crsName);
|
|
221
|
+
const crs = getCrs(crsName);
|
|
222
|
+
const geoid = options.geoid ?? lambert93;
|
|
223
|
+
const list = [];
|
|
224
|
+
const attitudes = [];
|
|
225
|
+
for (const feature of orientations.features) {
|
|
226
|
+
const [x = 0, y = 0, z = 0] = feature.geometry.coordinates;
|
|
227
|
+
const [lon = 0, lat = 0] = crs.inverse([x, y]);
|
|
228
|
+
const p = feature.properties;
|
|
229
|
+
const number = (key) => (typeof p[key] === 'number' ? p[key] : 0);
|
|
230
|
+
attitudes.push({ heading: number('heading'), pitch: number('pitch'), roll: number('roll') });
|
|
231
|
+
list.push({
|
|
232
|
+
index: list.length,
|
|
233
|
+
id: p.id ?? list.length,
|
|
234
|
+
lonLat: [lon, lat],
|
|
235
|
+
height: z + (geoid ? host.geoidHeight(lon, lat) : 0),
|
|
236
|
+
heading: number('heading'),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return new Panoramas(host, {
|
|
240
|
+
url: options.url,
|
|
241
|
+
backgroundDistance: options.backgroundDistance ?? 1000,
|
|
242
|
+
opacity: options.opacity ?? 1,
|
|
243
|
+
}, list, attitudes, cameras, lambert93);
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=panoramas.js.map
|