Skip to content

Commit

Permalink
Merge pull request #752 from gisaia/feat/basemap_sg
Browse files Browse the repository at this point in the history
Simplify configuration of protomap basemaps
  • Loading branch information
MohamedHamouGisaia authored Oct 26, 2023
2 parents 4886481 + 78345f1 commit 57ffaf8
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 324 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div *ngIf="showList" class="basemap-container">
<ng-container *ngIf="isOnline; else offline">
<div *ngFor="let style of onlineBasemaps?._styles" class="basemap" [class.selected]="style.name === onlineBasemaps?._selectedStyle?.name" (click)="onChangeOnlineBasemap(style)">
<ng-container>
<div *ngFor="let style of basemaps?._styles" class="basemap" [class.selected]="style.name === basemaps?._selectedStyle?.name" (click)="onChangeBasemap(style)">
<div class="image">
<img *ngIf="!!style?.image && style?.image !== ''" src="{{style?.image}}" (error)="style.image = null" />
<div *ngIf="!style?.image || (!!style?.image && style?.image === '')" class="no-image">
Expand All @@ -10,14 +10,4 @@
</div>
</div>
</ng-container>
<ng-template #offline>
<div *ngFor="let theme of offlineBasemaps?._themes" class="basemap" [class.selected]="theme.name === offlineBasemaps?._selectedTheme?.name" (click)="onChangeOfflineBasemap(theme)">
<div class="image">
<div class="no-image">
<mat-icon>wallpaper</mat-icon>
</div>
<div class="name">{{theme.name | translate}}</div>
</div>
</div>
</ng-template>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { MapSource } from '../mapgl/model/mapSource';
import { MapglService } from '../../services/mapgl.service';
import { HttpClient } from '@angular/common/http';
import { MapboxBasemapService } from '../mapgl/basemaps/basemap.service';
import { BasemapStyle, OfflineBasemapTheme } from '../mapgl/basemaps/basemap.config';
import { OfflineBasemap } from '../mapgl/basemaps/offline-basemap';
import { OnlineBasemap } from '../mapgl/basemaps/online-basemap';
import { BasemapStyle } from '../mapgl/basemaps/basemap.config';
import { ArlasBasemaps } from '../mapgl/basemaps/basemaps';

@Component({
selector: 'arlas-mapgl-basemap',
Expand All @@ -24,69 +23,51 @@ export class MapglBasemapComponent implements OnInit {
@Output() public blur = new Subject<void>();

public showList = false;
public isOnline = true;
public onlineBasemaps: OnlineBasemap;
public offlineBasemaps: OfflineBasemap;
public basemaps: ArlasBasemaps;


public constructor(
private mapglService: MapglService,
private basemapService: MapboxBasemapService,
private http: HttpClient) { }

public ngOnInit(): void {
this.isOnline = this.basemapService.isOnline();
if (this.isOnline) {
this.initOnlineBasemaps();
} else {
this.initOfflineBasemaps();
}
this.initBasemaps();
}

private initOnlineBasemaps() {
this.onlineBasemaps = this.basemapService.onlineBasemaps;
const styles = this.onlineBasemaps.styles();
if (!!this.onlineBasemaps && !!styles) {
this.showList = styles.length > 1;
styles.filter(bm => !bm.image).forEach(bm => {
const splitUrl = bm.styleFile.toString().split('/style.json?key=');
if (splitUrl.length === 2) {
bm.image = `${splitUrl[0]}/0/0/0.png?key=${splitUrl[1]}`;
}
});
private initBasemaps() {
this.basemaps = this.basemapService.basemaps;
if (!!this.basemaps) {
const styles = this.basemaps.styles();
if (!!styles) {
this.showList = styles.length > 1;
styles.filter(bm => !bm.image).forEach(bm => {
if (bm.type !== 'protomap') {
const splitUrl = bm.styleFile.toString().split('/style.json?key=');
if (splitUrl.length === 2) {
bm.image = `${splitUrl[0]}/0/0/0.png?key=${splitUrl[1]}`;
}
}
});
}
}
}

private initOfflineBasemaps() {
this.offlineBasemaps = this.basemapService.offlineBasemaps;
if (!!this.offlineBasemaps && !!this.offlineBasemaps.themes()) {
this.showList = this.offlineBasemaps.themes().length > 1;
public onChangeBasemap(newBasemap: BasemapStyle) {
const selectedBasemap = this.basemaps.getSelected();
if (selectedBasemap.type === 'protomap') {
this.basemapService.removeProtomapBasemap(this.map);
}
this.setBaseMapStyle(newBasemap);
}

public onChangeOfflineBasemap(selectedTheme: OfflineBasemapTheme) {
this.basemapService.changeOfflineBasemap(this.map, selectedTheme);
}

public onChangeOnlineBasemap(selectedStyle: BasemapStyle) {
this.setBaseMapStyle(selectedStyle.styleFile);
localStorage.setItem(this.LOCAL_STORAGE_BASEMAPS, JSON.stringify(selectedStyle));
this.onlineBasemaps.setSelected(selectedStyle);
}

public setBaseMapStyle(style: string | mapboxgl.Style) {
public setBaseMapStyle(newBasemap: BasemapStyle) {
if (this.map) {
const selectedStyle = this.onlineBasemaps.getSelected();
if (typeof selectedStyle.styleFile === 'string') {
this.http.get(selectedStyle.styleFile).subscribe((s: any) => {
this.setStyle(s, style);
});
} else {
this.setStyle(selectedStyle.styleFile, style);
}
this.setStyle(this.basemaps.getSelected().styleFile as mapboxgl.Style, newBasemap);
}
}

public setStyle(s: mapboxgl.Style, style: string | mapboxgl.Style) {
public setStyle(s: mapboxgl.Style, newBasemap: BasemapStyle) {
const selectedBasemapLayersSet = new Set<string>();
const layers: Array<mapboxgl.Layer> = (<mapboxgl.Map>this.map).getStyle().layers;
const sources = (<mapboxgl.Map>this.map).getStyle().sources;
Expand All @@ -95,7 +76,7 @@ export class MapglBasemapComponent implements OnInit {
}
const layersToSave = new Array<mapboxgl.Layer>();
const sourcesToSave = new Array<MapSource>();
layers.filter((l: mapboxgl.Layer) => !selectedBasemapLayersSet.has(l.id)).forEach(l => {
layers.filter((l: mapboxgl.Layer) => !selectedBasemapLayersSet.has(l.id) && !!l.source).forEach(l => {
layersToSave.push(l);
if (sourcesToSave.filter(ms => ms.id === l.source.toString()).length === 0) {
sourcesToSave.push({ id: l.source.toString(), source: sources[l.source.toString()] });
Expand All @@ -110,9 +91,16 @@ export class MapglBasemapComponent implements OnInit {
}
});
}
this.map.setStyle(style).once('styledata', () => {
const initStyle = this.basemapService.getInitStyle(newBasemap);
this.map.setStyle(initStyle).once('styledata', () => {
this.mapglService.addSourcesToMap(sourcesToSave, this.map);
layersToSave.forEach(l => this.map.addLayer(l as AnyLayer));
localStorage.setItem(this.LOCAL_STORAGE_BASEMAPS, JSON.stringify(newBasemap));
this.basemaps.setSelected(newBasemap);
if (newBasemap.type === 'protomap') {
this.basemapService.addProtomapBasemap(this.map);
this.basemapService.notifyProtomapAddition();
}
this.basemapChanged.emit();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,10 @@
*/


// DISCLAIMER
// -- The word 'Style' is reserved to online basemaps.
// -- The word 'Theme' is reserved to offline basemaps.

export interface BasemapsConfig {
isOnline: boolean;
onlineConfig?: OnlineBasemapConfig;
offlineConfig?: OfflineBasemapsConfig;
}

export interface OnlineBasemapConfig {
styles: BasemapStyle[];
defaultStyle: BasemapStyle;
}

export interface OfflineBasemapsConfig {
/** Path to pmtiles file */
url: string;
glyphsUrl: string;
themes: OfflineBasemapTheme[];
defaultTheme: OfflineBasemapTheme;
}


export interface OfflineBasemapTheme {
layers: mapboxgl.Layer[];
name: string;
}

export interface BasemapStyle {
name: string;
styleFile: string | mapboxgl.Style;
image?: string;
type?: 'protomap' | 'mapbox';
}

Original file line number Diff line number Diff line change
Expand Up @@ -21,97 +21,99 @@ import { Injectable } from '@angular/core';
import { ArlasBasemaps } from './basemaps';
import * as pmtiles from 'pmtiles';
import { CustomProtocol } from '../custom-protocol/mapbox-gl-custom-protocol';
import { OfflineBasemapTheme } from './basemap.config';
import { BasemapStyle } from './basemap.config';
import mapboxgl from 'mapbox-gl';
import { OfflineBasemap } from './offline-basemap';
import { Subject } from 'rxjs';
import { OnlineBasemap } from './online-basemap';
import { Observable, Subject, forkJoin, of, tap } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class MapboxBasemapService {
private basemaps: ArlasBasemaps;
private LOCAL_STORAGE_BASEMAPS = 'arlas_last_base_map';
public basemaps: ArlasBasemaps;

public onlineBasemaps: OnlineBasemap;
public offlineBasemaps: OfflineBasemap;
private protomapBasemapAddedSource = new Subject<boolean>();
public protomapBasemapAdded$ = this.protomapBasemapAddedSource.asObservable();

private offlineBasemapChangedSource = new Subject<boolean>();
public offlineBasemapChanged$ = this.offlineBasemapChangedSource.asObservable();
public constructor(private http: HttpClient) {}

public setBasemaps(basemaps: ArlasBasemaps) {
this.basemaps = basemaps;
this.onlineBasemaps = basemaps.onlineBasemaps;
this.offlineBasemaps = basemaps.offlineBasemaps;
}

/** Add offline basemap only if configured. */
public addOfflineBasemap(map: mapboxgl.Map) {
if (!!this.basemaps && this.basemaps.isOnline) {
return;
public addProtomapBasemap(map: mapboxgl.Map) {
const selectedBasemap = this.basemaps.getSelected();
if (selectedBasemap.type === 'protomap') {
const styleFile = selectedBasemap.styleFile as mapboxgl.Style;
const pmtilesSource = styleFile.sources['arlas_protomaps_source'];
if (pmtilesSource) {
map.addSource('arlas_protomaps_source', pmtilesSource as any);
styleFile.layers.forEach(l =>{
if (!!map.getLayer(l.id)) {
map.removeLayer(l.id);
}
map.addLayer(l as any);
});
}
} else {
/** no action needed. The base map has been added already thanks to getInitStyle */
}
}

public notifyProtomapAddition() {
this.protomapBasemapAddedSource.next(true);
}

public removeProtomapBasemap(map: mapboxgl.Map) {
const selectedBasemap = this.basemaps.getSelected();
if (selectedBasemap.type === 'protomap') {
(selectedBasemap.styleFile as mapboxgl.Style).layers.forEach(l => {
if (!!map.getLayer(l.id)) {
map.removeLayer(l.id);
}
});
map.removeSource('arlas_protomaps_source');
}
}

public declareProtomapProtocol(map: mapboxgl.Map) {
const protocol = new pmtiles.Protocol();
/** addSourceType is private */
(map as any).addSourceType('pmtiles-type', CustomProtocol(mapboxgl).vector, (e) => e && console.error('There was an error', e));
(mapboxgl as any).addProtocol('pmtiles', protocol.tile);
const pmtilesUrl = this.offlineBasemaps.getUrl();
map.addSource('protomaps_source', {
'type': 'pmtiles-type',
'tiles': ['pmtiles://' + pmtilesUrl + '/{z}/{x}/{y}'],
'maxzoom': 21
} as any);
}

if (this.offlineBasemaps && !!this.offlineBasemaps.getSelected()) {
this.offlineBasemaps.getSelected().layers.forEach(l =>{
map.addLayer(l as any);
});
public getInitStyle(selected: BasemapStyle) {
if (selected.type === 'protomap') {
/** This is necessaty to make it work for mapbox. */
const clonedStyleFile: mapboxgl.Style = Object.assign({}, selected.styleFile as mapboxgl.Style);
clonedStyleFile.sources = {};
clonedStyleFile.layers = [{
id: 'backgrounds',
type: 'background',
paint: {
'background-color': 'rgba(0,0,0,0)'
}
}];
return clonedStyleFile;
}
return selected.styleFile;
}

public changeOfflineBasemap(map: mapboxgl.Map, newTheme: OfflineBasemapTheme) {
const currentTheme = this.offlineBasemaps.getSelected();
currentTheme.layers.forEach(l => {
if (!!map.getLayer(l.id)) {
map.removeLayer(l.id);
}
});
this.offlineBasemaps.setSelected(newTheme).getSelected().layers.forEach(l =>{
map.addLayer(l as any);
});
this.offlineBasemapChangedSource.next(true);
}

public isOnline(): boolean {
if (!this.basemaps) {
throw new Error('No basemap configuration is set');
}
return !!this.basemaps && this.basemaps.isOnline;
public fetchSources$() {
const sources$: Observable<mapboxgl.Style>[] = [];
this.basemaps.styles().forEach(s => sources$.push(this.getStyleFile(s).pipe(
tap(sf => s.styleFile = sf as mapboxgl.Style)
)));
return forkJoin(sources$);
}

public getInitStyle() {
if (this.basemaps.isOnline) {
const initStyle = this.onlineBasemaps.getSelected();
return initStyle.styleFile;
private getStyleFile(b: BasemapStyle): Observable<mapboxgl.Style> {
if (typeof b.styleFile === 'string') {
return this.http.get(b.styleFile) as Observable<mapboxgl.Style>;
} else {
return {
version: 8,
name: 'Empty',
metadata: {
'mapbox:autocomposite': true
},
glyphs: this.offlineBasemaps.getGlyphs(),
sources: {},
layers: [
{
id: 'backgrounds',
type: 'background',
paint: {
'background-color': 'rgba(0,0,0,0)'
}
}
]
} as mapboxgl.Style;
return of(b.styleFile);
}
}
}
Loading

0 comments on commit 57ffaf8

Please sign in to comment.