Feature(360): actually show spheres when it is already linked

This commit is contained in:
Pieter Vander Vennet 2025-04-09 23:30:39 +02:00
parent 3e4708b0b9
commit e81b0d10ea
17 changed files with 124 additions and 30 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Pieter Vander Vennet
SPDX-License-Identifier: CC0-1.0

View file

@ -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",

View file

@ -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;

View file

@ -20,7 +20,7 @@ export interface ProvidedImage {
lat?: number
lon?: number
host?: string
isSpherical?: boolean
isSpherical: boolean
}
export interface PanoramaView {

View file

@ -32,6 +32,7 @@ export class Imgur extends ImageProvider {
key: key,
provider: this,
id: value,
isSpherical: false
},
]
}

View file

@ -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 = <string>response["thumb_1024_url"]
const url_hd = <string>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 <ProvidedImage>{
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]
}

View file

@ -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),
}
}

View file

@ -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<string> = 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<undefined> {
throw new Error("Method not implemented; shouldn't be needed!")
}
public getPanoramaInfo(image: { id: string }): Promise<Feature<Point, PanoramaView>> {
return undefined
}
}

View file

@ -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<LicenseInfo> {
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<Feature<Point, PanoramaView>> | undefined {
return undefined
}
}

View file

@ -234,7 +234,7 @@ class MapillaryFetcher implements ImageFetcher {
async fetchImages(lat: number, lon: number): Promise<P4CPicture[]> {
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
}

View file

@ -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<ProvidedImage> & { 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 @@
<Tr t={Translations.t.image.processing} />
</Loading>
</div>
{:else}
{:else if image.status !== "hidden"}
<div class="relative shrink-0">
<div
class="relative w-fit"
class={"relative w-fit"}
on:mouseenter={() => highlight()}
on:mouseleave={() => highlight(false)}
>
@ -132,6 +133,16 @@
src={image.url}
/>
{#if image.isSpherical}
<div class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none">
<div class="bg-black opacity-50 rounded-full p-[3.25rem]">
<div class="w-0 h-0 relative flex items-center justify-center">
<Panorama360 class="absolute w-16 h-16" color="#ffffff" />
</div>
</div>
</div>
{/if}
{#if canZoom && loaded}
<div
class="bg-black-transparent absolute right-0 top-0 rounded-bl-full"
@ -145,4 +156,7 @@
<ImageAttribution {image} {attributionFormat} />
</div>
</div>
{:else if image.status === "hidden"}
<div class="subtle">This image has been reported</div>
{/if}

View file

@ -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<Record<string, string>>
export let nearbyFeatures: Feature[] | Store<Feature[]> = []
let showDeleteDialog = new UIEventSource(false)
onDestroy(
showDeleteDialog.addCallbackAndRunD((shown) => {
@ -160,7 +163,7 @@
<div class="relative w-fit shrink-0" style="scroll-snap-align: start">
<div class="relative flex max-w-max items-center bg-gray-200">
<AttributedImage imgClass="carousel-max-height" {image} {state}>
<AttributedImage imgClass="carousel-max-height" {image} {state} {nearbyFeatures}>
<svelte:fragment slot="dot-menu-actions">
<button on:click={() => ImageProvider.offerImageAsDownload(image)}>
<DownloadIcon />

View file

@ -1,15 +1,30 @@
<script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource.js"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import DeletableImage from "./DeletableImage.svelte"
import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte"
import type { Feature, Point } from "geojson"
import ThemeViewState from "../../Models/ThemeViewState"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { GeoOperations } from "../../Logic/GeoOperations"
export let images: Store<ProvidedImage[]>
export let state: SpecialVisualizationState
export let state: ThemeViewState
export let tags: UIEventSource<Record<string, string>>
export let feature: Feature
export let estimated: Store<number>
export let layer: LayerConfig
let zoomToFeature: Feature<Point> = {
type: "Feature",
geometry: {
type: "Point",
coordinates: GeoOperations.centerpointCoordinates(feature)
},
properties: {
name: layer?.title?.GetRenderValue(feature.properties)?.Subs(feature.properties)?.txt ?? feature?.properties?.name,
focus: true
}
}
</script>
{#if $estimated > 0 && $images.length < 1}
@ -18,7 +33,7 @@
<div class="w-full overflow-x-auto" style="scroll-snap-type: x proximity">
<div class="flex space-x-2">
{#each $images as image (image.url)}
<DeletableImage {image} {state} {tags} />
<DeletableImage {image} {state} {tags} nearbyFeatures={[zoomToFeature]} />
{/each}
</div>
</div>

View file

@ -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"

View file

@ -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 })
},
},
{

File diff suppressed because one or more lines are too long