import Service, { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { later } from '@ember/runloop'; import { htmlSafe } from '@ember/template'; interface IGpsService { inUse: boolean allowedDistance: number mapsLoaded: boolean status: string errorTooltipText: SafeString | null lockAccuracy: number lockedLatitude: string | null lockedLongitude: string | null lockedAccuracy: number | null lockedPosition: IPosition | null intl: any start(): void } interface IPosition { longitude: string, latitude: string, coords?: any } interface IGpsWindow { google: any } declare const window: IGpsWindow /** * This service handles the application's gps. * * @class GpsService * @public */ export default class GpsService extends Service implements IGpsService { @service public intl!: any; public allowedDistance: number = 700 public lockAccuracy: number = 200 public inUse: boolean = false public status: string = 'unknown' public errorTooltipText: SafeString | null = null public lockedLatitude: string | null = null public lockedLongitude: string | null = null public lockedAccuracy: number | null = null public notSupported: boolean = false @tracked public lockedPosition: IPosition | null = null @tracked public distanceToProject: number | null = null protected currentLatitude: string | null = null protected currentLongitude: string | null = null protected currentAccuracy: number | null = null private errorShown: boolean = false; private _lastError: string | null = null; /** * Call this method to fetch the user's initlal location and to set up location watching.
* This method will check if GPS service is already in use so it will not start twice.
* Uses `navigator.geolocation` * * @method start * @public * @return {void} */ /* istanbul ignore next */ public start(): void { if (this.inUse) return this.inUse = true later(this, () => { if (!this.lockedPosition) { this.lastError = 'CUSTOMTIMEOUT' } }, 8000) if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(this.setPosition.bind(this), this.positionError.bind(this)) navigator.geolocation.watchPosition(this.setPosition.bind(this), this.positionError.bind(this), {enableHighAccuracy : true}) } else { this.notSupported = true } // this.allowedDistance = this.customerSettings.checkSetting('gps_distance') || 700 } /** * Get the distance between two `location: IPosition` objects. * @method getDistance * @param {IPosition} loc1 * @param {IPosition} loc2 * @public * @return {Number} */ /* istanbul ignore next */ public getDistance(loc1: IPosition, loc2: IPosition): number { const gloc1 = new window.google!.maps.LatLng(loc1.latitude, loc1.longitude) const gloc2 = new window.google!.maps.LatLng(loc2.latitude, loc2.longitude) return window.google!.maps.geometry.spherical.computeDistanceBetween(gloc1, gloc2) } /** * Success Callback for navigator.geolocation
* Sets user's current location, and calls lockLocation to lock the user's current position. * @method setPosition * @param {IPosition} position * @private * @return {Void} */ private setPosition(position: IPosition): void { this.lastError = null if (this.status === 'error') { this.status = 'searching' } this.currentAccuracy = position.coords.accuracy this.currentLongitude = position.coords.longitude this.currentLatitude = position.coords.latitude this.lockLocation(position.coords.latitude, position.coords.longitude, position.coords.accuracy) } /** * Error Callback for navigator.geolocation.
* Sets this service's last seen error message * @method positionError * @param {any} error * @private * @return {Void} */ private positionError(error: any) { this.resetPosition() if (error.code === error.PERMISSION_DENIED) this.setLastError('PERMISSION_DENIED') else if (error.code === error.POSITION_UNAVAILABLE) this.setLastError('POSITION_UNAVAILABLE') else if (error.code === error.TIMEOUT) this.setLastError ('TIMEOUT') else this.setLastError('UNKNOWN_ERROR') } /** * Called by `setPosition`, which is called by `navigator.geolocation.getCurrentPosition` and `navigator.geolocation.watchPosition` * * Sets the following public properties: * ``` * this.lockedLatitude = lat * this.lockedLongitude = lon * this.lockedPosition = {latitude: lat, longitude: lon} * this.lockedAccuracy = this.currentAccuracy * ``` * @method lockLocation * @param {String} lat * @param {String} long * @param {Number} accuracy * @private * @return {Void} */ private lockLocation(lat: string, lon: string, accuracy: number): void { /* istanbul ignore next */ if (!this.mapsLoaded) { later(this, () => { this.lockLocation(lat, lon, accuracy) }, 3000) return } if (accuracy < this.lockAccuracy) { if (this.checkLocationChange(lat, lon, 50)) { this.lockedLatitude = lat this.lockedLongitude = lon this.lockedPosition = {latitude: lat, longitude: lon} this.lockedAccuracy = this.currentAccuracy } this.status = 'located' } } /** * Checks if the gps location has changed enough. * Calls `getDistance`, comparing the method's parameters to lockedLatitude and lockedLongitude * returns true if the distance between is greater than the change param * @method checkLocationChange * @param {String} latitude * @param {String} longitude * @param {Number} change * @private * @return {Boolean} */ private checkLocationChange(latitude: string, longitude: string, change: number): boolean { if (!this.lockedLatitude || !this.lockedLongitude) return true const loc1 = {latitude: this.lockedLatitude, longitude: this.lockedLongitude} const loc2 = {latitude, longitude} const dist = this.getDistance(loc1, loc2) return (dist > change); } /** * Rests currentLatitude, currentLongitude, lockedLatitude, and lockedLongitude * @method resetPosition * @private * @return {Void} */ private resetPosition(): void { this.currentLatitude = this.currentLongitude = this.lockedLatitude = this.lockedLongitude = null } private setLastError(error: any) { if (!this.errorShown) { this.errorTooltipText = htmlSafe(this.intl.t('gps.errors.general_tooltip').replace(/\n/g, '
')) this.errorShown = true } this.lastError = error this.status = 'error' } get mapsLoaded(): boolean { return window.google?.maps ? true : false } get lastError(): string | null { return this._lastError } set lastError(error: string | null) { this._lastError = error } }