forked from MapComplete/MapComplete
139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
import { Store, Stores } from "../UIEventSource"
|
|
import BaseUIElement from "../../UI/BaseUIElement"
|
|
import { LicenseInfo } from "./LicenseInfo"
|
|
import { Utils } from "../../Utils"
|
|
import { Feature, Point } from "geojson"
|
|
|
|
export interface ProvidedImage {
|
|
url: string
|
|
url_hd?: string
|
|
key: string
|
|
provider: ImageProvider
|
|
id: string
|
|
date?: Date
|
|
status?: string | "ready"
|
|
/**
|
|
* Compass angle of the taken image
|
|
* 0 = north, 90° = East
|
|
*/
|
|
rotation?: number
|
|
lat?: number
|
|
lon?: number
|
|
host?: string
|
|
isSpherical: boolean,
|
|
license?: LicenseInfo
|
|
}
|
|
|
|
export interface PanoramaView {
|
|
url: string
|
|
/**
|
|
* 0 - 359
|
|
* Degrees in which the picture is taken, with north = 0; going clockwise
|
|
*/
|
|
northOffset?: number
|
|
pitchOffset?: number
|
|
}
|
|
|
|
/**
|
|
* The property of "nearbyFeatures" in ImagePreview.svelte.
|
|
* These properties declare how they are rendered
|
|
*/
|
|
export interface HotspotProperties {
|
|
/**
|
|
* The popup text when hovering
|
|
*/
|
|
name: string
|
|
|
|
/**
|
|
* If true: the panorama view will automatically turn towards this object
|
|
*/
|
|
focus: boolean
|
|
/**
|
|
* The pitch degrees to display this.
|
|
* If "auto": will determine the pitch automatically based on distance
|
|
*/
|
|
pitch: number | "auto"
|
|
|
|
gotoPanorama: Feature<Point, PanoramaView>
|
|
}
|
|
|
|
export default abstract class ImageProvider {
|
|
public abstract readonly defaultKeyPrefixes: string[]
|
|
|
|
public abstract readonly name: string
|
|
|
|
public abstract SourceIcon(
|
|
img?: { id: string; url: string; host?: string },
|
|
location?: { lon: number; lat: number }
|
|
): BaseUIElement
|
|
|
|
/**
|
|
* Gets all the relevant URLS for the given tags and for the given prefixes;
|
|
* extracts the necessary information
|
|
* @param tags
|
|
* @param prefixes
|
|
*/
|
|
public async getRelevantUrlsFor(
|
|
tags: Record<string, string>,
|
|
prefixes: string[]
|
|
): Promise<ProvidedImage[]> {
|
|
const relevantUrls: ProvidedImage[] = []
|
|
const seenValues = new Set<string>()
|
|
|
|
for (const key in tags) {
|
|
if (
|
|
!prefixes.some(
|
|
(prefix) => key === prefix || key.match(new RegExp(prefix + ":[0-9]+"))
|
|
)
|
|
) {
|
|
continue
|
|
}
|
|
const values = Utils.NoEmpty(tags[key]?.split(";")?.map((v) => v.trim()) ?? [])
|
|
for (const value of values) {
|
|
if (seenValues.has(value)) {
|
|
continue
|
|
}
|
|
seenValues.add(value)
|
|
let images = this.ExtractUrls(key, value)
|
|
if (!Array.isArray(images)) {
|
|
images = await images
|
|
}
|
|
if (images) {
|
|
relevantUrls.push(...images)
|
|
}
|
|
}
|
|
}
|
|
return relevantUrls
|
|
}
|
|
|
|
public getRelevantUrls(
|
|
tags: Record<string, string>,
|
|
prefixes: string[]
|
|
): Store<ProvidedImage[]> {
|
|
return Stores.FromPromise(this.getRelevantUrlsFor(tags, prefixes))
|
|
}
|
|
|
|
public abstract ExtractUrls(
|
|
key: string,
|
|
value: string
|
|
): undefined | ProvidedImage[] | Promise<ProvidedImage[]>
|
|
|
|
public abstract DownloadAttribution(providedImage: {
|
|
url: string
|
|
id: string
|
|
}): Promise<LicenseInfo>
|
|
|
|
public abstract apiUrls(): string[]
|
|
|
|
public abstract getPanoramaInfo(image: {
|
|
id: string
|
|
}): Promise<Feature<Point, PanoramaView>> | undefined
|
|
|
|
public static async offerImageAsDownload(image: ProvidedImage) {
|
|
const response = await fetch(image.url_hd ?? image.url)
|
|
const blob = await response.blob()
|
|
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
|
mimetype: "image/jpg",
|
|
})
|
|
}
|
|
}
|