UNPKG

@ducna01120/fleetops-engine

Version:

Fleet & Transport Management Extension for Fleetbase

280 lines (231 loc) 8.81 kB
import MarkerLayer from 'ember-leaflet/components/marker-layer'; import { isArray } from '@ember/array'; const arrayFromLatLng = (latlng) => { if (isArray(latlng)) { return latlng; } const latitude = latlng.lat; const longitude = latlng.lng; return [latitude, longitude]; }; const L = window.leaflet || window.L; const oldIE = L.DomUtil.TRANSFORM === 'msTransform'; L.TrackingMarker = L.Marker.extend({ // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects options: { bearingAngle: 0, rotationOrigin: '', }, initialize: function (latlng, options) { L.Marker.prototype.initialize.call(this); L.Util.setOptions(this, options); this._latlng = L.latLng(latlng); var duration = options.duration || 2000; var iconOptions = this.options.icon && this.options.icon.options; var iconAnchor = iconOptions && this.options.icon.options.iconAnchor; if (iconAnchor) { iconAnchor = iconAnchor[0] + 'px ' + iconAnchor[1] + 'px'; } this.options.duration = duration; this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center'; this.options.bearingAngle = this.options.bearingAngle || 0; // Ensure marker keeps rotated during dragging this.on('drag', function (e) { e.target._applyRotation(); }); this.on('move', this.slideCancel, this); this._slideToUntil = 0; this._slideToDuration = duration; this._slideToLatLng = [0, 0]; this._slideFromLatLng = [0, 0]; this._slideKeepAtCenter = false; this._slideDraggingWasAllowed = false; this._slideFrame = 0; }, slideTo: function (latlng, options = {}) { if (!this._map) return; // Convert latlng to L.LatLng latlng = L.latLng(latlng); const duration = options.duration || this.options.duration; this._slideToDuration = duration; this._slideToUntil = performance.now() + duration; this._slideFromLatLng = this.getLatLng(); this._previousPosition = arrayFromLatLng(this._slideFromLatLng); this._slideToLatLng = latlng; this._nextPosition = arrayFromLatLng(latlng); this._slideKeepAtCenter = !!options.keepAtCenter; this._slideDraggingWasAllowed = this._slideDraggingWasAllowed !== undefined ? this._slideDraggingWasAllowed : this._map.dragging.enabled(); if (this._slideKeepAtCenter) { this._map.dragging.disable(); this._map.doubleClickZoom.disable(); this._map.options.touchZoom = 'center'; this._map.options.scrollWheelZoom = 'center'; } this.fire('movestart'); this._slideTo(); return this; }, _slideTo: function () { if (!this._map) return; var remaining = this._slideToUntil - performance.now(); if (remaining < 0) { this.setLatLng(this._slideToLatLng); this.fire('moveend'); if (this._slideDraggingWasAllowed) { this._map.dragging.enable(); this._map.doubleClickZoom.enable(); this._map.options.touchZoom = true; this._map.options.scrollWheelZoom = true; } this._slideDraggingWasAllowed = false; return this; } var startPoint = this._map.latLngToContainerPoint(this._slideFromLatLng); var endPoint = this._map.latLngToContainerPoint(this._slideToLatLng); var percentDone = (this._slideToDuration - remaining) / this._slideToDuration; var currPoint = endPoint.multiplyBy(percentDone).add(startPoint.multiplyBy(1 - percentDone)); var currLatLng = this._map.containerPointToLatLng(currPoint); this.setLatLng(currLatLng); if (this._slideKeepAtCenter) { this._map.panTo(currLatLng, { animate: false }); } this._slideFrame = L.Util.requestAnimFrame(this._slideTo, this); }, // 🍂method slideCancel(): this // Cancels the sliding animation from `slideTo`, if applicable. slideCancel: function () { L.Util.cancelAnimFrame(this._slideFrame); }, onRemove: function (map) { L.Marker.prototype.onRemove.call(this, map); }, _setPos: function (pos) { L.Marker.prototype._setPos.call(this, pos); this._applyRotation(); }, _applyRotation: function () { if (this.options.bearingAngle) { this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin; if (oldIE) { // for IE 9, use the 2D rotation this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.bearingAngle + 'deg)'; } else { // for modern browsers, prefer the 3D accelerated version this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.bearingAngle + 'deg)'; } } }, setRotationAngle: function (angle) { this.options.bearingAngle = angle; this.update(); return this; }, setRotationOrigin: function (origin) { this.options.rotationOrigin = origin; this.update(); return this; }, }); const computeBearing = (previousPosition = [0, 0], nextPosition) => { let bearing = Math.atan2( nextPosition[1] - previousPosition[1], // Longitude difference (x-axis) nextPosition[0] - previousPosition[0] // Latitude difference (y-axis) ); bearing = bearing * (180 / Math.PI); // Convert from radians to degrees bearing = (bearing + 360) % 360; // Ensure the angle is between 0 and 360 return bearing; }; export default class LeafletTrackingMarkerComponent extends MarkerLayer { leafletOptions = [ ...this.leafletOptions, /** * The previous point coordinates. * Allows the marker to automatically computes its rotation angle. * To set a fixed value, consider using rotationAngle property. * * @argument previousPosition * @type {LatLng} */ 'previousPosition', /** * The rotation center, as a transform-origin CSS rule. * * @argument rotationOrigin * @type {String} */ 'rotationOrigin', /** * Rotation angle, in degrees, clockwise. Allows setting the marker rotation angle manually, * replacing the rotation angle value that was automatically computed using the previousPosition property if provided. * * @argument rotationAngle * @type {Number} */ 'rotationAngle', /** * Required, duration in milliseconds marker will take to destination point. * * @argument duration * @type {Number} */ 'duration', /** * Makes map view follow marker. * * @argument keepAtCenter * @type {Boolean} */ 'keepAtCenter', /** * The Public ID of the marker's model. * * @argument publicId * @type {String} */ 'publicId', ]; /** * The default value for the rotationOrigin. * * @memberof LeafletTrackingMarkerComponent */ rotationOrigin = 'center'; /** * The default value for the keepAtCenter. * * @memberof LeafletTrackingMarkerComponent */ keepAtCenter = false; /** * The default value for the rotationAngle. * * @memberof LeafletTrackingMarkerComponent */ rotationAngle = 0; getOption(key, defaultValue = null) { const value = this.options[key] ?? this[key]; if (value === undefined) { return defaultValue; } return value; } _movestart(event) { const marker = event.target; const rotationOrigin = this.getOption('rotationOrigin'); const previousPosition = marker._previousPosition; const position = marker._nextPosition; const moving = previousPosition?.[0] !== position[0] && previousPosition?.[1] !== position[1]; if (rotationOrigin) { marker.setRotationOrigin(rotationOrigin); } if (moving) { const bearingAngle = computeBearing(previousPosition, position); marker.setRotationAngle(bearingAngle); } } createLayer() { const { rotationAngle, location } = this.args; const bearingAngle = rotationAngle ?? computeBearing([0, 0], location); return new L.TrackingMarker(...this.requiredOptions, { ...this.options, bearingAngle }); } }