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 } 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, prefixes: string[] ): Promise { const relevantUrls: ProvidedImage[] = [] const seenValues = new Set() 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, prefixes: string[] ): Store { return Stores.FromPromise(this.getRelevantUrlsFor(tags, prefixes)) } public abstract ExtractUrls( key: string, value: string ): undefined | ProvidedImage[] | Promise public abstract DownloadAttribution(providedImage: { url: string id: string }): Promise public abstract apiUrls(): string[] /** * URL to visit the image on the original website */ public abstract visitUrl( image: Partial, location?: { lon: number; lat: number } ): string | undefined public abstract getPanoramaInfo(image: { id: string }): Promise> | 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", }) } }