diff --git a/src/app/map/geo.service.ts b/src/app/map/geo.service.ts new file mode 100644 index 0000000..6bdb546 --- /dev/null +++ b/src/app/map/geo.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { catchError, Observable, tap, throwError } from 'rxjs'; +import { NGXLogger } from 'ngx-logger'; + +@Injectable() +export class GeoService { + constructor(private readonly log: NGXLogger) {} + + myCurrentLocation() { + return new Observable(observer => { + window.navigator.geolocation.getCurrentPosition( + position => { + observer.next(position.coords); + observer.complete(); + }, + err => observer.error(err), + { enableHighAccuracy: false, timeout: 5000 } + ); + }).pipe( + tap(d => { + this.log.debug('Got your location', d); + }), + // it will be invoked if our source observable emits an error. + catchError(error => { + this.log.error('failed to get your location', error); + return throwError(() => error); + }) + ); + } +} \ No newline at end of file diff --git a/src/app/map/map.component.scss b/src/app/map/map.component.scss index 006dc19..d15486b 100644 --- a/src/app/map/map.component.scss +++ b/src/app/map/map.component.scss @@ -19,6 +19,8 @@ div.metrics { span.label { @include typography.prevent-select; + @include typography.default; + color: typography.$tertiary; display: inline-block; width: 70pt; padding: 5pt; @@ -28,7 +30,7 @@ div.metrics { div.map { @include typography.prevent-select; - background-color: $accent; + background-color: $dark-matter-bg; position: absolute; top: 0; bottom: 0; diff --git a/src/app/map/map.constants.ts b/src/app/map/map.constants.ts index 68c83d4..1aa61fa 100644 --- a/src/app/map/map.constants.ts +++ b/src/app/map/map.constants.ts @@ -1,7 +1,7 @@ import { ScatterplotLayer } from '@deck.gl/layers/typed'; export const DEFAULT_ZOOM = 3; -export const DEFAULT_TRANSITION_DURATION_MS= 2000 +export const DEFAULT_TRANSITION_DURATION_MS= 'auto'; export const FLY_TO_ZOOM = 7; @@ -46,7 +46,7 @@ export const CAPITOLS_LAYER = new ScatterplotLayer({ }); export enum LayerIndices { - MAP_LAYER_INDEX = 0, - EU_LAYER_INDEX = 1, - CAPITOLS_LAYER_INDEX = 2 + MAP_LAYER = 0, + EU_BORDERS_LAYER = 1, + CAPITOLS_LAYER = 2 } diff --git a/src/app/map/map.module.ts b/src/app/map/map.module.ts index 93be65e..d839e84 100644 --- a/src/app/map/map.module.ts +++ b/src/app/map/map.module.ts @@ -1,7 +1,8 @@ import { NgModule } from '@angular/core'; import { MapService } from './map.service'; +import { GeoService } from './geo.service'; @NgModule({ - providers: [MapService] + providers: [GeoService, MapService] }) export class MapModule {} diff --git a/src/app/map/map.service.ts b/src/app/map/map.service.ts index 03a158d..41e71fc 100644 --- a/src/app/map/map.service.ts +++ b/src/app/map/map.service.ts @@ -5,18 +5,18 @@ import { CAPITOLS_LAYER, CENTER_OF_EUROPE, DEFAULT_TRANSITION_DURATION_MS, - DEFAULT_ZOOM, FLY_TO_ZOOM, INITIAL_VIEW_STATE, LayerIndices } from './map.constants'; import { NotificationService } from '../notifications/notification.service'; -import { Deck, FlyToInterpolator, Layer } from '@deck.gl/core/typed'; +import { Deck, FlyToInterpolator, Layer, TRANSITION_EVENTS } from '@deck.gl/core/typed'; import { BitmapLayer, GeoJsonLayer } from '@deck.gl/layers/typed'; import { firstValueFrom, Subject } from 'rxjs'; import { TileLayer } from '@deck.gl/geo-layers/typed'; import { environment } from '../../environments/environment'; import { DeckMetrics } from '@deck.gl/core/typed/lib/deck'; +import { GeoService } from './geo.service'; @Injectable() export class MapService { @@ -26,11 +26,13 @@ export class MapService { private readonly euBordersLayer: Promise; private readonly layers: Promise; - private map?: Deck; + private theMap?: Deck; + private geoPosition?: GeolocationCoordinates; constructor( private readonly http: HttpClient, private readonly log: NGXLogger, + private readonly geoService: GeoService, private readonly notificationService: NotificationService ) { this.mapLayer = this.initMapLayer(); @@ -39,54 +41,72 @@ export class MapService { } async loadAllLayers(): Promise { - return Promise.all([this.getMapLayer(), this.getEuBordersLayer(), this.getCapitolsLayer()]); + return Promise.all([this.getMapLayer(), this.getEuBordersLayer(), this.getCapitolsLayer()]).then( + ([map, euBorder, capitols]) => { + //doing this for full control over ordering + const layers = new Array(3); + layers[LayerIndices.MAP_LAYER] = map; + layers[LayerIndices.EU_BORDERS_LAYER] = euBorder; + layers[LayerIndices.CAPITOLS_LAYER] = capitols; + return layers; + } + ); } async resetMapToEuropeanCenter() { - this.map!.setProps({ + // FIXME deck.gl - find out why resetting theMap is unreliable + console.log('--- Lat', this.theMap!.props.initialViewState.latitude); + console.log('--- Lon', this.theMap!.props.initialViewState.longitude); + console.log('--- Lat-S', CENTER_OF_EUROPE[0]); + console.log('--- Lon-S', CENTER_OF_EUROPE[1]); + + this.theMap!.setProps({ initialViewState: { - latitude: CENTER_OF_EUROPE[0], - longitude: CENTER_OF_EUROPE[1], - zoom: DEFAULT_ZOOM, - transitionInterpolator: new FlyToInterpolator(), - transitionDuration: DEFAULT_TRANSITION_DURATION_MS + ...Object.assign(this, INITIAL_VIEW_STATE), + transitionInterpolator: new FlyToInterpolator({ speed: 1.5 }), + transitionDuration: DEFAULT_TRANSITION_DURATION_MS, + transitionInterruption: TRANSITION_EVENTS.IGNORE } }); } async moveMapToMyLocation() { - navigator.geolocation.getCurrentPosition( - position => { - this.map!.setProps({ - initialViewState: { - latitude: position.coords.latitude, - longitude: position.coords.longitude, - zoom: FLY_TO_ZOOM, - transitionInterpolator: new FlyToInterpolator(), - transitionDuration: DEFAULT_TRANSITION_DURATION_MS - } - }); - }, - err => this.notificationService.showError('Could not get geolocation', err), - { - enableHighAccuracy: false + if (!this.geoPosition) { + firstValueFrom(this.geoService.myCurrentLocation()).then(p => { + this.geoPosition = p; + this.flyMapTo(p); + }); + } else { + this.flyMapTo(this.geoPosition); + } + } + + private flyMapTo(p: GeolocationCoordinates) { + this.theMap!.setProps({ + initialViewState: { + ...Object.assign(this, INITIAL_VIEW_STATE), + latitude: p.latitude, + longitude: p.longitude, + zoom: FLY_TO_ZOOM, + transitionInterpolator: new FlyToInterpolator(), + transitionDuration: DEFAULT_TRANSITION_DURATION_MS } - ); + }); } async doShowEuBorders(value: boolean) { - this.changeLayerVisibility(LayerIndices.EU_LAYER_INDEX, value).then(() => this.log.trace('Show EU borders', value)); + this.changeLayerVisibility(LayerIndices.EU_BORDERS_LAYER, value).then(() => + this.log.trace('Show EU borders', value) + ); } async doShowCapitols(value: boolean) { - this.changeLayerVisibility(LayerIndices.CAPITOLS_LAYER_INDEX, value).then(() => - this.log.trace('Show capitols', value) - ); + this.changeLayerVisibility(LayerIndices.CAPITOLS_LAYER, value).then(() => this.log.trace('Show capitols', value)); } initDeckGlMap(mapDiv: ElementRef, metricsRef: Subject) { this.layers.then(layers => { - this.map = new Deck({ + this.theMap = new Deck({ parent: mapDiv.nativeElement, initialViewState: INITIAL_VIEW_STATE, style: { position: 'relative', top: '0', bottom: '0' }, @@ -108,7 +128,7 @@ export class MapService { private async initMapLayer() { return new TileLayer({ - id: 'map-layer', + id: 'theMap-layer', data: environment.tileServerUrls, maxRequests: 20, pickable: false, @@ -169,7 +189,7 @@ export class MapService { this.layers.then(layers => { const clonedLayers = layers.slice(); clonedLayers[layerIndex] = layers[layerIndex].clone({ visible: value }); - this.map!.setProps({ layers: clonedLayers }); + this.theMap!.setProps({ layers: clonedLayers }); }); } diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss index 3a3d718..1c95eea 100644 --- a/src/assets/styles/variables.scss +++ b/src/assets/styles/variables.scss @@ -10,3 +10,4 @@ $tertiary: #fff; $accent: #404040; $accent-dark: #463A05FF; $accent-bright: #ffd617; +$dark-matter-bg: #262627;