@tangram.gl/tangram-renderer 0.0.0-stage → 0.0.1

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 +60 -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,1079 @@
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
+ // Point + text label rendering style
7
+
8
+
9
+ import log from '../../utils/log';
10
+ import {Style} from '../style';
11
+ import type {PointStyleRuntime, PointMethods, PointState, PointFeatureStyle, PointCandidate, AttachedTextQueue} from './point-runtime-types';
12
+ import type {ParsedTextFeature} from '../text/text-types';
13
+ import type {LabelPointCoordinate} from '../../labels/label-types';
14
+ import type {StyleState} from '../style-runtime-types';
15
+ import StyleParser from '../style_parser';
16
+ import gl from '../../gl/constants'; // web workers don't have access to GL context, so import all GL constants
17
+ import VertexLayout from '../../gl/vertex_layout';
18
+ import {PACKED_HEIGHT_SCALE, packProjectedAnnotationHeight} from '../../gl/vertex-constants';
19
+ import { buildQuadForPoint } from '../../builders/points';
20
+ import Texture from '../../gl/texture';
21
+ import Geo from '../../utils/geo';
22
+ import Collision from '../../labels/collision';
23
+ import LabelPoint from '../../labels/label_point';
24
+ import placePointsOnLine from '../../labels/point_placement';
25
+ import {TextLabels} from '../text/text_labels';
26
+ import {VIEW_PAN_SNAP_TIME} from '../../scene/view';
27
+ import debugSettings from '../../utils/debug_settings';
28
+
29
+ import points_vs from './points_vertex.glsl';
30
+ import points_fs from './points_fragment.glsl';
31
+ import {buildPointsWGSL} from './points_wgsl';
32
+ import {GLOBE_PROJECTION_GLSL, TERRAIN_DRAPING_GLSL} from '../../scene/projection_shaders';
33
+ import {GLOBE_VISIBILITY_GLSL} from '../globe_visibility_wgsl';
34
+
35
+ const PLACEMENT = LabelPoint.PLACEMENT;
36
+
37
+ export const Points: PointStyleRuntime = Object.create(Style);
38
+
39
+ const SHADER_POINT_VARIANT = '__shader_point';
40
+
41
+ // texture types
42
+ const TANGRAM_POINT_TYPE_TEXTURE = 1; // style texture/sprites (assigned by user)
43
+ const TANGRAM_POINT_TYPE_LABEL = 2; // labels (generated by rendering labels to canvas)
44
+ const TANGRAM_POINT_TYPE_SHADER = 3; // point (drawn in shader)
45
+
46
+ // default point size in pixels
47
+ const DEFAULT_POINT_SIZE = 16;
48
+
49
+ // Mixin text label methods
50
+ Object.assign(Points, TextLabels);
51
+
52
+ const pointMethods: PointMethods & Partial<PointState> & Partial<StyleState> & {built_in: boolean; collision: boolean} & ThisType<PointStyleRuntime> = {
53
+ name: 'points',
54
+ built_in: true,
55
+ vertex_shader_src: points_vs.replace('#pragma tangram: projection', GLOBE_PROJECTION_GLSL + TERRAIN_DRAPING_GLSL)
56
+ .replace('#pragma tangram: globe_visibility', GLOBE_VISIBILITY_GLSL),
57
+ fragment_shader_src: points_fs,
58
+ selection: true, // enable feature selection
59
+ collision: true, // style includes a collision pass
60
+ blend: 'overlay', // overlays drawn on top of all other styles, with blending
61
+
62
+ getWGSLShaderSource(selection = false) {
63
+ return buildPointsWGSL(Boolean(this.cpu_projection), selection, Boolean(this.terrain));
64
+ },
65
+
66
+ init(options = {}) {
67
+ Style.init.call(this, options);
68
+
69
+ // Shader defines
70
+ this.setupDefines();
71
+
72
+ // Include code for SDF-drawn shader points
73
+ this.defines.TANGRAM_HAS_SHADER_POINTS = true;
74
+
75
+ // texture types
76
+ this.defines.TANGRAM_POINT_TYPE_TEXTURE = TANGRAM_POINT_TYPE_TEXTURE;
77
+ this.defines.TANGRAM_POINT_TYPE_LABEL = TANGRAM_POINT_TYPE_LABEL;
78
+ this.defines.TANGRAM_POINT_TYPE_SHADER = TANGRAM_POINT_TYPE_SHADER;
79
+
80
+ this.collision_group_points = this.name+'-points';
81
+ this.collision_group_text = this.name+'-text';
82
+
83
+ // Stenciling proxy tiles (to avoid compounding alpha artifacts) doesn't work well with
84
+ // points/text labels, which have pure transparent pixels that interfere with the stencil buffer,
85
+ // causing a "cut-out"/"x-ray" effect (preventing pixels that would usually be covered by proxy tiles
86
+ // underneath from being rendered).
87
+ this.stencil_proxy_tiles = false;
88
+
89
+ this.reset();
90
+ },
91
+
92
+ // Setup defines common to points base and child (text) styles
93
+ setupDefines () {
94
+ this.defines.TANGRAM_CPU_PROJECTED = Boolean(this.cpu_projection);
95
+ // If we're not rendering as overlay, we need a layer attribute
96
+ if (this.blend !== 'overlay') {
97
+ this.defines.TANGRAM_LAYER_ORDER = true;
98
+ }
99
+
100
+ // Fade in labels
101
+ if (debugSettings.suppress_label_fade_in === true) {
102
+ this.fade_in_time = 0;
103
+ this.defines.TANGRAM_FADE_IN_RATE = null;
104
+ }
105
+ else {
106
+ this.fade_in_time = 0.15; // time in seconds
107
+ this.defines.TANGRAM_FADE_IN_RATE = 1 / this.fade_in_time;
108
+ }
109
+
110
+ // Snap points to pixel grid after panning stop
111
+ if (debugSettings.suppress_label_snap_animation !== true) {
112
+ this.defines.TANGRAM_VIEW_PAN_SNAP_RATE = 1 / VIEW_PAN_SNAP_TIME; // inverse time in seconds
113
+ }
114
+
115
+ // Show hidden labels for debugging
116
+ if (debugSettings.show_hidden_labels === true) {
117
+ this.defines.TANGRAM_SHOW_HIDDEN_LABELS = true;
118
+ }
119
+
120
+ // Enable wireframe for debugging
121
+ if (debugSettings.wireframe === true) {
122
+ this.defines.TANGRAM_WIREFRAME = true;
123
+ }
124
+ },
125
+
126
+ reset () {
127
+ this.queues = {};
128
+ this.resetText();
129
+ this.texture_missing_sprites = {}; // track which missing sprites we've found (reduce dupe log messages)
130
+ },
131
+
132
+ // Override to queue features instead of processing immediately
133
+ addFeature (feature, draw, context) {
134
+ let tile = context.tile;
135
+ if (tile.generation !== this.generation) {
136
+ return;
137
+ }
138
+ if (this.cpu_projection && !['Point', 'MultiPoint'].includes(feature.geometry.type)) {
139
+ throw new Error('Projected annotations require Point or MultiPoint source geometry');
140
+ }
141
+
142
+ // Point styling
143
+ let style: PointFeatureStyle = {};
144
+ style.color = this.parseColor(draw.color, context);
145
+ style.texture = draw.texture; // optional point texture, specified in `draw` or at style level
146
+ style.label_texture = null; // assigned by labelling code if needed
147
+ style.blend_order = draw.blend_order; // copy pre-computed blend order
148
+
149
+ // require color or texture
150
+ if (!style.color && !style.texture) {
151
+ return;
152
+ }
153
+
154
+ style.alpha = StyleParser.evalCachedProperty(draw.alpha, context) as number | undefined; // optional alpha override
155
+
156
+ // optional sprite and texture
157
+ let sprite_info;
158
+ if (this.hasSprites(style)) {
159
+ // populate sprite_info object with used sprites
160
+ sprite_info = this.parseSprite(style, draw, context);
161
+
162
+ if (sprite_info) {
163
+ style.texcoords = sprite_info.texcoords;
164
+ }
165
+ else {
166
+ // sprites are defined in the style's texture, but none are used in the current layer
167
+ log({ level: 'debug', once: true }, `Layer group '${draw.layers?.join(', ')}' ` +
168
+ `uses a texture '${style.texture}', but doesn't specify which sprite to draw. ` +
169
+ 'Features that match this layer group won\'t be drawn without specifying the sprite with the ' +
170
+ '\'sprite\' or \'sprite_default\' properties. The merged draw parameters for this layer group are:', draw).then(logged => {
171
+ if (logged) {
172
+ log('debug', `Example feature for layer group '${draw.layers?.join(', ')}'`, feature);
173
+ }
174
+ });
175
+ return;
176
+ }
177
+ } else if (draw.sprite) {
178
+ // sprite specified in the draw layer but no sprites defined in the texture
179
+ log({ level: 'warn', once: true }, `Layer group '${draw.layers?.join(', ')}' ` +
180
+ `specifies sprite '${draw.sprite}', but the texture '${draw.texture}' doesn't define any sprites. ` +
181
+ 'Features that match this layer group won\'t be drawn. The merged draw parameters for this layer group are:', draw);
182
+ return;
183
+ }
184
+
185
+ this.calcSize(draw, style, sprite_info, context);
186
+ if (!isPointPair(style.size)) throw new TypeError('Point size must contain two numeric dimensions');
187
+
188
+ // incorporate outline into size
189
+ if (draw.outline) {
190
+ style.outline_width = (StyleParser.evalCachedProperty(draw.outline.width, context) || StyleParser.defaults.outline.width) as number;
191
+ style.outline_color = this.parseColor(draw.outline.color, context);
192
+ }
193
+
194
+ style.outline_edge_pct = 0;
195
+ if (style.outline_width && style.outline_color) {
196
+ // adjust size and UVs for outline
197
+ let outline_width = style.outline_width;
198
+ style.size[0] += outline_width;
199
+ style.size[1] += outline_width;
200
+ style.outline_edge_pct = outline_width / Math.min(style.size[0], style.size[1]) * 2; // UV distance at which outline starts
201
+ style.outline_alpha = StyleParser.evalCachedProperty(draw.outline?.alpha, context) as number | undefined; // optional alpha override
202
+ }
203
+
204
+ // size will be scaled to 16-bit signed int, so max allowed width + height of 256 pixels
205
+ style.size[0] = Math.min(style.size[0], 256);
206
+ style.size[1] = Math.min(style.size[1], 256);
207
+
208
+ // Placement strategy
209
+ style.placement = typeof draw.placement === 'number' ? draw.placement : undefined;
210
+ style.placement_min_length_ratio = StyleParser.evalCachedProperty(draw.placement_min_length_ratio, context) as number;
211
+
212
+ // Spacing parameter (in pixels) to equally space points along a line
213
+ if (style.placement === PLACEMENT.SPACED && draw.placement_spacing) {
214
+ style.placement_spacing = StyleParser.evalCachedProperty(draw.placement_spacing, context) as number;
215
+ }
216
+
217
+ // Angle parameter (can be a number or the string "auto")
218
+ style.angle = (StyleParser.evalProperty(draw.angle, context) || 0) as number | 'auto';
219
+
220
+ // points can be placed off the ground
221
+ const height = this.cpu_projection ? StyleParser.evalProjectedHeightProperty(draw.z, context) :
222
+ StyleParser.evalCachedDistanceProperty(draw.z, context);
223
+ style.z = this.cpu_projection ? packProjectedAnnotationHeight(height, this.cpu_projection.allowElevation) :
224
+ (height || StyleParser.defaults.z) as number;
225
+ if (this.cpu_projection) style.projected_height = style.z / PACKED_HEIGHT_SCALE;
226
+
227
+ style.tile_edges = draw.tile_edges; // usually activated for debugging, or rare visualization needs
228
+
229
+ this.computeLayout(style, feature, draw, context, tile);
230
+
231
+ // Text styling
232
+ let tf: ParsedTextFeature | ParsedTextFeature[] | false | null | undefined =
233
+ draw.text &&
234
+ draw.text.visible !== false && // explicitly handle `visible` property for nested text
235
+ this.parseTextFeature(feature, draw.text, context, tile);
236
+
237
+ if (Array.isArray(tf)) {
238
+ tf = null; // NB: boundary labels not supported for point label attachments, should log warning
239
+ log({ level: 'warn', once: true }, `Layer group '${draw.layers?.join(', ')}': ` +
240
+ 'cannot use boundary labels (e.g. \'text_source: { left: ..., right: ... }\') for \'text\' labels attached to \'points\'; ' +
241
+ `provided 'text_source' value was ${JSON.stringify(draw.text?.text_source)}`);
242
+ }
243
+
244
+ if (tf) {
245
+ if (!isPointParent(style)) throw new TypeError('Attached text requires a sized point parent with an offset');
246
+ tf.layout.parent = style; // parent point will apply additional anchor/offset to text
247
+ if (this.cpu_projection) tf.layout.projected_height = style.projected_height;
248
+
249
+ // Text labels have a default priority of 0.5 below their parent point (+0.5, priority is lower-is-better)
250
+ // This can be overriden, as long as it is less than or equal to the default
251
+ tf.layout.priority = draw.text?.priority ? Math.max(tf.layout.priority, (style.priority as number) + 0.5) : ((style.priority as number) + 0.5);
252
+
253
+ Collision.addStyle(this.collision_group_text, tile.id);
254
+ }
255
+
256
+ this.queueFeature({ feature, draw, context, style, text_feature: tf }, tile); // queue the feature for later processing
257
+
258
+ // Register with collision manager
259
+ Collision.addStyle(this.collision_group_points, tile.id);
260
+ },
261
+
262
+ // Calcuate the size for the current point feature
263
+ calcSize (draw, style, sprite_info, context) {
264
+ // point size defined explicitly, or defaults to sprite size, or generic fallback
265
+ if (!draw.size) {
266
+ // a 'size' property has not been set in the draw layer -
267
+ // use the sprite size if it exists and a generic fallback if it doesn't
268
+ const size = (sprite_info && sprite_info.css_size) || [DEFAULT_POINT_SIZE, DEFAULT_POINT_SIZE];
269
+ if (!isPointPair(size)) throw new TypeError('Sprite size must contain two numeric dimensions');
270
+ style.size = size;
271
+ }
272
+ else {
273
+ // check for a cached size, passing the texture and any sprite references
274
+ const texture = style.texture ? Texture.textures[style.texture] : undefined;
275
+ if (texture && !isPointSizeImage(texture)) throw new TypeError('Texture dimensions are not initialized');
276
+ const size = StyleParser.evalCachedPointSizeProperty(draw.size, sprite_info, texture, context);
277
+ if (size == null) {
278
+ style.size = undefined;
279
+ // the StyleParser couldn't evaluate a sprite size
280
+ log({ level: 'warn', once: true }, `Layer group '${draw.layers?.join(', ')}': ` +
281
+ `'size' (${JSON.stringify(draw.size.value)}) couldn't be interpreted, features that match ` +
282
+ 'this layer group won\'t be drawn');
283
+ return;
284
+ }
285
+ else if (typeof size === 'number') {
286
+ style.size = [size, size]; // convert 1d size to 2d
287
+ }
288
+ else if (isPointPair(size)) style.size = size;
289
+ else throw new TypeError('Point size must contain two numeric dimensions');
290
+ }
291
+ },
292
+
293
+ hasSprites (style) {
294
+ return style.texture && Texture.textures[style.texture] && Texture.textures[style.texture].sprites;
295
+ },
296
+
297
+ // Generate a sprite_info object
298
+ getSpriteInfo (style, sprite) {
299
+ const textureName = style.texture;
300
+ if (!textureName) return undefined;
301
+ const key = String(sprite);
302
+ let info = Texture.textures[textureName]?.sprites?.[key] && Texture.getSpriteInfo(textureName, key);
303
+ if (info && !isPointSizeImage(info)) throw new TypeError('Sprite dimensions are not initialized');
304
+ if (sprite && !info) {
305
+ // track missing sprites (per texture)
306
+ this.texture_missing_sprites[textureName] = this.texture_missing_sprites[textureName] || {};
307
+ if (!this.texture_missing_sprites[textureName][key]) { // only log each missing sprite once
308
+ log('debug', `Style: in style '${this.name}', could not find sprite '${sprite}' for texture '${style.texture}'`);
309
+ this.texture_missing_sprites[textureName][key] = true;
310
+ }
311
+ }
312
+ else if (info) {
313
+ Object.assign(info, {sprite});
314
+ }
315
+ return info;
316
+ },
317
+
318
+ // Check a sprite name against available sprites and return a sprite_info object
319
+ parseSprite (style, draw, context) {
320
+ // check for functions
321
+ let sprite = StyleParser.evalProperty(draw.sprite, context);
322
+ let sprite_info = this.getSpriteInfo(style, sprite as string | undefined) || this.getSpriteInfo(style, draw.sprite_default);
323
+ return sprite_info;
324
+ },
325
+
326
+ // Queue features for deferred processing (collect all features first so we can do collision on the whole group)
327
+ queueFeature (q, tile) {
328
+ if (!this.tile_data[tile.id] || !this.queues[tile.id]) {
329
+ this.startData(tile);
330
+ }
331
+ this.queues[tile.id] = this.queues[tile.id] || [];
332
+ this.queues[tile.id].push(q);
333
+ },
334
+
335
+ // Override
336
+ async endData (tile) {
337
+ if (tile.canceled) {
338
+ log('trace', `Style ${this.name}: stop tile build because tile was canceled: ${tile.key}`);
339
+ return null;
340
+ }
341
+
342
+ let queue = this.queues[tile.id];
343
+ delete this.queues[tile.id];
344
+
345
+ // For each point feature, create one or more labels
346
+ let text_objs: AttachedTextQueue[] = [];
347
+ let point_objs: PointCandidate[] = [];
348
+
349
+ queue.forEach(q => {
350
+ let style = q.style;
351
+ let feature = q.feature;
352
+ let geometry = feature.geometry;
353
+ const size = style.size;
354
+ if (!isPointPair(size) || !isCommonLayout(style)) throw new TypeError('Queued point layout is incomplete');
355
+ let feature_labels = this.buildLabels(size, geometry, style);
356
+ for (let i = 0; i < feature_labels.length; i++) {
357
+ let label = feature_labels[i];
358
+ let point_obj: PointCandidate = {
359
+ feature,
360
+ draw: q.draw,
361
+ context: q.context,
362
+ style,
363
+ label
364
+ };
365
+ point_objs.push(point_obj);
366
+
367
+ if (q.text_feature) {
368
+ let text_obj = {
369
+ feature,
370
+ draw: q.text_feature.draw,
371
+ context: q.context,
372
+ text: q.text_feature.text,
373
+ text_settings_key: q.text_feature.text_settings_key,
374
+ layout: q.text_feature.layout,
375
+ point_label: label,
376
+ linked: point_obj // link so text only renders when parent point is placed
377
+ };
378
+ text_objs.push(text_obj);
379
+
380
+ // Unless text feature is optional, create a two-way link so that parent
381
+ // point will only render when text is also placed
382
+ if (!q.draw.text?.optional) {
383
+ point_obj.linked = text_obj; // two-way link
384
+ }
385
+ }
386
+ }
387
+ });
388
+
389
+ // Collide both points and text, then build features
390
+ const [, { labels, texts, textures }] = await Promise.all([
391
+ // Points
392
+ Collision.collide(point_objs, this.collision_group_points, tile.id).then(point_objs => {
393
+ point_objs.forEach(q => {
394
+ this.feature_style = q.style;
395
+ this.feature_style.label = q.label;
396
+ this.feature_style.linked = q.linked; // TODO: move linked into label to avoid extra prop tracking?
397
+ Style.addFeature.call(this, q.feature, q.draw, q.context);
398
+ });
399
+ }),
400
+ // Labels
401
+ this.collideAndRenderTextLabels(tile, this.collision_group_text, text_objs)
402
+ ]);
403
+
404
+ // Process labels
405
+ if (labels && texts) {
406
+ // Build queued features
407
+ labels.forEach(q => {
408
+ let text_settings_key = q.text_settings_key;
409
+ let text_info = texts[text_settings_key] && texts[text_settings_key][q.text];
410
+ const alignment = q.label.align === undefined ? undefined : text_info?.align?.[q.label.align];
411
+ if (!text_info?.size || !alignment || alignment.texture_id === undefined || !textures) throw new TypeError('Attached text atlas is incomplete');
412
+
413
+ // setup styling object expected by Style class
414
+ let style = this.feature_style;
415
+ style.label = q.label;
416
+ // The shared feature_style belongs to the last point, not necessarily this text.
417
+ if (this.cpu_projection) style.z = (q.layout.projected_height ?? 0) * PACKED_HEIGHT_SCALE;
418
+ style.linked = q.linked; // TODO: move linked into label to avoid extra prop tracking?
419
+ style.size = text_info.size.logical_size;
420
+ style.texcoords = alignment.texcoords;
421
+ style.label_texture = textures[alignment.texture_id];
422
+ style.blend_order = q.draw.blend_order; // copy blend order from parent point
423
+
424
+ Style.addFeature.call(this, q.feature, q.draw, q.context);
425
+ });
426
+ }
427
+ this.freeText(tile);
428
+
429
+ // Finish tile mesh
430
+ const tile_data = await Style.endData.call(this, tile);
431
+ // Attach tile-specific label atlas to mesh as a texture uniform
432
+ if (tile_data && textures && textures.length) {
433
+ tile_data.textures = tile_data.textures || [];
434
+ tile_data.textures.push(...textures); // assign texture ownership to tile
435
+ }
436
+ return tile_data;
437
+ },
438
+
439
+ _preprocess (draw) {
440
+ draw.color = StyleParser.createColorPropertyCache(draw.color);
441
+ draw.alpha = StyleParser.createPropertyCache(draw.alpha);
442
+ draw.texture = (draw.texture !== undefined ? draw.texture : this.texture); // optional or default texture
443
+ draw.blend_order = this.getBlendOrderForDraw(draw); // from draw block, or fall back on default style blend order
444
+
445
+ if (draw.outline) {
446
+ draw.outline.color = StyleParser.createColorPropertyCache(draw.outline.color);
447
+ draw.outline.alpha = StyleParser.createPropertyCache(draw.outline.alpha);
448
+ draw.outline.width = StyleParser.createPropertyCache(draw.outline.width, StyleParser.parsePositiveNumber);
449
+ }
450
+
451
+ draw.z = this.cpu_projection ? StyleParser.createProjectedHeightPropertyCache(draw.z) :
452
+ StyleParser.createPropertyCache(draw.z, StyleParser.parseUnits);
453
+
454
+ // Size (1d value or 2d array)
455
+ try {
456
+ draw.size = StyleParser.createPointSizePropertyCache(draw.size, draw.texture);
457
+ }
458
+ catch(e) {
459
+ log({ level: 'warn', once: true }, `Layer group '${draw.layers?.join(', ')}': ` +
460
+ `${e} (${JSON.stringify(draw.size)}), features that match this layer group won't be drawn.`);
461
+ return null;
462
+ }
463
+
464
+ // Offset (2d array)
465
+ draw.offset = StyleParser.createPropertyCache(draw.offset,
466
+ v => Array.isArray(v) && v.map(StyleParser.parseNumber)
467
+ );
468
+
469
+ // Buffer (1d value or or 2d array) - must be >= 0
470
+ draw.buffer = StyleParser.createPropertyCache(draw.buffer,
471
+ v => (Array.isArray(v) ? v : [v, v]).map(StyleParser.parsePositiveNumber)
472
+ );
473
+
474
+ // Repeat rules - no repeat limitation for points by default
475
+ draw.repeat_distance = StyleParser.createPropertyCache(draw.repeat_distance, StyleParser.parseNumber);
476
+
477
+ // Placement strategies
478
+ draw.placement = PLACEMENT[typeof draw.placement === 'string' ? draw.placement.toUpperCase() : ''];
479
+ if (draw.placement == null) {
480
+ draw.placement = PLACEMENT.VERTEX;
481
+ }
482
+
483
+ draw.placement_spacing = StyleParser.createPropertyCache(draw.placement_spacing != null ? draw.placement_spacing : 80, StyleParser.parsePositiveNumber);
484
+
485
+ draw.placement_min_length_ratio = StyleParser.createPropertyCache(draw.placement_min_length_ratio != null ? draw.placement_min_length_ratio : 1, StyleParser.parsePositiveNumber);
486
+
487
+ if (typeof draw.angle === 'number') {
488
+ draw.angle = draw.angle * Math.PI / 180; // convert static value to radians
489
+ }
490
+ else if (typeof draw.angle === 'function') {
491
+ // convert function return value to radians
492
+ const angle_func = draw.angle;
493
+ draw.angle = context => angle_func(context) * Math.PI / 180;
494
+ }
495
+ else {
496
+ draw.angle = draw.angle || 0; // angle can be a string like "auto" (use angle of geometry)
497
+ }
498
+
499
+ // Optional text styling
500
+ draw.text = draw.text ? this.preprocessText(draw.text) : undefined; // will return null if valid text styling wasn't provided
501
+ if (draw.text) {
502
+ draw.text.key = draw.key; // inherits parent properties
503
+ draw.text.group = draw.group;
504
+ draw.text.layers = draw.layers;
505
+ draw.text.order = draw.order;
506
+ draw.text.blend_order = draw.blend_order;
507
+ draw.text.repeat_group = (draw.text.repeat_group != null ? draw.text.repeat_group : draw.repeat_group);
508
+ draw.text.anchor = draw.text.anchor || this.default_anchor;
509
+ draw.text.optional = (typeof draw.text.optional === 'boolean') ? draw.text.optional : false; // default text to required
510
+ draw.text.interactive = draw.text.interactive || draw.interactive; // inherits from point
511
+ }
512
+
513
+ return draw;
514
+ },
515
+
516
+ // Default to trying all anchor placements
517
+ default_anchor: ['bottom', 'top', 'right', 'left'],
518
+
519
+ // Compute label layout-related properties
520
+ computeLayout (target, feature, draw, context, tile) {
521
+ let layout = target || {};
522
+ layout.id = feature;
523
+ layout.units_per_pixel = tile.units_per_pixel || 1;
524
+
525
+ // collision flag
526
+ layout.collide = (draw.collide === false) ? false : true;
527
+
528
+ // label anchors (point labels only)
529
+ // label position will be adjusted in the given direction, relative to its original point
530
+ // one of: left, right, top, bottom, top-left, top-right, bottom-left, bottom-right
531
+ layout.anchor = draw.anchor;
532
+
533
+ // label offset and buffer in pixel (applied in screen space)
534
+ layout.offset = (StyleParser.evalCachedProperty(draw.offset, context) || StyleParser.zeroPair) as LabelPointCoordinate;
535
+ layout.buffer = (StyleParser.evalCachedProperty(draw.buffer, context) || StyleParser.zeroPair) as LabelPointCoordinate;
536
+
537
+ // repeat rules
538
+ layout.repeat_distance = StyleParser.evalCachedProperty(draw.repeat_distance, context) as number | undefined;
539
+ if (layout.repeat_distance) {
540
+ layout.repeat_distance *= layout.units_per_pixel;
541
+ layout.repeat_scale = 1; // initial repeat pass in tile with full scale
542
+
543
+ if (typeof draw.repeat_group === 'function') {
544
+ layout.repeat_group = draw.repeat_group(context); // dynamic repeat group
545
+ }
546
+ else {
547
+ // default to top-level layer name
548
+ // (e.g. all labels under `roads` layer, including sub-layers, are in one repeat group)
549
+ layout.repeat_group = draw.repeat_group || context.layer;
550
+ }
551
+ }
552
+
553
+ // label priority (lower is higher)
554
+ let priority = draw.priority;
555
+ if (priority != null) {
556
+ if (typeof priority === 'function') {
557
+ priority = priority(context);
558
+ }
559
+ }
560
+ else {
561
+ priority = -1 >>> 0; // default to max priority value if none set
562
+ }
563
+ layout.priority = priority;
564
+
565
+ if (this.screen_space_labels || this.cpu_projection) {
566
+ // Keep all candidates until the main thread has the projected camera.
567
+ layout.projected_collide = layout.collide;
568
+ layout.collide = false;
569
+ const identity = feature.id ?? feature.properties?.id;
570
+ if (typeof identity === 'string' || typeof identity === 'number') {
571
+ layout.projected_identity = JSON.stringify([context.source, context.layer, this.name, draw.key,
572
+ typeof identity, identity]);
573
+ }
574
+ }
575
+
576
+ if (!isCommonLayout(layout)) throw new TypeError('Point layout is incomplete');
577
+ return layout;
578
+ },
579
+
580
+ // Implements label building for TextLabels mixin
581
+ buildTextLabels (tile, feature_queue) {
582
+ let labels = [];
583
+ for (let f=0; f < feature_queue.length; f++) {
584
+ let fq = feature_queue[f];
585
+ if (!('point_label' in fq) || !(fq.point_label instanceof LabelPoint)) throw new TypeError('Attached text has no parent label');
586
+ let text_info = this.texts[tile.id][fq.text_settings_key][fq.text];
587
+ if (!text_info?.size) throw new TypeError('Attached text measurement is incomplete');
588
+ let size = text_info.size.collision_size;
589
+ labels.push(Object.assign(fq, {label: new LabelPoint(fq.point_label.position, size, fq.layout)}));
590
+ }
591
+ return labels;
592
+ },
593
+
594
+ // Builds one or more point labels for a geometry
595
+ buildLabels (size, geometry, layout) {
596
+ let labels = [];
597
+
598
+ if (geometry.type === 'Point') {
599
+ labels.push(new LabelPoint(geometry.coordinates, size, layout, layout.angle as number | undefined));
600
+ }
601
+ else if (geometry.type === 'MultiPoint') {
602
+ let points = geometry.coordinates;
603
+ for (let i = 0; i < points.length; ++i) {
604
+ let point = points[i];
605
+ labels.push(new LabelPoint(point, size, layout, layout.angle as number | undefined));
606
+ }
607
+ }
608
+ else if (geometry.type === 'LineString') {
609
+ let line = geometry.coordinates;
610
+ let point_labels = placePointsOnLine(line, size, layout);
611
+ for (let i = 0; i < point_labels.length; ++i) {
612
+ labels.push(point_labels[i]);
613
+ }
614
+ }
615
+ else if (geometry.type === 'MultiLineString') {
616
+ let lines = geometry.coordinates;
617
+ for (let ln = 0; ln < lines.length; ln++) {
618
+ let line = lines[ln];
619
+ let point_labels = placePointsOnLine(line, size, layout);
620
+ for (let i = 0; i < point_labels.length; ++i) {
621
+ labels.push(point_labels[i]);
622
+ }
623
+ }
624
+ }
625
+ else if (geometry.type === 'Polygon') {
626
+ // Point at polygon centroid (of outer ring)
627
+ if (layout.placement === PLACEMENT.CENTROID) {
628
+ let centroid = Geo.centroid(geometry.coordinates);
629
+ if (centroid) { // skip degenerate polygons
630
+ labels.push(new LabelPoint([centroid[0], centroid[1]], size, layout, layout.angle as number | undefined));
631
+ }
632
+ }
633
+ // Point at each polygon vertex (all rings)
634
+ else {
635
+ let rings = geometry.coordinates;
636
+ for (let ln = 0; ln < rings.length; ln++) {
637
+ let point_labels = placePointsOnLine(rings[ln], size, layout);
638
+ for (let i = 0; i < point_labels.length; ++i) {
639
+ labels.push(point_labels[i]);
640
+ }
641
+ }
642
+ }
643
+ }
644
+ else if (geometry.type === 'MultiPolygon') {
645
+ if (layout.placement === PLACEMENT.CENTROID) {
646
+ let centroid = Geo.multiCentroid(geometry.coordinates);
647
+ if (centroid) { // skip degenerate polygons
648
+ labels.push(new LabelPoint([centroid[0], centroid[1]], size, layout, layout.angle as number | undefined));
649
+ }
650
+ }
651
+ else {
652
+ let polys = geometry.coordinates;
653
+ for (let p = 0; p < polys.length; p++) {
654
+ let rings = polys[p];
655
+ for (let ln = 0; ln < rings.length; ln++) {
656
+ let point_labels = placePointsOnLine(rings[ln], size, layout);
657
+ for (let i = 0; i < point_labels.length; ++i) {
658
+ labels.push(point_labels[i]);
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ return labels;
666
+ },
667
+
668
+ /**
669
+ * A "template" that sets constant attibutes for each vertex, which is then modified per vertex or per feature.
670
+ * A plain JS array matching the order of the vertex layout.
671
+ */
672
+ makeVertexTemplate(style, mesh, add_custom_attribs = true) {
673
+ if (!style.label) throw new TypeError('Point vertex template requires a prepared label');
674
+ const portable = mesh.vertex_data.vertex_layout.index.a_point_type != null;
675
+ let i = 0;
676
+
677
+ // a_position.xyz - vertex position
678
+ // a_position.w - layer order
679
+ this.vertex_template[i++] = 0;
680
+ this.vertex_template[i++] = 0;
681
+ this.vertex_template[i++] = style.z || 0;
682
+ this.vertex_template[i++] = this.scaleOrder(Number(style.order));
683
+
684
+ // a_shape.xy - size of point in pixels (scaling vector)
685
+ // a_shape.z - angle of point
686
+ // a_shape.w - show/hide flag
687
+ this.vertex_template[i++] = 0;
688
+ this.vertex_template[i++] = 0;
689
+ this.vertex_template[i++] = 0;
690
+ this.vertex_template[i++] = style.label.layout.collide ? 0 : 1; // set initial label hide/show state
691
+
692
+ // a_texcoord.xy - texture coords
693
+ if (!mesh.variant.shader_point || portable) {
694
+ this.vertex_template[i++] = 0;
695
+ this.vertex_template[i++] = 0;
696
+ }
697
+
698
+ // a_offset.xy - offset of point from center, in pixels
699
+ this.vertex_template[i++] = 0;
700
+ this.vertex_template[i++] = 0;
701
+
702
+ // a_color.rgba - feature color
703
+ const color = style.color || StyleParser.defaults.color;
704
+ this.vertex_template[i++] = color[0] * 255;
705
+ this.vertex_template[i++] = color[1] * 255;
706
+ this.vertex_template[i++] = color[2] * 255;
707
+ this.vertex_template[i++] = (style.alpha != null ? style.alpha : color[3]) * 255;
708
+
709
+ // a_selection_color.rgba - selection color
710
+ if (mesh.variant.selection) {
711
+ if (!style.selection_color) throw new TypeError('Interactive point has no selection color');
712
+ this.vertex_template[i++] = style.selection_color[0] * 255;
713
+ this.vertex_template[i++] = style.selection_color[1] * 255;
714
+ this.vertex_template[i++] = style.selection_color[2] * 255;
715
+ this.vertex_template[i++] = style.selection_color[3] * 255;
716
+ }
717
+
718
+ // point outline
719
+ if (mesh.variant.shader_point || portable) {
720
+ // a_outline_color.rgba - outline color
721
+ const outline_color = style.outline_color || StyleParser.defaults.outline.color;
722
+ this.vertex_template[i++] = outline_color[0] * 255;
723
+ this.vertex_template[i++] = outline_color[1] * 255;
724
+ this.vertex_template[i++] = outline_color[2] * 255;
725
+ this.vertex_template[i++] = (style.outline_alpha != null ? style.outline_alpha : outline_color[3]) * 255;
726
+
727
+ // a_outline_edge - point outline edge (as % of point size where outline begins)
728
+ this.vertex_template[i++] = style.outline_edge_pct || StyleParser.defaults.outline.width;
729
+ }
730
+
731
+ if (portable) {
732
+ this.vertex_template[i++] = Number(mesh.variant.point_type);
733
+ }
734
+
735
+ if (this.cpu_projection) {
736
+ const projected = mesh.vertex_data.vertex_layout.index.a_projected_position;
737
+ for (let component = 0; component < 3; component++) this.vertex_template[projected + component] = 0;
738
+ }
739
+ if (add_custom_attribs) {
740
+ this.addCustomAttributesToVertexTemplate(style, i);
741
+ }
742
+ return this.vertex_template;
743
+ },
744
+
745
+ buildQuad(point, size, angle, angles, pre_angles, offset, offsets, texcoords, curve, vertex_data, vertex_template) {
746
+ if (size[0] <= 0 || size[1] <= 0) {
747
+ return 0; // size must be positive
748
+ }
749
+
750
+ return buildQuadForPoint(
751
+ point,
752
+ vertex_data,
753
+ vertex_template,
754
+ vertex_data.vertex_layout.index,
755
+ size,
756
+ offset,
757
+ offsets,
758
+ pre_angles,
759
+ angle * 4096, // angle values have a 12-bit fraction
760
+ angles,
761
+ texcoords,
762
+ curve
763
+ );
764
+ },
765
+
766
+ // Build quad for point sprite
767
+ build (style, context) {
768
+ let label = style.label;
769
+ if (!label) throw new TypeError('Point geometry requires a prepared label');
770
+ if (label.type === 'curved') {
771
+ return this.buildCurvedLabel(label, style, context);
772
+ }
773
+ else {
774
+ return this.buildStraightLabel(label, style, context);
775
+ }
776
+ },
777
+
778
+ buildStraightLabel (label, style, context) {
779
+ let mesh = this.getTileMesh(context.tile, this.meshVariantTypeForDraw(style));
780
+ let vertex_template = this.makeVertexTemplate(style, mesh);
781
+
782
+ let size, texcoords;
783
+ if (label.type !== 'point') {
784
+ size = !Array.isArray(style.size) ? style.size?.straight : undefined;
785
+ texcoords = !Array.isArray(style.texcoords) ? style.texcoords?.straight?.texcoord : undefined;
786
+ if (!size || !texcoords) throw new TypeError('Straight text dimensions and atlas coordinates are missing');
787
+ }
788
+ else {
789
+ if (!isPointPair(style.size)) throw new TypeError('Point dimensions are missing');
790
+ size = style.size;
791
+ texcoords = Array.isArray(style.texcoords) ? style.texcoords : undefined;
792
+ }
793
+
794
+ // setup style or label texture if applicable
795
+ mesh.uniforms = mesh.uniforms || {};
796
+ if (style.label_texture) {
797
+ mesh.uniforms.u_texture = style.label_texture;
798
+ mesh.uniforms.u_point_type = TANGRAM_POINT_TYPE_LABEL;
799
+ mesh.uniforms.u_apply_color_blocks = false;
800
+ }
801
+ else if (style.texture) {
802
+ mesh.uniforms.u_texture = style.texture;
803
+ mesh.uniforms.u_point_type = TANGRAM_POINT_TYPE_TEXTURE;
804
+ mesh.uniforms.u_apply_color_blocks = true;
805
+ }
806
+ else {
807
+ mesh.uniforms.u_texture = Texture.default; // ensure a tetxure is always bound to avoid GL warnings ('no texture bound to unit' in Chrome)
808
+ mesh.uniforms.u_point_type = TANGRAM_POINT_TYPE_SHADER;
809
+ mesh.uniforms.u_apply_color_blocks = true;
810
+ }
811
+
812
+ let offset = label.offset;
813
+
814
+ // TODO: instead of passing null, pass arrays with fingerprintable values
815
+ // This value is checked in the shader to determine whether to apply curving logic
816
+ let geom_count = this.buildQuad(
817
+ label.position, // position
818
+ size, // size in pixels
819
+ label.angle, // angle in radians
820
+ null, // placeholder for multiple angles
821
+ null, // placeholder for multiple pre_angles
822
+ offset, // offset from center in pixels
823
+ null, // placeholder for multiple offsets
824
+ texcoords, // texture UVs
825
+ false, // if curved boolean
826
+ mesh.vertex_data, vertex_template // VBO and data for current vertex
827
+ );
828
+
829
+ // track label mesh buffer data
830
+ const linked = getLinkedLabelId(style.linked);
831
+ this.trackLabel(label, linked, mesh, geom_count, context);
832
+
833
+ return geom_count;
834
+ },
835
+
836
+ buildCurvedLabel (label, style, context) {
837
+ const sizes = !Array.isArray(style.size) ? style.size?.curved : undefined;
838
+ const texcoords = !Array.isArray(style.texcoords) ? style.texcoords?.curved : undefined;
839
+ const strokes = style.texcoords_stroke, textures = style.label_textures;
840
+ if (!sizes || !texcoords || !strokes || !textures) throw new TypeError('Curved text dimensions and atlas coordinates are missing');
841
+ let mesh, vertex_template;
842
+ let geom_count = 0;
843
+
844
+ // two passes for stroke and fill, where stroke needs to be drawn first (painter's algorithm)
845
+ // this ensures strokes don't overlap on other fills
846
+
847
+ // pass for stroke
848
+ for (let i = 0; i < label.num_segments; i++){
849
+ let size = sizes[i];
850
+ let texcoord_stroke = strokes[i];
851
+
852
+ // re-point to correct label texture
853
+ style.label_texture = textures[i];
854
+ mesh = this.getTileMesh(context.tile, this.meshVariantTypeForDraw(style));
855
+ vertex_template = this.makeVertexTemplate(style, mesh);
856
+
857
+ // add label texture uniform if needed
858
+ mesh.uniforms = mesh.uniforms || {};
859
+ mesh.uniforms.u_texture = style.label_texture;
860
+ mesh.uniforms.u_point_type = TANGRAM_POINT_TYPE_LABEL;
861
+ mesh.uniforms.u_apply_color_blocks = false;
862
+
863
+ let offset = label.offset || [0,0];
864
+ let position = label.position;
865
+
866
+ let angles = label.angles[i];
867
+ let offsets = label.offsets[i];
868
+ let pre_angles = label.pre_angles[i];
869
+
870
+ let seg_count = this.buildQuad(
871
+ position, // position
872
+ size, // size in pixels
873
+ label.angle, // angle in degrees
874
+ angles, // angles per segment
875
+ pre_angles, // pre_angle array (rotation applied before offseting)
876
+ offset, // offset from center in pixels
877
+ offsets, // offsets per segment
878
+ texcoord_stroke, // texture UVs for stroked text
879
+ true, // if curved
880
+ mesh.vertex_data, vertex_template // VBO and data for current vertex
881
+ );
882
+ geom_count += seg_count;
883
+
884
+ // track label mesh buffer data
885
+ const linked = getLinkedLabelId(style.linked);
886
+ this.trackLabel(label, linked, mesh, seg_count, context);
887
+ }
888
+
889
+ // pass for fill
890
+ for (let i = 0; i < label.num_segments; i++){
891
+ let size = sizes[i];
892
+ let texcoord = texcoords[i].texcoord;
893
+
894
+ // re-point to correct label texture
895
+ style.label_texture = textures[i];
896
+ mesh = this.getTileMesh(context.tile, this.meshVariantTypeForDraw(style));
897
+ vertex_template = this.makeVertexTemplate(style, mesh);
898
+
899
+ // add label texture uniform if needed
900
+ mesh.uniforms = mesh.uniforms || {};
901
+ mesh.uniforms.u_texture = style.label_texture;
902
+ mesh.uniforms.u_point_type = TANGRAM_POINT_TYPE_LABEL;
903
+ mesh.uniforms.u_apply_color_blocks = false;
904
+
905
+ let offset = label.offset || [0,0];
906
+ let position = label.position;
907
+
908
+ let angles = label.angles[i];
909
+ let offsets = label.offsets[i];
910
+ let pre_angles = label.pre_angles[i];
911
+
912
+ let seg_count = this.buildQuad(
913
+ position, // position
914
+ size, // size in pixels
915
+ label.angle, // angle in degrees
916
+ angles, // angles per segment
917
+ pre_angles, // pre_angle array (rotation applied before offseting)
918
+ offset, // offset from center in pixels
919
+ offsets, // offsets per segment
920
+ texcoord, // texture UVs for fill text
921
+ true, // if curved
922
+ mesh.vertex_data, vertex_template // VBO and data for current vertex
923
+ );
924
+ geom_count += seg_count;
925
+
926
+ // track label mesh buffer data
927
+ const linked = getLinkedLabelId(style.linked);
928
+ this.trackLabel(label, linked, mesh, seg_count, context);
929
+ }
930
+
931
+ return geom_count;
932
+ },
933
+
934
+ // track mesh data for label on main thread, for additional cross-tile collision/repeat passes
935
+ trackLabel (label, linked, mesh, geom_count/*, context*/) {
936
+ // track if collision is enabled, or if the label is near enough to the tile edge to
937
+ // necessitate further repeat checking
938
+ if (this.screen_space_labels || this.cpu_projection || label.layout.collide || label.may_repeat_across_tiles) {
939
+ mesh.labels = mesh.labels || {};
940
+ mesh.labels[label.id] = mesh.labels[label.id] || {
941
+ container: {
942
+ label: label.toJSON(),
943
+ linked,
944
+ },
945
+ ranges: [],
946
+ // debug: { // uncomment and pass in context for debugging
947
+ // id: context.feature.properties.id,
948
+ // name: context.feature.properties.name,
949
+ // props: JSON.stringify(context.feature.properties),
950
+ // point_type: mesh.uniforms.u_point_type
951
+ // }
952
+ };
953
+
954
+ // store byte ranges occupied by label in VBO, so they can be updated on main thread
955
+ const vertex_count = geom_count * 2; // geom count is triangles: 2 triangles = 1 quad = 4 vertices
956
+ const start = mesh.vertex_data.offset - mesh.vertex_data.stride * vertex_count; // start offset of byte range
957
+ mesh.labels[label.id].ranges.push([
958
+ start,
959
+ vertex_count
960
+ ]);
961
+ }
962
+ },
963
+
964
+ // Override to pass-through to generic point builder
965
+ buildLines (lines, style, context) {
966
+ return this.build(style, context);
967
+ },
968
+
969
+ buildPoints (points, style, context) {
970
+ return this.build(style, context);
971
+ },
972
+
973
+ buildPolygons (points, style, context) {
974
+ return this.build(style, context);
975
+ },
976
+
977
+ // Override
978
+ // Create or return desired vertex layout permutation based on flags
979
+ vertexLayoutForMeshVariant (variant) {
980
+ if (this.shader_language === 'wgsl') {
981
+ if (this.vertex_layouts.portable == null) {
982
+ this.vertex_layouts.portable = new VertexLayout([
983
+ { name: 'a_position', size: 4, type: gl.SHORT, normalized: false },
984
+ { name: 'a_shape', size: 4, type: gl.SHORT, normalized: false },
985
+ { name: 'a_texcoord', size: 2, type: gl.UNSIGNED_SHORT, normalized: true },
986
+ { name: 'a_offset', size: 2, type: gl.SHORT, normalized: false },
987
+ { name: 'a_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
988
+ { name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
989
+ { name: 'a_outline_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
990
+ { name: 'a_outline_edge', size: 1, type: gl.FLOAT, normalized: false },
991
+ { name: 'a_point_type', size: 1, type: gl.FLOAT, normalized: false },
992
+ ...(this.cpu_projection ? [{name: 'a_projected_position', size: 3, type: gl.FLOAT, normalized: false}] : [])
993
+ ]);
994
+ }
995
+ return this.vertex_layouts.portable;
996
+ }
997
+
998
+ // Vertex layout only depends on shader point flag, so using it as layout key to avoid duplicate layouts
999
+ const layoutKey = String(variant.shader_point);
1000
+ if (this.vertex_layouts[layoutKey] == null) {
1001
+ // Attributes for this mesh variant
1002
+ // Optional attributes have placeholder values assigned with `static` parameter
1003
+ // TODO: could support optional attributes for selection and offset, but may not be worth it
1004
+ // since points generally don't consume much memory anyway
1005
+ const attribs = [
1006
+ { name: 'a_position', size: 4, type: gl.SHORT, normalized: false },
1007
+ { name: 'a_shape', size: 4, type: gl.SHORT, normalized: false },
1008
+ { name: 'a_texcoord', size: 2, type: gl.UNSIGNED_SHORT, normalized: true, static: (variant.shader_point ? [0, 0] : null) },
1009
+ { name: 'a_offset', size: 2, type: gl.SHORT, normalized: false },
1010
+ { name: 'a_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
1011
+ { name: 'a_selection_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true, static: (variant.selection ? null : [0, 0, 0, 0]) },
1012
+ { name: 'a_outline_color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true, static: (variant.shader_point ? null : [0, 0, 0, 0]) },
1013
+ { name: 'a_outline_edge', size: 1, type: gl.FLOAT, normalized: false, static: (variant.shader_point ? null : 0) }
1014
+ ];
1015
+
1016
+ this.addCustomAttributesToAttributeList(attribs);
1017
+ if (this.cpu_projection) attribs.push({name: 'a_projected_position', size: 3, type: gl.FLOAT, normalized: false});
1018
+ this.vertex_layouts[layoutKey] = new VertexLayout(attribs);
1019
+ }
1020
+ return this.vertex_layouts[layoutKey];
1021
+
1022
+ },
1023
+
1024
+ // Override
1025
+ meshVariantTypeForDraw (draw) {
1026
+ const texture = draw.label_texture || draw.texture || SHADER_POINT_VARIANT; // unique key by texture name
1027
+ const key = texture + '/' + draw.blend_order;
1028
+ if (this.variants[key] == null) {
1029
+ this.variants[key] = {
1030
+ key,
1031
+ selection: 1, // TODO: make this vary by draw params
1032
+ shader_point: (texture === SHADER_POINT_VARIANT), // is shader point
1033
+ point_type: draw.label_texture ? TANGRAM_POINT_TYPE_LABEL :
1034
+ draw.texture ? TANGRAM_POINT_TYPE_TEXTURE : TANGRAM_POINT_TYPE_SHADER,
1035
+ blend_order: draw.blend_order,
1036
+ mesh_order: (draw.label_texture ? 1 : 0) // put text on top of points (e.g. for highway shields, etc.)
1037
+ };
1038
+ }
1039
+ return this.variants[key]; // return pre-calculated mesh variant
1040
+ },
1041
+
1042
+ // Override
1043
+ makeMesh (vertex_data, vertex_elements, options = {}) {
1044
+ // Add label fade time
1045
+ options = Object.assign({}, options, { fade_in_time: this.fade_in_time });
1046
+ return Style.makeMesh.call(this, vertex_data, vertex_elements, options);
1047
+ }
1048
+
1049
+ };
1050
+
1051
+ Object.assign(Points, pointMethods);
1052
+
1053
+ /** Cached point/sprite dimensions are a mutable numeric pair, retained without copying. */
1054
+ function isPointPair(value: unknown): value is LabelPointCoordinate {
1055
+ return Array.isArray(value) && value.length === 2 && typeof value[0] === 'number' && typeof value[1] === 'number';
1056
+ }
1057
+
1058
+ /** Validate only the geometry consumed by an attached text anchor, preserving parent identity. */
1059
+ function isPointParent(style: PointFeatureStyle): style is PointFeatureStyle & import('../../labels/label-types').LabelParent {
1060
+ return isPointPair(style.size) && isPointPair(style.offset);
1061
+ }
1062
+
1063
+ /** Shared layout fields are filled before point construction; text-only fields are optional. */
1064
+ function isCommonLayout(layout: Partial<import('../../labels/label-types').LabelLayout>): layout is import('../../labels/label-types').LabelLayout {
1065
+ return typeof layout.priority === 'number' && typeof layout.units_per_pixel === 'number' && isPointPair(layout.offset) && isPointPair(layout.buffer);
1066
+ }
1067
+
1068
+ /** Worker texture metadata contains the same dimensional capabilities as live textures. */
1069
+ function isPointSizeImage(value: object): value is import('../property-types').PointSizeImage {
1070
+ return 'css_size' in value && Array.isArray(value.css_size) && 'aspect' in value && typeof value.aspect === 'number';
1071
+ }
1072
+
1073
+ /** Preserve legacy falsy link identities while admitting only serializable label IDs. */
1074
+ function getLinkedLabelId(link: import('../../labels/collision-types').PendingCollisionObject | null | 0 | '' | undefined): number | string | null | undefined {
1075
+ if (!link) return link;
1076
+ const label = link.label;
1077
+ const identity = label && 'id' in label ? label.id : undefined;
1078
+ return typeof identity === 'number' || typeof identity === 'string' ? identity : undefined;
1079
+ }