maplibre-gl 2.0.3 → 2.1.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/build/rollup_plugins.js +1 -0
- package/dist/maplibre-gl-dev.js +54589 -0
- package/dist/maplibre-gl.css +1 -1
- package/dist/maplibre-gl.d.ts +49 -15
- package/dist/maplibre-gl.js +3 -3
- package/dist/maplibre-gl.js.map +1 -1
- package/dist/package.json +1 -0
- package/package.json +78 -100
- package/src/css/maplibre-gl.css +36 -36
- package/src/data/bucket/symbol_bucket.test.ts +1 -1
- package/src/data/bucket/symbol_bucket.ts +3 -3
- package/src/data/program_configuration.ts +1 -1
- package/src/render/draw_debug.ts +1 -1
- package/src/render/glyph_manager.ts +1 -1
- package/src/render/painter.ts +5 -3
- package/src/render/program/circle_program.ts +1 -1
- package/src/render/program/line_program.ts +3 -3
- package/src/render/program/symbol_program.ts +1 -1
- package/src/source/geojson_source.test.ts +2 -1
- package/src/source/geojson_source.ts +1 -1
- package/src/source/image_source.test.ts +153 -0
- package/src/source/raster_dem_tile_source.test.ts +2 -1
- package/src/source/raster_dem_tile_source.ts +1 -1
- package/src/source/raster_tile_source.test.ts +2 -1
- package/src/source/raster_tile_source.ts +1 -1
- package/src/source/source_cache.test.ts +1 -1
- package/src/source/tile.test.ts +1 -1
- package/src/source/tile_cache.test.ts +6 -4
- package/src/source/tile_id.test.ts +10 -12
- package/src/source/tile_id.ts +2 -2
- package/src/source/vector_tile_source.test.ts +14 -2
- package/src/source/vector_tile_source.ts +3 -4
- package/src/style/load_glyph_range.test.ts +0 -2
- package/src/style/load_sprite.ts +2 -1
- package/src/style/properties.ts +1 -1
- package/src/style/style.test.ts +24 -13
- package/src/style/style.ts +1 -1
- package/src/style/style_layer/custom_style_layer.ts +2 -2
- package/src/style/style_layer/fill_extrusion_style_layer.ts +1 -1
- package/src/style/style_layer/symbol_style_layer.ts +19 -0
- package/src/style/style_layer/symbol_style_layer_properties.ts +6 -0
- package/src/style-spec/CHANGELOG.md +6 -0
- package/src/style-spec/package.json +1 -1
- package/src/style-spec/reference/v8.json +68 -2
- package/src/style-spec/types.ts +2 -0
- package/src/symbol/collision_index.ts +19 -19
- package/src/symbol/grid_index.test.ts +42 -19
- package/src/symbol/grid_index.ts +62 -33
- package/src/symbol/placement.ts +82 -53
- package/src/symbol/symbol_style_layer.test.ts +48 -1
- package/src/ui/camera.test.ts +4 -4
- package/src/ui/camera.ts +8 -0
- package/src/ui/control/logo_control.test.ts +1 -0
- package/src/ui/handler/scroll_zoom.test.ts +2 -1
- package/src/ui/map.test.ts +77 -10
- package/src/ui/map.ts +33 -8
- package/src/util/ajax.test.ts +206 -0
- package/src/util/test/util.ts +14 -0
package/src/symbol/placement.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import CollisionIndex from './collision_index';
|
|
2
|
+
import type {FeatureKey} from './collision_index';
|
|
2
3
|
import EXTENT from '../data/extent';
|
|
3
4
|
import * as symbolSize from './symbol_size';
|
|
4
5
|
import * as projection from './projection';
|
|
@@ -12,6 +13,7 @@ import type Transform from '../geo/transform';
|
|
|
12
13
|
import type StyleLayer from '../style/style_layer';
|
|
13
14
|
import {PossiblyEvaluated} from '../style/properties';
|
|
14
15
|
import type {SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated} from '../style/style_layer/symbol_style_layer_properties';
|
|
16
|
+
import {getOverlapMode, OverlapMode} from '../style/style_layer/symbol_style_layer';
|
|
15
17
|
|
|
16
18
|
import type Tile from '../source/tile';
|
|
17
19
|
import SymbolBucket, {CollisionArrays, SingleCollisionBox} from '../data/bucket/symbol_bucket';
|
|
@@ -99,7 +101,7 @@ export class RetainedQueryData {
|
|
|
99
101
|
|
|
100
102
|
type CollisionGroup = {
|
|
101
103
|
ID: number;
|
|
102
|
-
predicate?:
|
|
104
|
+
predicate?: (key: FeatureKey) => boolean;
|
|
103
105
|
};
|
|
104
106
|
|
|
105
107
|
class CollisionGroups {
|
|
@@ -181,20 +183,20 @@ export type VariableOffset = {
|
|
|
181
183
|
};
|
|
182
184
|
|
|
183
185
|
type TileLayerParameters = {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
186
|
+
bucket: SymbolBucket;
|
|
187
|
+
layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>;
|
|
188
|
+
posMatrix: mat4;
|
|
189
|
+
textLabelPlaneMatrix: mat4;
|
|
190
|
+
labelToScreenMatrix: mat4;
|
|
191
|
+
scale: number;
|
|
192
|
+
textPixelRatio: number;
|
|
193
|
+
holdingForFade: boolean;
|
|
194
|
+
collisionBoxArray: CollisionBoxArray;
|
|
195
|
+
partiallyEvaluatedTextSize: {
|
|
196
|
+
uSize: number;
|
|
197
|
+
uSizeT: number;
|
|
198
|
+
};
|
|
199
|
+
collisionGroup: CollisionGroup;
|
|
198
200
|
};
|
|
199
201
|
|
|
200
202
|
export type BucketPart = {
|
|
@@ -344,7 +346,7 @@ export class Placement {
|
|
|
344
346
|
textPixelRatio: number,
|
|
345
347
|
posMatrix: mat4,
|
|
346
348
|
collisionGroup: CollisionGroup,
|
|
347
|
-
|
|
349
|
+
textOverlapMode: OverlapMode,
|
|
348
350
|
symbolInstance: SymbolInstance,
|
|
349
351
|
bucket: SymbolBucket,
|
|
350
352
|
orientation: number,
|
|
@@ -364,14 +366,14 @@ export class Placement {
|
|
|
364
366
|
shiftVariableCollisionBox(
|
|
365
367
|
textBox, shift.x, shift.y,
|
|
366
368
|
rotateWithMap, pitchWithMap, this.transform.angle),
|
|
367
|
-
|
|
369
|
+
textOverlapMode, textPixelRatio, posMatrix, collisionGroup.predicate);
|
|
368
370
|
|
|
369
371
|
if (iconBox) {
|
|
370
372
|
const placedIconBoxes = this.collisionIndex.placeCollisionBox(
|
|
371
373
|
shiftVariableCollisionBox(
|
|
372
374
|
iconBox, shift.x, shift.y,
|
|
373
375
|
rotateWithMap, pitchWithMap, this.transform.angle),
|
|
374
|
-
|
|
376
|
+
textOverlapMode, textPixelRatio, posMatrix, collisionGroup.predicate);
|
|
375
377
|
if (placedIconBoxes.box.length === 0) return;
|
|
376
378
|
}
|
|
377
379
|
|
|
@@ -424,8 +426,10 @@ export class Placement {
|
|
|
424
426
|
|
|
425
427
|
const textOptional = layout.get('text-optional');
|
|
426
428
|
const iconOptional = layout.get('icon-optional');
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
+
const textOverlapMode = getOverlapMode(layout, 'text-overlap', 'text-allow-overlap');
|
|
430
|
+
const textAlwaysOverlap = textOverlapMode === 'always';
|
|
431
|
+
const iconOverlapMode = getOverlapMode(layout, 'icon-overlap', 'icon-allow-overlap');
|
|
432
|
+
const iconAlwaysOverlap = iconOverlapMode === 'always';
|
|
429
433
|
const rotateWithMap = layout.get('text-rotation-alignment') === 'map';
|
|
430
434
|
const pitchWithMap = layout.get('text-pitch-alignment') === 'map';
|
|
431
435
|
const hasIconTextFit = layout.get('icon-text-fit') !== 'none';
|
|
@@ -445,8 +449,8 @@ export class Placement {
|
|
|
445
449
|
// This is the reverse of our normal policy of "fade in on pan", but should look like any other
|
|
446
450
|
// collision and hopefully not be too noticeable.
|
|
447
451
|
// See https://github.com/mapbox/mapbox-gl-js/issues/7172
|
|
448
|
-
const alwaysShowText =
|
|
449
|
-
const alwaysShowIcon =
|
|
452
|
+
const alwaysShowText = textAlwaysOverlap && (iconAlwaysOverlap || !bucket.hasIconData() || iconOptional);
|
|
453
|
+
const alwaysShowIcon = iconAlwaysOverlap && (textAlwaysOverlap || !bucket.hasTextData() || textOptional);
|
|
450
454
|
|
|
451
455
|
if (!bucket.collisionArrays && collisionBoxArray) {
|
|
452
456
|
bucket.deserializeCollisionBoxes(collisionBoxArray);
|
|
@@ -519,8 +523,12 @@ export class Placement {
|
|
|
519
523
|
|
|
520
524
|
if (!layout.get('text-variable-anchor')) {
|
|
521
525
|
const placeBox = (collisionTextBox, orientation) => {
|
|
522
|
-
const placedFeature = this.collisionIndex.placeCollisionBox(
|
|
523
|
-
|
|
526
|
+
const placedFeature = this.collisionIndex.placeCollisionBox(
|
|
527
|
+
collisionTextBox,
|
|
528
|
+
textOverlapMode,
|
|
529
|
+
textPixelRatio,
|
|
530
|
+
posMatrix,
|
|
531
|
+
collisionGroup.predicate);
|
|
524
532
|
if (placedFeature && placedFeature.box && placedFeature.box.length) {
|
|
525
533
|
this.markUsedOrientation(bucket, orientation, symbolInstance);
|
|
526
534
|
this.placedOrientations[symbolInstance.crossTileID] = orientation;
|
|
@@ -562,20 +570,20 @@ export class Placement {
|
|
|
562
570
|
const height = collisionTextBox.y2 - collisionTextBox.y1;
|
|
563
571
|
const textBoxScale = symbolInstance.textBoxScale;
|
|
564
572
|
|
|
565
|
-
const variableIconBox = hasIconTextFit &&
|
|
573
|
+
const variableIconBox = hasIconTextFit && (iconOverlapMode === 'never') ? collisionIconBox : null;
|
|
566
574
|
|
|
567
575
|
let placedBox: {
|
|
568
576
|
box: Array<number>;
|
|
569
577
|
offscreen: boolean;
|
|
570
578
|
} = {box: [], offscreen: false};
|
|
571
|
-
const placementAttempts =
|
|
579
|
+
const placementAttempts = (textOverlapMode !== 'never') ? anchors.length * 2 : anchors.length;
|
|
572
580
|
for (let i = 0; i < placementAttempts; ++i) {
|
|
573
581
|
const anchor = anchors[i % anchors.length];
|
|
574
|
-
const
|
|
582
|
+
const overlapMode = (i >= anchors.length) ? textOverlapMode : 'never';
|
|
575
583
|
const result = this.attemptAnchorPlacement(
|
|
576
584
|
anchor, collisionTextBox, width, height,
|
|
577
585
|
textBoxScale, rotateWithMap, pitchWithMap, textPixelRatio, posMatrix,
|
|
578
|
-
collisionGroup,
|
|
586
|
+
collisionGroup, overlapMode, symbolInstance, bucket, orientation, variableIconBox);
|
|
579
587
|
|
|
580
588
|
if (result) {
|
|
581
589
|
placedBox = result.placedGlyphBoxes;
|
|
@@ -637,26 +645,27 @@ export class Placement {
|
|
|
637
645
|
const textPixelPadding = layout.get('text-padding');
|
|
638
646
|
const circlePixelDiameter = symbolInstance.collisionCircleDiameter;
|
|
639
647
|
|
|
640
|
-
placedGlyphCircles = this.collisionIndex.placeCollisionCircles(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
648
|
+
placedGlyphCircles = this.collisionIndex.placeCollisionCircles(
|
|
649
|
+
textOverlapMode,
|
|
650
|
+
placedSymbol,
|
|
651
|
+
bucket.lineVertexArray,
|
|
652
|
+
bucket.glyphOffsetArray,
|
|
653
|
+
fontSize,
|
|
654
|
+
posMatrix,
|
|
655
|
+
textLabelPlaneMatrix,
|
|
656
|
+
labelToScreenMatrix,
|
|
657
|
+
showCollisionBoxes,
|
|
658
|
+
pitchWithMap,
|
|
659
|
+
collisionGroup.predicate,
|
|
660
|
+
circlePixelDiameter,
|
|
661
|
+
textPixelPadding);
|
|
653
662
|
|
|
654
663
|
assert(!placedGlyphCircles.circles.length || (!placedGlyphCircles.collisionDetected || showCollisionBoxes));
|
|
655
|
-
// If text-
|
|
664
|
+
// If text-overlap is set to 'always', force "placedCircles" to true
|
|
656
665
|
// In theory there should always be at least one circle placed
|
|
657
666
|
// in this case, but for now quirks in text-anchor
|
|
658
667
|
// and text-offset may prevent that from being true.
|
|
659
|
-
placeText =
|
|
668
|
+
placeText = textAlwaysOverlap || (placedGlyphCircles.circles.length > 0 && !placedGlyphCircles.collisionDetected);
|
|
660
669
|
offscreen = offscreen && placedGlyphCircles.offscreen;
|
|
661
670
|
}
|
|
662
671
|
|
|
@@ -673,7 +682,7 @@ export class Placement {
|
|
|
673
682
|
rotateWithMap, pitchWithMap, this.transform.angle) :
|
|
674
683
|
iconBox;
|
|
675
684
|
return this.collisionIndex.placeCollisionBox(shiftedIconBox,
|
|
676
|
-
|
|
685
|
+
iconOverlapMode, textPixelRatio, posMatrix, collisionGroup.predicate);
|
|
677
686
|
};
|
|
678
687
|
|
|
679
688
|
if (placedVerticalText && placedVerticalText.box && placedVerticalText.box.length && collisionArrays.verticalIconBox) {
|
|
@@ -701,22 +710,42 @@ export class Placement {
|
|
|
701
710
|
|
|
702
711
|
if (placeText && placedGlyphBoxes && placedGlyphBoxes.box) {
|
|
703
712
|
if (placedVerticalText && placedVerticalText.box && verticalTextFeatureIndex) {
|
|
704
|
-
this.collisionIndex.insertCollisionBox(
|
|
705
|
-
|
|
713
|
+
this.collisionIndex.insertCollisionBox(
|
|
714
|
+
placedGlyphBoxes.box,
|
|
715
|
+
textOverlapMode,
|
|
716
|
+
layout.get('text-ignore-placement'),
|
|
717
|
+
bucket.bucketInstanceId,
|
|
718
|
+
verticalTextFeatureIndex,
|
|
719
|
+
collisionGroup.ID);
|
|
706
720
|
} else {
|
|
707
|
-
this.collisionIndex.insertCollisionBox(
|
|
708
|
-
|
|
721
|
+
this.collisionIndex.insertCollisionBox(
|
|
722
|
+
placedGlyphBoxes.box,
|
|
723
|
+
textOverlapMode,
|
|
724
|
+
layout.get('text-ignore-placement'),
|
|
725
|
+
bucket.bucketInstanceId,
|
|
726
|
+
textFeatureIndex,
|
|
727
|
+
collisionGroup.ID);
|
|
709
728
|
}
|
|
710
729
|
|
|
711
730
|
}
|
|
712
731
|
if (placeIcon && placedIconBoxes) {
|
|
713
|
-
this.collisionIndex.insertCollisionBox(
|
|
714
|
-
|
|
732
|
+
this.collisionIndex.insertCollisionBox(
|
|
733
|
+
placedIconBoxes.box,
|
|
734
|
+
iconOverlapMode,
|
|
735
|
+
layout.get('icon-ignore-placement'),
|
|
736
|
+
bucket.bucketInstanceId,
|
|
737
|
+
iconFeatureIndex,
|
|
738
|
+
collisionGroup.ID);
|
|
715
739
|
}
|
|
716
740
|
if (placedGlyphCircles) {
|
|
717
741
|
if (placeText) {
|
|
718
|
-
this.collisionIndex.insertCollisionCircles(
|
|
719
|
-
|
|
742
|
+
this.collisionIndex.insertCollisionCircles(
|
|
743
|
+
placedGlyphCircles.circles,
|
|
744
|
+
textOverlapMode,
|
|
745
|
+
layout.get('text-ignore-placement'),
|
|
746
|
+
bucket.bucketInstanceId,
|
|
747
|
+
textFeatureIndex,
|
|
748
|
+
collisionGroup.ID);
|
|
720
749
|
}
|
|
721
750
|
|
|
722
751
|
if (showCollisionBoxes) {
|
|
@@ -887,7 +916,7 @@ export class Placement {
|
|
|
887
916
|
updateLayerOpacities(styleLayer: StyleLayer, tiles: Array<Tile>) {
|
|
888
917
|
const seenCrossTileIDs = {};
|
|
889
918
|
for (const tile of tiles) {
|
|
890
|
-
const symbolBucket =
|
|
919
|
+
const symbolBucket = tile.getBucket(styleLayer) as SymbolBucket;
|
|
891
920
|
if (symbolBucket && tile.latestFeatureIndex && styleLayer.id === symbolBucket.layerIds[0]) {
|
|
892
921
|
this.updateBucketOpacities(symbolBucket, seenCrossTileIDs, tile.collisionBoxArray);
|
|
893
922
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import SymbolStyleLayer from '../style/style_layer/symbol_style_layer';
|
|
1
|
+
import SymbolStyleLayer, {getOverlapMode} from '../style/style_layer/symbol_style_layer';
|
|
2
2
|
import FormatSectionOverride from '../style/format_section_override';
|
|
3
3
|
import properties, {SymbolPaintPropsPossiblyEvaluated} from '../style/style_layer/symbol_style_layer_properties';
|
|
4
4
|
import ZoomHistory from '../style/zoom_history';
|
|
@@ -101,3 +101,50 @@ describe('hasPaintOverrides', () => {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
describe('getOverlapMode', () => {
|
|
106
|
+
test('defaults - no props set', () => {
|
|
107
|
+
const props = {};
|
|
108
|
+
const layer = createSymbolLayer(props);
|
|
109
|
+
|
|
110
|
+
expect(getOverlapMode(layer.layout, 'icon-overlap', 'icon-allow-overlap')).toBe('never');
|
|
111
|
+
expect(getOverlapMode(layer.layout, 'text-overlap', 'text-allow-overlap')).toBe('never');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('-allow-overlap set', () => {
|
|
115
|
+
const props = {layout: {'icon-allow-overlap': false, 'text-allow-overlap': true}};
|
|
116
|
+
const layer = createSymbolLayer(props);
|
|
117
|
+
|
|
118
|
+
expect(getOverlapMode(layer.layout, 'icon-overlap', 'icon-allow-overlap')).toBe('never');
|
|
119
|
+
expect(getOverlapMode(layer.layout, 'text-overlap', 'text-allow-overlap')).toBe('always');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('-overlap set', () => {
|
|
123
|
+
let props = {layout: {'icon-overlap': 'never', 'text-overlap': 'always'}};
|
|
124
|
+
let layer = createSymbolLayer(props);
|
|
125
|
+
|
|
126
|
+
expect(getOverlapMode(layer.layout, 'icon-overlap', 'icon-allow-overlap')).toBe('never');
|
|
127
|
+
expect(getOverlapMode(layer.layout, 'text-overlap', 'text-allow-overlap')).toBe('always');
|
|
128
|
+
|
|
129
|
+
props = {layout: {'icon-overlap': 'always', 'text-overlap': 'cooperative'}};
|
|
130
|
+
layer = createSymbolLayer(props);
|
|
131
|
+
|
|
132
|
+
expect(getOverlapMode(layer.layout, 'icon-overlap', 'icon-allow-overlap')).toBe('always');
|
|
133
|
+
expect(getOverlapMode(layer.layout, 'text-overlap', 'text-allow-overlap')).toBe('cooperative');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('-overlap beats -allow-overlap', () => {
|
|
137
|
+
const props = {
|
|
138
|
+
layout: {
|
|
139
|
+
'icon-overlap': 'never',
|
|
140
|
+
'icon-allow-overlap': true,
|
|
141
|
+
'text-overlap': 'cooperative',
|
|
142
|
+
'text-allow-overlap': false
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const layer = createSymbolLayer(props);
|
|
146
|
+
|
|
147
|
+
expect(getOverlapMode(layer.layout, 'icon-overlap', 'icon-allow-overlap')).toBe('never');
|
|
148
|
+
expect(getOverlapMode(layer.layout, 'text-overlap', 'text-allow-overlap')).toBe('cooperative');
|
|
149
|
+
});
|
|
150
|
+
});
|
package/src/ui/camera.test.ts
CHANGED
|
@@ -756,9 +756,9 @@ describe('#easeTo', () => {
|
|
|
756
756
|
const camera = createCamera();
|
|
757
757
|
|
|
758
758
|
camera
|
|
759
|
-
.on('zoomstart', () => {
|
|
760
|
-
.on('zoom', () => {
|
|
761
|
-
.on('zoomend', () => {
|
|
759
|
+
.on('zoomstart', () => { done('zoomstart failed'); })
|
|
760
|
+
.on('zoom', () => { done('zoom failed'); })
|
|
761
|
+
.on('zoomend', () => { done('zoomend failed'); })
|
|
762
762
|
.on('moveend', () => { done(); });
|
|
763
763
|
|
|
764
764
|
camera.easeTo({center: [100, 0], duration: 0});
|
|
@@ -1545,7 +1545,7 @@ describe('#flyTo', () => {
|
|
|
1545
1545
|
.on('moveend', () => {
|
|
1546
1546
|
endTime = new Date();
|
|
1547
1547
|
timeDiff = endTime - startTime;
|
|
1548
|
-
expect(timeDiff).toBeLessThan(
|
|
1548
|
+
expect(timeDiff).toBeLessThan(30);
|
|
1549
1549
|
done();
|
|
1550
1550
|
});
|
|
1551
1551
|
|
package/src/ui/camera.ts
CHANGED
|
@@ -14,6 +14,14 @@ import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
|
|
|
14
14
|
import type {TaskID} from '../util/task_queue';
|
|
15
15
|
import type {PaddingOptions} from '../geo/edge_insets';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* A [Point](https://github.com/mapbox/point-geometry) or an array of two numbers representing `x` and `y` screen coordinates in pixels.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {(Point | [number, number])} PointLike
|
|
21
|
+
* @example
|
|
22
|
+
* var p1 = new Point(-77, 38); // a PointLike which is a Point
|
|
23
|
+
* var p2 = [-77, 38]; // a PointLike which is an array of two numbers
|
|
24
|
+
*/
|
|
17
25
|
export type PointLike = Point | [number, number];
|
|
18
26
|
|
|
19
27
|
export type RequireAtLeastOne<T> = { [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>; }[keyof T];
|
|
@@ -34,6 +34,7 @@ function createSource(options, logoRequired) {
|
|
|
34
34
|
_skuToken: '1234567890123',
|
|
35
35
|
canonicalizeTileset: tileJSON => tileJSON.tiles
|
|
36
36
|
},
|
|
37
|
+
style: {sourceCaches: {id: {clearTiles() {}}}},
|
|
37
38
|
transform: {angle: 0, pitch: 0, showCollisionBoxes: false},
|
|
38
39
|
_getMapId: () => 1
|
|
39
40
|
}as any as Map);
|
|
@@ -46,7 +46,7 @@ describe('ScrollZoomHandler', () => {
|
|
|
46
46
|
map.remove();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
test('Zooms for single mouse wheel tick with non-magical deltaY',
|
|
49
|
+
test('Zooms for single mouse wheel tick with non-magical deltaY', done => {
|
|
50
50
|
const browserNow = jest.spyOn(browser, 'now');
|
|
51
51
|
const now = 1555555555555;
|
|
52
52
|
browserNow.mockReturnValue(now);
|
|
@@ -60,6 +60,7 @@ describe('ScrollZoomHandler', () => {
|
|
|
60
60
|
simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -20});
|
|
61
61
|
map.on('zoomstart', () => {
|
|
62
62
|
map.remove();
|
|
63
|
+
done();
|
|
63
64
|
});
|
|
64
65
|
});
|
|
65
66
|
|
package/src/ui/map.test.ts
CHANGED
|
@@ -134,15 +134,17 @@ describe('Map', () => {
|
|
|
134
134
|
test('emits load event after a style is set', done => {
|
|
135
135
|
const map = new Map({container: window.document.createElement('div')} as any as MapOptions);
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
const fail = () => done('test failed');
|
|
138
|
+
const pass = () => done();
|
|
139
|
+
|
|
140
|
+
map.on('load', fail);
|
|
138
141
|
|
|
139
142
|
setTimeout(() => {
|
|
140
|
-
map.off('load',
|
|
141
|
-
map.on('load',
|
|
142
|
-
done();
|
|
143
|
-
});
|
|
143
|
+
map.off('load', fail);
|
|
144
|
+
map.on('load', pass);
|
|
144
145
|
map.setStyle(createStyle());
|
|
145
146
|
}, 1);
|
|
147
|
+
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
describe('#setStyle', () => {
|
|
@@ -918,7 +920,7 @@ describe('Map', () => {
|
|
|
918
920
|
test('does not fire "webglcontextlost" after #remove has been called', done => {
|
|
919
921
|
const map = createMap();
|
|
920
922
|
const canvas = map.getCanvas();
|
|
921
|
-
map.once('webglcontextlost', () => done
|
|
923
|
+
map.once('webglcontextlost', () => done('"webglcontextlost" fired after #remove has been called'));
|
|
922
924
|
map.remove();
|
|
923
925
|
// Dispatch the event manually because at the time of this writing, gl does not support
|
|
924
926
|
// the WEBGL_lose_context extension.
|
|
@@ -931,7 +933,7 @@ describe('Map', () => {
|
|
|
931
933
|
const canvas = map.getCanvas();
|
|
932
934
|
|
|
933
935
|
map.once('webglcontextlost', () => {
|
|
934
|
-
map.once('webglcontextrestored', () => done
|
|
936
|
+
map.once('webglcontextrestored', () => done('"webglcontextrestored" fired after #remove has been called'));
|
|
935
937
|
map.remove();
|
|
936
938
|
canvas.dispatchEvent(new window.Event('webglcontextrestored'));
|
|
937
939
|
done();
|
|
@@ -1885,7 +1887,9 @@ describe('Map', () => {
|
|
|
1885
1887
|
if (timer) clearTimeout(timer);
|
|
1886
1888
|
timer = setTimeout(() => {
|
|
1887
1889
|
map.off('render', undefined);
|
|
1888
|
-
map.on('render',
|
|
1890
|
+
map.on('render', () => {
|
|
1891
|
+
done('test failed');
|
|
1892
|
+
});
|
|
1889
1893
|
expect((map as any)._frameId).toBeFalsy();
|
|
1890
1894
|
done();
|
|
1891
1895
|
}, 100);
|
|
@@ -1896,7 +1900,9 @@ describe('Map', () => {
|
|
|
1896
1900
|
const style = createStyle();
|
|
1897
1901
|
const map = createMap({style});
|
|
1898
1902
|
map.on('idle', () => {
|
|
1899
|
-
map.on('render',
|
|
1903
|
+
map.on('render', () => {
|
|
1904
|
+
done('test failed');
|
|
1905
|
+
});
|
|
1900
1906
|
setTimeout(() => {
|
|
1901
1907
|
done();
|
|
1902
1908
|
}, 100);
|
|
@@ -2043,11 +2049,72 @@ describe('Map', () => {
|
|
|
2043
2049
|
});
|
|
2044
2050
|
|
|
2045
2051
|
map.on('styleimagemissing', ({id}) => {
|
|
2046
|
-
done
|
|
2052
|
+
done(`styleimagemissing fired for value ${id}`);
|
|
2047
2053
|
});
|
|
2048
2054
|
});
|
|
2049
2055
|
});
|
|
2050
2056
|
|
|
2057
|
+
describe('setPixelRatio', () => {
|
|
2058
|
+
test('resizes canvas', () => {
|
|
2059
|
+
const container = window.document.createElement('div');
|
|
2060
|
+
Object.defineProperty(container, 'clientWidth', {value: 512});
|
|
2061
|
+
Object.defineProperty(container, 'clientHeight', {value: 512});
|
|
2062
|
+
const map = createMap({container, pixelRatio: 1});
|
|
2063
|
+
expect(map.getCanvas().width).toBe(512);
|
|
2064
|
+
expect(map.getCanvas().height).toBe(512);
|
|
2065
|
+
map.setPixelRatio(2);
|
|
2066
|
+
expect(map.getCanvas().width).toBe(1024);
|
|
2067
|
+
expect(map.getCanvas().height).toBe(1024);
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
test('resizes painter', () => {
|
|
2071
|
+
const container = window.document.createElement('div');
|
|
2072
|
+
Object.defineProperty(container, 'clientWidth', {value: 512});
|
|
2073
|
+
Object.defineProperty(container, 'clientHeight', {value: 512});
|
|
2074
|
+
const map = createMap({container, pixelRatio: 1});
|
|
2075
|
+
expect(map.painter.pixelRatio).toBe(1);
|
|
2076
|
+
expect(map.painter.width).toBe(512);
|
|
2077
|
+
expect(map.painter.height).toBe(512);
|
|
2078
|
+
map.setPixelRatio(2);
|
|
2079
|
+
expect(map.painter.pixelRatio).toBe(2);
|
|
2080
|
+
expect(map.painter.width).toBe(1024);
|
|
2081
|
+
expect(map.painter.height).toBe(1024);
|
|
2082
|
+
});
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
describe('getPixelRatio', () => {
|
|
2086
|
+
test('returns the pixel ratio', () => {
|
|
2087
|
+
const map = createMap({pixelRatio: 1});
|
|
2088
|
+
expect(map.getPixelRatio()).toBe(1);
|
|
2089
|
+
map.setPixelRatio(2);
|
|
2090
|
+
expect(map.getPixelRatio()).toBe(2);
|
|
2091
|
+
});
|
|
2092
|
+
});
|
|
2093
|
+
|
|
2094
|
+
test('pixel ratio defaults to devicePixelRatio', () => {
|
|
2095
|
+
const map = createMap();
|
|
2096
|
+
expect(map.getPixelRatio()).toBe(devicePixelRatio);
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
test('canvas has the expected size', () => {
|
|
2100
|
+
const container = window.document.createElement('div');
|
|
2101
|
+
Object.defineProperty(container, 'clientWidth', {value: 512});
|
|
2102
|
+
Object.defineProperty(container, 'clientHeight', {value: 512});
|
|
2103
|
+
const map = createMap({container, pixelRatio: 2});
|
|
2104
|
+
expect(map.getCanvas().width).toBe(1024);
|
|
2105
|
+
expect(map.getCanvas().height).toBe(1024);
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
test('painter has the expected size and pixel ratio', () => {
|
|
2109
|
+
const container = window.document.createElement('div');
|
|
2110
|
+
Object.defineProperty(container, 'clientWidth', {value: 512});
|
|
2111
|
+
Object.defineProperty(container, 'clientHeight', {value: 512});
|
|
2112
|
+
const map = createMap({container, pixelRatio: 2});
|
|
2113
|
+
expect(map.painter.pixelRatio).toBe(2);
|
|
2114
|
+
expect(map.painter.width).toBe(1024);
|
|
2115
|
+
expect(map.painter.height).toBe(1024);
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2051
2118
|
});
|
|
2052
2119
|
|
|
2053
2120
|
function createStyle() {
|
package/src/ui/map.ts
CHANGED
|
@@ -101,6 +101,7 @@ export type MapOptions = {
|
|
|
101
101
|
localIdeographFontFamily?: string;
|
|
102
102
|
style: StyleSpecification | string;
|
|
103
103
|
pitchWithRotate?: boolean;
|
|
104
|
+
pixelRatio?: number;
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
// See article here: https://medium.com/terria/typescript-transforming-optional-properties-to-required-properties-that-may-be-undefined-7482cb4e1585
|
|
@@ -235,6 +236,7 @@ const defaultOptions = {
|
|
|
235
236
|
* @param {number} [options.fadeDuration=300] Controls the duration of the fade-in/fade-out animation for label collisions, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading.
|
|
236
237
|
* @param {boolean} [options.crossSourceCollisions=true] If `true`, symbols from multiple sources can collide with each other during collision detection. If `false`, collision detection is run separately for the symbols in each source.
|
|
237
238
|
* @param {Object} [options.locale=null] A patch to apply to the default localization table for UI strings, e.g. control tooltips. The `locale` object maps namespaced UI string IDs to translated strings in the target language; see `src/ui/default_locale.js` for an example with all supported string IDs. The object may specify all UI strings (thereby adding support for a new translation) or only a subset of strings (thereby patching the default translation table).
|
|
239
|
+
* @param {number} [options.pixelRatio] The pixel ratio. The canvas' `width` attribute will be `container.clientWidth * pixelRatio` and its `height` attribute will be `container.clientHeight * pixelRatio`. Defaults to `devicePixelRatio` if not specified.
|
|
238
240
|
* @example
|
|
239
241
|
* var map = new maplibregl.Map({
|
|
240
242
|
* container: 'map',
|
|
@@ -298,6 +300,7 @@ class Map extends Camera {
|
|
|
298
300
|
_locale: any;
|
|
299
301
|
_removed: boolean;
|
|
300
302
|
_clickTolerance: number;
|
|
303
|
+
_pixelRatio: number;
|
|
301
304
|
|
|
302
305
|
/**
|
|
303
306
|
* The map's {@link ScrollZoomHandler}, which implements zooming in and out with a scroll wheel or trackpad.
|
|
@@ -389,6 +392,7 @@ class Map extends Camera {
|
|
|
389
392
|
this._mapId = uniqueId();
|
|
390
393
|
this._locale = extend({}, defaultLocale, options.locale);
|
|
391
394
|
this._clickTolerance = options.clickTolerance;
|
|
395
|
+
this._pixelRatio = options.pixelRatio ?? devicePixelRatio;
|
|
392
396
|
|
|
393
397
|
this._requestManager = new RequestManager(options.transformRequest);
|
|
394
398
|
|
|
@@ -584,9 +588,9 @@ class Map extends Camera {
|
|
|
584
588
|
const width = dimensions[0];
|
|
585
589
|
const height = dimensions[1];
|
|
586
590
|
|
|
587
|
-
this._resizeCanvas(width, height);
|
|
591
|
+
this._resizeCanvas(width, height, this.getPixelRatio());
|
|
588
592
|
this.transform.resize(width, height);
|
|
589
|
-
this.painter.resize(width, height);
|
|
593
|
+
this.painter.resize(width, height, this.getPixelRatio());
|
|
590
594
|
|
|
591
595
|
const fireMoving = !this._moving;
|
|
592
596
|
if (fireMoving) {
|
|
@@ -602,6 +606,29 @@ class Map extends Camera {
|
|
|
602
606
|
return this;
|
|
603
607
|
}
|
|
604
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Returns the map's pixel ratio.
|
|
611
|
+
* @returns {number} The pixel ratio.
|
|
612
|
+
*/
|
|
613
|
+
getPixelRatio() {
|
|
614
|
+
return this._pixelRatio;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Sets the map's pixel ratio. This allows to override `devicePixelRatio`.
|
|
619
|
+
* After this call, the canvas' `width` attribute will be `container.clientWidth * pixelRatio`
|
|
620
|
+
* and its height attribute will be `container.clientHeight * pixelRatio`.
|
|
621
|
+
* @param {number} pixelRatio The pixel ratio.
|
|
622
|
+
*/
|
|
623
|
+
setPixelRatio(pixelRatio: number) {
|
|
624
|
+
const [width, height] = this._containerDimensions();
|
|
625
|
+
|
|
626
|
+
this._pixelRatio = pixelRatio;
|
|
627
|
+
|
|
628
|
+
this._resizeCanvas(width, height, pixelRatio);
|
|
629
|
+
this.painter.resize(width, height, pixelRatio);
|
|
630
|
+
}
|
|
631
|
+
|
|
605
632
|
/**
|
|
606
633
|
* Returns the map's geographical bounds. When the bearing or pitch is non-zero, the visible region is not
|
|
607
634
|
* an axis-aligned rectangle, and the result is the smallest bounds that encompasses the visible region.
|
|
@@ -828,11 +855,11 @@ class Map extends Camera {
|
|
|
828
855
|
}
|
|
829
856
|
|
|
830
857
|
/**
|
|
831
|
-
* Returns a
|
|
858
|
+
* Returns a [Point](https://github.com/mapbox/point-geometry) representing pixel coordinates, relative to the map's `container`,
|
|
832
859
|
* that correspond to the specified geographical location.
|
|
833
860
|
*
|
|
834
861
|
* @param {LngLatLike} lnglat The geographical location to project.
|
|
835
|
-
* @returns {Point} The
|
|
862
|
+
* @returns {Point} The [Point](https://github.com/mapbox/point-geometry) corresponding to `lnglat`, relative to the map's `container`.
|
|
836
863
|
* @example
|
|
837
864
|
* var coordinate = [-122.420679, 37.772537];
|
|
838
865
|
* var point = map.project(coordinate);
|
|
@@ -2349,7 +2376,7 @@ class Map extends Camera {
|
|
|
2349
2376
|
this._canvas.setAttribute('role', 'region');
|
|
2350
2377
|
|
|
2351
2378
|
const dimensions = this._containerDimensions();
|
|
2352
|
-
this._resizeCanvas(dimensions[0], dimensions[1]);
|
|
2379
|
+
this._resizeCanvas(dimensions[0], dimensions[1], this.getPixelRatio());
|
|
2353
2380
|
|
|
2354
2381
|
const controlContainer = this._controlContainer = DOM.create('div', 'maplibregl-control-container mapboxgl-control-container', container);
|
|
2355
2382
|
const positions = this._controlPositions = {};
|
|
@@ -2360,9 +2387,7 @@ class Map extends Camera {
|
|
|
2360
2387
|
this._container.addEventListener('scroll', this._onMapScroll, false);
|
|
2361
2388
|
}
|
|
2362
2389
|
|
|
2363
|
-
_resizeCanvas(width: number, height: number) {
|
|
2364
|
-
const pixelRatio = devicePixelRatio || 1;
|
|
2365
|
-
|
|
2390
|
+
_resizeCanvas(width: number, height: number, pixelRatio: number) {
|
|
2366
2391
|
// Request the required canvas size taking the pixelratio into account.
|
|
2367
2392
|
this._canvas.width = pixelRatio * width;
|
|
2368
2393
|
this._canvas.height = pixelRatio * height;
|