geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
1,189 lines (1,073 loc) • 113 kB
JavaScript
// import CSS
import "../../CSS/Controls/SearchEngine/GPFsearchEngine.css";
// import "../../CSS/Controls/SearchEngine/GPFsearchEngineStyle.css";
// import OpenLayers
// import Control from "ol/control/Control";
import Control from "../Control";
import Widget from "../Widget";
import Map from "ol/Map";
import Overlay from "ol/Overlay";
import {
transform as olProjTransform,
get as olProjGet,
transformExtent as olProjTransformExtent
} from "ol/proj";
import GeoJSON from "ol/format/GeoJSON";
// import geoportal library access
import Gp from "geoportal-access-lib";
// import local
import Config from "../../Utils/Config";
import Logger from "../../Utils/LoggerByDefault";
import Utils from "../../Utils/Helper";
import Markers from "../Utils/Markers";
import Interactions from "../Utils/Interactions";
import SelectorID from "../../Utils/SelectorID";
import MathUtils from "../../Utils/MathUtils";
import SearchEngineUtils from "../../Utils/SearchEngineUtils";
import GeocodeUtils from "../../Utils/GeocodeUtils";
import CRS from "../../CRS/CRS";
// import local des layers
import GeoportalWMS from "../../Layers/LayerWMS";
import GeoportalWMTS from "../../Layers/LayerWMTS";
import GeoportalWFS from "../../Layers/LayerWFS";
import GeoportalMapBox from "../../Layers/LayerMapBox";
// Service
import Search from "../../Services/Search";
// DOM
import SearchEngineDOM from "./SearchEngineDOM";
import checkDsfr from "../Utils/CheckDsfr";
var logger = Logger.getLogger("searchengine");
/**
* @typedef {Object} SearchEngineOptions
* @property {number} [id] - Identifiant du widget (option avancée)
* @property {string} [apiKey] - Clé API. "calcul" par défaut.
* @property {boolean} [ssl=true] - Utilisation du protocole https (true par défaut)
* @property {boolean} [collapsed=true] - Mode réduit (true par défaut)
* @property {boolean} [collapsible=true] - Contrôle pliable ou non (true par défaut)
* @property {string} [direction="start"] - Position du picto (loupe), "start" par défaut
* @property {string} [placeholder="Rechercher un lieu, une adresse"] - Placeholder de la barre de recherche
* @property {boolean} [displayMarker=true] - Afficher un marqueur sur le résultat (true par défaut)
* @property {string} [markerStyle="lightOrange"] - Style du marqueur ("lightOrange", "darkOrange", "red", "turquoiseBlue")
* @property {string} [markerUrl=""] - URL du marqueur (prioritaire sur markerStyle)
* @property {boolean} [splitResults=false] - Désactiver la recherche par couches (false par défaut)
* @property {boolean} [displayButtonAdvancedSearch=false] - Afficher le bouton de recherche avancée (false par défaut)
* @property {boolean} [displayButtonGeolocate=false] - Afficher le bouton de géolocalisation (false par défaut)
* @property {boolean} [displayButtonCoordinateSearch=false] - Afficher le bouton de recherche par coordonnées (false par défaut)
* @property {boolean} [coordinateSearchInAdvancedSearch=false] - Afficher la recherche par coordonnées dans la recherche avancée
* @property {boolean} [displayButtonClose=true] - Afficher le bouton de fermeture (true par défaut)
* @property {Object} [coordinateSearch] - Options de recherche par coordonnées
* @property {HTMLElement} [coordinateSearch.target=null] - Cible d'affichage des résultats
* @property {Array} [coordinateSearch.units] - Unités de coordonnées à afficher ("DEC", "DMS", "M", "KM")
* Values may be "DEC" (decimal degrees), "DMS" (sexagecimal) for geographical coordinates,
* and "M" or "KM" for metric coordinates
* @property {Array} [coordinateSearch.systems] - Systèmes de projection à afficher (objet avec crs, label, type)
* @property {Object} [advancedSearch] - Options de recherche avancée (voir geocodeOptions.filterOptions)
* @property {HTMLElement} [advancedSearch.target=null] - Cible d'affichage des résultats
* @property {Object} [resources] - Ressources utilisées par les services
* @property {string|string[]} [resources.geocode="location"] - Ressources de géocodage
* @property {string[]} [resources.autocomplete] - Ressources d'autocomplétion
* @property {boolean} [resources.search=false] - Activer le service de recherche (false par défaut)
* @property {Object} [searchOptions={}] - Options du service de recherche
* @property {boolean} [searchOptions.addToMap=true] - Ajouter la couche automatiquement à la carte
* @property {string[]} [searchOptions.filterServices] - Filtrer sur une liste de services ("WMTS,TMS" par défaut)
* @property {string[]} [searchOptions.filterWMTSPriority] - Filtrer sur les couches WMTS prioritaires
* @property {string[]} [searchOptions.filterProjections] - Filtrer sur une liste de projections
* @property {boolean} [searchOptions.filterLayersPriority=false] - Filtrer sur les couches prioritaires
* @property {boolean} [searchOptions.filterLayers=true] - Activer le filtrage automatique des couches
* @property {Object} [searchOptions.filterLayersList] - Liste des couches à filtrer {"layerName": "service"}
* @property {boolean} [searchOptions.filterTMS=true] - Garder les TMS avec style dans les métadonnées
* @property {Object} [searchOptions.serviceOptions] - Options du service de recherche
* @property {string} [searchOptions.serviceOptions.url] - URL du service
* @property {string} [searchOptions.serviceOptions.index="standard"] - Index de recherche
* @property {string[]} [searchOptions.serviceOptions.fields=["title","layer_name"]] - Champs de recherche
* @property {number} [searchOptions.serviceOptions.size=1000] - Nombre de réponses du service
* @property {number} [searchOptions.serviceOptions.maximumResponses=10] - Nombre de résultats à afficher
* @property {number} [searchOptions.maximumEntries] - Nombre maximum de résultats à afficher
* @property {Object} [geocodeOptions={}] - Options du service de géocodage (voir Gp.Services.geocode {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode}))
* @property {Object} [geocodeOptions.serviceOptions] - Options du service de géocodage
* @property {Object} [autocompleteOptions={}] - Options du service d'autocomplétion (voir Gp.Services.autoComplete {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~autoComplete Gp.Services.autoComplete})
* @property {Object} [autocompleteOptions.serviceOptions] - Options du service d'autocomplétion
* @property {boolean} [autocompleteOptions.triggerGeocode=false] - Déclencher une requête de géocodage si aucune suggestion
* @property {number} [autocompleteOptions.triggerDelay=1000] - Délai avant la requête de géocodage (ms)
* @property {number} [autocompleteOptions.maximumEntries] - Nombre maximum de résultats d'autocomplétion à afficher
* @property {boolean} [autocompleteOptions.prettifyResults=false] - Nettoyer/embellir les résultats d'autocomplétion
* @property {string|number|Function} [zoomTo] - Niveau de zoom à appliquer sur le résultat ("auto", niveau, ou fonction)
* Value possible : auto or zoom level.
* Possible to overload it with a function :
* zoomTo : function (info) {
* // do some stuff...
* return zoom;
* }
*/
/**
* @classdesc
* SearchEngine control
*
* @alias ol.control.SearchEngine
* @module SearchEngine
*/
class SearchEngine extends Control {
/**
* @constructor
* @param {SearchEngineOptions} options - control options
* @fires searchengine:autocomplete:click
* @fires searchengine:geocode:click
* @fires searchengine:search:click
* @fires searchengine:geolocation:click
* @fires searchengine:geolocation:remove
* @fires searchengine:coordinates:click
* @todo option : direction (start|end) de la position du picto (loupe)
* @todo option : choix du target pour les fenetres geocodage ou recherche par coordonnées
* @example
* var SearchEngine = ol.control.SearchEngine({
* apiKey : "CLEAPI",
* collapsed : true,
* collapsible : true,
* displayButtonAdvancedSearch : true,
* displayButtonGeolocate : true,
* displayButtonCoordinateSearch : true,
* markerStyle : "lightOrange" // "http://..." or "data/base64..."
* resources : {
* geocode : ["StreetAddress", "PositionOfInterest"],
* autocomplete : ["StreetAddress"],
* search : false
* },
* advancedSearch : {
* target : document.getElementById("dialog"),
* PositionOfInterest : [{name : "municipality", title : "Ville"}],
* StreetAddress : [{...}]
* },
* coordinateSearch : {
* target : null
* systems : [
* {
* "crs" : "EPSG:3857",
* "label" : "Web Mercator",
* "type" : "Metric"
* },
* {
* "crs" : "EPSG:4326",
* "label" : "Géographiques",
* "type" : "Geographical"
* }
* ],
* units : ["DEC", "DMS"]
* },
* geocodeOptions : {},
* autocompleteOptions : {},
* searchOptions : {}
* });
*
* SearchEngine.on("searchengine:autocomplete:click", function (e) {
* console.warn("autocomplete", e.location);
* });
* SearchEngine.on("searchengine:search:click", function (e) {
* console.warn("search", e.suggest);
* });
* SearchEngine.on("searchengine:geocode:click", function (e) {
* console.warn("geocode", e.location);
* });
* SearchEngine.on("searchengine:geolocation:click", function (e) {
* console.warn("geolocation", e.);
* });
* SearchEngine.on("searchengine:coordinate:click", function (e) {
* console.warn("coordinate", e.);
* });
*/
constructor (options) {
options = options || {};
// call ol.control.Control constructor
super(options);
if (!(this instanceof SearchEngine)) {
throw new TypeError("ERROR CLASS_CONSTRUCTOR");
}
/**
* Nom de la classe (heritage)
* @private
*/
this.CLASSNAME = "SearchEngine";
// initialisation du composant
this.initialize(options);
// // Widget main DOM container
this.container = this._initContainer();
// ajout du container
(this.element) ? this.element.appendChild(this.container) : this.element = this.container;
return this;
}
// ################################################################### //
// ##################### public methods ############################## //
// ################################################################### //
/**
* Overwrite OpenLayers setMap method
*
* @param {Map} map - Map.
*/
setMap (map) {
if (!map) {
this._clearResults();
}
// mode "collapsed"
if (!this.collapsed) {
this._showSearchEngineButton.setAttribute("aria-pressed", true);
}
// on appelle la méthode setMap originale d'OpenLayers
super.setMap(map);
// position
if (this.options.position) {
this.setPosition(this.options.position);
}
// reunion du bouton avec le précédent
if (this.options.gutter === false) {
this.getContainer().classList.add("gpf-button-no-gutter");
}
}
/**
* Returns true if widget is collapsed (minimized), false otherwise
*
* @returns {Boolean} collapsed - true if widget is collapsed
*/
getCollapsed () {
return this.collapsed;
}
/**
* Collapse or display widget main container
*
* @param {Boolean} collapsed - True to collapse widget, False to display it
*/
setCollapsed (collapsed) {
if (collapsed === undefined) {
logger.log("[ERROR] SearchEngine:setCollapsed - missing collapsed parameter");
return;
}
if (!this.options.collapsible) {
return; // on interdit le mode pliable !
}
if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) {
return;
}
this._showSearchEngineButton.click();
this.collapsed = collapsed;
}
/**
* Get locations data from geocode service
*
* @returns {Object} data - locations
*/
getData () {
return this._geocodedLocations;
}
/**
* Get container
*
* @returns {HTMLElement} container
*/
getContainer () {
return this.container;
}
// ################################################################### //
// ##################### init component ############################## //
// ################################################################### //
/**
* Initialize SearchEngine control (called by SearchEngine constructor)
*
* @param {Object} options - constructor options
* @private
*/
initialize (options) {
this._checkInputOptions(options);
// define default options
this.options = {
collapsed : true,
collapsible : true,
zoomTo : "",
resources : {
geocode : [],
autocomplete : [],
search : false
},
displayButtonClose : true,
displayButtonAdvancedSearch : false,
displayButtonGeolocate : false,
displayButtonCoordinateSearch : false,
coordinateSearchInAdvancedSearch : false,
advancedSearch : {},
coordinateSearch : {},
searchOptions : {
addToMap : true,
maximumEntries : 5,
serviceOptions : {
maximumResponses : 10,
},
filterLayers : true
},
geocodeOptions : {
serviceOptions : {}
},
autocompleteOptions : {
serviceOptions : {
maximumResponses : 5,
},
triggerGeocode : false,
triggerDelay : 1000,
prettifyResults : false
},
displayMarker : true,
markerStyle : "lightOrange",
markerUrl : "",
placeholder : "Rechercher un lieu, une adresse",
splitResults : false,
};
// 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"];
}
if (this.options.resources.search) {
// configuration avec gestion des options surchargées du service
if (this.options.searchOptions) {
if (this.options.searchOptions.serviceOptions) {
if (this.options.searchOptions.serviceOptions.url) {
Search.setUrl(this.options.searchOptions.serviceOptions.url);
}
if (this.options.searchOptions.serviceOptions.fields) {
Search.setFields(this.options.searchOptions.serviceOptions.fields);
}
if (this.options.searchOptions.serviceOptions.index) {
Search.setIndex(this.options.searchOptions.serviceOptions.index);
}
if (this.options.searchOptions.serviceOptions.size) {
Search.setSize(this.options.searchOptions.serviceOptions.size);
}
if (this.options.searchOptions.serviceOptions.maximumResponses) {
Search.setMaximumResponses(this.options.searchOptions.serviceOptions.maximumResponses);
}
}
if (this.options.searchOptions.filterServices) {
Search.setFiltersByService(this.options.searchOptions.filterServices);
}
if (this.options.searchOptions.filterLayersPriority) {
Search.setFiltersByLayerPriority(this.options.searchOptions.filterLayersPriority);
}
if (this.options.searchOptions.filterWMTSPriority) {
Search.setFilterWMTSPriority(this.options.searchOptions.filterWMTSPriority);
}
if (this.options.searchOptions.filterTMS === false) {
Search.setFilterTMS(this.options.searchOptions.filterTMS);
}
if (this.options.searchOptions.filterProjections) {
Search.setFiltersByProjection(this.options.searchOptions.filterProjections);
}
}
// abonnement au service
Search.target.addEventListener("suggest", (e) => {
logger.debug(e);
let suggestResults = e.detail;
// filtre des suggestions selon la configuration ou l'option filterLayersList
suggestResults = this._filterResultsFromConfigLayers(suggestResults);
this._fillSearchedSuggestListContainer(suggestResults);
});
}
if (!this.options.collapsible) {
this.options.collapsed = false; // on interdit le mode pliable !
}
/** {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 = this.options.id || SelectorID.generate();
this._showSearchEngineButton = null;
this._showSearchEngineAdvancedButton = null;
// container de l'input de recherche
/** @private */
this._inputSearchContainer = null;
// container des reponses de l'autocompletion / du service de recherche
/** @private */
this._autocompleteContainer = null;
/** @private */
this._containerResultsLocation = null;
/** @private */
this._containerResultsSuggest = null;
// Radio buttons correspondants
/** @private */
this._radioButtonLocation = null;
/** @private */
this._radioButtonSuggest = null;
// listes des reponses de l'autocompletion
/** @private */
this._suggestedLocations = [];
// container des reponses du geocodage
/** @private */
this._geocodedContainer = null;
// liste des reponses du geocodage
/** @private */
this._geocodedLocations = [];
// container des filtres du geocodage
/** @private */
this._filterContainer = null;
// ressource de geocodage selectionnée pour le geocodage avancé
/** @private */
this._currentGeocodingCode = null;
// localisant
/** @private */
this._currentGeocodingLocation = null;
// liste des filtres du geocodage pour le geocodage avancé
/** @private */
this._advancedSearchFilters = {};
this._initAdvancedSearchFilters();
// liste des ressources du geocodage pour le geocodage avancé
/** @private */
this._advancedSearchCodes = [];
this._initAdvancedSearchCodes();
// recherche par coordonnées : systemes de projections
/** @private */
this._coordinateSearchSystems = [];
if (this.options.displayButtonCoordinateSearch) {
this._initCoordinateSearchSystems();
this._currentCoordinateSearchSystems = this._coordinateSearchSystems[0]; // epsg:4326
this._currentCoordinateSearchType = this._coordinateSearchSystems[0].type; // geographical ou metric
}
// recherche par coordonnées : unités
/** @private */
this._coordinateSearchUnits = [];
if (this.options.displayButtonCoordinateSearch) {
this._initCoordinateSearchUnits();
this._currentCoordinateSearchUnits = this._coordinateSearchUnits[this._currentCoordinateSearchType][0].code; // decimal
}
/** @private */
this._coordinateSearchLngInput = null;
/** @private */
this._coordinateSearchLatInput = null;
// marker
/** @private */
this._marker = null;
// marker style or url
/** @private */
var _markerStyle = this.options.markerStyle;
/** @private */
var _markerUrl = this.options.markerUrl;
if (_markerUrl) {
this._markerUrl = _markerUrl;
} else {
this._markerUrl = (Object.keys(Markers).indexOf(_markerStyle) === -1) ? Markers["lightOrange"] : Markers[_markerStyle];
}
// marker display
/** @private */
this._displayMarker = this.options.displayMarker;
// popup
/** @private */
this._popupContent = null;
/** @private */
this._popupDiv = this._initPopupDiv();
/** @private */
this._popupOverlay = null;
// trigger geocode
/** @private */
this._triggerHandler = null;
}
/**
* this method is called by this.initialize()
* and makes sure input options are correctly formated
*
* @param {Object} options - options
*
* @private
*/
_checkInputOptions (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
*/
_initAdvancedSearchCodes () {
// 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
*/
_initAdvancedSearchFilters () {
// 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 the constructor and initialize the projection
* systems.
* getting coordinates in the requested projection :
* see this.onCoordinateSearchSystemChange()
*
* @private
*/
_initCoordinateSearchSystems () {
// on donne la possibilité à l'utilisateur de modifier
// la liste des systèmes à afficher
// Ex. this.options.coordinateSearch.systems
// systemes de projection disponible par defaut
var projectionSystemsByDefault = [{
label : "G\u00e9ographique",
crs : "EPSG:4326",
type : "Geographical"
}, {
label : "Web Mercator",
crs : "EPSG:3857",
type : "Metric"
}, {
label : "Lambert 93",
crs : "EPSG:2154",
type : "Metric"
}];
var systems = this.options.coordinateSearch.systems;
if (systems) {
// on ajoute les definitions d'un systeme de reference fournies par l'utilisateur
for (var i = 0; i < systems.length; i++) {
var sys = systems[i];
this._setSystem(sys);
}
}
// on ajoute les systèmes de projections par défaut
if (this._coordinateSearchSystems.length === 0) {
for (var j = 0; j < projectionSystemsByDefault.length; j++) {
this._setSystem(projectionSystemsByDefault[j]);
}
}
}
/**
* this method is called by the constructor and initialize the units.
* getting coordinates in the requested units :
* see this.onCoordinateSearchUnitsChange()
*
* @private
*/
_initCoordinateSearchUnits () {
// on donne la possibilité à l'utilisateur de modifier
// la liste des unités à afficher
// Ex.
// this.options.units : ["DEC", "DMS"]
// unités disponible par defaut
var projectionUnitsByDefault = {
Geographical : [{
code : "DEC",
label : "degrés décimaux",
format : MathUtils.coordinateToDecimal
}, {
code : "DMS",
label : "degrés sexagésimaux",
format : MathUtils.coordinateToDMS
}],
Metric : [{
code : "M",
label : "mètres",
format : MathUtils.coordinateToMeter
}, {
code : "KM",
label : "kilomètres",
format : MathUtils.coordinateToKMeter
}]
};
var units = this.options.coordinateSearch.units;
if (units) {
for (var type in projectionUnitsByDefault) {
if (projectionUnitsByDefault.hasOwnProperty(type)) {
var found = false;
for (var j = 0; j < projectionUnitsByDefault[type].length; j++) {
var obj = projectionUnitsByDefault[type][j];
for (var i = 0; i < units.length; i++) {
var unit = units[i];
if (obj.code === unit) {
found = true;
if (!this._coordinateSearchUnits[type]) {
this._coordinateSearchUnits[type] = [];
}
this._coordinateSearchUnits[type].push(obj);
}
}
}
if (!found) {
this._coordinateSearchUnits[type] = projectionUnitsByDefault[type];
}
}
}
}
// au cas où...
if (typeof this._coordinateSearchUnits === "object" && Object.keys(this._coordinateSearchUnits).length === 0) {
this._coordinateSearchUnits = projectionUnitsByDefault;
}
}
/**
* this method is called by this.initialize() and initialize popup div
* (to display results information on marker click)
*
* @returns {Object} element - DOM element for popup
* @private
*/
_initPopupDiv () {
var context = this;
var element = document.createElement("div");
element.className = "gp-feature-info-div gpf-widget-color";
// bouton de suppression de la pop-up / marker
// var span = document.createElement("span");
// span.className = "GPelementHidden gpf-visible"; // afficher en dsfr
// span.innerText = "Supprimer";
var remove = document.createElement("button");
remove.title = "Supprimer le marqueur";
remove.className = "gp-styling-button remove gpf-btn gpf-btn-icon-remove fr-btn--remove fr-btn fr-btn--tertiary-no-outline fr-mt-1v fr-mr-2v";
// on remove click : remove marker
remove.onclick = function () {
var map = context.getMap();
if (context._marker) {
map.removeOverlay(context._marker);
context._marker = null;
}
if (context._popupOverlay != null) {
context._popupOverlay.setPosition(undefined);
}
/**
* event triggered when i want a remove geolocation popup
*
* @event searchengine:geolocation:remove
* @property {Object} type - event
* @property {Object} target - instance SearchEngine
* @example
* SearchEngine.on("searchengine:geolocation:remove", function (e) {
* console.log(e.coordinates);
* })
*/
context.dispatchEvent({
type : "searchengine:geolocation:remove"
});
};
// remove.appendChild(span);
// bouton de fermeture de la pop-up
var closer = document.createElement("button");
closer.title = "Fermer la pop-up";
closer.className = "gp-styling-button closer gpf-btn gpf-btn-icon-close fr-btn--close fr-btn fr-btn--tertiary-no-outline fr-mt-1v fr-mr-2v";
// 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(closer);
element.appendChild(this._popupContent);
element.appendChild(remove);
return element;
}
// ################################################################### //
// ######################## DOM initialize ########################### //
// ################################################################### //
/**
* Create control main container
*
* @returns {HTMLElement} DOM element
*
* @private
*/
_initContainer () {
// create main container
var container = this._createMainContainerElement();
var searchDiv = this._createSearchDivElement();
// create search engine picto
var picto = this._showSearchEngineButton = this._createShowSearchEnginePictoElement(this.options.collapsible);
searchDiv.appendChild(picto);
// only dsfr : on applique un fond blanc sur une barre de recherche fixe
if (!this.options.collapsible) {
container.classList.add("gpf-widget-color", "gpf-widget-padding");
}
var search = this._inputSearchContainer = this._createSearchInputElement(this.options.placeholder);
if (this.options.displayButtonClose) {
search.appendChild(this._createSearchResetElement());
}
var context = this;
if (search.addEventListener) {
search.addEventListener("click", function () {
context.onAutoCompleteInputClick();
});
} else if (search.attachEvent) {
search.attachEvent("onclick", function () {
context.onAutoCompleteInputClick();
});
}
searchDiv.appendChild(search);
var buttonsContainer = this._createButtonsElement();
var firstLineWrapper = this._createFirstLineWrapper();
firstLineWrapper.appendChild(searchDiv);
firstLineWrapper.appendChild(buttonsContainer);
container.appendChild(firstLineWrapper);
if (checkDsfr() && this.options.splitResults) {
var radioContainer = this._createRadioContainer();
container.appendChild(radioContainer);
}
if (checkDsfr() && this.options.splitResults) {
var radioElements;
[radioElements, this._radioButtonLocation, this._radioButtonSuggest] = this._createRadioElements();
radioContainer.appendChild(radioElements);
}
if (this.options.displayButtonGeolocate) {
var geolocateShow = this._createShowGeolocateElement();
buttonsContainer.appendChild(geolocateShow);
}
if (this.options.displayButtonCoordinateSearch || this.options.coordinateSearchInAdvancedSearch) {
var searchByCoordinateShow = this._createShowSearchByCoordinateElement();
if (!this.options.coordinateSearchInAdvancedSearch) {
buttonsContainer.appendChild(searchByCoordinateShow);
}
var coordinatePanel = this._createCoordinateSearchPanelElement();
var coordinatePanelDiv = this._createCoordinateSearchPanelDivElement();
var coordinateHeader = this._createCoordinateSearchPanelHeaderElement();
var coordinateForm = this._createCoordinateSearchPanelFormElement();
var div = null;
div = this._containerSystems = this.__createCoordinateSearchDivElement();
coordinateForm.appendChild(div);
var labelSystems = this._createCoordinateSearchSystemsLabelElement();
var systems = this._setCoordinateSearchSystemsSelectElement(this._coordinateSearchSystems);
div.appendChild(labelSystems);
div.appendChild(systems);
div = this._containerUnits = this.__createCoordinateSearchDivElement();
coordinateForm.appendChild(div);
var labelUnits = this._createCoordinateSearchUnitsLabelElement();
var units = this._setCoordinateSearchUnitsSelectElement(this._coordinateSearchUnits[this._currentCoordinateSearchType]);
div.appendChild(labelUnits);
div.appendChild(units);
div = this._containerCoordinateLat = this.__createCoordinateSearchDivElement();
coordinateForm.appendChild(div);
var coordinateLat = this._setCoordinateSearchLatLabelElement(this._currentCoordinateSearchType);
var coordinateInputLat = this._coordinateSearchLatInput = this._setCoordinateSearchLatInputElement(this._currentCoordinateSearchUnits);
div.appendChild(coordinateLat);
div.appendChild(coordinateInputLat);
div = this._containerCoordinateLng = this.__createCoordinateSearchDivElement();
coordinateForm.appendChild(div);
var coordinateLng = this._setCoordinateSearchLngLabelElement(this._currentCoordinateSearchType);
var coordinateInputLng = this._coordinateSearchLngInput = this._setCoordinateSearchLngInputElement(this._currentCoordinateSearchUnits);
div.appendChild(coordinateLng);
div.appendChild(coordinateInputLng);
var submit = this._createCoordinateSearchSubmitElement();
coordinateForm.appendChild(submit);
coordinatePanelDiv.appendChild(coordinateHeader);
coordinatePanelDiv.appendChild(coordinateForm);
coordinatePanel.appendChild(coordinatePanelDiv);
if (!this.options.coordinateSearchInAdvancedSearch) {
container.appendChild(coordinatePanel);
}
}
if (this.options.displayButtonAdvancedSearch) {
var advancedShow = this._showSearchEngineAdvancedButton = this._createShowAdvancedSearchElement();
buttonsContainer.appendChild(advancedShow);
// INFO je decompose les appels car j'ai besoin de recuperer le container
// des filtres
var advancedPanel = this._createAdvancedSearchPanelElement();
var advancedPanelDiv = this._createAdvancedSearchPanelDivElement();
var advancedHeader = this._createAdvancedSearchPanelHeaderElement();
var advancedForm = this._createAdvancedSearchPanelFormElement(this._advancedSearchCodes, this.options.coordinateSearchInAdvancedSearch);
var advancedFormFilters = this._filterContainer = this._createAdvancedSearchFormFiltersElement();
this._setFilter(this._advancedSearchCodes[0].id); // ex "PositionOfInterest"
var advancedFormInput = this._createAdvancedSearchFormInputElement();
advancedForm.appendChild(advancedFormFilters);
if (this.options.coordinateSearchInAdvancedSearch) {
advancedForm.appendChild(coordinateForm);
}
advancedForm.appendChild(advancedFormInput);
advancedPanelDiv.appendChild(advancedHeader);
advancedPanelDiv.appendChild(advancedForm);
advancedPanel.appendChild(advancedPanelDiv);
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._createAutoCompleteListElement();
var containerResultsLocation = this._containerResultsLocation = this._createAutoCompletedLocationContainer();
var containerResultsSuggest = this._containerResultsSuggest = this._createSearchedSuggestContainer();
autocompleteList.appendChild(containerResultsLocation);
autocompleteList.appendChild(containerResultsSuggest);
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 geocodeDiv = this._createGeocodeResultsDivElement();
geocode.appendChild(geocodeDiv);
var geocodeList = this._geocodedContainer = this._createGeocodeResultsListElement();
geocodeDiv.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 {HTMLElement} DOM element
* @private
*/
_setFilter (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 (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
*/
_fillAutoCompletedLocationListContainer (locations) {
if (!locations || locations.length === 0) {
return;
}
// on vide la liste avant de la construire
var element = this._containerResultsLocation;
if (element.childElementCount) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
element.classList.add("GPelementHidden", "gpf-hidden");
if (locations.length) {
if (!this._radioButtonLocation || (this._radioButtonLocation && this._radioButtonLocation.checked)) {
element.classList.remove("GPelementHidden", "gpf-hidden");
}
this._displaySuggestedLocation();
if (!checkDsfr() || !this.options.splitResults) {
this._createAutoCompletedLocationTitleElement();
}
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.() (case of success)
* and fills the container of the suggest list.
* it creates a HTML Element per suggest
*
* @param {Array} suggests - Array of suggested corresponding to search results list
* @private
*/
_fillSearchedSuggestListContainer (suggests) {
// on réduit la liste de suggests au nombre d'éléments maximum que l'on veut afficher
if (this.options.searchOptions.maximumEntries) {
suggests = suggests.slice(0, this.options.searchOptions.maximumEntries);
}
// on vide la liste avant de la construire
var element = this._containerResultsSuggest;
if (element.childElementCount) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
element.classList.add("GPelementHidden", "gpf-hidden");
if (suggests.length) {
if (!this._radioButtonSuggest || (this._radioButtonSuggest && this._radioButtonSuggest.checked)) {
element.classList.remove("GPelementHidden", "gpf-hidden");
}
if (!checkDsfr() || !this.options.splitResults) {
this._createSearchedSuggestTitleElement();
}
for (let i = 0; i < suggests.length;