@sakuzu/maplibre-gl-draw 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/dataset/retained.js +19 -18
  3. package/dist/dataset/selection.d.ts +3 -3
  4. package/dist/dataset/selection.js +3 -7
  5. package/dist/dataset/table-source.js +13 -11
  6. package/dist/modes/select/click-handler.d.ts +4 -1
  7. package/dist/modes/select/click-handler.js +17 -9
  8. package/dist/view/layer/custom-layer.js +2 -1
  9. package/dist/view/layer/drape-planner.d.ts +3 -0
  10. package/dist/view/layer/drape-planner.js +9 -7
  11. package/dist/view/layer/frame-render.js +2 -3
  12. package/dist/view/layer/frame-state.d.ts +3 -0
  13. package/dist/view/layer/frame-state.js +2 -1
  14. package/dist/view/layer/gl-state.d.ts +2 -3
  15. package/dist/view/layer/gl-state.js +2 -3
  16. package/dist/view/layer/renderers.js +2 -1
  17. package/dist/view/renderers/line/dash.d.ts +27 -0
  18. package/dist/view/renderers/line/dash.js +23 -12
  19. package/dist/view/shaders/initializer.d.ts +3 -0
  20. package/dist/view/shaders/initializer.js +4 -2
  21. package/dist/view/terrain/anchor.js +20 -1
  22. package/dist/view/terrain/drape/bin-store.d.ts +11 -2
  23. package/dist/view/terrain/drape/bin-store.js +26 -2
  24. package/dist/view/terrain/drape/binning.d.ts +32 -0
  25. package/dist/view/terrain/drape/binning.js +15 -7
  26. package/dist/view/terrain/drape/geometry.d.ts +27 -0
  27. package/dist/view/terrain/drape/geometry.js +57 -6
  28. package/dist/view/terrain/drape/pass.d.ts +8 -3
  29. package/dist/view/terrain/drape/pass.js +39 -28
  30. package/dist/view/terrain/drape/renderer.d.ts +13 -0
  31. package/dist/view/terrain/drape/renderer.js +157 -30
  32. package/dist/view/ui/selection-ui/renderer.d.ts +2 -1
  33. package/dist/view/ui/selection-ui/renderer.js +88 -25
  34. package/dist/view/ui/selection-ui-drawer.js +4 -8
  35. package/package.json +1 -1
@@ -11,21 +11,31 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
12
12
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
13
13
  };
14
- var _SelectionUIRenderer_instances, _SelectionUIRenderer_strokeRenderer, _SelectionUIRenderer_extensions, _SelectionUIRenderer_config, _SelectionUIRenderer_transform, _SelectionUIRenderer_projectionData, _SelectionUIRenderer_drawRotatingBoundingBox, _SelectionUIRenderer_drawIndividualBoundingBox, _SelectionUIRenderer_drawCombinedBoundingBox, _SelectionUIRenderer_drawBoundingBoxWithMargin, _SelectionUIRenderer_drawPointBoundingBox, _SelectionUIRenderer_applyMargin, _SelectionUIRenderer_strokeBoundingBox, _SelectionUIRenderer_strokePath;
15
- import { applyMarginToBoundingBox } from '../../../shared/math/index.js';
14
+ var _SelectionUIRenderer_instances, _SelectionUIRenderer_strokeRenderer, _SelectionUIRenderer_extensions, _SelectionUIRenderer_terrain, _SelectionUIRenderer_config, _SelectionUIRenderer_transform, _SelectionUIRenderer_projectionData, _SelectionUIRenderer_drawRotatingBoundingBox, _SelectionUIRenderer_drawIndividualBoundingBox, _SelectionUIRenderer_drawCombinedBoundingBox, _SelectionUIRenderer_drawBoundingBoxWithMargin, _SelectionUIRenderer_drawPointBoundingBox, _SelectionUIRenderer_pointFrame, _SelectionUIRenderer_applyMargin, _SelectionUIRenderer_strokeBoundingBox, _SelectionUIRenderer_strokePath;
15
+ import { applyMarginToBoundingBox, } from '../../../shared/math/index.js';
16
+ import { anchorElevationMeters, isAnchorActive, projectAnchorAt } from '../../terrain/anchor.js';
17
+ import { TerrainContext } from '../../terrain/context.js';
16
18
  import { computeBoundingBox, computeCombinedBoundingBox, hasZeroArea, rotateBoundingBox, } from './bounding-box.js';
19
+ /**
20
+ * How far the positions differ that the scale of the screen around a point is measured
21
+ * between, in pixels at the zoom of the frame
22
+ */
23
+ const POINT_FRAME_PROBE_PX = 4;
17
24
  export class SelectionUIRenderer {
18
- constructor(strokeRenderer, config, extensions) {
25
+ constructor(strokeRenderer, config, extensions, terrain = new TerrainContext()) {
19
26
  _SelectionUIRenderer_instances.add(this);
20
27
  _SelectionUIRenderer_strokeRenderer.set(this, void 0);
21
28
  /** The Selection UI extension points of the draw instance this renderer belongs to */
22
29
  _SelectionUIRenderer_extensions.set(this, void 0);
30
+ /** The terrain state of the draw instance (an inactive one projects with the map) */
31
+ _SelectionUIRenderer_terrain.set(this, void 0);
23
32
  _SelectionUIRenderer_config.set(this, void 0);
24
33
  _SelectionUIRenderer_transform.set(this, null);
25
34
  _SelectionUIRenderer_projectionData.set(this, null);
26
35
  __classPrivateFieldSet(this, _SelectionUIRenderer_strokeRenderer, strokeRenderer, "f");
27
36
  __classPrivateFieldSet(this, _SelectionUIRenderer_config, config, "f");
28
37
  __classPrivateFieldSet(this, _SelectionUIRenderer_extensions, extensions, "f");
38
+ __classPrivateFieldSet(this, _SelectionUIRenderer_terrain, terrain, "f");
29
39
  }
30
40
  /** Compute the BoundingBox of a feature (also usable from outside) */
31
41
  computeFeatureBoundingBox(feature) {
@@ -80,7 +90,7 @@ export class SelectionUIRenderer {
80
90
  __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_drawCombinedBoundingBox).call(this, features, zoom);
81
91
  }
82
92
  }
83
- _SelectionUIRenderer_strokeRenderer = new WeakMap(), _SelectionUIRenderer_extensions = new WeakMap(), _SelectionUIRenderer_config = new WeakMap(), _SelectionUIRenderer_transform = new WeakMap(), _SelectionUIRenderer_projectionData = new WeakMap(), _SelectionUIRenderer_instances = new WeakSet(), _SelectionUIRenderer_drawRotatingBoundingBox = function _SelectionUIRenderer_drawRotatingBoundingBox(features, zoom, dragState) {
93
+ _SelectionUIRenderer_strokeRenderer = new WeakMap(), _SelectionUIRenderer_extensions = new WeakMap(), _SelectionUIRenderer_terrain = new WeakMap(), _SelectionUIRenderer_config = new WeakMap(), _SelectionUIRenderer_transform = new WeakMap(), _SelectionUIRenderer_projectionData = new WeakMap(), _SelectionUIRenderer_instances = new WeakSet(), _SelectionUIRenderer_drawRotatingBoundingBox = function _SelectionUIRenderer_drawRotatingBoundingBox(features, zoom, dragState) {
84
94
  // When dragState.operation === 'rotate', rotateInfo always exists
85
95
  const rotateInfo = dragState.rotateInfo;
86
96
  if (!rotateInfo)
@@ -124,17 +134,16 @@ _SelectionUIRenderer_strokeRenderer = new WeakMap(), _SelectionUIRenderer_extens
124
134
  if (!bbox)
125
135
  continue;
126
136
  // A single-coordinate feature (such as Point) is computed from the extent of the
127
- // point frame + the margin. The coordinate used is the center of the bbox (features
128
- // other than Point can be zero-area too, e.g. a MultiPoint with a single point, so
129
- // coordinates must not be treated as a Coordinate)
137
+ // point frame + the margin, as its own frame is (#pointFrame). The coordinate used is
138
+ // the center of the bbox (features other than Point can be zero-area too, e.g. a
139
+ // MultiPoint with a single point, so coordinates must not be treated as a Coordinate)
130
140
  if (hasZeroArea(bbox)) {
131
- const center = transform.project(bbox.center);
132
- for (const corner of __classPrivateFieldGet(this, _SelectionUIRenderer_extensions, "f").resolvePointFrameCorners(feature, center, margin)) {
133
- const at = transform.unproject(corner);
134
- minLng = Math.min(minLng, at.lng);
135
- maxLng = Math.max(maxLng, at.lng);
136
- minLat = Math.min(minLat, at.lat);
137
- maxLat = Math.max(maxLat, at.lat);
141
+ const frame = __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_pointFrame).call(this, feature, bbox.center, zoom);
142
+ for (const [lng, lat] of frame?.coords ?? []) {
143
+ minLng = Math.min(minLng, lng);
144
+ maxLng = Math.max(maxLng, lng);
145
+ minLat = Math.min(minLat, lat);
146
+ maxLat = Math.max(maxLat, lat);
138
147
  }
139
148
  continue;
140
149
  }
@@ -168,19 +177,37 @@ _SelectionUIRenderer_strokeRenderer = new WeakMap(), _SelectionUIRenderer_extens
168
177
  return;
169
178
  __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_strokeBoundingBox).call(this, __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_applyMargin).call(this, bbox, zoom), zoom);
170
179
  }, _SelectionUIRenderer_drawPointBoundingBox = function _SelectionUIRenderer_drawPointBoundingBox(feature, coord, zoom) {
171
- if (!__classPrivateFieldGet(this, _SelectionUIRenderer_transform, "f"))
180
+ const frame = __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_pointFrame).call(this, feature, coord, zoom);
181
+ if (!frame)
172
182
  return;
183
+ __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_strokePath).call(this, frame.coords, zoom, {
184
+ elevationMeters: frame.elevationMeters,
185
+ followTerrain: false,
186
+ });
187
+ }, _SelectionUIRenderer_pointFrame = function _SelectionUIRenderer_pointFrame(feature, coord, zoom) {
173
188
  const transform = __classPrivateFieldGet(this, _SelectionUIRenderer_transform, "f");
174
- const center = transform.project(coord);
189
+ if (!transform)
190
+ return null;
191
+ const terrain = __classPrivateFieldGet(this, _SelectionUIRenderer_terrain, "f");
192
+ const elevationMeters = anchorElevationMeters(terrain, coord[0], coord[1]);
193
+ const anchored = isAnchorActive(terrain);
194
+ const project = (at) => anchored ? projectAnchorAt(terrain, at[0], at[1], elevationMeters) : transform.project(at);
195
+ const center = project(coord);
196
+ if (!center)
197
+ return null;
175
198
  const margin = __classPrivateFieldGet(this, _SelectionUIRenderer_config, "f").boundingBox.margin;
176
- const coords = __classPrivateFieldGet(this, _SelectionUIRenderer_extensions, "f")
177
- .resolvePointFrameCorners(feature, center, margin)
178
- .map((corner) => {
199
+ const corners = __classPrivateFieldGet(this, _SelectionUIRenderer_extensions, "f").resolvePointFrameCorners(feature, center, margin);
200
+ const toPosition = anchored ? screenToPlane(project, coord, center, zoom) : null;
201
+ const coords = corners.map((corner) => {
202
+ const position = toPosition?.(corner);
203
+ if (position)
204
+ return position;
205
+ // No terrain, or the plane seen edge on: the corner is cast onto the map
179
206
  const at = transform.unproject(corner);
180
207
  return [at.lng, at.lat];
181
208
  });
182
209
  coords.push(coords[0]);
183
- __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_strokePath).call(this, coords, zoom);
210
+ return { coords, elevationMeters };
184
211
  }, _SelectionUIRenderer_applyMargin = function _SelectionUIRenderer_applyMargin(bbox, zoom) {
185
212
  if (!__classPrivateFieldGet(this, _SelectionUIRenderer_transform, "f"))
186
213
  return bbox;
@@ -192,13 +219,49 @@ _SelectionUIRenderer_strokeRenderer = new WeakMap(), _SelectionUIRenderer_extens
192
219
  }, _SelectionUIRenderer_strokeBoundingBox = function _SelectionUIRenderer_strokeBoundingBox(bbox, zoom) {
193
220
  const corners = [bbox.topLeft, bbox.topRight, bbox.bottomRight, bbox.bottomLeft];
194
221
  __classPrivateFieldGet(this, _SelectionUIRenderer_instances, "m", _SelectionUIRenderer_strokePath).call(this, [...corners, bbox.topLeft], zoom);
195
- }, _SelectionUIRenderer_strokePath = function _SelectionUIRenderer_strokePath(coords, zoom) {
222
+ }, _SelectionUIRenderer_strokePath = function _SelectionUIRenderer_strokePath(coords, zoom, placement = { followTerrain: true }) {
196
223
  __classPrivateFieldGet(this, _SelectionUIRenderer_strokeRenderer, "f").draw(coords, __classPrivateFieldGet(this, _SelectionUIRenderer_config, "f").boundingBox.stroke, {
197
224
  widthUnit: 'pixels',
198
225
  closed: true,
199
- // The frame follows the ground. The elevation comes from the same function as the
200
- // handles, so the corners of the frame always match the corner handles and never
201
- // drift from the position that can be grabbed
202
- followTerrain: true,
226
+ followTerrain: placement.followTerrain,
227
+ ...(placement.elevationMeters !== undefined && {
228
+ elevationMeters: placement.elevationMeters,
229
+ }),
203
230
  }, zoom, __classPrivateFieldGet(this, _SelectionUIRenderer_projectionData, "f") ?? undefined);
204
231
  };
232
+ /**
233
+ * The inverse of a projection around a point, to the first order: a point of the screen near
234
+ * `center` to the position whose projection it is
235
+ *
236
+ * The scale of the screen along the longitude and the latitude is measured by projecting
237
+ * positions a few pixels to either side of `coord`.
238
+ *
239
+ * @returns null when the projection is degenerate there (seen edge on, or a probe behind the
240
+ * camera)
241
+ */
242
+ function screenToPlane(project, coord, center, zoom) {
243
+ const [lng, lat] = coord;
244
+ // Degrees of longitude per pixel at the zoom on a 512 px world, a few pixels' worth
245
+ const step = (360 / (512 * 2 ** zoom)) * POINT_FRAME_PROBE_PX;
246
+ const latStep = Math.min(step, Math.max(1e-9, (89.9 - Math.abs(lat)) / 2));
247
+ const east = project([lng + step, lat]);
248
+ const west = project([lng - step, lat]);
249
+ const north = project([lng, lat + latStep]);
250
+ const south = project([lng, lat - latStep]);
251
+ if (!east || !west || !north || !south)
252
+ return null;
253
+ // The screen per degree of longitude (a, c) and of latitude (b, d)
254
+ const a = (east.x - west.x) / (2 * step);
255
+ const c = (east.y - west.y) / (2 * step);
256
+ const b = (north.x - south.x) / (2 * latStep);
257
+ const d = (north.y - south.y) / (2 * latStep);
258
+ const det = a * d - b * c;
259
+ const scale = Math.max(Math.abs(a), Math.abs(b), Math.abs(c), Math.abs(d));
260
+ if (!Number.isFinite(det) || Math.abs(det) <= 1e-12 * scale * scale)
261
+ return null;
262
+ return (point) => {
263
+ const dx = point.x - center.x;
264
+ const dy = point.y - center.y;
265
+ return [lng + (d * dx - b * dy) / det, lat + (a * dy - c * dx) / det];
266
+ };
267
+ }
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2026 SAKAIDA Atsushi
2
2
  // SPDX-License-Identifier: AGPL-3.0-only
3
3
  import { THINNING_VIEWPORT_MARGIN_PX } from '../../shared/config/selection.js';
4
+ import { createCoordinateTransform } from '../../shared/math/index.js';
4
5
  import { supportsVertexEditing } from '../../shared/utils/coordinates.js';
5
6
  import { isGlobeProjection } from '../../shared/utils/map.js';
6
7
  import { isLocallyHidden } from '../../store/local-visibility.js';
@@ -25,14 +26,9 @@ isLocked = false) {
25
26
  if (pointInstanceRenderer) {
26
27
  pointInstanceRenderer.setProjectionData(projectionData);
27
28
  }
28
- // Set the coordinate transform
29
- const coordinateTransform = {
30
- project: (lngLat) => map.project([lngLat[0], lngLat[1]]),
31
- unproject: (point) => {
32
- const ll = map.unproject([point.x, point.y]);
33
- return { lng: ll.lng, lat: ll.lat };
34
- },
35
- };
29
+ // Set the coordinate transform: the projection the markers are drawn with and the hit
30
+ // testing uses (the anchor projection while the terrain is on, the map's otherwise)
31
+ const coordinateTransform = createCoordinateTransform(map);
36
32
  selectionUIRenderer.setTransform(coordinateTransform);
37
33
  selectionUIRenderer.setProjectionData(projectionData);
38
34
  selectionHandlesRenderer.setTransform(coordinateTransform);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakuzu/maplibre-gl-draw",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "WebGL2 drawing and editing engine for MapLibre GL JS: points, lines, and polygons rendered in a custom layer, with selection, hit testing, and geometry utilities.",
5
5
  "keywords": [
6
6
  "maplibre",