kmap-ui
Version:
A components of zmap base on vue2.X
500 lines (468 loc) • 17.2 kB
JavaScript
import ol_ext_inherits from '../util/ext'
import ol_interaction_Interaction from 'ol/interaction/Interaction'
import ol_Geolocation from 'ol/Geolocation'
import ol_style_Circle from 'ol/style/Circle'
import ol_style_Stroke from 'ol/style/Stroke'
import ol_geom_Point from 'ol/geom/Point'
import ol_geom_LineString from 'ol/geom/LineString'
import ol_geom_Polygon from 'ol/geom/Polygon'
import ol_geom_Circle from 'ol/geom/Circle'
import { fromCircle as ol_geom_Polygon_fromCircle } from 'ol/geom/Polygon'
import ol_style_Style from 'ol/style/Style'
import ol_style_RegularShape from 'ol/style/RegularShape'
import ol_style_Fill from 'ol/style/Fill'
import ol_layer_Vector from 'ol/layer/Vector'
import ol_source_Vector from 'ol/source/Vector'
import ol_Feature from 'ol/Feature'
import ol_interaction_Pointer from 'ol/interaction/Pointer'
import {containsCoordinate as ol_extent_containsCoordinate, containsExtent as ol_extent_containsExtent} from 'ol/extent'
// import {ol_coordinate_dist2d, ol_coordinate_equal} from '../geom/GeomUtils'
import {transform as ol_proj_transform} from 'ol/proj'
import {getDistance as ol_spehre_getDistance} from 'ol/sphere'
/** Interaction to draw on the current geolocation
* It combines a draw with a ol_Geolocation
* @constructor
* @extends {ol_interaction_Interaction}
* @fires drawstart, drawend, drawing, tracking, follow
* @param {any} options
* @param { ol.Collection.<ol.Feature> | undefined } option.features Destination collection for the drawn features.
* @param { ol.source.Vector | undefined } options.source Destination source for the drawn features.
* @param {ol.geom.GeometryType} options.type Drawing type ('Point', 'LineString', 'Polygon'), default LineString.
* @param {Number | undefined} options.minAccuracy minimum accuracy underneath a new point will be register (if no condition), default 20
* @param {function | undefined} options.condition a function that take a ol_Geolocation object and return a boolean to indicate whether location should be handled or not, default return true if accuraty < minAccuraty
* @param {Object} options.attributes a list of attributes to register as Point properties: {accuracy:true,accuracyGeometry:true,heading:true,speed:true}, default none.
* @param {Number} options.tolerance tolerance to add a new point (in meter), default 5
* @param {Number} options.zoom zoom for tracking, default 16
* @param {Number} options.minZoom min zoom for tracking, if zoom is less it will zoom to it, default use zoom option
* @param {boolean|auto|position|visible} options.followTrack true if you want the interaction to follow the track on the map, default true
* @param { ol.style.Style | Array.<ol.style.Style> | ol.StyleFunction | undefined } options.style Style for sketch features.
*/
var ol_interaction_GeolocationDraw = function(options) {
if (!options) options={};
// Geolocation
this.geolocation = new ol_Geolocation(({
projection: "EPSG:4326",
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000
}
}));
this.geolocation.on('change', this.draw_.bind(this));
// Current path
this.path_ = [];
this.lastPosition_ = false;
// Default style
var white = [255, 255, 255, 1];
var blue = [0, 153, 255, 1];
var width = 3;
var circle = new ol_style_Circle({
radius: width * 2,
fill: new ol_style_Fill({ color: blue }),
stroke: new ol_style_Stroke({ color: white, width: width / 2 })
});
var style = [
new ol_style_Style({
stroke: new ol_style_Stroke({ color: white, width: width + 2 })
}),
new ol_style_Style({
stroke: new ol_style_Stroke({ color: blue, width: width }),
fill: new ol_style_Fill({
color: [255, 255, 255, 0.5]
})
})
];
var triangle = new ol_style_RegularShape({
radius: width * 3.5,
points: 3,
rotation: 0,
fill: new ol_style_Fill({ color: blue }),
stroke: new ol_style_Stroke({ color: white, width: width / 2 })
});
// stretch the symbol
var c = triangle.getImage();
var ctx = c.getContext("2d");
var c2 = document.createElement('canvas');
c2.width = c2.height = c.width;
c2.getContext("2d").drawImage(c, 0,0);
ctx.clearRect(0,0,c.width,c.height);
ctx.drawImage(c2, 0,0, c.width, c.height, width, 0, c.width-2*width, c.height);
var defaultStyle = function(f) {
if (f.get('heading')===undefined) {
style[1].setImage(circle);
} else {
style[1].setImage(triangle);
triangle.setRotation( f.get('heading') || 0);
}
return style;
}
// Style for the accuracy geometry
this.locStyle = {
error: new ol_style_Style({ fill: new ol_style_Fill({ color: [255, 0, 0, 0.2] }) }),
warn: new ol_style_Style({ fill: new ol_style_Fill({ color: [255, 192, 0, 0.2] }) }),
ok: new ol_style_Style({ fill: new ol_style_Fill({ color: [0, 255, 0, 0.2] }) }),
};
// Create a new overlay layer for the sketch
this.overlayLayer_ = new ol_layer_Vector({
source: new ol_source_Vector(),
name:'GeolocationDraw overlay',
style: options.style || defaultStyle
});
this.sketch_ = [new ol_Feature(), new ol_Feature(), new ol_Feature()];
this.overlayLayer_.getSource().addFeatures(this.sketch_);
this.features_ = options.features;
this.source_ = options.source;
this.condition_ = options.condition || function(loc) {
return loc.getAccuracy() < this.get("minAccuracy")
};
// Prevent interaction when tracking
ol_interaction_Interaction.call(this, {
handleEvent: function() {
return (!this.get('followTrack') || this.get('followTrack')=='auto');// || !geoloc.getTracking());
}
});
this.set('type', options.type||"LineString");
this.set('attributes', options.attributes||{});
this.set('minAccuracy', options.minAccuracy||20);
this.set('tolerance', options.tolerance||5);
this.set('zoom', options.zoom);
this.set('minZoom', options.minZoom);
this.setFollowTrack (options.followTrack===undefined ? true : options.followTrack);
this.setActive(false);
};
ol_ext_inherits(ol_interaction_GeolocationDraw, ol_interaction_Interaction);
/** Simplify 3D geometry
* @param {ol.geom.Geometry} geo
* @param {number} tolerance
*/
ol_interaction_GeolocationDraw.prototype.simplify3D = function(geo, tolerance) {
var geom = geo.getCoordinates();
var proj = this.getMap().getView().getProjection();
if (this.get("type")==='Polygon') {
geom = geom[0];
}
var simply = [geom[0]];
var pi, p = ol_proj_transform(geom[0], proj, 'EPSG:4326')
for (var i=1; i<geom.length; i++) {
pi = ol_proj_transform(geom[i], proj, 'EPSG:4326')
var d = ol_spehre_getDistance(p, pi);
if (d > tolerance) {
simply.push(geom[i]);
p = pi;
}
}
if (simply[simply.length-1] !== geom[geom.length-1]) {
simply.push(geom[geom.length-1]);
}
/*
var simply = geo.simplify(tolerance).getCoordinates();
if (this.get("type")==='Polygon') {
simply = simply[0];
}
var step=0;
simply.forEach(function(p) {
for (; step<this.path_.length; step++) {
if (ol_coordinate_equal(p, this.path_[step])) {
p[2] = this.path_[step][2];
p[3] = this.path_[step][3];
break;
}
}
}.bind(this));
*/
// Get 3D geom
if (this.get("type")==='Polygon') {
geo = new ol_geom_Polygon([simply], 'XYZM');
} else {
geo = new ol_geom_LineString(simply, 'XYZM');
}
return geo;
};
/**
* Remove the interaction from its current map, if any, and attach it to a new
* map, if any. Pass `null` to just remove the interaction from the current map.
* @param {ol.Map} map Map.
* @api stable
*/
ol_interaction_GeolocationDraw.prototype.setMap = function(map) {
if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_);
ol_interaction_Pointer.prototype.setMap.call (this, map);
this.overlayLayer_.setMap(map);
if (map) this.geolocation.setProjection(map.getView().getProjection());
};
/** Activate or deactivate the interaction.
* @param {boolean} active
*/
ol_interaction_GeolocationDraw.prototype.setActive = function(active) {
if (active === this.getActive()) return;
ol_interaction_Interaction.prototype.setActive.call(this, active);
this.overlayLayer_.setVisible(active);
if (this.getMap()) {
this.geolocation.setTracking(active);
this.getMap().renderSync();
}
this.pause(!active);
if (active) {
// Start drawing
this.reset();
this.dispatchEvent({ type:'drawstart', feature: this.sketch_[1]});
} else {
var f = this.sketch_[1].clone();
if (f.getGeometry()) {
if (this.features_) this.features_.push(f);
if (this.source_) this.source_.addFeature(f);
this.dispatchEvent({ type:'drawend', feature: f});
}
}
};
/** Simulate a track and override current geolocation
* @param {Array<ol.coordinate>|boolean} track a list of point or false to stop
* @param {*} options
* @param {number} delay delay in ms, default 1000 (1s)
* @param {number} accuracy gps accuracy, default 10
* @param {boolean} repeat repeat track, default true
*/
ol_interaction_GeolocationDraw.prototype.simulate = function(track, options) {
if (this._track) {
clearTimeout(this._track.timeout);
}
if (!track) {
this._track = false;
return;
}
options = options || {};
var delay = options.delay || 1000;
function handleTrack() {
if (this._track.pos >= this._track.track.length) {
this._track = false;
return;
}
var coord = this._track.track[this._track.pos];
coord[2] = coord[3] || 0;
coord[3] = (new Date()).getTime();
this._track.pos++;
if (options.repeat !== false) {
this._track.pos = this._track.pos % this._track.track.length;
}
if (this.getActive()) this.draw_(true, coord, options.accuracy);
this._track.timeout = setTimeout(handleTrack.bind(this), delay);
}
this._track = {
track: track,
pos: 0,
timeout: setTimeout(handleTrack.bind(this), 0)
}
};
/** Is simulation on ?
* @returns {boolean}
*/
ol_interaction_GeolocationDraw.prototype.simulating = function() {
return !!this._track;
};
/** Reset drawing
*/
ol_interaction_GeolocationDraw.prototype.reset = function() {
this.sketch_[1].setGeometry();
this.path_ = [];
this.lastPosition_ = false;
};
/** Start tracking = setActive(true)
*/
ol_interaction_GeolocationDraw.prototype.start = function() {
this.setActive(true);
};
/** Stop tracking = setActive(false)
*/
ol_interaction_GeolocationDraw.prototype.stop = function() {
this.setActive(false);
};
/** Pause drawing
* @param {boolean} b
*/
ol_interaction_GeolocationDraw.prototype.pause = function(b) {
this.pause_ = (b!==false);
};
/** Is paused
* @return {boolean} b
*/
ol_interaction_GeolocationDraw.prototype.isPaused = function() {
return this.pause_;
};
/** Enable following the track on the map
* @param {boolean|auto|position|visible} follow,
* false: don't follow,
* true: follow (position+zoom),
* 'position': follow only position,
* 'auto': start following until user move the map,
* 'visible': center when position gets out of the visible extent
*/
ol_interaction_GeolocationDraw.prototype.setFollowTrack = function(follow) {
this.set('followTrack', follow);
var map = this.getMap();
// Center if wanted
if (this.getActive() && map) {
var zoom;
if (follow !== 'position') {
if (this.get('minZoom')) {
zoom = Math.max(this.get('minZoom'), map.getView().getZoom());
} else {
zoom = this.get('zoom');
}
}
if (follow !== false && !this.lastPosition_) {
var pos = this.path_[this.path_.length-1];
if (pos) {
map.getView().animate({
center: pos,
zoom: zoom
});
}
} else if (follow==='auto' && this.lastPosition_) {
map.getView().animate({
center: this.lastPosition_,
zoom: zoom
});
}
}
this.lastPosition_ = false;
this.dispatchEvent({ type:'follow', following: follow!==false });
};
/** Add a new point to the current path
* @private
*/
ol_interaction_GeolocationDraw.prototype.draw_ = function(simulate, coord, accuracy) {
var map = this.getMap();
if (!map) return;
var accu, pos, p, loc, heading;
// Simulation mode
if (this._track) {
if (simulate!==true) return;
pos = coord;
accu = accuracy || 10;
if (this.path_ && this.path_.length) {
var pt = this.path_[this.path_.length-1];
heading = Math.atan2(coord[0]-pt[0],coord[1]-pt[1])
}
var circle = new ol_geom_Circle(pos, map.getView().getResolution()*accu);
p = ol_geom_Polygon_fromCircle(circle);
} else {
// Current location
loc = this.geolocation;
accu = loc.getAccuracy();
pos = loc.getPosition();
pos.push (Math.round((loc.getAltitude()||0)*100)/100);
pos.push (Math.round((new Date()).getTime()/1000));
p = loc.getAccuracyGeometry();
heading = loc.getHeading();
}
// Center on point
// console.log(this.get('followTrack'))
switch (this.get('followTrack')) {
// Follow center + zoom
case true: {
// modify zoom
if (this.get('followTrack') == true) {
if (this.get('minZoom')) {
if (this.get('minZoom') > map.getView().getZoom()) {
map.getView().setZoom(this.get('minZoom'));
}
} else {
map.getView().setZoom( this.get('zoom') || 16 );
}
if (!ol_extent_containsExtent(map.getView().calculateExtent(map.getSize()), p.getExtent())) {
map.getView().fit(p.getExtent());
}
}
map.getView().setCenter( pos );
break;
}
// Follow position
case 'position': {
// modify center
map.getView().setCenter( pos );
break;
}
// Keep on following
case 'auto': {
if (this.lastPosition_) {
var center = map.getView().getCenter();
// console.log(center,this.lastPosition_)
if (center[0]!=this.lastPosition_[0] || center[1]!=this.lastPosition_[1]) {
//this.dispatchEvent({ type:'follow', following: false });
this.setFollowTrack (false);
} else {
map.getView().setCenter( pos );
this.lastPosition_ = pos;
}
} else {
map.getView().setCenter( pos );
if (this.get('minZoom')) {
if (this.get('minZoom') > map.getView().getZoom()) {
map.getView().setZoom(this.get('minZoom'));
}
} else if (this.get('zoom')) {
map.getView().setZoom( this.get('zoom'));
}
this.lastPosition_ = pos;
}
break;
}
// Force to stay on the map
case 'visible': {
if (!ol_extent_containsCoordinate(map.getView().calculateExtent(map.getSize()), pos)) {
map.getView().setCenter (pos);
}
break;
}
// Don't follow
default: break;
}
// Draw occuracy
var f = this.sketch_[0];
f.setGeometry(p);
if (accu < this.get("minAccuracy")/2) f.setStyle(this.locStyle.ok);
else if (accu < this.get("minAccuracy")) f.setStyle(this.locStyle.warn);
else f.setStyle(this.locStyle.error);
var geo;
if (this.pause_) {
this.lastPosition_ = pos;
}
if (!this.pause_ && (!loc || this.condition_.call(this, loc))) {
f = this.sketch_[1];
this.path_.push(pos);
switch (this.get("type")) {
case "Point":
this.path_ = [pos];
f.setGeometry(new ol_geom_Point(pos, 'XYZM'));
var attr = this.get('attributes');
if (attr.heading) f.set("heading",loc.getHeading());
if (attr.accuracy) f.set("accuracy",loc.getAccuracy());
if (attr.altitudeAccuracy) f.set("altitudeAccuracy",loc.getAltitudeAccuracy());
if (attr.speed) f.set("speed",loc.getSpeed());
break;
case "LineString":
if (this.path_.length>1) {
geo = new ol_geom_LineString(this.path_, 'XYZM');
if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance"));
f.setGeometry(geo);
} else {
f.setGeometry();
}
break;
case "Polygon":
if (this.path_.length>2) {
geo = new ol_geom_Polygon([this.path_], 'XYZM');
if (this.get("tolerance")) geo = this.simplify3D (geo, this.get("tolerance"));
f.setGeometry(geo);
}
else f.setGeometry();
break;
}
this.dispatchEvent({ type:'drawing', feature: this.sketch_[1], geolocation: loc });
}
this.sketch_[2].setGeometry(new ol_geom_Point(pos));
this.sketch_[2].set("heading", heading);
// Drawing
this.dispatchEvent({ type:'tracking', feature: this.sketch_[1], geolocation: loc });
};
export default ol_interaction_GeolocationDraw