diff --git a/assets/svg/Panorama360.svg b/assets/svg/Panorama360.svg new file mode 100644 index 000000000..4b45c66ae --- /dev/null +++ b/assets/svg/Panorama360.svg @@ -0,0 +1,18 @@ + + + + diff --git a/assets/svg/Panorama360.svg.license b/assets/svg/Panorama360.svg.license new file mode 100644 index 000000000..ed0288300 --- /dev/null +++ b/assets/svg/Panorama360.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Pieter Vander Vennet +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index e94c29d51..e493a9b2f 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -1,4 +1,12 @@ [ + { + "path": "Panorama360.svg", + "license": "CC0-1.0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, { "path": "SocialImageForeground.svg", "license": "CC-BY-SA-4.0", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 62aec3673..2ac8e2f36 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -3354,6 +3354,11 @@ input[type="range"].range-lg::-moz-range-thumb { background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)) !important; } +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1)); +} + .bg-blue-100 { --tw-bg-opacity: 1; background-color: rgb(225 239 254 / var(--tw-bg-opacity, 1)); @@ -4015,6 +4020,10 @@ input[type="range"].range-lg::-moz-range-thumb { padding: 2rem; } +.p-\[3\.25rem\] { + padding: 3.25rem; +} + .\!px-0 { padding-left: 0px !important; padding-right: 0px !important; diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index 6cb152772..16ad6b7d8 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -20,7 +20,7 @@ export interface ProvidedImage { lat?: number lon?: number host?: string - isSpherical?: boolean + isSpherical: boolean } export interface PanoramaView { diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index 3f0f25570..4ccc4ca99 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -32,6 +32,7 @@ export class Imgur extends ImageProvider { key: key, provider: this, id: value, + isSpherical: false }, ] } diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 21d270525..3dd169a3e 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -204,19 +204,24 @@ export class Mapillary extends ImageProvider { const metadataUrl = "https://graph.mapillary.com/" + mapillaryId + - "?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,creator,camera_type&access_token=" + + "?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,computed_geometry,creator,camera_type&access_token=" + Constants.mapillary_client_token_v4 const response = await Utils.downloadJsonCached<{ - thumb_1024_url: string, thumb_original_url: string, captured_at, + thumb_1024_url: string, + thumb_original_url: string, + captured_at, compass_angle: number, - creator: string + creator: string, + computed_geometry: Point, + geometry: Point, + camera_type: "equirectangular" | "spherical" | string }>(metadataUrl, 60 * 60) const url = response["thumb_1024_url"] const url_hd = response["thumb_original_url"] const date = new Date() - const rotation = (720 - Number(response["compass_angle"])) % 360 - const geometry = response["geometry"] - date.setTime(response["captured_at"]) + const rotation: number = (720 - Number(response.compass_angle)) % 360 + const geometry: Point = response.computed_geometry ?? response.geometry + date.setTime(response.captured_at) return { id: "" + mapillaryId, url, @@ -225,6 +230,7 @@ export class Mapillary extends ImageProvider { date, key, rotation, + isSpherical: response.camera_type === "spherical" || response.camera_type === "equirectangular", lat: geometry.coordinates[1], lon: geometry.coordinates[0] } diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts index 26143f30d..61802ab98 100644 --- a/src/Logic/ImageProviders/Panoramax.ts +++ b/src/Logic/ImageProviders/Panoramax.ts @@ -97,6 +97,7 @@ export default class PanoramaxImageProvider extends ImageProvider { provider: this, status: meta.properties["geovisio:status"], rotation: Number(meta.properties["view:azimuth"]), + isSpherical: meta.properties.exif["Xmp.GPano.ProjectionType"] === "equirectangular", date: new Date(meta.properties.datetime), } } diff --git a/src/Logic/ImageProviders/WikidataImageProvider.ts b/src/Logic/ImageProviders/WikidataImageProvider.ts index 9ef77be37..20bd48ec5 100644 --- a/src/Logic/ImageProviders/WikidataImageProvider.ts +++ b/src/Logic/ImageProviders/WikidataImageProvider.ts @@ -1,18 +1,21 @@ -import ImageProvider, { ProvidedImage } from "./ImageProvider" +import ImageProvider, { PanoramaView, ProvidedImage } from "./ImageProvider" import BaseUIElement from "../../UI/BaseUIElement" import { WikimediaImageProvider } from "./WikimediaImageProvider" import Wikidata from "../Web/Wikidata" import SvelteUIElement from "../../UI/Base/SvelteUIElement" import * as Wikidata_icon from "../../assets/svg/Wikidata.svelte" import { Utils } from "../../Utils" +import { Feature, Point } from "geojson" export class WikidataImageProvider extends ImageProvider { + + public static readonly singleton = new WikidataImageProvider() public readonly defaultKeyPrefixes = ["wikidata"] public readonly name = "Wikidata" private static readonly keyBlacklist: ReadonlySet = new Set([ "mapillary", - ...Utils.Times((i) => "mapillary:" + i, 10), + ...Utils.Times((i) => "mapillary:" + i, 10) ]) private constructor() { @@ -66,4 +69,8 @@ export class WikidataImageProvider extends ImageProvider { public DownloadAttribution(): Promise { throw new Error("Method not implemented; shouldn't be needed!") } + + public getPanoramaInfo(image: { id: string }): Promise> { + return undefined + } } diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index 3ccc96f15..73d137602 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -1,10 +1,11 @@ -import ImageProvider, { ProvidedImage } from "./ImageProvider" +import ImageProvider, { PanoramaView, ProvidedImage } from "./ImageProvider" import BaseUIElement from "../../UI/BaseUIElement" import { Utils } from "../../Utils" import { LicenseInfo } from "./LicenseInfo" import Wikimedia from "../Web/Wikimedia" import SvelteUIElement from "../../UI/Base/SvelteUIElement" import Wikimedia_commons_white from "../../assets/svg/Wikimedia_commons_white.svelte" +import { Feature, Point } from "geojson" /** * This module provides endpoints for wikimedia and others @@ -123,7 +124,6 @@ export class WikimediaImageProvider extends ImageProvider { public async DownloadAttribution(img: { url: string }): Promise { const filename = WikimediaImageProvider.ExtractFileName(img.url) - if (filename === "") { return undefined } @@ -189,6 +189,11 @@ export class WikimediaImageProvider extends ImageProvider { key: undefined, provider: this, id: image, + isSpherical: false } } + + getPanoramaInfo(image: { id: string }): Promise> | undefined { + return undefined + } } diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 5d424ebd5..4c92f776b 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -234,7 +234,7 @@ class MapillaryFetcher implements ImageFetcher { async fetchImages(lat: number, lon: number): Promise { const boundingBox = new BBox([[lon, lat]]).padAbsolute(0.003) let url = - "https://graph.mapillary.com/images?fields=computed_geometry,creator,id,thumb_256_url,thumb_original_url,compass_angle&bbox=" + + "https://graph.mapillary.com/images?fields=geometry,computed_geometry,creator,id,thumb_256_url,thumb_original_url,compass_angle&bbox=" + [ boundingBox.getWest(), boundingBox.getSouth(), @@ -263,6 +263,7 @@ class MapillaryFetcher implements ImageFetcher { data: { id: string creator: string + geometry: Point computed_geometry: Point is_pano: boolean thumb_256_url: string @@ -272,7 +273,7 @@ class MapillaryFetcher implements ImageFetcher { }>(url) const pics: P4CPicture[] = [] for (const img of response.data) { - const c = img.computed_geometry.coordinates + const c = img.computed_geometry?.coordinates ?? img.geometry.coordinates if (img.thumb_original_url === undefined) { continue } diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 8be73f6d8..2b1601064 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -21,6 +21,7 @@ import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte" import { MenuState } from "../../Models/MenuState" import ThemeViewState from "../../Models/ThemeViewState" + import Panorama360 from "../../assets/svg/Panorama360.svelte" export let image: Partial & { id: string; url: string } let fallbackImage: string = undefined @@ -48,7 +49,7 @@ previewedImage.addCallbackAndRun((previewedImage) => { showBigPreview.set( previewedImage !== undefined && - (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) + (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) ) }) ) @@ -66,12 +67,12 @@ type: "Feature", properties: { id: image.id, - rotation: image.rotation, + rotation: image.rotation }, geometry: { type: "Point", - coordinates: [image.lon, image.lat], - }, + coordinates: [image.lon, image.lat] + } } state?.geocodedImages.set([f]) } @@ -100,10 +101,10 @@ -{:else} +{:else if image.status !== "hidden"} highlight()} on:mouseleave={() => highlight(false)} > @@ -132,6 +133,16 @@ src={image.url} /> + {#if image.isSpherical} + + + + + + + + {/if} + {#if canZoom && loaded} + +{:else if image.status === "hidden"} + This image has been reported {/if} diff --git a/src/UI/Image/DeletableImage.svelte b/src/UI/Image/DeletableImage.svelte index bd20108b4..ce719de3b 100644 --- a/src/UI/Image/DeletableImage.svelte +++ b/src/UI/Image/DeletableImage.svelte @@ -5,9 +5,8 @@ import Popup from "../Base/Popup.svelte" import AccordionSingle from "../Flowbite/AccordionSingle.svelte" import NextButton from "../Base/NextButton.svelte" - import { UIEventSource } from "../../Logic/UIEventSource" + import { Store, UIEventSource } from "../../Logic/UIEventSource" import AttributedImage from "./AttributedImage.svelte" - import type { SpecialVisualizationState } from "../SpecialVisualization" import Dropdown from "../Base/Dropdown.svelte" import { REPORT_REASONS, ReportReason } from "panoramax-js" import { onDestroy } from "svelte" @@ -19,10 +18,14 @@ import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" import { MenuState } from "../../Models/MenuState" + import type { Feature } from "geojson" + import ThemeViewState from "../../Models/ThemeViewState" export let image: ProvidedImage - export let state: SpecialVisualizationState + export let state: ThemeViewState export let tags: UIEventSource> + export let nearbyFeatures: Feature[] | Store = [] + let showDeleteDialog = new UIEventSource(false) onDestroy( showDeleteDialog.addCallbackAndRunD((shown) => { @@ -160,7 +163,7 @@ - + ImageProvider.offerImageAsDownload(image)}> diff --git a/src/UI/Image/ImageCarousel.svelte b/src/UI/Image/ImageCarousel.svelte index e98af2635..8e149e52e 100644 --- a/src/UI/Image/ImageCarousel.svelte +++ b/src/UI/Image/ImageCarousel.svelte @@ -1,15 +1,30 @@ {#if $estimated > 0 && $images.length < 1} @@ -18,7 +33,7 @@ {#each $images as image (image.url)} - + {/each} diff --git a/src/UI/Image/ImagePreview.svelte b/src/UI/Image/ImagePreview.svelte index a96a19982..6f9080b3d 100644 --- a/src/UI/Image/ImagePreview.svelte +++ b/src/UI/Image/ImagePreview.svelte @@ -7,7 +7,7 @@ import { UIEventSource } from "../../Logic/UIEventSource" import Zoomcontrol from "../Zoomcontrol" import { onDestroy } from "svelte" - import type { PanoramaView } from "./photoSphereViewerWrapper" + import type { PanoramaView } from "../../Logic/ImageProviders/ImageProvider" import { PhotoSphereViewerWrapper } from "./photoSphereViewerWrapper" import type { Feature, Point } from "geojson" diff --git a/src/UI/SpecialVisualisations/ImageVisualisations.ts b/src/UI/SpecialVisualisations/ImageVisualisations.ts index 64bf8e177..182a73ad8 100644 --- a/src/UI/SpecialVisualisations/ImageVisualisations.ts +++ b/src/UI/SpecialVisualisations/ImageVisualisations.ts @@ -70,7 +70,7 @@ export class ImageVisualisations { }, ], needsUrls: AllImageProviders.apiUrls, - constr: (state, tags, args) => { + constr: (state, tags, args, feature) => { let imagePrefixes: string[] = undefined if (args.length > 0) { imagePrefixes = [].concat(...args.map((a) => a.split(";"))) @@ -79,7 +79,7 @@ export class ImageVisualisations { const estimated = tags.mapD((tags) => AllImageProviders.estimateNumberOfImages(tags, imagePrefixes) ) - return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated }) + return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated, feature }) }, }, { diff --git a/src/assets/svg/Panorama360.svelte b/src/assets/svg/Panorama360.svelte new file mode 100644 index 000000000..cfcdd848b --- /dev/null +++ b/src/assets/svg/Panorama360.svelte @@ -0,0 +1,4 @@ + + \ No newline at end of file