UNPKG

geoportal-extensions-openlayers

Version:

![GitHub package.json version](https://img.shields.io/github/package-json/v/IGNF/geoportal-extensions?filename=build%2Fscripts%2Frelease%2Fpackage-openlayers.json)

1,236 lines (1,104 loc) 72.2 kB
// import CSS import "../CSS/Controls/ReverseGeocoding/GPreverseGeocodingOpenLayers.css"; // import OpenLayers import Control from "ol/control/Control"; import Overlay from "ol/Overlay"; import Collection from "ol/Collection"; import Feature from "ol/Feature"; import { Fill, Icon, Stroke, Style, Circle } from "ol/style"; import { Point, Polygon } from "ol/geom"; import { Select as SelectInteraction, Draw as DrawInteraction } from "ol/interaction"; import { pointerMove as eventPointerMove } from "ol/events/condition"; import { transform as olTransformProj } from "ol/proj"; import VectorLayer from "ol/layer/Vector"; import VectorSource from "ol/source/Vector"; // import geoportal library access import Gp from "geoportal-access-lib"; // import local import Utils from "../../Common/Utils"; import Logger from "../../Common/Utils/LoggerByDefault"; import SelectorID from "../../Common/Utils/SelectorID"; import Markers from "./Utils/Markers"; import Draggable from "../../Common/Utils/Draggable"; import Interactions from "./Utils/Interactions"; // import local with ol dependencies import LayerSwitcher from "./LayerSwitcher"; // DOM import ReverseGeocodingDOM from "../../Common/Controls/ReverseGeocodingDOM"; var logger = Logger.getLogger("reversegeocoding"); /** * @classdesc * * ReverseGeocode Control. * * @constructor * @alias ol.control.ReverseGeocode * @type {ol.control.ReverseGeocode} * @extends {ol.control.Control} * @param {Object} options - ReverseGeocode control options * @param {String} [options.apiKey] - API key for services call (reverse geocode service). The key "calcul" is used by default. * @param {String} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {Boolean} [options.collapsed = true] - Specify if widget has to be collapsed (true) or not (false) on map loading. Default is true. * @param {Boolean} [options.draggable = false] - Specify if widget is draggable * @param {Object} [options.resources = ["StreetAddress", "PositionOfInterest", "CadastralParcel"]] - resources for geocoding, by default : ["StreetAddress", "PositionOfInterest", "CadastralParcel"]. Possible values are : "StreetAddress", "PositionOfInterest", "CadastralParcel". Resources will be displayed in the same order in widget list. * @param {Object} [options.delimitations = ["Point", "Circle", "Extent"]] - delimitations for reverse geocoding, by default : ["Point", "Circle", "Extent"]. Possible values are : "Point", "Circle", "Extent". Delimitations will be displayed in the same order in widget list. * @param {Object} [options.reverseGeocodeOptions = {}] - reverse geocode service options. see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~reverseGeocode Gp.Services.reverseGeocode()} to know all reverse geocode options. * @param {Object} [options.layerDescription = {}] - Layer informations to be displayed in LayerSwitcher widget (only if a LayerSwitcher is also added to the map) * @param {String} [options.layerDescription.title = "Saisie (recherche inverse)"] - Layer title to be displayed in LayerSwitcher * @param {String} [options.layerDescription.description = "Couche de saisie d'une zone de recherche pour la recherche inverse"] - Layer description to be displayed in LayerSwitcher * @fires reversegeocode:compute * @fires reversegeocode:onclickresult * @example * var iso = ol.control.ReverseGeocode({ * "collapsed" : false, * "draggable" : true, * "resources" : ["StreetAddress", "PositionOfInterest"], * "delimitations" : ["Point", "Circle"], * "reverseGeocodeOptions" : {} * }); */ var ReverseGeocode = (function (Control) { /** * See {@link ol.control.ReverseGeocode} * @module ReverseGeocode * @alias module:~Controls/ReverseGeocode * @param {*} options - options * @example * import ReverseGeocode from "src/OpenLayers/Controls/ReverseGeocode" */ function ReverseGeocode (options) { options = options || {}; if (!(this instanceof ReverseGeocode)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } // initialisation du composant this.initialize(options); // Widget main DOM container this._container = this._initContainer(); 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) ReverseGeocode.__proto__ = Control; /** * @lends module:ReverseGeocode */ ReverseGeocode.prototype = Object.create(Control.prototype, {}); // on récupère les méthodes de la classe commune ReverseGeocodingDOM Utils.assign(ReverseGeocode.prototype, ReverseGeocodingDOM); /** * Constructor (alias) * * @private */ ReverseGeocode.prototype.constructor = ReverseGeocode; // ################################################################### // // ############## public methods (getters, setters) ################## // // ################################################################### // /** * Returns true if widget is collapsed (minimized), false otherwise * * @returns {Boolean} collapsed - true if widget is collapsed */ ReverseGeocode.prototype.getCollapsed = function () { return this.collapsed; }; /** * Collapse or display widget main container * * @param {Boolean} collapsed - True to collapse widget, False to display it */ ReverseGeocode.prototype.setCollapsed = function (collapsed) { if (collapsed === undefined) { logger.log("[ERROR] ReverseGeocode:setCollapsed - missing collapsed parameter"); return; } if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) { return; } if (collapsed) { document.getElementById("GPreverseGeocodingPanelClose-" + this._uid).click(); } else { document.getElementById("GPshowReverseGeocoding-" + this._uid).click(); } this.collapsed = collapsed; }; /** * Overwrite OpenLayers setMap method * * @param {ol.Map} map - Map. */ ReverseGeocode.prototype.setMap = function (map) { if (map) { // lors de l'ajout à la map, on active la saisie du point ou de la zone de recherche sur la carte, // mais seulement si le widget est ouvert this._activateMapInteraction(map); // mode "draggable" if (this.draggable) { Draggable.dragElement( this._panelContainer, this._panelHeaderContainer, map.getTargetElement() ); } } else { var _map = this.getMap(); // on remet à zéro = on efface les géométries + interactions + valeurs stockées // suppression des résultats précédents this._clearResults(); // on efface les points qui ont pu être saisis précédemment this._clearInputFeatures(); // on supprime l'éventuelle précédente interaction this._removeMapInteraction(_map); // on retire aussi la couche de saisie de la zone de recherche à la fermeture du widget if (this._inputFeaturesLayer != null) { _map.removeLayer(this._inputFeaturesLayer); this._inputFeaturesLayer = null; this._inputFeaturesSources = null; this._inputFeatures = null; } } // on appelle la méthode setMap originale d'OpenLayers Control.prototype.setMap.call(this, map); }; /** * Get locations data * * @returns {Object} data - locations */ ReverseGeocode.prototype.getData = function () { return this._reverseGeocodingLocations; }; // ################################################################### // // ##################### init component ############################## // // ################################################################### // /** * Initialize ReverseGeocode control (called by ReverseGeocode constructor) * * @param {Object} options - constructor options * @private */ ReverseGeocode.prototype.initialize = function (options) { // ############################################################ // // ################### Options du composant ################### // // check input options format (resources and delimitations arrays) this._checkInputOptions(options); // set default options this.options = { collapsed : true, draggable : false, resources : ["StreetAddress", "PositionOfInterest", "CadastralParcel"], delimitations : ["Point", "Circle", "Extent"], reverseGeocodeOptions : {}, layerDescription : { title : "Saisie (recherche inverse)", description : "Couche de saisie d'une zone de recherche pour la recherche inverse" } }; // merge with user options Utils.assign(this.options, options); /** {Boolean} specify if reverseGeocoding control is collapsed (true) or not (false) */ this.collapsed = this.options.collapsed; /** {Boolean} specify if reverseGeocoding control is draggable (true) or not (false) */ this.draggable = this.options.draggable; // 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(); // #################################################################### // // ################### informations sur les droits #################### // // Type de géocodage sélectionné (StreetAddress, PositionOfInterest, ...) this._currentGeocodingType = null; this._initGeocodingType(); // Type de délimitation à utiliser pour la requête + pour sélection sur la containerDistance this._currentGeocodingDelimitation = null; this._initGeocodingDelimitation(); // ################################################################## // // ################### Elements principaux du DOM ################### // // containers principaux this._showReverseGeocodingInput = null; // panel this._panelContainer = null; this._panelHeaderContainer = null; this._panelTitleContainer = null; this._returnPictoContainer = null; // form this._formContainer = null; // results this._resultsContainer = null; this._resultsListContainer = null; // waiting this._waitingContainer = null; // ###################################################################### // // ################### informations des points saisis ################### // // collection des points saisis sur la carte this._inputFeatures = null; // source contenant les features ci-dessus this._inputFeaturesSource = null; // couche vectorielle dans laquelle seront saisis les points (features ci-dessus) this._inputFeaturesLayer = null; // interaction avec la carte (de type "Point", "Circle" ou "Polygon") this._mapInteraction = null; // #################################################################### // // ################### informations pour la requête ################### // // options pour la requête de géocodage inverse this._requestOptions = null; // geometrie de recherche du géocodage inverse qui sera envoyée dans la requête this._requestGeom = null; // pour savoir si un calcul est en cours ou non this._waiting = false; // timer pour cacher la patience après un certain temps this._timer = null; // #################################################################### // // #################### informations des résultats #################### // this._reverseGeocodingLocations = []; this._reverseGeocodingLocationsMarkers = []; this._resultsDefaultStyle = new Style({ image : new Icon({ src : Markers["lightOrange"], anchor : [0.5, 1] }) }); this._resultsSelectedStyle = new Style({ image : new Icon({ src : Markers["red"], anchor : [0.5, 1] }) }); this._resultsHoverInteraction = null; this._resultsSelectInteraction = null; // container de la popup (affichage des infos au clic sur les markers) this._popupContent = null; this._popupDiv = this._initPopupDiv(); this._popupOverlay = null; }; /** * this method is called by this.initialize() * and makes sure input options are correctly formated * * @param {Object} options - options * * @private */ ReverseGeocode.prototype._checkInputOptions = function (options) { var i; var j; // on vérifie le tableau des resources if (options.resources) { var resources = options.resources; // on vérifie que la liste des ressources de geocodage est bien un tableau if (Array.isArray(resources)) { var resourcesList = ["StreetAddress", "PositionOfInterest", "CadastralParcel"]; var wrongResourcesIndexes = []; for (i = 0; i < resources.length; i++) { if (resourcesList.indexOf(resources[i]) === -1) { // si la resource n'est pas référencée, on stocke son index pour la retirer du tableau (après avoir terminé de parcourir le tableau) wrongResourcesIndexes.push(i); logger.log("[ReverseGeocode] options.resources : " + resources[i] + " is not a resource for reverse geocode"); } } // on retire les ressoures non référencées qu'on a pu rencontrer if (wrongResourcesIndexes.length !== 0) { for (j = 0; j < wrongResourcesIndexes.length; j++) { resources.splice(wrongResourcesIndexes[j], 1); } } } else { logger.log("[ReverseGeocode] 'options.resources' parameter should be an array"); resources = null; } } // et le tableau des délimitations if (options.delimitations) { var delimitations = options.delimitations; // on vérifie que la liste des delimitations est bien un tableau if (Array.isArray(delimitations)) { var delimitationsList = ["Circle", "Point", "Extent"]; var wrongDelimitationsIndexes = []; for (i = 0; i < delimitations.length; i++) { if (delimitationsList.indexOf(delimitations[i]) === -1) { // si la delimitations n'est pas référencée, on stocke son index pour la retirer du tableau (après avoir terminé de parcourir le tableau) wrongDelimitationsIndexes.push(i); logger.log("[ReverseGeocode] options.delimitations : " + delimitations[i] + " is not a delimitation for reverse geocode"); } } // on retire les ressoures non référencées qu'on a pu rencontrer if (wrongDelimitationsIndexes.length !== 0) { for (j = 0; j < wrongDelimitationsIndexes.length; j++) { delimitations.splice(wrongDelimitationsIndexes[j], 1); } } } else { logger.log("[ReverseGeocode] 'options.delimitations' parameter should be an array"); delimitations = null; } } }; /** * this method is called by this.initialize() and initialize geocoding type (=resource) * ("StreetAddress", "PositionOfInterest", "CadastralParcel") * * @private */ ReverseGeocode.prototype._initGeocodingType = function () { // Type de géocodage selectionné this._currentGeocodingType = "StreetAddress"; // par defaut // par defaut var resources = this.options.resources; if (!resources || resources.length === 0) { this.options.resources = ["StreetAddress", "PositionOfInterest", "CadastralParcel"]; } // options utilisateur if (Array.isArray(resources) && resources.length) { // récupération du type par défaut if (resources[0] === "StreetAddress" || resources[0] === "PositionOfInterest" || resources[0] === "CadastralParcel") { this._currentGeocodingType = resources[0]; } } // si l'utilisateur a spécifié au moins une ressource dans le service, on surcharge les options du widget var serviceOptions = this.options.reverseGeocodeOptions; if (serviceOptions.filterOptions && Array.isArray(serviceOptions.filterOptions.type) && serviceOptions.filterOptions.type.length !== 0) { this._currentGeocodingType = serviceOptions.filterOptions.type[0]; } }; /** * this method is called by this.initialize() and initialize geocoding delimitation * ("Point", "Circle", "Extent") * * @private */ ReverseGeocode.prototype._initGeocodingDelimitation = function () { // Type de délimitation selectionné this._currentGeocodingDelimitation = "Point"; // par defaut // par defaut var delimitations = this.options.delimitations; if (!delimitations || delimitations.length === 0) { this.options.delimitations = ["Point", "Circle", "Extent"]; } // options utilisateur if (Array.isArray(delimitations) && delimitations.length) { var d = delimitations[0].toLowerCase(); if (d === "point" || d === "circle" || d === "extent") { this._currentGeocodingDelimitation = delimitations[0]; } } }; /** * 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 */ ReverseGeocode.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"; element.appendChild(this._popupContent); element.appendChild(closer); return element; }; /** * Create control main container (DOM initialize) * * @returns {DOMElement} DOM element * * @private */ ReverseGeocode.prototype._initContainer = function () { // create main container var container = this._createMainContainerElement(); // create show ReverseGeocode element var inputShow = this._showReverseGeocodingInput = this._createShowReverseGeocodingElement(); container.appendChild(inputShow); // mode "collapsed" if (!this.collapsed) { inputShow.checked = true; } // create ReverseGeocode picto var picto = this._createShowReverseGeocodingPictoElement(); container.appendChild(picto); // panel var reverseGeocodingPanel = this._panelContainer = this._createReverseGeocodingPanelElement(); // header var panelHeader = this._panelHeaderContainer = this._createReverseGeocodingPanelHeaderElement(); // return picto (hidden at start) var returnPicto = this._returnPictoContainer = this._createReverseGeocodingPanelReturnPictoElement(); panelHeader.appendChild(returnPicto); // pane title var panelTitle = this._panelTitleContainer = this._createReverseGeocodingPanelTitleElement(); panelHeader.appendChild(panelTitle); // close picto var closeDiv = this._createReverseGeocodingPanelCloseElement(); panelHeader.appendChild(closeDiv); reverseGeocodingPanel.appendChild(panelHeader); // form var reverseGeocodingForm = this._formContainer = this._createReverseGeocodingPanelFormElement(); // choices element reverseGeocodingForm.appendChild(this._createReverseGeocodingFormModeChoiceGeocodingTypeElement(this.options.resources)); reverseGeocodingForm.appendChild(this._createReverseGeocodingFormModeChoiceGeocodingDelimitationElement(this.options.delimitations)); // submit (bouton "Chercher") var submit = this._createReverseGeocodingSubmitFormElement(); reverseGeocodingForm.appendChild(submit); reverseGeocodingPanel.appendChild(reverseGeocodingForm); // waiting var waiting = this._waitingContainer = this._createReverseGeocodingWaitingElement(); reverseGeocodingPanel.appendChild(waiting); // results (dans le panel) var resultsPanel = this._resultsContainer = this._createReverseGeocodingResultsPanelElement(); var reverseGeocodingResultsList = this._resultsListContainer = this._createReverseGeocodingResultsListElement(); resultsPanel.appendChild(reverseGeocodingResultsList); reverseGeocodingPanel.appendChild(resultsPanel); container.appendChild(reverseGeocodingPanel); logger.log(container); return container; }; // ################################################################### // // ################### Map interactions management ################### // // ################################################################### // /** * this method is called by this.setMap, * or by this.onShowReverseGeocodingClick, * and calls method corresponding to current delimitation, if widget is not collapsed. * * @param {ol.Map} map - control map. * @private */ ReverseGeocode.prototype._activateMapInteraction = function (map) { if (!this.collapsed) { // 1. Creation de la couche vectorielle sur laquelle on va dessiner if (this._inputFeaturesLayer == null) { // on crée une collection, qui accueillera les points saisis sur la carte par les interactions, // sous formes de features (dans une couche vectorielle). // on les stocke de sorte à pouvoir les supprimer facilement this._inputFeatures = new Collection(); // on crée la couche qui va accueillir les features this._inputFeaturesSource = new VectorSource({ features : this._inputFeatures }); this._inputFeaturesLayer = new VectorLayer({ source : this._inputFeaturesSource, style : new Style({ fill : new Fill({ color : "rgba(0, 183, 152, 0.3)" }), stroke : new Stroke({ color : "rgba(0, 183, 152, 0.8)", width : 3 }), image : new Icon({ src : Markers["turquoiseBlue"], anchor : [0.5, 1] }) }) }); // on rajoute le champ gpResultLayerId permettant d'identifier une couche crée par le composant. (pour layerSwitcher par ex) this._inputFeaturesLayer.gpResultLayerId = "reverseGeocoding"; // on ajoute la couche à la carte map.addLayer(this._inputFeaturesLayer); } // 2. Création de l'interaction de dessin, selon le type de délimitation sélectionné var delimitation = this._currentGeocodingDelimitation.toLowerCase(); switch (delimitation) { case "point": this._activatePointInteraction(map); break; case "circle": this._activateCircleInteraction(map); break; case "extent": this._activateBoxInteraction(map); break; default: break; } // 3. Si un layer switcher est présent dans la carte, on lui affecte des informations pour cette couche map.getControls().forEach( (control) => { if (control instanceof LayerSwitcher) { // un layer switcher est présent dans la carte var layerId = this._inputFeaturesLayer.gpLayerId; // on n'ajoute des informations que s'il n'y en a pas déjà (si le titre est le numéro par défaut) if (control._layers[layerId].title === layerId) { control.addLayer( this._inputFeaturesLayer, { title : this.options.layerDescription.title, description : this.options.layerDescription.description } ); control.setRemovable(this._inputFeaturesLayer, false); } } } ); } }; /** * this method is called by this._activateMapInteraction, * and creates map point drawing interaction. * * @param {ol.Map} map - control map. * @private */ ReverseGeocode.prototype._activatePointInteraction = function (map) { // interaction permettant de dessiner un point this._mapInteraction = new DrawInteraction({ style : new Style({ image : new Circle({ radius : 0, fill : new Fill({ color : "rgba(0, 183, 152, 0.8)" }) }) }), type : ("Point"), source : this._inputFeaturesSource }); this._mapInteraction.on( "drawstart", (e) => { logger.log("on drawstart ", e); // on efface les points qui ont pu être saisis précédemment (on vide la collection des features de la couche) this._inputFeatures.clear(); // on récupère les coordonnées du point qui vient d'être saisi this._onDrawStart(e, "point"); } ); this._mapInteraction.on( "drawend", (e) => { logger.log("on drawend", e); // on récupère le rayon du cercle qui vient d'être tracé if (e.feature && e.feature.getGeometry) { this._requestGeom = { type : "Point", coordinates : [ this._requestPosition.lon, this._requestPosition.lat ] }; } } ); map.addInteraction(this._mapInteraction); this._setCursor("crosshair", map); }; /** * this method is called by this._activateMapInteraction, * and creates map circle drawing interaction. * * @param {ol.Map} map - control map. * @private */ ReverseGeocode.prototype._activateCircleInteraction = function (map) { // interaction permettant de dessiner un cercle this._mapInteraction = new DrawInteraction({ style : new Style({ fill : new Fill({ color : "rgba(0, 183, 152, 0.3)" }), stroke : new Stroke({ color : "rgba(0, 183, 152, 0.8)", width : 3 }), image : new Circle({ radius : 4, fill : new Fill({ color : "rgba(0, 183, 152, 0.8)" }) }) }), type : ("Circle"), source : this._inputFeaturesSource }); this._mapInteraction.on( "drawstart", (e) => { logger.log("on drawstart ", e); // on efface les points qui ont pu être saisis précédemment (on vide la collection des features de la couche) this._inputFeatures.clear(); // on récupère les coordonnées du centre du cercle = premier point du tracé this._onDrawStart(e, "circle"); } ); this._mapInteraction.on( "drawend", (e) => { logger.log("on drawend", e); // on récupère le rayon du cercle qui vient d'être tracé if (e.feature && e.feature.getGeometry) { var radius = e.feature.getGeometry().getRadius(); // et on le stocke comme filtre pour la requête this._requestGeom = {}; this._requestGeom.type = "Circle"; this._requestGeom.radius = radius; if (this._requestPosition) { this._requestGeom.coordinates = [ this._requestPosition.lon, this._requestPosition.lat ]; } logger.log("circle radius : ", radius); } } ); map.addInteraction(this._mapInteraction); }; /** * this method is called by this._activateMapInteraction, * and creates map box drawing interaction. * * @param {ol.Map} map - control map. * @private */ ReverseGeocode.prototype._activateBoxInteraction = function (map) { // info : il n'y a pas de geometry de type rectangle, donc on va créer un objet de type "LineString", // avec seulement 2 points qui formeront les extrémités du rectangle. // on aura donc une géométrie LineString avec 5 coordonnées : start, point2, end, point4, start, // où les coordonnées de point2 et point4 sont calculées à partir de start et end, et start est répété à la fin pour fermer la géométrie. // function to draw rectangle with only 2 points var geometryFunction = function (coordinates, geometry) { if (!geometry) { geometry = new Polygon([]); } var start = coordinates[0]; var end = coordinates[1]; // on crée les 5 coordonnées de la ligne à partir des 2 points saisis. geometry.setCoordinates([ [start, [start[0], end[1]], end, [end[0], start[1]], start] ]); return geometry; }; // interaction permettant de dessiner un rectangle (= LineString de 5 points, à partir de 2 points saisis) this._mapInteraction = new DrawInteraction({ style : new Style({ fill : new Fill({ color : "rgba(0, 183, 152, 0.3)" }), stroke : new Stroke({ color : "rgba(0, 183, 152, 0.8)", width : 3 }), image : new Circle({ radius : 4, fill : new Fill({ color : "rgba(0, 183, 152, 0.8)" }) }) }), type : ("LineString"), source : this._inputFeaturesSource, maxPoints : 2, geometryFunction : geometryFunction }); this._mapInteraction.on( "drawstart", (e) => { logger.log("on drawstart", e); // on efface les points qui ont pu être saisis précédemment (on vide la collection des features de la couche) this._inputFeatures.clear(); } ); this._mapInteraction.on( "drawend", (e) => { logger.log("on drawend", e); // on va récupérer les coordonnées du rectangle qui vient d'être tracé this._onBoxDrawEnd(e); } ); map.addInteraction(this._mapInteraction); }; /** * remove draw interaction from map (if exists) * * @param {ol.Map} map - control map. * @private */ ReverseGeocode.prototype._removeMapInteraction = function (map) { if (this._mapInteraction != null) { map.removeInteraction(this._mapInteraction); this._mapInteraction = null; } this._setCursor(); }; /** * this method is called by event 'drawstart' on map point or circle drawing interaction * (cf. this._activatePointInteraction), and it gets map click coordinates. * this point is saved as a parameter for reverse Geocode service. * * @param {Object} e - HTMLElement * @param {String} type - geometry type : "point" or "circle" * @private */ ReverseGeocode.prototype._onDrawStart = function (e, type) { var coordinate; if (e.feature && e.feature.getGeometry) { var geometry = e.feature.getGeometry(); if (type === "point") { coordinate = geometry.getCoordinates(); } if (type === "circle") { coordinate = geometry.getCenter(); } } if (!coordinate) { return; } var crs; if (this.options.reverseGeocodeOptions && this.options.reverseGeocodeOptions.srs) { crs = this.options.reverseGeocodeOptions.srs; } else { var map = this.getMap(); if (!map || !map.getView()) { return; } crs = map.getView().getProjection(); } var geoCoordinate = olTransformProj(coordinate, crs, "EPSG:4326"); this._requestPosition = { lon : geoCoordinate[0], lat : geoCoordinate[1] }; logger.log("position coordinates : ", this._requestPosition); }; /** * this method is called by event 'drawend' on map box drawing interaction * (cf. this._activateBoxInteraction), and it gets geometry coordinates, * to be saved as a filter parameter for reverse Geocode service. * * @param {Object} e - HTMLElement * @private */ ReverseGeocode.prototype._onBoxDrawEnd = function (e) { // on va récupérer les coordonnées du rectangle qui vient d'être tracé if (e.feature && e.feature.getGeometry) { // info: coordinates est un tableau [start, point2, end, point4, start] // car c'est une linestring donc on a 5 coordonnées pour boucler var coordinates = e.feature.getGeometry().getCoordinates()[0]; var start = coordinates[0]; var end = coordinates[2]; var crs; if (this.options.reverseGeocodeOptions && this.options.reverseGeocodeOptions.srs) { crs = this.options.reverseGeocodeOptions.srs; } else { var map = this.getMap(); if (!map || !map.getView()) { return; } crs = map.getView().getProjection(); } // on reprojette les coordonnées des deux extrémités du rectangle (start et end) var startGeoCoordinate = olTransformProj(start, crs, "EPSG:4326"); var endGeoCoordinate = olTransformProj(end, crs, "EPSG:4326"); var bbox = {}; // on récupère les valeurs left, right, top et bottom, pour le filtre bbox du service reverseGeocode if (startGeoCoordinate[0] < endGeoCoordinate[0]) { bbox.left = startGeoCoordinate[0]; bbox.right = endGeoCoordinate[0]; } else { bbox.left = endGeoCoordinate[0]; bbox.right = startGeoCoordinate[0]; } if (startGeoCoordinate[1] < endGeoCoordinate[1]) { bbox.bottom = startGeoCoordinate[1]; bbox.top = endGeoCoordinate[1]; } else { bbox.bottom = endGeoCoordinate[1]; bbox.top = startGeoCoordinate[1]; } this._requestGeom = { type : "Polygon", coordinates : [[ [bbox.left, bbox.top], [bbox.left, bbox.bottom], [bbox.right, bbox.bottom], [bbox.right, bbox.top], [bbox.left, bbox.top] ]] }; logger.log("searchGeometry filter : ", this._requestGeom); } }; /** * this change the cursor of the map when entering a point. * * @param {String} cursor - cursor style * @param {ol.Map} map - control map (optional) * @private */ ReverseGeocode.prototype._setCursor = function (cursor, map) { map = map || this.getMap(); if (!map) { return; } var div = map.getTargetElement(); if (cursor) { div.style.cursor = cursor; } else { div.style.cursor = null; } }; // ################################################################### // // ##################### Reverse Geocoding request ################### // // ################################################################### // /** * this methode is called by this.onReverseGeocodingSubmit method, * it generates and sends reverse geocode request, then displays results * * @private */ ReverseGeocode.prototype._reverseGeocodingRequest = function () { var map = this.getMap(); // on construit les options pour la requête this._requestOptions = this._getReverseGeocodingRequestOptions(); // retrait de l'interaction sur la map pendant l'attente (et l'affichage des résultats) this._removeMapInteraction(map); // affichage d'une patience pendant l'attente this._displayWaitingContainer(); // envoi de la requête Gp.Services.reverseGeocode(this._requestOptions); }; /** * this methode is called by this._reverseGeocodingRequest method, * and returns options object for Gp.Services.reverseGeocode request * * @returns {Object} requestOptions - reverse geocode options * @private */ ReverseGeocode.prototype._getReverseGeocodingRequestOptions = function () { var map = this.getMap(); // on recupere les éventuelles options du service passées par l'utilisateur var reverseGeocodeOptions = this.options.reverseGeocodeOptions; // on crée les options pour le service reverseGeocode var context = this; if (typeof this.options.ssl !== "boolean") { this.options.ssl = true; } // gestion des callback var bOnFailure = !!(reverseGeocodeOptions.onFailure !== null && typeof reverseGeocodeOptions.onFailure === "function"); // cast variable to boolean var bOnSuccess = !!(reverseGeocodeOptions.onSuccess !== null && typeof reverseGeocodeOptions.onSuccess === "function"); var requestOptions = { apiKey : reverseGeocodeOptions.apiKey || this.options.apiKey, ssl : this.options.ssl, position : this._requestPosition, filterOptions : { type : [this._currentGeocodingType] }, srs : "CRS:84", returnFreeForm : false, maximumResponses : reverseGeocodeOptions.maximumResponses || 18, timeOut : reverseGeocodeOptions.timeOut || 30000, // protocol : reverseGeocodeOptions.protocol || "XHR", // callback onSuccess onSuccess : function (response) { if (response.locations) { logger.log("reverseGeocode results : ", response.locations); context._displayGeocodedLocations(response.locations); } if (bOnSuccess) { reverseGeocodeOptions.onSuccess.call(context, response.locations); } }, // callback onFailure onFailure : function (error) { // FIXME mise à jour du controle mais le service ne repond pas en 200 !? // on cache la patience context._hideWaitingContainer(); // suppression d'éventuels résultats précédents context._clearResults(); // on efface les points qui ont été saisis précédemment context._clearInputFeatures(); // et on réactive l'interaction sur la map context._activateMapInteraction(map); logger.log(error.message); if (bOnFailure) { reverseGeocodeOptions.onFailure.call(context, error); } } }; // on récupère d'éventuels filtres if (this._requestGeom.type.toLowerCase() === "circle") { // FIXME : a confirmer en fonction du service ! if (this._requestGeom.radius > 500) { logger.log("INFO : initial circle radius (" + this._requestGeom.radius + ") limited to 1000m."); this._requestGeom.radius = 500; } requestOptions.searchGeometry = this._requestGeom; } else if (this._requestGeom.type.toLowerCase() === "polygon") { requestOptions.searchGeometry = this._requestGeom; } else if (this._requestGeom.type.toLowerCase() === "point") { if (this._currentGeocodingType === "StreetAddress") { requestOptions.searchGeometry = { type : "Circle", radius : 50, coordinates : this._requestGeom.coordinates }; requestOptions.maximumResponses = 1; } else { requestOptions.searchGeometry = this._requestGeom; } } logger.log("reverseGeocode request options : ", requestOptions); return requestOptions; }; /** * this method is called by this._reverseGeocodingRequest() (in case of reverse geocode success) * and display results : in both container list and map * * @param {Array} locations - array of geocoded locations (reverse geocode results) * @private */ ReverseGeocode.prototype._displayGeocodedLocations = function (locations) { // 1. on vide les résultats précédents this._clearResults(); this._reverseGeocodingLocations = locations; /** * event triggered when the compute is finished * * @event reversegeocode:compute * @property {Object} type - event * @property {Object} target - instance ReverseGeocode * @example * ReverseGeocode.on("reversegeocode:compute", function (e) { * console.log(e.target.getData()); * }) */ this.dispatchEvent({ type : "reversegeocode:compute" }); // 2. cache de la patience et du formulaire this._formContainer.className = "GPreverseGeocodingComponentHidden"; this._hideWaitingContainer(); // affichage de la div des résultats (et changement du titre) this._panelTitleContainer.innerHTML = "Résultats de la recherche"; this._returnPictoContainer.className = ""; this._resultsContainer.className = "GPpanel"; // 3. ajout de la liste des résultats dans le container des resultats this._fillGeocodedLocationListContainer(locations); // 4. affichage des résultats sur la carte (+ zoom ?) this._displayGeocodedLocationsOnMap(locations); }; // ################################################################### // // ############################# results list ######################## // // ################################################################### // /** * this method is called by this._displayGeocodedLocations() * and fills the container with results list * * @param {Array} locations - array of geocoded locations (reverse geocode results) * @private */ ReverseGeocode.prototype._fillGeocodedLocationListContainer = function (locations) { // ajout de la liste des résultats dans le container des resultats for (var i = 0; i < locations.length; i++) { var location = locations[i]; logger.log(location); // on récupère la description à afficher dans la liste var locationDescription = this._fillGeocodedLocationDescription(location); // on ajoute chaque résutat à la liste if (locationDescription.length !== 0) { this._createReverseGeocodingResultElement(locationDescription, i); } } }; /** * this method is called by this._fillGeocodedLocationListContainer() * and fills location description (String), depending on matchType * * @param {Object} location - geocoded location (from reverse geocode results) * @returns {String} locationDescription - geocoded location description to be displayed * @private */ ReverseGeocode.prototype._fillGeocodedLocationDescription = function (location) { if (!location || !location.placeAttributes) { return; } var attr = location.placeAttributes; var locationDescription = ""; // on sélectionne les infos à afficher selon le type switch (location.type) { case "StreetAddress": if (attr.street) { locationDescription += attr.housenumber ? attr.housenumber + " " : ""; locationDescription += attr.street + ", "; } locationDescription += attr.postcode + " " + attr.city; break; case "PositionOfInterest": locationDescription += attr.toponym; if (attr.postcode.length === 1) { locationDescription += ", " + attr.postcode[0]; } locationDescription += " (" + attr.category.join(",") + ")"; break; case "CadastralParcel": locationDescription += attr.id; locationDescription += attr.city ? " (" + attr.city + ")" : ""; break; default: locationDescription += attr.city ? attr.city : ""; break; }; return locationDescription; }; // ################################################################### // // ######################## map results (markers) #################### // // ################################################################### // /** * this method is called by this._displayGeocodedLocations() * and display locations in map (markers) * * @param {Object} locations - geocoded locations (reverse geocode result) * @private */ ReverseGeocode.prototype._displayGeocodedLocationsOnMap = function (locations) { if (this._reverseGeocodingLocations.length !== 0) { var map = this.getMap(); // 1. création de la couche où seront ajoutés les résultats this._createResultsLayer(); // ajout de chaque résultat à la couche (marker) for (var i = 0; i < locations.length; i++) { this._addResultFeature(locations[i], i); } // 2. Zoom sur l'étendue des résultats (features) if (this._resultsFeatures.getLength() > 1) { if (this._resultsFeaturesSource && this._resultsFeaturesSource.getExtent) { var extent = this._resultsFeaturesSource.getExtent(); map.getView().fit(extent, map.getSize()); } } else { // dans le cas où on n'a qu'un seul résultat, l'étendue n'est pas définie, on zoome donc sur le résula