Skip to content

Commit

Permalink
Fully implement functionality for show/hide of layers
Browse files Browse the repository at this point in the history
  • Loading branch information
holgerstolzenberg committed Mar 3, 2024
1 parent 04cfc67 commit 69bd5f9
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 76 deletions.
6 changes: 4 additions & 2 deletions src/app/map/map.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<div leaflet
[leafletOptions]="options"
[leafletLayers]="layers"
(leafletMapReady)="onMapReady($event)"
(leafletMapZoomStart)="onMapZoomStart()"
(leafletMapZoomEnd)="onMapZoomEnd($event)"
class="map">
<div *ngFor="let value of layers | async" [leafletLayer]="value"></div>
</div>
<div class="loader" *ngIf="showLoader">
<span>{{ showLoader }}</span>
</div>
<div class="loader" *ngIf="showLoader"><span>{{ showLoader }}</span></div>
122 changes: 91 additions & 31 deletions src/app/map/map.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { control, latLng, Layer, LeafletEvent, Map as LeafletMap, MapOptions } from 'leaflet';
import {
CircleMarker,
control,
GeoJSON,
latLng,
Layer,
LayerGroup,
LeafletEvent,
Map as LeafletMap,
MapOptions
} from 'leaflet';
import { MapService } from './map.service';
import { NGXLogger } from 'ngx-logger';
import { centerOfEurope, defaultZoom } from './map.constants';
import { Subscription } from 'rxjs';
import {
attributionOptions,
centerOfEurope,
defaultCapitolsStyle,
defaultZoom,
defaultZoomPanOptions,
euBorderStyle,
LayerIndices,
noDrawStyle
} from './map.constants';
import { Subject, takeUntil } from 'rxjs';

@Component({
selector: 'app-map',
Expand All @@ -24,53 +43,71 @@ export class MapComponent implements OnInit, OnDestroy {
attributionControl: false
};

layers: Layer[] = [];
layers: Promise<Layer[]>;
showLoader: boolean = false;

private map?: LeafletMap;
private theMap?: LeafletMap;
private theZoom?: number;
private onReset?: Subscription;
private onToMyLocation?: Subscription;

private onUnsubscribe$: Subject<boolean> = new Subject<boolean>();

constructor(
private log: NGXLogger,
private mapService: MapService
) {}
) {
this.layers = this.loadAllLayers();
}

ngOnInit(): void {
this.onReset = this.mapService.resetMap.subscribe(() => {
this.mapService.resetMap$.pipe(takeUntil(this.onUnsubscribe$)).subscribe(() => {
this.resetMap();
});

this.onToMyLocation = this.mapService.toMyLocation.subscribe(() => {
this.mapService.toMyLocation$.pipe(takeUntil(this.onUnsubscribe$)).subscribe(() => {
this.myLocation();
});

this.mapService.showEuBorders$.pipe(takeUntil(this.onUnsubscribe$)).subscribe(value => {
this.showEuBorders(value);
});

this.mapService.showCapitols$.pipe(takeUntil(this.onUnsubscribe$)).subscribe(value => {
this.showCapitols(value);
});
}

ngOnDestroy(): void {
this.onReset!.unsubscribe();
this.onToMyLocation!.unsubscribe();
this.unsubscribeAll();
this.disposeMap();
}

this.log.info('Disposing map');
private unsubscribeAll() {
this.onUnsubscribe$.next(true);
this.onUnsubscribe$.complete();
}

this.map!.clearAllEventListeners();
this.map!.remove();
private disposeMap() {
this.theMap!.clearAllEventListeners();
this.theMap!.remove();
this.log.info('Disposed theMap');
}

onMapReady(map: LeafletMap) {
this.map = map;

this.map.addControl(
control.attribution({
position: 'bottomleft',
prefix: ''
})
);
this.theMap = map;
this.log.info('Leaflet theMap ready');
this.theMap.addControl(control.attribution(attributionOptions));
this.theZoom = map.getZoom();
}

this.layers.push(this.mapService.getBaseLayer());
this.mapService.load(this.layers);
private async loadAllLayers() {
return this.mapService.loadAllLayers().then(([baseLayer, euBorderLayer, capitolsLayer]) => {
const layers = new Array<Layer>(3);
layers[LayerIndices.BASE_LAYER_INDEX] = baseLayer;
layers[LayerIndices.EU_LAYER_INDEX] = euBorderLayer;
layers[LayerIndices.CAPITOLS_LAYER_INDEX] = capitolsLayer;

this.theZoom = map.getZoom();
return layers;
});
}

onMapZoomStart() {
Expand All @@ -85,17 +122,18 @@ export class MapComponent implements OnInit, OnDestroy {
}

resetMap() {
this.map!.flyTo(centerOfEurope, defaultZoom);
this.theMap!.flyTo(centerOfEurope, defaultZoom);
// TODO clear my location marker
}

myLocation() {
navigator.geolocation.getCurrentPosition(
position => {
this.map!.flyTo(latLng(position.coords.latitude, position.coords.longitude), defaultZoom + 2, {
animate: true,
duration: 1
});
this.theMap!.flyTo(
latLng(position.coords.latitude, position.coords.longitude),
defaultZoom + 2,
defaultZoomPanOptions
);
},
err => {
this.log.error('Could not get geolocation', err);
Expand All @@ -104,4 +142,26 @@ export class MapComponent implements OnInit, OnDestroy {
}
);
}

private showEuBorders(value: boolean) {
this.layers
.then(layer => {
return layer[LayerIndices.EU_LAYER_INDEX] as GeoJSON;
})
.then(l => l.setStyle(value ? euBorderStyle : noDrawStyle));
}

private showCapitols(value: boolean) {
this.theMap!.closePopup();

this.layers
.then(layer => {
return layer[LayerIndices.CAPITOLS_LAYER_INDEX] as LayerGroup;
})
.then(capitols => capitols.getLayers().forEach(capitol => this.markerVisible(capitol, value)));
}

private markerVisible(layer: Layer, visible: boolean) {
(layer as CircleMarker).setStyle(visible ? defaultCapitolsStyle : noDrawStyle);
}
}
50 changes: 42 additions & 8 deletions src/app/map/map.constants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
// TODO find a way to configure color
import { circleMarker, latLng, layerGroup, tileLayer } from 'leaflet';
import {
CircleMarker,
circleMarker,
Control,
latLng,
LayerGroup,
layerGroup,
tileLayer,
ZoomPanOptions
} from 'leaflet';
import { environment } from '../../environments/environment';
import AttributionOptions = Control.AttributionOptions;

export const attributionOptions: AttributionOptions = {
position: 'bottomleft',
prefix: ''
};

export const defaultZoomPanOptions: ZoomPanOptions = {
animate: true,
duration: 1
};

export const noDrawStyle = { radius: 0, opacity: 0, fill: false, stroke: false, popup: false };

export const euBorderStyle = {
color: '#fff',
fillColor: '#fff',
opacity: 0.4,
weight: 0.6,
fillOpacity: 0.06
fillOpacity: 0.06,
fill: true,
stroke: true
};

export const darkMatterNoLabelsLayer = tileLayer(environment.tileServerUrl, {
Expand All @@ -18,14 +42,24 @@ export const darkMatterNoLabelsLayer = tileLayer(environment.tileServerUrl, {
export const centerOfEurope = latLng(54.526, 15.2551);

// TODO find a way to configure color
const berlin = circleMarker([52.52, 13.405], {
export const defaultCapitolsStyle = {
radius: 8,
weight: 2,
color: '#ffd617'
}).bindPopup('Berlin', {
closeButton: false
});
color: '#ffd617',
opacity: 1.0,
fill: true,
stroke: true,
popup: true
};

export const capitols = layerGroup([berlin]);
const berlin = circleMarker([52.52, 13.405], defaultCapitolsStyle).bindPopup('Berlin', { closeButton: false });

export const capitols: LayerGroup<CircleMarker> = layerGroup([berlin]);

export const defaultZoom = 5;

export enum LayerIndices {
BASE_LAYER_INDEX = 0,
EU_LAYER_INDEX = 1,
CAPITOLS_LAYER_INDEX = 2
}
68 changes: 51 additions & 17 deletions src/app/map/map.service.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,78 @@
import { EventEmitter, Injectable, Output } from '@angular/core';
import { geoJson, Layer } from 'leaflet';
import { EventEmitter, Injectable } from '@angular/core';
import { GeoJSON, geoJson, Layer } from 'leaflet';
import { HttpClient } from '@angular/common/http';
import { NGXLogger } from 'ngx-logger';
import { GeoJsonObject } from 'geojson';
import { capitols, centerOfEurope, darkMatterNoLabelsLayer, euBorderStyle } from './map.constants';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class MapService {
@Output() resetMap = new EventEmitter<string>();
@Output() toMyLocation = new EventEmitter<string>();
resetMap$ = new EventEmitter<string>();
toMyLocation$ = new EventEmitter<string>();
showEuBorders$ = new EventEmitter<boolean>();
showCapitols$ = new EventEmitter<boolean>();

private readonly euBordersLayer: Promise<GeoJSON>;

constructor(
private http: HttpClient,
private log: NGXLogger
) {}
) {
this.euBordersLayer = this.initEuBordersLayer();
}

getBaseLayer() {
return darkMatterNoLabelsLayer;
private async initEuBordersLayer() {
return firstValueFrom(this.http.get<GeoJsonObject>('./assets/geo-data/european-borders.json'))
.then(jsonResponse => {
this.log.debug('Loaded borders json', jsonResponse);
return geoJson(jsonResponse, { style: euBorderStyle });
})
.catch(err => {
this.log.error('Error loading EU borders geo json', err);
// TODO error notification
return geoJson();
});
}

getCenterOfEurope() {
return centerOfEurope;
}

load(layers: Layer[]) {
this.http.get<GeoJsonObject>('./assets/geo-data/european-borders.json').subscribe((json: GeoJsonObject) => {
this.log.info('Loaded borders json', json);
async loadAllLayers(): Promise<Layer[]> {
return Promise.all([this.getBaseLayer(), this.getEuBordersLayer(), this.getCapitolsLayer()]);
}

private async getBaseLayer() {
return darkMatterNoLabelsLayer;
}

layers.push(geoJson(json, { style: euBorderStyle }));
layers.push(capitols);
});
private async getEuBordersLayer() {
return this.euBordersLayer;
}

resetMapToEuropeanCenter() {
// TODO load capitols via HTTP
private async getCapitolsLayer() {
return capitols;
}

async resetMapToEuropeanCenter() {
this.log.info('Reset map');
this.resetMap.emit();
this.resetMap$.emit();
}

moveMapToMyLocation() {
async moveMapToMyLocation() {
this.log.info('Move map to my location');
this.toMyLocation.emit();
this.toMyLocation$.emit();
}

async doShowEuBorders(value: boolean) {
this.log.trace('Show EU borders', value);
this.showEuBorders$.emit(value);
}

async doShowCapitols(value: boolean) {
this.log.trace('Show capitols', value);
this.showCapitols$.emit(value);
}
}
8 changes: 4 additions & 4 deletions src/app/option-pane/option-pane.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
<div class="row">
<mat-slide-toggle
[checked]="showEuBorders"
(change)="euBordersChanged()">
(change)="euBordersChanged($event)">
{{ 'option-pane.show-eu-borders' | transloco }}
</mat-slide-toggle>
</div>
<div class="row">
<mat-slide-toggle
[checked]="showBerlin"
(change)="berlinChanged()">
{{ 'option-pane.show-berlin' | transloco }}
[checked]="showCapitols"
(change)="capitolsChanged($event)">
{{ 'option-pane.show-capitols' | transloco }}
</mat-slide-toggle>
</div>
</div>
Expand Down
Loading

0 comments on commit 69bd5f9

Please sign in to comment.