maplibre-gl 2.3.1-pre.2 → 2.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-csp-worker.js +1 -1
- package/dist/maplibre-gl-csp.js +1 -1
- package/dist/maplibre-gl-dev.js +60 -16
- package/dist/maplibre-gl.d.ts +12 -0
- package/dist/maplibre-gl.js +3 -3
- package/package.json +11 -10
- package/src/render/draw_debug.ts +1 -1
- package/src/render/draw_fill.ts +9 -1
- package/src/render/draw_fill_extrusion.ts +8 -1
- package/src/render/draw_heatmap.ts +5 -3
- package/src/render/draw_hillshade.ts +17 -2
- package/src/render/program/line_program.ts +1 -1
- package/src/style/style.ts +1 -0
- package/src/style/style_layer/symbol_style_layer.ts +4 -5
- package/src/style-spec/CHANGELOG.md +2 -2
- package/src/ui/camera.test.ts +64 -1
- package/src/ui/camera.ts +36 -0
- package/src/ui/control/attribution_control.test.ts +62 -0
- package/src/ui/control/attribution_control.ts +4 -2
- package/src/ui/control/terrain_control.test.ts +60 -0
- package/src/ui/handler/map_event.test.ts +65 -1
- package/src/ui/handler/map_event.ts +4 -1
- package/src/ui/map.test.ts +105 -0
- package/src/ui/map.ts +8 -1
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": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"main": "dist/maplibre-gl.js",
|
|
6
6
|
"style": "dist/maplibre-gl.css",
|
|
7
7
|
"license": "BSD-3-Clause",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"@types/d3": "^7.4.0",
|
|
53
53
|
"@types/diff": "^5.0.2",
|
|
54
54
|
"@types/earcut": "^2.1.1",
|
|
55
|
-
"@types/eslint": "^8.4.
|
|
55
|
+
"@types/eslint": "^8.4.6",
|
|
56
56
|
"@types/gl": "^4.1.1",
|
|
57
57
|
"@types/glob": "^7.2.0",
|
|
58
|
-
"@types/jest": "^28.1.
|
|
58
|
+
"@types/jest": "^28.1.7",
|
|
59
59
|
"@types/jsdom": "^20.0.0",
|
|
60
60
|
"@types/minimist": "^1.2.2",
|
|
61
61
|
"@types/murmurhash-js": "^1.0.3",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"@types/shuffle-seed": "^1.1.0",
|
|
71
71
|
"@types/supercluster": "^7.1.0",
|
|
72
72
|
"@types/window-or-global": "^1.0.4",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
74
|
-
"@typescript-eslint/parser": "^5.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
|
74
|
+
"@typescript-eslint/parser": "^5.33.1",
|
|
75
75
|
"acorn-import-assertions": "^1.8.0",
|
|
76
76
|
"address": "^1.2.0",
|
|
77
77
|
"benchmark": "^2.1.4",
|
|
@@ -80,13 +80,13 @@
|
|
|
80
80
|
"d3": "^7.6.1",
|
|
81
81
|
"d3-queue": "^3.0.7",
|
|
82
82
|
"diff": "^5.1.0",
|
|
83
|
-
"documentation": "14.0.0
|
|
83
|
+
"documentation": "14.0.0",
|
|
84
84
|
"dts-bundle-generator": "^6.12.0",
|
|
85
85
|
"eslint": "^8.22.0",
|
|
86
86
|
"eslint-config-mourner": "^3.0.0",
|
|
87
87
|
"eslint-plugin-html": "^7.1.0",
|
|
88
88
|
"eslint-plugin-import": "^2.26.0",
|
|
89
|
-
"eslint-plugin-jest": "^26.7
|
|
89
|
+
"eslint-plugin-jest": "^26.8.7",
|
|
90
90
|
"eslint-plugin-jsdoc": "^39.3.4",
|
|
91
91
|
"eslint-plugin-react": "^7.30.1",
|
|
92
92
|
"gl": "^5.0.3",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"react": "^18.2.0",
|
|
117
117
|
"react-dom": "^18.2.0",
|
|
118
118
|
"request": "^2.88.0",
|
|
119
|
-
"rollup": "^2.78.
|
|
119
|
+
"rollup": "^2.78.1",
|
|
120
120
|
"rollup-plugin-import-assert": "^2.1.0",
|
|
121
121
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
122
122
|
"rollup-plugin-terser": "^7.0.2",
|
|
@@ -125,8 +125,8 @@
|
|
|
125
125
|
"shuffle-seed": "^1.1.6",
|
|
126
126
|
"source-map-explorer": "^2.5.2",
|
|
127
127
|
"st": "^3.0.0",
|
|
128
|
-
"stylelint": "^14.
|
|
129
|
-
"stylelint-config-standard": "^
|
|
128
|
+
"stylelint": "^14.11.0",
|
|
129
|
+
"stylelint-config-standard": "^28.0.0",
|
|
130
130
|
"ts-jest": "^28.0.8",
|
|
131
131
|
"ts-node": "^10.9.1",
|
|
132
132
|
"typescript": "^4.7.4"
|
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
"generate-typings": "node --loader ts-node/esm --experimental-specifier-resolution=node build/generate-typings.ts",
|
|
140
140
|
"generate-query-test-fixtures": "node --loader ts-node/esm --experimental-specifier-resolution=node build/generate-query-test-fixtures.ts",
|
|
141
141
|
"generate-debug-index-file": "node --loader ts-node/esm --experimental-specifier-resolution=node build/generate-debug-index-file.ts",
|
|
142
|
+
"build-dist": "npm run generate-typings && npm run build-dev && npm run build-prod && npm run build-csp && npm run build-css",
|
|
142
143
|
"build-dev": "rollup --configPlugin @rollup/plugin-typescript -c --environment BUILD:dev",
|
|
143
144
|
"watch-dev": "rollup --configPlugin @rollup/plugin-typescript -c --environment BUILD:dev --watch",
|
|
144
145
|
"build-prod": "rollup --configPlugin @rollup/plugin-typescript -c --environment BUILD:production",
|
package/src/render/draw_debug.ts
CHANGED
|
@@ -66,7 +66,7 @@ function drawDebug(painter: Painter, sourceCache: SourceCache, coords: Array<Ove
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function drawDebugTile(painter, sourceCache, coord: OverscaledTileID) {
|
|
69
|
+
function drawDebugTile(painter: Painter, sourceCache: SourceCache, coord: OverscaledTileID) {
|
|
70
70
|
const context = painter.context;
|
|
71
71
|
const gl = context.gl;
|
|
72
72
|
|
package/src/render/draw_fill.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Color from '../style-spec/util/color';
|
|
2
2
|
import DepthMode from '../gl/depth_mode';
|
|
3
3
|
import CullFaceMode from '../gl/cull_face_mode';
|
|
4
|
+
import ColorMode from '../gl/color_mode';
|
|
4
5
|
import {
|
|
5
6
|
fillUniformValues,
|
|
6
7
|
fillPatternUniformValues,
|
|
@@ -56,7 +57,14 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function drawFillTiles(
|
|
60
|
+
function drawFillTiles(
|
|
61
|
+
painter: Painter,
|
|
62
|
+
sourceCache: SourceCache,
|
|
63
|
+
layer: FillStyleLayer,
|
|
64
|
+
coords: Array<OverscaledTileID>,
|
|
65
|
+
depthMode: Readonly<DepthMode>,
|
|
66
|
+
colorMode: Readonly<ColorMode>,
|
|
67
|
+
isOutline: boolean) {
|
|
60
68
|
const gl = painter.context.gl;
|
|
61
69
|
|
|
62
70
|
const patternProperty = layer.paint.get('fill-pattern');
|
|
@@ -45,7 +45,14 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function drawExtrusionTiles(
|
|
48
|
+
function drawExtrusionTiles(
|
|
49
|
+
painter: Painter,
|
|
50
|
+
source: SourceCache,
|
|
51
|
+
layer: FillExtrusionStyleLayer,
|
|
52
|
+
coords: OverscaledTileID[],
|
|
53
|
+
depthMode: DepthMode,
|
|
54
|
+
stencilMode: Readonly<StencilMode>,
|
|
55
|
+
colorMode: Readonly<ColorMode>) {
|
|
49
56
|
const context = painter.context;
|
|
50
57
|
const gl = context.gl;
|
|
51
58
|
const patternProperty = layer.paint.get('fill-extrusion-pattern');
|
|
@@ -4,6 +4,8 @@ import DepthMode from '../gl/depth_mode';
|
|
|
4
4
|
import StencilMode from '../gl/stencil_mode';
|
|
5
5
|
import ColorMode from '../gl/color_mode';
|
|
6
6
|
import CullFaceMode from '../gl/cull_face_mode';
|
|
7
|
+
import Context from '../gl/context';
|
|
8
|
+
import Framebuffer from '../gl/framebuffer';
|
|
7
9
|
import {
|
|
8
10
|
heatmapUniformValues,
|
|
9
11
|
heatmapTextureUniformValues
|
|
@@ -67,7 +69,7 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
function bindFramebuffer(context, painter, layer) {
|
|
72
|
+
function bindFramebuffer(context: Context, painter: Painter, layer: HeatmapStyleLayer) {
|
|
71
73
|
const gl = context.gl;
|
|
72
74
|
context.activeTexture.set(gl.TEXTURE1);
|
|
73
75
|
|
|
@@ -94,7 +96,7 @@ function bindFramebuffer(context, painter, layer) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
function bindTextureToFramebuffer(context, painter, texture, fbo) {
|
|
99
|
+
function bindTextureToFramebuffer(context: Context, painter: Painter, texture: WebGLTexture, fbo: Framebuffer) {
|
|
98
100
|
const gl = context.gl;
|
|
99
101
|
// Use the higher precision half-float texture where available (producing much smoother looking heatmaps);
|
|
100
102
|
// Otherwise, fall back to a low precision texture
|
|
@@ -103,7 +105,7 @@ function bindTextureToFramebuffer(context, painter, texture, fbo) {
|
|
|
103
105
|
fbo.colorAttachment.set(texture);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
function renderTextureToMap(painter, layer) {
|
|
108
|
+
function renderTextureToMap(painter: Painter, layer: HeatmapStyleLayer) {
|
|
107
109
|
const context = painter.context;
|
|
108
110
|
const gl = context.gl;
|
|
109
111
|
|
|
@@ -2,6 +2,8 @@ import Texture from './texture';
|
|
|
2
2
|
import StencilMode from '../gl/stencil_mode';
|
|
3
3
|
import DepthMode from '../gl/depth_mode';
|
|
4
4
|
import CullFaceMode from '../gl/cull_face_mode';
|
|
5
|
+
import ColorMode from '../gl/color_mode';
|
|
6
|
+
import Tile from '../source/tile';
|
|
5
7
|
import {
|
|
6
8
|
hillshadeUniformValues,
|
|
7
9
|
hillshadeUniformPrepareValues
|
|
@@ -37,7 +39,14 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh
|
|
|
37
39
|
context.viewport.set([0, 0, painter.width, painter.height]);
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
function renderHillshade(
|
|
42
|
+
function renderHillshade(
|
|
43
|
+
painter: Painter,
|
|
44
|
+
coord: OverscaledTileID,
|
|
45
|
+
tile: Tile,
|
|
46
|
+
layer: HillshadeStyleLayer,
|
|
47
|
+
depthMode: Readonly<DepthMode>,
|
|
48
|
+
stencilMode: Readonly<StencilMode>,
|
|
49
|
+
colorMode: Readonly<ColorMode>) {
|
|
41
50
|
const context = painter.context;
|
|
42
51
|
const gl = context.gl;
|
|
43
52
|
const fbo = tile.fbo;
|
|
@@ -58,7 +67,13 @@ function renderHillshade(painter, coord, tile, layer, depthMode, stencilMode, co
|
|
|
58
67
|
|
|
59
68
|
// hillshade rendering is done in two steps. the prepare step first calculates the slope of the terrain in the x and y
|
|
60
69
|
// directions for each pixel, and saves those values to a framebuffer texture in the r and g channels.
|
|
61
|
-
function prepareHillshade(
|
|
70
|
+
function prepareHillshade(
|
|
71
|
+
painter: Painter,
|
|
72
|
+
tile: Tile,
|
|
73
|
+
layer: HillshadeStyleLayer,
|
|
74
|
+
depthMode: Readonly<DepthMode>,
|
|
75
|
+
stencilMode: Readonly<StencilMode>,
|
|
76
|
+
colorMode: Readonly<ColorMode>) {
|
|
62
77
|
const context = painter.context;
|
|
63
78
|
const gl = context.gl;
|
|
64
79
|
const dem = tile.dem;
|
|
@@ -186,7 +186,7 @@ function calculateTileRatio(tile: Tile, transform: Transform) {
|
|
|
186
186
|
return 1 / pixelsToTileUnits(tile, 1, transform.tileZoom);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
function calculateMatrix(painter, tile, layer, coord) {
|
|
189
|
+
function calculateMatrix(painter: Painter, tile: Tile, layer: LineStyleLayer, coord: OverscaledTileID) {
|
|
190
190
|
return painter.translatePosMatrix(
|
|
191
191
|
coord ? coord.posMatrix : tile.tileID.posMatrix,
|
|
192
192
|
tile,
|
package/src/style/style.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type {BucketParameters} from '../../data/bucket';
|
|
|
24
24
|
import type {SymbolLayoutProps, SymbolPaintProps} from './symbol_style_layer_properties.g';
|
|
25
25
|
import type EvaluationParameters from '../evaluation_parameters';
|
|
26
26
|
import type {LayerSpecification} from '../../style-spec/types.g';
|
|
27
|
-
import type {Feature, SourceExpression
|
|
27
|
+
import type {Feature, SourceExpression} from '../../style-spec/expression';
|
|
28
28
|
import type {Expression} from '../../style-spec/expression/expression';
|
|
29
29
|
import type {CanonicalTileID} from '../../source/tile_id';
|
|
30
30
|
import {FormattedType} from '../../style-spec/expression/types';
|
|
@@ -122,12 +122,11 @@ class SymbolStyleLayer extends StyleLayer {
|
|
|
122
122
|
const styleExpression = new StyleExpression(override, overriden.property.specification);
|
|
123
123
|
let expression = null;
|
|
124
124
|
if (overriden.value.kind === 'constant' || overriden.value.kind === 'source') {
|
|
125
|
-
expression =
|
|
125
|
+
expression = new ZoomConstantExpression('source', styleExpression) as SourceExpression;
|
|
126
126
|
} else {
|
|
127
|
-
expression =
|
|
127
|
+
expression = new ZoomDependentExpression('composite',
|
|
128
128
|
styleExpression,
|
|
129
|
-
overriden.value.zoomStops
|
|
130
|
-
(overriden.value as any)._interpolationType) as CompositeExpression);
|
|
129
|
+
overriden.value.zoomStops);
|
|
131
130
|
}
|
|
132
131
|
this.paint._values[overridable] = new PossiblyEvaluatedPropertyValue(overriden.property,
|
|
133
132
|
expression,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
### Breaking changes
|
|
8
8
|
|
|
9
|
-
* Renamed `ParsingError` to `ExpressionParsingError` as there were two with the same name and added typescript typings [1468](https://github.com/maplibre/maplibre-gl-js/pull/1468)
|
|
9
|
+
* Renamed `ParsingError` to `ExpressionParsingError` as there were two with the same name and added typescript typings [#1468](https://github.com/maplibre/maplibre-gl-js/pull/1468)
|
|
10
10
|
|
|
11
11
|
## 16.1.0
|
|
12
12
|
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
## 13.16.0
|
|
56
56
|
|
|
57
57
|
### ✨ Features and improvements
|
|
58
|
-
* Added `volatile` source property to control storing the tiles in local storage. ([9702](https://github.com/mapbox/mapbox-gl-js/pull/9702))
|
|
58
|
+
* Added `volatile` source property to control storing the tiles in local storage. ([#9702](https://github.com/mapbox/mapbox-gl-js/pull/9702))
|
|
59
59
|
|
|
60
60
|
* Added `clusterMinPoints` option for clustered GeoJSON sources that defines the minimum number of points to form a cluster. ([#9748](https://github.com/mapbox/mapbox-gl-js/pull/9748))
|
|
61
61
|
|
package/src/ui/camera.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import Camera from '../ui/camera';
|
|
1
|
+
import Camera, {CameraOptions} from '../ui/camera';
|
|
2
2
|
import Transform from '../geo/transform';
|
|
3
3
|
import TaskQueue, {TaskID} from '../util/task_queue';
|
|
4
4
|
import browser from '../util/browser';
|
|
5
5
|
import {fixedLngLat, fixedNum} from '../../test/unit/lib/fixed';
|
|
6
6
|
import {setMatchMedia} from '../util/test/util';
|
|
7
|
+
import {mercatorZfromAltitude} from '../geo/mercator_coordinate';
|
|
7
8
|
|
|
8
9
|
beforeEach(() => {
|
|
9
10
|
setMatchMedia();
|
|
@@ -55,6 +56,68 @@ function assertTransitionTime(done, camera, min, max) {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
describe('#calculateCameraOptionsFromTo', () => {
|
|
60
|
+
// Choose initial zoom to avoid center being constrained by mercator latitude limits.
|
|
61
|
+
const camera = createCamera({zoom: 1});
|
|
62
|
+
|
|
63
|
+
test('look at north', () => {
|
|
64
|
+
const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 1, lat: 1});
|
|
65
|
+
expect(cameraOptions).toBeDefined();
|
|
66
|
+
expect(cameraOptions.center).toBeDefined();
|
|
67
|
+
expect(cameraOptions.bearing).toBeCloseTo(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('look at west', () => {
|
|
71
|
+
const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 0, lat: 0});
|
|
72
|
+
expect(cameraOptions).toBeDefined();
|
|
73
|
+
expect(cameraOptions.bearing).toBeCloseTo(-90);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('pitch 45', () => {
|
|
77
|
+
// altitude same as grounddistance => 45°
|
|
78
|
+
// distance between lng x and lng x+1 is 111.2km at same lat
|
|
79
|
+
const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 111200, {lng: 0, lat: 0});
|
|
80
|
+
expect(cameraOptions).toBeDefined();
|
|
81
|
+
expect(cameraOptions.pitch).toBeCloseTo(45);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('pitch 90', () => {
|
|
85
|
+
const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 0, {lng: 0, lat: 0});
|
|
86
|
+
expect(cameraOptions).toBeDefined();
|
|
87
|
+
expect(cameraOptions.pitch).toBeCloseTo(90);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('pitch 153.435', () => {
|
|
91
|
+
|
|
92
|
+
// distance between lng x and lng x+1 is 111.2km at same lat
|
|
93
|
+
// (elevation difference of cam and center) / 2 = grounddistance =>
|
|
94
|
+
// acos(111.2 / sqrt(111.2² + (111.2 * 2)²)) = acos(1/sqrt(5)) => 63.435 + 90 (looking up) = 153.435
|
|
95
|
+
const cameraOptions: CameraOptions = camera.calculateCameraOptionsFromTo({lng: 1, lat: 0}, 111200, {lng: 0, lat: 0}, 111200 * 3);
|
|
96
|
+
expect(cameraOptions).toBeDefined();
|
|
97
|
+
expect(cameraOptions.pitch).toBeCloseTo(153.435);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('zoom distance 1000', () => {
|
|
101
|
+
const expectedZoom = Math.log2(camera.transform.cameraToCenterDistance / mercatorZfromAltitude(1000, 0) / camera.transform.tileSize);
|
|
102
|
+
const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 0, lat: 0}, 1000);
|
|
103
|
+
|
|
104
|
+
expect(cameraOptions).toBeDefined();
|
|
105
|
+
expect(cameraOptions.zoom).toBeCloseTo(expectedZoom);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('zoom distance 1 lng (111.2km), 111.2km altitude away', () => {
|
|
109
|
+
const expectedZoom = Math.log2(camera.transform.cameraToCenterDistance / mercatorZfromAltitude(Math.hypot(111200, 111200), 0) / camera.transform.tileSize);
|
|
110
|
+
const cameraOptions = camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 1, lat: 0}, 111200);
|
|
111
|
+
|
|
112
|
+
expect(cameraOptions).toBeDefined();
|
|
113
|
+
expect(cameraOptions.zoom).toBeCloseTo(expectedZoom);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('same To as From error', () => {
|
|
117
|
+
expect(() => { camera.calculateCameraOptionsFromTo({lng: 0, lat: 0}, 0, {lng: 0, lat: 0}, 0); }).toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
58
121
|
describe('#jumpTo', () => {
|
|
59
122
|
// Choose initial zoom to avoid center being constrained by mercator latitude limits.
|
|
60
123
|
const camera = createCamera({zoom: 1});
|
package/src/ui/camera.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {LngLatLike} from '../geo/lng_lat';
|
|
|
12
12
|
import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
|
|
13
13
|
import type {TaskID} from '../util/task_queue';
|
|
14
14
|
import type {PaddingOptions} from '../geo/edge_insets';
|
|
15
|
+
import MercatorCoordinate from '../geo/mercator_coordinate';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* A [Point](https://github.com/mapbox/point-geometry) or an array of two numbers representing `x` and `y` screen coordinates in pixels.
|
|
@@ -774,6 +775,41 @@ abstract class Camera extends Evented {
|
|
|
774
775
|
return this.fire(new Event('moveend', eventData));
|
|
775
776
|
}
|
|
776
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Calculates pitch, zoom and bearing for looking at @param newCenter with the camera position being @param newCenter
|
|
780
|
+
* and returns them as Cameraoptions.
|
|
781
|
+
* @memberof Map#
|
|
782
|
+
* @param from The camera to look from
|
|
783
|
+
* @param altitudeFrom The altitude of the camera to look from
|
|
784
|
+
* @param to The center to look at
|
|
785
|
+
* @param altitudeTo Optional altitude of the center to look at. If none given the ground height will be used.
|
|
786
|
+
* @returns {CameraOptions} the calculated camera options
|
|
787
|
+
*/
|
|
788
|
+
calculateCameraOptionsFromTo(from: LngLat, altitudeFrom: number, to: LngLat, altitudeTo: number = 0) : CameraOptions {
|
|
789
|
+
const fromMerc = MercatorCoordinate.fromLngLat(from, altitudeFrom);
|
|
790
|
+
const toMerc = MercatorCoordinate.fromLngLat(to, altitudeTo);
|
|
791
|
+
const dx = toMerc.x - fromMerc.x;
|
|
792
|
+
const dy = toMerc.y - fromMerc.y;
|
|
793
|
+
const dz = toMerc.z - fromMerc.z;
|
|
794
|
+
|
|
795
|
+
const distance3D = Math.hypot(dx, dy, dz);
|
|
796
|
+
if (distance3D === 0) throw new Error('Can\'t calculate camera options with same From and To');
|
|
797
|
+
|
|
798
|
+
const groundDistance = Math.hypot(dx, dy);
|
|
799
|
+
|
|
800
|
+
const zoom = this.transform.scaleZoom(this.transform.cameraToCenterDistance / distance3D / this.transform.tileSize);
|
|
801
|
+
const bearing = (Math.atan2(dx, -dy) * 180) / Math.PI;
|
|
802
|
+
let pitch = (Math.acos(groundDistance / distance3D) * 180) / Math.PI;
|
|
803
|
+
pitch = dz < 0 ? 90 - pitch : 90 + pitch;
|
|
804
|
+
|
|
805
|
+
return {
|
|
806
|
+
center: toMerc.toLngLat(),
|
|
807
|
+
zoom,
|
|
808
|
+
pitch,
|
|
809
|
+
bearing
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
777
813
|
/**
|
|
778
814
|
* Changes any combination of `center`, `zoom`, `bearing`, `pitch`, and `padding` with an animated transition
|
|
779
815
|
* between old and new values. The map will retain its current values for any
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import AttributionControl from './attribution_control';
|
|
2
2
|
import {createMap as globalCreateMap, setWebGlContext, setPerformance, setMatchMedia} from '../../util/test/util';
|
|
3
3
|
import simulate from '../../../test/unit/lib/simulate_interaction';
|
|
4
|
+
import {fakeServer} from 'nise';
|
|
4
5
|
|
|
5
6
|
function createMap() {
|
|
6
7
|
|
|
@@ -272,6 +273,67 @@ describe('AttributionControl', () => {
|
|
|
272
273
|
});
|
|
273
274
|
});
|
|
274
275
|
|
|
276
|
+
test('does not show attributions for sources that are used for terrain when they are not in use', done => {
|
|
277
|
+
global.fetch = null;
|
|
278
|
+
const server = fakeServer.create();
|
|
279
|
+
server.respondWith('/source.json', JSON.stringify({
|
|
280
|
+
minzoom: 5,
|
|
281
|
+
maxzoom: 12,
|
|
282
|
+
attribution: 'Terrain',
|
|
283
|
+
tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
|
|
284
|
+
bounds: [-47, -7, -45, -5]
|
|
285
|
+
}));
|
|
286
|
+
|
|
287
|
+
const attribution = new AttributionControl();
|
|
288
|
+
map.addControl(attribution);
|
|
289
|
+
|
|
290
|
+
map.on('load', () => {
|
|
291
|
+
map.addSource('1', {type: 'raster-dem', url: '/source.json'});
|
|
292
|
+
server.respond();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
let times = 0;
|
|
296
|
+
map.on('data', (e) => {
|
|
297
|
+
if (e.dataType === 'source' && e.sourceDataType === 'visibility') {
|
|
298
|
+
if (++times === 1) {
|
|
299
|
+
expect(attribution._innerContainer.innerHTML).toBe('');
|
|
300
|
+
done();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('shows attributions for sources that are used for terrain', done => {
|
|
307
|
+
global.fetch = null;
|
|
308
|
+
const server = fakeServer.create();
|
|
309
|
+
server.respondWith('/source.json', JSON.stringify({
|
|
310
|
+
minzoom: 5,
|
|
311
|
+
maxzoom: 12,
|
|
312
|
+
attribution: 'Terrain',
|
|
313
|
+
tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
|
|
314
|
+
bounds: [-47, -7, -45, -5]
|
|
315
|
+
}));
|
|
316
|
+
|
|
317
|
+
const attribution = new AttributionControl();
|
|
318
|
+
map.addControl(attribution);
|
|
319
|
+
|
|
320
|
+
map.on('load', () => {
|
|
321
|
+
map.addSource('1', {type: 'raster-dem', url: '/source.json'});
|
|
322
|
+
server.respond();
|
|
323
|
+
map.setTerrain({source: '1'});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
let times = 0;
|
|
327
|
+
map.on('data', (e) => {
|
|
328
|
+
if (e.dataType === 'source' && e.sourceDataType === 'visibility') {
|
|
329
|
+
if (++times === 1) {
|
|
330
|
+
expect(attribution._innerContainer.innerHTML).toBe('Terrain');
|
|
331
|
+
done();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
275
337
|
test('toggles attributions for sources whose visibility changes when zooming', done => {
|
|
276
338
|
const attribution = new AttributionControl();
|
|
277
339
|
map.addControl(attribution);
|
|
@@ -63,6 +63,7 @@ class AttributionControl implements IControl {
|
|
|
63
63
|
|
|
64
64
|
this._map.on('styledata', this._updateData);
|
|
65
65
|
this._map.on('sourcedata', this._updateData);
|
|
66
|
+
this._map.on('terrain', this._updateData);
|
|
66
67
|
this._map.on('resize', this._updateCompact);
|
|
67
68
|
this._map.on('drag', this._updateCompactMinimize);
|
|
68
69
|
|
|
@@ -74,6 +75,7 @@ class AttributionControl implements IControl {
|
|
|
74
75
|
|
|
75
76
|
this._map.off('styledata', this._updateData);
|
|
76
77
|
this._map.off('sourcedata', this._updateData);
|
|
78
|
+
this._map.off('terrain', this._updateData);
|
|
77
79
|
this._map.off('resize', this._updateCompact);
|
|
78
80
|
this._map.off('drag', this._updateCompactMinimize);
|
|
79
81
|
|
|
@@ -101,7 +103,7 @@ class AttributionControl implements IControl {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
_updateData(e: any) {
|
|
104
|
-
if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style')) {
|
|
106
|
+
if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style' || e.type === 'terrain')) {
|
|
105
107
|
this._updateAttributions();
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -131,7 +133,7 @@ class AttributionControl implements IControl {
|
|
|
131
133
|
const sourceCaches = this._map.style.sourceCaches;
|
|
132
134
|
for (const id in sourceCaches) {
|
|
133
135
|
const sourceCache = sourceCaches[id];
|
|
134
|
-
if (sourceCache.used) {
|
|
136
|
+
if (sourceCache.used || sourceCache.usedForTerrain) {
|
|
135
137
|
const source = sourceCache.getSource();
|
|
136
138
|
if (source.attribution && attributions.indexOf(source.attribution) < 0) {
|
|
137
139
|
attributions.push(source.attribution);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import TerrainControl from './terrain_control';
|
|
2
|
+
import {createMap as globalCreateMap, setWebGlContext, setPerformance, setMatchMedia} from '../../util/test/util';
|
|
3
|
+
|
|
4
|
+
function createMap() {
|
|
5
|
+
|
|
6
|
+
return globalCreateMap({
|
|
7
|
+
attributionControl: false,
|
|
8
|
+
style: {
|
|
9
|
+
version: 8,
|
|
10
|
+
sources: {
|
|
11
|
+
terrain: {
|
|
12
|
+
minzoom: 5,
|
|
13
|
+
maxzoom: 12,
|
|
14
|
+
attribution: 'MapLibre',
|
|
15
|
+
tiles: ['http://example.com/{z}/{x}/{y}.pngraw'],
|
|
16
|
+
bounds: [-47, -7, -45, -5]
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
layers: [],
|
|
20
|
+
owner: 'mapblibre',
|
|
21
|
+
id: 'demotiles',
|
|
22
|
+
},
|
|
23
|
+
hash: true
|
|
24
|
+
}, undefined);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let map;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
setWebGlContext();
|
|
31
|
+
setPerformance();
|
|
32
|
+
setMatchMedia();
|
|
33
|
+
map = createMap();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
map.remove();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('TerrainControl', () => {
|
|
41
|
+
test('appears in top-right by default', () => {
|
|
42
|
+
map.addControl(new TerrainControl({source: 'terrain'}));
|
|
43
|
+
|
|
44
|
+
expect(
|
|
45
|
+
map.getContainer().querySelectorAll('.maplibregl-ctrl-top-right .maplibregl-ctrl-terrain')
|
|
46
|
+
).toHaveLength(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('appears in the position specified by the position option', () => {
|
|
50
|
+
map.addControl(new TerrainControl({source: 'terrain'}), 'bottom-right');
|
|
51
|
+
|
|
52
|
+
expect(
|
|
53
|
+
map.getContainer().querySelectorAll('.maplibregl-ctrl-bottom-right .maplibregl-ctrl-terrain')
|
|
54
|
+
).toHaveLength(1);
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
map.getContainer().querySelectorAll('.maplibregl-ctrl-top-right .maplibregl-ctrl-terrain')
|
|
58
|
+
).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -4,7 +4,7 @@ import simulate from '../../../test/unit/lib/simulate_interaction';
|
|
|
4
4
|
import {setMatchMedia, setPerformance, setWebGlContext} from '../../util/test/util';
|
|
5
5
|
|
|
6
6
|
function createMap() {
|
|
7
|
-
return new Map({interactive:
|
|
7
|
+
return new Map({interactive: true, container: DOM.create('div', '', window.document.body)} as any as MapOptions);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
@@ -93,4 +93,68 @@ describe('map events', () => {
|
|
|
93
93
|
|
|
94
94
|
map.remove();
|
|
95
95
|
});
|
|
96
|
+
|
|
97
|
+
test('MapEvent handler fires contextmenu on MacOS/Linux, but only at mouseup', () => {
|
|
98
|
+
const map = createMap();
|
|
99
|
+
const target = map.getCanvas();
|
|
100
|
+
map.dragPan.enable();
|
|
101
|
+
|
|
102
|
+
const contextmenu = jest.fn();
|
|
103
|
+
|
|
104
|
+
map.on('contextmenu', contextmenu);
|
|
105
|
+
|
|
106
|
+
simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
107
|
+
simulate.contextmenu(map.getCanvas(), {target}); // triggered immediately after mousedown
|
|
108
|
+
expect(contextmenu).toHaveBeenCalledTimes(0);
|
|
109
|
+
simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
110
|
+
expect(contextmenu).toHaveBeenCalledTimes(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('MapEvent handler does not fire contextmenu on MacOS/Linux, when moved', () => {
|
|
114
|
+
const map = createMap();
|
|
115
|
+
const target = map.getCanvas();
|
|
116
|
+
map.dragPan.enable();
|
|
117
|
+
|
|
118
|
+
const contextmenu = jest.fn();
|
|
119
|
+
|
|
120
|
+
map.on('contextmenu', contextmenu);
|
|
121
|
+
|
|
122
|
+
simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
123
|
+
simulate.contextmenu(map.getCanvas(), {target}); // triggered immediately after mousedown
|
|
124
|
+
simulate.mousemove(map.getCanvas(), {target, buttons: 2, clientX: 50, clientY: 10});
|
|
125
|
+
simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 70, clientY: 10});
|
|
126
|
+
expect(contextmenu).toHaveBeenCalledTimes(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('MapEvent handler fires contextmenu on Windows', () => {
|
|
130
|
+
const map = createMap();
|
|
131
|
+
const target = map.getCanvas();
|
|
132
|
+
map.dragPan.enable();
|
|
133
|
+
|
|
134
|
+
const contextmenu = jest.fn();
|
|
135
|
+
|
|
136
|
+
map.on('contextmenu', contextmenu);
|
|
137
|
+
|
|
138
|
+
simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
139
|
+
simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
140
|
+
expect(contextmenu).toHaveBeenCalledTimes(0);
|
|
141
|
+
simulate.contextmenu(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10}); // triggered only after mouseup
|
|
142
|
+
expect(contextmenu).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('MapEvent handler does not fire contextmenu on Windows, when moved', () => {
|
|
146
|
+
const map = createMap();
|
|
147
|
+
const target = map.getCanvas();
|
|
148
|
+
map.dragPan.enable();
|
|
149
|
+
|
|
150
|
+
const contextmenu = jest.fn();
|
|
151
|
+
|
|
152
|
+
map.on('contextmenu', contextmenu);
|
|
153
|
+
|
|
154
|
+
simulate.mousedown(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10});
|
|
155
|
+
simulate.mousemove(map.getCanvas(), {target, buttons: 2, clientX: 50, clientY: 10});
|
|
156
|
+
simulate.mouseup(map.getCanvas(), {target, button: 2, clientX: 50, clientY: 10});
|
|
157
|
+
simulate.contextmenu(map.getCanvas(), {target, button: 2, clientX: 10, clientY: 10}); // triggered only after mouseup
|
|
158
|
+
expect(contextmenu).toHaveBeenCalledTimes(0);
|
|
159
|
+
});
|
|
96
160
|
});
|