From 875f300ba1bcb4eae2ea0e3815c8bcff49baddc2 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 20 Jul 2019 19:52:47 +0200 Subject: [PATCH] adding zoom control and multiple layer support to maps --- .../model/diagnostics/ConfigDiagnostics.ts | 11 ++- common/config/public/ConfigClass.ts | 9 +- .../controls.lightbox.gallery.component.ts | 2 - .../lightbox.map.gallery.component.html | 88 ++++++++++++------- .../lightbox.map.gallery.component.ts | 85 +++++++++--------- frontend/app/ui/gallery/map/map.service.ts | 60 ++++++++----- .../_abstract/abstract.settings.component.ts | 3 + .../settings/map/map.settings.component.css | 9 ++ .../settings/map/map.settings.component.html | 55 +++++++++--- .../ui/settings/map/map.settings.component.ts | 10 +++ frontend/app/ui/settings/settings.service.ts | 2 +- .../usermanager.settings.component.html | 6 +- frontend/main.ts | 13 ++- test/e2e/app.e2e-spec.ts | 15 ---- test/e2e/app.po.ts | 11 --- test/e2e/tsconfig.e2e.json | 12 --- 16 files changed, 232 insertions(+), 159 deletions(-) delete mode 100644 test/e2e/app.e2e-spec.ts delete mode 100644 test/e2e/app.po.ts delete mode 100644 test/e2e/tsconfig.e2e.json diff --git a/backend/model/diagnostics/ConfigDiagnostics.ts b/backend/model/diagnostics/ConfigDiagnostics.ts index 54c6d3e9..6c6d666d 100644 --- a/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/backend/model/diagnostics/ConfigDiagnostics.ts @@ -163,8 +163,15 @@ export class ConfigDiagnostics { throw new Error('Mapbox needs a valid api key.'); } if (map.mapProvider === ClientConfig.MapProviders.Custom && - (!map.tileUrl || map.tileUrl.length === 0)) { - throw new Error('Custom maps need a valid tile url'); + (!map.customLayers || map.customLayers.length === 0)) { + throw new Error('Custom maps need at least one valid layer'); + } + if (map.mapProvider === ClientConfig.MapProviders.Custom) { + map.customLayers.forEach(l => { + if (!l.url || l.url.length === 0) { + throw new Error('Custom maps url need to be a valid layer'); + } + }); } } diff --git a/common/config/public/ConfigClass.ts b/common/config/public/ConfigClass.ts index 77da5861..436269b8 100644 --- a/common/config/public/ConfigClass.ts +++ b/common/config/public/ConfigClass.ts @@ -31,11 +31,16 @@ export module ClientConfig { enabled: boolean; } + export interface MapLayers { + name: string; + url: string; + } + export interface MapConfig { enabled: boolean; mapProvider: MapProviders; mapboxAccessToken: string; - tileUrl: string; + customLayers: MapLayers[]; } export interface ThumbnailConfig { @@ -125,7 +130,7 @@ export class PublicConfigClass { enabled: true, mapProvider: ClientConfig.MapProviders.OpenStreetMap, mapboxAccessToken: '', - tileUrl: '' + customLayers: [{name: 'street', url: ''}] }, RandomPhoto: { enabled: true diff --git a/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts b/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts index feeddf3c..0f34fb60 100644 --- a/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts +++ b/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts @@ -90,12 +90,10 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { } public containerWidth() { - console.log(this.photoFrameDim); return this.root.nativeElement.width; } public containerHeight() { - console.log(this.photoFrameDim); return this.root.nativeElement.height; } diff --git a/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.html b/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.html index fe505d64..aaca1034 100644 --- a/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.html +++ b/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.html @@ -10,46 +10,70 @@ - - - - - - - - -
+ + + + + + + + + + + + + + +
-
-
-
+
+
+
+ + + + + + + + + + -
diff --git a/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts b/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts index 3c959b4f..33d0019c 100644 --- a/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts +++ b/frontend/app/ui/gallery/map/lightbox/lightbox.map.gallery.component.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, HostListener, Input, OnChanges, ViewChild, AfterViewInit} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, HostListener, Input, OnChanges, OnInit, ViewChild} from '@angular/core'; import {PhotoDTO} from '../../../../../../common/entities/PhotoDTO'; import {Dimension} from '../../../../model/IRenderable'; import {FullScreenService} from '../../fullscreen.service'; @@ -23,9 +23,9 @@ import {FixOrientationPipe} from '../../../../pipes/FixOrientationPipe'; }) export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { + @Input() photos: PhotoDTO[]; @Input() gpxFiles: FileDTO[]; - private startPosition: Dimension = null; public lightboxDimension: Dimension = {top: 0, left: 0, width: 0, height: 0}; public mapDimension: Dimension = {top: 0, left: 0, width: 0, height: 0}; public visible = false; @@ -33,27 +33,34 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { public opacity = 1.0; mapPhotos: MapPhoto[] = []; paths: LatLng[][] = []; - - @ViewChild('root', {static: false}) elementRef: ElementRef; - @ViewChild('yagaMap', {static: false}) yagaMap: MapComponent; - + @ViewChild('root', {static: true}) elementRef: ElementRef; + @ViewChild('yagaMap', {static: true}) yagaMap: MapComponent; public smallIconSize = new Point(Config.Client.Thumbnail.iconSize * 0.75, Config.Client.Thumbnail.iconSize * 0.75); public iconSize = new Point(Config.Client.Thumbnail.iconSize, Config.Client.Thumbnail.iconSize); + private startPosition: Dimension = null; constructor(public fullScreenService: FullScreenService, private thumbnailService: ThumbnailManagerService, public mapService: MapService) { } + ngOnChanges() { if (this.visible === false) { return; } this.showImages(); + } ngAfterViewInit() { - + let i = 0; + this.yagaMap.eachLayer(l => { + if (i >= 3 || (this.paths.length === 0 && i >= 2)) { + this.yagaMap.removeLayer(l); + } + ++i; + }); } @HostListener('window:resize', ['$event']) @@ -75,7 +82,6 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { } - public async show(position: Dimension) { this.hideImages(); this.visible = true; @@ -145,6 +151,7 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { const iconTh = this.thumbnailService.getIcon(new MediaIcon(p)); iconTh.Visible = true; const obj: MapPhoto = { + name: p.name, lat: p.metadata.positionData.GPSData.latitude, lng: p.metadata.positionData.GPSData.longitude, iconThumbnail: iconTh, @@ -175,29 +182,6 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { } - private centerMap() { - if (this.mapPhotos.length > 0) { - this.yagaMap.fitBounds(this.mapPhotos); - } - } - - - private async loadGPXFiles(): Promise { - this.paths = []; - for (let i = 0; i < this.gpxFiles.length; i++) { - const file = this.gpxFiles[i]; - const path = await this.mapService.getMapPath(file); - if (file !== this.gpxFiles[i]) { // check race condition - return; - } - if (path.length === 0) { - continue; - } - this.paths.push(path); - } - } - - public loadPreview(mp: MapPhoto) { mp.preview.thumbnail.load(); mp.preview.thumbnail.CurrentlyWaiting = true; @@ -211,15 +195,6 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { this.mapPhotos = []; } - - private getScreenWidth() { - return window.innerWidth; - } - - private getScreenHeight() { - return window.innerHeight; - } - @HostListener('window:keydown', ['$event']) onKeyPress(e: KeyboardEvent) { if (this.visible !== true) { @@ -241,10 +216,40 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit { } } + private centerMap() { + if (this.mapPhotos.length > 0) { + this.yagaMap.fitBounds(this.mapPhotos); + } + } + + private async loadGPXFiles(): Promise { + this.paths = []; + for (let i = 0; i < this.gpxFiles.length; i++) { + const file = this.gpxFiles[i]; + const path = await this.mapService.getMapPath(file); + if (file !== this.gpxFiles[i]) { // check race condition + return; + } + if (path.length === 0) { + continue; + } + this.paths.push(path); + } + } + + private getScreenWidth() { + return window.innerWidth; + } + + private getScreenHeight() { + return window.innerHeight; + } + } export interface MapPhoto { + name: string; lat: number; lng: number; iconUrl?: string; diff --git a/frontend/app/ui/gallery/map/map.service.ts b/frontend/app/ui/gallery/map/map.service.ts index de96b1cd..f3098ee7 100644 --- a/frontend/app/ui/gallery/map/map.service.ts +++ b/frontend/app/ui/gallery/map/map.service.ts @@ -4,30 +4,27 @@ import {FileDTO} from '../../../../../common/entities/FileDTO'; import {Utils} from '../../../../../common/Utils'; import {Config} from '../../../../../common/config/public/Config'; import {ClientConfig} from '../../../../../common/config/public/ConfigClass'; +import MapLayers = ClientConfig.MapLayers; @Injectable() export class MapService { - + private static readonly OSMLAYERS = [{name: 'street', url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'}]; + private static MAPBOXLAYERS: MapLayers[] = []; constructor(private networkService: NetworkService) { - } - - - public async getMapPath(file: FileDTO): Promise { - const filePath = Utils.concatUrls(file.directory.path, file.directory.name, file.name); - const gpx = await this.networkService.getXML('/gallery/content/' + filePath); - const elements = gpx.getElementsByTagName('trkpt'); - const points: MapPath[] = []; - for (let i = 0; i < elements.length; i++) { - points.push({ - lat: parseFloat(elements[i].getAttribute('lat')), - lng: parseFloat(elements[i].getAttribute('lon')) - }); + MapService.MAPBOXLAYERS = [{ + name: 'street', url: 'https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=' + + Config.Client.Map.mapboxAccessToken + }, { + name: 'satellite', url: 'https://api.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.png?access_token=' + + Config.Client.Map.mapboxAccessToken + }, { + name: 'hybrid', url: 'https://api.tiles.mapbox.com/v4/mapbox.streets-satellite/{z}/{x}/{y}.png?access_token=' + + Config.Client.Map.mapboxAccessToken } - return points; + ]; } - public get ShortAttributions(): string[] { const yaga = 'YAGA'; const lf = 'leaflet-ng2'; @@ -62,14 +59,33 @@ export class MapService { } public get MapLayer(): string { - if (Config.Client.Map.mapProvider === ClientConfig.MapProviders.Custom) { - return Config.Client.Map.tileUrl; + return this.Layers[0].url; + } + + public get Layers(): { name: string, url: string }[] { + switch (Config.Client.Map.mapProvider) { + case ClientConfig.MapProviders.Custom: + return Config.Client.Map.customLayers; + case ClientConfig.MapProviders.Mapbox: + return MapService.MAPBOXLAYERS; + case ClientConfig.MapProviders.OpenStreetMap: + return MapService.OSMLAYERS; } - if (Config.Client.Map.mapProvider === ClientConfig.MapProviders.Mapbox) { - return 'https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=' - + Config.Client.Map.mapboxAccessToken; + } + + + public async getMapPath(file: FileDTO): Promise { + const filePath = Utils.concatUrls(file.directory.path, file.directory.name, file.name); + const gpx = await this.networkService.getXML('/gallery/content/' + filePath); + const elements = gpx.getElementsByTagName('trkpt'); + const points: MapPath[] = []; + for (let i = 0; i < elements.length; i++) { + points.push({ + lat: parseFloat(elements[i].getAttribute('lat')), + lng: parseFloat(elements[i].getAttribute('lon')) + }); } - return 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + return points; } } diff --git a/frontend/app/ui/settings/_abstract/abstract.settings.component.ts b/frontend/app/ui/settings/_abstract/abstract.settings.component.ts index 3c9412ea..b0a76f54 100644 --- a/frontend/app/ui/settings/_abstract/abstract.settings.component.ts +++ b/frontend/app/ui/settings/_abstract/abstract.settings.component.ts @@ -67,6 +67,9 @@ export abstract class SettingsComponent @@ -33,15 +33,43 @@ -
- -
- - - The map module will use this url to fetch the map tiles. - +
+ + + + + + + + + + + + + +
NameTile Url*
+ + + +
+ +
+ + *The map module will use these urls to fetch the map tiles. + +
+
+
@@ -52,7 +80,8 @@ [(ngModel)]="settings.mapboxAccessToken" name="mapboxAccessToken" id="mapboxAccessToken" required> - MapBox needs an access token to work, create one at https://www.mapbox.com. + MapBox needs an access token to work, create one at +  https://www.mapbox.com.
diff --git a/frontend/app/ui/settings/map/map.settings.component.ts b/frontend/app/ui/settings/map/map.settings.component.ts index 2bc29c3b..2864f1e2 100644 --- a/frontend/app/ui/settings/map/map.settings.component.ts +++ b/frontend/app/ui/settings/map/map.settings.component.ts @@ -32,6 +32,16 @@ export class MapSettingsComponent extends SettingsComponent diff --git a/frontend/main.ts b/frontend/main.ts index d4d307db..5d207f1f 100644 --- a/frontend/main.ts +++ b/frontend/main.ts @@ -1,11 +1,16 @@ import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {enableProdMode} from '@angular/core'; +import {ApplicationRef, enableProdMode} from '@angular/core'; import {environment} from './environments/environment'; import {AppModule} from './app/app.module'; +import {enableDebugTools} from '@angular/platform-browser'; if (environment.production) { enableProdMode(); -} - -platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); +} +platformBrowserDynamic().bootstrapModule(AppModule).then(moduleRef => { + const applicationRef = moduleRef.injector.get(ApplicationRef); + const componentRef = applicationRef.components[0]; + // allows to run `ng.profiler.timeChangeDetection();` + enableDebugTools(componentRef); +}).catch(err => console.error(err)); diff --git a/test/e2e/app.e2e-spec.ts b/test/e2e/app.e2e-spec.ts deleted file mode 100644 index e52439d0..00000000 --- a/test/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {TestProjectPage} from './app.po'; - -describe('test-project App', () => { - let page: TestProjectPage; - - beforeEach(() => { - page = new TestProjectPage(); - }); - - it('should display welcome message', async (done) => { - page.navigateTo(); - expect(await page.getParagraphText()).toEqual('Welcome to app!!'); - done(); - }); -}); diff --git a/test/e2e/app.po.ts b/test/e2e/app.po.ts deleted file mode 100644 index f4eef22e..00000000 --- a/test/e2e/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {browser, by, element} from 'protractor'; - -export class TestProjectPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('app-root h1')).getText(); - } -} diff --git a/test/e2e/tsconfig.e2e.json b/test/e2e/tsconfig.e2e.json deleted file mode 100644 index e2a9a2fc..00000000 --- a/test/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "node" - ] - } -}