Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Search Control #808

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@
<mapml-viewer projection="CBMTILE" zoom="2" lat="45" lon="-90" controls>
<map-caption>A pleasing map of Canada</map-caption>
<layer- label="CBMT" src="https://geogratis.gc.ca/mapml/en/cbmtile/cbmt/" checked></layer->
<!--Removing this layer should hide the search control-->
<layer- label="Search Layer" checked>
<map-extent units="CBMTILE">
<map-input name="s" type="search"></map-input>
<map-link rel="search" tref="https://geogratis.gc.ca/mapml/en/cbmtile/{s}/"></map-link><!--https://geogratis.gc.ca/mapml/en/cbmtile/{s}/-->
<map-link rel="searchSuggestion" query="place_id" desc="display_name" tref="https://nominatim.openstreetmap.org/search.php?q={s}&format=jsonv2"></map-link>
</map-extent>
</layer->

<!--https://nominatim.openstreetmap.org/search.php?q=waterloo&polygon_geojson=1&format=jsonv2
https://nominatim.openstreetmap.org/search.php?q=waterloo&format=jsonv2
https://nominatim.openstreetmap.org/ui/search.html?q=waterloo
-->
</mapml-viewer>
</body>
</html>
5 changes: 4 additions & 1 deletion src/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export class MapLayer extends HTMLElement {
if (this._layerControl && !this.hidden) {
this._layerControl.addOrUpdateOverlay(this._layer, this.label);
}
// Update search control to update searchable layers
this._layer._map.options.mapEl._searchBar._updateSearchableLayers();
}, {once:true}); //listener stops listening after event occurs once
//if map is already created then dispatch createmap event, allowing layer to be built
if(this.parentNode._map)this.parentNode.dispatchEvent(new CustomEvent('createmap'));
Expand Down Expand Up @@ -204,7 +206,8 @@ export class MapLayer extends HTMLElement {
total++;
layer._extent._mapExtents[i].removeAttribute("disabled");
layer._extent._mapExtents[i].disabled = false;
if(!(layer._extent._mapExtents[i].templatedLayer._templates[j].layer.isVisible)){
if(!(layer._extent._mapExtents[i].templatedLayer._templates[j].layer &&
layer._extent._mapExtents[i].templatedLayer._templates[j].layer.isVisible)){
count++;
layer._extent._mapExtents[i].setAttribute("disabled", "");
layer._extent._mapExtents[i].disabled = true;
Expand Down
23 changes: 21 additions & 2 deletions src/mapml-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class MapViewer extends HTMLElement {
this._controlsList = new DOMTokenList(
this.getAttribute("controlslist"),
this, "controlslist",
["noreload","nofullscreen","nozoom","nolayer"]
["noreload","nofullscreen","nozoom","nolayer","nosearch"]
);

// the dimension attributes win, if they're there. A map does not
Expand Down Expand Up @@ -354,6 +354,9 @@ export class MapViewer extends HTMLElement {
totalSize += 49;
this._fullScreenControl = M.fullscreenButton().addTo(this._map);
}
if (!this._searchBar) {
this._searchBar = M.searchBar().addTo(this._map);
}
}

// Sets controls by hiding/unhiding them based on the map attribute
Expand All @@ -372,12 +375,17 @@ export class MapViewer extends HTMLElement {
this._setControlsVisibility("layercontrol",true);
this._setControlsVisibility("reload",true);
this._setControlsVisibility("zoom",true);
this._setControlsVisibility("search",true);
}
_showControls() {
this._setControlsVisibility("fullscreen",false);
this._setControlsVisibility("layercontrol",false);
this._setControlsVisibility("reload",false);
this._setControlsVisibility("zoom",false);
// show search control, if any layer is searchable
if (this._searchBar?.searchableLayers.length > 0) {
this._setControlsVisibility("search",false);
}

// prune the controls shown if necessary
// this logic could be embedded in _showControls
Expand All @@ -398,11 +406,17 @@ export class MapViewer extends HTMLElement {
case 'nozoom':
this._setControlsVisibility("zoom",true);
break;
case 'nosearch':
this._setControlsVisibility("search",true);
break;
}
});
}
if (this._layerControl && this._layerControl._layers.length === 0) {
if (this._layerControl?._layers.length === 0) {
this._layerControl._container.setAttribute("hidden","");
if (this._searchBar) {
this._setControlsVisibility("search",true);
}
}
}

Expand Down Expand Up @@ -431,6 +445,11 @@ export class MapViewer extends HTMLElement {
container = this._layerControl._container;
}
break;
case "search":
if (this._searchBar) {
container = this._searchBar._container;
}
break;
}
if (container) {
if (hide) {
Expand Down
35 changes: 35 additions & 0 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,41 @@
background-size: 34px;
}

.mapml-search-control {
position: absolute;
margin-left: 55px !important;
width: 201px;
}
.mapml-search-control-section {
width: 180px;
display: grid;
grid-template-columns: 83% 1fr;
}
.mapml-search-control-section input[type=search] {
padding: 5px;
font-size: 16px;
border: 1px solid grey;
width: 100%;
border-radius: 5px;
grid-column: 1;
overflow: hidden;
}
.mapml-search-control-section input[type=button] {
width: 100%;
height: 30px;
border-radius: 5px;
border: 1px solid black;
grid-column: 2;
overflow: hidden;
background-image: url("data:image/svg+xml,%3Csvg xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns%23' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns%23' xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' width='40' height='40' viewBox='0 0 500.00001 500.00001' id='svg4162' version='1.1' inkscape:version='0.92.3 (2405546, 2018-03-11)' sodipodi:docname='Search_Icon.svg'%3E%3Cdefs id='defs4164'/%3E%3Csodipodi:namedview id='base' pagecolor='%23ffffff' bordercolor='%23666666' borderopacity='1.0' inkscape:pageopacity='0.0' inkscape:pageshadow='2' inkscape:zoom='0.954' inkscape:cx='250' inkscape:cy='250' inkscape:document-units='px' inkscape:current-layer='layer1' showgrid='false' units='px' inkscape:window-width='1366' inkscape:window-height='706' inkscape:window-x='-8' inkscape:window-y='-8' inkscape:window-maximized='1'/%3E%3Cmetadata id='metadata4167'%3E%3Crdf:RDF%3E%3Ccc:Work rdf:about=''%3E%3Cdc:format%3Eimage/svg+xml%3C/dc:format%3E%3Cdc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/%3E%3Cdc:title/%3E%3C/cc:Work%3E%3C/rdf:RDF%3E%3C/metadata%3E%3Cg inkscape:label='Layer 1' inkscape:groupmode='layer' id='layer1' transform='translate(0,-552.36216)'%3E%3Cg id='g1400' transform='translate(-4.3609793,-7.6704785)'%3E%3Cpath inkscape:connector-curvature='0' id='path4714' d='M 232.83952,614.96702 A 154.04816,154.04794 0 0 0 78.79153,769.01382 154.04816,154.04794 0 0 0 232.83952,923.06184 154.04816,154.04794 0 0 0 386.88751,769.01382 154.04816,154.04794 0 0 0 232.83952,614.96702 Z m 0,26.77613 A 129.95832,127.2707 0 0 1 362.79832,769.01382 129.95832,127.2707 0 0 1 232.83952,896.28449 129.95832,127.2707 0 0 1 102.88194,769.01382 129.95832,127.2707 0 0 1 232.83952,641.74315 Z' style='opacity:1;fill:%232b0000;fill-opacity:1;stroke:none;stroke-opacity:1'/%3E%3Crect ry='18.08342' rx='33.249443' transform='matrix(0.65316768,0.7572133,-0.60689051,0.79478545,0,0)' y='319.55432' x='794.8775' height='36.16684' width='173.02675' id='rect4721' style='opacity:1;fill:%232b0000;fill-opacity:1;stroke:none;stroke-opacity:1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
background-size: 27px;
background-repeat: no-repeat;
background-position: center;
}
.mapml-search-control input[type=button]:hover {
background-color: #fafafa;
}

/* Revert Leaflet styles that are causing misalignment. */
.leaflet-control-layers-selector {
margin-top: revert;
Expand Down
168 changes: 168 additions & 0 deletions src/mapml/control/SearchControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
export var SearchBar = L.Control.extend({

options: {
position: 'topleft'
},

onAdd: function(map) {
this._initLayout();
this._map = map;

L.DomEvent.on(this._button, 'click', this.search, this);

// get a list of layers that are searchable
this.searchableLayers = [];

return this._container;
},

onRemove: function(map) {
L.DomEvent.off(this._button);
},

_initLayout() {
const className = 'mapml-search-control',
container = this._container = L.DomUtil.create('div', className),
section = this._section = L.DomUtil.create('div', `${className}-section`);

// TODO - Create a label for the search input
this._input = L.DomUtil.create('input', `${className}-input`, section);
this._input.setAttribute("list", "suggestions"); // connect to datalist
this._input.type = 'search';
this._input.size = '15';
this._input.onkeyup = (e)=> {
if (e.code === 'Enter') {
this.search();
} else {
this.suggest();
}
};

this._createSuggestions();

this._button = L.DomUtil.create('input', `${className}-button`, section);
this._button.type = 'button';
this._button.title = 'Search';

container.appendChild(section);
},

search() {
let input = this._input;
this._updateSearchableLayers(input.value);

// TODO - search through all layers when multiple searchable layers present,
// currently hardcoded to only search the first searchable layer
fetch(this.searchableLayers[0], {
"headers": {
"accept": "text/mapml",
},
"method": "GET",
"mode": "cors",
})
.then((response) => {
if (response.ok) {
return response.text();
}
throw new Error('Invalid Search Response');
})
.then((data)=> {
// TODO - work with data variable - mapml response or geojson
let l = document.createElement("layer-");
l.src = this.searchableLayers[0];
l.checked = true;

this._map.options.mapEl.appendChild(l);
})
.catch((error) => {
console.error("Error:", error);
});
},

// suggest values when user is typing in the search bar
suggest() {
let val = this._input.value;
if (val.length >= 3) {
for (let layer of [... this._map.options.mapEl.layers]) {
if (layer._layer && layer._layer._templatedLayer.search) {
//layer.search = layer.search.replace('QUERY', input.value);
//let link = this.parseLink(layer._layer._templatedLayer._templates[0], val);
//this.searchableLayers.push(link);
let suggestionLink = layer._layer._templatedLayer._templates[0].linkEl.parentElement.querySelector("map-link[rel=searchSuggestion]");

if (suggestionLink) {
const query = suggestionLink.getAttribute("query");
const desc = suggestionLink.getAttribute("desc");
let tref = suggestionLink.getAttribute("tref");

// TODO - currently hardcoded to get first map-input, need to loop through all
let inpName = layer._layer._templatedLayer._templates[0].values[0].getAttribute("name");

tref = tref.replace('{' + inpName + '}', val);
fetch(tref)
.then((response) => response.json())
.then((data)=> {
this.clearItems();
for (const obj in data) {
this.addItem(data[obj][query], data[obj][desc]);
}
});
}
}
}
} else {
this.clearItems();
}
},

_updateSearchableLayers(val){
this.searchableLayers = [];
for (let layer of [... this._map.options.mapEl.layers]) {
if (layer._layer && layer._layer._templatedLayer?.search) {
//layer.search = layer.search.replace('QUERY', input.value);
let link = this.parseLink(layer._layer._templatedLayer._templates[0], val);
this.searchableLayers.push(link);
}
}
if (this.searchableLayers.length === 0) {
this._map.options.mapEl._setControlsVisibility("search",true);
} else {
this._map.options.mapEl._setControlsVisibility("search",false);
}
},

parseLink(template, val) {
let link = template.template;
let inpName = template.values[0].getAttribute("name");
let inpType = template.values[0].getAttribute("type");
//let inpVal = template.values[0].innerHTML;
link = link.replace('{' + inpName + '}',val);
return link;
},

_createSuggestions() {
this._suggestion = L.DomUtil.create(
'datalist',
'leaflet-searchbox-autocomplete',
this._container);
this._suggestion.id = "suggestions";
this._items = [];
},

addItem(value, text) {
var listItem = L.DomUtil.create('option', 'leaflet-searchbox-autocomplete-item', this._suggestion);
listItem.innerHTML = text;
listItem.value = value;
this._items.push(listItem);
},

clearItems() {
this._suggestion.innerHTML = '';
this._items = [];
}
});


export var searchBar = function (options) {
return new SearchBar(options);
};
4 changes: 4 additions & 0 deletions src/mapml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { Util } from './utils/Util';
import { ReloadButton, reloadButton } from './control/ReloadButton';
import { FullscreenButton, fullscreenButton } from './control/FullscreenButton';
import {attributionControl} from "./control/AttributionControl";
import { SearchBar, searchBar } from "./control/SearchControl";
import { Crosshair, crosshair } from "./layers/Crosshair";
import { Feature, feature } from "./features/feature";
import { FeatureRenderer, featureRenderer } from './features/featureRenderer';
Expand Down Expand Up @@ -659,6 +660,9 @@ M.fullscreenButton = fullscreenButton;

M.attributionControl = attributionControl;

M.SearchBar = SearchBar;
M.searchBar = searchBar;

M.StaticTileLayer = StaticTileLayer;
M.staticTileLayer = staticTileLayer;

Expand Down
4 changes: 2 additions & 2 deletions src/mapml/layers/MapMLLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ export var MapMLLayer = L.Layer.extend({
function _initTemplateVars(serverExtent, metaExtent, projection, mapml, base, projectionMatch){
var templateVars = [];
// set up the URL template and associated inputs (which yield variable values when processed)
var tlist = serverExtent.querySelectorAll('map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query]'),
var tlist = serverExtent.querySelectorAll('map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query],map-link[rel=search]'),
varNamesRe = (new RegExp('(?:\{)(.*?)(?:\})','g')),
zoomInput = serverExtent.querySelector('map-input[type="zoom" i]'),
includesZoom = false, extentFallback = {};
Expand Down Expand Up @@ -1051,7 +1051,7 @@ export var MapMLLayer = L.Layer.extend({
layer._extent._mapExtents = []; // stores all the map-extent elements in the layer
layer._extent._templateVars = []; // stores all template variables coming from all extents
for(let j = 0; j < serverExtent.length; j++){
if (serverExtent[j].querySelector('map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query]') &&
if (serverExtent[j].querySelector('map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query],map-link[rel=search]') &&
serverExtent[j].hasAttribute("units")) {
layer._extent._mapExtents.push(serverExtent[j]);
projectionMatch = projectionMatch || selectedAlternate;
Expand Down
6 changes: 4 additions & 2 deletions src/mapml/layers/TemplatedLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export var TemplatedLayer = L.Layer.extend({
templates[i].extentBounds = inputData.bounds;
templates[i].zoomBounds = inputData.zoomBounds;
this._queries.push(L.extend(templates[i], this._setupQueryVars(templates[i])));
} else if (templates[i].rel === 'search') {
this.search = true;
}
}
},
Expand Down Expand Up @@ -207,7 +209,7 @@ export var TemplatedLayer = L.Layer.extend({
},
onAdd: function (map) {
for (var i=0;i<this._templates.length;i++) {
if (this._templates[i].rel !== 'query') {
if (this._templates[i].rel !== 'query' && this._templates[i].rel !== 'search') {
map.addLayer(this._templates[i].layer);
}
}
Expand All @@ -231,7 +233,7 @@ export var TemplatedLayer = L.Layer.extend({
onRemove: function (map) {
L.DomUtil.remove(this._container);
for (var i=0;i<this._templates.length;i++) {
if (this._templates[i].rel !== 'query') {
if (this._templates[i].rel !== 'query' && this._templates[i].rel !== 'search') {
map.removeLayer(this._templates[i].layer);
}
}
Expand Down
Loading