/* 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