@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,801 @@
|
|
|
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 {LabelLayout, LabelPointCoordinate, SerializedLabel} from './label-types';
|
|
8
|
+
import Label, {textLayoutToJSON} from './label';
|
|
9
|
+
import Vector from '../utils/vector';
|
|
10
|
+
import OBB from '../utils/obb';
|
|
11
|
+
|
|
12
|
+
const STOPS = [0, 0.33, 0.66, 0.99]; // zoom levels for curved label snapshot data (offsets and angles)
|
|
13
|
+
const LINE_EXCEED_STRAIGHT = 1.5; // minimal ratio for straight labels (label length) / (line length)
|
|
14
|
+
const LINE_EXCEED_STRAIGHT_NO_CURVE = 1.8; // minimal ratio for straight labels that have no curved option (like Arabic)
|
|
15
|
+
const LINE_EXCEED_STAIGHT_LOOSE = 2.3; // 2nd pass minimal ratio for straight labels
|
|
16
|
+
const STRAIGHT_ANGLE_TOLERANCE = 0.1; // multiple "almost straight" segments within this angle tolerance can be considered one straight segment (in radians)
|
|
17
|
+
const CURVE_MIN_TOTAL_COST = 1.3; // curved line total curvature tolerance (sum in radians)
|
|
18
|
+
const CURVE_MIN_AVG_COST = 0.4; // curved line average curvature tolerance (mean)
|
|
19
|
+
const CURVE_MAX_ANGLE = 1; // curved line singular curvature tolerance (value in radians)
|
|
20
|
+
const ORIENTED_LABEL_OFFSET_FACTOR = 1.2; // multiply offset by this amount to avoid linked label collision
|
|
21
|
+
const VERTICAL_ANGLE_TOLERANCE = 0.01; // nearly vertical lines considered vertical within this angle tolerance
|
|
22
|
+
|
|
23
|
+
let LabelLine = {
|
|
24
|
+
// Given a label's bounding box size and size of broken up individual segments
|
|
25
|
+
// return a label that fits along the line geometry that is either straight (preferred) or curved (if straight tolerances aren't met)
|
|
26
|
+
create : function(segment_sizes: LabelPointCoordinate | LabelPointCoordinate[], total_size: LabelPointCoordinate | undefined, line: LabelPointCoordinate[], layout: LabelLayout): LabelLineStraight | LabelLineCurved | false{
|
|
27
|
+
// The passes done for fitting a label, and provided tolerances for each pass
|
|
28
|
+
// First straight is chosen with a low tolerance. Then curved. Then straight with a higher tolerance.
|
|
29
|
+
const passes = [
|
|
30
|
+
{ type: 'straight', tolerance : (layout.no_curving) ? LINE_EXCEED_STRAIGHT_NO_CURVE : LINE_EXCEED_STRAIGHT },
|
|
31
|
+
{ type: 'curved' },
|
|
32
|
+
{ type: 'straight', tolerance : LINE_EXCEED_STAIGHT_LOOSE }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// loop through passes. first label found wins.
|
|
36
|
+
for (let i = 0; i < passes.length; i++){
|
|
37
|
+
let check = passes[i];
|
|
38
|
+
let label;
|
|
39
|
+
if (check.type === 'straight'){
|
|
40
|
+
label = new LabelLineStraight(total_size!, line, layout, check.tolerance!);
|
|
41
|
+
}
|
|
42
|
+
else if (check.type === 'curved' && !layout.no_curving && line.length > 2){
|
|
43
|
+
label = new LabelLineCurved(segment_sizes as LabelPointCoordinate[], line, layout);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (label && !label.throw_away) {
|
|
47
|
+
return label;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default LabelLine;
|
|
56
|
+
|
|
57
|
+
// Base class for a labels.
|
|
58
|
+
/** Shared geometry and collision behavior for straight and articulated labels. */
|
|
59
|
+
export class LabelLineBase {
|
|
60
|
+
/** Worker-scoped identity. */
|
|
61
|
+
declare id: number;
|
|
62
|
+
/** Normalized placement configuration. */
|
|
63
|
+
declare layout: LabelLayout;
|
|
64
|
+
/** Tile-local placement position, set by a successful fit. */
|
|
65
|
+
declare position: LabelPointCoordinate;
|
|
66
|
+
/** Global text rotation. */
|
|
67
|
+
declare angle: number;
|
|
68
|
+
/** Pixel offset. */
|
|
69
|
+
declare offset: LabelPointCoordinate;
|
|
70
|
+
/** Tile units per pixel. */
|
|
71
|
+
declare unit_scale: number;
|
|
72
|
+
/** Segment narrow-phase boxes. */
|
|
73
|
+
declare obbs: OBB[];
|
|
74
|
+
/** Segment broad-phase extents. */
|
|
75
|
+
declare aabbs: number[][];
|
|
76
|
+
/** Placement kind. */
|
|
77
|
+
declare type: string;
|
|
78
|
+
/** Whether fitting rejected the candidate. */
|
|
79
|
+
declare throw_away: boolean;
|
|
80
|
+
/** Whole-label dimensions for straight text. */
|
|
81
|
+
declare size: LabelPointCoordinate;
|
|
82
|
+
/** Tile-edge collision flags, assigned when bounds are built. */
|
|
83
|
+
declare breach?: boolean;
|
|
84
|
+
declare may_repeat_across_tiles?: boolean;
|
|
85
|
+
/** Collision result and grid memberships, assigned by collision. */
|
|
86
|
+
declare placed?: boolean | null;
|
|
87
|
+
declare cells?: CollisionBounds[];
|
|
88
|
+
/** Canvas alignment for point-compatible text consumers. */
|
|
89
|
+
declare align?: string;
|
|
90
|
+
/** Repeat behavior hook reused from Label by hosts when installed. */
|
|
91
|
+
declare mayRepeatAcrossTiles?: Label['mayRepeatAcrossTiles'];
|
|
92
|
+
|
|
93
|
+
constructor (layout: LabelLayout) {
|
|
94
|
+
this.id = Label.nextLabelId();
|
|
95
|
+
this.layout = layout;
|
|
96
|
+
this.position = [] as unknown as LabelPointCoordinate;
|
|
97
|
+
this.angle = 0;
|
|
98
|
+
this.offset = layout.offset.slice() as LabelPointCoordinate;
|
|
99
|
+
this.unit_scale = this.layout.units_per_pixel;
|
|
100
|
+
this.obbs = [];
|
|
101
|
+
this.aabbs = [];
|
|
102
|
+
this.type = ''; // "curved" or "straight" to be set by child class
|
|
103
|
+
this.throw_away = false; // boolean that determines if label should be discarded
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Minimal representation of label
|
|
107
|
+
toJSON (): SerializedLabel {
|
|
108
|
+
return {
|
|
109
|
+
id: this.id,
|
|
110
|
+
type: this.type,
|
|
111
|
+
position: this.position,
|
|
112
|
+
size: this.size,
|
|
113
|
+
offset: this.offset,
|
|
114
|
+
angle: this.angle,
|
|
115
|
+
breach: this.breach,
|
|
116
|
+
may_repeat_across_tiles: this.may_repeat_across_tiles,
|
|
117
|
+
layout: textLayoutToJSON(this.layout)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Given a line, find the longest series of segments that maintains a constant orientation in the x-direction.
|
|
122
|
+
// This assures us that the line has no orientation flip, so text would not appear upside-down.
|
|
123
|
+
// If the line's orientation is reversed, the flip return value will be true, otherwise false
|
|
124
|
+
static splitLineByOrientation(line: LabelPointCoordinate[]): [LabelPointCoordinate[], boolean]{
|
|
125
|
+
let current_line = [line[0]];
|
|
126
|
+
let current_length = 0;
|
|
127
|
+
let max_length = 0;
|
|
128
|
+
let orientation = 0;
|
|
129
|
+
let longest_line = current_line;
|
|
130
|
+
let flip = false;
|
|
131
|
+
|
|
132
|
+
for (let i = 1; i < line.length; i++) {
|
|
133
|
+
let pt = line[i];
|
|
134
|
+
let prev_pt = line[i - 1];
|
|
135
|
+
let length = Vector.length(Vector.sub(pt, prev_pt));
|
|
136
|
+
|
|
137
|
+
if (pt[0] > prev_pt[0]){
|
|
138
|
+
// positive orientation
|
|
139
|
+
if (orientation === 1){
|
|
140
|
+
current_line.push(pt);
|
|
141
|
+
current_length += length;
|
|
142
|
+
if (current_length > max_length){
|
|
143
|
+
longest_line = current_line;
|
|
144
|
+
max_length = current_length;
|
|
145
|
+
flip = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
current_line = [prev_pt, pt];
|
|
150
|
+
current_length = length;
|
|
151
|
+
if (current_length > max_length){
|
|
152
|
+
longest_line = current_line;
|
|
153
|
+
max_length = current_length;
|
|
154
|
+
flip = false;
|
|
155
|
+
}
|
|
156
|
+
orientation = 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (pt[0] < prev_pt[0]) {
|
|
160
|
+
// negative orientation
|
|
161
|
+
if (orientation === -1){
|
|
162
|
+
current_line.unshift(pt);
|
|
163
|
+
current_length += length;
|
|
164
|
+
if (current_length > max_length){
|
|
165
|
+
longest_line = current_line;
|
|
166
|
+
max_length = current_length;
|
|
167
|
+
flip = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// prepend points (reverse order)
|
|
172
|
+
current_line = [pt, prev_pt];
|
|
173
|
+
current_length = length;
|
|
174
|
+
if (current_length > max_length){
|
|
175
|
+
longest_line = current_line;
|
|
176
|
+
max_length = current_length;
|
|
177
|
+
flip = true;
|
|
178
|
+
}
|
|
179
|
+
orientation = -1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// vertical line (doesn't change previous orientation)
|
|
184
|
+
if (orientation === -1){
|
|
185
|
+
current_line.unshift(pt);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
current_line.push(pt);
|
|
189
|
+
orientation = 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
current_length += length;
|
|
193
|
+
if (current_length > max_length){
|
|
194
|
+
longest_line = current_line;
|
|
195
|
+
max_length = current_length;
|
|
196
|
+
|
|
197
|
+
flip = (orientation === -1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return [longest_line, flip];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Checks each segment to see if it should be discarded (via collision). If any segment fails this test, they all fail.
|
|
206
|
+
discard(bboxes: CollisionBounds, exclude: CollisionLabel | null | 0 | '' = null): boolean {
|
|
207
|
+
if (this.throw_away) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < this.obbs.length; i++){
|
|
212
|
+
let aabb = this.aabbs[i];
|
|
213
|
+
let obb = this.obbs[i];
|
|
214
|
+
let obj = { aabb, obb };
|
|
215
|
+
|
|
216
|
+
let shouldDiscard = Label.prototype.occluded.call(obj, bboxes, exclude);
|
|
217
|
+
if (shouldDiscard) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Checks each segment to see if it is within the tile. If any segment fails this test, they all fail.
|
|
225
|
+
inTileBounds() {
|
|
226
|
+
for (let i = 0; i < this.aabbs.length; i++) {
|
|
227
|
+
let aabb = this.aabbs[i];
|
|
228
|
+
let obj = { aabb };
|
|
229
|
+
let in_bounds = Label.prototype.inTileBounds.call(obj);
|
|
230
|
+
if (!in_bounds) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Method to calculate oriented bounding box
|
|
238
|
+
// "angle" is the angle of the text segment, "angle_offset" is the angle applied to the offset.
|
|
239
|
+
// Offset angle is constant for the entire label, while segment angles are not.
|
|
240
|
+
static createOBB (position: LabelPointCoordinate, width: number, height: number, angle: number, angle_offset: number, offset: LabelPointCoordinate | null, upp: number): OBB {
|
|
241
|
+
let p0 = position[0];
|
|
242
|
+
let p1 = position[1];
|
|
243
|
+
|
|
244
|
+
// apply offset, x positive, y pointing down
|
|
245
|
+
if (offset && (offset[0] !== 0 || offset[1] !== 0)) {
|
|
246
|
+
offset = Vector.rot(offset, angle_offset) as LabelPointCoordinate;
|
|
247
|
+
p0 += offset[0] * upp;
|
|
248
|
+
p1 -= offset[1] * upp;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// the angle of the obb is negative since it's the tile system y axis is pointing down
|
|
252
|
+
return new OBB(p0, p1, -angle, width, height);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Class for straight labels.
|
|
257
|
+
// Extends base LabelLine class.
|
|
258
|
+
export class LabelLineStraight extends LabelLineBase {
|
|
259
|
+
/** Discriminator for one unarticulated text quad. */
|
|
260
|
+
declare type: 'straight';
|
|
261
|
+
constructor (size: LabelPointCoordinate, line: LabelPointCoordinate[], layout: LabelLayout, tolerance: number){
|
|
262
|
+
super(layout);
|
|
263
|
+
this.type = 'straight';
|
|
264
|
+
this.size = size;
|
|
265
|
+
this.throw_away = !this.fit(size, line, layout, tolerance);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Determine if the label can fit the geometry within provided tolerance
|
|
269
|
+
// A straight label is generally placed at segment midpoints, but can "look ahead" to further segments
|
|
270
|
+
// if they are within an angle bound given by STRAIGHT_ANGLE_TOLERANCE and place at the midpoint between non-consecutive segments
|
|
271
|
+
fit (size: LabelPointCoordinate, line: LabelPointCoordinate[], layout: LabelLayout, tolerance: number): boolean{
|
|
272
|
+
let upp = this.unit_scale;
|
|
273
|
+
let flipped; // boolean indicating if orientation of line is changed
|
|
274
|
+
|
|
275
|
+
// Make new copy of line, with consistent orientation
|
|
276
|
+
[line, flipped] = LabelLineBase.splitLineByOrientation(line);
|
|
277
|
+
|
|
278
|
+
// matches for "left" or "right" labels where the offset angle is dependent on the geometry
|
|
279
|
+
if (typeof layout.orientation === 'number'){
|
|
280
|
+
this.offset[1] += ORIENTED_LABEL_OFFSET_FACTOR * (size[1] - Number(layout.vertical_buffer));
|
|
281
|
+
|
|
282
|
+
// if line is flipped, or the orientation is "left" (-1), flip the offset's y-axis
|
|
283
|
+
if (flipped){
|
|
284
|
+
this.offset[1] *= -1;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (layout.orientation === -1){
|
|
288
|
+
this.offset[1] *= -1;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let line_lengths = getLineLengths(line);
|
|
293
|
+
let label_length = size[0] * upp;
|
|
294
|
+
|
|
295
|
+
// loop through line looking for a placement for the label
|
|
296
|
+
for (let i = 0; i < line.length - 1; i++){
|
|
297
|
+
let curr = line[i];
|
|
298
|
+
|
|
299
|
+
let curve_tolerance = 0;
|
|
300
|
+
let length = 0;
|
|
301
|
+
let ahead_index = i + 1;
|
|
302
|
+
let prev_angle!: number;
|
|
303
|
+
|
|
304
|
+
// look ahead to further line segments within an angle tolerance
|
|
305
|
+
while (ahead_index < line.length){
|
|
306
|
+
let ahead_curr = line[ahead_index - 1];
|
|
307
|
+
let ahead_next = line[ahead_index];
|
|
308
|
+
|
|
309
|
+
let next_angle = getAngleForSegment(ahead_curr, ahead_next);
|
|
310
|
+
|
|
311
|
+
if (ahead_index !== i + 1){
|
|
312
|
+
curve_tolerance += getAbsAngleDiff(next_angle, prev_angle);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// if curve tolerance is exceeded, break out of loop
|
|
316
|
+
if (Math.abs(curve_tolerance) > STRAIGHT_ANGLE_TOLERANCE){
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
length += line_lengths[ahead_index - 1];
|
|
321
|
+
|
|
322
|
+
// check if label fits geometry
|
|
323
|
+
if (calcFitness(length, label_length) < tolerance){
|
|
324
|
+
let curr_midpt = Vector.mult(Vector.add(curr, ahead_next), 0.5);
|
|
325
|
+
|
|
326
|
+
// TODO: modify angle if line chosen within curve_angle_tolerance
|
|
327
|
+
// Currently line angle is the same as the starting angle, perhaps it should average across segments?
|
|
328
|
+
this.angle = -next_angle;
|
|
329
|
+
|
|
330
|
+
// ensure that all vertical labels point up (not down) by snapping angles close to pi/2 to -pi/2
|
|
331
|
+
if (Math.abs(this.angle - Math.PI/2) < VERTICAL_ANGLE_TOLERANCE) {
|
|
332
|
+
// flip angle and offset
|
|
333
|
+
this.angle = -Math.PI/2;
|
|
334
|
+
|
|
335
|
+
if (typeof layout.orientation === 'number'){
|
|
336
|
+
this.offset[1] *= -1;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.position = curr_midpt as LabelPointCoordinate;
|
|
341
|
+
this.updateBBoxes(this.position, size, this.angle, this.angle, this.offset);
|
|
342
|
+
return true; // use this placement
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
prev_angle = next_angle;
|
|
346
|
+
ahead_index++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Calculate bounding boxes
|
|
354
|
+
updateBBoxes(this: Pick<LabelLineBase, "unit_scale" | "obbs" | "aabbs" | "breach" | "may_repeat_across_tiles"> & {layout: Pick<LabelLayout, 'buffer'>} & Partial<Pick<LabelLineBase, "inTileBounds" | "mayRepeatAcrossTiles">>, position: LabelPointCoordinate, size: LabelPointCoordinate, angle: number, angle_offset: number, offset: LabelPointCoordinate) {
|
|
355
|
+
let upp = this.unit_scale;
|
|
356
|
+
|
|
357
|
+
// reset bounding boxes
|
|
358
|
+
this.obbs = [];
|
|
359
|
+
this.aabbs = [];
|
|
360
|
+
|
|
361
|
+
let width = (size[0] + 2 * this.layout.buffer[0]) * upp * Label.epsilon;
|
|
362
|
+
let height = (size[1] + 2 * this.layout.buffer[1]) * upp * Label.epsilon;
|
|
363
|
+
|
|
364
|
+
let obb = LabelLineBase.createOBB(position, width, height, angle, angle_offset, offset, upp);
|
|
365
|
+
let aabb = obb.getExtent();
|
|
366
|
+
|
|
367
|
+
this.obbs.push(obb);
|
|
368
|
+
this.aabbs.push(aabb);
|
|
369
|
+
|
|
370
|
+
if (this.inTileBounds) {
|
|
371
|
+
this.breach = !this.inTileBounds();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (this.mayRepeatAcrossTiles) {
|
|
375
|
+
this.may_repeat_across_tiles = this.mayRepeatAcrossTiles();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Class for curved labels
|
|
381
|
+
// Extends base LabelLine class to support angles, pre_angles, offsets as arrays for each segment
|
|
382
|
+
class LabelLineCurved extends LabelLineBase {
|
|
383
|
+
/** Discriminator for per-segment curved text quads. */
|
|
384
|
+
declare type: 'curved';
|
|
385
|
+
/** Per-segment samples at fractional zoom stops. */
|
|
386
|
+
declare angles: number[][];
|
|
387
|
+
declare pre_angles: number[][];
|
|
388
|
+
declare offsets: number[][];
|
|
389
|
+
/** Segment count and unscaled dimensions. */
|
|
390
|
+
declare num_segments: number;
|
|
391
|
+
declare sizes: LabelPointCoordinate[];
|
|
392
|
+
|
|
393
|
+
constructor (segment_sizes: LabelPointCoordinate[], line: LabelPointCoordinate[], layout: LabelLayout) {
|
|
394
|
+
super(layout);
|
|
395
|
+
this.type = 'curved';
|
|
396
|
+
|
|
397
|
+
// extra data for curved labels
|
|
398
|
+
this.angles = [];
|
|
399
|
+
this.pre_angles = [];
|
|
400
|
+
this.offsets = [];
|
|
401
|
+
this.num_segments = segment_sizes.length;
|
|
402
|
+
this.sizes = segment_sizes;
|
|
403
|
+
|
|
404
|
+
this.throw_away = !this.fit(this.sizes, line, layout);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Minimal representation of label
|
|
408
|
+
toJSON () {
|
|
409
|
+
return {
|
|
410
|
+
id: this.id,
|
|
411
|
+
type: this.type,
|
|
412
|
+
obbs: this.obbs.map(o => o.toJSON()),
|
|
413
|
+
position: this.position,
|
|
414
|
+
breach: this.breach,
|
|
415
|
+
may_repeat_across_tiles: this.may_repeat_across_tiles,
|
|
416
|
+
layout: textLayoutToJSON(this.layout)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Determine if the curved label can fit the geometry.
|
|
421
|
+
// No tolerance is provided because the label must fit entirely within the line geometry.
|
|
422
|
+
fit (size: LabelPointCoordinate[], line: LabelPointCoordinate[], layout: LabelLayout): boolean{
|
|
423
|
+
let upp = this.unit_scale;
|
|
424
|
+
let flipped; // boolean determining if the line orientation is reversed
|
|
425
|
+
|
|
426
|
+
let height_px = Math.max(...size.map(s => s[1])); // use max segment height
|
|
427
|
+
let height = height_px * upp;
|
|
428
|
+
|
|
429
|
+
// Make new copy of line, with consistent orientation
|
|
430
|
+
[line, flipped] = LabelLineBase.splitLineByOrientation(line);
|
|
431
|
+
|
|
432
|
+
// matches for "left" or "right" labels where the offset angle is dependent on the geometry
|
|
433
|
+
if (typeof layout.orientation === 'number'){
|
|
434
|
+
this.offset[1] += ORIENTED_LABEL_OFFSET_FACTOR * (height_px - Number(layout.vertical_buffer));
|
|
435
|
+
|
|
436
|
+
// if line is flipped, or the orientation is "left" (-1), flip the offset's y-axis
|
|
437
|
+
if (flipped){
|
|
438
|
+
this.offset[1] *= -1;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (layout.orientation === -1){
|
|
442
|
+
this.offset[1] *= -1;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let line_lengths = getLineLengths(line);
|
|
447
|
+
let label_lengths = size.map(size => size[0] * upp);
|
|
448
|
+
|
|
449
|
+
let total_line_length = line_lengths.reduce((prev, next) => prev + next, 0);
|
|
450
|
+
let total_label_length = label_lengths.reduce((prev, next) => prev + next, 0);
|
|
451
|
+
|
|
452
|
+
// if label displacement is longer than the line, no fit can be possible
|
|
453
|
+
if (total_label_length > total_line_length){
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// need two line segments for a curved label
|
|
458
|
+
// NB: single segment lines should still be labeled if possible during straight label placement pass
|
|
459
|
+
let start_index = 0, end_index = line.length-1;
|
|
460
|
+
if (end_index - start_index < 2){
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// all positional offsets of the label are relative to the anchor
|
|
465
|
+
let anchor_index = LabelLineCurved.curvaturePlacement(line, total_line_length, line_lengths, total_label_length, start_index, end_index);
|
|
466
|
+
let anchor = line[anchor_index];
|
|
467
|
+
|
|
468
|
+
// if anchor not found, or greater than the end_index, no fit possible
|
|
469
|
+
if (anchor_index === -1 || end_index - anchor_index < 2){
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// set start position at anchor position
|
|
474
|
+
this.position = anchor;
|
|
475
|
+
|
|
476
|
+
// Loop through labels at each zoom level stop
|
|
477
|
+
// TODO: Can be made faster since we are computing every segment for every zoom stop
|
|
478
|
+
// We can skip a segment's calculation once a segment's angle equals its fully zoomed angle
|
|
479
|
+
for (var i = 0; i < label_lengths.length; i++){
|
|
480
|
+
this.offsets[i] = [];
|
|
481
|
+
this.angles[i] = [];
|
|
482
|
+
this.pre_angles[i] = [];
|
|
483
|
+
|
|
484
|
+
// loop through stops (z = [0, .33, .66, .99] + base zoom)
|
|
485
|
+
for (var j = 0; j < STOPS.length; j++){
|
|
486
|
+
let stop = STOPS[j];
|
|
487
|
+
|
|
488
|
+
// scale the line geometry by the zoom magnification
|
|
489
|
+
let [new_line, line_lengths] = LabelLineCurved.scaleLine(stop, line);
|
|
490
|
+
anchor = new_line[anchor_index];
|
|
491
|
+
|
|
492
|
+
// calculate label data relative to anchor position
|
|
493
|
+
let {positions, offsets, angles, pre_angles} = LabelLineCurved.placeAtIndex(anchor_index, new_line, line_lengths, label_lengths);
|
|
494
|
+
|
|
495
|
+
// translate 2D offsets into "polar coordinates"" (1D distances with angles)
|
|
496
|
+
let offsets1d = offsets.map(offset => {
|
|
497
|
+
return Math.sqrt(offset[0] * offset[0] + offset[1] * offset[1]) / upp;
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Calculate everything that is independent of zoom level (angle for offset, bounding boxes, etc)
|
|
501
|
+
if (stop === 0){
|
|
502
|
+
// use average angle for a global label offset (if offset is specified)
|
|
503
|
+
this.angle = 1 / angles.length * angles.reduce((prev, next) => prev + next);
|
|
504
|
+
|
|
505
|
+
// calculate bounding boxes for collision at zoom level 0
|
|
506
|
+
for (let i = 0; i < positions.length; i++){
|
|
507
|
+
let position = positions[i];
|
|
508
|
+
let pre_angle = pre_angles[i];
|
|
509
|
+
let width = label_lengths[i];
|
|
510
|
+
let angle_segment = pre_angle + angles[i];
|
|
511
|
+
let angle_offset = this.angle;
|
|
512
|
+
|
|
513
|
+
let obb = LabelLineBase.createOBB(position, width, height, angle_segment, angle_offset, this.offset, upp);
|
|
514
|
+
let aabb = obb.getExtent();
|
|
515
|
+
|
|
516
|
+
this.obbs.push(obb);
|
|
517
|
+
this.aabbs.push(aabb);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// push offsets/angles/pre_angles for each zoom and for each label segment
|
|
522
|
+
this.offsets[i].push(offsets1d[i]);
|
|
523
|
+
this.angles[i].push(angles[i]);
|
|
524
|
+
this.pre_angles[i].push(pre_angles[i]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Find optimal starting segment for placing a curved label along a line within provided tolerances
|
|
532
|
+
// This is determined by calculating the curvature at each interior vertex of a line
|
|
533
|
+
// then construct a "window" whose breadth is the length of the label. Place this label at each vertex
|
|
534
|
+
// and add the curvatures of each vertex within the window. The vertex mimimizing this value is the "best" placement.
|
|
535
|
+
// Return -1 is no placement found.
|
|
536
|
+
static curvaturePlacement(line: LabelPointCoordinate[], total_line_length: number, line_lengths: number[], label_length: number, start_index?: number, end_index?: number): number{
|
|
537
|
+
start_index = start_index || 0;
|
|
538
|
+
end_index = end_index || line.length - 1;
|
|
539
|
+
|
|
540
|
+
var curvatures = []; // array of curvature values per line vertex
|
|
541
|
+
|
|
542
|
+
// calculate curvature values
|
|
543
|
+
for (let i = start_index + 1; i < end_index; i++){
|
|
544
|
+
var prev = line[i - 1];
|
|
545
|
+
var curr = line[i];
|
|
546
|
+
var next = line[i + 1];
|
|
547
|
+
|
|
548
|
+
var norm_1 = Vector.perp(curr, prev);
|
|
549
|
+
var norm_2 = Vector.perp(next, curr);
|
|
550
|
+
|
|
551
|
+
var curvature = Vector.angleBetween(norm_1, norm_2);
|
|
552
|
+
|
|
553
|
+
// If curvature at a vertex is greater than the tolerance, remove it from consideration
|
|
554
|
+
// by giving it an infinite penalty
|
|
555
|
+
if (curvature > CURVE_MAX_ANGLE) {
|
|
556
|
+
curvature = Infinity;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
curvatures.push(curvature);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
curvatures.push(Infinity); // Infinite penalty for going off end of line
|
|
563
|
+
|
|
564
|
+
// calculate curvature costs
|
|
565
|
+
var total_costs = [];
|
|
566
|
+
var avg_costs = [];
|
|
567
|
+
var line_index = start_index;
|
|
568
|
+
var position = 0;
|
|
569
|
+
|
|
570
|
+
for (let i = 0; i < start_index; i++){
|
|
571
|
+
position += line_lengths[i];
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// move window along line, starting at first vertex
|
|
575
|
+
while (position + label_length < total_line_length){
|
|
576
|
+
// define window breadth
|
|
577
|
+
var window_start = position;
|
|
578
|
+
var window_end = window_start + label_length;
|
|
579
|
+
|
|
580
|
+
var line_position = window_start;
|
|
581
|
+
var ahead_index = line_index;
|
|
582
|
+
var cost = 0;
|
|
583
|
+
|
|
584
|
+
// iterate through points on line intersecting window
|
|
585
|
+
while (ahead_index < end_index && line_position + line_lengths[ahead_index] < window_end){
|
|
586
|
+
cost += curvatures[ahead_index];
|
|
587
|
+
if (cost === Infinity) {
|
|
588
|
+
break; // no further progress can be made
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
line_position += line_lengths[ahead_index];
|
|
592
|
+
ahead_index++;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// if optimal cost, break out
|
|
596
|
+
if (cost === 0) {
|
|
597
|
+
return line_index;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
var avg_cost = cost / (ahead_index - line_index);
|
|
601
|
+
|
|
602
|
+
total_costs.push(cost);
|
|
603
|
+
avg_costs.push(avg_cost);
|
|
604
|
+
|
|
605
|
+
position += line_lengths[line_index];
|
|
606
|
+
line_index++;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (total_costs.length === 0) {
|
|
610
|
+
return -1;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// calculate min cost and avg cost to determine if label can fit within curvatures tolerances
|
|
614
|
+
var min_total_cost = Math.min.apply(null, total_costs);
|
|
615
|
+
var min_index = total_costs.indexOf(min_total_cost);
|
|
616
|
+
var min_avg_cost = avg_costs[min_index];
|
|
617
|
+
|
|
618
|
+
if (min_total_cost < CURVE_MIN_TOTAL_COST && min_avg_cost < CURVE_MIN_AVG_COST){
|
|
619
|
+
// return index with best placement (least curvature)
|
|
620
|
+
return total_costs.indexOf(min_total_cost);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
// if tolerances aren't satisfied, throw away tile
|
|
624
|
+
return -1;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Scale the line by a scale factor (used for computing the angles and offsets at fractional zoom levels)
|
|
629
|
+
// Return the new line positions and their lengths
|
|
630
|
+
static scaleLine(scale: number, line: LabelPointCoordinate[]): [LabelPointCoordinate[], number[]]{
|
|
631
|
+
var new_line = [line[0]];
|
|
632
|
+
var line_lengths: number[] = [];
|
|
633
|
+
|
|
634
|
+
line.forEach((pt, i) => {
|
|
635
|
+
if (i === line.length - 1) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
var v = Vector.sub(line[i+1], line[i]);
|
|
639
|
+
var delta = Vector.mult(v, 1 + scale);
|
|
640
|
+
|
|
641
|
+
new_line.push(Vector.add(new_line[i], delta) as LabelPointCoordinate);
|
|
642
|
+
line_lengths.push(Vector.length(delta));
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
return [new_line, line_lengths];
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Place a label at a given line index
|
|
649
|
+
static placeAtIndex(anchor_index: number, line: LabelPointCoordinate[], line_lengths: number[], label_lengths: number[]){
|
|
650
|
+
let anchor = line[anchor_index];
|
|
651
|
+
|
|
652
|
+
// Use flat coordinates. Get nearest line vertex index, and offset from the vertex for all labels.
|
|
653
|
+
let [indices, relative_offsets] = LabelLineCurved.getIndicesAndOffsets(anchor_index, line_lengths, label_lengths);
|
|
654
|
+
|
|
655
|
+
// get 2D positions based on "flat" indices and offsets
|
|
656
|
+
let positions = LabelLineCurved.getPositionsFromIndicesAndOffsets(line, indices, relative_offsets);
|
|
657
|
+
|
|
658
|
+
// get 2d offsets, angles and pre_angles relative to anchor
|
|
659
|
+
let [offsets, angles, pre_angles] = LabelLineCurved.getAnglesFromIndicesAndOffsets(anchor, indices, line, positions);
|
|
660
|
+
|
|
661
|
+
return {positions, offsets, angles, pre_angles};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Given label lengths to place along a line broken into several lengths, computer what indices and at which offsets
|
|
665
|
+
// the labels will appear on the line. Assume the line is straight, as it is not necessary to consider angles.
|
|
666
|
+
//
|
|
667
|
+
// Label lengths:
|
|
668
|
+
// |-----|----|-----|-----------------|-------------|
|
|
669
|
+
//
|
|
670
|
+
// Line Lengths;
|
|
671
|
+
// |---------|---------|-------------|------------|----------|-------|
|
|
672
|
+
//
|
|
673
|
+
// Result: indices: [0,0,1,1,3,4]
|
|
674
|
+
static getIndicesAndOffsets(line_index: number, line_lengths: number[], label_lengths: number[]): [number[], number[]]{
|
|
675
|
+
let num_labels = label_lengths.length;
|
|
676
|
+
|
|
677
|
+
let indices = [];
|
|
678
|
+
let offsets: number[] = [];
|
|
679
|
+
|
|
680
|
+
let label_index = 0;
|
|
681
|
+
let label_offset = 0;
|
|
682
|
+
let line_offset = 0;
|
|
683
|
+
|
|
684
|
+
// iterate along line
|
|
685
|
+
while (label_index < num_labels){
|
|
686
|
+
let label_length = label_lengths[label_index];
|
|
687
|
+
|
|
688
|
+
// iterate along labels within the line segment
|
|
689
|
+
while (label_index < num_labels && label_offset + 0.5 * label_length <= line_offset + line_lengths[line_index]){
|
|
690
|
+
let offset = label_offset - line_offset + 0.5 * label_length;
|
|
691
|
+
offsets.push(offset);
|
|
692
|
+
indices.push(line_index);
|
|
693
|
+
|
|
694
|
+
label_offset += label_length;
|
|
695
|
+
label_index++;
|
|
696
|
+
label_length = label_lengths[label_index];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
line_offset += line_lengths[line_index];
|
|
700
|
+
line_index++;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return [indices, offsets];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Given indices and 1D offsets on a line, compute their 2D positions
|
|
707
|
+
static getPositionsFromIndicesAndOffsets(line: LabelPointCoordinate[], indices: number[], offsets: number[]): LabelPointCoordinate[]{
|
|
708
|
+
let positions: LabelPointCoordinate[] = [];
|
|
709
|
+
for (let i = 0; i < indices.length; i++){
|
|
710
|
+
let index = indices[i];
|
|
711
|
+
let offset = offsets[i];
|
|
712
|
+
|
|
713
|
+
let angle = getAngleForSegment(line[index], line[index + 1]);
|
|
714
|
+
|
|
715
|
+
let offset2d = Vector.rot([offset, 0], angle);
|
|
716
|
+
let position = Vector.add(line[index], offset2d);
|
|
717
|
+
|
|
718
|
+
positions.push(position as LabelPointCoordinate);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return positions;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Given indices and 1D offsets on a line, compute their angles and pre-angles from a reference anchor point
|
|
725
|
+
static getAnglesFromIndicesAndOffsets(anchor: LabelPointCoordinate, indices: number[], line: LabelPointCoordinate[], positions: LabelPointCoordinate[]): [LabelPointCoordinate[], number[], number[]]{
|
|
726
|
+
let angles = [];
|
|
727
|
+
let pre_angles = [];
|
|
728
|
+
let offsets = [];
|
|
729
|
+
|
|
730
|
+
for (let i = 0; i < positions.length; i++){
|
|
731
|
+
let position = positions[i];
|
|
732
|
+
let index = indices[i];
|
|
733
|
+
|
|
734
|
+
let offset = Vector.sub(position, anchor);
|
|
735
|
+
let offset_angle = -Vector.angle(offset);
|
|
736
|
+
|
|
737
|
+
let angle = getTextAngleForSegment(line[index], line[index + 1]);
|
|
738
|
+
let pre_angle = angle - offset_angle;
|
|
739
|
+
|
|
740
|
+
if (i > 0){
|
|
741
|
+
let prev_angle = angles[i - 1];
|
|
742
|
+
let prev_pre_angle = pre_angles[i - 1];
|
|
743
|
+
if (Math.abs(offset_angle - prev_angle) > Math.PI) {
|
|
744
|
+
offset_angle += (offset_angle > prev_angle) ? -2 * Math.PI : 2 * Math.PI;
|
|
745
|
+
}
|
|
746
|
+
if (Math.abs(prev_pre_angle - pre_angle) > Math.PI) {
|
|
747
|
+
pre_angle += (pre_angle > prev_pre_angle) ? -2 * Math.PI : 2 * Math.PI;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
angles.push(offset_angle);
|
|
752
|
+
pre_angles.push(pre_angle);
|
|
753
|
+
offsets.push(offset as LabelPointCoordinate);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return [offsets, angles, pre_angles];
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Fitness function (label length / line length)
|
|
761
|
+
function calcFitness(line_length: number, label_length: number): number {
|
|
762
|
+
return label_length / line_length;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function getAngleForSegment(p: LabelPointCoordinate, q: LabelPointCoordinate): number{
|
|
766
|
+
let pq = Vector.sub(q,p);
|
|
767
|
+
return Vector.angle(pq);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function getTextAngleForSegment(pt1: LabelPointCoordinate, pt2: LabelPointCoordinate): number {
|
|
771
|
+
return -getAngleForSegment(pt1, pt2);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function getLineLengths(line: LabelPointCoordinate[]): number[]{
|
|
775
|
+
let lengths = [];
|
|
776
|
+
for (let i = 0; i < line.length - 1; i++){
|
|
777
|
+
let p = line[i];
|
|
778
|
+
let q = line[i+1];
|
|
779
|
+
let length = Math.hypot(p[0] - q[0], p[1] - q[1]);
|
|
780
|
+
lengths.push(length);
|
|
781
|
+
}
|
|
782
|
+
return lengths;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function getAbsAngleDiff(angle1: number, angle2: number): number{
|
|
786
|
+
let small, big;
|
|
787
|
+
if (angle1 > angle2){
|
|
788
|
+
small = angle2;
|
|
789
|
+
big = angle1;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
small = angle1;
|
|
793
|
+
big = angle2;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
while (big - small > Math.PI){
|
|
797
|
+
small += 2 * Math.PI;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return Math.abs(big - small);
|
|
801
|
+
}
|