Skip to content

Commit f2d95d0

Browse files
authored
Implement the step attribute (#662)
* Redefine _clampZoom * Add tests * Split up tests for clarity * Fix failing test * Handle different step value cases * Scale templated image layers * Scale templated feature layers * Prevent requesting onAdd when expecting a scaled feature layer * Add request tests for maps with input step attribute * Fix shift zooming issue * Fix shift pan issue * Add tests for scaling templated image layers * Shift zoom for templated features * Run npm install --legacy-peer-deps * Fix previous.zoom is undefined issue when clicking reload button * Comment TemplatedFeaturesLayer.js changes * Add feedback
1 parent 28c8d0f commit f2d95d0

14 files changed

+449
-42
lines changed

.github/workflows/ci-testing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
with:
1515
node-version: '16.x'
1616
- run: sudo apt-get install xvfb
17-
- run: npm install
17+
- run: npm install --legacy-peer-deps
1818
- run: npm install -g grunt-cli
1919
- run: grunt default
2020
- run: xvfb-run --auto-servernum -- npm test

src/mapml/layers/FeatureLayer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export var MapMLFeatures = L.FeatureGroup.extend({
6666
};
6767
}
6868
return {
69-
'moveend':this._removeCSS
7069
};
7170
},
7271

src/mapml/layers/ImageLayer.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export var ImageOverlay = L.ImageOverlay.extend({
1818
viewreset: this._reset
1919
};
2020

21-
if (this._zoomAnimated) {
21+
if (this._zoomAnimated && this._step <= 1) {
2222
events.zoomanim = this._animateZoom;
2323
}
2424

@@ -63,19 +63,22 @@ export var ImageOverlay = L.ImageOverlay.extend({
6363
L.DomUtil.setPosition(this._image, translate);
6464
}
6565
},
66-
_reset: function () {
66+
_reset: function (e) {
6767
var image = this._image,
6868
location = this._location,
6969
size = this._size,
7070
angle = 0.0;
71+
// TBD use the angle to establish the image rotation in CSS
7172

72-
// TBD use the angle to establish the image rotation in CSS
73-
73+
if(e && this._step > 1 &&
74+
(this._overlayToRemove === undefined || this._url === this._overlayToRemove)) {
75+
return;
76+
}
7477
L.DomUtil.setPosition(image, location);
7578

7679
image.style.width = size.x + 'px';
7780
image.style.height = size.y + 'px';
78-
},
81+
},
7982
_updateOpacity: function () {
8083
if (!this._map) { return; }
8184

src/mapml/layers/MapLayer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,8 @@ export var MapMLLayer = L.Layer.extend({
969969
if(!includesZoom && zoomInput) {
970970
inputs.push(zoomInput);
971971
}
972+
let step = zoomInput ? zoomInput.getAttribute("step") : 1;
973+
if(!step || step === "0" || isNaN(step)) step = 1;
972974
// template has a matching input for every variable reference {varref}
973975
templateVars.push({
974976
template:decodeURI(new URL(template, base)),
@@ -982,6 +984,7 @@ export var MapMLLayer = L.Layer.extend({
982984
projectionMatch: projectionMatch,
983985
projection:serverExtent.getAttribute("units") || FALLBACK_PROJECTION,
984986
tms:tms,
987+
step:step,
985988
});
986989
}
987990
}

src/mapml/layers/TemplatedFeaturesLayer.js

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
6565
}
6666
}));
6767
};
68+
if(map.getZoom() % this._template.step !== 0) {
69+
this._onMoveEnd();
70+
return;
71+
}
6872
_pullFeatureFeed(this._getfeaturesUrl(), 10)
6973
.then(function() {
7074
map.addLayer(features);
@@ -76,14 +80,44 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
7680
this._onMoveEnd();
7781
},
7882

83+
_removeCSS: function () {
84+
let toDelete = this._container.querySelectorAll("link[rel=stylesheet],style");
85+
for(let i = 0; i < toDelete.length;i++){
86+
let parent = toDelete[i].parentNode;
87+
parent.removeChild(toDelete[i]);
88+
}
89+
},
90+
7991
_onMoveEnd: function() {
92+
let history = this._map.options.mapEl._history;
93+
let current = history[history.length - 1];
94+
let previous = history[history.length - 2] ?? current;
95+
let step = this._template.step;
8096
let mapZoom = this._map.getZoom();
97+
let steppedZoom = mapZoom;
98+
//If zooming out from one step interval into a lower one or panning, set the stepped zoom
99+
if (((step !== "1") && ((mapZoom + 1) % step === 0) && current.zoom === previous.zoom - 1) ||
100+
(current.zoom === previous.zoom) ||
101+
(Math.floor(mapZoom / step) * step !== Math.floor(previous.zoom / step) * step)) {
102+
steppedZoom = Math.floor(mapZoom / step) * step;
103+
}
104+
//No request needed if in a step interval (unless panning)
105+
else if(mapZoom % this._template.step !== 0) return;
106+
107+
let scaleBounds = this._map.getPixelBounds(this._map.getCenter(), steppedZoom);
108+
let url = this._getfeaturesUrl(steppedZoom, scaleBounds);
109+
//No request needed if the current template url is the same as the url to request
110+
if(url === this._url) return;
111+
81112
let mapBounds = M.pixelToPCRSBounds(this._map.getPixelBounds(),mapZoom,this._map.options.projection);
82113
this.isVisible = mapZoom <= this.zoomBounds.maxZoom && mapZoom >= this.zoomBounds.minZoom &&
83114
this.extentBounds.overlaps(mapBounds);
84115

85116
this._features.clearLayers();
86-
if(!(this.isVisible)){
117+
this._removeCSS();
118+
//Leave the layers cleared if the layer is not visible
119+
if(!(this.isVisible) && steppedZoom === mapZoom){
120+
this._url = "";
87121
return;
88122
}
89123

@@ -114,9 +148,12 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
114148
}
115149
}));
116150
};
117-
_pullFeatureFeed(this._getfeaturesUrl(), MAX_PAGES)
151+
152+
this._url = url;
153+
_pullFeatureFeed(url, MAX_PAGES)
118154
.then(function() {
119155
map.addLayer(features);
156+
//Fires event for feature index overlay
120157
map.fire("templatedfeatureslayeradd");
121158
M.TemplatedFeaturesLayer.prototype._updateTabIndex(context);
122159
})
@@ -153,10 +190,12 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
153190
onRemove: function () {
154191
this._map.removeLayer(this._features);
155192
},
156-
_getfeaturesUrl: function() {
193+
_getfeaturesUrl: function(zoom, bounds) {
194+
if(zoom === undefined) zoom = this._map.getZoom();
195+
if(bounds === undefined) bounds = this._map.getPixelBounds();
157196
var obj = {};
158197
if (this.options.feature.zoom) {
159-
obj[this.options.feature.zoom] = this._map.getZoom();
198+
obj[this.options.feature.zoom] = zoom;
160199
}
161200
if (this.options.feature.width) {
162201
obj[this.options.feature.width] = this._map.getSize().x;
@@ -165,16 +204,16 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
165204
obj[this.options.feature.height] = this._map.getSize().y;
166205
}
167206
if (this.options.feature.bottom) {
168-
obj[this.options.feature.bottom] = this._TCRSToPCRS(this._map.getPixelBounds().max,this._map.getZoom()).y;
207+
obj[this.options.feature.bottom] = this._TCRSToPCRS(bounds.max, zoom).y;
169208
}
170209
if (this.options.feature.left) {
171-
obj[this.options.feature.left] = this._TCRSToPCRS(this._map.getPixelBounds().min, this._map.getZoom()).x;
210+
obj[this.options.feature.left] = this._TCRSToPCRS(bounds.min, zoom).x;
172211
}
173212
if (this.options.feature.top) {
174-
obj[this.options.feature.top] = this._TCRSToPCRS(this._map.getPixelBounds().min, this._map.getZoom()).y;
213+
obj[this.options.feature.top] = this._TCRSToPCRS(bounds.min, zoom).y;
175214
}
176215
if (this.options.feature.right) {
177-
obj[this.options.feature.right] = this._TCRSToPCRS(this._map.getPixelBounds().max,this._map.getZoom()).x;
216+
obj[this.options.feature.right] = this._TCRSToPCRS(bounds.max, zoom).x;
178217
}
179218
// hidden and other variables that may be associated
180219
for (var v in this.options.feature) {

src/mapml/layers/TemplatedImageLayer.js

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export var TemplatedImageLayer = L.Layer.extend({
2020
onAdd: function () {
2121
this._map._addZoomLimit(this); //used to set the zoom limit of the map
2222
this.setZIndex(this.options.zIndex);
23-
this._onMoveEnd();
23+
this._onAdd();
2424
},
2525
redraw: function() {
2626
this._onMoveEnd();
@@ -33,26 +33,90 @@ export var TemplatedImageLayer = L.Layer.extend({
3333
}
3434
},
3535

36-
_onMoveEnd: function() {
37-
let mapZoom = this._map.getZoom();
38-
let mapBounds = M.pixelToPCRSBounds(this._map.getPixelBounds(),mapZoom,this._map.options.projection);
39-
this.isVisible = mapZoom <= this.zoomBounds.maxZoom && mapZoom >= this.zoomBounds.minZoom &&
40-
this.extentBounds.overlaps(mapBounds);
41-
if(!(this.isVisible)){
42-
this._clearLayer();
43-
return;
44-
}
45-
var map = this._map,
46-
loc = map.getPixelBounds().min.subtract(map.getPixelOrigin()),
47-
size = map.getSize(),
48-
src = this.getImageUrl(),
49-
overlayToRemove = this._imageOverlay;
50-
this._imageOverlay = M.imageOverlay(src,loc,size,0,this._container);
51-
52-
this._imageOverlay.addTo(map);
53-
if (overlayToRemove) {
54-
this._imageOverlay.on('load error', function () {map.removeLayer(overlayToRemove);});
55-
}
36+
_addImage: function (bounds, zoom, loc) {
37+
let map = this._map;
38+
let overlayToRemove = this._imageOverlay;
39+
let src = this.getImageUrl(bounds, zoom);
40+
let size = map.getSize();
41+
this._imageOverlay = M.imageOverlay(src, loc, size, 0, this._container);
42+
this._imageOverlay._step = this._template.step;
43+
this._imageOverlay.addTo(map);
44+
if (overlayToRemove) {
45+
this._imageOverlay._overlayToRemove = overlayToRemove._url;
46+
this._imageOverlay.on('load error', function () {map.removeLayer(overlayToRemove);});
47+
}
48+
},
49+
50+
_scaleImage: function (bounds, zoom) {
51+
let obj = this;
52+
setTimeout(function () {
53+
let step = obj._template.step;
54+
let steppedZoom = Math.floor(zoom / step) * step;
55+
let scale = obj._map.getZoomScale(zoom, steppedZoom);
56+
let translate = bounds.min.multiplyBy(scale)
57+
.subtract(obj._map._getNewPixelOrigin(obj._map.getCenter(), zoom)).round();
58+
L.DomUtil.setTransform(obj._imageOverlay._image, translate, scale);
59+
});
60+
},
61+
62+
_onAdd: function () {
63+
let zoom = this._map.getZoom();
64+
let steppedZoom = zoom;
65+
let step = this._template.step;
66+
67+
if (zoom % step !== 0) steppedZoom = Math.floor(zoom / step) * step;
68+
let bounds = this._map.getPixelBounds(this._map.getCenter(), steppedZoom);
69+
this._addImage(bounds, steppedZoom, L.point(0,0));
70+
this._pixelOrigins = {};
71+
this._pixelOrigins[steppedZoom] = bounds.min;
72+
if(zoom !== steppedZoom) {
73+
this._scaleImage(bounds, zoom);
74+
}
75+
},
76+
77+
_onMoveEnd: function(e) {
78+
let mapZoom = this._map.getZoom();
79+
let history = this._map.options.mapEl._history;
80+
let current = history[history.length - 1];
81+
let previous = history[history.length - 2];
82+
if(!previous) previous = current;
83+
let step = this._template.step;
84+
let steppedZoom = Math.floor(mapZoom / step) * step;
85+
let bounds = this._map.getPixelBounds(this._map.getCenter(), steppedZoom);
86+
//Zooming from one step increment into a lower one
87+
if((step !== "1") && ((mapZoom + 1) % step === 0) &&
88+
current.zoom === previous.zoom - 1){
89+
this._addImage(bounds, steppedZoom, L.point(0,0));
90+
this._scaleImage(bounds, mapZoom);
91+
//Zooming or panning within a step increment
92+
} else if (e && mapZoom % step !== 0) {
93+
this._imageOverlay._overlayToRemove = this._imageOverlay._url;
94+
if (current.zoom !== previous.zoom) {
95+
//Zoomed from within one step increment into another
96+
if(steppedZoom !== Math.floor(previous.zoom / step) * step){
97+
this._addImage(bounds, steppedZoom, L.point(0,0));
98+
this._pixelOrigins[steppedZoom] = bounds.min;
99+
}
100+
this._scaleImage(bounds, mapZoom);
101+
} else {
102+
let pixelOrigin = this._pixelOrigins[steppedZoom];
103+
let loc = bounds.min.subtract(pixelOrigin);
104+
if(this.getImageUrl(bounds, steppedZoom) === this._imageOverlay._url) return;
105+
this._addImage(bounds, steppedZoom, loc);
106+
this._scaleImage(bounds, mapZoom);
107+
}
108+
} else {
109+
let mapBounds = M.pixelToPCRSBounds(this._map.getPixelBounds(),mapZoom,this._map.options.projection);
110+
this.isVisible = mapZoom <= this.zoomBounds.maxZoom && mapZoom >= this.zoomBounds.minZoom &&
111+
this.extentBounds.overlaps(mapBounds);
112+
if(!(this.isVisible)){
113+
this._clearLayer();
114+
return;
115+
}
116+
var map = this._map, loc = map.getPixelBounds().min.subtract(map.getPixelOrigin());
117+
this._addImage(map.getPixelBounds(), mapZoom, loc);
118+
this._pixelOrigins[mapZoom] = map.getPixelOrigin();
119+
}
56120
},
57121
setZIndex: function (zIndex) {
58122
this.options.zIndex = zIndex;
@@ -70,14 +134,14 @@ export var TemplatedImageLayer = L.Layer.extend({
70134
map._removeZoomLimit(this);
71135
this._container = null;
72136
},
73-
getImageUrl: function() {
137+
getImageUrl: function(pixelBounds, zoom) {
74138
var obj = {};
75139
obj[this.options.extent.width] = this._map.getSize().x;
76140
obj[this.options.extent.height] = this._map.getSize().y;
77-
obj[this.options.extent.bottom] = this._TCRSToPCRS(this._map.getPixelBounds().max,this._map.getZoom()).y;
78-
obj[this.options.extent.left] = this._TCRSToPCRS(this._map.getPixelBounds().min, this._map.getZoom()).x;
79-
obj[this.options.extent.top] = this._TCRSToPCRS(this._map.getPixelBounds().min, this._map.getZoom()).y;
80-
obj[this.options.extent.right] = this._TCRSToPCRS(this._map.getPixelBounds().max,this._map.getZoom()).x;
141+
obj[this.options.extent.bottom] = this._TCRSToPCRS(pixelBounds.max, zoom).y;
142+
obj[this.options.extent.left] = this._TCRSToPCRS(pixelBounds.min, zoom).x;
143+
obj[this.options.extent.top] = this._TCRSToPCRS(pixelBounds.min, zoom).y;
144+
obj[this.options.extent.right] = this._TCRSToPCRS(pixelBounds.max, zoom).x;
81145
// hidden and other variables that may be associated
82146
for (var v in this.options.extent) {
83147
if (["width","height","left","right","top","bottom"].indexOf(v) < 0) {

src/mapml/layers/TemplatedTileLayer.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,20 @@ export var TemplatedTileLayer = L.TileLayer.extend({
447447
pcrs2tilematrix(pcrsBounds.max,z)) :
448448
L.bounds(L.point([-1,-1]),L.point([-1,-1])));
449449
}
450-
}
450+
},
451+
_clampZoom: function (zoom) {
452+
let clamp = L.GridLayer.prototype._clampZoom.call(this, zoom);
453+
if(this._template.step > this.zoomBounds.maxNativeZoom) this._template.step = this.zoomBounds.maxNativeZoom;
454+
455+
if(zoom !== clamp){
456+
zoom = clamp;
457+
} else {
458+
if(zoom % this._template.step !== 0){
459+
zoom = Math.floor(zoom / this._template.step) * this._template.step;
460+
}
461+
}
462+
return zoom;
463+
},
451464
});
452465
export var templatedTileLayer = function(template, options) {
453466
return new TemplatedTileLayer(template, options);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const request = require('./request');
2+
describe("Templated features layer with step", () => {
3+
beforeAll(async () => {
4+
await page.goto(PATH + 'step/templatedFeaturesLayer.html');
5+
});
6+
7+
afterAll(async function () {
8+
await context.close();
9+
});
10+
11+
request.test(0, 1, 0, 0,
12+
"http://localhost:30001/data/alabama_feature.mapml?",
13+
"-8030725.916518498-9758400.22013378111151604.1148082329423929.8111929520",
14+
"",
15+
"-437169.06273812056-2131770.3835407723531588.8747777491836987.55397510533",
16+
"",
17+
"",
18+
1, "-437169.06273812056-1766644.65328931063531588.8747777492202113.28422656663"
19+
);
20+
});

0 commit comments

Comments
 (0)