@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.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/README.md +59 -2
- package/dist/core.js +36923 -0
- package/dist/core.js.map +6 -0
- package/dist/index.js +32 -0
- package/dist/loaders-gl-worker.js +82 -0
- package/dist/loaders-gl-worker.js.map +6 -0
- package/dist/map-logic.js +2 -0
- package/dist/map-logic.js.map +6 -0
- package/dist/projected-basemaps-worker.js +3 -0
- package/dist/projected-basemaps-worker.js.map +6 -0
- package/dist/shader-modules.js +141 -0
- package/dist/shader-modules.js.map +6 -0
- package/dist/style-schema.js +126 -0
- package/dist/style-schema.js.map +6 -0
- package/dist/tangram-style.schema.json +882 -0
- package/dist/tangram.debug.mjs +37139 -0
- package/dist/tangram.debug.mjs.map +6 -0
- package/dist/tangram.min.mjs +4950 -0
- package/dist/types/builders/earcut.d.ts +15 -0
- package/dist/types/core.d.ts +29 -0
- package/dist/types/gl/constants.d.ts +2 -0
- package/dist/types/gl/globe_mesh.d.ts +48 -0
- package/dist/types/gl/glsl.d.ts +17 -0
- package/dist/types/gl/vertex-constants.d.ts +4 -0
- package/dist/types/gl/vertex-types.d.ts +34 -0
- package/dist/types/gl/vertex_data.d.ts +32 -0
- package/dist/types/gl/vertex_elements.d.ts +8 -0
- package/dist/types/gl/vertex_layout.d.ts +32 -0
- package/dist/types/glsl.d.ts +14 -0
- package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
- package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
- package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/lights/light-definitions.d.ts +121 -0
- package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
- package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
- package/dist/types/map-logic/globe-terrain.d.ts +54 -0
- package/dist/types/map-logic/index.d.ts +33 -0
- package/dist/types/map-logic/label-identity.d.ts +22 -0
- package/dist/types/map-logic/label-placement.d.ts +32 -0
- package/dist/types/map-logic/screen-bounds.d.ts +15 -0
- package/dist/types/map-logic/terrain-data.d.ts +55 -0
- package/dist/types/map-logic/terrain-error.d.ts +51 -0
- package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
- package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
- package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
- package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
- package/dist/types/map-logic/terrain-query.d.ts +33 -0
- package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
- package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
- package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
- package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
- package/dist/types/map-logic/tile-residency.d.ts +17 -0
- package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
- package/dist/types/procedures/js-yaml.d.ts +15 -0
- package/dist/types/procedures/mesh-projector.d.ts +107 -0
- package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
- package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
- package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
- package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
- package/dist/types/procedures/terrain-draping.d.ts +165 -0
- package/dist/types/procedures/terrain-globe.d.ts +6 -0
- package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
- package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
- package/dist/types/procedures/web-mercator-math.d.ts +4 -0
- package/dist/types/scene/globals.d.ts +11 -0
- package/dist/types/scene/ground_footprint.d.ts +16 -0
- package/dist/types/scene/host_frame.d.ts +54 -0
- package/dist/types/scene/projection_math.d.ts +52 -0
- package/dist/types/scene/scene-resource-types.d.ts +177 -0
- package/dist/types/scene/scene_bundle.d.ts +69 -0
- package/dist/types/scene/visibility_adapter.d.ts +106 -0
- package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
- package/dist/types/selection/terrain_surface.d.ts +149 -0
- package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
- package/dist/types/shader-modules/height-decode.d.ts +13 -0
- package/dist/types/shader-modules/hillshade.d.ts +13 -0
- package/dist/types/shader-modules/index.d.ts +6 -0
- package/dist/types/shader-modules/planar.d.ts +12 -0
- package/dist/types/shader-modules/sphere-map.d.ts +13 -0
- package/dist/types/shader-modules/triplanar.d.ts +15 -0
- package/dist/types/sources/decoded_tile_store.d.ts +95 -0
- package/dist/types/sources/tile_source_metadata.d.ts +32 -0
- package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
- package/dist/types/tile/tile_id.d.ts +37 -0
- package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
- package/dist/types/types.d.ts +293 -0
- package/dist/types/utils/geo.d.ts +57 -0
- package/dist/types/utils/hash.d.ts +1 -0
- package/dist/types/utils/log.d.ts +27 -0
- package/dist/types/utils/props.d.ts +6 -0
- package/dist/types/utils/subscribe.d.ts +10 -0
- package/dist/types/utils/thread.d.ts +6 -0
- package/dist/types/utils/urls.d.ts +26 -0
- package/dist/types/utils/utils.d.ts +26 -0
- package/dist/types/utils/version.d.ts +2 -0
- package/dist/types/utils/worker-types.d.ts +68 -0
- package/dist/types/utils/worker_broker.d.ts +3 -0
- package/package.json +103 -3
- package/src/builders/common.ts +41 -0
- package/src/builders/earcut.d.ts +15 -0
- package/src/builders/geometry-types.ts +60 -0
- package/src/builders/points.ts +115 -0
- package/src/builders/polygons.ts +225 -0
- package/src/builders/polylines.ts +682 -0
- package/src/builders/wireframe.ts +31 -0
- package/src/core.d.ts +29 -0
- package/src/core.ts +33 -0
- package/src/experimental/loaders-gl-worker.ts +19 -0
- package/src/experimental/projected-basemaps-worker.ts +13 -0
- package/src/experimental/projected-mesh-cache.ts +137 -0
- package/src/experimental/projected-mesh-domain.ts +110 -0
- package/src/experimental/projected-mesh-refinement.ts +193 -0
- package/src/experimental/projected-mesh.ts +399 -0
- package/src/experimental/projected-normal.ts +26 -0
- package/src/experimental/projected-symbol-mesh.ts +74 -0
- package/src/gl/constants.ts +18 -0
- package/src/gl/context.ts +103 -0
- package/src/gl/extensions.ts +30 -0
- package/src/gl/globe_mesh.ts +285 -0
- package/src/gl/glsl.ts +257 -0
- package/src/gl/mesh-types.ts +56 -0
- package/src/gl/render_state.ts +111 -0
- package/src/gl/shader_program.ts +1310 -0
- package/src/gl/texture.ts +814 -0
- package/src/gl/uniform_buffer.ts +340 -0
- package/src/gl/vao.ts +154 -0
- package/src/gl/vbo_mesh.ts +349 -0
- package/src/gl/vertex-constants.ts +19 -0
- package/src/gl/vertex-types.ts +35 -0
- package/src/gl/vertex_data.ts +126 -0
- package/src/gl/vertex_elements.ts +54 -0
- package/src/gl/vertex_layout.ts +241 -0
- package/src/glsl.d.ts +14 -0
- package/src/gpu/luma_device_renderer.ts +579 -0
- package/src/gpu/pixel_readback.ts +51 -0
- package/src/gpu/resource_lifecycle.ts +32 -0
- package/src/gpu/tangram_gpu_backend.ts +195 -0
- package/src/index.d.ts +155 -0
- package/src/index.ts +105 -0
- package/src/labels/collision-types.ts +94 -0
- package/src/labels/collision.ts +273 -0
- package/src/labels/collision_grid.ts +61 -0
- package/src/labels/intersect.ts +30 -0
- package/src/labels/label-types.ts +91 -0
- package/src/labels/label.ts +207 -0
- package/src/labels/label_line.ts +801 -0
- package/src/labels/label_point.ts +131 -0
- package/src/labels/main-pass-types.ts +73 -0
- package/src/labels/main_pass.ts +221 -0
- package/src/labels/point_anchor.ts +100 -0
- package/src/labels/point_placement.ts +150 -0
- package/src/labels/projected-pass.ts +259 -0
- package/src/labels/repeat_group.ts +72 -0
- package/src/lights/ambient_light.glsl +18 -0
- package/src/lights/directional_light.glsl +39 -0
- package/src/lights/geographic-lights.ts +21 -0
- package/src/lights/light-definitions.ts +285 -0
- package/src/lights/light.ts +590 -0
- package/src/lights/lighting-uniforms.ts +80 -0
- package/src/lights/lighting-wgsl.ts +116 -0
- package/src/lights/material.glsl +237 -0
- package/src/lights/material.ts +145 -0
- package/src/lights/native-falloff.ts +37 -0
- package/src/lights/point_light.glsl +96 -0
- package/src/lights/spot_light.glsl +111 -0
- package/src/map-logic/draped-terrain-bounds.ts +71 -0
- package/src/map-logic/globe-terrain-preparation.ts +217 -0
- package/src/map-logic/globe-terrain.ts +296 -0
- package/src/map-logic/index.ts +37 -0
- package/src/map-logic/label-identity.ts +36 -0
- package/src/map-logic/label-placement.ts +117 -0
- package/src/map-logic/screen-bounds.ts +34 -0
- package/src/map-logic/terrain-data.ts +107 -0
- package/src/map-logic/terrain-error.ts +134 -0
- package/src/map-logic/terrain-ground-topology.ts +164 -0
- package/src/map-logic/terrain-height-field.ts +254 -0
- package/src/map-logic/terrain-navigation.ts +50 -0
- package/src/map-logic/terrain-neighborhood.ts +43 -0
- package/src/map-logic/terrain-query.ts +51 -0
- package/src/map-logic/terrain-resource-budget.ts +124 -0
- package/src/map-logic/terrain-transition-topology.ts +164 -0
- package/src/map-logic/tile-build-queue.ts +110 -0
- package/src/map-logic/tile-cache-policy.ts +89 -0
- package/src/map-logic/tile-residency.ts +50 -0
- package/src/map-logic/triangle-query-index.ts +265 -0
- package/src/procedures/js-yaml.d.ts +15 -0
- package/src/procedures/mesh-projector.ts +191 -0
- package/src/procedures/mlt-loaders.ts +55 -0
- package/src/procedures/mvt-dependencies.d.ts +20 -0
- package/src/procedures/mvt-legacy.ts +150 -0
- package/src/procedures/mvt-loaders.ts +53 -0
- package/src/procedures/mvt-parser.ts +48 -0
- package/src/procedures/mvt-properties.ts +36 -0
- package/src/procedures/mvt-tile-provider.ts +72 -0
- package/src/procedures/pmtiles-loader.ts +75 -0
- package/src/procedures/projected-coordinate-transform.ts +79 -0
- package/src/procedures/projection-batch-executor.ts +140 -0
- package/src/procedures/scene-yaml-legacy.ts +16 -0
- package/src/procedures/scene-yaml-loaders.ts +10 -0
- package/src/procedures/terrain-draping.ts +713 -0
- package/src/procedures/terrain-globe.ts +32 -0
- package/src/procedures/terrain-styled-topology.ts +184 -0
- package/src/procedures/web-mercator-legacy.ts +24 -0
- package/src/procedures/web-mercator-math.ts +50 -0
- package/src/scene/camera.ts +298 -0
- package/src/scene/camera_base.ts +116 -0
- package/src/scene/classic_scene.ts +20 -0
- package/src/scene/external_camera.ts +101 -0
- package/src/scene/globals.ts +120 -0
- package/src/scene/ground_footprint.ts +139 -0
- package/src/scene/host_frame.ts +326 -0
- package/src/scene/projected_tile_lod.ts +125 -0
- package/src/scene/projection_math.ts +142 -0
- package/src/scene/projection_shaders.ts +183 -0
- package/src/scene/renderer.ts +516 -0
- package/src/scene/scene-resource-types.ts +174 -0
- package/src/scene/scene.ts +2378 -0
- package/src/scene/scene_bundle.ts +304 -0
- package/src/scene/scene_debug.ts +129 -0
- package/src/scene/scene_loader.ts +402 -0
- package/src/scene/scene_worker.ts +528 -0
- package/src/scene/view.ts +683 -0
- package/src/scene/visibility_adapter.ts +319 -0
- package/src/scene/worker_url.ts +15 -0
- package/src/selection/draped-terrain-surface.ts +377 -0
- package/src/selection/selection-types.ts +79 -0
- package/src/selection/selection.ts +542 -0
- package/src/selection/selection_fragment.glsl +18 -0
- package/src/selection/selection_globals.glsl +9 -0
- package/src/selection/selection_pixels.ts +59 -0
- package/src/selection/selection_vertex.glsl +15 -0
- package/src/selection/terrain_surface.ts +298 -0
- package/src/shader-modules/globe-horizon.ts +52 -0
- package/src/shader-modules/height-decode.ts +41 -0
- package/src/shader-modules/hillshade.ts +70 -0
- package/src/shader-modules/index.ts +10 -0
- package/src/shader-modules/planar.ts +24 -0
- package/src/shader-modules/sphere-map.ts +38 -0
- package/src/shader-modules/triplanar.ts +45 -0
- package/src/sources/aligned_tile_source.ts +122 -0
- package/src/sources/data_source.ts +655 -0
- package/src/sources/decoded_tile_store.ts +245 -0
- package/src/sources/geojson.ts +300 -0
- package/src/sources/mvt.ts +247 -0
- package/src/sources/raster.ts +341 -0
- package/src/sources/shared_tile_source_adapter.ts +117 -0
- package/src/sources/sources.ts +10 -0
- package/src/sources/tile_source_adapter.ts +84 -0
- package/src/sources/tile_source_metadata.ts +65 -0
- package/src/sources/tile_source_state.ts +112 -0
- package/src/sources/topojson.ts +90 -0
- package/src/styles/feature_annotations.ts +16 -0
- package/src/styles/filter.ts +190 -0
- package/src/styles/geometry-style-types.ts +209 -0
- package/src/styles/globe_visibility_wgsl.ts +22 -0
- package/src/styles/layer-types.ts +45 -0
- package/src/styles/layer.ts +586 -0
- package/src/styles/lines/dasharray.ts +49 -0
- package/src/styles/lines/lines.ts +707 -0
- package/src/styles/lines/lines_wgsl.ts +224 -0
- package/src/styles/points/point-runtime-types.ts +125 -0
- package/src/styles/points/points.ts +1079 -0
- package/src/styles/points/points_fragment.glsl +126 -0
- package/src/styles/points/points_vertex.glsl +230 -0
- package/src/styles/points/points_wgsl.ts +189 -0
- package/src/styles/polygons/polygons.ts +268 -0
- package/src/styles/polygons/polygons_fragment.glsl +173 -0
- package/src/styles/polygons/polygons_vertex.glsl +213 -0
- package/src/styles/polygons/polygons_wgsl.ts +127 -0
- package/src/styles/property-types.ts +125 -0
- package/src/styles/raster/raster.ts +33 -0
- package/src/styles/raster/raster_globals.glsl +46 -0
- package/src/styles/style-mixing-types.ts +101 -0
- package/src/styles/style-runtime-types.ts +220 -0
- package/src/styles/style-schema.ts +133 -0
- package/src/styles/style.ts +1033 -0
- package/src/styles/style_globals.glsl +69 -0
- package/src/styles/style_manager.ts +464 -0
- package/src/styles/style_parser.ts +620 -0
- package/src/styles/text/font_manager.ts +180 -0
- package/src/styles/text/fontfaceobserver.d.ts +10 -0
- package/src/styles/text/text-types.ts +220 -0
- package/src/styles/text/text.ts +378 -0
- package/src/styles/text/text_canvas.ts +773 -0
- package/src/styles/text/text_labels.ts +436 -0
- package/src/styles/text/text_segments.ts +142 -0
- package/src/styles/text/text_settings.ts +228 -0
- package/src/styles/text/text_wgsl.ts +159 -0
- package/src/styles/text/text_wrap.ts +194 -0
- package/src/tile/globe_tile_preload.ts +40 -0
- package/src/tile/tangram_tileset_2d.ts +108 -0
- package/src/tile/tile-build-types.ts +155 -0
- package/src/tile/tile.ts +807 -0
- package/src/tile/tile_id.ts +172 -0
- package/src/tile/tile_manager.ts +889 -0
- package/src/tile/tile_pyramid.ts +119 -0
- package/src/tile/tile_resource_cache.ts +91 -0
- package/src/tile/tile_traversal_adapter.ts +132 -0
- package/src/types.ts +332 -0
- package/src/utils/debounce.ts +17 -0
- package/src/utils/debug_settings.ts +43 -0
- package/src/utils/errors.ts +12 -0
- package/src/utils/functions.ts +95 -0
- package/src/utils/geo.ts +364 -0
- package/src/utils/hash.ts +14 -0
- package/src/utils/log.ts +108 -0
- package/src/utils/media_capture.ts +193 -0
- package/src/utils/merge.ts +44 -0
- package/src/utils/obb.ts +134 -0
- package/src/utils/props.ts +43 -0
- package/src/utils/slice.ts +14 -0
- package/src/utils/subscribe.ts +58 -0
- package/src/utils/task.ts +154 -0
- package/src/utils/thread.ts +26 -0
- package/src/utils/urls.ts +223 -0
- package/src/utils/utils.ts +310 -0
- package/src/utils/vector.ts +197 -0
- package/src/utils/version.ts +8 -0
- package/src/utils/worker-types.ts +79 -0
- package/src/utils/worker_broker.ts +523 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
import Label from './label';
|
|
7
|
+
import type {CollisionBounds, CollisionLabel} from './collision-types';
|
|
8
|
+
import type {LabelLayout, LabelParent} from './label-types';
|
|
9
|
+
import PointAnchor from './point_anchor';
|
|
10
|
+
import OBB from '../utils/obb';
|
|
11
|
+
import StyleParser from '../styles/style_parser';
|
|
12
|
+
|
|
13
|
+
type LabelPointCoordinate = [number, number];
|
|
14
|
+
type LabelPointSize = [number, number];
|
|
15
|
+
type LabelPointLayout = LabelLayout;
|
|
16
|
+
const typedStyleParser = StyleParser as unknown as {zeroPair: readonly number[]};
|
|
17
|
+
|
|
18
|
+
export default class LabelPoint extends Label {
|
|
19
|
+
/** Discriminator used by the checked point/text quad builders. */
|
|
20
|
+
declare type: 'point';
|
|
21
|
+
|
|
22
|
+
position: LabelPointCoordinate;
|
|
23
|
+
angle: number;
|
|
24
|
+
parent: LabelParent | undefined;
|
|
25
|
+
start_anchor_index: number;
|
|
26
|
+
degenerate: boolean;
|
|
27
|
+
throw_away: boolean;
|
|
28
|
+
static PLACEMENT: Record<string, number>;
|
|
29
|
+
|
|
30
|
+
constructor (position: LabelPointCoordinate, size: LabelPointSize, layout: LabelPointLayout, angle = 0) {
|
|
31
|
+
super(size, layout);
|
|
32
|
+
this.type = 'point';
|
|
33
|
+
this.position = [position[0], position[1]];
|
|
34
|
+
this.angle = angle;
|
|
35
|
+
this.parent = this.layout.parent;
|
|
36
|
+
this.update();
|
|
37
|
+
|
|
38
|
+
this.start_anchor_index = 1;
|
|
39
|
+
this.degenerate = !this.size[0] && !this.size[1] && !this.layout.buffer[0] && !this.layout.buffer[1];
|
|
40
|
+
this.throw_away = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
update() {
|
|
44
|
+
super.update();
|
|
45
|
+
this.computeOffset();
|
|
46
|
+
this.updateBBoxes();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
computeOffset () {
|
|
50
|
+
this.offset = [this.layout.offset[0], this.layout.offset[1]];
|
|
51
|
+
|
|
52
|
+
// Additional anchor/offset for point:
|
|
53
|
+
if (this.parent) {
|
|
54
|
+
let parent = this.parent;
|
|
55
|
+
// point's own anchor, text anchor applied to point, additional point offset
|
|
56
|
+
this.offset = PointAnchor.computeOffset(this.offset, parent.size, parent.anchor, PointAnchor.zero_buffer);
|
|
57
|
+
this.offset = PointAnchor.computeOffset(this.offset, parent.size, this.anchor, PointAnchor.zero_buffer);
|
|
58
|
+
if (parent.offset !== typedStyleParser.zeroPair) { // point has an offset
|
|
59
|
+
if (this.offset === typedStyleParser.zeroPair) { // no text offset, use point's
|
|
60
|
+
this.offset = parent.offset;
|
|
61
|
+
}
|
|
62
|
+
else { // text has offset, add point's
|
|
63
|
+
this.offset[0] += parent.offset[0];
|
|
64
|
+
this.offset[1] += parent.offset[1];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.offset = PointAnchor.computeOffset(this.offset, this.size, this.anchor);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
updateBBoxes (this: Pick<LabelPoint, 'size' | 'unit_scale' | 'position' | 'offset' | 'angle' | 'obb' | 'aabb' | 'breach' | 'may_repeat_across_tiles'> & {layout: Pick<LabelLayout, 'buffer' | 'italic'>} & Partial<Pick<LabelPoint, 'inTileBounds' | 'mayRepeatAcrossTiles'>>) {
|
|
73
|
+
let width = (this.size[0] + this.layout.buffer[0] * 2) * this.unit_scale * Label.epsilon;
|
|
74
|
+
let height = (this.size[1] + this.layout.buffer[1] * 2) * this.unit_scale * Label.epsilon;
|
|
75
|
+
|
|
76
|
+
// fudge width value as text may overflow bounding box if it has italic, bold, etc style
|
|
77
|
+
if (this.layout.italic) {
|
|
78
|
+
width += 5 * this.unit_scale;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// make bounding boxes
|
|
82
|
+
this.obb = new OBB(
|
|
83
|
+
this.position[0] + (this.offset[0] * this.unit_scale),
|
|
84
|
+
this.position[1] - (this.offset[1] * this.unit_scale),
|
|
85
|
+
-this.angle, // angle is negative because tile system y axis is pointing down
|
|
86
|
+
width,
|
|
87
|
+
height
|
|
88
|
+
);
|
|
89
|
+
this.aabb = this.obb.getExtent();
|
|
90
|
+
|
|
91
|
+
if (this.inTileBounds) {
|
|
92
|
+
this.breach = !this.inTileBounds();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.mayRepeatAcrossTiles) {
|
|
96
|
+
this.may_repeat_across_tiles = this.mayRepeatAcrossTiles();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
discard (bboxes: CollisionBounds, exclude: CollisionLabel | null | 0 | '' = null) {
|
|
101
|
+
if (this.degenerate) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (super.discard(bboxes, exclude)) {
|
|
106
|
+
// If more than one anchor specified, try them in order
|
|
107
|
+
if (Array.isArray(this.layout.anchor)) {
|
|
108
|
+
// Start on second anchor (first anchor was set on creation)
|
|
109
|
+
for (let i=this.start_anchor_index; i < this.layout.anchor.length; i++) {
|
|
110
|
+
this.anchor = this.layout.anchor[i];
|
|
111
|
+
this.update();
|
|
112
|
+
|
|
113
|
+
if (!super.discard(bboxes, exclude)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Placement strategies
|
|
126
|
+
LabelPoint.PLACEMENT = {
|
|
127
|
+
VERTEX: 0, // place labels at endpoints of line segments
|
|
128
|
+
MIDPOINT: 1, // place labels at midpoints of line segments
|
|
129
|
+
SPACED: 2, // place labels equally spaced along line
|
|
130
|
+
CENTROID: 3 // place labels at center of polygons
|
|
131
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {CollisionLabel, CollisionObject} from './collision-types';
|
|
6
|
+
import type {LabelPointCoordinate, SerializedLabel} from './label-types';
|
|
7
|
+
import type OBB from '../utils/obb';
|
|
8
|
+
|
|
9
|
+
/** Serialized label container attached to a worker-built mesh. */
|
|
10
|
+
export interface MeshLabel {
|
|
11
|
+
container: {label: SerializedLabel; linked?: number | string | null};
|
|
12
|
+
/** Byte offset and vertex count for each label geometry range. */
|
|
13
|
+
ranges: [number, number][];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Mutable GPU-mesh surface consumed by the visibility pass. */
|
|
17
|
+
export interface LabelMesh {
|
|
18
|
+
/** First terrain draw timestamp, shared with color/selection morphing. */
|
|
19
|
+
terrainVisibleAt?: number;
|
|
20
|
+
/** Last submitted height blend, for non-terrain callers and diagnostics. */
|
|
21
|
+
terrainBlend?: number;
|
|
22
|
+
/** Captured worker generation's duration; a pending field must not retime admitted labels. */
|
|
23
|
+
variant?: {terrainTransitionDuration?: number};
|
|
24
|
+
valid: boolean;
|
|
25
|
+
labels?: Record<string, MeshLabel>;
|
|
26
|
+
vertex_layout: {offset?: Record<string, number>; stride: number};
|
|
27
|
+
/** CPU bytes are absent after an unretained mesh upload or destruction. */
|
|
28
|
+
vertex_data?: import('../gl/mesh-types').MeshVertexData;
|
|
29
|
+
upload(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Current/proxy tile metadata needed for cross-tile collisions. */
|
|
33
|
+
export interface LabelTile {
|
|
34
|
+
coords: {z: number};
|
|
35
|
+
style_z: number;
|
|
36
|
+
build_id: number;
|
|
37
|
+
min: {x: number; y: number};
|
|
38
|
+
span: {x: number};
|
|
39
|
+
meshes: Record<string, LabelMesh[]>;
|
|
40
|
+
pending_label_meshes: Record<string, LabelMesh[]> | null;
|
|
41
|
+
isProxy(): boolean;
|
|
42
|
+
/** Whether a proxy style still draws while a child finishes its label meshes. */
|
|
43
|
+
shouldProxyForStyle?(style: string): boolean;
|
|
44
|
+
swapPendingLabels(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Main-pass reconstruction: screen-scaled boxes without alternate-anchor fitting. */
|
|
48
|
+
export interface MainPassLabel extends CollisionLabel {
|
|
49
|
+
id: number;
|
|
50
|
+
type: string;
|
|
51
|
+
layout: SerializedLabel['layout'] & {repeat_scale: number};
|
|
52
|
+
build_id: number;
|
|
53
|
+
unit_scale: number;
|
|
54
|
+
size: LabelPointCoordinate;
|
|
55
|
+
offset: LabelPointCoordinate;
|
|
56
|
+
angle: number;
|
|
57
|
+
obb: OBB | null;
|
|
58
|
+
aabb: number[] | null;
|
|
59
|
+
obbs: OBB[];
|
|
60
|
+
aabbs: number[][];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolved cross-tile candidate, including its mesh update ranges. */
|
|
64
|
+
export interface MainPassContainer extends CollisionObject {
|
|
65
|
+
label: MainPassLabel;
|
|
66
|
+
ranges: [number, number][];
|
|
67
|
+
mesh: LabelMesh;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Temporary worker ID link, resolved before collision submission. */
|
|
71
|
+
export type UnresolvedMainPassContainer = Omit<MainPassContainer, 'linked'> & {
|
|
72
|
+
linked?: number | string | CollisionObject | null;
|
|
73
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
import type {CollisionBounds, CollisionLabel} from './collision-types';
|
|
7
|
+
import type {LabelTile, LabelMesh, MainPassLabel, MainPassContainer, UnresolvedMainPassContainer} from './main-pass-types';
|
|
8
|
+
import Label from './label';
|
|
9
|
+
import LabelPoint from './label_point';
|
|
10
|
+
import {LabelLineStraight} from './label_line';
|
|
11
|
+
import Collision from './collision';
|
|
12
|
+
import OBB from '../utils/obb';
|
|
13
|
+
import Geo from '../utils/geo';
|
|
14
|
+
|
|
15
|
+
let visible: Record<string, boolean> = {}; // currently visible labels
|
|
16
|
+
let prev_visible: Record<string, boolean> = {}; // previously visible labels (in last collision run)
|
|
17
|
+
|
|
18
|
+
export default async function mainThreadLabelCollisionPass (tiles: LabelTile[], view_zoom: number, hide_breach = false): Promise<{labels: MainPassContainer[]; containers: MainPassContainer[]}> {
|
|
19
|
+
// Swap/reset visible label set
|
|
20
|
+
prev_visible = visible; // save last visible label set
|
|
21
|
+
visible = {}; // initialize new visible label set
|
|
22
|
+
|
|
23
|
+
// Build label containers from tiles
|
|
24
|
+
let containers = buildLabels(tiles, view_zoom);
|
|
25
|
+
|
|
26
|
+
// Collide all labels in a single group
|
|
27
|
+
// TODO: maybe rename tile and style to group/subgroup?
|
|
28
|
+
Collision.startTile('main', { apply_repeat_groups: true, return_hidden: true });
|
|
29
|
+
Collision.addStyle('main', 'main');
|
|
30
|
+
|
|
31
|
+
// Adaptive collision grid, using a heuristic based on the tile with the most labels
|
|
32
|
+
const max_tile_label_count =
|
|
33
|
+
Math.max(0, ...Object.values(tiles)
|
|
34
|
+
.map(t => Object.values(t.meshes))
|
|
35
|
+
.flat()
|
|
36
|
+
.map(meshes => Math.max(0, ...meshes.map(mesh => mesh.labels ? Object.keys(mesh.labels).length : 0)))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const grid_divs = Math.floor(max_tile_label_count / Geo.tile_size); // heuristic of label density to tile size
|
|
40
|
+
if (grid_divs > 0) {
|
|
41
|
+
Collision.initGrid({
|
|
42
|
+
anchor: { x: Math.min(...tiles.map(t => t.min.x)), y: Math.min(...tiles.map(t => t.min.y)) },
|
|
43
|
+
span: tiles[0].span.x / grid_divs
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
Collision.initGrid();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const labels = await Collision.collide(containers, 'main', 'main');
|
|
51
|
+
|
|
52
|
+
// Update label visiblity
|
|
53
|
+
let meshes: LabelMesh[] = [];
|
|
54
|
+
labels.forEach(container => {
|
|
55
|
+
// Hide breach labels (those that cross tile boundaries) while tiles are loading, unless they
|
|
56
|
+
// were previously visible (otherwise fully loaded/collided breach labels will flicker in and out
|
|
57
|
+
// when new tiles load, even if they aren't adjacent)
|
|
58
|
+
let show = 0;
|
|
59
|
+
if (container.show === true &&
|
|
60
|
+
(!hide_breach || !container.label.breach || prev_visible[container.label.id])) {
|
|
61
|
+
show = 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (show) {
|
|
65
|
+
visible[container.label.id] = true; // track visible labels
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let changed = true; // check if label visibility changed on this collision pass
|
|
69
|
+
|
|
70
|
+
container.ranges.forEach(r => {
|
|
71
|
+
if (!changed) {
|
|
72
|
+
return; // skip rest of label if state hasn't changed
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let mesh = container.mesh;
|
|
76
|
+
if (!mesh.valid || !mesh.vertex_data || !mesh.vertex_layout.offset) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let off = mesh.vertex_layout.offset.a_shape; // byte offset (within each vertex) of attribute
|
|
81
|
+
let stride = mesh.vertex_layout.stride; // byte stride per vertex
|
|
82
|
+
|
|
83
|
+
for (let i=0; i < r[1]; i++) {
|
|
84
|
+
// NB: +6 is because attribute is a short int (2 bytes each), and we're skipping to 3rd element, 6=3*2
|
|
85
|
+
if (mesh.vertex_data[r[0] + i * stride + off + 6] === show) {
|
|
86
|
+
changed = false;
|
|
87
|
+
return; // label hasn't changed states, skip further updates
|
|
88
|
+
}
|
|
89
|
+
mesh.vertex_data[r[0] + i * stride + off + 6] = show;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (meshes.indexOf(mesh) === -1) {
|
|
93
|
+
meshes.push(mesh);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Upload updated meshes and make them visible
|
|
99
|
+
meshes.forEach(mesh => mesh.upload());
|
|
100
|
+
tiles.forEach(t => t.swapPendingLabels());
|
|
101
|
+
|
|
102
|
+
return { labels, containers }; // currently returned for debugging
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildLabels (tiles: LabelTile[], view_zoom: number): MainPassContainer[] {
|
|
106
|
+
const labels: Record<string, MainPassLabel> = {};
|
|
107
|
+
let containers: Record<string, UnresolvedMainPassContainer> | UnresolvedMainPassContainer[] = {};
|
|
108
|
+
|
|
109
|
+
// Collect labels from each tile and turn into new label instances
|
|
110
|
+
tiles.forEach(tile => {
|
|
111
|
+
const units_per_meter = Geo.unitsPerMeter(tile.coords.z); // scale from tile units to mercator meters
|
|
112
|
+
const zoom_scale = Math.pow(2, view_zoom - tile.style_z); // adjust label size by view zoom
|
|
113
|
+
const size_scale = units_per_meter * zoom_scale; // scale from tile units to zoom-adjusted meters
|
|
114
|
+
const meters_per_pixel = Geo.metersPerPixel(view_zoom);
|
|
115
|
+
|
|
116
|
+
// First pass: create label instances and centralize collision containers
|
|
117
|
+
// Combine existing (previously collided) and pending (waiting to be collided for first time) meshes
|
|
118
|
+
const tile_meshes = Object.assign({}, tile.meshes, tile.pending_label_meshes);
|
|
119
|
+
for (let style in tile_meshes) {
|
|
120
|
+
const meshes = tile_meshes[style];
|
|
121
|
+
meshes.forEach(mesh => {
|
|
122
|
+
if (mesh.labels) {
|
|
123
|
+
for (let label_id in mesh.labels) {
|
|
124
|
+
// For proxy tiles, only allow visible labels to be *hidden* by further collisions,
|
|
125
|
+
// don't allow new ones to appear. Promotes label stability and prevents thrash
|
|
126
|
+
// from different labels (often not thematically relevant given the different zoom level of
|
|
127
|
+
// the proxy tile content, e.g. random POIs popping in/out when zooming out to city-wide view).
|
|
128
|
+
if (tile.isProxy() && !prev_visible[label_id]) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const params = mesh.labels[label_id].container.label;
|
|
133
|
+
const linked = mesh.labels[label_id].container.linked;
|
|
134
|
+
const ranges = mesh.labels[label_id].ranges;
|
|
135
|
+
// const debug = Object.assign({}, mesh.labels[label_id].debug, { tile, params, label_id });
|
|
136
|
+
|
|
137
|
+
// The worker snapshot and bounds are installed below before collision reads this record.
|
|
138
|
+
let label = labels[label_id] = {} as MainPassLabel;
|
|
139
|
+
label.discard = discard.bind(label);
|
|
140
|
+
label.build_id = tile.build_id; // original order in which tiles were built
|
|
141
|
+
|
|
142
|
+
Object.assign(label, params);
|
|
143
|
+
label.layout = Object.assign({}, params.layout) as MainPassLabel['layout']; // repeat_scale is installed below
|
|
144
|
+
label.layout.repeat_scale = 0.75; // looser second pass on repeat groups, to weed out repeats near tile edges
|
|
145
|
+
label.layout.repeat_distance = label.layout.repeat_distance || 0;
|
|
146
|
+
label.layout.repeat_distance /= size_scale; // TODO: where should this be scaled?
|
|
147
|
+
label.position = [ // don't overwrite referenced values
|
|
148
|
+
label.position[0] / units_per_meter + tile.min.x,
|
|
149
|
+
label.position[1] / units_per_meter + tile.min.y
|
|
150
|
+
];
|
|
151
|
+
label.unit_scale = meters_per_pixel;
|
|
152
|
+
|
|
153
|
+
if (label.type === 'point') { // TODO: move to integer constants to avoid excess string copies
|
|
154
|
+
LabelPoint.prototype.updateBBoxes.call(label);
|
|
155
|
+
}
|
|
156
|
+
else if (label.type === 'straight') {
|
|
157
|
+
LabelLineStraight.prototype.updateBBoxes.call(label, label.position, label.size, label.angle, label.angle, label.offset);
|
|
158
|
+
}
|
|
159
|
+
else if (params.obbs) {
|
|
160
|
+
// NB: this is a very rough approximation of curved label collision at intermediate zooms,
|
|
161
|
+
// because the position/scale of each collision box isn't correctly updated; however,
|
|
162
|
+
// it's good enough to provide some additional label coverage, with less overhead
|
|
163
|
+
const obbs = params.obbs.map(o => {
|
|
164
|
+
let { x, y, a, w, h } = o;
|
|
165
|
+
x = x / units_per_meter + tile.min.x;
|
|
166
|
+
y = y / units_per_meter + tile.min.y;
|
|
167
|
+
w /= size_scale;
|
|
168
|
+
h /= size_scale;
|
|
169
|
+
return new OBB(x, y, a, w, h);
|
|
170
|
+
});
|
|
171
|
+
label.obbs = obbs;
|
|
172
|
+
label.aabbs = obbs.map(o => o.getExtent());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
(containers as Record<string, UnresolvedMainPassContainer>)[label_id] = {
|
|
176
|
+
label,
|
|
177
|
+
linked,
|
|
178
|
+
ranges,
|
|
179
|
+
mesh,
|
|
180
|
+
// debug
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Resolve links between label containers
|
|
189
|
+
for (let c in containers) {
|
|
190
|
+
const container = containers[c];
|
|
191
|
+
if (container.linked) {
|
|
192
|
+
container.linked = containers[container.linked as string] as MainPassContainer;
|
|
193
|
+
}
|
|
194
|
+
// NB: if linked label not found, it was discarded in initial tile collision pass
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Convert container map to array
|
|
198
|
+
containers = Object.keys(containers).map(k => (containers as Record<string, UnresolvedMainPassContainer>)[k]);
|
|
199
|
+
return containers as MainPassContainer[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Generic discard function for labels, does simple occlusion with one or more bounding boxes
|
|
203
|
+
// (no additional logic to try alternate anchors or other layout options, etc.)
|
|
204
|
+
function discard (this: MainPassLabel, bboxes: CollisionBounds, exclude: CollisionLabel | null | 0 | '' = null): boolean {
|
|
205
|
+
if (this.obb) { // single collision box
|
|
206
|
+
return Label.prototype.occluded.call(this, bboxes, exclude);
|
|
207
|
+
}
|
|
208
|
+
else if (this.obbs) { // mutliple collision boxes
|
|
209
|
+
for (let i = 0; i < this.obbs.length; i++){
|
|
210
|
+
let aabb = this.aabbs[i];
|
|
211
|
+
let obb = this.obbs[i];
|
|
212
|
+
let obj = { aabb, obb };
|
|
213
|
+
|
|
214
|
+
let should_discard = Label.prototype.occluded.call(obj, bboxes, exclude);
|
|
215
|
+
if (should_discard) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
// Sets of values to match for directional and corner anchors
|
|
7
|
+
const lefts = ['left', 'top-left', 'bottom-left'];
|
|
8
|
+
const rights = ['right', 'top-right', 'bottom-right'];
|
|
9
|
+
const tops = ['top', 'top-left', 'top-right'];
|
|
10
|
+
const bottoms = ['bottom', 'bottom-left', 'bottom-right'];
|
|
11
|
+
|
|
12
|
+
type AnchorName = string | string[] | undefined;
|
|
13
|
+
type Point = [number, number];
|
|
14
|
+
type Buffer = [number, number, number, number];
|
|
15
|
+
interface PointAnchorNamespace {
|
|
16
|
+
computeOffset(offset: Point, size: Point, anchor: AnchorName, buffer?: Buffer | null): Point;
|
|
17
|
+
alignForAnchor(anchor: AnchorName): string;
|
|
18
|
+
isLeftAnchor(anchor: AnchorName): boolean;
|
|
19
|
+
isRightAnchor(anchor: AnchorName): boolean;
|
|
20
|
+
isTopAnchor(anchor: AnchorName): boolean;
|
|
21
|
+
isBottomAnchor(anchor: AnchorName): boolean;
|
|
22
|
+
default_buffer: Buffer;
|
|
23
|
+
zero_buffer: Buffer;
|
|
24
|
+
}
|
|
25
|
+
const PointAnchor = {
|
|
26
|
+
|
|
27
|
+
computeOffset (offset: Point, size: Point, anchor: AnchorName, buffer: Buffer | null = null): Point {
|
|
28
|
+
if (!anchor || anchor === 'center') {
|
|
29
|
+
return offset;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const offset2: Point = [offset[0], offset[1]];
|
|
33
|
+
buffer = buffer || this.default_buffer;
|
|
34
|
+
|
|
35
|
+
// An optional left/right offset
|
|
36
|
+
if (this.isLeftAnchor(anchor)) {
|
|
37
|
+
offset2[0] -= size[0] / 2;
|
|
38
|
+
if (anchor === 'left') {
|
|
39
|
+
offset2[0] -= buffer[0];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (this.isRightAnchor(anchor)) {
|
|
43
|
+
offset2[0] += size[0] / 2;
|
|
44
|
+
if (anchor === 'right') {
|
|
45
|
+
offset2[0] += buffer[1];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// An optional top/bottom offset
|
|
50
|
+
if (this.isTopAnchor(anchor)) {
|
|
51
|
+
offset2[1] -= size[1] / 2;
|
|
52
|
+
if (anchor === 'top') {
|
|
53
|
+
offset2[1] -= buffer[2];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (this.isBottomAnchor(anchor)) {
|
|
57
|
+
offset2[1] += size[1] / 2;
|
|
58
|
+
if (anchor === 'bottom') {
|
|
59
|
+
offset2[1] += buffer[3];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return offset2;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
alignForAnchor (anchor: AnchorName): string {
|
|
67
|
+
if (anchor && anchor !== 'center') {
|
|
68
|
+
if (this.isLeftAnchor(anchor)) {
|
|
69
|
+
return 'right';
|
|
70
|
+
}
|
|
71
|
+
else if (this.isRightAnchor(anchor)) {
|
|
72
|
+
return 'left';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return 'center';
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
isLeftAnchor (anchor: AnchorName): boolean {
|
|
79
|
+
return (lefts.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
isRightAnchor (anchor: AnchorName): boolean {
|
|
83
|
+
return (rights.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
isTopAnchor (anchor: AnchorName): boolean {
|
|
87
|
+
return (tops.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
isBottomAnchor (anchor: AnchorName): boolean {
|
|
91
|
+
return (bottoms.indexOf(typeof anchor === 'string' ? anchor : '') > -1);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Buffers: [left, right, top, bottom]
|
|
95
|
+
default_buffer: [2.5, 2.5, 1.5, 0.75],
|
|
96
|
+
zero_buffer: [0, 0, 0, 0]
|
|
97
|
+
|
|
98
|
+
} as PointAnchorNamespace;
|
|
99
|
+
|
|
100
|
+
export default PointAnchor;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Tangram
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) 2013-2016 Brett Camper and Mapzen
|
|
4
|
+
// Copyright (c) 2026 vis.gl contributors
|
|
5
|
+
|
|
6
|
+
// Logic for placing point labels along a line geometry
|
|
7
|
+
|
|
8
|
+
import LabelPoint from './label_point';
|
|
9
|
+
import type {LabelLayout} from './label-types';
|
|
10
|
+
import {isCoordOutsideTile} from '../builders/common';
|
|
11
|
+
|
|
12
|
+
const PLACEMENT = LabelPoint.PLACEMENT;
|
|
13
|
+
const default_spacing = 80; // spacing of points along line in pixels
|
|
14
|
+
type Point = [number, number];
|
|
15
|
+
type PlacementLayout = LabelLayout;
|
|
16
|
+
type PlacementResult = {positions: Point[]; angles: number[]};
|
|
17
|
+
|
|
18
|
+
export default function placePointsOnLine (line: Point[], size: Point, layout: PlacementLayout): LabelPoint[] {
|
|
19
|
+
const labels: LabelPoint[] = [];
|
|
20
|
+
const strategy = layout.placement;
|
|
21
|
+
const min_length = Math.max(size[0], size[1]) * Number(layout.placement_min_length_ratio) * layout.units_per_pixel;
|
|
22
|
+
|
|
23
|
+
if (strategy === PLACEMENT.SPACED) {
|
|
24
|
+
let result = getPositionsAndAngles(line, min_length, layout);
|
|
25
|
+
// false will be returned if line have no length
|
|
26
|
+
if (!result) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let positions = result.positions;
|
|
31
|
+
let angles = result.angles;
|
|
32
|
+
for (let i = 0; i < positions.length; i++) {
|
|
33
|
+
let position = positions[i];
|
|
34
|
+
let angle = angles[i];
|
|
35
|
+
if (layout.tile_edges === true || !isCoordOutsideTile(position)) {
|
|
36
|
+
labels.push(new LabelPoint(position, size, layout, angle));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (strategy === PLACEMENT.VERTEX) {
|
|
41
|
+
let p, q;
|
|
42
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
43
|
+
p = line[i];
|
|
44
|
+
q = line[i + 1];
|
|
45
|
+
if (layout.tile_edges === true || !isCoordOutsideTile(p)) {
|
|
46
|
+
const angle = getAngle(p as Point, q as Point, layout.angle);
|
|
47
|
+
labels.push(new LabelPoint(p as Point, size, layout, angle));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// add last endpoint
|
|
52
|
+
const angle = getAngle(p as Point, q as Point, layout.angle);
|
|
53
|
+
labels.push(new LabelPoint(q as Point, size, layout, angle));
|
|
54
|
+
}
|
|
55
|
+
else if (strategy === PLACEMENT.MIDPOINT) {
|
|
56
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
57
|
+
let p = line[i];
|
|
58
|
+
let q = line[i + 1];
|
|
59
|
+
let position = [
|
|
60
|
+
0.5 * (p[0] + q[0]),
|
|
61
|
+
0.5 * (p[1] + q[1])
|
|
62
|
+
];
|
|
63
|
+
if (layout.tile_edges === true || !isCoordOutsideTile(position)) {
|
|
64
|
+
if (!min_length || norm(p, q) > min_length) {
|
|
65
|
+
const angle = getAngle(p, q, layout.angle);
|
|
66
|
+
labels.push(new LabelPoint(position as Point, size, layout, angle));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return labels;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getPositionsAndAngles(line: Point[], min_length: number, layout: PlacementLayout): PlacementResult | false {
|
|
75
|
+
let upp = layout.units_per_pixel;
|
|
76
|
+
let spacing = (layout.placement_spacing || default_spacing) * upp;
|
|
77
|
+
|
|
78
|
+
let length = getLineLength(line);
|
|
79
|
+
if (length <= min_length) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let num_labels = Math.max(Math.floor(length / spacing), 1);
|
|
84
|
+
let remainder = length - (num_labels - 1) * spacing;
|
|
85
|
+
let positions: Point[] = [];
|
|
86
|
+
let angles: number[] = [];
|
|
87
|
+
|
|
88
|
+
let distance = 0.5 * remainder;
|
|
89
|
+
for (let i = 0; i < num_labels; i++) {
|
|
90
|
+
let {position, angle} = interpolateLine(line, distance, min_length, layout);
|
|
91
|
+
if (position != null && angle != null) {
|
|
92
|
+
positions.push(position);
|
|
93
|
+
angles.push(angle);
|
|
94
|
+
}
|
|
95
|
+
distance += spacing;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {positions, angles};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getAngle(p: Point, q: Point, angle: number | 'auto' = 0): number {
|
|
102
|
+
return (angle === 'auto') ? Math.atan2(q[0] - p[0], q[1] - p[1]) : angle;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getLineLength(line: Point[]): number {
|
|
106
|
+
let distance = 0;
|
|
107
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
108
|
+
distance += norm(line[i], line[i+1]);
|
|
109
|
+
}
|
|
110
|
+
return distance;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function norm(p: Point, q: Point): number {
|
|
114
|
+
return Math.sqrt(Math.pow(p[0] - q[0], 2) + Math.pow(p[1] - q[1], 2));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// TODO: can be optimized.
|
|
118
|
+
// you don't have to start from the first index every time for placement
|
|
119
|
+
function interpolateLine(line: Point[], distance: number, min_length: number, layout: PlacementLayout): {position?: Point; angle?: number} {
|
|
120
|
+
let sum = 0;
|
|
121
|
+
let position: Point | undefined;
|
|
122
|
+
let angle: number | undefined;
|
|
123
|
+
for (let i = 0; i < line.length-1; i++) {
|
|
124
|
+
let p = line[i];
|
|
125
|
+
let q = line[i+1];
|
|
126
|
+
|
|
127
|
+
const length = norm(p, q);
|
|
128
|
+
if (length <= min_length) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
sum += length;
|
|
133
|
+
|
|
134
|
+
if (sum > distance) {
|
|
135
|
+
position = interpolateSegment(p, q, sum - distance);
|
|
136
|
+
angle = getAngle(p, q, layout.angle);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {position, angle};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function interpolateSegment(p: Point, q: Point, distance: number): Point {
|
|
144
|
+
let length = norm(p, q);
|
|
145
|
+
let ratio = distance / length;
|
|
146
|
+
return [
|
|
147
|
+
ratio * p[0] + (1 - ratio) * q[0],
|
|
148
|
+
ratio * p[1] + (1 - ratio) * q[1]
|
|
149
|
+
];
|
|
150
|
+
}
|