geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
1,138 lines (1,023 loc) • 111 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 Overlay from "ol/Overlay";
import {
transform as olProjTransform,
get as olProjGet,
transformExtent as olProjTransformExtent
} from "ol/proj";
// 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");
/**
* @classdesc
* SearchEngine control
*
* @constructor
* @extends {ol.control.Control}
* @type {ol.control.SearchEngine}
* @alias ol.control.SearchEngine
* @param {Object} options - control options
* @param {Number} [options.id] - Ability to add an identifier on the widget (advanced option)
* @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
* @param {Boolean} [options.collapsible = true] - force control to be collapsed or not, true by default.
* @param {String} [options.direction = "start"] - TODO : position of picto, by default : "start"
* @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 {String} [options.markerUrl = ""] - Marker url. By default, if not specified, use option markerStyle. Otherwise, you can added a http url or a base64 image.
* @param {Boolean} [options.splitResults = true] - False to disable layers search
* @param {Boolean} [options.displayButtonAdvancedSearch = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed)
* @param {Boolean} [options.displayButtonGeolocate = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed)
* @param {Boolean} [options.displayButtonCoordinateSearch = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed)
* @param {Boolean} [options.coordinateSearchInAdvancedSearch = false] -True to display coord search in advanced search
* @param {Boolean} [options.displayButtonClose = true] - False to disable advanced search tools (it will not be displayed). Default is true (displayed)
* @param {Object} [options.coordinateSearch] - coordinates search options.
* @param {DOMElement} [options.coordinateSearch.target = null] - TODO : target location of results window. By default under the search bar.
* @param {Array} [options.coordinateSearch.units] - list of coordinates units, to be displayed in control units list.
* Values may be "DEC" (decimal degrees), "DMS" (sexagecimal) for geographical coordinates,
* and "M" or "KM" for metric coordinates
* @param {Array} [options.coordinateSearch.systems] - list of projection systems, default are Geographical ("EPSG:4326"), Web Mercator ("EPSG:3857") and Lambert 93 ("EPSG:2154").
* Each array element (=system) is an object with following properties :
* @param {String} [options.coordinateSearch.systems.crs] - Proj4 crs alias (from proj4 defs). e.g. : "EPSG:4326". Required
* @param {String} [options.coordinateSearch.systems.label] - CRS label to be displayed in control. Default is crs code (e.g. "EPSG:4326")
* @param {String} [options.coordinateSearch.systems.type] - CRS units type for coordinates conversion : "Geographical" or "Metric". Default: "Geographical"
* @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 {DOMElement} [options.advancedSearch.target = null] - TODO : target location of results window. By default under the search bar.
* @param {Object} [options.resources] - resources to be used by geocode and autocompletion services :
* @param {String} [options.resources.geocode = "location"] - resources geocoding, by default : "location"
* @param {Array} [options.resources.autocomplete] - resources autocompletion, by default : ["PositionOfInterest", "StreetAddress"]
* @param {Boolean} [options.resources.search = false] - false to disable search service, by default : "false"
* @param {Object} [options.searchOptions = {}] - options of search service
* @param {Boolean} [options.searchOptions.addToMap = true] - add layer automatically to map, defaults to true.
* @param {String[]} [options.searchOptions.filterServices] - filter on a list of search services, each field is separated by a comma. "WMTS,TMS" by default
* @param {String[]} [options.searchOptions.filterWMTSPriority] - filter on priority WMTS layer in search, each field is separated by a comma. "PLAN.IGN,ORTHOIMAGERY.ORTHOPHOTOS" by default
* @param {String[]} [options.searchOptions.filterProjections] - filter on a list of projections : the searchEngine ignore the suggestions with one of the projections listed. Each field is separated by a comma.
* @param {Boolean} [options.searchOptions.filterLayersPriority = false] - filter on priority layers in search, false by default
* @param {Boolean} [options.searchOptions.filterLayers] - false to disable the automatic filter from Config or from the filterLayerList parameter. True by Default.
* @param {Object} [options.searchOptions.filterLayersList] - filter on list of search layers list with a struture {"layerName" : "service"}. By Default, the layers available in Config.configuration.layers.
* @param {Boolean} [options.searchOptions.filterTMS] - filter the results to keep TMS with at least a style (.json) into the metadata. True by Default.
* @param {Object} [options.searchOptions.serviceOptions] - options of search service
* @param {String} [options.searchOptions.serviceOptions.url] - url of service
* @param {String} [options.searchOptions.serviceOptions.index] - index of search, "standard" by default
* @param {String[]} [options.searchOptions.serviceOptions.fields] - list of search fields, each field is separated by a comma. "title,layer_name" by default
* @param {Number} [options.searchOptions.serviceOptions.size] - number of response in the service. 1000 by default
* @param {Number} [options.searchOptions.serviceOptions.maximumResponses] - number of results in the response. 10 by default
* @param {Number} [options.searchOptions.maximumEntries] - maximum search results we want to display.
* @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.geocodeOptions.serviceOptions] - options of geocode service
* @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 {Number} [options.autocompleteOptions.maximumEntries] - maximum autocompletion results we want to display
* @param {Boolean} [options.autocompleteOptions.prettifyResults = false] - apply a filter/prettifier function to clean or prettify autocomplete entries
* @param {Sting|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;
* }
* @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.);
* });
*/
var SearchEngine = class SearchEngine extends Control {
/**
* See {@link ol.control.SearchEngine}
* @module SearchEngine
* @alias module:~controls/SearchEngine
* @param {*} options - options
* @example
* import SearchEngine from "gpf-ext-ol/controls/SearchEngine"
* ou
* import { SearchEngine } from "gpf-ext-ol"
*/
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 {ol.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 {DOMElement} 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
this._inputSearchContainer = null;
// container des reponses de l'autocompletion / du service de recherche
this._autocompleteContainer = null;
this._containerResultsLocation = null;
this._containerResultsSuggest = null;
// Radio buttons correspondants
this._radioButtonLocation = null;
this._radioButtonSuggest = 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();
// recherche par coordonnées : systemes de projections
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
this._coordinateSearchUnits = [];
if (this.options.displayButtonCoordinateSearch) {
this._initCoordinateSearchUnits();
this._currentCoordinateSearchUnits = this._coordinateSearchUnits[this._currentCoordinateSearchType][0].code; // decimal
}
this._coordinateSearchLngInput = null;
this._coordinateSearchLatInput = null;
// marker
this._marker = null;
// marker style or url
var _markerStyle = this.options.markerStyle;
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
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
*/
_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)
*
* @return {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 {DOMElement} 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._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);
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);
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 {DOMElement} 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