maplibre-gl 6.2.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/maplibre-gl-dev.mjs +1108 -427
- package/dist/maplibre-gl-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared-dev.mjs +359 -291
- package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
- package/dist/maplibre-gl-shared.mjs +2 -2
- package/dist/maplibre-gl-shared.mjs.map +1 -1
- package/dist/maplibre-gl-worker-dev.mjs +23 -49
- package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
- package/dist/maplibre-gl-worker.mjs +2 -2
- package/dist/maplibre-gl-worker.mjs.map +1 -1
- package/dist/maplibre-gl.css +1 -1
- package/dist/maplibre-gl.d.ts +467 -151
- package/dist/maplibre-gl.mjs +49 -49
- package/dist/maplibre-gl.mjs.map +1 -1
- package/package.json +12 -22
- package/src/data/bucket/fill_extrusion_bucket.ts +1 -13
- package/src/data/bucket/round_polygon_corners.test.ts +84 -36
- package/src/data/bucket/round_polygon_corners.ts +90 -39
- package/src/data/extent_bounds.ts +28 -1
- package/src/data/feature_index.bench.ts +21 -0
- package/src/data/segment.test.ts +2 -2
- package/src/geo/edge_insets.test.ts +1 -1
- package/src/geo/lng_lat.test.ts +7 -7
- package/src/geo/lng_lat_bounds.test.ts +4 -4
- package/src/geo/mercator_coordinate.test.ts +2 -2
- package/src/geo/projection/camera_helper.ts +5 -0
- package/src/geo/projection/globe_transform.test.ts +154 -1
- package/src/geo/projection/globe_transform.ts +7 -3
- package/src/geo/projection/globe_utils.ts +189 -2
- package/src/geo/projection/mercator_camera_helper.ts +1 -1
- package/src/geo/projection/mercator_transform.ts +21 -5
- package/src/geo/projection/vertical_perspective_camera_helper.test.ts +24 -0
- package/src/geo/projection/vertical_perspective_camera_helper.ts +71 -41
- package/src/geo/projection/vertical_perspective_transform.ts +7 -1
- package/src/geo/transform_interface.ts +13 -1
- package/src/index.ts +7 -2
- package/src/render/image_atlas.test.ts +180 -0
- package/src/render/image_atlas.ts +52 -6
- package/src/render/image_manager.test.ts +111 -0
- package/src/render/image_manager.ts +143 -144
- package/src/render/painter.ts +6 -14
- package/src/render/pattern_atlas.test.ts +131 -0
- package/src/render/pattern_atlas.ts +144 -0
- package/src/render/update_pattern_positions_in_program.test.ts +1 -1
- package/src/shaders/glsl/_prelude.vertex.glsl +10 -11
- package/src/shaders/glsl/_prelude.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/circle.vertex.glsl +4 -4
- package/src/shaders/glsl/circle.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/color_relief.fragment.glsl +1 -2
- package/src/shaders/glsl/color_relief.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/fill_extrusion.vertex.glsl +3 -3
- package/src/shaders/glsl/fill_extrusion.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl +5 -5
- package/src/shaders/glsl/fill_extrusion_pattern.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/heatmap.vertex.glsl +4 -4
- package/src/shaders/glsl/heatmap.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/hillshade_prepare.fragment.glsl +12 -12
- package/src/shaders/glsl/hillshade_prepare.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/line.vertex.glsl +7 -7
- package/src/shaders/glsl/line.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient.vertex.glsl +6 -6
- package/src/shaders/glsl/line_gradient.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_gradient_sdf.vertex.glsl +7 -7
- package/src/shaders/glsl/line_gradient_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_pattern.vertex.glsl +7 -7
- package/src/shaders/glsl/line_pattern.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/line_sdf.vertex.glsl +7 -7
- package/src/shaders/glsl/line_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/raster.fragment.glsl +4 -4
- package/src/shaders/glsl/raster.fragment.glsl.g.ts +1 -1
- package/src/shaders/glsl/raster.vertex.glsl +11 -6
- package/src/shaders/glsl/raster.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_icon.vertex.glsl +5 -5
- package/src/shaders/glsl/symbol_icon.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_sdf.vertex.glsl +5 -5
- package/src/shaders/glsl/symbol_sdf.vertex.glsl.g.ts +1 -1
- package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl +6 -6
- package/src/shaders/glsl/symbol_text_and_icon.vertex.glsl.g.ts +1 -1
- package/src/source/canvas_source.test.ts +31 -0
- package/src/source/canvas_source.ts +2 -1
- package/src/source/geojson_worker_source.test.ts +88 -0
- package/src/source/geojson_worker_source.ts +22 -39
- package/src/source/image_source.test.ts +246 -38
- package/src/source/image_source.ts +132 -19
- package/src/source/raster_dem_tile_source.test.ts +37 -3
- package/src/source/raster_dem_tile_source.ts +1 -2
- package/src/source/raster_dem_tile_worker_source.test.ts +1 -1
- package/src/source/raster_tile_source.test.ts +5 -26
- package/src/source/raster_tile_source.ts +4 -3
- package/src/source/vector_tile_worker_source.test.ts +94 -2
- package/src/source/vector_tile_worker_source.ts +18 -27
- package/src/source/video_source.test.ts +12 -8
- package/src/source/worker_tile.bench.ts +10 -0
- package/src/source/worker_tile.ts +0 -3
- package/src/style/create_style_layer.bench.ts +15 -0
- package/src/style/properties.bench.ts +81 -0
- package/src/style/style.test.ts +75 -10
- package/src/style/style.ts +37 -64
- package/src/style/style_image.ts +67 -9
- package/src/style/style_layer.bench.ts +65 -0
- package/src/style/style_layer.test.ts +1 -1
- package/src/style/style_layer.ts +2 -2
- package/src/style/validate_style.bench.ts +11 -0
- package/src/style/validate_style.test.ts +2 -2
- package/src/symbol/anchor.test.ts +2 -2
- package/src/symbol/collision_index.bench.ts +116 -0
- package/src/symbol/cross_tile_symbol_index.bench.ts +49 -0
- package/src/symbol/cross_tile_symbol_index.test.ts +31 -0
- package/src/symbol/cross_tile_symbol_index.ts +67 -37
- package/src/symbol/shaping.test.ts +2 -1
- package/src/symbol/symbol_layout.bench.ts +30 -0
- package/src/tile/tile_id.test.ts +1 -1
- package/src/tile/tile_manager.test.ts +69 -1
- package/src/tile/tile_manager.ts +16 -11
- package/src/ui/control/fullscreen_control.test.ts +10 -10
- package/src/ui/control/geolocate_control.test.ts +3 -5
- package/src/ui/control/globe_control.test.ts +6 -6
- package/src/ui/events.ts +1 -1
- package/src/ui/handler/scroll_zoom.test.ts +50 -0
- package/src/ui/handler/two_fingers_touch.test.ts +17 -6
- package/src/ui/handler_inertia.test.ts +91 -0
- package/src/ui/handler_inertia.ts +65 -14
- package/src/ui/handler_manager.test.ts +243 -3
- package/src/ui/handler_manager.ts +79 -16
- package/src/ui/map.ts +17 -3
- package/src/ui/map_tests/map_cross_window.test.ts +2 -2
- package/src/ui/map_tests/map_events.test.ts +3 -3
- package/src/ui/map_tests/map_images.test.ts +55 -0
- package/src/ui/map_tests/map_style.test.ts +9 -8
- package/src/ui/map_tests/map_webgl.test.ts +30 -0
- package/src/ui/marker.test.ts +177 -42
- package/src/ui/marker.ts +120 -10
- package/src/ui/popup.test.ts +14 -14
- package/src/util/evented.test.ts +33 -28
- package/src/util/evented.ts +69 -43
- package/src/util/image_request.ts +31 -1
- package/src/util/test/null_gl.ts +10 -0
- package/src/util/test/util.ts +2 -1
- package/src/util/util.test.ts +1 -1
- package/src/util/web_worker_transfer.test.ts +5 -5
- package/src/webgl/context.ts +20 -0
- package/src/webgl/draw/draw_background.ts +1 -1
- package/src/webgl/draw/draw_raster.ts +11 -6
- package/src/webgl/program/pattern.ts +3 -3
- package/src/webgl/program/raster_program.ts +11 -0
- package/src/webgl/program.ts +31 -16
- package/src/webgl/texture.test.ts +16 -1
- package/src/webgl/texture.ts +3 -0
- package/src/webgl/vertex_buffer.test.ts +17 -2
- package/src/webgl/vertex_buffer.ts +24 -13
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maplibre-gl",
|
|
3
3
|
"description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.4.0",
|
|
5
5
|
"module": "dist/maplibre-gl.mjs",
|
|
6
6
|
"style": "dist/maplibre-gl.css",
|
|
7
7
|
"license": "BSD-3-Clause",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"@mapbox/mapbox-gl-rtl-text": "^0.4.0",
|
|
52
52
|
"@mapbox/mvt-fixtures": "^4.0.0",
|
|
53
53
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
54
|
-
"@types/benchmark": "^2.1.5",
|
|
55
54
|
"@types/d3": "^7.4.3",
|
|
56
55
|
"@types/earcut": "^3.0.0",
|
|
57
56
|
"@types/eslint": "^9.6.1",
|
|
@@ -60,39 +59,36 @@
|
|
|
60
59
|
"@types/minimist": "^1.2.5",
|
|
61
60
|
"@types/murmurhash-js": "^1.0.7",
|
|
62
61
|
"@types/nise": "^1.4.5",
|
|
63
|
-
"@types/node": "^26.
|
|
62
|
+
"@types/node": "^26.2.0",
|
|
64
63
|
"@types/offscreencanvas": "^2019.7.3",
|
|
65
64
|
"@types/pixelmatch": "^5.2.6",
|
|
66
65
|
"@types/pngjs": "^6.0.5",
|
|
67
|
-
"@types/react": "^19.2.18",
|
|
68
|
-
"@types/react-dom": "^19.2.4",
|
|
69
66
|
"@types/request": "^2.48.13",
|
|
70
67
|
"@types/shuffle-seed": "^1.1.3",
|
|
71
68
|
"@types/window-or-global": "^1.0.6",
|
|
72
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
69
|
+
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
|
73
70
|
"@typescript-eslint/parser": "^8.59.3",
|
|
74
71
|
"@typescript/native-preview": "beta",
|
|
75
72
|
"@unicode/unicode-17.0.0": "^1.6.17",
|
|
76
73
|
"@vitest/coverage-v8": "4.1.10",
|
|
77
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
74
|
+
"@vitest/eslint-plugin": "^1.6.27",
|
|
78
75
|
"@vitest/ui": "4.1.10",
|
|
79
76
|
"address": "^2.0.3",
|
|
80
77
|
"autoprefixer": "^10.5.4",
|
|
81
|
-
"benchmark": "^2.1.4",
|
|
82
78
|
"canvas": "^3.2.3",
|
|
83
79
|
"cross-env": "^10.1.0",
|
|
84
80
|
"cspell": "^10.0.1",
|
|
85
|
-
"cssnano": "^8.0.
|
|
81
|
+
"cssnano": "^8.0.5",
|
|
86
82
|
"d3": "^7.9.0",
|
|
87
83
|
"d3-queue": "^3.0.7",
|
|
88
|
-
"devtools-protocol": "^0.0.
|
|
84
|
+
"devtools-protocol": "^0.0.1676914",
|
|
89
85
|
"diff": "^9.0.0",
|
|
90
|
-
"eslint": "^10.8.
|
|
86
|
+
"eslint": "^10.8.1",
|
|
91
87
|
"eslint-plugin-html": "^8.1.4",
|
|
92
88
|
"eslint-plugin-tsdoc": "0.5.2",
|
|
93
89
|
"expect": "^30.4.1",
|
|
94
90
|
"glob": "^13.0.6",
|
|
95
|
-
"globals": "^17.
|
|
91
|
+
"globals": "^17.9.0",
|
|
96
92
|
"is-builtin-module": "^5.0.0",
|
|
97
93
|
"jsdom": "^30.0.1",
|
|
98
94
|
"minimist": "^1.2.8",
|
|
@@ -108,11 +104,9 @@
|
|
|
108
104
|
"postcss-cli": "^11.0.1",
|
|
109
105
|
"postcss-inline-svg": "^6.0.0",
|
|
110
106
|
"pretty-bytes": "^7.1.1",
|
|
111
|
-
"puppeteer": "^25.
|
|
112
|
-
"react": "^19.2.8",
|
|
113
|
-
"react-dom": "^19.2.8",
|
|
107
|
+
"puppeteer": "^25.5.0",
|
|
114
108
|
"regenerate": "^1.4.2",
|
|
115
|
-
"rolldown": "^1.2.
|
|
109
|
+
"rolldown": "^1.2.3",
|
|
116
110
|
"rolldown-plugin-dts": "^0.27.8",
|
|
117
111
|
"rw": "^1.3.3",
|
|
118
112
|
"semver": "^7.8.5",
|
|
@@ -125,7 +119,7 @@
|
|
|
125
119
|
"typedoc": "^0.28.20",
|
|
126
120
|
"typedoc-plugin-markdown": "^4.11.0",
|
|
127
121
|
"typescript": "^6.0.3",
|
|
128
|
-
"vite": "^8.2.
|
|
122
|
+
"vite": "^8.2.1",
|
|
129
123
|
"vitest": "^4.1.5",
|
|
130
124
|
"vitest-webgl-canvas-mock": "^1.1.0"
|
|
131
125
|
},
|
|
@@ -143,14 +137,11 @@
|
|
|
143
137
|
"build-prod": "cross-env BUILD=production rolldown -c rolldown.config.ts",
|
|
144
138
|
"build-css": "postcss -o dist/maplibre-gl.css src/css/maplibre-gl.css",
|
|
145
139
|
"watch-css": "postcss --watch -o dist/maplibre-gl.css src/css/maplibre-gl.css",
|
|
146
|
-
"build-benchmarks": "npm run build-dev && rolldown -c test/bench/rolldown_config_benchmarks.ts",
|
|
147
|
-
"watch-benchmarks": "rolldown -c test/bench/rolldown_config_benchmarks.ts --watch",
|
|
148
140
|
"spellcheck": "cspell",
|
|
149
141
|
"docs": "npm run generate-docs && docker compose run --rm docs",
|
|
150
142
|
"start-server": "st --no-cache -H localhost --port 9966 .",
|
|
151
143
|
"start-docs": "docker compose run --rm -it --service-ports docs-serve",
|
|
152
144
|
"start": "run-p watch-css watch-dev start-server",
|
|
153
|
-
"start-bench": "run-p watch-css watch-benchmarks start-server",
|
|
154
145
|
"lint": "eslint",
|
|
155
146
|
"lint-css": "stylelint **/*.css verbose",
|
|
156
147
|
"test": "run-p lint lint-css test-render test-unit test-integration test-build",
|
|
@@ -164,8 +155,7 @@
|
|
|
164
155
|
"test-render": "vitest test/integration/render/render.test.ts",
|
|
165
156
|
"codegen": "run-p --print-label generate-style-code generate-unicode-data generate-struct-arrays generate-shaders && npm run generate-typings",
|
|
166
157
|
"bench": "vitest bench --run --config vitest.config.bench.ts",
|
|
167
|
-
"
|
|
168
|
-
"gl-stats": "node test/bench/gl-stats.ts",
|
|
158
|
+
"bench-e2e": "node test/bench/e2e/run.ts",
|
|
169
159
|
"prepare": "npm run codegen",
|
|
170
160
|
"typecheck": "tsgo --noEmit && tsgo --project tsconfig.dist.json"
|
|
171
161
|
},
|
|
@@ -4,7 +4,7 @@ import {members as layoutAttributes, centroidAttributes} from './fill_extrusion_
|
|
|
4
4
|
import {type Segment, SegmentVector} from '../segment.ts';
|
|
5
5
|
import {ProgramConfigurationSet} from '../program_configuration.ts';
|
|
6
6
|
import {TriangleIndexArray} from '../index_array_type.ts';
|
|
7
|
-
import {
|
|
7
|
+
import {isBoundaryEdge, isEntirelyOutside} from '../extent_bounds.ts';
|
|
8
8
|
import {VectorTileFeature} from '@mapbox/vector-tile';
|
|
9
9
|
import {classifyRings} from '@maplibre/maplibre-gl-style-spec';
|
|
10
10
|
const EARCUT_MAX_RINGS = 500;
|
|
@@ -335,15 +335,3 @@ function accumulatePointsToCentroid(centroid: CentroidAccumulator, geometry: Poi
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
register('FillExtrusionBucket', FillExtrusionBucket, {omit: ['layers', 'features']});
|
|
338
|
-
|
|
339
|
-
function isBoundaryEdge(p1, p2) {
|
|
340
|
-
return (p1.x === p2.x && (p1.x < 0 || p1.x > EXTENT)) ||
|
|
341
|
-
(p1.y === p2.y && (p1.y < 0 || p1.y > EXTENT));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function isEntirelyOutside(ring) {
|
|
345
|
-
return ring.every(p => p.x < 0) ||
|
|
346
|
-
ring.every(p => p.x > EXTENT) ||
|
|
347
|
-
ring.every(p => p.y < 0) ||
|
|
348
|
-
ring.every(p => p.y > EXTENT);
|
|
349
|
-
}
|
|
@@ -10,6 +10,14 @@ function round(p: Point):GeoJSON.Position {
|
|
|
10
10
|
return [x, y];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const square = [
|
|
14
|
+
new Point(0, 0),
|
|
15
|
+
new Point(10, 0),
|
|
16
|
+
new Point(10, 10),
|
|
17
|
+
new Point(0, 10),
|
|
18
|
+
new Point(0, 0)
|
|
19
|
+
];
|
|
20
|
+
|
|
13
21
|
describe('roundPolygonCorners', () => {
|
|
14
22
|
const canonical = new CanonicalTileID(10, 500, 300);
|
|
15
23
|
|
|
@@ -50,58 +58,102 @@ describe('roundPolygonCorners', () => {
|
|
|
50
58
|
expect(output).toStrictEqual(input);
|
|
51
59
|
});
|
|
52
60
|
|
|
53
|
-
test('
|
|
54
|
-
const
|
|
55
|
-
new Point(0, 0),
|
|
56
|
-
new Point(10, 0),
|
|
57
|
-
new Point(10, 10),
|
|
58
|
-
new Point(0, 10),
|
|
59
|
-
new Point(0, 0)
|
|
60
|
-
]];
|
|
61
|
+
test('collapses every arc back onto its corner when the distance is below one tile unit', () => {
|
|
62
|
+
const output = roundPolygonCorners([square], 0.1, canonical);
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
expect(output[0].map(round)).toEqual(square.map(round));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('rounds corners of a square polygon into arc vertices', () => {
|
|
68
|
+
const output = roundPolygonCorners([square], 2, canonical);
|
|
63
69
|
const points = output[0].map(round);
|
|
64
70
|
|
|
65
71
|
expect(points).toEqual([
|
|
66
|
-
[0,
|
|
67
|
-
[10, 9
|
|
72
|
+
[0, 1], [0, 0], [1, 0], [9, 0], [10, 0], [10, 1],
|
|
73
|
+
[10, 9], [10, 10], [9, 10], [1, 10], [0, 10], [0, 9], [0, 1]
|
|
68
74
|
]);
|
|
69
75
|
});
|
|
70
76
|
|
|
71
|
-
test('
|
|
77
|
+
test('clamps corner rounding distance to 20% of edge length when requested distance is large', () => {
|
|
78
|
+
const output = roundPolygonCorners([square], 1000, canonical);
|
|
79
|
+
const points = output[0].map(round);
|
|
80
|
+
|
|
81
|
+
expect(points).toEqual([
|
|
82
|
+
[0, 2], [0, 1], [1, 0], [2, 0], [8, 0], [9, 0], [10, 1], [10, 2],
|
|
83
|
+
[10, 8], [10, 9], [9, 10], [8, 10], [2, 10], [1, 10], [0, 9], [0, 8], [0, 2]
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('emits only integer tile coordinates, so that the triangulator cannot merge arc points itself', () => {
|
|
72
88
|
const input = [[
|
|
73
89
|
new Point(0, 0),
|
|
74
|
-
new Point(
|
|
75
|
-
new Point(
|
|
76
|
-
new Point(
|
|
90
|
+
new Point(137, 41),
|
|
91
|
+
new Point(96, 158),
|
|
92
|
+
new Point(13, 111),
|
|
77
93
|
new Point(0, 0)
|
|
78
94
|
]];
|
|
79
95
|
|
|
80
|
-
const
|
|
96
|
+
for (const distance of [0.5, 1, 2, 5, 50, 1000]) {
|
|
97
|
+
for (const ring of roundPolygonCorners(input, distance, canonical)) {
|
|
98
|
+
for (const point of ring) {
|
|
99
|
+
expect(point.x).toBe(Math.round(point.x));
|
|
100
|
+
expect(point.y).toBe(Math.round(point.y));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('leaves the corners of a cut sharp and rounds the rest of the same ring', () => {
|
|
107
|
+
const input = [[
|
|
108
|
+
new Point(1000, -100),
|
|
109
|
+
new Point(3000, -100),
|
|
110
|
+
new Point(3000, 4000),
|
|
111
|
+
new Point(1000, 4000),
|
|
112
|
+
new Point(1000, -100)
|
|
113
|
+
]];
|
|
114
|
+
|
|
115
|
+
const output = roundPolygonCorners(input, 1000, canonical);
|
|
81
116
|
const points = output[0].map(round);
|
|
82
117
|
|
|
83
118
|
expect(points).toEqual([
|
|
84
|
-
[
|
|
85
|
-
[
|
|
119
|
+
[1000, -100],
|
|
120
|
+
[3000, -100],
|
|
121
|
+
[3000, 3600],
|
|
122
|
+
[2946, 3800],
|
|
123
|
+
[2800, 3946],
|
|
124
|
+
[2600, 4000],
|
|
125
|
+
[1400, 4000],
|
|
126
|
+
[1200, 3946],
|
|
127
|
+
[1054, 3800],
|
|
128
|
+
[1000, 3600],
|
|
129
|
+
[1000, -100]
|
|
86
130
|
]);
|
|
87
131
|
});
|
|
88
132
|
|
|
89
|
-
test('
|
|
133
|
+
test('rounds a corner of the feature that sits in the buffer, since only a cut stays sharp', () => {
|
|
90
134
|
const input = [[
|
|
91
|
-
new Point(
|
|
92
|
-
new Point(
|
|
93
|
-
new Point(
|
|
94
|
-
new Point(
|
|
95
|
-
new Point(
|
|
135
|
+
new Point(1200, 200),
|
|
136
|
+
new Point(2000, -100),
|
|
137
|
+
new Point(2800, 200),
|
|
138
|
+
new Point(2000, 3000),
|
|
139
|
+
new Point(1200, 200)
|
|
96
140
|
]];
|
|
97
141
|
|
|
98
142
|
const output = roundPolygonCorners(input, 1000, canonical);
|
|
99
143
|
const points = output[0].map(round);
|
|
100
144
|
|
|
101
|
-
expect(points).
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
145
|
+
expect(points).not.toContainEqual([2000, -100]);
|
|
146
|
+
expect(output[0].length).toBeGreaterThan(input[0].length);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('drops arc points that collapse onto their neighbour', () => {
|
|
150
|
+
const output = roundPolygonCorners([square], 2, canonical);
|
|
151
|
+
const ring = output[0];
|
|
152
|
+
|
|
153
|
+
for (let i = 1; i < ring.length; i++) {
|
|
154
|
+
expect([ring[i].x, ring[i].y]).not.toEqual([ring[i - 1].x, ring[i - 1].y]);
|
|
155
|
+
}
|
|
156
|
+
expect([ring[0].x, ring[0].y]).toEqual([ring[ring.length - 1].x, ring[ring.length - 1].y]);
|
|
105
157
|
});
|
|
106
158
|
|
|
107
159
|
test('preserves near-zero-degree spike vertex without adding arc points', () => {
|
|
@@ -115,10 +167,7 @@ describe('roundPolygonCorners', () => {
|
|
|
115
167
|
const output = roundPolygonCorners(input, 5, canonical);
|
|
116
168
|
const points = output[0].map(round);
|
|
117
169
|
|
|
118
|
-
expect(points).toEqual([
|
|
119
|
-
[0, 0.08], [0.01, 0.04], [0.04, 0.01], [0.08, 0], [100, 0], [0.08, 0.4],
|
|
120
|
-
[0.05, 0.39], [0.02, 0.38], [0.01, 0.35], [0, 0.32], [0, 0.08]
|
|
121
|
-
]);
|
|
170
|
+
expect(points).toEqual([[0, 0], [100, 0], [0, 0.4], [0, 0]]);
|
|
122
171
|
});
|
|
123
172
|
|
|
124
173
|
test('preserves collinear 180-degree vertices without adding arc points', () => {
|
|
@@ -135,8 +184,8 @@ describe('roundPolygonCorners', () => {
|
|
|
135
184
|
const points = output[0].map(round);
|
|
136
185
|
|
|
137
186
|
expect(points).toEqual([
|
|
138
|
-
[0, 1], [0
|
|
139
|
-
[10, 8], [
|
|
187
|
+
[0, 1], [0, 0], [1, 0], [5, 0], [9, 0], [10, 0], [10, 1],
|
|
188
|
+
[10, 8], [10, 9], [9, 10], [8, 10], [2, 10], [1, 10], [0, 9], [0, 8], [0, 1]
|
|
140
189
|
]);
|
|
141
190
|
});
|
|
142
191
|
|
|
@@ -152,8 +201,7 @@ describe('roundPolygonCorners', () => {
|
|
|
152
201
|
const points = output[0].map(round);
|
|
153
202
|
|
|
154
203
|
expect(points).toEqual([
|
|
155
|
-
[
|
|
156
|
-
[98.25, 0.16], [98.33, 0.32], [98.32, 0.5], [98.23, 0.66], [98.09, 0.77], [51.91, 19.23], [50, 19.6], [48.09, 19.23], [1.91, 0.77]
|
|
204
|
+
[2, 1], [2, 0], [98, 0], [98, 1], [52, 19], [50, 20], [48, 19], [2, 1]
|
|
157
205
|
]);
|
|
158
206
|
});
|
|
159
207
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import Point from '@mapbox/point-geometry';
|
|
2
|
-
import {vec2} from 'gl-matrix';
|
|
3
1
|
import {EXTENT} from '../extent.ts';
|
|
2
|
+
import {isBoundaryEdge} from '../extent_bounds.ts';
|
|
4
3
|
import {MercatorCoordinate} from '../../geo/mercator_coordinate.ts';
|
|
5
4
|
import {tileCoordinatesToLocation} from '../../geo/projection/mercator_utils.ts';
|
|
5
|
+
import type Point from '@mapbox/point-geometry';
|
|
6
6
|
import type {CanonicalTileID} from '../../tile/tile_id.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -32,6 +32,19 @@ function getTileUnitsForMeters(distanceInMeters: number, canonical: CanonicalTil
|
|
|
32
32
|
return distanceInMeters * meterInMercator * tileUnitsPerMercator;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Rounds the corners of a single ring.
|
|
37
|
+
*
|
|
38
|
+
* Corners that tile clipping created are left sharp: they belong to the cut rather than to the
|
|
39
|
+
* feature, and the neighbouring tile cuts the same feature elsewhere, so rounding them would leave
|
|
40
|
+
* the two halves out of step. Every vertex ends up on the integer tile grid, because triangulation,
|
|
41
|
+
* subdivision and the vertex buffers snap and deduplicate vertices there - arcs finer than a tile
|
|
42
|
+
* unit would otherwise be merged only after they were triangulated, turning the mesh into spikes.
|
|
43
|
+
*
|
|
44
|
+
* A ring that collapses into fewer than three distinct vertices is returned unchanged.
|
|
45
|
+
* @param ring - Ring to round, closed or open
|
|
46
|
+
* @param distanceInTileUnits - Corner rounding distance, already converted to tile units
|
|
47
|
+
*/
|
|
35
48
|
function roundRing(ring: Point[], distanceInTileUnits: number): Point[] {
|
|
36
49
|
if (!ring || ring.length < 3) {
|
|
37
50
|
return ring;
|
|
@@ -44,49 +57,94 @@ function roundRing(ring: Point[], distanceInTileUnits: number): Point[] {
|
|
|
44
57
|
return ring;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
const
|
|
48
|
-
const newRing: vec2[] = [];
|
|
60
|
+
const newRing: Point[] = [];
|
|
49
61
|
|
|
50
62
|
for (let i = 0; i < vertexCount; i++) {
|
|
51
|
-
const
|
|
52
|
-
const current =
|
|
53
|
-
const next =
|
|
63
|
+
const previous = ring[(i - 1 + vertexCount) % vertexCount];
|
|
64
|
+
const current = ring[i];
|
|
65
|
+
const next = ring[(i + 1) % vertexCount];
|
|
66
|
+
|
|
67
|
+
if (isBoundaryEdge(previous, current) || isBoundaryEdge(current, next)) {
|
|
68
|
+
newRing.push(current.clone());
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
54
71
|
|
|
55
|
-
appendRoundCorner(newRing,
|
|
72
|
+
appendRoundCorner(newRing, previous, current, next, distanceInTileUnits);
|
|
56
73
|
}
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
|
|
75
|
+
const snapped = snapToIntegerGrid(newRing);
|
|
76
|
+
|
|
77
|
+
if (snapped.length < 3) {
|
|
78
|
+
return ring;
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
if (isClosed) {
|
|
82
|
+
snapped.push(snapped[0].clone());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return snapped;
|
|
63
86
|
}
|
|
64
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Rounds every vertex to the integer tile grid, dropping vertices that collapse onto their neighbour.
|
|
90
|
+
* The ring is treated as closed, so the wrap-around duplicate is dropped as well.
|
|
91
|
+
* @param ring - Ring to snap
|
|
92
|
+
*/
|
|
93
|
+
function snapToIntegerGrid(ring: Point[]): Point[] {
|
|
94
|
+
const snapped: Point[] = [];
|
|
95
|
+
|
|
96
|
+
for (const p of ring) {
|
|
97
|
+
const point = p.round();
|
|
98
|
+
const previous = snapped[snapped.length - 1];
|
|
99
|
+
|
|
100
|
+
if (previous?.x === point.x && previous?.y === point.y) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
snapped.push(point);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
while (snapped.length > 1 && snapped[0].x === snapped[snapped.length - 1].x && snapped[0].y === snapped[snapped.length - 1].y) {
|
|
108
|
+
snapped.pop();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return snapped;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Appends the arc that replaces one corner, or the corner itself when it is too shallow or too sharp
|
|
116
|
+
* to round.
|
|
117
|
+
* @param newRing - Ring being built, the arc points are appended to it
|
|
118
|
+
* @param prev - Vertex before the corner
|
|
119
|
+
* @param current - The corner
|
|
120
|
+
* @param next - Vertex after the corner
|
|
121
|
+
* @param distanceInTileUnits - Corner rounding distance, already converted to tile units
|
|
122
|
+
*/
|
|
65
123
|
function appendRoundCorner(
|
|
66
|
-
newRing:
|
|
67
|
-
prev:
|
|
68
|
-
current:
|
|
69
|
-
next:
|
|
124
|
+
newRing: Point[],
|
|
125
|
+
prev: Point,
|
|
126
|
+
current: Point,
|
|
127
|
+
next: Point,
|
|
70
128
|
distanceInTileUnits: number
|
|
71
129
|
): void {
|
|
72
130
|
// Unit edge vectors from the current vertex towards its neighbours
|
|
73
|
-
const ua =
|
|
74
|
-
const ub =
|
|
75
|
-
const lenA =
|
|
76
|
-
const lenB =
|
|
131
|
+
const ua = prev.sub(current);
|
|
132
|
+
const ub = next.sub(current);
|
|
133
|
+
const lenA = ua.mag();
|
|
134
|
+
const lenB = ub.mag();
|
|
77
135
|
|
|
78
136
|
if (lenA < 1e-6 || lenB < 1e-6) {
|
|
79
|
-
newRing.push(
|
|
137
|
+
newRing.push(current.clone());
|
|
80
138
|
return;
|
|
81
139
|
}
|
|
82
140
|
|
|
83
|
-
|
|
84
|
-
|
|
141
|
+
ua._div(lenA);
|
|
142
|
+
ub._div(lenB);
|
|
85
143
|
|
|
86
144
|
// Straight lines or zero-degree turns
|
|
87
|
-
const dot =
|
|
145
|
+
const dot = ua.x * ub.x + ua.y * ub.y;
|
|
88
146
|
if (Math.abs(dot) > Math.cos(5 * Math.PI / 180)) {
|
|
89
|
-
newRing.push(
|
|
147
|
+
newRing.push(current.clone());
|
|
90
148
|
return;
|
|
91
149
|
}
|
|
92
150
|
|
|
@@ -95,27 +153,20 @@ function appendRoundCorner(
|
|
|
95
153
|
const r = Math.min(distanceInTileUnits, lenA * maxEdgeLenPercent, lenB * maxEdgeLenPercent);
|
|
96
154
|
|
|
97
155
|
// Tangent points on edges to prevPoint and nextPoint
|
|
98
|
-
const tangentA =
|
|
99
|
-
const tangentB =
|
|
100
|
-
|
|
101
|
-
const bisector = vec2.add(vec2.create(), ua, ub);
|
|
102
|
-
vec2.normalize(bisector, bisector);
|
|
156
|
+
const tangentA = current.add(ua.mult(r));
|
|
157
|
+
const tangentB = current.add(ub.mult(r));
|
|
103
158
|
|
|
104
159
|
// Center of the rounding arc, at r / cos(theta/2) along the bisector
|
|
105
160
|
const cosHalfTheta = Math.sqrt((1 + dot) / 2);
|
|
106
|
-
const center =
|
|
161
|
+
const center = current.add(ua.add(ub)._unit()._mult(r / cosHalfTheta));
|
|
107
162
|
|
|
108
|
-
// Both tangent points lie on the arc circle
|
|
109
|
-
//
|
|
110
|
-
const
|
|
111
|
-
const radiusB = vec2.sub(vec2.create(), tangentB, center);
|
|
112
|
-
const sweepAngle = vec2.angle(radiusA, radiusB);
|
|
113
|
-
const direction = Math.sign(radiusA[0] * radiusB[1] - radiusA[1] * radiusB[0]); // 2D cross product -> winding direction
|
|
163
|
+
// Both tangent points lie on the arc circle, so rotating tangent A around the center by the angle
|
|
164
|
+
// between the two radii traces the fillet onto tangent B along the shortest arc.
|
|
165
|
+
const sweepAngle = tangentA.sub(center).angleWith(tangentB.sub(center));
|
|
114
166
|
|
|
115
167
|
// ~30 deg per segment; epsilon keeps fp noise from adding one at exact multiples.
|
|
116
|
-
const numSegments = Math.max(2, Math.ceil(sweepAngle / (Math.PI / 6) - 1e-6));
|
|
168
|
+
const numSegments = Math.max(2, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 6) - 1e-6));
|
|
117
169
|
for (let s = 0; s <= numSegments; s++) {
|
|
118
|
-
|
|
119
|
-
newRing.push(vec2.rotate(vec2.create(), tangentA, center, angle));
|
|
170
|
+
newRing.push(tangentA.rotateAround(sweepAngle * (s / numSegments), center));
|
|
120
171
|
}
|
|
121
172
|
}
|
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
import Point from '@mapbox/point-geometry';
|
|
2
|
+
import {type Point2D} from '@maplibre/maplibre-gl-style-spec';
|
|
2
3
|
import {Bounds, type ReadOnlyBounds} from '../geo/bounds.ts';
|
|
3
4
|
import {EXTENT} from './extent.ts';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The bounding box covering the entire extent of a tile.
|
|
7
8
|
*/
|
|
8
|
-
export const EXTENT_BOUNDS = Bounds.fromPoints([new Point(0, 0), new Point(EXTENT, EXTENT)]) as ReadOnlyBounds;
|
|
9
|
+
export const EXTENT_BOUNDS = Bounds.fromPoints([new Point(0, 0), new Point(EXTENT, EXTENT)]) as ReadOnlyBounds;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether an edge runs along one of the lines the tile was cut out of the world at. Clipping happens
|
|
13
|
+
* on the buffer rectangle, outside the tile, so an axis parallel edge out there belongs to the cut
|
|
14
|
+
* rather than to the feature. Such an edge is shared with the neighbouring tile, which cuts the same
|
|
15
|
+
* feature somewhere else, so geometry derived from it has to stay identical in both tiles.
|
|
16
|
+
* @param p1 - First vertex of the edge
|
|
17
|
+
* @param p2 - Second vertex of the edge
|
|
18
|
+
* @returns True when the edge lies on a clip line.
|
|
19
|
+
*/
|
|
20
|
+
export function isBoundaryEdge(p1: Point2D, p2: Point2D): boolean {
|
|
21
|
+
return (p1.x === p2.x && (p1.x < 0 || p1.x > EXTENT)) ||
|
|
22
|
+
(p1.y === p2.y && (p1.y < 0 || p1.y > EXTENT));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a ring lies completely off one side of the tile, and so cannot contribute a visible pixel.
|
|
27
|
+
* @param ring - Ring to test
|
|
28
|
+
* @returns True when every vertex is beyond the same edge of the tile.
|
|
29
|
+
*/
|
|
30
|
+
export function isEntirelyOutside(ring: Point2D[]): boolean {
|
|
31
|
+
return ring.every(p => p.x < 0) ||
|
|
32
|
+
ring.every(p => p.x > EXTENT) ||
|
|
33
|
+
ring.every(p => p.y < 0) ||
|
|
34
|
+
ring.every(p => p.y > EXTENT);
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {bench} from 'vitest';
|
|
2
|
+
import {FeatureIndex} from './feature_index.ts';
|
|
3
|
+
import {OverscaledTileID} from '../tile/tile_id.ts';
|
|
4
|
+
|
|
5
|
+
import type {FeatureFilter} from '@maplibre/maplibre-gl-style-spec';
|
|
6
|
+
import type {VectorTileFeatureLike, VectorTileLayerLike} from '@maplibre/vt-pbf';
|
|
7
|
+
|
|
8
|
+
const layerCount = 100;
|
|
9
|
+
const filter = {needGeometry: false} as FeatureFilter;
|
|
10
|
+
|
|
11
|
+
const featureIndex = new FeatureIndex(new OverscaledTileID(0, 0, 0, 0, 0));
|
|
12
|
+
featureIndex.bucketLayerIDs = [Array.from({length: layerCount}, (_, i) => `layer-${i}`)];
|
|
13
|
+
featureIndex.vtLayers = {
|
|
14
|
+
'0': {feature: () => ({} as VectorTileFeatureLike)} as unknown as VectorTileLayerLike
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const layerIdsToTest = new Set(Array.from({length: layerCount}, (_, i) => `non-existing-layer-${i}`));
|
|
18
|
+
|
|
19
|
+
bench('FeatureIndex.loadMatchingFeature', () => {
|
|
20
|
+
featureIndex.loadMatchingFeature({}, 0, 0, 0, filter, layerIdsToTest, [], {}, {});
|
|
21
|
+
});
|
package/src/data/segment.test.ts
CHANGED
|
@@ -4,13 +4,13 @@ import {SegmentVector} from './segment.ts';
|
|
|
4
4
|
|
|
5
5
|
describe('SegmentVector', () => {
|
|
6
6
|
test('constructor', () => {
|
|
7
|
-
expect(new SegmentVector()
|
|
7
|
+
expect(new SegmentVector()).toBeInstanceOf(SegmentVector);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
test('simpleSegment', () => {
|
|
11
11
|
SegmentVector.MAX_VERTEX_ARRAY_LENGTH = 16;
|
|
12
12
|
const segmentVector = SegmentVector.simpleSegment(0, 0, 10, 0);
|
|
13
|
-
expect(segmentVector
|
|
13
|
+
expect(segmentVector).toBeInstanceOf(SegmentVector);
|
|
14
14
|
expect(segmentVector.segments).toHaveLength(1);
|
|
15
15
|
expect(segmentVector.segments[0].vertexLength).toBe(10);
|
|
16
16
|
});
|
|
@@ -4,7 +4,7 @@ import {EdgeInsets} from '../geo/edge_insets.ts';
|
|
|
4
4
|
describe('EdgeInsets', () => {
|
|
5
5
|
describe('constructor', () => {
|
|
6
6
|
test('creates an object with default values', () => {
|
|
7
|
-
expect(new EdgeInsets()
|
|
7
|
+
expect(new EdgeInsets()).toBeInstanceOf(EdgeInsets);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
test('invalid initialization', () => {
|
package/src/geo/lng_lat.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {LngLat} from '../geo/lng_lat.ts';
|
|
|
3
3
|
|
|
4
4
|
describe('LngLat', () => {
|
|
5
5
|
test('constructor', () => {
|
|
6
|
-
expect(new LngLat(0, 0)
|
|
6
|
+
expect(new LngLat(0, 0)).toBeInstanceOf(LngLat);
|
|
7
7
|
|
|
8
8
|
expect(() => {
|
|
9
9
|
new LngLat(0, -91);
|
|
@@ -15,12 +15,12 @@ describe('LngLat', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
test('convert', () => {
|
|
18
|
-
expect(LngLat.convert([0, 10])
|
|
19
|
-
expect(LngLat.convert({lng: 0, lat: 10})
|
|
20
|
-
expect(LngLat.convert({lng: 0, lat: 0})
|
|
21
|
-
expect(LngLat.convert({lon: 0, lat: 10})
|
|
22
|
-
expect(LngLat.convert({lon: 0, lat: 0})
|
|
23
|
-
expect(LngLat.convert(new LngLat(0, 0))
|
|
18
|
+
expect(LngLat.convert([0, 10])).toBeInstanceOf(LngLat);
|
|
19
|
+
expect(LngLat.convert({lng: 0, lat: 10})).toBeInstanceOf(LngLat);
|
|
20
|
+
expect(LngLat.convert({lng: 0, lat: 0})).toBeInstanceOf(LngLat);
|
|
21
|
+
expect(LngLat.convert({lon: 0, lat: 10})).toBeInstanceOf(LngLat);
|
|
22
|
+
expect(LngLat.convert({lon: 0, lat: 0})).toBeInstanceOf(LngLat);
|
|
23
|
+
expect(LngLat.convert(new LngLat(0, 0))).toBeInstanceOf(LngLat);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
test('wrap', () => {
|
|
@@ -336,25 +336,25 @@ describe('LngLatBounds', () => {
|
|
|
336
336
|
test('point is in bounds', () => {
|
|
337
337
|
const llb = new LngLatBounds([-1, -1], [1, 1]);
|
|
338
338
|
const ll = {lng: 0, lat: 0};
|
|
339
|
-
expect(llb.contains(ll)).
|
|
339
|
+
expect(llb.contains(ll)).toBe(true);
|
|
340
340
|
});
|
|
341
341
|
|
|
342
342
|
test('point is not in bounds', () => {
|
|
343
343
|
const llb = new LngLatBounds([-1, -1], [1, 1]);
|
|
344
344
|
const ll = {lng: 3, lat: 3};
|
|
345
|
-
expect(llb.contains(ll)).
|
|
345
|
+
expect(llb.contains(ll)).toBe(false);
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
test('point is in bounds that spans dateline', () => {
|
|
349
349
|
const llb = new LngLatBounds([190, -10], [170, 10]);
|
|
350
350
|
const ll = {lng: 180, lat: 0};
|
|
351
|
-
expect(llb.contains(ll)).
|
|
351
|
+
expect(llb.contains(ll)).toBe(true);
|
|
352
352
|
});
|
|
353
353
|
|
|
354
354
|
test('point is not in bounds that spans dateline', () => {
|
|
355
355
|
const llb = new LngLatBounds([190, -10], [170, 10]);
|
|
356
356
|
const ll = {lng: 0, lat: 0};
|
|
357
|
-
expect(llb.contains(ll)).
|
|
357
|
+
expect(llb.contains(ll)).toBe(false);
|
|
358
358
|
});
|
|
359
359
|
});
|
|
360
360
|
});
|
|
@@ -4,8 +4,8 @@ import {MercatorCoordinate, mercatorScale} from './mercator_coordinate.ts';
|
|
|
4
4
|
|
|
5
5
|
describe('LngLat', () => {
|
|
6
6
|
test('constructor', () => {
|
|
7
|
-
expect(new MercatorCoordinate(0, 0)
|
|
8
|
-
expect(new MercatorCoordinate(0, 0, 0)
|
|
7
|
+
expect(new MercatorCoordinate(0, 0)).toBeInstanceOf(MercatorCoordinate);
|
|
8
|
+
expect(new MercatorCoordinate(0, 0, 0)).toBeInstanceOf(MercatorCoordinate);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
test('fromLngLat', () => {
|