forked from MapComplete/MapComplete
Feature(360): actually show spheres when it is already linked
This commit is contained in:
parent
3e4708b0b9
commit
e81b0d10ea
17 changed files with 124 additions and 30 deletions
18
assets/svg/Panorama360.svg
Normal file
18
assets/svg/Panorama360.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.4 KiB |
2
assets/svg/Panorama360.svg.license
Normal file
2
assets/svg/Panorama360.svg.license
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: Pieter Vander Vennet
|
||||
SPDX-License-Identifier: CC0-1.0
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface ProvidedImage {
|
|||
lat?: number
|
||||
lon?: number
|
||||
host?: string
|
||||
isSpherical?: boolean
|
||||
isSpherical: boolean
|
||||
}
|
||||
|
||||
export interface PanoramaView {
|
||||
|
|
|
@ -32,6 +32,7 @@ export class Imgur extends ImageProvider {
|
|||
key: key,
|
||||
provider: this,
|
||||
id: value,
|
||||
isSpherical: false
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 })
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
4
src/assets/svg/Panorama360.svelte
Normal file
4
src/assets/svg/Panorama360.svelte
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue