/* Copyright (c) 2017 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import {transform as ol_proj_transform} from 'ol/proj.js' import {transformExtent as ol_proj_transformExtent} from 'ol/proj.js' import ol_control_Search from './Search.js' import ol_control_SearchJSON from './SearchJSON.js' /** * Search places using the French National Base Address (BAN) API. * * @constructor * @extends {ol.control.SearchJSON} * @fires select * @param {any} options extend ol.control.SearchJSON options * @param {string} options.className control class name * @param {string | undefined} [options.apiKey] the service api key. * @param {string | undefined} [options.version] API version 1 or 2 or geoplateforme (latest), default latest * @param {string | undefined} options.authentication: basic authentication for the service API as btoa("login:pwd") * @param {Element | string | undefined} options.target Specify a target if you want the control to be rendered outside of the map's viewport. * @param {string | undefined} options.label Text label to use for the search button, default "search" * @param {boolean | undefined} options.reverse enable reverse geocoding, default false * @param {string | undefined} options.placeholder placeholder, default "Search..." * @param {number | undefined} options.typing a delay on each typing to start searching (ms), default 500. * @param {integer | undefined} options.minLength minimum length to start searching, default 3 * @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10 * @param {StreetAddress|PositionOfInterest|CadastralParcel|Commune} [options.type] type of search. Using Commune will return the INSEE code, default StreetAddress,PositionOfInterest * @param {string} [options.terr] territory METROPOLE|DOMTOM|dep code * @param {boolean} [options.position] Search, with priority to geo position (map center), default false * @param {ol.extent} [options.bbox] if set search inside the bbox (in map projection) * @param {boolean} [options.useExtent] returns candidates inside the current map extent, default false * @see {@link https://geoservices.ign.fr/documentation/geoservices/geocodage.html} * @see {@link https://geoservices.ign.fr/documentation/services/services-deprecies/itineraires-deprecies/autocompletion-rest} * @see {@link https://geoservices.ign.fr/documentation/services/api-et-services-ogc/geocodage-beta-20/documentation-technique-de-lapi} */ var ol_control_SearchGeoportail = class olcontrolSearchGeoportail extends ol_control_SearchJSON { constructor(options) { options = options || {}; options.className = options.className || 'IGNF'; options.typing = options.typing || 500; if (options.version == 1) { options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/ols/apis/completion'; options.copy = '© IGN-Géoportail'; } else if (options.version == 2) { options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/geoportail/geocodage/rest/0.1/completion'; options.copy = '© IGN-Géoportail'; } else { options.url = 'https://data.geopf.fr/geocodage/completion'; options.copy = '© IGN-Géoplateforme'; } super(options); this.set('position', options.position); this.set('useExtent', options.useExtent); this.set('bbox', options.bbox) this.set('type', options.type || 'StreetAddress,PositionOfInterest'); this.set('terr', options.terr); this.set('timeout', options.timeout || 2000); // Authentication // this._auth = options.authentication; } /** Reverse geocode * @param {ol.coordinate} coord * @param {function|*} options callback function called when revers located or options passed to the select event * @api */ reverseGeocode(coord, options) { var lonlat = ol_proj_transform(coord, this.getMap().getView().getProjection(), 'EPSG:4326'); this._handleSelect({ x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }, true, options); // Search type var type = this.get('type') === 'Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress'; if (/,/.test(type)) type = 'StreetAddress'; // Search url var url = this.get('url').replace('ols/apis/completion', 'geoportail/ols').replace('completion', 'reverse'); if (/ols/.test(url)) { // request var request = '' + '' + ' ' + ' ' + ' ' + type + '' + ' ' + ' ' + lonlat[1] + ' ' + lonlat[0] + '' + ' ' + ' ' + ' ' + ''; this.ajax(url, { xls: request }, function (xml) { var f = {}; if (!xml) { f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }; } else { xml = xml.replace(/\n|\r/g, ''); var p = (xml.replace(/.*(.*)<\/gml:pos>.*/, "$1")).split(' '); if (!Number(p[1]) && !Number(p[0])) { f = { x: lonlat[0], y: lonlat[1], fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }; } else { f.x = lonlat[0]; f.y = lonlat[1]; f.city = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.insee = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.zipcode = (xml.replace(/.*([^<]*)<\/PostalCode>.*/, "$1")); if (//.test(xml)) { f.kind = ''; f.country = 'StreetAddress'; f.street = (xml.replace(/.*([^<]*)<\/Street>.*/, "$1")); var number = (xml.replace(/.*([^<]*)<\/Place>.*/, "$1")); f.country = 'PositionOfInterest'; f.street = ''; f.fulltext = f.zipcode + ' ' + f.city; } } } if (typeof (options) === 'function') { options.call(this, [f]); } else { this.getHistory().shift(); this._handleSelect(f, true, options); // this.setInput('', true); // this.drawList_(); } }.bind(this), { timeout: this.get('timeout'), dataType: 'XML' }); } else { this.ajax(url + '?lon='+lonlat[0] + '&lat=' + lonlat[1], {}, function(resp) { var f; try { resp = JSON.parse(resp).features[0]; f = resp.properties; // lonlat f.x = resp.geometry.coordinates[0]; f.y = resp.geometry.coordinates[1]; f.click = lonlat; // Fulltext if (f.name) { f.fulltext = f.name + ', ' + f.postcode + ' ' + f.city; } else { f.fulltext = f.postcode + ' ' + f.city; } } catch(e) { f = { x: lonlat[0], y: lonlat[1], lonlat: lonlat, fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6) }; } if (typeof (options) === 'function') { options.call(this, [f]); } else { this.getHistory().shift(); this._handleSelect(f, true, options); // this.setInput('', true); // this.drawList_(); } }.bind(this), { timeout: this.get('timeout'), dataType: 'XML' } ); } } /** Returns the text to be displayed in the menu * @param {ol.Feature} f the feature * @return {string} the text to be displayed in the index * @api */ getTitle(f) { return (f.fulltext); } /** * @param {string} s the search string * @return {Object} request data (as key:value) * @api */ requestData(s) { var rdata = { text: s, type: this.get('type') === 'Commune' ? 'PositionOfInterest' : this.get('type') || 'StreetAddress,PositionOfInterest', terr: this.get('terr') || undefined, maximumResponses: this.get('maxItems') }; if (this.get('type') === 'Commune') rdata.poiType = 'commune'; if (this.get('position')) { var center = this.getMap().getView().getCenter() rdata.lonlat = ol_proj_transform(center, this.getMap().getView().getProjection(), 'EPSG:4326').join(','); } if (this.get('bbox')) { rdata.bbox = ol_proj_transformExtent(this.get('bbox'), this.getMap().getView().getProjection(), 'EPSG:4326').join(',') } else if (this.get('useExtent')) { var bbox = this.getMap().getView().calculateExtent() rdata.bbox = ol_proj_transformExtent(bbox, this.getMap().getView().getProjection(), 'EPSG:4326').join(',') } return rdata; } /** * Handle server response to pass the features array to the display list * @param {any} response server response * @return {Array} an array of feature * @api */ handleResponse(response) { return response.results; } /** A ligne has been clicked in the menu > dispatch event * @param {any} f the feature, as passed in the autocomplete * @param {boolean} reverse true if reverse geocode * @param {ol.coordinate} coord * @param {*} options options passed to the event * @api */ select(f, reverse, coord, options) { if (f.x || f.y) { var c = [Number(f.x), Number(f.y)]; // Add coordinate to the event try { c = ol_proj_transform(c, 'EPSG:4326', this.getMap().getView().getProjection()); } catch (e) { /* ok */ } // Get insee commune ? if (this.get('type') === 'Commune') { this.searchCommune(f, function () { ol_control_Search.prototype.select.call(this, f, reverse, c, options); }.bind(this)); } else { super.select(f, reverse, c, options); } } else { this.searchCommune(f); } } /** Search if no position and get the INSEE code * @param {string} s le nom de la commune */ searchCommune(f, cback) { // Search url var url = this.get('url').replace('ols/apis/completion', 'geoportail/ols').replace('completion', 'reverse'); if (/ols/.test(url)) { var request = '' + '' + '' + '' + '' + '
' + '' + f.zipcode + ' ' + f.city + '+' + '
' + '
' + '
' + '
'; // Search this.ajax(this.get('url').replace('ols/apis/completion', 'geoportail/ols'), { 'xls': request }, function (xml) { if (xml) { // XML to JSON var parser = new DOMParser(); var xmlDoc = parser.parseFromString(xml, "text/xml"); var com = xmlDoc.getElementsByTagName('GeocodedAddress')[0]; var coord = com.getElementsByTagName('gml:Point')[0].textContent.trim().split(' '); f.x = Number(coord[1]); f.y = Number(coord[0]); var place = com.getElementsByTagName('Place'); for (var i = 0; i < place.length; i++) { switch (place[i].attributes.type.value) { case 'Nature': f.kind = place[i].textContent; break; case 'INSEE': f.insee = place[i].textContent; break; } } if (f.x || f.y) { if (cback) cback.call(this, [f]); else this._handleSelect(f); } } }.bind(this), { dataType: 'XML' } ); } else { this.ajax(url + '?lon=' + f.x + '&lat=' + f.y + '&index=parcel&limit=1', {}, function (resp) { try { var r = JSON.parse(resp).features[0]; f.insee = r.properties.departmentcode + r.properties.municipalitycode f.districtcode = r.properties.districtcode // f.insee = r.properties.citycode if (cback) { cback.call(this, [f]); } else { this._handleSelect(f); } } catch(e) { /* ok */ } }.bind(this), { timeout: this.get('timeout'), dataType: 'XML' } ) } } } export default ol_control_SearchGeoportail