src/controls/GeoLocationButton.js
import $ from 'jquery'
import Feature from 'ol/Feature'
import Geolocation from 'ol/Geolocation'
import Point from 'ol/geom/Point'
import VectorSource from 'ol/source/Vector'
import { Control } from './Control'
import { addTooltip } from '../html/html'
import { VectorLayer } from '../layers/VectorLayer'
import { MessageDisplay } from '../MessageDisplay'
import { cssClasses } from '../globals'
import '../../less/geolocation.less'
import { ActivatableMixin } from './ActivatableMixin'
import { ListenerOrganizerMixin } from '../ListenerOrganizerMixin'
import { mixin } from '../utilities'
import { GEOLOCATION } from 'ol/has'
/**
* @typedef {g4uControlOptions} GeoLocationButtonOptions
* @property {boolean} [animated] if the move on the map to the geoposition should be animated
* @property {StyleLike} [style='#defaultStyle']
* @property {number} [maxZoom]
* @property {boolean} [followLocation=false]
* @property {boolean} [active=false]
*/
/**
* This class provides a button to center the view on your current geoposition.
*/
export class GeolocationButton extends mixin(Control, [ActivatableMixin, ListenerOrganizerMixin]) {
/**
* @param {GeoLocationButtonOptions} [options={}]
*/
constructor (options = {}) {
options.className = options.className || 'g4u-geolocation'
options.singleButton = true
options.element = $('<button>').get(0)
super(options)
/**
* @type {string}
* @private
*/
this.classNamePushed_ = this.className_ + '-pushed'
/**
* @type {boolean}
* @private
*/
this.animated_ = options.animated
/**
* @type {StyleLike}
* @private
*/
this.style_ = options.style || '#defaultStyle'
/**
* @type {number}
* @private
*/
this.maxZoom_ = options.maxZoom
this.setTitle(this.getTitle() || this.getLocaliser().localiseUsingDictionary('GeolocationButton title'))
this.setTipLabel(this.getTipLabel() || this.getLocaliser().localiseUsingDictionary('GeolocationButton tipLabel'))
this.get$Element()
.addClass(this.className_)
.addClass(cssClasses.mainButton)
.html(this.getTitle())
addTooltip(this.get$Element(), this.getTipLabel())
/**
* @type {MessageDisplay}
* @private
*/
this.buttonMessageDisplay_ = new MessageDisplay(this.get$Element())
this.get$Element().on('click touch', () => {
if (GEOLOCATION) {
this.setActive(!this.getActive())
} else {
this.buttonMessageDisplay_.error(
this.getLocaliser().localiseUsingDictionary('geolocation geolocation-not-possible'),
this.getMap().get('mobile') ? { position: 'top middle' } : {}
)
}
})
this.layer_ = null
const geolocationOptions = { tracking: false }
if (options.hasOwnProperty('trackingOptions')) {
geolocationOptions.trackingOptions = options.trackingOptions
}
if (options.hasOwnProperty('projection')) {
geolocationOptions.projection = options.projection
}
this.followLocation_ = options.followLocation || false
this.geolocation_ = new Geolocation(geolocationOptions)
this.geolocation_.on('error', () => {
this.buttonMessageDisplay_.error(
this.getLocaliser().localiseUsingDictionary('geolocation geolocation-not-possible'),
this.getMap().get('mobile') ? { position: 'top middle' } : {}
)
this.setActive(false)
})
this.on('change:active', e => this.activeChangeHandler_(e))
}
/**
* @param {G4UMap} map
*/
setMap (map) {
if (this.getMap()) {
this.getMap().getLayers().remove(this.layer_)
}
super.setMap(map)
if (map) {
const projection = map.getView().getProjection()
this.geolocation_.setProjection(projection)
const layerOptions = { source: new VectorSource({ projection: projection }), visible: true }
this.layer_ = new VectorLayer(layerOptions)
this.layer_.setStyle(map.get('styling').getStyle(this.style_))
map.get('styling').manageLayer(this.layer_)
map.addLayer(this.layer_)
this.activateOnMapChange()
}
}
changeHandler_ (options) {
const source = this.layer_.getSource()
source.clear()
const position = this.geolocation_.getPosition()
source.addFeature(new Feature({ geometry: new Point(position) }))
const circle = this.geolocation_.getAccuracyGeometry()
source.addFeature(new Feature({ geometry: circle }))
if (options.hasOwnProperty('initialRun') && options.initialRun) {
this.getMap().get('move').toExtent(circle.getExtent(), { animated: this.animated_, maxZoom: this.maxZoom_ })
} else {
if (this.animated_) {
this.getMap().getView().animate({ center: position })
} else {
this.getMap().getView().setCenter(position)
}
}
if (options.hasOwnProperty('stopTracking')) {
this.geolocation_.setTracking(!options.stopTracking)
}
}
/**
* Show/Hide the geolocation on the map as point with a circle in the size of the accuracy around
*/
activeChangeHandler_ () {
const active = this.getActive()
this.get$Element().toggleClass(this.classNamePushed_, active)
if (active) {
this.geolocation_.setTracking(true)
const position = this.geolocation_.getPosition()
if (position) {
this.changeHandler_({ stopTracking: !this.followLocation_, initialRun: true })
if (this.followLocation_) {
this.listenAt(this.geolocation_).on('change', e => this.changeHandler_(e))
}
} else {
this.listenAt(this.geolocation_).once('change', () => {
this.changeHandler_({ stopTracking: !this.followLocation_, initialRun: true })
if (this.followLocation_) {
this.listenAt(this.geolocation_).on('change', e => this.changeHandler_(e))
}
})
}
} else {
this.layer_.getSource().clear()
this.detachFrom(this.geolocation_, 'change')
this.geolocation_.setTracking(false)
}
}
}