import ol_interaction_Interaction from 'ol/interaction/Interaction.js'
import ol_Geolocation from 'ol/Geolocation.js'
import ol_style_Circle from 'ol/style/Circle.js'
import ol_style_Stroke from 'ol/style/Stroke.js'
import ol_geom_Point from 'ol/geom/Point.js'
import ol_geom_LineString from 'ol/geom/LineString.js'
import ol_geom_Polygon from 'ol/geom/Polygon.js'
import ol_geom_Circle from 'ol/geom/Circle.js'
import { fromCircle as ol_geom_Polygon_fromCircle } from 'ol/geom/Polygon.js'
import ol_style_Style from 'ol/style/Style.js'
import ol_style_RegularShape from 'ol/style/RegularShape.js'
import ol_style_Fill from 'ol/style/Fill.js'
import ol_layer_Vector from 'ol/layer/Vector.js'
import ol_source_Vector from 'ol/source/Vector.js'
import ol_Feature from 'ol/Feature.js'
import {containsCoordinate as ol_extent_containsCoordinate, containsExtent as ol_extent_containsExtent} from 'ol/extent.js'
// import {ol_coordinate_dist2d, ol_coordinate_equal} from '../geom/GeomUtils'
import {transform as ol_proj_transform} from 'ol/proj.js'
import {getDistance as ol_sphere_getDistance} from 'ol/sphere.js'
/** 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.
| 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 accuracy < minAccuracy
* @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.StyleFunction | undefined } options.style Style for sketch features.
*/
var ol_interaction_GeolocationDraw = class olinteractionGeolocationDraw extends ol_interaction_Interaction {
constructor(options) {
options = options || {}
super({
handleEvent: function () {
return (!this.get('followTrack') || this.get('followTrack') == 'auto') // || !geoloc.getTracking());
}
})
// 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")
}
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)
}
/** Simplify 3D geometry
* @param {ol.geom.Geometry} geo
* @param {number} tolerance
*/
simplify3D(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_sphere_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|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
*/
simulate(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}
*/
simulating() {
return !!this._track
}
/** Reset drawing
*/
reset() {
this.sketch_[1].setGeometry()
this.path_ = []
this.lastPosition_ = false
}
/** Start tracking = setActive(true)
*/
start() {
this.setActive(true)
}
/** Stop tracking = setActive(false)
*/
stop() {
this.setActive(false)
}
/** Pause drawing
* @param {boolean} b
*/
pause(b) {
this.pause_ = (b !== false)
}
/** Is paused
* @return {boolean} b
*/
isPaused() {
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
*/
setFollowTrack(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
*/
draw_(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 = this.getPosition(loc)
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 })
}
/** Get a position according to the geolocation
* @param {Geolocation} loc
* @returns {Array} an array of measure X,Y,Z,T
* @api
*/
getPosition(loc) {
var pos = loc.getPosition()
pos.push(Math.round((loc.getAltitude() || 0) * 100) / 100)
pos.push(Math.round((new Date()).getTime() / 1000))
return pos
}
}
export default ol_interaction_GeolocationDraw