isard 2.2.2 → 2.3.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.
@@ -290,9 +290,9 @@ export class Engine {
290
290
  */
291
291
  camera(): Camera;
292
292
  /**
293
- * Removes the clipping box: the layers whole again
293
+ * Removes the clipping volume: the layers whole again
294
294
  */
295
- clearClipBox(): void;
295
+ clearClipVolume(): void;
296
296
  /**
297
297
  * Removes the panorama
298
298
  */
@@ -469,12 +469,14 @@ export class Engine {
469
469
  */
470
470
  setCircles(id: number, points: Float64Array, feature: Uint32Array): void;
471
471
  /**
472
- * Cuts the 3D layers by a box: `values` its foot's longitude, latitude (degrees) and altitude (metres), its
473
- * width, length and height (metres), its heading (degrees clockwise from north of its length), 1 to cut what is
474
- * inside it (else what is outside), 1 for an altitude above the relief (else above the ellipsoid); `section` the
475
- * color of the faces the cut opens (RGB 0-1), empty to leave them open
472
+ * Cuts the 3D layers by a volume: `values` its foot's longitude, latitude (degrees) and altitude (metres), its
473
+ * width, length and height (metres; a cylinder's width its diameter), its heading (degrees clockwise from north of
474
+ * a box's length), 1 to cut what is inside it (else what is outside), 1 for an altitude above the relief (else
475
+ * above the ellipsoid), its shape (0 a box, 1 a cylinder, 2 a prism on a polygon); `polygon` the polygon's
476
+ * vertices east and north of its foot's center (metres, 16 at most); `section` the color of the faces the cut
477
+ * opens (RGB 0-1), empty to leave them open
476
478
  */
477
- setClipBox(values: Float64Array, section: Float32Array): void;
479
+ setClipVolume(values: Float64Array, polygon: Float64Array, section: Float32Array): void;
478
480
  /**
479
481
  * A raster layer draped on the roofs, or all the faces, of a 3D Tiles layer, an extrusion layer, a vector tiles
480
482
  * layer's buildings or a model layer (`raster` undefined: none)
@@ -945,7 +947,7 @@ export interface InitOutput {
945
947
  readonly engine_backend: (a: number) => [number, number];
946
948
  readonly engine_bounds: (a: number) => [number, number];
947
949
  readonly engine_camera: (a: number) => any;
948
- readonly engine_clearClipBox: (a: number) => void;
950
+ readonly engine_clearClipVolume: (a: number) => void;
949
951
  readonly engine_clearPanorama: (a: number) => void;
950
952
  readonly engine_clearPivot: (a: number) => void;
951
953
  readonly engine_clearPreload: (a: number) => void;
@@ -983,7 +985,7 @@ export interface InitOutput {
983
985
  readonly engine_setCamera: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
984
986
  readonly engine_setCircleAlignment: (a: number, b: number, c: number) => void;
985
987
  readonly engine_setCircles: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
986
- readonly engine_setClipBox: (a: number, b: number, c: number, d: number, e: number) => void;
988
+ readonly engine_setClipVolume: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
987
989
  readonly engine_setDrape: (a: number, b: number, c: number, d: number, e: number) => void;
988
990
  readonly engine_setExtrusionAnchors: (a: number, b: number, c: number, d: number, e: number) => [number, number];
989
991
  readonly engine_setExtrusionBatch: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => [number, number];
@@ -357,10 +357,10 @@ export class Engine {
357
357
  return ret;
358
358
  }
359
359
  /**
360
- * Removes the clipping box: the layers whole again
360
+ * Removes the clipping volume: the layers whole again
361
361
  */
362
- clearClipBox() {
363
- wasm.engine_clearClipBox(this.__wbg_ptr);
362
+ clearClipVolume() {
363
+ wasm.engine_clearClipVolume(this.__wbg_ptr);
364
364
  }
365
365
  /**
366
366
  * Removes the panorama
@@ -737,19 +737,24 @@ export class Engine {
737
737
  wasm.engine_setCircles(this.__wbg_ptr, id, ptr0, len0, ptr1, len1);
738
738
  }
739
739
  /**
740
- * Cuts the 3D layers by a box: `values` its foot's longitude, latitude (degrees) and altitude (metres), its
741
- * width, length and height (metres), its heading (degrees clockwise from north of its length), 1 to cut what is
742
- * inside it (else what is outside), 1 for an altitude above the relief (else above the ellipsoid); `section` the
743
- * color of the faces the cut opens (RGB 0-1), empty to leave them open
740
+ * Cuts the 3D layers by a volume: `values` its foot's longitude, latitude (degrees) and altitude (metres), its
741
+ * width, length and height (metres; a cylinder's width its diameter), its heading (degrees clockwise from north of
742
+ * a box's length), 1 to cut what is inside it (else what is outside), 1 for an altitude above the relief (else
743
+ * above the ellipsoid), its shape (0 a box, 1 a cylinder, 2 a prism on a polygon); `polygon` the polygon's
744
+ * vertices east and north of its foot's center (metres, 16 at most); `section` the color of the faces the cut
745
+ * opens (RGB 0-1), empty to leave them open
744
746
  * @param {Float64Array} values
747
+ * @param {Float64Array} polygon
745
748
  * @param {Float32Array} section
746
749
  */
747
- setClipBox(values, section) {
750
+ setClipVolume(values, polygon, section) {
748
751
  const ptr0 = passArrayF64ToWasm0(values, wasm.__wbindgen_malloc);
749
752
  const len0 = WASM_VECTOR_LEN;
750
- const ptr1 = passArrayF32ToWasm0(section, wasm.__wbindgen_malloc);
753
+ const ptr1 = passArrayF64ToWasm0(polygon, wasm.__wbindgen_malloc);
751
754
  const len1 = WASM_VECTOR_LEN;
752
- wasm.engine_setClipBox(this.__wbg_ptr, ptr0, len0, ptr1, len1);
755
+ const ptr2 = passArrayF32ToWasm0(section, wasm.__wbindgen_malloc);
756
+ const len2 = WASM_VECTOR_LEN;
757
+ wasm.engine_setClipVolume(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2);
753
758
  }
754
759
  /**
755
760
  * A raster layer draped on the roofs, or all the faces, of a 3D Tiles layer, an extrusion layer, a vector tiles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isard",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "A WebGL2 map engine for large, interactive data layers, labels and 3D Tiles over raster or vector basemaps (Rust core in WebAssembly)",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Graff",
@@ -1,52 +0,0 @@
1
- /**
2
- * The clipping box (`GeoMap.setClipBox`): given to the engine, and its handles over the map, an outline of its edges
3
- * and a disc on each face to size it, one at its middle to move it, placed after every frame.
4
- */
5
- import type { ClipBox, LonLat, Point } from '../types.ts';
6
- import type { Positioned } from '../ui/overlay.ts';
7
- import type { Engine } from '../wasm/isard_webgpu.js';
8
- /** Sets the box in the engine, or clears it without one */
9
- export declare const applyClipBox: (engine: Engine, box: ClipBox | undefined) => void;
10
- /** What the handles need from the map */
11
- export interface ClipBoxHost {
12
- project(lonLat: LonLat, height?: number): Point | undefined;
13
- unproject(point: Point): LonLat | undefined;
14
- elevationAt(lonLat: LonLat): number | undefined;
15
- getCanvas(): HTMLCanvasElement;
16
- /** The box changed by a handle: set, and told */
17
- moved(box: ClipBox): void;
18
- }
19
- /** The handles of a box, over the map */
20
- export declare class ClipBoxHandles implements Positioned {
21
- private readonly host;
22
- private box;
23
- /** Metres of the body's radius (metres to degrees) */
24
- private readonly radius;
25
- /** Over the canvas, its drawing within */
26
- readonly element: HTMLDivElement;
27
- private readonly lines;
28
- private readonly discs;
29
- /** The handle held (its disc), the pointer and the box where it was pressed */
30
- private held;
31
- constructor(host: ClipBoxHost, box: ClipBox,
32
- /** Metres of the body's radius (metres to degrees) */
33
- radius: number);
34
- /** The box the handles follow from the next placing (`position`, after each frame) */
35
- set(box: ClipBox): void;
36
- position(): void;
37
- /** A point of the box, metres across and along its heading from its foot's center and up from its foot */
38
- private at;
39
- private corners;
40
- /** Where a handle stands on the screen: a face's center, or the box's */
41
- private handleAt;
42
- private local;
43
- private down;
44
- private move;
45
- /** Metres the pointer dragged a face outward: the drag along the face's axis on the screen */
46
- private dragged;
47
- /** The box with a face moved `metres` outward, the opposite face where it was */
48
- private resized;
49
- /** A position `east` and `north` metres from another */
50
- private offset;
51
- private up;
52
- }
@@ -1,233 +0,0 @@
1
- /**
2
- * The clipping box (`GeoMap.setClipBox`): given to the engine, and its handles over the map, an outline of its edges
3
- * and a disc on each face to size it, one at its middle to move it, placed after every frame.
4
- */
5
- import { rgb } from "../settings.js";
6
- /** The faces' color by default: plaster, as a section on a plan */
7
- const SECTION = '#d9cfc1';
8
- /** Metres a side of the box keeps at least */
9
- const SMALLEST = 0.2;
10
- const SVG = 'http://www.w3.org/2000/svg';
11
- /** Sets the box in the engine, or clears it without one */
12
- export const applyClipBox = (engine, box) => {
13
- if (!box) {
14
- engine.clearClipBox();
15
- return;
16
- }
17
- const [width, length, height] = box.size;
18
- const section = box.sectionColor === null ? [] : rgb(box.sectionColor ?? SECTION);
19
- engine.setClipBox(new Float64Array([
20
- box.position[0],
21
- box.position[1],
22
- box.altitude ?? 0,
23
- width,
24
- length,
25
- height,
26
- box.heading ?? 0,
27
- box.cut === 'outside' ? 0 : 1,
28
- box.altitudeReference === 'sea' ? 0 : 1,
29
- ]), new Float32Array(section));
30
- };
31
- /** Metres across, along and up from the foot's center of a face's center, `out` metres out of it */
32
- const faceAt = (box, { axis, side }, out = 0) => {
33
- const [, , h] = box.size;
34
- const offset = [0, 0, h / 2];
35
- offset[axis] = axis === 2 ? (side > 0 ? h : 0) + side * out : side * (box.size[axis] / 2 + out);
36
- return offset;
37
- };
38
- /** The 12 edges of the box, as pairs of corners (bit 0 across, 1 along, 2 up) */
39
- const EDGES = [
40
- [0, 1],
41
- [2, 3],
42
- [4, 5],
43
- [6, 7],
44
- [0, 2],
45
- [1, 3],
46
- [4, 6],
47
- [5, 7],
48
- [0, 4],
49
- [1, 5],
50
- [2, 6],
51
- [3, 7],
52
- ];
53
- const HANDLES = [
54
- { axis: 0, side: -1 },
55
- { axis: 0, side: 1 },
56
- { axis: 1, side: -1 },
57
- { axis: 1, side: 1 },
58
- { axis: 2, side: -1 },
59
- { axis: 2, side: 1 },
60
- 'move',
61
- ];
62
- /** The handles of a box, over the map */
63
- export class ClipBoxHandles {
64
- host;
65
- box;
66
- radius;
67
- /** Over the canvas, its drawing within */
68
- element;
69
- lines;
70
- discs;
71
- /** The handle held (its disc), the pointer and the box where it was pressed */
72
- held;
73
- constructor(host, box,
74
- /** Metres of the body's radius (metres to degrees) */
75
- radius) {
76
- this.host = host;
77
- this.box = box;
78
- this.radius = radius;
79
- const svg = document.createElementNS(SVG, 'svg');
80
- this.element = document.createElement('div');
81
- this.element.className = 'isard-clipbox';
82
- this.element.append(svg);
83
- this.lines = EDGES.map(() => svg.appendChild(document.createElementNS(SVG, 'line')));
84
- this.discs = HANDLES.map((handle) => {
85
- const disc = document.createElementNS(SVG, 'circle');
86
- disc.setAttribute('r', handle === 'move' ? '8' : '6');
87
- disc.setAttribute('class', handle === 'move' ? 'isard-clipbox-move' : 'isard-clipbox-face');
88
- disc.addEventListener('pointerdown', (e) => this.down(e, handle, disc));
89
- disc.addEventListener('pointermove', (e) => this.move(e));
90
- disc.addEventListener('pointerup', (e) => this.up(e));
91
- disc.addEventListener('pointercancel', (e) => this.up(e));
92
- // Safari may let the capture go: the release would then reach the map, not the disc
93
- disc.addEventListener('lostpointercapture', (e) => this.up(e));
94
- return svg.appendChild(disc);
95
- });
96
- }
97
- /** The box the handles follow from the next placing (`position`, after each frame) */
98
- set(box) {
99
- this.box = box;
100
- }
101
- position() {
102
- const corners = this.corners(this.box);
103
- const centers = HANDLES.map((handle) => this.handleAt(this.box, handle));
104
- EDGES.forEach(([a, b], i) => {
105
- const [p, q] = [corners[a], corners[b]];
106
- const line = this.lines[i];
107
- if (!line)
108
- return;
109
- line.style.display = p && q ? '' : 'none';
110
- if (!p || !q)
111
- return;
112
- line.setAttribute('x1', String(p[0]));
113
- line.setAttribute('y1', String(p[1]));
114
- line.setAttribute('x2', String(q[0]));
115
- line.setAttribute('y2', String(q[1]));
116
- });
117
- centers.forEach((p, i) => {
118
- const disc = this.discs[i];
119
- if (!disc)
120
- return;
121
- disc.style.display = p ? '' : 'none';
122
- if (!p)
123
- return;
124
- disc.setAttribute('cx', String(p[0]));
125
- disc.setAttribute('cy', String(p[1]));
126
- });
127
- }
128
- /** A point of the box, metres across and along its heading from its foot's center and up from its foot */
129
- at(box, across, along, up) {
130
- const heading = ((box.heading ?? 0) * Math.PI) / 180;
131
- const [sin, cos] = [Math.sin(heading), Math.cos(heading)];
132
- const lonLat = this.offset(box.position, across * cos + along * sin, along * cos - across * sin);
133
- // `project` measures from the relief: an altitude above the ellipsoid less the relief under the box
134
- const ground = box.altitudeReference === 'sea' ? (this.host.elevationAt(box.position) ?? 0) : 0;
135
- return this.host.project(lonLat, (box.altitude ?? 0) - ground + up);
136
- }
137
- corners(box) {
138
- const [w, l, h] = box.size;
139
- return Array.from({ length: 8 }, (_, k) => this.at(box, (k & 1 ? 0.5 : -0.5) * w, (k & 2 ? 0.5 : -0.5) * l, k & 4 ? h : 0));
140
- }
141
- /** Where a handle stands on the screen: a face's center, or the box's */
142
- handleAt(box, handle) {
143
- return handle === 'move' ? this.at(box, 0, 0, box.size[2] / 2) : this.at(box, ...faceAt(box, handle));
144
- }
145
- local(e) {
146
- const rect = this.host.getCanvas().getBoundingClientRect();
147
- return [e.clientX - rect.left, e.clientY - rect.top];
148
- }
149
- down(e, handle, disc) {
150
- if (e.button !== 0)
151
- return;
152
- const start = this.local(e);
153
- // The spot of the ground grabbed, kept under the pointer as the box moves
154
- const grab = handle === 'move' ? this.host.unproject(start) : undefined;
155
- // The map must not pan under the handle
156
- e.stopPropagation();
157
- e.preventDefault();
158
- disc.setPointerCapture(e.pointerId);
159
- this.held = {
160
- handle,
161
- disc,
162
- start,
163
- box: { ...this.box, size: [...this.box.size] },
164
- grab: grab ? [grab[0] - this.box.position[0], grab[1] - this.box.position[1]] : [0, 0],
165
- };
166
- }
167
- move(e) {
168
- if (!this.held)
169
- return;
170
- // No button held any more: its release was missed (the capture lost), the drag is over
171
- if (e.buttons === 0) {
172
- this.up(e);
173
- return;
174
- }
175
- e.stopPropagation();
176
- const { handle, start, box, grab } = this.held;
177
- const at = this.local(e);
178
- if (handle === 'move') {
179
- // The ground under the pointer, the grabbed spot kept under it
180
- const lonLat = this.host.unproject(at);
181
- if (lonLat)
182
- this.host.moved({ ...box, position: [lonLat[0] - grab[0], lonLat[1] - grab[1]] });
183
- return;
184
- }
185
- const metres = this.dragged(box, handle, [at[0] - start[0], at[1] - start[1]]);
186
- if (metres !== undefined)
187
- this.host.moved(this.resized(box, handle, metres));
188
- }
189
- /** Metres the pointer dragged a face outward: the drag along the face's axis on the screen */
190
- dragged(box, face, [dx, dy]) {
191
- const from = this.at(box, ...faceAt(box, face));
192
- // Where the face's center goes a metre out, on the screen: pixels per metre along its axis
193
- const to = this.at(box, ...faceAt(box, face, 1));
194
- if (!from || !to)
195
- return undefined;
196
- const [ax, ay] = [to[0] - from[0], to[1] - from[1]];
197
- const squared = ax * ax + ay * ay;
198
- return squared > 1e-6 ? (dx * ax + dy * ay) / squared : undefined;
199
- }
200
- /** The box with a face moved `metres` outward, the opposite face where it was */
201
- resized(box, { axis, side }, metres) {
202
- const size = [...box.size];
203
- const grown = Math.max(SMALLEST, size[axis] + metres);
204
- const moved = grown - size[axis];
205
- size[axis] = grown;
206
- if (axis === 2) {
207
- // The foot lowered when the bottom face goes down
208
- return side > 0 ? { ...box, size } : { ...box, size, altitude: (box.altitude ?? 0) - moved };
209
- }
210
- // The center half-way along the move, on the side of the face moved
211
- const heading = ((box.heading ?? 0) * Math.PI) / 180;
212
- const [sin, cos] = [Math.sin(heading), Math.cos(heading)];
213
- const shift = (side * moved) / 2;
214
- const [east, north] = axis === 0 ? [shift * cos, -shift * sin] : [shift * sin, shift * cos];
215
- return { ...box, size, position: this.offset(box.position, east, north) };
216
- }
217
- /** A position `east` and `north` metres from another */
218
- offset([lon, lat], east, north) {
219
- return [
220
- lon + (east / (this.radius * Math.cos((lat * Math.PI) / 180))) * (180 / Math.PI),
221
- lat + (north / this.radius) * (180 / Math.PI),
222
- ];
223
- }
224
- up(e) {
225
- if (!this.held)
226
- return;
227
- e.stopPropagation();
228
- if (this.held.disc.hasPointerCapture(e.pointerId))
229
- this.held.disc.releasePointerCapture(e.pointerId);
230
- this.held = undefined;
231
- }
232
- }
233
- //# sourceMappingURL=clip-box.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"clip-box.js","sourceRoot":"","sources":["../../src/map/clip-box.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The clipping box (`GeoMap.setClipBox`): given to the engine, and its handles over the map, an outline of its edges\n * and a disc on each face to size it, one at its middle to move it, placed after every frame.\n */\n\nimport { rgb } from '../settings.ts';\nimport type { ClipBox, LonLat, Point } from '../types.ts';\nimport type { Positioned } from '../ui/overlay.ts';\nimport type { Engine } from '../wasm/isard_webgpu.js';\n\n/** The faces' color by default: plaster, as a section on a plan */\nconst SECTION = '#d9cfc1';\n/** Metres a side of the box keeps at least */\nconst SMALLEST = 0.2;\nconst SVG = 'http://www.w3.org/2000/svg';\n\n/** Sets the box in the engine, or clears it without one */\nexport const applyClipBox = (engine: Engine, box: ClipBox | undefined): void => {\n if (!box) {\n engine.clearClipBox();\n\n return;\n }\n\n const [width, length, height] = box.size;\n const section = box.sectionColor === null ? [] : rgb(box.sectionColor ?? SECTION);\n\n engine.setClipBox(\n new Float64Array([\n box.position[0],\n box.position[1],\n box.altitude ?? 0,\n width,\n length,\n height,\n box.heading ?? 0,\n box.cut === 'outside' ? 0 : 1,\n box.altitudeReference === 'sea' ? 0 : 1,\n ]),\n new Float32Array(section),\n );\n};\n\n/** What the handles need from the map */\nexport interface ClipBoxHost {\n project(lonLat: LonLat, height?: number): Point | undefined;\n unproject(point: Point): LonLat | undefined;\n elevationAt(lonLat: LonLat): number | undefined;\n getCanvas(): HTMLCanvasElement;\n /** The box changed by a handle: set, and told */\n moved(box: ClipBox): void;\n}\n\n/** A face's handle: its axis (0 across, 1 along, 2 up) and side */\ninterface Face {\n axis: 0 | 1 | 2;\n side: -1 | 1;\n}\n\n/** A handle: on a face, or at the middle (moves the box, clear of the bottom face's) */\ntype Handle = Face | 'move';\n\n/** Metres across, along and up from the foot's center of a face's center, `out` metres out of it */\nconst faceAt = (box: ClipBox, { axis, side }: Face, out = 0): [number, number, number] => {\n const [, , h] = box.size;\n const offset: [number, number, number] = [0, 0, h / 2];\n\n offset[axis] = axis === 2 ? (side > 0 ? h : 0) + side * out : side * (box.size[axis] / 2 + out);\n\n return offset;\n};\n\n/** The 12 edges of the box, as pairs of corners (bit 0 across, 1 along, 2 up) */\nconst EDGES: [number, number][] = [\n [0, 1],\n [2, 3],\n [4, 5],\n [6, 7],\n [0, 2],\n [1, 3],\n [4, 6],\n [5, 7],\n [0, 4],\n [1, 5],\n [2, 6],\n [3, 7],\n];\n\nconst HANDLES: Handle[] = [\n { axis: 0, side: -1 },\n { axis: 0, side: 1 },\n { axis: 1, side: -1 },\n { axis: 1, side: 1 },\n { axis: 2, side: -1 },\n { axis: 2, side: 1 },\n 'move',\n];\n\n/** The handles of a box, over the map */\nexport class ClipBoxHandles implements Positioned {\n /** Over the canvas, its drawing within */\n readonly element: HTMLDivElement;\n private readonly lines: SVGLineElement[];\n private readonly discs: SVGCircleElement[];\n /** The handle held (its disc), the pointer and the box where it was pressed */\n private held: { handle: Handle; disc: SVGCircleElement; start: Point; box: ClipBox; grab: Point } | undefined;\n\n constructor(\n private readonly host: ClipBoxHost,\n private box: ClipBox,\n /** Metres of the body's radius (metres to degrees) */\n private readonly radius: number,\n ) {\n const svg = document.createElementNS(SVG, 'svg');\n\n this.element = document.createElement('div');\n this.element.className = 'isard-clipbox';\n this.element.append(svg);\n this.lines = EDGES.map(() => svg.appendChild(document.createElementNS(SVG, 'line')));\n this.discs = HANDLES.map((handle) => {\n const disc = document.createElementNS(SVG, 'circle');\n\n disc.setAttribute('r', handle === 'move' ? '8' : '6');\n disc.setAttribute('class', handle === 'move' ? 'isard-clipbox-move' : 'isard-clipbox-face');\n disc.addEventListener('pointerdown', (e) => this.down(e, handle, disc));\n disc.addEventListener('pointermove', (e) => this.move(e));\n disc.addEventListener('pointerup', (e) => this.up(e));\n disc.addEventListener('pointercancel', (e) => this.up(e));\n // Safari may let the capture go: the release would then reach the map, not the disc\n disc.addEventListener('lostpointercapture', (e) => this.up(e));\n\n return svg.appendChild(disc);\n });\n }\n\n /** The box the handles follow from the next placing (`position`, after each frame) */\n set(box: ClipBox): void {\n this.box = box;\n }\n\n position(): void {\n const corners = this.corners(this.box);\n const centers = HANDLES.map((handle) => this.handleAt(this.box, handle));\n\n EDGES.forEach(([a, b], i) => {\n const [p, q] = [corners[a], corners[b]];\n const line = this.lines[i];\n\n if (!line) return;\n line.style.display = p && q ? '' : 'none';\n if (!p || !q) return;\n line.setAttribute('x1', String(p[0]));\n line.setAttribute('y1', String(p[1]));\n line.setAttribute('x2', String(q[0]));\n line.setAttribute('y2', String(q[1]));\n });\n centers.forEach((p, i) => {\n const disc = this.discs[i];\n\n if (!disc) return;\n disc.style.display = p ? '' : 'none';\n if (!p) return;\n disc.setAttribute('cx', String(p[0]));\n disc.setAttribute('cy', String(p[1]));\n });\n }\n\n /** A point of the box, metres across and along its heading from its foot's center and up from its foot */\n private at(box: ClipBox, across: number, along: number, up: number): Point | undefined {\n const heading = ((box.heading ?? 0) * Math.PI) / 180;\n const [sin, cos] = [Math.sin(heading), Math.cos(heading)];\n const lonLat = this.offset(box.position, across * cos + along * sin, along * cos - across * sin);\n // `project` measures from the relief: an altitude above the ellipsoid less the relief under the box\n const ground = box.altitudeReference === 'sea' ? (this.host.elevationAt(box.position) ?? 0) : 0;\n\n return this.host.project(lonLat, (box.altitude ?? 0) - ground + up);\n }\n\n private corners(box: ClipBox): (Point | undefined)[] {\n const [w, l, h] = box.size;\n\n return Array.from({ length: 8 }, (_, k) =>\n this.at(box, (k & 1 ? 0.5 : -0.5) * w, (k & 2 ? 0.5 : -0.5) * l, k & 4 ? h : 0),\n );\n }\n\n /** Where a handle stands on the screen: a face's center, or the box's */\n private handleAt(box: ClipBox, handle: Handle): Point | undefined {\n return handle === 'move' ? this.at(box, 0, 0, box.size[2] / 2) : this.at(box, ...faceAt(box, handle));\n }\n\n private local(e: PointerEvent): Point {\n const rect = this.host.getCanvas().getBoundingClientRect();\n\n return [e.clientX - rect.left, e.clientY - rect.top];\n }\n\n private down(e: PointerEvent, handle: Handle, disc: SVGCircleElement): void {\n if (e.button !== 0) return;\n\n const start = this.local(e);\n // The spot of the ground grabbed, kept under the pointer as the box moves\n const grab = handle === 'move' ? this.host.unproject(start) : undefined;\n\n // The map must not pan under the handle\n e.stopPropagation();\n e.preventDefault();\n disc.setPointerCapture(e.pointerId);\n this.held = {\n handle,\n disc,\n start,\n box: { ...this.box, size: [...this.box.size] },\n grab: grab ? [grab[0] - this.box.position[0], grab[1] - this.box.position[1]] : [0, 0],\n };\n }\n\n private move(e: PointerEvent): void {\n if (!this.held) return;\n // No button held any more: its release was missed (the capture lost), the drag is over\n if (e.buttons === 0) {\n this.up(e);\n\n return;\n }\n e.stopPropagation();\n\n const { handle, start, box, grab } = this.held;\n const at = this.local(e);\n\n if (handle === 'move') {\n // The ground under the pointer, the grabbed spot kept under it\n const lonLat = this.host.unproject(at);\n\n if (lonLat) this.host.moved({ ...box, position: [lonLat[0] - grab[0], lonLat[1] - grab[1]] });\n\n return;\n }\n\n const metres = this.dragged(box, handle, [at[0] - start[0], at[1] - start[1]]);\n\n if (metres !== undefined) this.host.moved(this.resized(box, handle, metres));\n }\n\n /** Metres the pointer dragged a face outward: the drag along the face's axis on the screen */\n private dragged(box: ClipBox, face: Face, [dx, dy]: Point): number | undefined {\n const from = this.at(box, ...faceAt(box, face));\n // Where the face's center goes a metre out, on the screen: pixels per metre along its axis\n const to = this.at(box, ...faceAt(box, face, 1));\n\n if (!from || !to) return undefined;\n\n const [ax, ay] = [to[0] - from[0], to[1] - from[1]];\n const squared = ax * ax + ay * ay;\n\n return squared > 1e-6 ? (dx * ax + dy * ay) / squared : undefined;\n }\n\n /** The box with a face moved `metres` outward, the opposite face where it was */\n private resized(box: ClipBox, { axis, side }: Face, metres: number): ClipBox {\n const size: [number, number, number] = [...box.size];\n const grown = Math.max(SMALLEST, size[axis] + metres);\n const moved = grown - size[axis];\n\n size[axis] = grown;\n if (axis === 2) {\n // The foot lowered when the bottom face goes down\n return side > 0 ? { ...box, size } : { ...box, size, altitude: (box.altitude ?? 0) - moved };\n }\n\n // The center half-way along the move, on the side of the face moved\n const heading = ((box.heading ?? 0) * Math.PI) / 180;\n const [sin, cos] = [Math.sin(heading), Math.cos(heading)];\n const shift = (side * moved) / 2;\n const [east, north] = axis === 0 ? [shift * cos, -shift * sin] : [shift * sin, shift * cos];\n return { ...box, size, position: this.offset(box.position, east, north) };\n }\n\n /** A position `east` and `north` metres from another */\n private offset([lon, lat]: LonLat, east: number, north: number): LonLat {\n return [\n lon + (east / (this.radius * Math.cos((lat * Math.PI) / 180))) * (180 / Math.PI),\n lat + (north / this.radius) * (180 / Math.PI),\n ];\n }\n\n private up(e: PointerEvent): void {\n if (!this.held) return;\n e.stopPropagation();\n if (this.held.disc.hasPointerCapture(e.pointerId)) this.held.disc.releasePointerCapture(e.pointerId);\n this.held = undefined;\n }\n}\n"]}
@@ -1,9 +0,0 @@
1
- /** `<ClipBox>`: a box cutting the map's 3D layers, its handles' changes told back */
2
- import { type ReactNode } from 'react';
3
- import type { ClipBox as ClipBoxOptions } from '../types.ts';
4
- export interface ClipBoxProps extends ClipBoxOptions {
5
- /** The box moved or sized by its handles (`handles`): keep it in a state to follow them */
6
- onChange?: (box: ClipBoxOptions) => void;
7
- }
8
- /** The map's clipping box (`GeoMap.setClipBox`) while mounted */
9
- export declare const ClipBox: ({ onChange, ...box }: ClipBoxProps) => ReactNode;
@@ -1,21 +0,0 @@
1
- /** `<ClipBox>`: a box cutting the map's 3D layers, its handles' changes told back */
2
- import { useEffect } from 'react';
3
- import { useMap, useMapEvent } from "./context.js";
4
- /** The map's clipping box (`GeoMap.setClipBox`) while mounted */
5
- export const ClipBox = ({ onChange, ...box }) => {
6
- const map = useMap();
7
- // Numbers, strings and arrays: compared by value
8
- const options = JSON.stringify(box);
9
- // The layers whole again when unmounted
10
- useEffect(() => {
11
- if (!map)
12
- return;
13
- return () => map.setClipBox(undefined);
14
- }, [map]);
15
- useEffect(() => {
16
- map?.setClipBox(JSON.parse(options));
17
- }, [map, options]);
18
- useMapEvent('clipbox', (moved) => onChange?.(moved));
19
- return null;
20
- };
21
- //# sourceMappingURL=ClipBox.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ClipBox.js","sourceRoot":"","sources":["../../src/react/ClipBox.tsx"],"names":[],"mappings":"AAAA,qFAAqF;AAErF,OAAO,EAAkB,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOnD,iEAAiE;AACjE,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,GAAG,EAAgB,EAAa,EAAE;IACvE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,iDAAiD;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAEpC,wCAAwC;IACxC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACV,SAAS,CAAC,GAAG,EAAE;QACb,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC,CAAC;IACzD,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IACnB,WAAW,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAErD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC","sourcesContent":["/** `<ClipBox>`: a box cutting the map's 3D layers, its handles' changes told back */\n\nimport { type ReactNode, useEffect } from 'react';\nimport type { ClipBox as ClipBoxOptions } from '../types.ts';\nimport { useMap, useMapEvent } from './context.ts';\n\nexport interface ClipBoxProps extends ClipBoxOptions {\n /** The box moved or sized by its handles (`handles`): keep it in a state to follow them */\n onChange?: (box: ClipBoxOptions) => void;\n}\n\n/** The map's clipping box (`GeoMap.setClipBox`) while mounted */\nexport const ClipBox = ({ onChange, ...box }: ClipBoxProps): ReactNode => {\n const map = useMap();\n // Numbers, strings and arrays: compared by value\n const options = JSON.stringify(box);\n\n // The layers whole again when unmounted\n useEffect(() => {\n if (!map) return;\n\n return () => map.setClipBox(undefined);\n }, [map]);\n useEffect(() => {\n map?.setClipBox(JSON.parse(options) as ClipBoxOptions);\n }, [map, options]);\n useMapEvent('clipbox', (moved) => onChange?.(moved));\n\n return null;\n};\n"]}