geoportal-extensions-openlayers
Version:

1,222 lines (1,092 loc) • 67.9 kB
JavaScript
// import CSS
import "../CSS/Controls/SearchEngine/GPsearchEngineOpenLayers.css";
// import OpenLayers
import Control from "ol/control/Control";
import Overlay from "ol/Overlay";
import { transform as olProjTransform } from "ol/proj";
// import geoportal library access
import Gp from "geoportal-access-lib";
// import local
import Logger from "../../Common/Utils/LoggerByDefault";
import Utils from "../../Common/Utils";
import Markers from "./Utils/Markers";
import Interactions from "./Utils/Interactions";
import SelectorID from "../../Common/Utils/SelectorID";
import SearchEngineUtils from "../../Common/Utils/SearchEngineUtils";
import GeocodeUtils from "../../Common/Utils/GeocodeUtils";
// DOM
import SearchEngineDOM from "../../Common/Controls/SearchEngineDOM";
var logger = Logger.getLogger("searchengine");
/**
* @classdesc
* SearchEngine control
*
* @constructor
* @extends {ol.control.Control}
* @type {ol.control.SearchEngine}
* @alias ol.control.SearchEngine
* @param {Object} options - control options
* @param {String} [options.apiKey] - API key. The key "calcul" 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 = true] - collapse mode, true by default
* 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] - Placeholder in search bar. Default is "Rechercher un lieu, une adresse".
* @param {Boolean} [options.displayMarker = true] - set a marker on search result, defaults to true.
* @param {String} [options.markerStyle = "lightOrange"] - Marker style. Currently possible values are "lightOrange" (default value), "darkOrange", "red" and "turquoiseBlue".
* @param {Boolean} [options.displayAdvancedSearch = true] - False to disable advanced search tools (it will not be displayed). Default is true (displayed)
* @param {Object} [options.advancedSearch] - advanced search options for geocoding (filters). Properties can be found among geocode options.filterOptions (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode})
* @param {Object} [options.resources] - resources to be used by geocode and autocompletion services :
* @param {String} [options.resources.geocode] - resources geocoding, by default : "location"
* @param {Array} [options.resources.autocomplete] - resources autocompletion, by default : ["PositionOfInterest", "StreetAddress"]
* @param {Boolean} [options.displayAdvancedSearch = true] - False to disable advanced search tools (it will not be displayed). Default is true (displayed)
* @param {Object} [options.advancedSearch] - advanced search options for geocoding (filters). Properties can be found among geocode options.filterOptions (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode})
* @param {Object} [options.geocodeOptions = {}] - options of geocode service (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode})
* @param {Object} [options.autocompleteOptions = {}] - options of autocomplete service (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~autoComplete Gp.Services.autoComplete}
* @param {Object} [options.autocompleteOptions.serviceOptions] - options of autocomplete service
* @param {Boolean} [options.autocompleteOptions.triggerGeocode = false] - trigger a geocoding request if the autocompletion does not return any suggestions, false by default
* @param {Number} [options.autocompleteOptions.triggerDelay = 1000] - waiting time before sending the geocoding request, 1000ms by default
* @param {Sting|Numeric|Function} [options.zoomTo] - zoom to results, by default, current zoom.
* @fires searchengine:autocomplete:click
* @fires searchengine:geocode:click
* @example
* var SearchEngine = ol.control.SearchEngine({
* apiKey : "CLEAPI",
* collapsed : true,
* resources : {
* geocode : ["StreetAddress", "PositionOfInterest"],
* autocomplete : ["StreetAddress"]
* },
* advancedSearch : {
* PositionOfInterest : [{name : "municipality", title : "Ville"}],
* StreetAddress : [{...}]
* },
* geocodeOptions : {},
* autocompleteOptions : {}
* });
*
* SearchEngine.on("searchengine:autocomplete:click", function (e) {
* console.warn("autocomplete", e.location);
* });
* SearchEngine.on("searchengine:geocode:click", function (e) {
* console.warn("geocode", e.location);
* });
*/
var SearchEngine = (function (Control) {
/**
* See {@link ol.control.SearchEngine}
* @module SearchEngine
* @alias module:~Controls/SearchEngine
* @param {*} options - options
* @example
* import SearchEngine from "src/OpenLayers/Controls/SearchEngine"
*/
function SearchEngine (options) {
options = options || {};
if (!(this instanceof SearchEngine)) {
throw new TypeError("ERROR CLASS_CONSTRUCTOR");
}
// initialisation du composant
this.initialize(options);
// // Widget main DOM container
this._container = this._initContainer();
// info: le container sera complété lors de l'ajout à la carte (setMap), car certains composants nécessitent d'être liés à la map.
this._containerElement = null;
// on peut éventuellement encapsuler le composant dans une div passée par l'utilisateur
if (options.element && options.element.appendChild) {
// dans ce cas on stocke les deux container
options.element.appendChild(this._container);
this._containerElement = options.element;
}
// call ol.control.Control constructor
Control.call(this, {
element : this._containerElement || this._container,
target : options.target,
render : options.render
});
};
// Inherits from ol.control.Control
if (Control) SearchEngine.__proto__ = Control;
/*
* @lends module:SearchEngine
*/
SearchEngine.prototype = Object.create(Control.prototype, {});
// on récupère les méthodes de la classe commune IsoDOM
Utils.assign(SearchEngine.prototype, SearchEngineDOM);
/**
* Constructor (alias)
*
* @private
*/
SearchEngine.prototype.constructor = SearchEngine;
// ################################################################### //
// ##################### public methods ############################## //
// ################################################################### //
/**
* Overwrite OpenLayers setMap method
*
* @param {ol.Map} map - Map.
*/
SearchEngine.prototype.setMap = function (map) {
if (!map) {
this._clearResults();
}
// on appelle la méthode setMap originale d'OpenLayers
Control.prototype.setMap.call(this, map);
};
/**
* Returns true if widget is collapsed (minimized), false otherwise
*
* @returns {Boolean} collapsed - true if widget is collapsed
*/
SearchEngine.prototype.getCollapsed = function () {
return this.collapsed;
};
/**
* Collapse or display widget main container
*
* @param {Boolean} collapsed - True to collapse widget, False to display it
*/
SearchEngine.prototype.setCollapsed = function (collapsed) {
if (collapsed === undefined) {
logger.log("[ERROR] SearchEngine:setCollapsed - missing collapsed parameter");
return;
}
if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) {
return;
}
this._showSearchEngineInput.click();
this.collapsed = collapsed;
};
/**
* Get locations data from geocode service
*
* @returns {Object} data - locations
*/
SearchEngine.prototype.getData = function () {
return this._geocodedLocations;
};
// ################################################################### //
// ##################### init component ############################## //
// ################################################################### //
/**
* Initialize SearchEngine control (called by SearchEngine constructor)
*
* @param {Object} options - constructor options
* @private
*/
SearchEngine.prototype.initialize = function (options) {
this._checkInputOptions(options);
// define default options
this.options = {
collapsed : true,
zoomTo : "",
resources : {
geocode : [],
autocomplete : []
},
displayAdvancedSearch : true,
advancedSearch : {},
geocodeOptions : {
serviceOptions : {}
},
autocompleteOptions : {
serviceOptions : {},
triggerGeocode : false,
triggerDelay : 1000
},
displayMarker : true,
markerStyle : "lightOrange",
placeholder : "Rechercher un lieu, une adresse"
};
// merge with user options
Utils.mergeParams(this.options, options);
if (this.options.resources.geocode === "") {
this.options.resources.geocode = ["PositionOfInterest", "StreetAddress"];
}
if (this.options.resources.autocomplete.length === 0) {
this.options.resources.autocomplete = ["PositionOfInterest", "StreetAddress"];
}
/** {Boolean} specify if searchEngine control is collapsed (true) or not (false) */
this.collapsed = this.options.collapsed;
// identifiant du contrôle : utile pour suffixer les identifiants CSS (pour gérer le cas où il y en a plusieurs dans la même page)
this._uid = SelectorID.generate();
this._showSearchEngineInput = null;
// container de l'input de recherche
this._inputSearchContainer = null;
// container des reponses de l'autocompletion
this._autocompleteContainer = null;
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 = {};
this._initAdvancedSearchFilters();
// liste des ressources du geocodage pour le geocodage avancé
this._advancedSearchCodes = [];
this._initAdvancedSearchCodes();
// marker
this._marker = null;
// marker style
var _markerStyle = this.options.markerStyle;
this._markerUrl = (Object.keys(Markers).indexOf(_markerStyle) === -1) ? Markers["lightOrange"] : Markers[_markerStyle];
// marker display
this._displayMarker = this.options.displayMarker;
// popup
this._popupContent = null;
this._popupDiv = this._initPopupDiv();
this._popupOverlay = null;
// trigger geocode
this._triggerHandler = null;
};
/**
* this method is called by this.initialize()
* and makes sure input options are correctly formated
*
* @param {Object} options - options
*
* @private
*/
SearchEngine.prototype._checkInputOptions = function (options) {
var i;
if (options.resources) {
// on vérifie que resources est bien un objet
if (typeof options.resources === "object") {
// ressources de geocodage
var geocodeResources = options.resources.geocode;
if (geocodeResources) {
// on vérifie que la liste des ressources de geocodage est bien un tableau
if (Array.isArray(geocodeResources)) {
var geocodeResourcesList = ["StreetAddress", "PositionOfInterest", "CadastralParcel", "Administratif"];
for (i = 0; i < geocodeResources.length; i++) {
if (geocodeResourcesList.indexOf(geocodeResources[i]) === -1) {
// si la resource n'est pas référencée, on l'enlève
// geocodeResources.splice(i, 1);
logger.log("[SearchEngine] options.resources.geocode : " + geocodeResources[i] + " is not a resource for geocode");
}
}
} else {
logger.log("[SearchEngine] 'options.resources.geocode' parameter should be an array");
geocodeResources = null;
}
}
// ressources d'autocompletion
var autocompleteResources = options.resources.autocomplete;
if (autocompleteResources) {
// on vérifie que la liste des ressources d'autocompletion est bien un tableau
if (Array.isArray(autocompleteResources)) {
var autocompleteResourcesList = ["StreetAddress", "PositionOfInterest"];
for (i = 0; i < autocompleteResources.length; i++) {
if (autocompleteResourcesList.indexOf(autocompleteResources[i]) === -1) {
// si la resource n'est pas référencée, on l'enlève
// autocompleteResources.splice(i, 1);
logger.log("[SearchEngine] options.resources.autocomplete : " + autocompleteResources[i] + " is not a resource for autocomplete");
}
}
} else {
logger.log("[SearchEngine] 'options.resources.autocomplete' parameter should be an array");
autocompleteResources = null;
}
}
} else {
logger.log("[SearchEngine] 'resources' parameter should be an object");
options.resources = null;
}
}
};
/**
* this method is called by this.initialize()
* and initialize the geocoding resources titles.
*
* @private
*/
SearchEngine.prototype._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.
*
* @private
*/
SearchEngine.prototype._initAdvancedSearchFilters = function () {
// 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.initialize() and initialize popup div
* (to display results information on marker click)
*
* @return {Object} element - DOM element for popup
* @private
*/
SearchEngine.prototype._initPopupDiv = function () {
var context = this;
var element = document.createElement("div");
element.className = "gp-feature-info-div";
var closer = document.createElement("input");
closer.type = "button";
closer.className = "gp-styling-button closer";
// on closer click : remove popup
closer.onclick = function () {
if (context._popupOverlay != null) {
context._popupOverlay.setPosition(undefined);
}
return false;
};
this._popupContent = document.createElement("div");
this._popupContent.className = "gp-features-content-div";
this._popupContent.style["min-width"] = "200px";
element.appendChild(this._popupContent);
element.appendChild(closer);
return element;
};
// ################################################################### //
// ######################## DOM initialize ########################### //
// ################################################################### //
/**
* Create control main container
*
* @returns {DOMElement} DOM element
*
* @private
*/
SearchEngine.prototype._initContainer = function () {
// create main container
var container = this._createMainContainerElement();
// create show search engine element
var inputShow = this._showSearchEngineInput = this._createShowSearchEngineElement();
container.appendChild(inputShow);
// mode "collapsed"
if (!this.options.collapsed) {
inputShow.checked = true;
}
// create search engine picto
var picto = this._createShowSearchEnginePictoElement();
container.appendChild(picto);
var search = this._inputSearchContainer = this._createSearchInputElement(this.options.placeholder);
var context = this;
if (search.addEventListener) {
search.addEventListener("click", function () {
context.onAutoCompleteInputClick();
});
} else if (search.attachEvent) {
search.attachEvent("onclick", function () {
context.onAutoCompleteInputClick();
});
}
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._autocompleteContainer = 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;
};
/**
* this method is called by :
* - this._initContainer() : ...
* - 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
*/
SearchEngine.prototype._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
*/
SearchEngine.prototype._requestAutoComplete = function (settings) {
// on ne fait pas de requête si on n'a pas renseigné de parametres !
if (!settings || (typeof settings === "object" && 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
Utils.assign(options, this.options.autocompleteOptions.serviceOptions);
// ainsi que la recherche et les callbacks
Utils.assign(options, settings);
// on ajoute le paramètre filterOptions.type spécifiant les ressources.
var resources = this.options.resources.autocomplete;
if (resources && Array.isArray(resources)) {
// il se peut que l'utilisateur ait surchargé ce paramètre dans geocodeOptions,
if (!options.type) {
options.type = 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.
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)
if (typeof options.ssl !== "boolean") {
if (typeof this.options.ssl === "boolean") {
options.ssl = this.options.ssl;
} else {
options.ssl = true;
}
}
logger.log(options);
Gp.Services.autoComplete(options);
};
/**
* this method is called by this.onAutoCompleteSearchText() (case of success)
* and fills the container of the location list.
* it creates a HTML Element per location
*
* @param {Array} locations - Array of Gp.Services.AutoComplete.SuggestedLocation corresponding to autocomplete results list
* @private
*/
SearchEngine.prototype._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);
}
}
this._displaySuggestedLocation();
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
*/
SearchEngine.prototype._requestGeocoding = function (settings) {
// on ne fait pas de requête si on n'a pas renseigné de parametres !
if (!settings || (typeof settings === "object" && 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
Utils.assign(options, this.options.geocodeOptions.serviceOptions);
// ainsi que la recherche et les callbacks
Utils.assign(options, settings);
// on redefinie les callbacks si les callbacks de service existent
var self = this;
var bOnFailure = !!(this.options.geocodeOptions.serviceOptions.onFailure !== null && typeof this.options.geocodeOptions.serviceOptions.onFailure === "function"); // cast variable to boolean
var bOnSuccess = !!(this.options.geocodeOptions.serviceOptions.onSuccess !== null && typeof this.options.geocodeOptions.serviceOptions.onSuccess === "function");
if (bOnSuccess) {
var cbOnSuccess = function (e) {
settings.onSuccess.call(self, e);
self.options.geocodeOptions.serviceOptions.onSuccess.call(self, e);
};
options.onSuccess = cbOnSuccess;
}
if (bOnFailure) {
var cbOnFailure = function (e) {
settings.onFailure.call(self, e);
self.options.geocodeOptions.serviceOptions.onFailure.call(self, e);
};
options.onFailure = cbOnFailure;
}
// 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
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)
if (typeof options.ssl !== "boolean") {
if (typeof this.options.ssl === "boolean") {
options.ssl = this.options.ssl;
} else {
options.ssl = true;
}
}
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
*/
SearchEngine.prototype._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;
};
// ################################################################### //
// ######################### other methods ########################### //
// ################################################################### //
/**
* this sends the label to the panel.
*
* @param {String} label - label suggested location
* @private
*/
SearchEngine.prototype._setLabel = function (label) {
document.getElementById("GPsearchInputText-" + this._uid).value = label;
};
/**
* this method is called by this.on*ResultsItemClick()
* and move/zoom on a position.
*
* @param {Array} position - ol.Coordinate object [lon, lat] (en lat/lon : "EPSG:4326")
* @param {Number} zoom - zoom level
* @private
*/
SearchEngine.prototype._setPosition = function (position, zoom) {
var view = this.getMap().getView();
view.setCenter(position);
view.setZoom(zoom);
};
/**
* this method is called by this.on*ResultsItemClick()
* and displays a marker.
* FIXME
*
* @param {Array} position - ol.Coordinate object [lon, lat] ou [x, y]
* @param {Object} info - location information
* @private
*/
SearchEngine.prototype._setMarker = function (position, info) {
var map = this.getMap();
var context = this;
// remove previous markers
if (this._marker != null) {
map.removeOverlay(this._marker);
this._marker = null;
}
if (position) {
// création de l'élément DOM
var markerDiv = document.createElement("img");
markerDiv.src = this._markerUrl;
// ajout de l'évènement onclick (pour afficher une popup)
if (markerDiv.addEventListener) {
markerDiv.addEventListener(
"click",
function () {
context._onResultMarkerSelect(info);
}
);
} else if (markerDiv.attachEvent) {
// Internet Explorer
markerDiv.attachEvent(
"onclick",
function () {
context._onResultMarkerSelect(info);
}
);
}
// création du marker (overlay)
this._marker = new Overlay({
position : position,
offset : [-25.5, -38],
element : markerDiv,
stopEvent : false
});
map.addOverlay(this._marker);
}
};
/**
* this method is called by this.on*ResultsItemClick()
* and get zoom to results.
*
* @param {Object} info - info
*
* @returns {Integer} zoom
* @private
*/
SearchEngine.prototype._getZoom = function (info) {
var map = this.getMap();
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.getView().getZoom();
}
// FIXME test si le zoom est dans l'espace de la carte
var min = map.minZoom; // .getMinZoom();
var max = map.maxZoom; // .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 on 'click' on this._marker
* (cf. this._setMarker() )
* and sets a popup with marker information
*
* @param {Object} information - location information
* @private
*/
SearchEngine.prototype._onResultMarkerSelect = function (information) {
var map = this.getMap();
var popupContent = "";
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._popupContent.innerHTML = popupContent;
if (!this._popupOverlay) {
// ajout de la popup a la carte comme un overlay
this._popupOverlay = new Overlay({
element : this._popupDiv,
positioning : "bottom-center",
position : this._marker.getPosition(),
offset : [0, -42]
});
map.addOverlay(this._popupOverlay);
} else {
// si l'overlay est déjà créé, on modifie juste sa position
this._popupOverlay.setPosition(this._marker.getPosition());
}
};
// ################################################################### //
// ###################### 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.
*
* @private
*/
SearchEngine.prototype.onShowSearchEngineClick = function () {
var map = this.getMap();
// on supprime toutes les interactions
Interactions.unset(map);
this.collapsed = this._showSearchEngineInput.checked;
// on génère nous même l'evenement OpenLayers de changement de propriété
// (utiliser ol.control.SearchEngine.on("change:collapsed", function ) pour s'abonner à cet évènement)
this.dispatchEvent("change:collapsed");
};
/**
* this method is called by event 'click' on 'GPsearchInputReset' tag div
* (cf. this._createSearchInputElement), and it cleans the value of input.
*
* @private
*/
SearchEngine.prototype.onSearchResetClick = function () {
this._clearResults();
};
// ################################################################### //
// ################## handlers events AutoComplete ################### //
// ################################################################### //
/**
* this method is called by event 'click' on 'GPlocationOrigin' input
*
* @private
*/
SearchEngine.prototype.onAutoCompleteInputClick = function () {
var inputSearchTextContainer = document.getElementById("GPsearchInputText-" + this._uid);
if (inputSearchTextContainer && !inputSearchTextContainer.disabled && inputSearchTextContainer.value.length > 2) {
this._displaySuggestedLocation();
}
};
/**
* 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.
*
* @param {Object} e - HTMLElement
* @private
*/
SearchEngine.prototype.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) {
this._clearSuggestedLocation();
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.
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 /* && value.length === 5 */) {
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({
location : 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
);
}
}
});
var map = this.getMap();
map.on(
"click",
this._hideSuggestedLocation,
this
);
map.on(
"pointerdrag",
this._hideSuggestedLocation,
this
);
};
/**
* 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
*/
SearchEngine.prototype._getGeocodeCoordinatesFromFullText = function (suggestedLocation, i) {
var context = this;
Gp.Services.geocode({
apiKey : this.options.apiKey,
ssl : this.options.ssl,
q : GeocodeUtils.getSuggestedLocationFreeform(suggestedLocation),
index : suggestedLocation.type,
// 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
/// \TODO verifier si l'inversion des coordonnees est necessaire
if (context._suggestedLocations && context._suggestedLocations[i]) {
context._suggestedLocations[i].position = {
lon : response.locations[0].position.y,
lat : response.locations[0].position.x
};
// et on l'affiche dans la liste
context._locationsToBeDisplayed.unshift(context._suggestedLocations[i]);
context._fillAutoComple