325 lines
14 KiB
JavaScript
325 lines
14 KiB
JavaScript
/* 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 = '<a href="https://www.geoportail.gouv.fr/" target="new">© IGN-Géoportail</a>';
|
|
} else if (options.version == 2) {
|
|
options.url = 'https://wxs.ign.fr/' + (options.apiKey || 'essentiels') + '/geoportail/geocodage/rest/0.1/completion';
|
|
options.copy = '<a href="https://www.geoportail.gouv.fr/" target="new">© IGN-Géoportail</a>';
|
|
} else {
|
|
options.url = 'https://data.geopf.fr/geocodage/completion';
|
|
options.copy = '<a href="https://geoservices.ign.fr/" target="new">© IGN-Géoplateforme</a>';
|
|
}
|
|
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 = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
+ '<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">'
|
|
+ ' <Request requestID="1" version="1.2" methodName="ReverseGeocodeRequest" maximumResponses="1" >'
|
|
+ ' <ReverseGeocodeRequest>'
|
|
+ ' <ReverseGeocodePreference>' + type + '</ReverseGeocodePreference>'
|
|
+ ' <Position>'
|
|
+ ' <gml:Point><gml:pos>' + lonlat[1] + ' ' + lonlat[0] + '</gml:pos></gml:Point>'
|
|
+ ' </Position>'
|
|
+ ' </ReverseGeocodeRequest>'
|
|
+ ' </Request>'
|
|
+ '</XLS>';
|
|
|
|
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>(.*)<\/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 type="Municipality">([^<]*)<\/Place>.*/, "$1"));
|
|
f.insee = (xml.replace(/.*<Place type="INSEE">([^<]*)<\/Place>.*/, "$1"));
|
|
f.zipcode = (xml.replace(/.*<PostalCode>([^<]*)<\/PostalCode>.*/, "$1"));
|
|
if (/<Street>/.test(xml)) {
|
|
f.kind = '';
|
|
f.country = 'StreetAddress';
|
|
f.street = (xml.replace(/.*<Street>([^<]*)<\/Street>.*/, "$1"));
|
|
var number = (xml.replace(/.*<Building number="([^"]*).*/, "$1"));
|
|
f.fulltext = number + ' ' + f.street + ', ' + f.zipcode + ' ' + f.city;
|
|
} else {
|
|
f.kind = (xml.replace(/.*<Place type="Nature">([^<]*)<\/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<any>} 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 = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
+ '<XLS xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.opengis.net/xls" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.2/olsAll.xsd">'
|
|
+ '<RequestHeader/>'
|
|
+ '<Request requestID="1" version="1.2" methodName="LocationUtilityService">'
|
|
+ '<GeocodeRequest returnFreeForm="false">'
|
|
+ '<Address countryCode="PositionOfInterest">'
|
|
+ '<freeFormAddress>' + f.zipcode + ' ' + f.city + '+</freeFormAddress>'
|
|
+ '</Address>'
|
|
+ '</GeocodeRequest>'
|
|
+ '</Request>'
|
|
+ '</XLS>';
|
|
|
|
// 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
|