Skip to content

Instantly share code, notes, and snippets.

@ThomasG77
Last active June 22, 2023 08:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasG77/0b99013795f76699c5c9a0d7daf4411e to your computer and use it in GitHub Desktop.
Save ThomasG77/0b99013795f76699c5c9a0d7daf4411e to your computer and use it in GitHub Desktop.
Leaflet demo to use autocompletion with French GeoAPI and Leaflet.js library http://bl.ocks.org/ThomasG77/0b99013795f76699c5c9a0d7daf4411e
.autocomplete-suggestions {
text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1);
/* core styles should not be changed */
position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
}
.autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; }
.autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; }
.autocomplete-suggestion.selected { background: #f0f0f0; }
/*
JavaScript autoComplete v1.0.3
Copyright (c) 2014 Simon Steinberger / Pixabay
GitHub: https://github.com/Pixabay/JavaScript-autoComplete
License: http://www.opensource.org/licenses/mit-license.php
*/
var autoComplete = (function(){
// "use strict";
function autoComplete(options){
if (!document.querySelector) return;
// helpers
function hasClass(el, className){ return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); }
function addEvent(el, type, handler){
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
}
function removeEvent(el, type, handler){
// if (el.removeEventListener) not working in IE11
if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
}
function live(elClass, event, cb, context){
addEvent(context || document, event, function(e){
var found, el = e.target || e.srcElement;
while (el && !(found = hasClass(el, elClass))) el = el.parentElement;
if (found) cb.call(el, e);
});
}
var o = {
selector: 0,
source: 0,
minChars: 3,
delay: 150,
cache: 1,
menuClass: '',
renderItem: function (item, search){
// escape special characters
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item.replace(re, "<b>$1</b>") + '</div>';
},
onSelect: function(e, term, item){}
};
for (var k in options) { if (options.hasOwnProperty(k)) o[k] = options[k]; }
// init
var elems = typeof o.selector == 'object' ? [o.selector] : document.querySelectorAll(o.selector);
for (var i=0; i<elems.length; i++) {
var that = elems[i];
// create suggestions container "sc"
that.sc = document.createElement('div');
that.sc.className = 'autocomplete-suggestions '+o.menuClass;
that.autocompleteAttr = that.getAttribute('autocomplete');
that.setAttribute('autocomplete', 'off');
that.cache = {};
that.last_val = '';
that.updateSC = function(resize, next){
var rect = that.getBoundingClientRect();
that.sc.style.left = rect.left + (window.pageXOffset || document.documentElement.scrollLeft) + 'px';
that.sc.style.top = rect.bottom + (window.pageYOffset || document.documentElement.scrollTop) + 1 + 'px';
that.sc.style.width = rect.right - rect.left + 'px'; // outerWidth
if (!resize) {
that.sc.style.display = 'block';
if (!that.sc.maxHeight) { that.sc.maxHeight = parseInt((window.getComputedStyle ? getComputedStyle(that.sc, null) : that.sc.currentStyle).maxHeight); }
if (!that.sc.suggestionHeight) that.sc.suggestionHeight = that.sc.querySelector('.autocomplete-suggestion').offsetHeight;
if (that.sc.suggestionHeight)
if (!next) that.sc.scrollTop = 0;
else {
var scrTop = that.sc.scrollTop, selTop = next.getBoundingClientRect().top - that.sc.getBoundingClientRect().top;
if (selTop + that.sc.suggestionHeight - that.sc.maxHeight > 0)
that.sc.scrollTop = selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight;
else if (selTop < 0)
that.sc.scrollTop = selTop + scrTop;
}
}
}
addEvent(window, 'resize', that.updateSC);
document.body.appendChild(that.sc);
live('autocomplete-suggestion', 'mouseleave', function(e){
var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
if (sel) setTimeout(function(){ sel.className = sel.className.replace('selected', ''); }, 20);
}, that.sc);
live('autocomplete-suggestion', 'mouseover', function(e){
var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
if (sel) sel.className = sel.className.replace('selected', '');
this.className += ' selected';
}, that.sc);
live('autocomplete-suggestion', 'mousedown', function(e){
if (hasClass(this, 'autocomplete-suggestion')) { // else outside click
var v = this.getAttribute('data-val');
that.value = v;
o.onSelect(e, v, this);
that.sc.style.display = 'none';
}
}, that.sc);
that.blurHandler = function(){
try { var over_sb = document.querySelector('.autocomplete-suggestions:hover'); } catch(e){ var over_sb = 0; }
if (!over_sb) {
that.last_val = that.value;
that.sc.style.display = 'none';
setTimeout(function(){ that.sc.style.display = 'none'; }, 350); // hide suggestions on fast input
} else if (that !== document.activeElement) setTimeout(function(){ that.focus(); }, 20);
};
addEvent(that, 'blur', that.blurHandler);
var suggest = function(data){
var val = that.value;
that.cache[val] = data;
if (data.length && val.length >= o.minChars) {
var s = '';
for (var i=0;i<data.length;i++) s += o.renderItem(data[i], val);
that.sc.innerHTML = s;
that.updateSC(0);
}
else
that.sc.style.display = 'none';
}
that.keydownHandler = function(e){
var key = window.event ? e.keyCode : e.which;
// down (40), up (38)
if ((key == 40 || key == 38) && that.sc.innerHTML) {
var next, sel = that.sc.querySelector('.autocomplete-suggestion.selected');
if (!sel) {
next = (key == 40) ? that.sc.querySelector('.autocomplete-suggestion') : that.sc.childNodes[that.sc.childNodes.length - 1]; // first : last
next.className += ' selected';
that.value = next.getAttribute('data-val');
} else {
next = (key == 40) ? sel.nextSibling : sel.previousSibling;
if (next) {
sel.className = sel.className.replace('selected', '');
next.className += ' selected';
that.value = next.getAttribute('data-val');
}
else { sel.className = sel.className.replace('selected', ''); that.value = that.last_val; next = 0; }
}
that.updateSC(0, next);
return false;
}
// esc
else if (key == 27) { that.value = that.last_val; that.sc.style.display = 'none'; }
// enter
else if (key == 13 || key == 9) {
var sel = that.sc.querySelector('.autocomplete-suggestion.selected');
if (sel && that.sc.style.display != 'none') { o.onSelect(e, sel.getAttribute('data-val'), sel); setTimeout(function(){ that.sc.style.display = 'none'; }, 20); }
}
};
addEvent(that, 'keydown', that.keydownHandler);
that.keyupHandler = function(e){
var key = window.event ? e.keyCode : e.which;
if (!key || (key < 35 || key > 40) && key != 13 && key != 27) {
var val = that.value;
if (val.length >= o.minChars) {
if (val != that.last_val) {
that.last_val = val;
clearTimeout(that.timer);
if (o.cache) {
if (val in that.cache) { suggest(that.cache[val]); return; }
// no requests if previous suggestions were empty
for (var i=1; i<val.length-o.minChars; i++) {
var part = val.slice(0, val.length-i);
if (part in that.cache && !that.cache[part].length) { suggest([]); return; }
}
}
that.timer = setTimeout(function(){ o.source(val, suggest) }, o.delay);
}
} else {
that.last_val = val;
that.sc.style.display = 'none';
}
}
};
addEvent(that, 'keyup', that.keyupHandler);
that.focusHandler = function(e){
that.last_val = '\n';
that.keyupHandler(e)
};
if (!o.minChars) addEvent(that, 'focus', that.focusHandler);
}
// public destroy method
this.destroy = function(){
for (var i=0; i<elems.length; i++) {
var that = elems[i];
removeEvent(window, 'resize', that.updateSC);
removeEvent(that, 'blur', that.blurHandler);
removeEvent(that, 'focus', that.focusHandler);
removeEvent(that, 'keydown', that.keydownHandler);
removeEvent(that, 'keyup', that.keyupHandler);
if (that.autocompleteAttr)
that.setAttribute('autocomplete', that.autocompleteAttr);
else
that.removeAttribute('autocomplete');
document.body.removeChild(that.sc);
that = null;
}
};
}
return autoComplete;
})();
(function(){
if (typeof define === 'function' && define.amd)
define('autoComplete', function () { return autoComplete; });
else if (typeof module !== 'undefined' && module.exports)
module.exports = autoComplete;
else
window.autoComplete = autoComplete;
})();
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
<style type="text/css">
div.myform > label {
color: rgba(255,122,122,1);
display:block;
}
div.myform > label > input {
height: 1.5rem;
width: auto;
margin: 5px 0 0px 10px;
padding: 0 0.5rem 0 0.5rem;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
font-size: 1rem;
text-overflow: ellipsis;
color: rgba(255, 122, 122, 0.3);
outline: none;
border-radius: 3rem;
border: 0.05rem solid rgba(255, 122, 122, 0.5);
background-image: none;
/*display: block;*/
background-color: #fff;
transition: all 0.4s ease;
-webkit-transition: all -webkit-transform 0.4s ease;
}
</style>
</head>
<body>
<div class="autoComplete_wrapper">
<input id="autoComplete" type="search" dir="ltr" spellcheck=false autocorrect="off" autocomplete="off" autocapitalize="off">
</div>
<div class="myform">
<label>Adresse<input id="adress" class="normal-input"></label>
<label>Code postal<input id="postcode"></label>
<label>Code INSEE<input id="citycode"></label>
<label>Nom commune<input id="city"></label>
<label>Longitude<input id="lon"></label>
<label>Latitude<input id="lat"></label>
</div>
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script>
<script>
var myobject = []
const adress = document.querySelector('#adress')
const postcode = document.querySelector('#postcode')
const citycode = document.querySelector('#citycode')
const city = document.querySelector('#city')
const lon = document.querySelector('#lon')
const lat = document.querySelector('#lat')
const autoCompleteJS = new autoComplete({
selector: "#autoComplete",
placeHolder: "Recherche adresse...",
data: {
src: async () => {
const term = document.querySelector('#autoComplete').value;
if (term) {
const response = await fetch(`https://api-adresse.data.gouv.fr/search/?q=${term}`)
const json = await response.json()
myobject = json.features.map(function(el) {
return {
label: el.properties.label,
value: el.properties.label,
lat: el.geometry.coordinates[1],
lon: el.geometry.coordinates[0],
housenumber: el.properties.housenumber,
name: el.properties.name,
postcode: el.properties.postcode,
citycode: el.properties.citycode,
city: el.properties.city,
context: el.properties.context,
type: el.properties.type,
street: el.properties.street,
boundingbox: null
}
})
adresses = myobject.map(el => el.value)
console.log(adresses);
} else {
adresses = []
}
// ... other code
return adresses
},
// src: ["Sauce - Thousand Island", "Wild Boar - Tenderloin", "Goat - Whole Cut"],
cache: false,
},
resultsList: {
element: (list, data) => {
if (!data.results.length) {
// Create "No Results" message element
const message = document.createElement("div");
// Add class to the created element
message.setAttribute("class", "no_result");
// Add message text content
message.innerHTML = `<span>Found No Results for "${data.query}"</span>`;
// Append message element to the results list
list.prepend(message);
}
},
noResults: true,
},
resultItem: {
highlight: true
},
events: {
input: {
selection: (event) => {
const selection = event.detail.selection.value;
autoCompleteJS.input.value = selection;
var result = myobject.find(el => el.value == event.detail.selection.value)
if (result) {
adress.value = result.name
postcode.value = result.postcode
citycode.value = result.citycode
city.value = result.city
lon.value = result.lon
lat.value = result.lat
} else {
adress.value = ''
postcode.value = ''
citycode.value = ''
city.value = ''
lon.value = ''
lat.value = ''
}
}
}
}
});
</script>
</body>
<!DOCTYPE html><html lang="en">
<head>
<meta charset="utf-8">
<title>Autcompletion avec Leaflet (avec le plugin leaflet-photon) en utilisant la Base Adresse Nationale (BAN)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://raw.githack.com/komoot/leaflet.photon/master/leaflet.photon.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#map {
/* configure the size of the map */
width: 100%;
height: 100%;
}
ul.photon-autocomplete {
position: absolute;
background-color: white;
z-index: 1000;
box-shadow: 0 4px 9px #999999;
display: none;
padding-inline-start: 0px;
}
.photon-autocomplete li span {
float: right;
font-size: 12px;
font-style: italic;
font-weight: normal;
}
</style>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
</head>
<body>
<div id="map" class="map"></div>
<script src="https://raw.githack.com/komoot/leaflet.photon/master/leaflet.photon.js"></script>
<script>
var OpenStreetMap_France = L.tileLayer('http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '&copy; Openstreetmap France | &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
var map = L.map('map').setView({lon: 1.87528, lat: 46.60611}, 5);
map.addLayer(OpenStreetMap_France);
var formatResult = function(feature, el) {
var title = document.createElement('strong');
el.appendChild(title);
var detailsContainer = document.createElement('small');
el.appendChild(detailsContainer);
var details = [];
title.innerHTML = feature.properties.label || feature.properties.name;
var types = {
housenumber: 'numéro',
street: 'rue',
locality: 'lieu-dit',
municipality: 'commune'
};
if (types[feature.properties.type]) {
var spanType = document.createElement('span');
spanType.className = 'type';
title.appendChild(spanType);
spanType.innerHTML = types[feature.properties.type];
}
if (feature.properties.city && feature.properties.city !== feature.properties.name) {
details.push(feature.properties.city);
}
if (feature.properties.context) {
details.push(feature.properties.context);
}
detailsContainer.innerHTML = details.join(', ');
};
// Si vous avez besoin d'effectuer des opérations après la sélection
function myHandler(geojson) {
console.debug(geojson);
};
var photonOptions = {
resultsHandler: myHandler,
placeholder: 'Adresse...',
position: 'topright',
feedbackEmail: null,
lang: 'fr',
url: 'https://api-adresse.data.gouv.fr/search/?',
formatResult: formatResult
}
var searchControl = L.control.photon(photonOptions);
searchControl.addTo(map);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<!--
Copyright (c) 2017-2018 Jean-Marc VIGLINO,
released under CeCILL-B (french BSD like) licence: http://www.cecill.info/
-->
<title>ol-ext: Search BAN control</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Control to add a grid reference to a map." />
<meta name="keywords" content="ol3, control, search, BAN, french, places, autocomplete" />
<!-- Openlayers -->
<link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" />
<script type="text/javascript" src="https://openlayers.org/en/latest/build/ol.js"></script>
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL,Object.assign"></script>
<!-- ol-ext -->
<link rel="stylesheet" href="https://viglino.github.io/ol-ext/dist/ol-ext.css" />
<script type="text/javascript" src="https://viglino.github.io/ol-ext/dist/ol-ext.js"></script>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<script src="https://unpkg.com/elm-pep"></script>
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#map {
/* configure the size of the map */
width: 100%;
height: 100%;
}
.ol-search ul {
color: #333;
font-size:0.85em;
max-width: 21em;
}
.ol-search ul i {
display: block;
color: #333;
font-size:0.85em;
}
.ol-search ul li.copy {
display: none;
}
.info a img {
display: inline-block;
height: 100px;
margin: .5em;
}
</style>
</head>
<body >
<!-- DIV pour la carte -->
<div id="map"></div>
<script type="text/javascript">
// Layers
var layers = [
new ol.layer.Tile({ title: 'OSM', source: new ol.source.OSM(), visible: false }),
new ol.layer.Geoportail({
layer: 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2',
}), new ol.layer.Geoportail({
layer: 'CADASTRALPARCELS.PARCELLAIRE_EXPRESS',
visible: false
})
];
// The map
var map = new ol.Map ({
target: 'map',
view: new ol.View ({
zoom: 5,
center: [166326, 5992663]
}),
interactions: ol.interaction.defaults({ altShiftDragRotate:false, pinchRotate:false }),
layers: layers
});
map.addControl(new ol.control.LayerSwitcher());
// Set the control grid reference
var search = new ol.control.SearchBAN({
reverse:true,
placeholder: 'Adresse',
typing: 150,
position: true // Search, with priority to geo position
});
map.addControl(search);
// Select feature when click on the reference index
search.on('select', function(e) {
map.getView().animate({
center:e.coordinate,
zoom: Math.max (map.getView().getZoom(),16)
});
});
</script>
</body>
</html>
<!DOCTYPE html><html lang="en">
<head>
<meta charset="utf-8">
<title>Vanilla JavaScript autoComplete with French GeoAPI endpoint with Leaflet (adapted from my own OpenLayers sample)</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="auto-complete.css">
<script src=""></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#map {
/* configure the size of the map */
width: 100%;
height: 100%;
}
</style>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
</head>
<body>
<form onsubmit="return false;" style="border-top: 1px solid #eee;border-bottom:1px solid #eee;background:#fafafa;margin:30px 0;padding:20px 10px;text-align:center">
<input id="places-search" autofocus type="text" name="q" placeholder="Communes ..." style="width:100%;max-width:600px;outline:0">
</form>
<div id="map" class="map"></div>
<script src="auto-complete.js"></script>
<script>
var OpenStreetMap_France = L.tileLayer('http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '&copy; Openstreetmap France | &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
var map = L.map('map').setView({lon: 1.87528, lat: 46.60611}, 5);
map.addLayer(OpenStreetMap_France);
var demo_with_map = new autoComplete({
selector: '#places-search',
minChars: 2,
source: function(term, response) {
fetch('https://geo.api.gouv.fr/communes?boost=population&fields=centre&nom=' + term)
.then(function(response) {
return response.text();
}).then(function(body) {
var json = JSON.parse(body);
var new_json = json.map(function(el) {
return {
label: el.nom + ' (' + el.code + ')',
value: el.code,
lat: el.centre.coordinates[1],
lon: el.centre.coordinates[0],
boundingbox: null
}
})
response(new_json);
});
},
renderItem: function(item, search) {
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
var optional_bbox_attribute = '';
if (item.boundingbox) {
var bbox = [item.boundingbox[2], item.boundingbox[0], item.boundingbox[3], item.boundingbox[1]];
var optional_bbox_attribute = 'data-bbox="' + bbox.join(',') + '" ';
}
return '<div class="autocomplete-suggestion" ' + optional_bbox_attribute +
'data-lon="' + item.lon + '" data-lat="' + item.lat +
'" data-val="' + item.label + '">' +
item.label.replace(re, "<b>$1</b>") +
'</div>';
},
onSelect: function(e, term, item) {
if (item.getAttribute('data-bbox') && (item.getAttribute('data-bbox').split(',')).length > 0) {
var extent = item.getAttribute('data-bbox').split(',');
if (extent.length > 0) {
extent = extent.map(function(el) {
return Number(el);
});
}
var bounds = [[extent[1], extent[0]], [extent[3], extent[2]]];
// zoom the map to the bounds
map.fitBounds(bounds);
} else {
var lat = Number(item.getAttribute('data-lat'));
var lon = Number(item.getAttribute('data-lon'));
map.setView(L.latLng(lat, lon), 12);
}
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment