@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.
Files changed (321) hide show
  1. package/README.md +59 -2
  2. package/dist/core.js +36923 -0
  3. package/dist/core.js.map +6 -0
  4. package/dist/index.js +32 -0
  5. package/dist/loaders-gl-worker.js +82 -0
  6. package/dist/loaders-gl-worker.js.map +6 -0
  7. package/dist/map-logic.js +2 -0
  8. package/dist/map-logic.js.map +6 -0
  9. package/dist/projected-basemaps-worker.js +3 -0
  10. package/dist/projected-basemaps-worker.js.map +6 -0
  11. package/dist/shader-modules.js +141 -0
  12. package/dist/shader-modules.js.map +6 -0
  13. package/dist/style-schema.js +126 -0
  14. package/dist/style-schema.js.map +6 -0
  15. package/dist/tangram-style.schema.json +882 -0
  16. package/dist/tangram.debug.mjs +37139 -0
  17. package/dist/tangram.debug.mjs.map +6 -0
  18. package/dist/tangram.min.mjs +4950 -0
  19. package/dist/types/builders/earcut.d.ts +15 -0
  20. package/dist/types/core.d.ts +29 -0
  21. package/dist/types/gl/constants.d.ts +2 -0
  22. package/dist/types/gl/globe_mesh.d.ts +48 -0
  23. package/dist/types/gl/glsl.d.ts +17 -0
  24. package/dist/types/gl/vertex-constants.d.ts +4 -0
  25. package/dist/types/gl/vertex-types.d.ts +34 -0
  26. package/dist/types/gl/vertex_data.d.ts +32 -0
  27. package/dist/types/gl/vertex_elements.d.ts +8 -0
  28. package/dist/types/gl/vertex_layout.d.ts +32 -0
  29. package/dist/types/glsl.d.ts +14 -0
  30. package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
  31. package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
  32. package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
  33. package/dist/types/index.d.ts +155 -0
  34. package/dist/types/lights/light-definitions.d.ts +121 -0
  35. package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
  36. package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
  37. package/dist/types/map-logic/globe-terrain.d.ts +54 -0
  38. package/dist/types/map-logic/index.d.ts +33 -0
  39. package/dist/types/map-logic/label-identity.d.ts +22 -0
  40. package/dist/types/map-logic/label-placement.d.ts +32 -0
  41. package/dist/types/map-logic/screen-bounds.d.ts +15 -0
  42. package/dist/types/map-logic/terrain-data.d.ts +55 -0
  43. package/dist/types/map-logic/terrain-error.d.ts +51 -0
  44. package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
  45. package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
  46. package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
  47. package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
  48. package/dist/types/map-logic/terrain-query.d.ts +33 -0
  49. package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
  50. package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
  51. package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
  52. package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
  53. package/dist/types/map-logic/tile-residency.d.ts +17 -0
  54. package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
  55. package/dist/types/procedures/js-yaml.d.ts +15 -0
  56. package/dist/types/procedures/mesh-projector.d.ts +107 -0
  57. package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
  58. package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
  59. package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
  60. package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
  61. package/dist/types/procedures/terrain-draping.d.ts +165 -0
  62. package/dist/types/procedures/terrain-globe.d.ts +6 -0
  63. package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
  64. package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
  65. package/dist/types/procedures/web-mercator-math.d.ts +4 -0
  66. package/dist/types/scene/globals.d.ts +11 -0
  67. package/dist/types/scene/ground_footprint.d.ts +16 -0
  68. package/dist/types/scene/host_frame.d.ts +54 -0
  69. package/dist/types/scene/projection_math.d.ts +52 -0
  70. package/dist/types/scene/scene-resource-types.d.ts +177 -0
  71. package/dist/types/scene/scene_bundle.d.ts +69 -0
  72. package/dist/types/scene/visibility_adapter.d.ts +106 -0
  73. package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
  74. package/dist/types/selection/terrain_surface.d.ts +149 -0
  75. package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
  76. package/dist/types/shader-modules/height-decode.d.ts +13 -0
  77. package/dist/types/shader-modules/hillshade.d.ts +13 -0
  78. package/dist/types/shader-modules/index.d.ts +6 -0
  79. package/dist/types/shader-modules/planar.d.ts +12 -0
  80. package/dist/types/shader-modules/sphere-map.d.ts +13 -0
  81. package/dist/types/shader-modules/triplanar.d.ts +15 -0
  82. package/dist/types/sources/decoded_tile_store.d.ts +95 -0
  83. package/dist/types/sources/tile_source_metadata.d.ts +32 -0
  84. package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
  85. package/dist/types/tile/tile_id.d.ts +37 -0
  86. package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
  87. package/dist/types/types.d.ts +293 -0
  88. package/dist/types/utils/geo.d.ts +57 -0
  89. package/dist/types/utils/hash.d.ts +1 -0
  90. package/dist/types/utils/log.d.ts +27 -0
  91. package/dist/types/utils/props.d.ts +6 -0
  92. package/dist/types/utils/subscribe.d.ts +10 -0
  93. package/dist/types/utils/thread.d.ts +6 -0
  94. package/dist/types/utils/urls.d.ts +26 -0
  95. package/dist/types/utils/utils.d.ts +26 -0
  96. package/dist/types/utils/version.d.ts +2 -0
  97. package/dist/types/utils/worker-types.d.ts +68 -0
  98. package/dist/types/utils/worker_broker.d.ts +3 -0
  99. package/package.json +103 -3
  100. package/src/builders/common.ts +41 -0
  101. package/src/builders/earcut.d.ts +15 -0
  102. package/src/builders/geometry-types.ts +60 -0
  103. package/src/builders/points.ts +115 -0
  104. package/src/builders/polygons.ts +225 -0
  105. package/src/builders/polylines.ts +682 -0
  106. package/src/builders/wireframe.ts +31 -0
  107. package/src/core.d.ts +29 -0
  108. package/src/core.ts +33 -0
  109. package/src/experimental/loaders-gl-worker.ts +19 -0
  110. package/src/experimental/projected-basemaps-worker.ts +13 -0
  111. package/src/experimental/projected-mesh-cache.ts +137 -0
  112. package/src/experimental/projected-mesh-domain.ts +110 -0
  113. package/src/experimental/projected-mesh-refinement.ts +193 -0
  114. package/src/experimental/projected-mesh.ts +399 -0
  115. package/src/experimental/projected-normal.ts +26 -0
  116. package/src/experimental/projected-symbol-mesh.ts +74 -0
  117. package/src/gl/constants.ts +18 -0
  118. package/src/gl/context.ts +103 -0
  119. package/src/gl/extensions.ts +30 -0
  120. package/src/gl/globe_mesh.ts +285 -0
  121. package/src/gl/glsl.ts +257 -0
  122. package/src/gl/mesh-types.ts +56 -0
  123. package/src/gl/render_state.ts +111 -0
  124. package/src/gl/shader_program.ts +1310 -0
  125. package/src/gl/texture.ts +814 -0
  126. package/src/gl/uniform_buffer.ts +340 -0
  127. package/src/gl/vao.ts +154 -0
  128. package/src/gl/vbo_mesh.ts +349 -0
  129. package/src/gl/vertex-constants.ts +19 -0
  130. package/src/gl/vertex-types.ts +35 -0
  131. package/src/gl/vertex_data.ts +126 -0
  132. package/src/gl/vertex_elements.ts +54 -0
  133. package/src/gl/vertex_layout.ts +241 -0
  134. package/src/glsl.d.ts +14 -0
  135. package/src/gpu/luma_device_renderer.ts +579 -0
  136. package/src/gpu/pixel_readback.ts +51 -0
  137. package/src/gpu/resource_lifecycle.ts +32 -0
  138. package/src/gpu/tangram_gpu_backend.ts +195 -0
  139. package/src/index.d.ts +155 -0
  140. package/src/index.ts +105 -0
  141. package/src/labels/collision-types.ts +94 -0
  142. package/src/labels/collision.ts +273 -0
  143. package/src/labels/collision_grid.ts +61 -0
  144. package/src/labels/intersect.ts +30 -0
  145. package/src/labels/label-types.ts +91 -0
  146. package/src/labels/label.ts +207 -0
  147. package/src/labels/label_line.ts +801 -0
  148. package/src/labels/label_point.ts +131 -0
  149. package/src/labels/main-pass-types.ts +73 -0
  150. package/src/labels/main_pass.ts +221 -0
  151. package/src/labels/point_anchor.ts +100 -0
  152. package/src/labels/point_placement.ts +150 -0
  153. package/src/labels/projected-pass.ts +259 -0
  154. package/src/labels/repeat_group.ts +72 -0
  155. package/src/lights/ambient_light.glsl +18 -0
  156. package/src/lights/directional_light.glsl +39 -0
  157. package/src/lights/geographic-lights.ts +21 -0
  158. package/src/lights/light-definitions.ts +285 -0
  159. package/src/lights/light.ts +590 -0
  160. package/src/lights/lighting-uniforms.ts +80 -0
  161. package/src/lights/lighting-wgsl.ts +116 -0
  162. package/src/lights/material.glsl +237 -0
  163. package/src/lights/material.ts +145 -0
  164. package/src/lights/native-falloff.ts +37 -0
  165. package/src/lights/point_light.glsl +96 -0
  166. package/src/lights/spot_light.glsl +111 -0
  167. package/src/map-logic/draped-terrain-bounds.ts +71 -0
  168. package/src/map-logic/globe-terrain-preparation.ts +217 -0
  169. package/src/map-logic/globe-terrain.ts +296 -0
  170. package/src/map-logic/index.ts +37 -0
  171. package/src/map-logic/label-identity.ts +36 -0
  172. package/src/map-logic/label-placement.ts +117 -0
  173. package/src/map-logic/screen-bounds.ts +34 -0
  174. package/src/map-logic/terrain-data.ts +107 -0
  175. package/src/map-logic/terrain-error.ts +134 -0
  176. package/src/map-logic/terrain-ground-topology.ts +164 -0
  177. package/src/map-logic/terrain-height-field.ts +254 -0
  178. package/src/map-logic/terrain-navigation.ts +50 -0
  179. package/src/map-logic/terrain-neighborhood.ts +43 -0
  180. package/src/map-logic/terrain-query.ts +51 -0
  181. package/src/map-logic/terrain-resource-budget.ts +124 -0
  182. package/src/map-logic/terrain-transition-topology.ts +164 -0
  183. package/src/map-logic/tile-build-queue.ts +110 -0
  184. package/src/map-logic/tile-cache-policy.ts +89 -0
  185. package/src/map-logic/tile-residency.ts +50 -0
  186. package/src/map-logic/triangle-query-index.ts +265 -0
  187. package/src/procedures/js-yaml.d.ts +15 -0
  188. package/src/procedures/mesh-projector.ts +191 -0
  189. package/src/procedures/mlt-loaders.ts +55 -0
  190. package/src/procedures/mvt-dependencies.d.ts +20 -0
  191. package/src/procedures/mvt-legacy.ts +150 -0
  192. package/src/procedures/mvt-loaders.ts +53 -0
  193. package/src/procedures/mvt-parser.ts +48 -0
  194. package/src/procedures/mvt-properties.ts +36 -0
  195. package/src/procedures/mvt-tile-provider.ts +72 -0
  196. package/src/procedures/pmtiles-loader.ts +75 -0
  197. package/src/procedures/projected-coordinate-transform.ts +79 -0
  198. package/src/procedures/projection-batch-executor.ts +140 -0
  199. package/src/procedures/scene-yaml-legacy.ts +16 -0
  200. package/src/procedures/scene-yaml-loaders.ts +10 -0
  201. package/src/procedures/terrain-draping.ts +713 -0
  202. package/src/procedures/terrain-globe.ts +32 -0
  203. package/src/procedures/terrain-styled-topology.ts +184 -0
  204. package/src/procedures/web-mercator-legacy.ts +24 -0
  205. package/src/procedures/web-mercator-math.ts +50 -0
  206. package/src/scene/camera.ts +298 -0
  207. package/src/scene/camera_base.ts +116 -0
  208. package/src/scene/classic_scene.ts +20 -0
  209. package/src/scene/external_camera.ts +101 -0
  210. package/src/scene/globals.ts +120 -0
  211. package/src/scene/ground_footprint.ts +139 -0
  212. package/src/scene/host_frame.ts +326 -0
  213. package/src/scene/projected_tile_lod.ts +125 -0
  214. package/src/scene/projection_math.ts +142 -0
  215. package/src/scene/projection_shaders.ts +183 -0
  216. package/src/scene/renderer.ts +516 -0
  217. package/src/scene/scene-resource-types.ts +174 -0
  218. package/src/scene/scene.ts +2378 -0
  219. package/src/scene/scene_bundle.ts +304 -0
  220. package/src/scene/scene_debug.ts +129 -0
  221. package/src/scene/scene_loader.ts +402 -0
  222. package/src/scene/scene_worker.ts +528 -0
  223. package/src/scene/view.ts +683 -0
  224. package/src/scene/visibility_adapter.ts +319 -0
  225. package/src/scene/worker_url.ts +15 -0
  226. package/src/selection/draped-terrain-surface.ts +377 -0
  227. package/src/selection/selection-types.ts +79 -0
  228. package/src/selection/selection.ts +542 -0
  229. package/src/selection/selection_fragment.glsl +18 -0
  230. package/src/selection/selection_globals.glsl +9 -0
  231. package/src/selection/selection_pixels.ts +59 -0
  232. package/src/selection/selection_vertex.glsl +15 -0
  233. package/src/selection/terrain_surface.ts +298 -0
  234. package/src/shader-modules/globe-horizon.ts +52 -0
  235. package/src/shader-modules/height-decode.ts +41 -0
  236. package/src/shader-modules/hillshade.ts +70 -0
  237. package/src/shader-modules/index.ts +10 -0
  238. package/src/shader-modules/planar.ts +24 -0
  239. package/src/shader-modules/sphere-map.ts +38 -0
  240. package/src/shader-modules/triplanar.ts +45 -0
  241. package/src/sources/aligned_tile_source.ts +122 -0
  242. package/src/sources/data_source.ts +655 -0
  243. package/src/sources/decoded_tile_store.ts +245 -0
  244. package/src/sources/geojson.ts +300 -0
  245. package/src/sources/mvt.ts +247 -0
  246. package/src/sources/raster.ts +341 -0
  247. package/src/sources/shared_tile_source_adapter.ts +117 -0
  248. package/src/sources/sources.ts +10 -0
  249. package/src/sources/tile_source_adapter.ts +84 -0
  250. package/src/sources/tile_source_metadata.ts +65 -0
  251. package/src/sources/tile_source_state.ts +112 -0
  252. package/src/sources/topojson.ts +90 -0
  253. package/src/styles/feature_annotations.ts +16 -0
  254. package/src/styles/filter.ts +190 -0
  255. package/src/styles/geometry-style-types.ts +209 -0
  256. package/src/styles/globe_visibility_wgsl.ts +22 -0
  257. package/src/styles/layer-types.ts +45 -0
  258. package/src/styles/layer.ts +586 -0
  259. package/src/styles/lines/dasharray.ts +49 -0
  260. package/src/styles/lines/lines.ts +707 -0
  261. package/src/styles/lines/lines_wgsl.ts +224 -0
  262. package/src/styles/points/point-runtime-types.ts +125 -0
  263. package/src/styles/points/points.ts +1079 -0
  264. package/src/styles/points/points_fragment.glsl +126 -0
  265. package/src/styles/points/points_vertex.glsl +230 -0
  266. package/src/styles/points/points_wgsl.ts +189 -0
  267. package/src/styles/polygons/polygons.ts +268 -0
  268. package/src/styles/polygons/polygons_fragment.glsl +173 -0
  269. package/src/styles/polygons/polygons_vertex.glsl +213 -0
  270. package/src/styles/polygons/polygons_wgsl.ts +127 -0
  271. package/src/styles/property-types.ts +125 -0
  272. package/src/styles/raster/raster.ts +33 -0
  273. package/src/styles/raster/raster_globals.glsl +46 -0
  274. package/src/styles/style-mixing-types.ts +101 -0
  275. package/src/styles/style-runtime-types.ts +220 -0
  276. package/src/styles/style-schema.ts +133 -0
  277. package/src/styles/style.ts +1033 -0
  278. package/src/styles/style_globals.glsl +69 -0
  279. package/src/styles/style_manager.ts +464 -0
  280. package/src/styles/style_parser.ts +620 -0
  281. package/src/styles/text/font_manager.ts +180 -0
  282. package/src/styles/text/fontfaceobserver.d.ts +10 -0
  283. package/src/styles/text/text-types.ts +220 -0
  284. package/src/styles/text/text.ts +378 -0
  285. package/src/styles/text/text_canvas.ts +773 -0
  286. package/src/styles/text/text_labels.ts +436 -0
  287. package/src/styles/text/text_segments.ts +142 -0
  288. package/src/styles/text/text_settings.ts +228 -0
  289. package/src/styles/text/text_wgsl.ts +159 -0
  290. package/src/styles/text/text_wrap.ts +194 -0
  291. package/src/tile/globe_tile_preload.ts +40 -0
  292. package/src/tile/tangram_tileset_2d.ts +108 -0
  293. package/src/tile/tile-build-types.ts +155 -0
  294. package/src/tile/tile.ts +807 -0
  295. package/src/tile/tile_id.ts +172 -0
  296. package/src/tile/tile_manager.ts +889 -0
  297. package/src/tile/tile_pyramid.ts +119 -0
  298. package/src/tile/tile_resource_cache.ts +91 -0
  299. package/src/tile/tile_traversal_adapter.ts +132 -0
  300. package/src/types.ts +332 -0
  301. package/src/utils/debounce.ts +17 -0
  302. package/src/utils/debug_settings.ts +43 -0
  303. package/src/utils/errors.ts +12 -0
  304. package/src/utils/functions.ts +95 -0
  305. package/src/utils/geo.ts +364 -0
  306. package/src/utils/hash.ts +14 -0
  307. package/src/utils/log.ts +108 -0
  308. package/src/utils/media_capture.ts +193 -0
  309. package/src/utils/merge.ts +44 -0
  310. package/src/utils/obb.ts +134 -0
  311. package/src/utils/props.ts +43 -0
  312. package/src/utils/slice.ts +14 -0
  313. package/src/utils/subscribe.ts +58 -0
  314. package/src/utils/task.ts +154 -0
  315. package/src/utils/thread.ts +26 -0
  316. package/src/utils/urls.ts +223 -0
  317. package/src/utils/utils.ts +310 -0
  318. package/src/utils/vector.ts +197 -0
  319. package/src/utils/version.ts +8 -0
  320. package/src/utils/worker-types.ts +79 -0
  321. 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
+ }