Skip to content

Commit 0f94342

Browse files
yhy0217prushforthAliyanH
authored
Add "Zoom to here" link in popup (#831)
* create shadow for MapExtent, add zoomTo link to query / templated features, make minor fix for MapFeature * small fix * fix bug that features cannot be seen when they are added to an empty layer * add zoomTo link tests for templated features and query * Add queryableMapExtent.test.js to verify that remote queryable mapml has 'Zoom to here' link, no errors. * Image Map-extent - Custom Projection test * update custom projection tests to include map-feature along with map-extent * fix bugs: custom projection, no shadowroot attaches * Finalize custom projection test to ensure compatibility with map-extent and map-feature * re-define fallback (native) zoom and cs for templated features and query --------- Co-authored-by: Peter Rushforth <peter.rushforth@gmail.com> Co-authored-by: AliyanH <aliyanulhaq@gmail.com>
1 parent 92ccc41 commit 0f94342

22 files changed

+535
-127
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module.exports = function(grunt) {
2828
'dist/DOMTokenList.js': ['src/mapml/utils/DOMTokenList.js'],
2929
'dist/map-caption.js': ['src/map-caption.js'],
3030
'dist/map-feature.js': ['src/map-feature.js'],
31+
'dist/map-extent.js': ['src/map-extent.js'],
3132
'dist/map-area.js': ['src/map-area.js'],
3233
'dist/layer.js': ['src/layer.js'],
3334
'dist/leaflet.js': ['dist/leaflet-src.js',

src/layer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ export class MapLayer extends HTMLElement {
8787
}
8888

8989
connectedCallback() {
90-
//creates listener that waits for createmap event, this allows for delayed builds of maps
91-
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
9290
if(this.hasAttribute("data-moving")) return;
9391
this._onAdd();
9492
}
@@ -97,6 +95,8 @@ export class MapLayer extends HTMLElement {
9795
if(this.getAttribute('src') && !this.shadowRoot) {
9896
this.attachShadow({mode: 'open'});
9997
}
98+
//creates listener that waits for createmap event, this allows for delayed builds of maps
99+
//this allows a safeguard for the case where loading a custom TCRS takes longer than loading mapml-viewer.js/web-map.js
100100
this.parentNode.addEventListener('createmap', ()=>{
101101
this._ready();
102102
// if the map has been attached, set this layer up wrt Leaflet map

src/map-extent.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { MapInput } from './map-input.js';
2-
import { MapLink } from './map-link.js';
3-
41
export class MapExtent extends HTMLElement {
52
static get observedAttributes() {
63
return ['units','checked','label','opacity'];
@@ -71,10 +68,23 @@ export class MapExtent extends HTMLElement {
7168
super();
7269
}
7370
connectedCallback() {
74-
71+
if(this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot) {
72+
this.attachShadow({mode: 'open'});
73+
}
74+
let parentLayer = this.parentNode.nodeName.toUpperCase() === "LAYER-" ? this.parentNode : this.parentNode.host;
75+
if (!parentLayer._layer) {
76+
// for custom projection cases, the MapMLLayer has not yet created and binded with the layer- at this point,
77+
// because the "createMap" event of mapml-viewer has not yet been dispatched, the map has not yet been created
78+
// the event will be dispatched after defineCustomProjection > projection setter
79+
// should wait until MapMLLayer is built
80+
parentLayer.parentNode.addEventListener('createmap', (e) => {
81+
this._layer = parentLayer._layer;
82+
});
83+
} else {
84+
this._layer = parentLayer._layer;
85+
}
7586
}
7687
disconnectedCallback() {
7788

7889
}
79-
}
80-
window.customElements.define('map-extent', MapExtent);
90+
}

src/map-feature.js

Lines changed: 134 additions & 79 deletions
Large diffs are not rendered by default.

src/mapml-viewer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import DOMTokenList from './DOMTokenList.js';
44
import { MapLayer } from './layer.js';
55
import { MapCaption } from './map-caption.js';
66
import { MapFeature } from './map-feature.js';
7+
import { MapExtent } from './map-extent.js';
78

89
export class MapViewer extends HTMLElement {
910
static get observedAttributes() {
@@ -1049,3 +1050,4 @@ window.customElements.define('mapml-viewer', MapViewer);
10491050
window.customElements.define('layer-', MapLayer);
10501051
window.customElements.define('map-caption',MapCaption);
10511052
window.customElements.define('map-feature', MapFeature);
1053+
window.customElements.define('map-extent', MapExtent);

src/mapml/handlers/QueryHandler.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,18 @@ export var QueryHandler = L.Handler.extend({
8888
if(features.length) layer._mapmlFeatures = layer._mapmlFeatures.concat(features);
8989
} else {
9090
// synthesize a single feature from text or html content
91-
let geom = "<map-geometry cs='gcrs'>"+e.latlng.lng+" "+e.latlng.lat+"</map-geometry>",
91+
let geom = "<map-geometry cs='gcrs'><map-point><map-coordinates>"+e.latlng.lng+" "+e.latlng.lat+"</map-coordinates></map-point></map-geometry>",
9292
feature = parser.parseFromString("<map-feature><map-properties>"+
9393
response.text+"</map-properties>"+geom+"</map-feature>", "text/html").querySelector("map-feature");
9494
layer._mapmlFeatures.push(feature);
9595
}
96-
if(lastOne) return displayFeaturesPopup(layer._mapmlFeatures, e.latlng);
96+
if(lastOne) {
97+
// create connection between queried <map-feature> and its parent <map-extent>
98+
for (let feature of layer._mapmlFeatures) {
99+
feature._extentEl = template._extentEl;
100+
}
101+
displayFeaturesPopup(layer._mapmlFeatures, e.latlng);
102+
}
97103
}).catch((err) => {
98104
console.log('Looks like there was a problem. Status: ' + err.message);
99105
});

src/mapml/layers/FeatureLayer.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ export var FeatureLayer = L.FeatureGroup.extend({
55
* M.MapML turns any MapML feature data into a Leaflet layer. Based on L.GeoJSON.
66
*/
77
initialize: function (mapml, options) {
8-
8+
/*
9+
mapml:
10+
1. for query: an array of map-feature elements that it fetches
11+
2. for static templated feature: null
12+
3. for non-templated feature: layer- (with no src) or mapml file (with src)
13+
*/
914
L.setOptions(this, options);
1015
if(this.options.static) {
1116
this._container = L.DomUtil.create('div', 'leaflet-layer', this.options.pane);
@@ -68,15 +73,23 @@ export var FeatureLayer = L.FeatureGroup.extend({
6873
};
6974
},
7075

71-
76+
// for query
7277
showPaginationFeature: function(e){
7378
if(this.options.query && this._mapmlFeatures[e.i]){
7479
let feature = this._mapmlFeatures[e.i];
80+
// remove the prev / next one <map-feature> from shadow if there is any
81+
feature._extentEl.shadowRoot.firstChild?.remove();
7582
this.clearLayers();
76-
this.addData(feature, this.options.nativeCS, this.options.nativeZoom);
83+
feature._featureGroup = this.addData(feature, this.options.nativeCS, this.options.nativeZoom);
84+
feature._extentEl.shadowRoot.appendChild(feature);
7785
e.popup._navigationBar.querySelector("p").innerText = (e.i + 1) + "/" + this.options._leafletLayer._totalFeatureCount;
7886
e.popup._content.querySelector("iframe").setAttribute("sandbox", "allow-same-origin allow-forms");
7987
e.popup._content.querySelector("iframe").srcdoc = feature.querySelector("map-properties").innerHTML;
88+
// "zoom to here" link need to be re-set for every pagination
89+
this._map.fire("attachZoomLink", {i:e.i, currFeature: feature});
90+
this._map.once("popupclose", function (e) {
91+
this.shadowRoot.innerHTML = '';
92+
}, feature._extentEl);
8093
}
8194
},
8295

@@ -217,9 +230,10 @@ export var FeatureLayer = L.FeatureGroup.extend({
217230
feature = features[i];
218231
var geometriesExist = feature.getElementsByTagName("map-geometry").length && feature.getElementsByTagName("map-coordinates").length;
219232
if (geometriesExist) {
220-
if (mapml.nodeType === Node.DOCUMENT_NODE && feature._DOMnode) {
233+
if (mapml.nodeType === Node.DOCUMENT_NODE) {
221234
// if the <map-feature> element has migrated from mapml file,
222235
// the featureGroup object should bind with the **CLONED** map-feature element in DOM instead of the feature in mapml
236+
if (!feature._DOMnode) feature._DOMnode = feature.cloneNode(true);
223237
feature._DOMnode._featureGroup = this.addData(feature._DOMnode, nativeCS, nativeZoom);
224238
} else {
225239
feature._featureGroup = this.addData(feature, nativeCS, nativeZoom);

src/mapml/layers/MapMLLayer.js

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ export var MapMLLayer = L.Layer.extend({
108108
opacity: extentEl._templateVars.opacity,
109109
_leafletLayer: this,
110110
crs: extentEl.crs,
111-
extentZIndex: extentEl.extentZIndex
111+
extentZIndex: extentEl.extentZIndex,
112+
extentEl: extentEl._DOMnode || extentEl
112113
}).addTo(this._map);
113114
extentEl.templatedLayer.setZIndex();
114115
this._setLayerElExtent();
@@ -147,14 +148,6 @@ export var MapMLLayer = L.Layer.extend({
147148
var c = document.createElement('div');
148149
c.classList.add("mapml-popup-content");
149150
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
150-
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
151-
let zoomLink = c.querySelector('a.zoomLink');
152-
zoomLink.onclick = zoomLink.onkeydown = function (e) {
153-
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
154-
e.preventDefault();
155-
let mapmlFeature = geometry._featureEl ? geometry._featureEl : geometry._groupLayer._featureEl;
156-
mapmlFeature.zoomTo();
157-
};
158151
geometry.bindPopup(c, {autoClose: false, minWidth: 165});
159152
}
160153
}
@@ -187,14 +180,6 @@ export var MapMLLayer = L.Layer.extend({
187180
var c = document.createElement('div');
188181
c.classList.add("mapml-popup-content");
189182
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
190-
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
191-
let zoomLink = c.querySelector('a.zoomLink');
192-
zoomLink.onclick = zoomLink.onkeydown = function (e) {
193-
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
194-
e.preventDefault();
195-
let mapmlFeature = geometry._featureEl ? geometry._featureEl : geometry._groupLayer._featureEl;
196-
mapmlFeature.zoomTo();
197-
};
198183
geometry.bindPopup(c, {autoClose: false, minWidth: 165});
199184
}
200185
},
@@ -261,6 +246,9 @@ export var MapMLLayer = L.Layer.extend({
261246
_leafletLayer: this,
262247
crs: this._extent.crs,
263248
extentZIndex: this._extent._mapExtents[i].extentZIndex,
249+
// when a <map-extent> migrates from a remote mapml file and attaches to the shadow of <layer- >
250+
// this._extent._mapExtents[i] refers to the <map-extent> in remote mapml
251+
extentEl: this._extent._mapExtents[i]._DOMnode || this._extent._mapExtents[i],
264252
}).addTo(map);
265253
this._extent._mapExtents[i].templatedLayer = this._templatedLayer;
266254
if(this._templatedLayer._queries){
@@ -278,7 +266,6 @@ export var MapMLLayer = L.Layer.extend({
278266
}
279267
},
280268

281-
282269
_validProjection : function(map){
283270
let noLayer = false;
284271
if(this._extent && this._extent._mapExtents){
@@ -1324,8 +1311,12 @@ export var MapMLLayer = L.Layer.extend({
13241311
if(popup._source._eventParents){ // check if the popup is for a feature or query
13251312
layer = popup._source._eventParents[Object.keys(popup._source._eventParents)[0]]; // get first parent of feature, there should only be one
13261313
group = popup._source.group;
1314+
// if the popup is for a static / templated feature, the "zoom to here" link can be attached once the popup opens
1315+
attachZoomLink.call(popup);
13271316
} else {
13281317
layer = popup._source._templatedLayer;
1318+
// if the popup is for a query, the "zoom to here" link should be re-attached every time new pagination features are displayed
1319+
map.on("attachZoomLink", attachZoomLink, popup);
13291320
}
13301321

13311322
if(popup._container.querySelector('nav[class="mapml-focus-buttons"]')){
@@ -1381,7 +1372,7 @@ export var MapMLLayer = L.Layer.extend({
13811372
map._controlContainer.querySelector("A:not([hidden])").focus();
13821373
}, popup);
13831374

1384-
let divider = L.DomUtil.create("hr");
1375+
let divider = L.DomUtil.create("hr", "mapml-popup-divider");
13851376

13861377
popup._navigationBar = div;
13871378
popup._content.appendChild(divider);
@@ -1452,12 +1443,36 @@ export var MapMLLayer = L.Layer.extend({
14521443
}
14531444
}
14541445

1446+
function attachZoomLink (e) {
1447+
// this === popup
1448+
let content = this._content,
1449+
featureEl = e ? e.currFeature : this._source._groupLayer._featureEl;
1450+
if (content.querySelector('a.mapml-zoom-link')) {
1451+
content.querySelector('a.mapml-zoom-link').remove();
1452+
}
1453+
if (!featureEl.querySelector('map-geometry')) return;
1454+
let tL = featureEl.extent.topLeft.gcrs,
1455+
bR = featureEl.extent.bottomRight.gcrs,
1456+
center = L.latLngBounds(L.latLng(tL.horizontal, tL.vertical), L.latLng(bR.horizontal, bR.vertical)).getCenter(true);
1457+
let zoomLink = document.createElement('a');
1458+
zoomLink.href = `#${featureEl.getMaxZoom()},${center.lng},${center.lat}`;
1459+
zoomLink.innerHTML = `${M.options.locale.popupZoom}`;
1460+
zoomLink.className = "mapml-zoom-link";
1461+
zoomLink.onclick = zoomLink.onkeydown = function (e) {
1462+
if (!(e instanceof MouseEvent) && e.keyCode !== 13) return;
1463+
e.preventDefault();
1464+
featureEl.zoomTo();
1465+
};
1466+
content.insertBefore(zoomLink, content.querySelector('hr.mapml-popup-divider'));
1467+
}
1468+
14551469
// if popup closes then the focusFeature handler can be removed
14561470
map.on("popupclose", removeHandlers);
14571471
function removeHandlers(removeEvent){
14581472
if (removeEvent.popup === popup){
14591473
map.off("keydown", focusFeature);
14601474
map.off("keydown", focusMap);
1475+
map.off("popupopen", attachZoomLink);
14611476
map.off('popupclose', removeHandlers);
14621477
if(group) group.setAttribute("aria-expanded", "false");
14631478
}

src/mapml/layers/TemplatedFeaturesLayer.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
66
this.extentBounds=inputData.bounds;
77
this.isVisible = true;
88
this._template = template;
9+
this._extentEl = options.extentEl;
910
this._container = L.DomUtil.create('div', 'leaflet-layer', options.pane);
1011
L.extend(options, this.zoomBounds);
1112
L.DomUtil.addClass(this._container, 'mapml-features-container');
@@ -39,7 +40,6 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
3940
var c = document.createElement('div');
4041
c.classList.add("mapml-popup-content");
4142
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
42-
c.insertAdjacentHTML('beforeend', `<a href="" class="zoomLink">${M.options.locale.popupZoom}</a>`);
4343
geometry.bindPopup(c, {autoClose: false, minWidth: 108});
4444
}
4545
});
@@ -85,6 +85,10 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
8585
this.extentBounds.overlaps(mapBounds);
8686

8787
this._features.clearLayers();
88+
// shadow may has not yet attached to <map-extent> for the first-time rendering
89+
if (this._extentEl.shadowRoot) {
90+
this._extentEl.shadowRoot.innerHTML = "";
91+
}
8892
this._removeCSS();
8993
//Leave the layers cleared if the layer is not visible
9094
if(!(this.isVisible) && steppedZoom === mapZoom){
@@ -96,6 +100,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
96100
var mapml, headers = new Headers({'Accept': 'text/mapml;q=0.9,application/geo+json;q=0.8'}),
97101
parser = new DOMParser(),
98102
features = this._features,
103+
extentEl = this._extentEl,
99104
map = this._map,
100105
context = this,
101106
MAX_PAGES = 10,
@@ -109,17 +114,23 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
109114
url = mapml.querySelector('map-link[rel=next]')? mapml.querySelector('map-link[rel=next]').getAttribute('href') : null;
110115
url = url ? (new URL(url, base)).href: null;
111116
// TODO if the xml parser barfed but the response is application/geo+json, use the parent addData method
112-
let nativeZoom = mapml.querySelector("map-meta[name=zoom]") &&
117+
let nativeZoom = extentEl._nativeZoom = mapml.querySelector("map-meta[name=zoom]") &&
113118
+M._metaContentToObject(mapml.querySelector("map-meta[name=zoom]").getAttribute("content")).value || 0;
114-
let nativeCS = mapml.querySelector("map-meta[name=cs]") &&
115-
M._metaContentToObject(mapml.querySelector("map-meta[name=cs]").getAttribute("content")).content || "GCRS";
119+
let nativeCS = extentEl._nativeCS = mapml.querySelector("map-meta[name=cs]") &&
120+
M._metaContentToObject(mapml.querySelector("map-meta[name=cs]").getAttribute("content")).content || "PCRS";
116121
features.addData(mapml, nativeCS, nativeZoom);
122+
// "migrate" to extent's shadow
123+
// make a clone, prevent the elements from being removed from mapml file
124+
// same as _attachToLayer() in MapMLLayer.js
125+
for (let el of mapml.querySelector('map-body').children) {
126+
extentEl.shadowRoot.append(el._DOMnode);
127+
el._DOMnode._extentEl = extentEl;
128+
}
117129
if (url && --limit) {
118130
return _pullFeatureFeed(url, limit);
119131
}
120132
}));
121133
};
122-
123134
this._url = url;
124135
_pullFeatureFeed(url, MAX_PAGES)
125136
.then(function() {

src/mapml/layers/TemplatedLayer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export var TemplatedLayer = L.Layer.extend({
2727
let inputData = M._extractInputBounds(templates[i]);
2828
templates[i].extentBounds = inputData.bounds;
2929
templates[i].zoomBounds = inputData.zoomBounds;
30+
templates[i]._extentEl = this.options.extentEl;
3031
this._queries.push(L.extend(templates[i], this._setupQueryVars(templates[i])));
3132
}
3233
}

0 commit comments

Comments
 (0)