forked from MapComplete/MapComplete
Feature: add bing maps (test), see #648
This commit is contained in:
parent
94f39e89fe
commit
ed050d5be3
6 changed files with 170 additions and 35 deletions
|
@ -2,11 +2,13 @@ import Script from "./Script"
|
||||||
import { Utils } from "../src/Utils"
|
import { Utils } from "../src/Utils"
|
||||||
import { Eli, EliEntry } from "./@types/eli"
|
import { Eli, EliEntry } from "./@types/eli"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
import { BingRasterLayer } from "../src/UI/Map/BingRasterLayer"
|
||||||
|
|
||||||
class DownloadEli extends Script {
|
class DownloadEli extends Script {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Downloads a fresh copy of the editor layer index, removes all unnecessary data.")
|
super("Downloads a fresh copy of the editor layer index, removes all unnecessary data.")
|
||||||
}
|
}
|
||||||
|
|
||||||
async main(args: string[]): Promise<void> {
|
async main(args: string[]): Promise<void> {
|
||||||
const url = "https://osmlab.github.io/editor-layer-index/imagery.geojson"
|
const url = "https://osmlab.github.io/editor-layer-index/imagery.geojson"
|
||||||
// Target should use '.json' instead of '.geojson', as the latter cannot be imported by the build systems
|
// Target should use '.json' instead of '.geojson', as the latter cannot be imported by the build systems
|
||||||
|
@ -20,7 +22,18 @@ class DownloadEli extends Script {
|
||||||
|
|
||||||
if (props.type === "bing") {
|
if (props.type === "bing") {
|
||||||
// A lot of work to implement - see https://github.com/pietervdvn/MapComplete/issues/648
|
// A lot of work to implement - see https://github.com/pietervdvn/MapComplete/issues/648
|
||||||
continue
|
try {
|
||||||
|
const bing = await BingRasterLayer.get()
|
||||||
|
if (bing === "error") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete props.default
|
||||||
|
props.category = "photo"
|
||||||
|
props.url = bing.properties.url.replace("%7Bquadkey%7D", "{quadkey}")
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Could not fetch URL for bing", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.id === "MAPNIK") {
|
if (props.id === "MAPNIK") {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { RasterLayerPolygon, RasterLayerUtils } from "../../Models/RasterLayers"
|
||||||
*/
|
*/
|
||||||
export default class BackgroundLayerResetter {
|
export default class BackgroundLayerResetter {
|
||||||
constructor(
|
constructor(
|
||||||
currentBackgroundLayer: UIEventSource<RasterLayerPolygon>,
|
currentBackgroundLayer: UIEventSource<RasterLayerPolygon | undefined>,
|
||||||
availableLayers: Store<RasterLayerPolygon[]>
|
availableLayers: Store<RasterLayerPolygon[]>
|
||||||
) {
|
) {
|
||||||
if (Utils.runningFromConsole) {
|
if (Utils.runningFromConsole) {
|
||||||
|
|
|
@ -8,9 +8,14 @@ import { RasterLayerProperties } from "./RasterLayerProperties"
|
||||||
|
|
||||||
export class AvailableRasterLayers {
|
export class AvailableRasterLayers {
|
||||||
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
|
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
|
||||||
RasterLayerPolygon)[] = <any>editorlayerindex.features
|
RasterLayerPolygon)[] = (<any>editorlayerindex.features).filter(
|
||||||
|
(l) => l.properties.id !== "Bing"
|
||||||
|
)
|
||||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers
|
public static globalLayers: RasterLayerPolygon[] = globallayers.layers
|
||||||
.filter((properties) => properties.id !== "osm.carto" /*Added separately*/)
|
.filter(
|
||||||
|
(properties) =>
|
||||||
|
properties.id !== "osm.carto" && properties.id !== "Bing" /*Added separately*/
|
||||||
|
)
|
||||||
.map(
|
.map(
|
||||||
(properties) =>
|
(properties) =>
|
||||||
<RasterLayerPolygon>{
|
<RasterLayerPolygon>{
|
||||||
|
@ -19,6 +24,9 @@ export class AvailableRasterLayers {
|
||||||
geometry: BBox.global.asGeometry(),
|
geometry: BBox.global.asGeometry(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
public static bing: RasterLayerPolygon = (<any>editorlayerindex.features).find(
|
||||||
|
(l) => l.properties.id === "Bing"
|
||||||
|
)
|
||||||
public static readonly osmCartoProperties: RasterLayerProperties = {
|
public static readonly osmCartoProperties: RasterLayerProperties = {
|
||||||
id: "osm",
|
id: "osm",
|
||||||
name: "OpenStreetMap",
|
name: "OpenStreetMap",
|
||||||
|
@ -56,7 +64,8 @@ export class AvailableRasterLayers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static layersAvailableAt(
|
public static layersAvailableAt(
|
||||||
location: Store<{ lon: number; lat: number }>
|
location: Store<{ lon: number; lat: number }>,
|
||||||
|
enableBing?: Store<boolean>
|
||||||
): Store<RasterLayerPolygon[]> {
|
): Store<RasterLayerPolygon[]> {
|
||||||
const availableLayersBboxes = Stores.ListStabilized(
|
const availableLayersBboxes = Stores.ListStabilized(
|
||||||
location.mapD((loc) => {
|
location.mapD((loc) => {
|
||||||
|
@ -67,20 +76,26 @@ export class AvailableRasterLayers {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return Stores.ListStabilized(
|
return Stores.ListStabilized(
|
||||||
availableLayersBboxes.map((eliPolygons) => {
|
availableLayersBboxes.map(
|
||||||
const loc = location.data
|
(eliPolygons) => {
|
||||||
const lonlat: [number, number] = [loc.lon, loc.lat]
|
const loc = location.data
|
||||||
const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => {
|
const lonlat: [number, number] = [loc.lon, loc.lat]
|
||||||
if (eliPolygon.geometry === null) {
|
const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => {
|
||||||
return true // global ELI-layer
|
if (eliPolygon.geometry === null) {
|
||||||
|
return true // global ELI-layer
|
||||||
|
}
|
||||||
|
return GeoOperations.inside(lonlat, eliPolygon)
|
||||||
|
})
|
||||||
|
matching.unshift(AvailableRasterLayers.osmCarto)
|
||||||
|
matching.push(AvailableRasterLayers.maptilerDefaultLayer)
|
||||||
|
if (enableBing?.data) {
|
||||||
|
matching.push(AvailableRasterLayers.bing)
|
||||||
}
|
}
|
||||||
return GeoOperations.inside(lonlat, eliPolygon)
|
matching.push(...AvailableRasterLayers.globalLayers)
|
||||||
})
|
return matching
|
||||||
matching.unshift(AvailableRasterLayers.osmCarto)
|
},
|
||||||
matching.push(AvailableRasterLayers.maptilerDefaultLayer)
|
[enableBing]
|
||||||
matching.push(...AvailableRasterLayers.globalLayers)
|
)
|
||||||
return matching
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
)
|
)
|
||||||
this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties)
|
this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties)
|
||||||
|
|
||||||
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
|
this.availableLayers = AvailableRasterLayers.layersAvailableAt(
|
||||||
|
this.mapProperties.location,
|
||||||
|
this.osmConnection.isLoggedIn
|
||||||
|
)
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
|
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
|
||||||
|
|
89
src/UI/Map/BingRasterLayer.ts
Normal file
89
src/UI/Map/BingRasterLayer.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||||
|
import { Polygon } from "geojson"
|
||||||
|
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||||
|
import { BBox } from "../../Logic/BBox"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
|
||||||
|
export class BingRasterLayerProperties implements Partial<RasterLayerProperties> {
|
||||||
|
private static singleton: BingRasterLayerProperties | "error"
|
||||||
|
name = "Bing Maps Aerial"
|
||||||
|
id = "Bing"
|
||||||
|
type = "bing"
|
||||||
|
category = "photo"
|
||||||
|
min_zoom = 1
|
||||||
|
max_zoom = 22
|
||||||
|
best = false
|
||||||
|
attribution = {
|
||||||
|
url: "https://www.bing.com/maps",
|
||||||
|
}
|
||||||
|
url: string
|
||||||
|
|
||||||
|
private constructor(url: string) {
|
||||||
|
this.url = url
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async get(): Promise<BingRasterLayerProperties | "error"> {
|
||||||
|
if (BingRasterLayerProperties.singleton === undefined) {
|
||||||
|
try {
|
||||||
|
const url = await this.getEndpoint()
|
||||||
|
BingRasterLayerProperties.singleton = new BingRasterLayerProperties(url)
|
||||||
|
} catch (e) {
|
||||||
|
BingRasterLayerProperties.singleton = "error"
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BingRasterLayerProperties.singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getEndpoint() {
|
||||||
|
console.log("Getting bing endpoint")
|
||||||
|
// Key by 'pietervdvn@outlook.com' from https://www.bingmapsportal.com/Application
|
||||||
|
// Inspired by https://github.com/zlant/parking-lanes/pull/159
|
||||||
|
const key = "An0vKZ4r_PZx820sn3seuPKjd1Vyc5WE3s1b-qN4HCgI-Nr6QR83aLOQ-3fbFl08"
|
||||||
|
const url = `https://dev.virtualearth.net/REST/v1/Imagery/Metadata/AerialOSM?include=ImageryProviders&uriScheme=https&key=${key}`
|
||||||
|
|
||||||
|
// Get the image tiles template:
|
||||||
|
const metadata = await Utils.downloadJson(url)
|
||||||
|
// FYI:
|
||||||
|
// "imageHeight": 256, "imageWidth": 256,
|
||||||
|
// "imageUrlSubdomains": ["t0","t1","t2","t3"],
|
||||||
|
// "zoomMax": 21,
|
||||||
|
const imageryResource = metadata.resourceSets[0].resources[0]
|
||||||
|
const template = new URL(imageryResource.imageUrl)
|
||||||
|
// Add tile image strictness param (n=)
|
||||||
|
// • n=f -> (Fail) returns a 404
|
||||||
|
// • n=z -> (Empty) returns a 200 with 0 bytes (no content)
|
||||||
|
// • n=t -> (Transparent) returns a 200 with a transparent (png) tile
|
||||||
|
if (!template.searchParams.has("n")) template.searchParams.append("n", "f")
|
||||||
|
|
||||||
|
// FYI: `template` looks like this but partly encoded
|
||||||
|
// https://ecn.{subdomain}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14107&pr=odbl&n=z
|
||||||
|
const subdomains = ["t0", "t1", "t2", "t3"]
|
||||||
|
const index = Math.floor(Math.random() * subdomains.length)
|
||||||
|
return template.toString().replace("{subdomain}", subdomains[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BingRasterLayer implements RasterLayerPolygon {
|
||||||
|
private static singleton: RasterLayerPolygon | "error"
|
||||||
|
readonly type: "Feature" = "Feature"
|
||||||
|
readonly geometry: Polygon = BBox.global.asGeometry()
|
||||||
|
readonly id = "bing"
|
||||||
|
readonly properties: RasterLayerProperties
|
||||||
|
|
||||||
|
constructor(properties: RasterLayerProperties) {
|
||||||
|
this.properties = properties
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async get(): Promise<RasterLayerPolygon | "error"> {
|
||||||
|
if (BingRasterLayer.singleton === undefined) {
|
||||||
|
const properties = await BingRasterLayerProperties.get()
|
||||||
|
if (properties === "error") {
|
||||||
|
BingRasterLayer.singleton = "error"
|
||||||
|
} else {
|
||||||
|
BingRasterLayer.singleton = new BingRasterLayer(properties)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BingRasterLayer.singleton
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue