forked from MapComplete/MapComplete
		
	Feature: add Protomaps as background layers
This commit is contained in:
		
							parent
							
								
									4ff67addf7
								
							
						
					
					
						commit
						f2a6225c80
					
				
					 8 changed files with 272 additions and 104 deletions
				
			
		|  | @ -7,6 +7,10 @@ export type EliCategory = | |||
|     | "qa" | ||||
|     | "elevation" | ||||
|     | "other" | ||||
| 
 | ||||
| /** | ||||
|  * This class has grown beyond the point of only containing Raster Layers | ||||
|  */ | ||||
| export interface RasterLayerProperties { | ||||
|     /** | ||||
|      * The name of the imagery source | ||||
|  | @ -19,7 +23,8 @@ export interface RasterLayerProperties { | |||
| 
 | ||||
|     readonly url: string | ||||
|     readonly category?: string | EliCategory | ||||
|     readonly type?: "vector" | string | ||||
|     readonly type?: "vector" | "raster" | string | ||||
|     readonly style?: string, | ||||
| 
 | ||||
|     readonly attribution?: { | ||||
|         readonly url?: string | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import type { Map as MLMap } from "maplibre-gl" | ||||
| import { Map as MLMap } from "maplibre-gl" | ||||
| import { Map as MlMap, SourceSpecification } from "maplibre-gl" | ||||
| import maplibregl from "maplibre-gl"; | ||||
| import { RasterLayerPolygon } from "../../Models/RasterLayers" | ||||
| import { Utils } from "../../Utils" | ||||
| import { BBox } from "../../Logic/BBox" | ||||
|  | @ -11,7 +12,7 @@ import { RasterLayerProperties } from "../../Models/RasterLayerProperties" | |||
| import * as htmltoimage from "html-to-image" | ||||
| import RasterLayerHandler from "./RasterLayerHandler" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| import { Protocol } from "pmtiles"; | ||||
| /** | ||||
|  * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` | ||||
|  */ | ||||
|  | @ -46,6 +47,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|     readonly pitch: UIEventSource<number> | ||||
|     readonly useTerrain: Store<boolean> | ||||
| 
 | ||||
|    private static pmtilesInited = false | ||||
|     /** | ||||
|      * Functions that are called when one of those actions has happened | ||||
|      * @private | ||||
|  | @ -55,6 +57,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|     private readonly _maplibreMap: Store<MLMap> | ||||
| 
 | ||||
|     constructor(maplibreMap: Store<MLMap>, state?: Partial<MapProperties>) { | ||||
|         if(!MapLibreAdaptor.pmtilesInited){ | ||||
|             maplibregl.addProtocol("pmtiles",new Protocol().tile); | ||||
|             MapLibreAdaptor.pmtilesInited = true | ||||
|         } | ||||
|         this._maplibreMap = maplibreMap | ||||
| 
 | ||||
|         this.location = state?.location ?? new UIEventSource(undefined) | ||||
|  | @ -103,6 +109,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|         } | ||||
| 
 | ||||
|         maplibreMap.addCallbackAndRunD((map) => { | ||||
| 
 | ||||
|             map.on("load", () => { | ||||
|                 self.MoveMapToCurrentLoc(self.location.data) | ||||
|                 self.SetZoom(self.zoom.data) | ||||
|  | @ -212,7 +219,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|     } | ||||
| 
 | ||||
|     public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification { | ||||
|         return RasterLayerHandler.prepareWmsSource(layer) | ||||
|         return RasterLayerHandler.prepareSource(layer) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import { Map as MLMap, SourceSpecification } from "maplibre-gl" | ||||
| import { Map as MLMap, RasterSourceSpecification, VectorTileSource } from "maplibre-gl" | ||||
| import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { RasterLayerPolygon } from "../../Models/RasterLayers" | ||||
| import { RasterLayerProperties } from "../../Models/RasterLayerProperties" | ||||
| import { Utils } from "../../Utils" | ||||
| import { VectorSourceSpecification } from "@maplibre/maplibre-gl-style-spec" | ||||
| 
 | ||||
| class SingleBackgroundHandler { | ||||
|     // Value between 0 and 1.0
 | ||||
|  | @ -17,6 +18,7 @@ class SingleBackgroundHandler { | |||
|      */ | ||||
|     public static readonly DEACTIVATE_AFTER = 60 | ||||
|     private fadeStep = 0.1 | ||||
| 
 | ||||
|     constructor( | ||||
|         map: Store<MLMap>, | ||||
|         targetLayer: RasterLayerPolygon, | ||||
|  | @ -75,6 +77,7 @@ class SingleBackgroundHandler { | |||
|             this.fadeIn() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async awaitStyleIsLoaded(): Promise<void> { | ||||
|         const map = this._map.data | ||||
|         if (!map) { | ||||
|  | @ -85,11 +88,11 @@ class SingleBackgroundHandler { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async enable(){ | ||||
|     private async enable() { | ||||
|         let ttl = 15 | ||||
|         await this.awaitStyleIsLoaded() | ||||
|         while(!this.tryEnable() && ttl > 0){ | ||||
|             ttl --; | ||||
|         while (!this.tryEnable() && ttl > 0) { | ||||
|             ttl-- | ||||
|             await Utils.waitFor(250) | ||||
|         } | ||||
|     } | ||||
|  | @ -110,9 +113,10 @@ class SingleBackgroundHandler { | |||
|             // The background layer is already an OSM-based map or another map, so we don't want anything from the baselayer
 | ||||
|             addLayerBeforeId = undefined | ||||
|         } | ||||
| 
 | ||||
|         if (!map.getSource(background.id)) { | ||||
|             try { | ||||
|                 map.addSource(background.id, RasterLayerHandler.prepareWmsSource(background)) | ||||
|                 map.addSource(background.id, RasterLayerHandler.prepareSource(background)) | ||||
|             } catch (e) { | ||||
|                 return false | ||||
|             } | ||||
|  | @ -122,21 +126,25 @@ class SingleBackgroundHandler { | |||
|                 .getStyle() | ||||
|                 .layers.find((l) => l.id.startsWith("mapcomplete_"))?.id | ||||
| 
 | ||||
|             map.addLayer( | ||||
|                 { | ||||
|                     id: background.id, | ||||
|                     type: "raster", | ||||
|                     source: background.id, | ||||
|                     paint: { | ||||
|                         "raster-opacity": 0, | ||||
|                     }, | ||||
|                 }, | ||||
|                 addLayerBeforeId | ||||
|             ) | ||||
|             if (background.type === "vector") { | ||||
|                 map.setStyle(background["style"]) | ||||
|             } else { | ||||
| 
 | ||||
|             this.opacity.addCallbackAndRun((o) => { | ||||
|                 map.setPaintProperty(background.id, "raster-opacity", o) | ||||
|             }) | ||||
|                 map.addLayer( | ||||
|                     { | ||||
|                         id: background.id, | ||||
|                         type: "raster", | ||||
|                         source: background.id, | ||||
|                         paint: { | ||||
|                             "raster-opacity": 0 | ||||
|                         } | ||||
|                     }, | ||||
|                     addLayerBeforeId | ||||
|                 ) | ||||
|                 this.opacity.addCallbackAndRun((o) => { | ||||
|                     map.setPaintProperty(background.id, "raster-opacity", o) | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
|  | @ -168,7 +176,14 @@ export default class RasterLayerHandler { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification { | ||||
|     public static prepareSource(layer: RasterLayerProperties): RasterSourceSpecification | VectorSourceSpecification { | ||||
|         if (layer.type === "vector") { | ||||
|             const vs: VectorSourceSpecification = { | ||||
|                 type: "vector", | ||||
|                 url: layer.url | ||||
|             } | ||||
|             return vs | ||||
|         } | ||||
|         return { | ||||
|             type: "raster", | ||||
|             // use the tiles option to specify a 256WMS tile source URL
 | ||||
|  | @ -178,7 +193,7 @@ export default class RasterLayerHandler { | |||
|             minzoom: layer["min_zoom"] ?? 1, | ||||
|             maxzoom: layer["max_zoom"] ?? 25, | ||||
|             // Bit of a hack, but seems to work
 | ||||
|             scheme: layer.url.includes("{-y}") ? "tms" : "xyz", | ||||
|             scheme: layer.url.includes("{-y}") ? "tms" : "xyz" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -192,7 +207,7 @@ export default class RasterLayerHandler { | |||
|             "{width}": "" + size, | ||||
|             "{height}": "" + size, | ||||
|             "{zoom}": "{z}", | ||||
|             "{-y}": "{y}", | ||||
|             "{-y}": "{y}" | ||||
|         } | ||||
| 
 | ||||
|         for (const key in toReplace) { | ||||
|  |  | |||
|  | @ -88,7 +88,8 @@ | |||
|         "text": "Stamen/Stadiamaps", | ||||
|         "url": "https://stadiamaps.com/" | ||||
|       } | ||||
|     },   { | ||||
|     }, | ||||
|     { | ||||
|       "name": "Stamen Watercolor", | ||||
|       "url": "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg?key=14c5a900-7137-42f7-9cb9-fff0f4696f75", | ||||
|       "category": "osmbasedmap", | ||||
|  | @ -142,8 +143,6 @@ | |||
|         "url": "https://carto.com/" | ||||
|       } | ||||
|     }, | ||||
|      | ||||
|      | ||||
|     { | ||||
|       "url": "https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk", | ||||
|       "name": "Carto Positron (no labels)", | ||||
|  | @ -176,6 +175,66 @@ | |||
|         "text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>", | ||||
|         "url": "https://carto.com/" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "url": "pmtiles://https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=2af8b969a9e8b692", | ||||
|       "style": "https://api.protomaps.com/styles/v2/white.json?key=2af8b969a9e8b692", | ||||
|       "id": "protomaps.white", | ||||
|       "name": "Protomaps White", | ||||
|       "type": "vector", | ||||
|       "category": "osmbasedmap", | ||||
|       "attribution": { | ||||
|         "text": "Protomaps", | ||||
|         "url": "https://protomaps.com/" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "url": "pmtiles://https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=2af8b969a9e8b692", | ||||
|       "style": "https://api.protomaps.com/styles/v2/light.json?key=2af8b969a9e8b692", | ||||
|       "id": "protomaps.light", | ||||
|       "name": "Protomaps Light", | ||||
|       "type": "vector", | ||||
|       "category": "osmbasedmap", | ||||
|       "attribution": { | ||||
|         "text": "Protomaps", | ||||
|         "url": "https://protomaps.com/" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "url": "pmtiles://https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=2af8b969a9e8b692", | ||||
|       "style": "https://api.protomaps.com/styles/v2/grayscale.json?key=2af8b969a9e8b692", | ||||
|       "id": "protomaps.grayscale", | ||||
|       "name": "Protomaps Grayscale", | ||||
|       "type": "vector", | ||||
|       "category": "osmbasedmap", | ||||
|       "attribution": { | ||||
|         "text": "Protomaps", | ||||
|         "url": "https://protomaps.com/" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "url": "pmtiles://https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=2af8b969a9e8b692", | ||||
|       "style": "https://api.protomaps.com/styles/v2/dark.json?key=2af8b969a9e8b692", | ||||
|       "id": "protomaps.dark", | ||||
|       "name": "Protomaps Dark", | ||||
|       "type": "vector", | ||||
|       "category": "osmbasedmap", | ||||
|       "attribution": { | ||||
|         "text": "Protomaps", | ||||
|         "url": "https://protomaps.com/" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "url": "pmtiles://https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=2af8b969a9e8b692", | ||||
|       "style": "https://api.protomaps.com/styles/v2/black.json?key=2af8b969a9e8b692", | ||||
|       "id": "protomaps.black", | ||||
|       "name": "Protomaps Black", | ||||
|       "type": "vector", | ||||
|       "category": "osmbasedmap", | ||||
|       "attribution": { | ||||
|         "text": "Protomaps", | ||||
|         "url": "https://protomaps.com/" | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue