@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.
- package/README.md +60 -2
- package/dist/core.js +36923 -0
- package/dist/core.js.map +6 -0
- package/dist/index.js +32 -0
- package/dist/loaders-gl-worker.js +82 -0
- package/dist/loaders-gl-worker.js.map +6 -0
- package/dist/map-logic.js +2 -0
- package/dist/map-logic.js.map +6 -0
- package/dist/projected-basemaps-worker.js +3 -0
- package/dist/projected-basemaps-worker.js.map +6 -0
- package/dist/shader-modules.js +141 -0
- package/dist/shader-modules.js.map +6 -0
- package/dist/style-schema.js +126 -0
- package/dist/style-schema.js.map +6 -0
- package/dist/tangram-style.schema.json +882 -0
- package/dist/tangram.debug.mjs +37139 -0
- package/dist/tangram.debug.mjs.map +6 -0
- package/dist/tangram.min.mjs +4950 -0
- package/dist/types/builders/earcut.d.ts +15 -0
- package/dist/types/core.d.ts +29 -0
- package/dist/types/gl/constants.d.ts +2 -0
- package/dist/types/gl/globe_mesh.d.ts +48 -0
- package/dist/types/gl/glsl.d.ts +17 -0
- package/dist/types/gl/vertex-constants.d.ts +4 -0
- package/dist/types/gl/vertex-types.d.ts +34 -0
- package/dist/types/gl/vertex_data.d.ts +32 -0
- package/dist/types/gl/vertex_elements.d.ts +8 -0
- package/dist/types/gl/vertex_layout.d.ts +32 -0
- package/dist/types/glsl.d.ts +14 -0
- package/dist/types/gpu/luma_device_renderer.d.ts +66 -0
- package/dist/types/gpu/resource_lifecycle.d.ts +4 -0
- package/dist/types/gpu/tangram_gpu_backend.d.ts +151 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/lights/light-definitions.d.ts +121 -0
- package/dist/types/map-logic/draped-terrain-bounds.d.ts +41 -0
- package/dist/types/map-logic/globe-terrain-preparation.d.ts +85 -0
- package/dist/types/map-logic/globe-terrain.d.ts +54 -0
- package/dist/types/map-logic/index.d.ts +33 -0
- package/dist/types/map-logic/label-identity.d.ts +22 -0
- package/dist/types/map-logic/label-placement.d.ts +32 -0
- package/dist/types/map-logic/screen-bounds.d.ts +15 -0
- package/dist/types/map-logic/terrain-data.d.ts +55 -0
- package/dist/types/map-logic/terrain-error.d.ts +51 -0
- package/dist/types/map-logic/terrain-ground-topology.d.ts +26 -0
- package/dist/types/map-logic/terrain-height-field.d.ts +59 -0
- package/dist/types/map-logic/terrain-navigation.d.ts +27 -0
- package/dist/types/map-logic/terrain-neighborhood.d.ts +5 -0
- package/dist/types/map-logic/terrain-query.d.ts +33 -0
- package/dist/types/map-logic/terrain-resource-budget.d.ts +51 -0
- package/dist/types/map-logic/terrain-transition-topology.d.ts +23 -0
- package/dist/types/map-logic/tile-build-queue.d.ts +51 -0
- package/dist/types/map-logic/tile-cache-policy.d.ts +46 -0
- package/dist/types/map-logic/tile-residency.d.ts +17 -0
- package/dist/types/map-logic/triangle-query-index.d.ts +87 -0
- package/dist/types/procedures/js-yaml.d.ts +15 -0
- package/dist/types/procedures/mesh-projector.d.ts +107 -0
- package/dist/types/procedures/mvt-dependencies.d.ts +20 -0
- package/dist/types/procedures/projected-coordinate-transform.d.ts +27 -0
- package/dist/types/procedures/projection-batch-executor.d.ts +53 -0
- package/dist/types/procedures/scene-yaml-legacy.d.ts +6 -0
- package/dist/types/procedures/terrain-draping.d.ts +165 -0
- package/dist/types/procedures/terrain-globe.d.ts +6 -0
- package/dist/types/procedures/terrain-styled-topology.d.ts +34 -0
- package/dist/types/procedures/web-mercator-legacy.d.ts +4 -0
- package/dist/types/procedures/web-mercator-math.d.ts +4 -0
- package/dist/types/scene/globals.d.ts +11 -0
- package/dist/types/scene/ground_footprint.d.ts +16 -0
- package/dist/types/scene/host_frame.d.ts +54 -0
- package/dist/types/scene/projection_math.d.ts +52 -0
- package/dist/types/scene/scene-resource-types.d.ts +177 -0
- package/dist/types/scene/scene_bundle.d.ts +69 -0
- package/dist/types/scene/visibility_adapter.d.ts +106 -0
- package/dist/types/selection/draped-terrain-surface.d.ts +78 -0
- package/dist/types/selection/terrain_surface.d.ts +149 -0
- package/dist/types/shader-modules/globe-horizon.d.ts +15 -0
- package/dist/types/shader-modules/height-decode.d.ts +13 -0
- package/dist/types/shader-modules/hillshade.d.ts +13 -0
- package/dist/types/shader-modules/index.d.ts +6 -0
- package/dist/types/shader-modules/planar.d.ts +12 -0
- package/dist/types/shader-modules/sphere-map.d.ts +13 -0
- package/dist/types/shader-modules/triplanar.d.ts +15 -0
- package/dist/types/sources/decoded_tile_store.d.ts +95 -0
- package/dist/types/sources/tile_source_metadata.d.ts +32 -0
- package/dist/types/styles/text/fontfaceobserver.d.ts +10 -0
- package/dist/types/tile/tile_id.d.ts +37 -0
- package/dist/types/tile/tile_traversal_adapter.d.ts +63 -0
- package/dist/types/types.d.ts +293 -0
- package/dist/types/utils/geo.d.ts +57 -0
- package/dist/types/utils/hash.d.ts +1 -0
- package/dist/types/utils/log.d.ts +27 -0
- package/dist/types/utils/props.d.ts +6 -0
- package/dist/types/utils/subscribe.d.ts +10 -0
- package/dist/types/utils/thread.d.ts +6 -0
- package/dist/types/utils/urls.d.ts +26 -0
- package/dist/types/utils/utils.d.ts +26 -0
- package/dist/types/utils/version.d.ts +2 -0
- package/dist/types/utils/worker-types.d.ts +68 -0
- package/dist/types/utils/worker_broker.d.ts +3 -0
- package/package.json +103 -3
- package/src/builders/common.ts +41 -0
- package/src/builders/earcut.d.ts +15 -0
- package/src/builders/geometry-types.ts +60 -0
- package/src/builders/points.ts +115 -0
- package/src/builders/polygons.ts +225 -0
- package/src/builders/polylines.ts +682 -0
- package/src/builders/wireframe.ts +31 -0
- package/src/core.d.ts +29 -0
- package/src/core.ts +33 -0
- package/src/experimental/loaders-gl-worker.ts +19 -0
- package/src/experimental/projected-basemaps-worker.ts +13 -0
- package/src/experimental/projected-mesh-cache.ts +137 -0
- package/src/experimental/projected-mesh-domain.ts +110 -0
- package/src/experimental/projected-mesh-refinement.ts +193 -0
- package/src/experimental/projected-mesh.ts +399 -0
- package/src/experimental/projected-normal.ts +26 -0
- package/src/experimental/projected-symbol-mesh.ts +74 -0
- package/src/gl/constants.ts +18 -0
- package/src/gl/context.ts +103 -0
- package/src/gl/extensions.ts +30 -0
- package/src/gl/globe_mesh.ts +285 -0
- package/src/gl/glsl.ts +257 -0
- package/src/gl/mesh-types.ts +56 -0
- package/src/gl/render_state.ts +111 -0
- package/src/gl/shader_program.ts +1310 -0
- package/src/gl/texture.ts +814 -0
- package/src/gl/uniform_buffer.ts +340 -0
- package/src/gl/vao.ts +154 -0
- package/src/gl/vbo_mesh.ts +349 -0
- package/src/gl/vertex-constants.ts +19 -0
- package/src/gl/vertex-types.ts +35 -0
- package/src/gl/vertex_data.ts +126 -0
- package/src/gl/vertex_elements.ts +54 -0
- package/src/gl/vertex_layout.ts +241 -0
- package/src/glsl.d.ts +14 -0
- package/src/gpu/luma_device_renderer.ts +579 -0
- package/src/gpu/pixel_readback.ts +51 -0
- package/src/gpu/resource_lifecycle.ts +32 -0
- package/src/gpu/tangram_gpu_backend.ts +195 -0
- package/src/index.d.ts +155 -0
- package/src/index.ts +105 -0
- package/src/labels/collision-types.ts +94 -0
- package/src/labels/collision.ts +273 -0
- package/src/labels/collision_grid.ts +61 -0
- package/src/labels/intersect.ts +30 -0
- package/src/labels/label-types.ts +91 -0
- package/src/labels/label.ts +207 -0
- package/src/labels/label_line.ts +801 -0
- package/src/labels/label_point.ts +131 -0
- package/src/labels/main-pass-types.ts +73 -0
- package/src/labels/main_pass.ts +221 -0
- package/src/labels/point_anchor.ts +100 -0
- package/src/labels/point_placement.ts +150 -0
- package/src/labels/projected-pass.ts +259 -0
- package/src/labels/repeat_group.ts +72 -0
- package/src/lights/ambient_light.glsl +18 -0
- package/src/lights/directional_light.glsl +39 -0
- package/src/lights/geographic-lights.ts +21 -0
- package/src/lights/light-definitions.ts +285 -0
- package/src/lights/light.ts +590 -0
- package/src/lights/lighting-uniforms.ts +80 -0
- package/src/lights/lighting-wgsl.ts +116 -0
- package/src/lights/material.glsl +237 -0
- package/src/lights/material.ts +145 -0
- package/src/lights/native-falloff.ts +37 -0
- package/src/lights/point_light.glsl +96 -0
- package/src/lights/spot_light.glsl +111 -0
- package/src/map-logic/draped-terrain-bounds.ts +71 -0
- package/src/map-logic/globe-terrain-preparation.ts +217 -0
- package/src/map-logic/globe-terrain.ts +296 -0
- package/src/map-logic/index.ts +37 -0
- package/src/map-logic/label-identity.ts +36 -0
- package/src/map-logic/label-placement.ts +117 -0
- package/src/map-logic/screen-bounds.ts +34 -0
- package/src/map-logic/terrain-data.ts +107 -0
- package/src/map-logic/terrain-error.ts +134 -0
- package/src/map-logic/terrain-ground-topology.ts +164 -0
- package/src/map-logic/terrain-height-field.ts +254 -0
- package/src/map-logic/terrain-navigation.ts +50 -0
- package/src/map-logic/terrain-neighborhood.ts +43 -0
- package/src/map-logic/terrain-query.ts +51 -0
- package/src/map-logic/terrain-resource-budget.ts +124 -0
- package/src/map-logic/terrain-transition-topology.ts +164 -0
- package/src/map-logic/tile-build-queue.ts +110 -0
- package/src/map-logic/tile-cache-policy.ts +89 -0
- package/src/map-logic/tile-residency.ts +50 -0
- package/src/map-logic/triangle-query-index.ts +265 -0
- package/src/procedures/js-yaml.d.ts +15 -0
- package/src/procedures/mesh-projector.ts +191 -0
- package/src/procedures/mlt-loaders.ts +55 -0
- package/src/procedures/mvt-dependencies.d.ts +20 -0
- package/src/procedures/mvt-legacy.ts +150 -0
- package/src/procedures/mvt-loaders.ts +53 -0
- package/src/procedures/mvt-parser.ts +48 -0
- package/src/procedures/mvt-properties.ts +36 -0
- package/src/procedures/mvt-tile-provider.ts +72 -0
- package/src/procedures/pmtiles-loader.ts +75 -0
- package/src/procedures/projected-coordinate-transform.ts +79 -0
- package/src/procedures/projection-batch-executor.ts +140 -0
- package/src/procedures/scene-yaml-legacy.ts +16 -0
- package/src/procedures/scene-yaml-loaders.ts +10 -0
- package/src/procedures/terrain-draping.ts +713 -0
- package/src/procedures/terrain-globe.ts +32 -0
- package/src/procedures/terrain-styled-topology.ts +184 -0
- package/src/procedures/web-mercator-legacy.ts +24 -0
- package/src/procedures/web-mercator-math.ts +50 -0
- package/src/scene/camera.ts +298 -0
- package/src/scene/camera_base.ts +116 -0
- package/src/scene/classic_scene.ts +20 -0
- package/src/scene/external_camera.ts +101 -0
- package/src/scene/globals.ts +120 -0
- package/src/scene/ground_footprint.ts +139 -0
- package/src/scene/host_frame.ts +326 -0
- package/src/scene/projected_tile_lod.ts +125 -0
- package/src/scene/projection_math.ts +142 -0
- package/src/scene/projection_shaders.ts +183 -0
- package/src/scene/renderer.ts +516 -0
- package/src/scene/scene-resource-types.ts +174 -0
- package/src/scene/scene.ts +2378 -0
- package/src/scene/scene_bundle.ts +304 -0
- package/src/scene/scene_debug.ts +129 -0
- package/src/scene/scene_loader.ts +402 -0
- package/src/scene/scene_worker.ts +528 -0
- package/src/scene/view.ts +683 -0
- package/src/scene/visibility_adapter.ts +319 -0
- package/src/scene/worker_url.ts +15 -0
- package/src/selection/draped-terrain-surface.ts +377 -0
- package/src/selection/selection-types.ts +79 -0
- package/src/selection/selection.ts +542 -0
- package/src/selection/selection_fragment.glsl +18 -0
- package/src/selection/selection_globals.glsl +9 -0
- package/src/selection/selection_pixels.ts +59 -0
- package/src/selection/selection_vertex.glsl +15 -0
- package/src/selection/terrain_surface.ts +298 -0
- package/src/shader-modules/globe-horizon.ts +52 -0
- package/src/shader-modules/height-decode.ts +41 -0
- package/src/shader-modules/hillshade.ts +70 -0
- package/src/shader-modules/index.ts +10 -0
- package/src/shader-modules/planar.ts +24 -0
- package/src/shader-modules/sphere-map.ts +38 -0
- package/src/shader-modules/triplanar.ts +45 -0
- package/src/sources/aligned_tile_source.ts +122 -0
- package/src/sources/data_source.ts +655 -0
- package/src/sources/decoded_tile_store.ts +245 -0
- package/src/sources/geojson.ts +300 -0
- package/src/sources/mvt.ts +247 -0
- package/src/sources/raster.ts +341 -0
- package/src/sources/shared_tile_source_adapter.ts +117 -0
- package/src/sources/sources.ts +10 -0
- package/src/sources/tile_source_adapter.ts +84 -0
- package/src/sources/tile_source_metadata.ts +65 -0
- package/src/sources/tile_source_state.ts +112 -0
- package/src/sources/topojson.ts +90 -0
- package/src/styles/feature_annotations.ts +16 -0
- package/src/styles/filter.ts +190 -0
- package/src/styles/geometry-style-types.ts +209 -0
- package/src/styles/globe_visibility_wgsl.ts +22 -0
- package/src/styles/layer-types.ts +45 -0
- package/src/styles/layer.ts +586 -0
- package/src/styles/lines/dasharray.ts +49 -0
- package/src/styles/lines/lines.ts +707 -0
- package/src/styles/lines/lines_wgsl.ts +224 -0
- package/src/styles/points/point-runtime-types.ts +125 -0
- package/src/styles/points/points.ts +1079 -0
- package/src/styles/points/points_fragment.glsl +126 -0
- package/src/styles/points/points_vertex.glsl +230 -0
- package/src/styles/points/points_wgsl.ts +189 -0
- package/src/styles/polygons/polygons.ts +268 -0
- package/src/styles/polygons/polygons_fragment.glsl +173 -0
- package/src/styles/polygons/polygons_vertex.glsl +213 -0
- package/src/styles/polygons/polygons_wgsl.ts +127 -0
- package/src/styles/property-types.ts +125 -0
- package/src/styles/raster/raster.ts +33 -0
- package/src/styles/raster/raster_globals.glsl +46 -0
- package/src/styles/style-mixing-types.ts +101 -0
- package/src/styles/style-runtime-types.ts +220 -0
- package/src/styles/style-schema.ts +133 -0
- package/src/styles/style.ts +1033 -0
- package/src/styles/style_globals.glsl +69 -0
- package/src/styles/style_manager.ts +464 -0
- package/src/styles/style_parser.ts +620 -0
- package/src/styles/text/font_manager.ts +180 -0
- package/src/styles/text/fontfaceobserver.d.ts +10 -0
- package/src/styles/text/text-types.ts +220 -0
- package/src/styles/text/text.ts +378 -0
- package/src/styles/text/text_canvas.ts +773 -0
- package/src/styles/text/text_labels.ts +436 -0
- package/src/styles/text/text_segments.ts +142 -0
- package/src/styles/text/text_settings.ts +228 -0
- package/src/styles/text/text_wgsl.ts +159 -0
- package/src/styles/text/text_wrap.ts +194 -0
- package/src/tile/globe_tile_preload.ts +40 -0
- package/src/tile/tangram_tileset_2d.ts +108 -0
- package/src/tile/tile-build-types.ts +155 -0
- package/src/tile/tile.ts +807 -0
- package/src/tile/tile_id.ts +172 -0
- package/src/tile/tile_manager.ts +889 -0
- package/src/tile/tile_pyramid.ts +119 -0
- package/src/tile/tile_resource_cache.ts +91 -0
- package/src/tile/tile_traversal_adapter.ts +132 -0
- package/src/types.ts +332 -0
- package/src/utils/debounce.ts +17 -0
- package/src/utils/debug_settings.ts +43 -0
- package/src/utils/errors.ts +12 -0
- package/src/utils/functions.ts +95 -0
- package/src/utils/geo.ts +364 -0
- package/src/utils/hash.ts +14 -0
- package/src/utils/log.ts +108 -0
- package/src/utils/media_capture.ts +193 -0
- package/src/utils/merge.ts +44 -0
- package/src/utils/obb.ts +134 -0
- package/src/utils/props.ts +43 -0
- package/src/utils/slice.ts +14 -0
- package/src/utils/subscribe.ts +58 -0
- package/src/utils/task.ts +154 -0
- package/src/utils/thread.ts +26 -0
- package/src/utils/urls.ts +223 -0
- package/src/utils/utils.ts +310 -0
- package/src/utils/vector.ts +197 -0
- package/src/utils/version.ts +8 -0
- package/src/utils/worker-types.ts +79 -0
- package/src/utils/worker_broker.ts +523 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// tangram-layers
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
/** One consumer's interest in a decoded tile; releasing it cannot cancel another consumer. */
|
|
6
|
+
export interface DecodedTileLease<ValueT> {
|
|
7
|
+
/** Independent completion, rejected promptly when this lease is released before completion. */
|
|
8
|
+
readonly promise: Promise<ValueT>;
|
|
9
|
+
/** Whether this consumer still owns a lease, including after synchronous source invalidation. */
|
|
10
|
+
isActive(): boolean;
|
|
11
|
+
/** Idempotently release this consumer's residency and request ownership. */
|
|
12
|
+
release(): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Source revision and unwrapped normalized data identity, never a mesh/style key. */
|
|
16
|
+
export interface DecodedTileAcquisition<ValueT> {
|
|
17
|
+
/** Opaque source-instance revision, owned by the worker adapter. */
|
|
18
|
+
readonly source: object;
|
|
19
|
+
/** Normalized data coordinate key, preserving world-copy distinctions. */
|
|
20
|
+
readonly key: string;
|
|
21
|
+
/** Original acquisition/decode procedure; invoked once for compatible concurrent leases. */
|
|
22
|
+
load(signal: AbortSignal): Promise<ValueT>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Detached diagnostics; missing allocation estimates never masquerade as zero decoded bytes. */
|
|
26
|
+
export interface DecodedTileStatistics {
|
|
27
|
+
/** Pending unique acquisitions, including queued work. */
|
|
28
|
+
loadingTiles: number;
|
|
29
|
+
/** Unique acquisitions waiting for a source-procedure slot. */
|
|
30
|
+
queuedTiles: number;
|
|
31
|
+
/** Running source procedures, including cancelled procedures that have not settled. */
|
|
32
|
+
activeAcquisitions: number;
|
|
33
|
+
/** Configured source-procedure limit; undefined means unlimited. */
|
|
34
|
+
maxConcurrentLoads: number | undefined;
|
|
35
|
+
/** Ready records protected by live leases; there is no unreferenced warm cache. */
|
|
36
|
+
readyTiles: number;
|
|
37
|
+
/** Live consumer leases across pending and ready records. */
|
|
38
|
+
consumers: number;
|
|
39
|
+
/** Actual acquisition attempts since store creation. */
|
|
40
|
+
acquisitions: number;
|
|
41
|
+
/** Acquisitions served by an existing pending or ready record. */
|
|
42
|
+
sharedAcquisitions: number;
|
|
43
|
+
/** Requests cancelled when their final lease or source revision was released. */
|
|
44
|
+
cancelledAcquisitions: number;
|
|
45
|
+
/** Rejected acquisition attempts, excluding invalidated late completions. */
|
|
46
|
+
failedAcquisitions: number;
|
|
47
|
+
/** Ready payload bytes, undefined if any live ready record has unknown size. */
|
|
48
|
+
decodedBytes: number | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Request capacity is independent of styled mesh-build scheduling and residency. */
|
|
52
|
+
export interface DecodedTileStoreOptions {
|
|
53
|
+
/** Positive safe integer; omission preserves unlimited acquisition. */
|
|
54
|
+
maxConcurrentLoads?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Validate an optional load budget before creating resources or worker state. */
|
|
58
|
+
export function validateConcurrentTileLoads(value: unknown): number | undefined {
|
|
59
|
+
if (value === undefined) return undefined;
|
|
60
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
|
|
61
|
+
throw new Error('maxConcurrentTileLoadsPerWorker must be a positive safe integer');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Consumer settlement hooks do not contain renderer or worker tile objects. */
|
|
67
|
+
interface LeaseSettlement<ValueT> {
|
|
68
|
+
/** Resolve only this lease. */
|
|
69
|
+
resolve(value: ValueT): void;
|
|
70
|
+
/** Reject only this lease. */
|
|
71
|
+
reject(error: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One source/data acquisition, independent of styled meshes and build generations. */
|
|
75
|
+
interface DecodedRecord<ValueT> {
|
|
76
|
+
/** Cancellation belongs to the unique acquisition, not its first consumer. */
|
|
77
|
+
readonly controller: AbortController;
|
|
78
|
+
/** Live consumers, including ready records retained for overzoom/rebuild reuse. */
|
|
79
|
+
readonly consumers: Set<LeaseSettlement<ValueT>>;
|
|
80
|
+
/** Successful content publication, absent while acquisition is pending. */
|
|
81
|
+
result?: {value: ValueT};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Portable cancellation error for leases, source invalidation, and stale publication. */
|
|
85
|
+
export function createTileLeaseAbortError(): Error {
|
|
86
|
+
const error = new Error('Decoded tile lease released');
|
|
87
|
+
error.name = 'AbortError';
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Worker-local shared acquisition with source-instance revisions and zero warm retention. */
|
|
92
|
+
export default class DecodedTileStore<ValueT> {
|
|
93
|
+
/** Source revisions stay retained only while they have live records. */
|
|
94
|
+
private readonly sources = new Map<object, Map<string, DecodedRecord<ValueT>>>();
|
|
95
|
+
/** Optional decoded allocation estimator; encoded network bytes are not a substitute. */
|
|
96
|
+
private readonly getByteLength: (value: ValueT) => number | undefined;
|
|
97
|
+
/** FIFO unique-content queue; duplicate leases never consume another slot. */
|
|
98
|
+
private readonly queued = new Map<DecodedRecord<ValueT>, DecodedTileAcquisition<ValueT>>();
|
|
99
|
+
/** Fixed worker-local source-procedure limit, unrelated to GPU mesh ownership. */
|
|
100
|
+
private readonly maxConcurrentLoads: number | undefined;
|
|
101
|
+
/** Settling an aborted but non-cooperative procedure still releases exactly one slot. */
|
|
102
|
+
private activeAcquisitions = 0;
|
|
103
|
+
/** Batch synchronous leases and cancellation before starting their providers. */
|
|
104
|
+
private scheduled = false;
|
|
105
|
+
/** Actual unique acquisition attempts. */
|
|
106
|
+
private acquisitions = 0;
|
|
107
|
+
/** Reused pending/ready acquisitions. */
|
|
108
|
+
private sharedAcquisitions = 0;
|
|
109
|
+
/** Underlying requests cancelled at final release or invalidation. */
|
|
110
|
+
private cancelledAcquisitions = 0;
|
|
111
|
+
/** Underlying acquisition failures. */
|
|
112
|
+
private failedAcquisitions = 0;
|
|
113
|
+
|
|
114
|
+
/** Create an empty store without fetch, decode, traversal, or GPU dependencies. */
|
|
115
|
+
constructor(getByteLength: (value: ValueT) => number | undefined = () => undefined, options: DecodedTileStoreOptions = {}) {
|
|
116
|
+
this.getByteLength = getByteLength;
|
|
117
|
+
this.maxConcurrentLoads = validateConcurrentTileLoads(options.maxConcurrentLoads);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Acquire one consumer lease, sharing only the exact source revision and data key. */
|
|
121
|
+
acquireTile(request: DecodedTileAcquisition<ValueT>): DecodedTileLease<ValueT> {
|
|
122
|
+
let records = this.sources.get(request.source);
|
|
123
|
+
if (!records) { records = new Map(); this.sources.set(request.source, records); }
|
|
124
|
+
let record = records.get(request.key);
|
|
125
|
+
const created = !record;
|
|
126
|
+
if (!record) {
|
|
127
|
+
record = {controller: new AbortController(), consumers: new Set()};
|
|
128
|
+
records.set(request.key, record);
|
|
129
|
+
} else this.sharedAcquisitions++;
|
|
130
|
+
const current = record;
|
|
131
|
+
let resolveLease: (value: ValueT) => void = () => {};
|
|
132
|
+
let rejectLease: (error: unknown) => void = () => {};
|
|
133
|
+
const promise = new Promise<ValueT>((resolve, reject) => {
|
|
134
|
+
resolveLease = resolve;
|
|
135
|
+
rejectLease = reject;
|
|
136
|
+
});
|
|
137
|
+
const consumer = {resolve: resolveLease, reject: rejectLease};
|
|
138
|
+
current.consumers.add(consumer);
|
|
139
|
+
if (current.result) consumer.resolve(current.result.value);
|
|
140
|
+
if (created) {
|
|
141
|
+
this.queued.set(current, request);
|
|
142
|
+
this.scheduleAcquisitions();
|
|
143
|
+
}
|
|
144
|
+
return {promise, isActive: () => current.consumers.has(consumer), release: () => {
|
|
145
|
+
if (!current.consumers.delete(consumer)) return;
|
|
146
|
+
if (!current.result) consumer.reject(createTileLeaseAbortError());
|
|
147
|
+
if (current.consumers.size === 0) {
|
|
148
|
+
this.forgetRecord(request.source, request.key, current);
|
|
149
|
+
this.cancelRecord(current);
|
|
150
|
+
}
|
|
151
|
+
}};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Release one obsolete source revision without touching a successor source instance. */
|
|
155
|
+
invalidateSource(source: object): void {
|
|
156
|
+
const records = this.sources.get(source);
|
|
157
|
+
if (!records) return;
|
|
158
|
+
this.sources.delete(source);
|
|
159
|
+
for (const record of records.values()) {
|
|
160
|
+
for (const lease of record.consumers) lease.reject(createTileLeaseAbortError());
|
|
161
|
+
record.consumers.clear();
|
|
162
|
+
this.cancelRecord(record);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Return unique content and consumer counts without retaining or exposing internal records. */
|
|
167
|
+
getStatistics(): DecodedTileStatistics {
|
|
168
|
+
let loadingTiles = 0, readyTiles = 0, consumers = 0, decodedBytes: number | undefined = 0;
|
|
169
|
+
for (const records of this.sources.values()) for (const record of records.values()) {
|
|
170
|
+
consumers += record.consumers.size;
|
|
171
|
+
if (!record.result) { loadingTiles++; continue; }
|
|
172
|
+
readyTiles++;
|
|
173
|
+
const bytes = this.getByteLength(record.result.value);
|
|
174
|
+
if (bytes === undefined || !Number.isFinite(bytes) || bytes < 0) decodedBytes = undefined;
|
|
175
|
+
else if (decodedBytes !== undefined) decodedBytes += bytes;
|
|
176
|
+
}
|
|
177
|
+
return {loadingTiles, queuedTiles: this.queued.size, activeAcquisitions: this.activeAcquisitions,
|
|
178
|
+
maxConcurrentLoads: this.maxConcurrentLoads, readyTiles, consumers, decodedBytes, acquisitions: this.acquisitions,
|
|
179
|
+
sharedAcquisitions: this.sharedAcquisitions, cancelledAcquisitions: this.cancelledAcquisitions,
|
|
180
|
+
failedAcquisitions: this.failedAcquisitions};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Cancel all pending work and release content; subsequent use starts from an empty store. */
|
|
184
|
+
finalize(): void { for (const source of this.sources.keys()) this.invalidateSource(source); }
|
|
185
|
+
|
|
186
|
+
/** Drain only when a source procedure settles, not when its styled mesh finishes. */
|
|
187
|
+
private scheduleAcquisitions(): void {
|
|
188
|
+
if (this.scheduled || this.queued.size === 0) return;
|
|
189
|
+
this.scheduled = true;
|
|
190
|
+
void Promise.resolve().then(() => {
|
|
191
|
+
this.scheduled = false;
|
|
192
|
+
for (const [record, request] of this.queued) {
|
|
193
|
+
if (this.activeAcquisitions >= (this.maxConcurrentLoads ?? Infinity)) break;
|
|
194
|
+
this.queued.delete(record);
|
|
195
|
+
if (!this.isCurrent(request.source, request.key, record)) continue;
|
|
196
|
+
this.startAcquisition(request, record);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A slot follows the original procedure even if all consumer leases are released early. */
|
|
202
|
+
private startAcquisition(request: DecodedTileAcquisition<ValueT>, record: DecodedRecord<ValueT>): void {
|
|
203
|
+
this.activeAcquisitions++;
|
|
204
|
+
this.acquisitions++;
|
|
205
|
+
let loading: Promise<ValueT>;
|
|
206
|
+
try { loading = request.load(record.controller.signal); }
|
|
207
|
+
catch (error) { loading = Promise.reject(error); }
|
|
208
|
+
void Promise.resolve(loading).then(value => {
|
|
209
|
+
if (!this.isCurrent(request.source, request.key, record)) return;
|
|
210
|
+
record.result = {value};
|
|
211
|
+
for (const lease of record.consumers) lease.resolve(value);
|
|
212
|
+
}, error => {
|
|
213
|
+
if (!this.isCurrent(request.source, request.key, record)) return;
|
|
214
|
+
this.failedAcquisitions++;
|
|
215
|
+
this.forgetRecord(request.source, request.key, record);
|
|
216
|
+
for (const lease of record.consumers) lease.reject(error);
|
|
217
|
+
record.consumers.clear();
|
|
218
|
+
}).finally(() => {
|
|
219
|
+
this.activeAcquisitions--;
|
|
220
|
+
this.scheduleAcquisitions();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** A late completion cannot publish over a replacement attempt for the same coordinate. */
|
|
225
|
+
private isCurrent(source: object, key: string, record: DecodedRecord<ValueT>): boolean {
|
|
226
|
+
return !record.controller.signal.aborted && this.sources.get(source)?.get(key) === record;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Forget only the matching attempt and release empty source maps immediately. */
|
|
230
|
+
private forgetRecord(source: object, key: string, record: DecodedRecord<ValueT>): void {
|
|
231
|
+
const records = this.sources.get(source);
|
|
232
|
+
if (records?.get(key) !== record) return;
|
|
233
|
+
records.delete(key);
|
|
234
|
+
if (records.size === 0) this.sources.delete(source);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Ready content has no pending request; cancellation is issued at most once. */
|
|
238
|
+
private cancelRecord(record: DecodedRecord<ValueT>): void {
|
|
239
|
+
if (!record.result && !record.controller.signal.aborted) {
|
|
240
|
+
this.cancelledAcquisitions++;
|
|
241
|
+
record.controller.abort();
|
|
242
|
+
this.queued.delete(record);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
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 log from '../utils/log';
|
|
7
|
+
import DataSource, {NetworkSource, NetworkTileSource} from './data_source';
|
|
8
|
+
import {decodeMultiPolygon} from './mvt';
|
|
9
|
+
import Geo from '../utils/geo';
|
|
10
|
+
|
|
11
|
+
// For tiling GeoJSON client-side
|
|
12
|
+
import geojsonvt from 'geojson-vt';
|
|
13
|
+
|
|
14
|
+
type SourceConfig = Record<string, any>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
GeoJSON standalone (non-tiled) source
|
|
18
|
+
Uses geojson-vt split into tiles client-side
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export class GeoJSONSource extends NetworkSource {
|
|
22
|
+
|
|
23
|
+
load_data: Promise<any> | null = null;
|
|
24
|
+
tile_indexes!: Record<string, any>;
|
|
25
|
+
loaded = false;
|
|
26
|
+
|
|
27
|
+
constructor(source: SourceConfig, sources?: Record<string, unknown>) {
|
|
28
|
+
super(source, sources);
|
|
29
|
+
this.load_data = null;
|
|
30
|
+
this.tile_indexes = {}; // geojson-vt tile indices, by layer name
|
|
31
|
+
this.setTileSize(512); // auto-tile to 512px tiles for fewer internal tiles
|
|
32
|
+
this.pad_scale = 0; // we don't want padding on auto-tiled sources
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_load(dest?: any): Promise<any> {
|
|
36
|
+
if (!dest) {
|
|
37
|
+
throw new Error('GeoJSON source loading requires a destination tile');
|
|
38
|
+
}
|
|
39
|
+
if (!this.load_data) {
|
|
40
|
+
this.load_data = super._load({ source_data: { layers: {} } }).then(data => {
|
|
41
|
+
// Warn and continue on data source error
|
|
42
|
+
if (data.source_data.error) {
|
|
43
|
+
log('warn', `data source load error(s) for source '${this.name}', URL '${this.url}': ${data.source_data.error}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let layers = data.source_data.layers;
|
|
47
|
+
for (let layer_name in layers) {
|
|
48
|
+
this.tile_indexes[layer_name] = geojsonvt(layers[layer_name], {
|
|
49
|
+
maxZoom: this.max_zoom, // max zoom to preserve detail on
|
|
50
|
+
tolerance: 1.5, // simplification tolerance (higher means simpler) NB: half the default to accomodate 512px tiles
|
|
51
|
+
extent: Geo.tile_scale, // tile extent (both width and height)
|
|
52
|
+
buffer: 0.0001 // tile buffer on each side
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.loaded = true;
|
|
57
|
+
return data;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.load_data.then(() => {
|
|
62
|
+
for (let layer_name in this.tile_indexes) {
|
|
63
|
+
dest.source_data.layers[layer_name] = this.getTileFeatures(dest, layer_name);
|
|
64
|
+
}
|
|
65
|
+
return dest;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getTileFeatures(tile: any, layer_name: string): any {
|
|
70
|
+
let coords = Geo.wrapTile(tile.coords, {x: true, y: false});
|
|
71
|
+
|
|
72
|
+
// request a particular tile
|
|
73
|
+
let t = this.tile_indexes[layer_name].getTile(coords.z, coords.x, coords.y);
|
|
74
|
+
|
|
75
|
+
// Convert from MVT-style JSON struct to GeoJSON
|
|
76
|
+
let collection;
|
|
77
|
+
if (t && t.features) {
|
|
78
|
+
collection = {
|
|
79
|
+
type: 'FeatureCollection',
|
|
80
|
+
features: [] as any[]
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
for (let i=0; i < t.features.length; i++) {
|
|
84
|
+
const feature = t.features[i];
|
|
85
|
+
|
|
86
|
+
// GeoJSON feature
|
|
87
|
+
let f: any = {
|
|
88
|
+
type: 'Feature',
|
|
89
|
+
geometry: {},
|
|
90
|
+
id: feature.id,
|
|
91
|
+
properties: feature.tags
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (feature.type === 1) {
|
|
95
|
+
f.geometry.coordinates = feature.geometry.map((coord: any) => [coord[0], coord[1]]);
|
|
96
|
+
f.geometry.type = 'MultiPoint';
|
|
97
|
+
}
|
|
98
|
+
else if (feature.type === 2 || feature.type === 3) {
|
|
99
|
+
f.geometry.coordinates = feature.geometry.map((ring: any[]) =>
|
|
100
|
+
ring.map((coord: any) => [coord[0], coord[1]])
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (feature.type === 2) {
|
|
104
|
+
f.geometry.type = 'MultiLineString';
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
f.geometry = decodeMultiPolygon(f.geometry); // un-flatten rings
|
|
108
|
+
if (f.geometry == null) { // skip polys that couldn't be decoded (e.g. degenerate)
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
collection.features.push(f);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return collection;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
formatURL () {
|
|
125
|
+
return this.url;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
parseSourceData (tile?: any, source?: any, response?: any): void {
|
|
129
|
+
if (!tile || !source) {
|
|
130
|
+
throw new Error('GeoJSON source parsing requires a tile and source data');
|
|
131
|
+
}
|
|
132
|
+
let data = typeof response === 'string' ? JSON.parse(response) : response;
|
|
133
|
+
let layers = this.getLayers(data);
|
|
134
|
+
source.layers = this.preprocessLayers(layers, tile);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
preprocessLayers (layers: any, tile: any): any {
|
|
138
|
+
for (let key in layers) {
|
|
139
|
+
let layer = layers[key];
|
|
140
|
+
layer.features = this.preprocessFeatures(layer.features);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Apply optional data transform
|
|
144
|
+
if (typeof this.transform === 'function') {
|
|
145
|
+
const tile_data = {
|
|
146
|
+
min: Object.assign({}, tile.min),
|
|
147
|
+
max: Object.assign({}, tile.max),
|
|
148
|
+
coords: Object.assign({}, tile.coords)
|
|
149
|
+
};
|
|
150
|
+
if (Object.keys(layers).length === 1 && layers._default) {
|
|
151
|
+
layers._default = this.transform(layers._default, this.extra_data, tile_data); // single-layer
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
layers = this.transform(layers, this.extra_data, tile_data); // multiple layers
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return layers;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Preprocess features. Currently used to add a new "centroid" feature for polygon labeling
|
|
162
|
+
preprocessFeatures (features: any[]): any[] {
|
|
163
|
+
// Remove features without geometry (which is valid GeoJSON)
|
|
164
|
+
features = features.filter(f => f.geometry != null);
|
|
165
|
+
|
|
166
|
+
// Define centroids for polygons for centroid label placement
|
|
167
|
+
// Avoids redundant label placement for each generated tile at higher zoom levels
|
|
168
|
+
if (this.config.generate_label_centroids){
|
|
169
|
+
let features_centroid: any[] = [];
|
|
170
|
+
let centroid_properties = {'label_placement' : true};
|
|
171
|
+
|
|
172
|
+
features.forEach(feature => {
|
|
173
|
+
let coordinates, centroid_feature;
|
|
174
|
+
if (feature.geometry.type === 'Polygon') {
|
|
175
|
+
coordinates = feature.geometry.coordinates;
|
|
176
|
+
centroid_feature = getCentroidFeatureForPolygon(coordinates, feature.id, feature.properties, centroid_properties);
|
|
177
|
+
features_centroid.push(centroid_feature);
|
|
178
|
+
}
|
|
179
|
+
else if (feature.geometry.type === 'MultiPolygon') {
|
|
180
|
+
// Add centroid feature for largest polygon
|
|
181
|
+
coordinates = feature.geometry.coordinates;
|
|
182
|
+
let max_area = -Infinity;
|
|
183
|
+
let max_area_index = 0;
|
|
184
|
+
for (let index = 0; index < coordinates.length; index++) {
|
|
185
|
+
let area = Geo.polygonArea(coordinates[index]) || 0;
|
|
186
|
+
if (area > max_area) {
|
|
187
|
+
max_area = area;
|
|
188
|
+
max_area_index = index;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
centroid_feature = getCentroidFeatureForPolygon(coordinates[max_area_index], feature.id, feature.properties, centroid_properties);
|
|
192
|
+
features_centroid.push(centroid_feature);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// append centroid features to features array
|
|
197
|
+
features_centroid = features_centroid.filter(x => x); // remove null features
|
|
198
|
+
Array.prototype.push.apply(features, features_centroid);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return features;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Detect single or multiple layers in returned data
|
|
205
|
+
getLayers (data: any): any {
|
|
206
|
+
if (data.type === 'Feature') {
|
|
207
|
+
return {
|
|
208
|
+
_default: {
|
|
209
|
+
type: 'FeatureCollection',
|
|
210
|
+
features: [data]
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
else if (data.type === 'FeatureCollection') {
|
|
215
|
+
return {
|
|
216
|
+
_default: data
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
return data;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
GeoJSON vector tiles
|
|
228
|
+
@class GeoJSONTileSource
|
|
229
|
+
*/
|
|
230
|
+
export class GeoJSONTileSource extends NetworkTileSource {
|
|
231
|
+
|
|
232
|
+
constructor(source: SourceConfig, sources?: Record<string, unknown>) {
|
|
233
|
+
super(source, sources);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
parseSourceData (tile?: any, source?: any, response?: any): void {
|
|
237
|
+
if (!tile || !source) {
|
|
238
|
+
throw new Error('GeoJSON tile parsing requires a tile and source data');
|
|
239
|
+
}
|
|
240
|
+
let data = typeof response === 'string' ? JSON.parse(response) : response;
|
|
241
|
+
this.prepareGeoJSON(data, tile, source);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
prepareGeoJSON (data: any, tile: any, source: any): void {
|
|
245
|
+
// Apply optional data transform
|
|
246
|
+
if (typeof this.transform === 'function') {
|
|
247
|
+
const tile_data = {
|
|
248
|
+
min: Object.assign({}, tile.min),
|
|
249
|
+
max: Object.assign({}, tile.max),
|
|
250
|
+
coords: Object.assign({}, tile.coords)
|
|
251
|
+
};
|
|
252
|
+
data = this.transform(data, this.extra_data, tile_data);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
source.layers = GeoJSONSource.prototype.getLayers(data);
|
|
256
|
+
|
|
257
|
+
// A "synthetic" tile that adjusts the tile min anchor to account for tile longitude wrapping
|
|
258
|
+
let anchor = {
|
|
259
|
+
coords: tile.coords,
|
|
260
|
+
min: Geo.metersForTile(Geo.wrapTile(tile.coords, {x: true, y: false}))
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
DataSource.projectData(source); // mercator projection
|
|
264
|
+
DataSource.scaleData(source, anchor); // re-scale from meters to local tile coords
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check for URL tile pattern, if not found, treat as standalone GeoJSON/TopoJSON object
|
|
270
|
+
DataSource.register('GeoJSON', (source: SourceConfig) => {
|
|
271
|
+
return GeoJSONTileSource.urlHasTilePattern(source.url) ? GeoJSONTileSource : GeoJSONSource;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
// Helper function to create centroid point feature from polygon coordinates and provided feature meta-data
|
|
276
|
+
function getCentroidFeatureForPolygon (
|
|
277
|
+
coordinates: any,
|
|
278
|
+
id: string | number | undefined,
|
|
279
|
+
properties: Record<string, unknown>,
|
|
280
|
+
newProperties: Record<string, unknown>
|
|
281
|
+
): any {
|
|
282
|
+
let centroid = Geo.centroid(coordinates);
|
|
283
|
+
if (!centroid) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// clone properties and mixix newProperties
|
|
288
|
+
let centroid_properties = {};
|
|
289
|
+
Object.assign(centroid_properties, properties, newProperties);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
type: 'Feature',
|
|
293
|
+
id,
|
|
294
|
+
properties: centroid_properties,
|
|
295
|
+
geometry: {
|
|
296
|
+
type: 'Point',
|
|
297
|
+
coordinates: centroid
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|