kmap-ui
Version:
A components of zmap base on vue2.X
497 lines (443 loc) • 17.1 kB
JavaScript
/* Copyright (c) 2018 Jean-Marc VIGLINO,
released under the CeCILL-B license (French BSD license)
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
import ol_ext_inherits from '../util/ext'
import ol_control_Control from 'ol/control/Control'
import ol_geom_LineString from 'ol/geom/LineString';
import ol_Feature from 'ol/Feature'
import ol_ext_element from '../util/element';
import ol_control_SearchGeoportail from './SearchGeoportail'
import ol_source_Vector from 'ol/source/Vector'
import ol_geom_Point from 'ol/geom/Point'
import {transform as ol_proj_transform} from 'ol/proj'
/**
* Geoportail routing Control.
* @constructor
* @extends {ol_control_Control}
* @fires select
* @fires change:input
* @param {Object=} options
* @param {string} options.className control class name
* @param {string | undefined} options.apiKey the service api key.
* @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 {string | undefined} options.placeholder placeholder, default "Search..."
* @param {string | undefined} options.inputLabel label for the input, default none
* @param {string | undefined} options.noCollapse prevent collapsing on input blur, default false
* @param {number | undefined} options.typing a delay on each typing to start searching (ms) use -1 to prevent autocompletion, default 300.
* @param {integer | undefined} options.minLength minimum length to start searching, default 1
* @param {integer | undefined} options.maxItems maximum number of items to display in the autocomplete list, default 10
* @param {integer | undefined} options.maxHistory maximum number of items to display in history. Set -1 if you don't want history, default maxItems
* @param {function} options.getTitle a function that takes a feature and return the name to display in the index.
* @param {function} options.autocomplete a function that take a search string and callback function to send an array
* @param {number} options.timeout default 10s
*/
var ol_control_RoutingGeoportail = function(options) {
var self = this;
if (!options) options = {};
if (options.typing == undefined) options.typing = 300;
// Class name for history
this._classname = options.className || 'search';
this._source = new ol_source_Vector();
// Authentication
this._auth = options.authentication;
var element = document.createElement("DIV");
var classNames = (options.className||"")+ " ol-routing";
if (!options.target) {
classNames += " ol-unselectable ol-control";
}
element.setAttribute('class', classNames);
if (!options.target) {
var bt = ol_ext_element.create('BUTTON', { parent: element })
bt.addEventListener('click', function(){
element.classList.toggle('ol-collapsed');
});
}
ol_control_Control.call(this, {
element: element,
target: options.target
});
this.set('url', 'https://wxs.ign.fr/'+options.apiKey+'/itineraire/rest/route.json');
var content = ol_ext_element.create('DIV', { className: 'content', parent: element } )
var listElt = ol_ext_element.create('DIV', { className: 'search-input', parent: content });
this._search = [];
this.addSearch(listElt, options);
this.addSearch(listElt, options);
ol_ext_element.create('I', { className: 'ol-car', title: options.carlabel||'by car', parent: content })
.addEventListener("click", function() {
self.setMode('car');
});
ol_ext_element.create('I', { className: 'ol-pedestrian', title: options.pedlabel||'pedestrian', parent: content })
.addEventListener("click", function() {
self.setMode('pedestrian');
});
ol_ext_element.create('I', { className: 'ol-ok', title: options.runlabel||'search', html:'OK', parent: content })
.addEventListener("click", function() {
self.calculate();
});
ol_ext_element.create('I', { className: 'ol-cancel', html:'cancel', parent: content })
.addEventListener("click", function() {
this.resultElement.innerHTML = '';
}.bind(this));
this.resultElement = document.createElement("DIV");
this.resultElement.setAttribute('class', 'ol-result');
element.appendChild(this.resultElement);
this.setMode(options.mode || 'car');
this.set('timeout', options.timeout || 10000);
};
ol_ext_inherits(ol_control_RoutingGeoportail, ol_control_Control);
ol_control_RoutingGeoportail.prototype.setMode = function (mode, silent) {
this.set('mode', mode);
this.element.querySelector(".ol-car").classList.remove("selected");
this.element.querySelector(".ol-pedestrian").classList.remove("selected");
this.element.querySelector(".ol-"+mode).classList.add("selected");
if (!silent) this.calculate();
};
ol_control_RoutingGeoportail.prototype.setMethod = function (method, silent) {
this.set('method', method);
if (!silent) this.calculate();
};
ol_control_RoutingGeoportail.prototype.addButton = function (className, title, info) {
var bt = document.createElement("I");
bt.setAttribute("class", className);
bt.setAttribute("type", "button");
bt.setAttribute("title", title);
bt.innerHTML = info||'';
this.element.appendChild(bt);
return bt;
};
/** Get point source
* @return {ol.source.Vector }
*/
ol_control_RoutingGeoportail.prototype.getSource = function () {
return this._source;
};
ol_control_RoutingGeoportail.prototype._resetArray = function (element) {
this._search = [];
var q = element.parentNode.querySelectorAll('.search-input > div')
q.forEach(function(d) {
if (d.olsearch) {
if (d.olsearch.get('feature')) {
d.olsearch.get('feature').set('step', this._search.length);
if (this._search.length===0) d.olsearch.get('feature').set('pos', 'start');
else if (this._search.length === q.length-1) d.olsearch.get('feature').set('pos', 'end');
else d.olsearch.get('feature').set('pos', '');
}
this._search.push(d.olsearch);
}
}.bind(this));
};
/** Remove a new search input
* @private
*/
ol_control_RoutingGeoportail.prototype.removeSearch = function (element, options, after) {
element.removeChild(after);
if (after.olsearch.get('feature')) this._source.removeFeature(after.olsearch.get('feature'));
if (this.getMap()) this.getMap().removeControl(after.olsearch);
this._resetArray(element);
};
/** Add a new search input
* @private
*/
ol_control_RoutingGeoportail.prototype.addSearch = function (element, options, after) {
var self = this;
var div = ol_ext_element.create('DIV');
if (after) element.insertBefore(div, after.nextSibling);
else element.appendChild(div);
ol_ext_element.create ('BUTTON', { title: options.startlabel||"add/remove", parent: div})
.addEventListener('click', function(e) {
if (e.ctrlKey) {
if (this._search.length>2) this.removeSearch(element, options, div);
} else if (e.shiftKey) {
this.addSearch(element, options, div);
}
}.bind(this));
var search = div.olsearch = new ol_control_SearchGeoportail({
className: 'IGNF ol-collapsed',
apiKey: options.apiKey,
authentication: options.authentication,
target: div,
reverse: true
});
search._changeCounter = 0;
this._resetArray(element);
search.on('select', function(e){
search.setInput(e.search.fulltext);
var f = search.get('feature');
if (!f) {
f = new ol_Feature(new ol_geom_Point(e.coordinate));
search.set('feature', f);
this._source.addFeature(f);
// Check geometry change
search.checkgeom = true;
f.getGeometry().on('change', function() {
if (search.checkgeom) this.onGeometryChange(search, f);
}.bind(this));
} else {
search.checkgeom = false;
if (!e.silent) f.getGeometry().setCoordinates(e.coordinate);
search.checkgeom = true;
}
f.set('name', search.getTitle(e.search));
f.set('step', this._search.indexOf(search));
if (f.get('step') === 0) f.set('pos','start');
else if (f.get('step') === this._search.length-1) f.set('pos','end');
search.set('selection', e.search);
}.bind(this));
search.element.querySelector('input').addEventListener('change', function(){
search.set('selection', null);
self.resultElement.innerHTML = '';
});
if (this.getMap()) this.getMap().addControl(search);
};
/** Geometry has changed
* @private
*/
ol_control_RoutingGeoportail.prototype.onGeometryChange = function (search, f, delay) {
// Set current geom
var lonlat = ol_proj_transform(f.getGeometry().getCoordinates(), this.getMap().getView().getProjection(), 'EPSG:4326');
search._handleSelect({
x: lonlat[0],
y: lonlat[1],
fulltext: lonlat[0].toFixed(6) + ',' + lonlat[1].toFixed(6)
}, true, { silent: true });
// Try to revers geocode
if (delay) {
search._changeCounter--;
if (!search._changeCounter) {
search.reverseGeocode(f.getGeometry().getCoordinates(), { silent: true });
return;
}
} else {
search._changeCounter++;
setTimeout(function() {
this.onGeometryChange(search, f, true);
}.bind(this), 1000);
}
}
/**
* Set the map instance the control is associated with
* and add its controls associated to this map.
* @param {_ol_Map_} map The map instance.
*/
ol_control_RoutingGeoportail.prototype.setMap = function (map) {
ol_control_Control.prototype.setMap.call(this, map);
for (var i=0; i<this._search.length; i++) {
var c = this._search[i];
c.setMap(map);
}
};
/** Get request data
* @private
*/
ol_control_RoutingGeoportail.prototype.requestData = function (steps) {
var start = steps[0];
var end = steps[steps.length-1];
var waypoints = '';
for (var i=1; i<steps.length-1; i++) {
waypoints += (waypoints ? ';':'') + steps[i].x+','+steps[i].y;
}
return {
'gp-access-lib': '1.1.0',
origin: start.x+','+start.y,
destination: end.x+','+end.y,
method: this.get('method') || 'time', // 'distance'
graphName: this.get('mode')==='pedestrian' ? 'Pieton' : 'Voiture',
waypoints: waypoints,
format: 'STANDARDEXT'
};
};
/** Gets time as string
* @param {*} routing routing response
* @return {string}
* @api
*/
ol_control_RoutingGeoportail.prototype.getTimeString = function (t) {
t /= 60;
return (t<1) ? '' : (t<60) ? t.toFixed(0)+' min' : (t/60).toFixed(0)+' h '+(t%60).toFixed(0)+' min';
};
/** Gets distance as string
* @param {number} d distance
* @return {string}
* @api
*/
ol_control_RoutingGeoportail.prototype.getDistanceString = function (d) {
return (d<1000) ? d.toFixed(0)+' m' : (d/1000).toFixed(2)+' km';
};
/** Show routing as a list
* @private
*/
ol_control_RoutingGeoportail.prototype.listRouting = function (routing) {
this.resultElement.innerHTML = '';
var t = this.getTimeString(routing.duration);
t += ' ('+this.getDistanceString(routing.distance)+')';
var iElement = document.createElement('i');
iElement.textContent = t;
this.resultElement.appendChild(iElement)
var ul = document.createElement('ul');
this.resultElement.appendChild(ul);
var info = {
'none': 'Prendre sur ',
'R': 'Tourner à droite sur ',
'FR': 'Tourner légèrement à droite sur ',
'L': 'Tourner à gauche sur ',
'FL': 'Tourner légèrement à gauche sur ',
'F': 'Continuer tout droit sur ',
}
for (var i=0, f; f=routing.features[i]; i++) {
var d = this.getDistanceString(f.get('distance'));
t = this.getTimeString(f.get('durationT'));
var li = document.createElement('li');
li.classList.add(f.get('instruction'));
li.innerHTML = (info[f.get('instruction')||'none']||'#')
+ ' ' + f.get('name')
+ '<i>' + d + (t ? ' - ' + t : '') +'</i>'
ul.appendChild(li);
}
};
/** Handle routing response
* @private
*/
ol_control_RoutingGeoportail.prototype.handleResponse = function (data, start, end) {
if (data.status === 'ERROR') {
this.dispatchEvent({
type: 'errror',
status: '200',
statusText: data.message
})
return;
}
var routing = { type:'routing' };
routing.features = [];
var distance = 0;
var duration = 0;
var f, route = [];
for (var i=0, l; l=data.legs[i]; i++) {
for (var j=0, s; s=l.steps[j]; j++) {
var geom = [];
for (var k=0, p; p=s.points[k]; k++){
p = p.split(',');
geom.push([parseFloat(p[0]),parseFloat(p[1])]);
if (i===0 || k!==0) route.push(geom[k]);
}
geom = new ol_geom_LineString(geom);
var options = {
geometry: geom.transform('EPSG:4326',this.getMap().getView().getProjection()),
name: s.name,
instruction: s.navInstruction,
distance: parseFloat(s.distanceMeters),
duration: parseFloat(s.durationSeconds)
}
//console.log(duration, options.duration, s)
distance += options.distance;
duration += options.duration;
options.distanceT = distance;
options.durationT = duration;
f = new ol_Feature(options);
routing.features.push(f);
}
}
routing.distance = parseFloat(data.distanceMeters);
routing.duration = parseFloat(data.durationSeconds);
// Full route
route = new ol_geom_LineString(route);
routing.feature = new ol_Feature ({
geometry: route.transform('EPSG:4326',this.getMap().getView().getProjection()),
start: this._search[0].getTitle(start),
end: this._search[0].getTitle(end),
distance: routing.distance,
duration: routing.duration
});
// console.log(data, routing);
this.dispatchEvent(routing);
this.path = routing;
return routing;
};
/** Calculate route
* @param {Array<ol.coordinate>|undefined} steps an array of steps in EPSG:4326, default use control input values
* @return {boolean} true is a new request is send (more than 2 points to calculate)
*/
ol_control_RoutingGeoportail.prototype.calculate = function (steps) {
this.resultElement.innerHTML = '';
if (steps) {
var convert = [];
steps.forEach(function(s) {
convert.push({ x: s[0], y: s[1] });
});
steps = convert;
} else {
steps = []
for (var i=0; i<this._search.length; i++) {
if (this._search[i].get('selection')) steps.push(this._search[i].get('selection'));
}
}
if (steps.length<2) return false;
var start = steps[0];
var end = steps[steps.length-1];
var data = this.requestData(steps);
var url = encodeURI(this.get('url'));
var parameters = '';
for (var index in data) {
parameters += (parameters) ? '&' : '?';
if (data.hasOwnProperty(index)) parameters += index + '=' + data[index];
}
var self = this;
this.ajax(url + parameters,
function (resp) {
if (resp.status >= 200 && resp.status < 400) {
self.listRouting(self.handleResponse (JSON.parse(resp.response), start, end));
} else {
//console.log(url + parameters, arguments);
this.dispatchEvent({ type: 'error', status: resp.status, statusText: resp.statusText});
}
}.bind(this),
function(resp){
console.log('ERROR', resp)
this.dispatchEvent({ type: 'error', status: resp.status, statusText: resp.statusText});
}.bind(this)
);
return true;
};
/** Send an ajax request (GET)
* @param {string} url
* @param {function} onsuccess callback
* @param {function} onerror callback
*/
ol_control_RoutingGeoportail.prototype.ajax = function (url, onsuccess, onerror){
var self = this;
// Abort previous request
if (this._request) {
this._request.abort();
}
// New request
var ajax = this._request = new XMLHttpRequest();
ajax.open('GET', url, true);
ajax.timeout = this.get('timeout') || 10000;
if (this._auth) {
ajax.setRequestHeader("Authorization", "Basic " + this._auth);
}
this.element.classList.add('ol-searching');
// Load complete
ajax.onload = function() {
self._request = null;
self.element.classList.remove('ol-searching');
onsuccess.call(self, this);
};
// Timeout
ajax.ontimeout = function () {
self._request = null;
self.element.classList.remove('ol-searching');
if (onerror) onerror.call(self, this);
};
// Oops, TODO do something ?
ajax.onerror = function() {
self._request = null;
self.element.classList.remove('ol-searching');
if (onerror) onerror.call(self, this);
};
// GO!
ajax.send();
};
export default ol_control_RoutingGeoportail