geoportal-extensions-leaflet
Version:
French Geoportal Extension for Leaflet
1,309 lines (1,154 loc) • 49.8 kB
JavaScript
/* global KeyboardEvent */
import Gp from "geoportal-access-lib";
import L from "leaflet";
import Logger from "../../Common/Utils/LoggerByDefault";
import ID from "../../Common/Utils/SelectorID";
import SearchEngineUtils from "../../Common/Utils/SearchEngineUtils";
import GeocodeUtils from "../../Common/Utils/GeocodeUtils";
import IconDefault from "./Utils/IconDefault";
import SearchEngineDOM from "../../Common/Controls/SearchEngineDOM";
import Utils from "../../Common/Utils";
var logger = Logger.getLogger("searchengine");
/**
* @classdesc
*
* Leaflet Control Class to search positons of geographic identifiers (places names, address, cadastral parcel) using :
*
* - the [geocoding web service of the Geoportal Platform]{@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html}.
* - the [autocompletion service of the Geoportal Platform]{@link https://geoservices.ign.fr/documentation/geoservices/autocompletion.html}
* <br/>
*
* Use {@link module :Controls.SearchEngine L.geoportalControl.SearchEngine()} factory to create instances of that class.
*
* **Extends** Leaflet <a href="http://leafletjs.com/reference.html#control" target="_blank">L.Control</a> native class.
*
* @namespace
* @alias L.geoportalControl.SearchEngine
*/
var SearchEngine = L.Control.extend(/** @lends L.geoportalControl.SearchEngine.prototype */ {
includes : SearchEngineDOM,
/**
* options by default
*
* @private
*/
options : {
position : "topleft",
collapsed : true,
displayInfo : true,
zoomTo : "",
resources : [],
placeholder : "Rechercher un lieu, une adresse",
displayMarker : true,
markerStyle : "blue",
displayAdvancedSearch : true,
advancedSearch : {},
geocodeOptions : {},
autocompleteOptions : {
serviceOptions : {},
triggerGeocode : false,
triggerDelay : 1000
}
},
/**
* @constructor SearchEngine
*
* @private
* @alias SearchEngine
* @extends {L.Control}
* @param {Object} options - control options
* @param {String} [options.apiKey] - API key. The "calcul" key is used by default.
* @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol)
* @param {Boolean} [options.collapsed] - collapse mode, false by default
* @param {String} [options.position] - position of component into the map, 'topleft' by default
* @param {Boolean} [options.displayInfo] - get informations on popup marker
* @param {String|Numeric|Function} [options.zoomTo] - zoom to results, by default, current zoom.
* Value possible : auto or zoom level.
* Possible to overload it with a function :
* zoomTo : function (info) {
* // do some stuff...
* return zoom;
* }
* @param {String} [options.placeholder] - set placeholder in search bar. Default is "Rechercher un lieu, une adresse".
* @param {Boolean} [options.displayMarker] - set a marker on search result, defaults to true.
* @param {String|Object} [options.markerStyle] - set a marker style. Currently possible values are "blue" (default value), "orange", "red" and "green". But you can use an L.Icon object (see {@link http://leafletjs.com/reference-1.2.0.html#icon L.Icon }).
* @param {Sting} [options.apiKey] - API key. The "calcul" key is used by default.
* @param {String} [options.resources.geocode] - resources geocoding, by default : "location"
* @param {Object} [options.resources.autocomplete] - resources to be used by autocompletion service, by default : ["StreetAddress", "PositionOfInterest"]
* @param {Boolean} [options.displayAdvancedSearch] - False to disable advanced search tools (it will not be displayed). Default is true (displayed)
* @param {Object} [options.advancedSearch] - advanced search for geocoding (filters)
* @param {Object} [options.geocodeOptions] - options of geocode service
* @param {Object} [options.autocompleteOptions] - options of autocomplete service
* @param {Object} [options.autocompleteOptions.serviceOptions] - options of autocomplete service
* @param {Boolean} [options.autocompleteOptions.triggerGeocode] - trigger a geocoding request if the autocompletion does not return any suggestions, false by default
* @param {Number} [options.autocompleteOptions.triggerDelay] - waiting time before sending the geocoding request, 1000ms by default
* @example
* var SearchEngine = L.geoportalControl.SearchEngine({
* position : "topright",
* collapsed : true,
* displayInfo : true,
* displayAdvancedSearch : true,
* placeholder : "Rechercher un lieu, une adresse",
* displayMarker : true,
* markerStyle : L.icon(iconUrl : 'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png');
* zoomTo : 15,
* resources : ["PositionOfInterest", "StreetAddress"],
* advancedSearch : {
* PositionOfInterest : [{name : "municipality", title : "Ville"}],
* StreetAddress : [{...}],
* CadastralParcel : null,
* },
* apiKey : "zfgzrgffg57rfg8ar7gr4g5r4",
* geocodeOptions : {},
* autocompleteOptions : {}
* });
*
*/
initialize : function (options) {
// on transmet les options au controle
L.Util.setOptions(this, options);
if (typeof this.options.resources === "undefined") {
this.options.resources = {};
}
if (typeof this.options.resources.geocode === "undefined" || this.options.resources.geocode === "") {
this.options.resources.geocode = "location";
}
if (typeof this.options.resources.autocomplete === "undefined" || this.options.resources.autocomplete.length === 0) {
this.options.resources.autocomplete = ["PositionOfInterest", "StreetAddress"];
}
/** uuid */
this._uid = ID.generate();
/** affichage du container de saisie */
this._showContainer = null;
this._pictoContainer = null;
/** container de la saisie du la recherche */
this._inputContainer = null;
/** container des reponses de l'autocompletion */
this._suggestedContainer = null;
/** listes des reponses de l'autocompletion */
this._suggestedLocations = [];
/** container des reponses du geocodage */
this._geocodedContainer = null;
/** liste des reponses du geocodage */
this._geocodedLocations = [];
/** container des filtres du geocodage */
this._filterContainer = null;
/** ressource de geocodage selectionnée pour le geocodage avancé */
this._currentGeocodingCode = null;
/** localisant */
this._currentGeocodingLocation = null;
/** liste des filtres du geocodage pour le geocodage avancé */
this._advancedSearchFilters = {};
/** liste des ressources du geocodage pour le geocodage avancé */
this._advancedSearchCodes = [];
/** marker */
this._marker = null;
// trigger geocode
this._triggerHandler = null;
},
/**
* this method is called by this.addTo(map)
* and fills variable : this._container = this.onAdd(map)
*
* @returns {DOMElement} DOM element
* @private
*/
onAdd : function (/* map */) {
// TODO initialisation des ressources du geocodage avancé
this._initAdvancedSearchCodes();
// initialisation des filtres du geocodage avancé
this._initAdvancedSearchFilters();
// initialisation du DOM du composant
var container = this._initLayout();
// deactivate of events that may interfere with the map
L.DomEvent
.disableClickPropagation(container)
.disableScrollPropagation(container);
return container;
},
/**
* this method is called when the control is removed from the map
* and removes events on map.
*
* @param {Object} map - the map
*
* @private
*/
onRemove : function (map) {
this._clearSuggestedLocation();
this._clearGeocodedLocation();
if (this._marker != null) {
map.removeLayer(this._marker);
this._marker = null;
}
},
// ################################################################### //
// ########################## methods DOM ############################ //
// ################################################################### //
/**
* this method is called by this.onAdd(map)
* and initialize the container HTMLElement
*
* @returns {DOMElement} DOM element
*
* @private
*/
_initLayout : function () {
// create main container
var container = this._createMainContainerElement();
// create show search engine element
var inputShow = this._showContainer = this._createShowSearchEngineElement();
container.appendChild(inputShow);
// mode "collapsed"
if (!this.options.collapsed) {
inputShow.checked = "true";
}
// create search engine picto
var picto = this._pictoContainer = this._createShowSearchEnginePictoElement();
container.appendChild(picto);
var search = this._createSearchInputElement(this.options.placeholder);
container.appendChild(search);
if (this.options.displayAdvancedSearch) {
var advancedShow = this._createShowAdvancedSearchElement();
container.appendChild(advancedShow);
// INFO je decompose les appels car j'ai besoin de recuperer le container
// des filtres
var advancedPanel = this._createAdvancedSearchPanelElement();
var advancedHeader = this._createAdvancedSearchPanelHeaderElement();
var advancedForm = this._createAdvancedSearchPanelFormElement(this._advancedSearchCodes);
var advancedFormFilters = this._filterContainer = this._createAdvancedSearchFormFiltersElement();
this._setFilter(this._advancedSearchCodes[0].id); // ex "PositionOfInterest"
var advancedFormInput = this._createAdvancedSearchFormInputElement();
advancedForm.appendChild(advancedFormFilters);
advancedForm.appendChild(advancedFormInput);
advancedPanel.appendChild(advancedHeader);
advancedPanel.appendChild(advancedForm);
container.appendChild(advancedPanel);
}
// INFO je decompose les appels car j'ai besoin de recuperer le container
// des resultats de l'autocompletion
var autocomplete = this._createAutoCompleteElement();
var autocompleteList = this._suggestedContainer = this._createAutoCompleteListElement();
autocomplete.appendChild(autocompleteList);
container.appendChild(autocomplete);
// INFO je decompose les appels car j'ai besoin de recuperer le container
// des resultats du geocodage
var geocode = this._createGeocodeResultsElement();
var geocodeList = this._geocodedContainer = this._createGeocodeResultsListElement();
geocode.appendChild(geocodeList);
container.appendChild(geocode);
return container;
},
// ################################################################### //
// ################# methods Filters Geocode Advanced ################ //
// ################################################################### //
/**
* this method is called by this.onAdd()
* and initialize the geocoding resources.
* TODO
*
* @private
*/
_initAdvancedSearchCodes : function () {
// INFORMATION
// on y ajoute les filtres attributaires pour une table de ressources
// selectionnée via un evenement (onchange) de la liste deroulante du
// menu avancé du geocodage.
// cf. onGeocodingAdvancedSearchCodeChange() pour la selection de la
// ressource de geocodage à afficher
var geocodeResources = this.options.resources.geocode;
if (geocodeResources === "location") {
geocodeResources = ["PositionOfInterest", "StreetAddress", "CadastralParcel"];
}
if (!Array.isArray(geocodeResources)) {
geocodeResources = [geocodeResources];
}
for (var i = 0; i < geocodeResources.length; i++) {
switch (geocodeResources[i]) {
case "PositionOfInterest":
this._advancedSearchCodes.push({
id : "PositionOfInterest",
title : "Lieux/toponymes"
});
break;
case "StreetAddress":
this._advancedSearchCodes.push({
id : "StreetAddress",
title : "Adresses"
});
break;
case "CadastralParcel":
this._advancedSearchCodes.push({
id : "CadastralParcel",
title : "Parcelles cadastrales"
});
break;
default:
break;
}
}
// par défaut, au cas où aucune ressource passée en option ne correspond à celles attendues
if (this._advancedSearchCodes.length === 0) {
this._advancedSearchCodes = [{
id : "StreetAddress",
title : "Adresses"
}, {
id : "PositionOfInterest",
title : "Lieux/toponymes"
}, {
id : "CadastralParcel",
title : "Cadastre"
}];
}
logger.log("advancedSearchCodes", this._advancedSearchCodes);
},
/**
* this method is called by this.onAdd()
* and initialize the advanced geocoding filters.
* FIXME
*
* @private
*/
_initAdvancedSearchFilters : function () {
// FIXME la liste des filtres attributaires doit elle être recuperée
// de l'objet geocode ? doit on tous les mettre ou doit on faire un choix ?
// liste des filtres par defauts pour toutes les ressources
this._advancedSearchFilters = SearchEngineUtils.advancedSearchFiltersByDefault;
// on merge les options avancées avec celles par defaut
var advancedSearchFiltersCustom = this.options.advancedSearch;
Utils.assign(this._advancedSearchFilters, advancedSearchFiltersCustom);
logger.log("advancedSearchFilters", this._advancedSearchFilters);
},
/**
* this method is called by :
* - this._initLayout() : ...
* - this.onGeocodingAdvancedSearchCodeChoice() : ...
* and initialize or create the filters container HTMLElement
* to the geocoding advanced menu.
*
* @param {String} code - resource geocoding name
*
* @returns {DOMElement} DOM element
*
* @private
*/
_setFilter : function (code) {
// INFORMATION
// Nous avons 2 solutions possibles pour la mise en place des filtres.
// 1. Soit on decide de creer tous les filtres pour chaque ressource
// de geocodage à l'initialisation du composant, et on joue sur le
// mode 'hidden' pour n'afficher que la ressource selectionnée.
// 2. Soit on decide de creer à chaque fois les filtres pour la
// ressource selectionnée.
// Chaque solution a ses inconvenients/avantages.
// Implementation du choix 2 car elle offre plus de souplesse pour
// recuperer les 'form-data'...
var container = this._filterContainer;
var codeFound = false;
for (var i = 0; i < this._advancedSearchCodes.length; i++) {
if (this._advancedSearchCodes[i].id === code) {
codeFound = true;
break;
}
}
if (!codeFound) {
// cette ressource n'est pas disponible,
// on supprime les anciens enfants...
while (container.firstChild) {
container.removeChild(container.firstChild);
}
return;
}
// on sauvegarde la ressource de geocodage sélectionnée
this._currentGeocodingCode = code;
// on supprime les enfants...
while (container.firstChild) {
container.removeChild(container.firstChild);
}
var lstAttributs = this._advancedSearchFilters[code];
if (!lstAttributs || lstAttributs.length === 0) {
// cette ressource n'est pas parametrable
return;
}
var divTable = this._createAdvancedSearchFiltersTableElement(code, true);
for (var j = 0; j < lstAttributs.length; j++) {
var divFilter = this._createAdvancedSearchFiltersAttributElement(lstAttributs[j]);
divTable.appendChild(divFilter);
}
container.appendChild(divTable);
return container;
},
// ################################################################### //
// ################ methods to request and results ################### //
// ################################################################### //
/**
* this method is called by this.onAutoCompleteSearch()
* and executes a request to the service.
*
* @param {Object} settings - service settings
* @param {String} settings.text - text
* @param {Function} settings.onSuccess - callback
* @param {Function} settings.onFailure - callback
*
* @private
*/
_requestAutoComplete : function (settings) {
// on ne fait pas de requête si on n'a pas renseigné de parametres !
if (!settings || Object.keys(settings).length === 0) {
return;
}
// on ne fait pas de requête si la parametre 'text' est vide !
if (!settings.text) {
return;
}
logger.log(settings);
var options = {};
// on recupere les options du service
L.Util.extend(options, this.options.autocompleteOptions.serviceOptions);
// ainsi que la recherche et les callbacks
L.Util.extend(options, settings);
// cas où la clef API n'est pas renseignée dans les options du service,
// on utilise celle renseignée au niveau du controle ou la clé "calcul" par défaut.
L.Util.extend(options, {
apiKey : options.apiKey || this.options.apiKey
});
// si l'utilisateur a spécifié le paramètre ssl au niveau du control, on s'en sert
// true par défaut (https)
L.Util.extend(options, {
ssl : this.options.ssl
});
logger.log(options);
Gp.Services.autoComplete(options);
},
/**
* this method is called by this.onGeocodingSearch()
* and fills the container of the location list.
* it creates a HTML Element per location
* (cf. this. ...)
*
* @param {Array} locations - Array of Gp.Services.AutoComplete.SuggestedLocation corresponding to autocomplete results list
* @private
*/
_fillAutoCompletedLocationListContainer : function (locations) {
if (!locations || locations.length === 0) {
return;
}
// on vide la liste avant de la construire
var element = this._suggestedContainer;
if (element.childElementCount) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
for (var i = 0; i < locations.length; i++) {
// Proposals are dynamically filled in Javascript by autocomplete service
this._createAutoCompletedLocationElement(locations[i], i);
}
},
/**
* this method is called by this.onAutoCompleteSearch()
* and executes a request to the service.
*
* @param {Object} settings - service settings
* @param {String} settings.location - text
* @param {Function} settings.onSuccess - callback
* @param {Function} settings.onFailure - callback
*
* @private
*/
_requestGeocoding : function (settings) {
// on ne fait pas de requête si on n'a pas renseigné de parametres !
if (!settings || Object.keys(settings).length === 0) {
return;
}
// on ne fait pas de requête si la parametre 'text' est vide !
if (settings.query === null) {
return;
}
logger.log(settings);
var options = {};
// on recupere les options du service
L.Util.extend(options, this.options.geocodeOptions);
// ainsi que la recherche, les filtres du geocodage avancé et les callbacks
L.Util.extend(options, settings);
// on ajoute le paramètre index spécifiant les ressources.
var resources = this.options.resources.geocode;
if (resources) {
// il se peut que l'utilisateur ait surchargé ce paramètre dans geocodeOptions,
// ou qu'il ait déjà été rempli (cas de la recherche avancée)
if (!options.index) {
options.index = resources;
}
}
// cas où la clef API n'est pas renseignée dans les options du service,
// on utilise celle renseignée au niveau du controle ou la clé "calcul" par défaut.
L.Util.extend(options, {
apiKey : options.apiKey || this.options.apiKey
});
// si l'utilisateur a spécifié le paramètre ssl au niveau du control, on s'en sert
// true par défaut (https)
L.Util.extend(options, {
ssl : this.options.ssl
});
logger.log(options);
Gp.Services.geocode(options);
},
/**
* this method is called by this.onGeocodingSearch()
* and fills the container of the location results.
* it creates a HTML Element per location
* (cf. this. ...)
*
* @param {Object[]} locations - locations
*
* @private
*/
_fillGeocodedLocationListContainer : function (locations) {
if (!locations || locations.length === 0) {
this._clearGeocodedLocation();
return;
}
// on vide la liste avant de la construire
var element = this._geocodedContainer;
if (element.childElementCount) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
for (var i = 0; i < locations.length; i++) {
logger.log(locations[i]);
// Proposals are dynamically filled in Javascript by autocomplete service
this._createGeocodedLocationElement(locations[i], i);
}
// sauvegarde de l'etat des locations
this._geocodedLocations = locations;
},
/**
* this method is called by Gp.Services.autoComplete callback in case of success
* (cf. this.onAutoCompleteSearchText), for suggested locations with null coordinates
* (case of postalCode research for instance).
* Send a geocode request with suggested location 'fullText' attribute, to get its coordinates and display it in autocomplete results list container.
*
* @param {Gp.Services.AutoCompleteResponse.SuggestedLocation} suggestedLocation - autocompletion result (with null coordinates) to be geocoded
* @param {Number} i - suggestedLocation position in Gp.Services.AutoCompleteResponse.suggestedLocations autocomplete results list
* @private
*/
_getGeocodeCoordinatesFromFullText : function (suggestedLocation, i) {
var _location = GeocodeUtils.getSuggestedLocationFreeform(suggestedLocation);
var context = this;
this._requestGeocoding({
query : _location,
// callback onSuccess
onSuccess : function (response) {
logger.log("request from Geocoding (coordinates null)", response);
if (response.locations && response.locations.length !== 0 && response.locations[0].position) {
// on modifie les coordonnées du résultat en EPSG:4326 donc lat,lon
if (context._suggestedLocations && context._suggestedLocations[i]) {
context._suggestedLocations[i].position = {
lat : response.locations[0].position.y,
lon : response.locations[0].position.x
};
// et on l'affiche dans la liste
context._locationsToBeDisplayed.unshift(context._suggestedLocations[i]);
context._fillAutoCompletedLocationListContainer(context._locationsToBeDisplayed);
}
}
},
// callback onFailure
onFailure : function () {
// si on n'a pas réussi à récupérer les coordonnées, on affiche quand même le résultat
if (context._suggestedLocations && context._suggestedLocations[i]) {
context._createAutoCompletedLocationElement(context._suggestedLocations[i], i);
}
}
});
},
// ################################################################### //
// ######################### other methods ########################### //
// ################################################################### //
/**
* this sends the label to the input panel.
*
* FIXME appel en dur d'un identifiant CSS !
*
* @param {String} label - label suggested location
*
* @private
*/
_setLabel : function (label) {
var element = L.DomUtil.get("GPsearchInputText-" + this._uid);
element.value = label || "";
},
/**
* this method is called by this.on*ResultsItemClick()
* and move/zoom on a position.
*
* @param {Object} position - {x : ..., y : ...}
* @param {Number} zoom - zoom level
*
* @private
*/
_setPosition : function (position, zoom) {
var map = this._map;
map.setZoomAround(L.latLng(position), zoom, true);
map.panTo(L.latLng(position));
},
/**
* this method is called by this.on*ResultsItemClick()
* and get zoom.
*
* @param {Object} info - info
*
* @returns {Integer} zoom level
*
* @private
*/
_getZoom : function (info) {
var map = this._map;
var key = this.options.zoomTo;
var zoom = null;
// les valeurs du zooms sont determinées
// soit par les mots clefs suivants : max, min ou auto
// soit par un niveau de zoom
// soit defini par l'utilisateur via une fonction
if (typeof key === "function") {
logger.trace("zoom function");
zoom = key.call(this, info);
}
if (typeof key === "number") {
logger.trace("zoom level");
zoom = key;
}
if (typeof key === "string") {
// if (key === "max") {
// zoom = map.getMaxZoom();
// } else if (key === "min") {
// zoom = map.getMinZoom();
// } else
if (key === "auto") {
logger.trace("zoom auto");
zoom = SearchEngineUtils.zoomToResultsByDefault(info);
} else {
logger.trace("zoom level parsing");
var value = parseInt(key, 10);
if (!isNaN(value)) {
logger.trace("zoom parsing");
zoom = value;
}
}
}
// polyfill IE
Number.isInteger = Number.isInteger || function (value) {
return typeof value === "number" &&
isFinite(value) &&
Math.floor(value) === value;
};
// test de validité du zoom,
// on prend le zoom courant par defaut ...
if (!zoom || zoom === "" || !Number.isInteger(zoom)) {
logger.trace("zoom not found, current zoom...");
zoom = map.getZoom();
}
// test si le zoom est dans l'espace de la carte
var min = map.getMinZoom();
var max = map.getMaxZoom();
if (zoom < min) {
logger.trace("zoom level min...");
zoom = min;
}
if (zoom > max) {
logger.trace("zoom level max...");
zoom = max;
}
logger.trace("zoom", zoom);
return zoom;
},
/**
* this method is called by this.on*ResultsItemClick()
* and displays a marker.
* FIXME
*
* @param {Object} position - position {x : ..., y : ...}
* @param {Object} information - suggested or geocoded information
* @param {Boolean} display - display a popup information
* @param {String} marker - style style
*
* @private
*/
_setMarker : function (position, information, display, marker) {
var map = this._map;
if (this._marker != null) {
map.removeLayer(this._marker);
this._marker = null;
}
if (position) {
var _icon = null;
if (typeof marker === "string") {
_icon = new IconDefault(marker);
} else if (marker instanceof L.Icon) {
_icon = marker;
} else {
_icon = new IconDefault("blue");
logger.log("Utilisation du marker par défaut !");
}
// cf. http://leafletjs.com/reference.html#marker-options
var options = {
clickable : true,
zIndexOffset : 1000,
icon : _icon
};
this._marker = L.marker(L.latLng(position), options);
this._marker.addTo(map);
// FIXME
// doit on mettre une information
// - correctement construite ?
// - uniquement informatif ?
// - RIEN ?
if (display) {
var popupContent = null;
if (typeof information !== "string") {
if (information.service === "GeocodedLocation") {
popupContent = "<ul>";
var attributes = information.location.placeAttributes;
for (var attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
if (attr !== "trueGeometry" && attr !== "extraFields" && attr !== "houseNumberInfos" && attr !== "_count") {
popupContent += "<li>";
popupContent += "<span class=\"gp-attname-others-span\">" + attr.toUpperCase() + " : </span>";
popupContent += attributes[attr];
popupContent += " </li>";
}
}
}
popupContent += " </ul>";
} else if (information.service === "SuggestedLocation") {
popupContent = GeocodeUtils.getSuggestedLocationFreeform(information.location);
} else {
popupContent = "sans informations.";
}
} else {
popupContent = information;
}
this._marker.bindPopup(popupContent);
}
}
},
/**
* this method is called by this.onSearchReset()
* and it clears all results and the marker.
*
* @private
*/
_clearResults : function () {
this._currentGeocodingLocation = null;
this._clearSuggestedLocation();
this._clearGeocodedLocation();
this._setMarker();
},
/**
* this method is called by this.onAutoCompleteSearchText()
* and it clears all suggested location.
*
* @private
*/
_clearSuggestedLocation : function () {
this._suggestedLocations = [];
if (this._suggestedContainer) {
while (this._suggestedContainer.firstChild) {
this._suggestedContainer.removeChild(this._suggestedContainer.firstChild);
}
}
},
/**
* this method is called by this.onGeocodingAdvancedSearchSubmit()
* and it clears all geocoded location.
*
* @private
*/
_clearGeocodedLocation : function () {
this._geocodedLocations = [];
if (this._geocodedContainer) {
while (this._geocodedContainer.firstChild) {
this._geocodedContainer.removeChild(this._geocodedContainer.firstChild);
}
}
},
// ################################################################### //
// ###################### other handlers events ###################### //
// ################################################################### //
/**
* this method is called by event 'click' on 'GPshowSearchEnginePicto' tag label
* (cf. this._createShowSearchEnginePictoElement), and it cleans the component
* when it's closed.
* FIXME
*
* @private
*/
onShowSearchEngineClick : function () {
// FIXME on nettoie ou pas ?
// this._clearResults();
},
/**
* this method is called by event 'click' on 'GPsearchInputReset' tag div
* (cf. this._createSearchInputElement), and it cleans the value of input.
*
* @private
*/
onSearchResetClick : function () {
this._clearResults();
},
// ################################################################### //
// ################## handlers events AutoComplete ################### //
// ################################################################### //
/**
* this method is called by event 'keyup' on 'GPsearchInputText' tag input
* (cf. this._createSearchInputElement), and it gets the value of input.
* this value is passed as a parameter for the service autocomplete (text).
* the results of the request are displayed into a drop down menu.
* FIXME
*
* @param {Object} e - HTMLElement
*
* @private
*/
onAutoCompleteSearchText : function (e) {
var value = e.target.value;
if (!value) {
return;
}
// on sauvegarde le localisant
this._currentGeocodingLocation = value;
// on limite les requêtes à partir de 3 car. saisie !
if (value.length < 3) {
return;
}
var _triggerGeocode = this.options.autocompleteOptions.triggerGeocode;
var _triggerDelay = this.options.autocompleteOptions.triggerDelay;
// INFORMATION
// on effectue la requête au service d'autocompletion.
// on met en place des callbacks afin de recuperer les resultats ou
// les messages d'erreurs du service.
// les resultats sont affichés dans une liste deroulante.
// les messages d'erreurs sont affichés sur la console (?)
var context = this;
this._requestAutoComplete({
text : value,
// callback onSuccess
onSuccess : function (results) {
logger.log("request from AutoComplete", results);
if (results) {
// on sauvegarde l'etat des résultats
context._suggestedLocations = results.suggestedLocations;
context._locationsToBeDisplayed = [];
// on vérifie qu'on n'a pas récupéré des coordonnées nulles (par ex recherche par code postal)
for (var i = 0; i < context._suggestedLocations.length; i++) {
var ilocation = context._suggestedLocations[i];
if (ilocation.position && ilocation.position.x === 0 && ilocation.position.y === 0 && ilocation.fullText) {
// si les coordonnées sont nulles, il faut relancer une requête de géocodage avec l'attribut "fullText" récupéré
context._getGeocodeCoordinatesFromFullText(ilocation, i);
} else {
// sinon on peut afficher normalement le résultat dans la liste
context._locationsToBeDisplayed.push(ilocation);
}
};
// on affiche les résultats qui n'ont pas des coordonnées nulles
context._fillAutoCompletedLocationListContainer(context._locationsToBeDisplayed);
// on annule eventuellement une requete de geocodage en cours car on obtient des
// de nouveau des resultats d'autocompletion...
if (context._triggerHandler) {
clearTimeout(context._triggerHandler);
context._triggerHandler = null;
logger.warn("Cancel a geocode request !");
}
}
},
// callback onFailure
onFailure : function (error) {
// FIXME
// où affiche t on les messages : ex. 'No suggestion matching the search' ?
context._clearSuggestedLocation();
logger.log(error.message);
// on envoie une requete de geocodage si aucun resultat d'autocompletion
// n'a été trouvé ! Et on n'oublie pas d'annuler celle qui est en cours !
if (error.message === "No suggestion matching the search" && _triggerGeocode) {
if (context._triggerHandler) {
clearTimeout(context._triggerHandler);
logger.warn("Cancel the last geocode request !");
}
context._triggerHandler = setTimeout(
function () {
logger.warn("Launch a geocode request (code postal) !");
context._requestGeocoding({
query : value,
// callback onSuccess
onSuccess : function (results) {
logger.log("request from Geocoding", results);
if (results) {
context._locationsToBeDisplayed = [];
// on modifie la structure des reponses pour être
// compatible avec l'autocompletion !
var locations = results.locations;
for (var i = 0; i < locations.length; i++) {
var location = locations[i];
location.fullText = GeocodeUtils.getGeocodedLocationFreeform(location);
location.position = {
x : location.position.lon,
y : location.position.lat
};
context._locationsToBeDisplayed.push(location);
}
context._fillAutoCompletedLocationListContainer(locations);
}
},
// callback onFailure
onFailure : function (error) {
logger.log(error.message);
}
});
}, _triggerDelay
);
}
}
});
},
/**
* this method is called by event 'click' on 'GPautoCompleteResultsList' tag div
* (cf. this._createAutoCompleteListElement), and it selects the location.
* this location displays a marker on the map.
* FIXME
*
* @param {Object} e - HTMLElement
*
* @private
*/
onAutoCompletedResultsItemClick : function (e) {
var idx = ID.index(e.target.id);
var label = e.target.innerHTML;
logger.log(idx, label);
logger.log(this._locationsToBeDisplayed[idx]);
if (!idx) {
return;
}
// FIXME
// les coordonnées sont inversées entre les 2 services !?
// AutoCompletion : lon/lat
// Geocoding : lat/lon
var position = {
lat : this._locationsToBeDisplayed[idx].position.y,
lon : this._locationsToBeDisplayed[idx].position.x
};
var info = {
service : "SuggestedLocation",
location : this._locationsToBeDisplayed[idx]
};
var zoom = this._getZoom(info);
this._setLabel(label);
this._setPosition(position, zoom);
if (this.options.displayMarker) {
this._setMarker(position, info, this.options.displayInfo, this.options.markerStyle);
}
/**
* event triggered when an element of the results is clicked for autocompletion
*
* @event searchengine:autocomplete:click
*/
this.fire("searchengine:autocomplete:click", {
data : this._locationsToBeDisplayed[idx]
});
},
// ################################################################### //
// ################### handlers events Geocode ####################### //
// ################################################################### //
/**
* this method is called by event 'submit' on 'GPsearchInput' tag form
* (cf. this._createSearchInputElement), and it gets the value of input.
* this value is passed as a parameter for the service geocoding.
* the results of the request are displayed into a window.
*
* @param {Object} e - HTMLElement
*
* @private
*/
onGeocodingSearchSubmit : function (e) {
logger.log(e);
var value = e.target[0].value;
if (!value) {
return;
}
// on sauvegarde le localisant
this._currentGeocodingLocation = value;
// on met en place l'affichage des resultats dans une fenetre de recherche.
var context = this;
this._requestGeocoding({
query : value,
// callback onSuccess
onSuccess : function (results) {
logger.log("request from Geocoding", results);
if (results) {
var locations = results.locations;
context._fillGeocodedLocationListContainer(locations);
}
},
// callback onFailure
onFailure : function (error) {
// FIXME cf. this.onAutoCompleteSearch()
context._clearGeocodedLocation();
logger.log(error.message);
}
});
},
/**
* this method is called by event 'submit' on 'GPgeocodeResultsList' tag div
* (cf. this._createGeocodeResultsListElement), and it selects the location.
* this location displays a marker on the map.
*
* @param {Object} e - HTMLElement
*
* @private
*/
onGeocodedResultsItemClick : function (e) {
var idx = ID.index(e.target.id);
var label = e.target.innerHTML;
logger.log(idx, label);
logger.log(this._geocodedLocations[idx]);
if (!idx) {
return;
}
var position = this._geocodedLocations[idx].position;
var info = {
service : "GeocodedLocation",
location : this._geocodedLocations[idx]
};
var zoom = this._getZoom(info);
this._setLabel(label);
this._setPosition(position, zoom);
if (this.options.displayMarker) {
this._setMarker(position, info, this.options.displayInfo, this.options.markerStyle);
}
/**
* event triggered when an element of the results is clicked for geocoding
*
* @event searchengine:geocode:click
*/
this.fire("searchengine:geocode:click", {
data : this._geocodedLocations[idx]
});
},
// ################################################################### //
// ############## handlers events Geocode Advanced ################### //
// ################################################################### //
/**
* this method is called by event 'change' on 'GPadvancedSearchCode' tag select
* (cf. this._createAdvancedSearchFormCodeElement), and it gets the value of
* option selected.
* this value is passed as a parameter to create the attributs container.
*
* @param {Object} e - HTMLElement
*
* @private
*/
onGeocodingAdvancedSearchCodeChange : function (e) {
logger.log(e);
var idx = e.target.selectedIndex;
var value = e.target.options[idx].value;
if (!value) {
return;
}
// INFORMATION
// le declenchement de l'evenement va creer un container de filtre à la volée...
// l'insertion des containers d'attributs dans le DOM sont :
// - soit GPadvancedSearchFilters > PositionOfInterest
// - soit GPadvancedSearchFilters > StreetAddress
// - soit GPadvancedSearchFilters > CadastralParcel
// cf. _setFilter() pour la creation du container
this._setFilter(value);
},
/**
* this method is called by event 'submit' on 'GPadvancedSearchForm' tag form
* (cf. this._createAdvancedSearchPanelFormElement), and it gets the value of all input.
* this value is passed as a parameter for the service geocoding.
* the results of the request are displayed into a window.
* TODO
*
* @param {Object} e - HTMLElement
* @param {Array} data - [{key : ..., value : ...}]
*
* @private
*/
onGeocodingAdvancedSearchSubmit : function (e, data) {
logger.log(data);
if (!data || data.length === 0) {
return;
}
// recuperation des parametres des filtres pour les transmettre
// à la requête, ainsi que le type de table de ressources de geocodage,
// et le localisant
var _index = this._currentGeocodingCode;
var inputSearchTextContainer = L.DomUtil.get("GPsearchInputText-" + this._uid);
var _location = inputSearchTextContainer.value;
var _filterOptions = {};
for (var i = 0; i < data.length; i++) {
var filter = data[i];
if (filter.value) {
// On passe la section en majuscule car le service est caseSensitive
if (filter.key === "section") {
filter.value = filter.value.toUpperCase();
}
_filterOptions[filter.key] = filter.value;
}
}
// on met en place l'affichage des resultats dans une fenetre de recherche.
var context = this;
this._requestGeocoding({
query : _location,
index : _index,
filters : _filterOptions,
// callback onSuccess
onSuccess : function (results) {
logger.log(results);
if (results) {
var locations = results.locations;
context._fillGeocodedLocationListContainer(locations);
}
},
// callback onFailure
onFailure : function (error) {
// FIXME cf. this.onAutoCompleteSearch()
context._clearGeocodedLocation();
logger.log(error.message);
}
});
},
// ################################################################### //
// ###### METHODES PUBLIQUES (INTERFACE AVEC LE CONTROLE) ############ //
// ################################################################### //
/**
* This method is public.
* It allows to control the execution of a geocoding or an autocompletion.
*
* @param {String} text - location
* @param {Boolean} type - true (geocoding) / false (autocompletion)
* @param {Object} options - options
*/
setText : function (text, type, options) {
if (!this._showContainer.checked) {
this._pictoContainer.click();
}
// on récupere les options des services
L.Util.extend(this.options, options);
var element = L.DomUtil.get("GPsearchInputText-" + this._uid);
element.value = text;
if (type) {
var form = L.DomUtil.get("GPsearchInput-" + this._uid);
form.dispatchEvent(new Event("submit", {
bubbles : true,
cancelable : true
}));
} else {
element.dispatchEvent(new KeyboardEvent("keyup"));
}
}
});
/** mix in L.Evented into control */
L.extend(SearchEngine.prototype, L.Evented.prototype);
export default SearchEngine;