forked from MapComplete/MapComplete
Compare commits
1 commit
develop
...
fix/photos
| Author | SHA1 | Date | |
|---|---|---|---|
| 611dabd5cb |
10 changed files with 156 additions and 134 deletions
|
|
@ -3,6 +3,7 @@ import BaseUIElement from "../../UI/BaseUIElement"
|
||||||
import { LicenseInfo } from "./LicenseInfo"
|
import { LicenseInfo } from "./LicenseInfo"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { Feature, Point } from "geojson"
|
import { Feature, Point } from "geojson"
|
||||||
|
import { P4CPicture } from "../Web/NearbyImagesSearch"
|
||||||
|
|
||||||
export interface ProvidedImage {
|
export interface ProvidedImage {
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -36,7 +37,8 @@ export interface PanoramaView {
|
||||||
*/
|
*/
|
||||||
northOffset?: number
|
northOffset?: number
|
||||||
pitchOffset?: number
|
pitchOffset?: number
|
||||||
provider: ImageProvider | string
|
provider: ImageProvider | string,
|
||||||
|
p4c: P4CPicture
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,6 +62,7 @@ export interface HotspotProperties {
|
||||||
pitch: number | "auto"
|
pitch: number | "auto"
|
||||||
|
|
||||||
gotoPanorama: Feature<Point, PanoramaView>
|
gotoPanorama: Feature<Point, PanoramaView>
|
||||||
|
originalP4C: P4CPicture
|
||||||
}
|
}
|
||||||
|
|
||||||
export default abstract class ImageProvider {
|
export default abstract class ImageProvider {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class NearbyImageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
class P4CImageFetcher implements ImageFetcher {
|
class P4CImageFetcher implements ImageFetcher {
|
||||||
public static readonly services = ["flickr", "kartaview", "wikicommons"] as const
|
public static readonly services = ["flickr", "kartaview", "wikicommons", "mapillary"] as const
|
||||||
public static readonly apiUrls = ["https://api.flickr.com"]
|
public static readonly apiUrls = ["https://api.flickr.com"]
|
||||||
private _options: { maxDaysOld: number; searchRadius: number }
|
private _options: { maxDaysOld: number; searchRadius: number }
|
||||||
public readonly name: P4CService
|
public readonly name: P4CService
|
||||||
|
|
@ -374,7 +374,7 @@ export class CombinedFetcher {
|
||||||
start_captured_at: maxage,
|
start_captured_at: maxage,
|
||||||
panoramas: "no",
|
panoramas: "no",
|
||||||
}),
|
}),
|
||||||
// new P4CImageFetcher("mapillary"),
|
new P4CImageFetcher("mapillary"),
|
||||||
new P4CImageFetcher("wikicommons"),
|
new P4CImageFetcher("wikicommons"),
|
||||||
].map((f) => new CachedFetcher(f))
|
].map((f) => new CachedFetcher(f))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,14 +146,14 @@
|
||||||
<LinkableImage
|
<LinkableImage
|
||||||
{tags}
|
{tags}
|
||||||
{state}
|
{state}
|
||||||
image={{
|
image={new UIEventSource({
|
||||||
pictureUrl: image,
|
pictureUrl: image,
|
||||||
provider: "Velopark",
|
provider: "Velopark",
|
||||||
thumbUrl: image,
|
thumbUrl: image,
|
||||||
details: undefined,
|
details: undefined,
|
||||||
coordinates: undefined,
|
coordinates: undefined,
|
||||||
osmTags: { image },
|
osmTags: { image },
|
||||||
}}
|
})}
|
||||||
{feature}
|
{feature}
|
||||||
{layer}
|
{layer}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@
|
||||||
* Shows an image with attribution
|
* Shows an image with attribution
|
||||||
*/
|
*/
|
||||||
import ImageAttribution from "./ImageAttribution.svelte"
|
import ImageAttribution from "./ImageAttribution.svelte"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
|
||||||
import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
|
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
|
||||||
import { CloseButton } from "flowbite-svelte"
|
import { CloseButton } from "flowbite-svelte"
|
||||||
import ImageOperations from "./ImageOperations.svelte"
|
import ImageOperations from "./ImageOperations.svelte"
|
||||||
|
|
@ -17,12 +16,11 @@
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import DotMenu from "../Base/DotMenu.svelte"
|
|
||||||
import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte"
|
import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte"
|
||||||
import { MenuState } from "../../Models/MenuState"
|
import { MenuState } from "../../Models/MenuState"
|
||||||
import ThemeViewState from "../../Models/ThemeViewState"
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
import Panorama360 from "../../assets/svg/Panorama360.svelte"
|
import Panorama360 from "../../assets/svg/Panorama360.svelte"
|
||||||
import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import AttributedImageDotMenu from "./AttributedImageDotMenu.svelte"
|
||||||
|
|
||||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||||
let fallbackImage: string = undefined
|
let fallbackImage: string = undefined
|
||||||
|
|
@ -41,7 +39,6 @@
|
||||||
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let visitUrl = image.provider?.visitUrl(image)
|
|
||||||
let showBigPreview = new UIEventSource(false)
|
let showBigPreview = new UIEventSource(false)
|
||||||
onDestroy(
|
onDestroy(
|
||||||
showBigPreview.addCallbackAndRun((shown) => {
|
showBigPreview.addCallbackAndRun((shown) => {
|
||||||
|
|
@ -71,24 +68,37 @@
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
properties: {
|
properties: {
|
||||||
id: image.id,
|
id: image.id,
|
||||||
rotation: image.rotation,
|
rotation: image.rotation
|
||||||
},
|
},
|
||||||
geometry: {
|
geometry: {
|
||||||
type: "Point",
|
type: "Point",
|
||||||
coordinates: [image.lon, image.lat],
|
coordinates: [image.lon, image.lat]
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
state?.geocodedImages.set([f])
|
state?.geocodedImages.set([f])
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
|
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
|
||||||
<div style="height: 80vh">
|
<div style="height: 80vh" class="relative h-full w-full">
|
||||||
<ImageOperations {image} {nearbyFeatures}>
|
<ImageOperations on:imagechange {image} {nearbyFeatures}>
|
||||||
<slot name="preview-action" />
|
<slot name="preview-action" />
|
||||||
<slot name="dot-menu-actions" slot="dot-menu-actions" />
|
|
||||||
</ImageOperations>
|
</ImageOperations>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
||||||
|
>
|
||||||
|
<div class="pointer-events-auto m-1 w-fit transition-colors duration-200">
|
||||||
|
<ImageAttribution {image} attributionFormat="large" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<AttributedImageDotMenu {image}>
|
||||||
|
<slot name="dot-menu-actions" />
|
||||||
|
</AttributedImageDotMenu>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="absolute right-4 top-4">
|
<div class="absolute right-4 top-4">
|
||||||
<CloseButton
|
<CloseButton
|
||||||
class="normal-background"
|
class="normal-background"
|
||||||
|
|
@ -112,17 +122,9 @@
|
||||||
on:mouseenter={() => highlight()}
|
on:mouseenter={() => highlight()}
|
||||||
on:mouseleave={() => highlight(false)}
|
on:mouseleave={() => highlight(false)}
|
||||||
>
|
>
|
||||||
{#if $$slots["dot-menu-actions"] || visitUrl !== undefined}
|
<AttributedImageDotMenu {image}>
|
||||||
<DotMenu dotsPosition="top-0 left-0 absolute" hideBackground>
|
|
||||||
<slot name="dot-menu-actions" />
|
<slot name="dot-menu-actions" />
|
||||||
{#if visitUrl !== undefined}
|
</AttributedImageDotMenu>
|
||||||
<a href={visitUrl} target="_blank" rel="noopener">
|
|
||||||
<ExternalLinkIcon class="w-6" />
|
|
||||||
<Tr t={Translations.t.image.openOnWebsite.Subs(image.provider)} />
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</DotMenu>
|
|
||||||
{/if}
|
|
||||||
{#if !loaded}
|
{#if !loaded}
|
||||||
<LoadingPlaceholder />
|
<LoadingPlaceholder />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
33
src/UI/Image/AttributedImageDotMenu.svelte
Normal file
33
src/UI/Image/AttributedImageDotMenu.svelte
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import Translations from "../i18n/Translations"
|
||||||
|
import DotMenu from "../Base/DotMenu.svelte"
|
||||||
|
import { DownloadIcon, ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
|
||||||
|
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||||
|
let visitUrl = image.provider?.visitUrl(image)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DotMenu dotsPosition="top-0 left-0 absolute" hideBackground>
|
||||||
|
{#if visitUrl !== undefined}
|
||||||
|
<a href={visitUrl} target="_blank" rel="noopener">
|
||||||
|
<ExternalLinkIcon class="w-6" />
|
||||||
|
<Tr t={Translations.t.image.openOnWebsite.Subs(image.provider)} />
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="no-image-background pointer-events-auto flex items-center justify-start"
|
||||||
|
on:click={() => ImageProvider.offerImageAsDownload(image)}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
<Tr t={Translations.t.general.download.downloadImage} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
</DotMenu>
|
||||||
|
|
@ -3,66 +3,26 @@
|
||||||
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
|
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import type { HotspotProperties } from "../../Logic/ImageProviders/ImageProvider"
|
|
||||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
|
||||||
import ImageAttribution from "./ImageAttribution.svelte"
|
|
||||||
import ImagePreview from "./ImagePreview.svelte"
|
import ImagePreview from "./ImagePreview.svelte"
|
||||||
import { DownloadIcon, ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import Tr from "../Base/Tr.svelte"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import DotMenu from "../Base/DotMenu.svelte"
|
|
||||||
import type { Feature, Geometry } from "geojson"
|
import type { Feature, Geometry } from "geojson"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
|
||||||
|
|
||||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||||
export let clss: string = undefined
|
|
||||||
export let nearbyFeatures:
|
export let nearbyFeatures:
|
||||||
| Feature<Geometry, HotspotProperties>[]
|
| Feature<Geometry, HotspotProperties>[]
|
||||||
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||||
let visitUrl = image.provider?.visitUrl(image)
|
|
||||||
let isLoaded = new UIEventSource(false)
|
let isLoaded = new UIEventSource(false)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={twMerge("relative h-full w-full", clss)}>
|
|
||||||
<div class="panzoom-container focusable absolute left-0 top-0 h-full w-full overflow-hidden">
|
<div class="panzoom-container focusable absolute left-0 top-0 h-full w-full overflow-hidden">
|
||||||
{#if !$isLoaded}
|
{#if !$isLoaded}
|
||||||
<div class="flex h-full w-full items-center justify-center">
|
<div class="flex h-full w-full items-center justify-center">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<ImagePreview {image} {isLoaded} {nearbyFeatures} />
|
<ImagePreview on:imagechange {image} {isLoaded} {nearbyFeatures} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $$slots["dot-menu-actions"]}
|
|
||||||
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
|
|
||||||
<slot name="dot-menu-actions" />
|
|
||||||
<button
|
|
||||||
class="no-image-background pointer-events-auto flex items-center"
|
|
||||||
on:click={() => ImageProvider.offerImageAsDownload(image)}
|
|
||||||
>
|
|
||||||
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
|
|
||||||
<Tr t={Translations.t.general.download.downloadImage} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if visitUrl !== undefined}
|
|
||||||
<a href={visitUrl} target="_blank" rel="noopener">
|
|
||||||
<ExternalLinkIcon class="w-6" />
|
|
||||||
<Tr t={Translations.t.image.openOnWebsite.Subs(image.provider)} />
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</DotMenu>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
|
||||||
>
|
|
||||||
<div class="pointer-events-auto m-1 w-fit transition-colors duration-200">
|
|
||||||
<ImageAttribution image={$image} attributionFormat="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@
|
||||||
*/
|
*/
|
||||||
import panzoom from "panzoom"
|
import panzoom from "panzoom"
|
||||||
import type { HotspotProperties, PanoramaView, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
import type { HotspotProperties, PanoramaView, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Zoomcontrol from "../Zoomcontrol"
|
import Zoomcontrol from "../Zoomcontrol"
|
||||||
import { onDestroy } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { PhotoSphereViewerWrapper } from "./photoSphereViewerWrapper"
|
import { PhotoSphereViewerWrapper } from "./photoSphereViewerWrapper"
|
||||||
import type { Feature, Geometry, Point } from "geojson"
|
import type { Feature, Geometry, Point } from "geojson"
|
||||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||||
|
|
||||||
export let nearbyFeatures:
|
export let nearbyFeatures:
|
||||||
| Feature<Geometry, HotspotProperties>[]
|
| Feature<Geometry, HotspotProperties>[]
|
||||||
|
|
@ -21,6 +20,8 @@
|
||||||
let viewerEl: HTMLElement
|
let viewerEl: HTMLElement
|
||||||
export let isLoaded: UIEventSource<boolean> = undefined
|
export let isLoaded: UIEventSource<boolean> = undefined
|
||||||
|
|
||||||
|
let dispatch = createEventDispatcher<{ imagechange: P4CPicture }>()
|
||||||
|
|
||||||
onDestroy(Zoomcontrol.createLock())
|
onDestroy(Zoomcontrol.createLock())
|
||||||
|
|
||||||
let destroyed = false
|
let destroyed = false
|
||||||
|
|
@ -29,20 +30,14 @@
|
||||||
})
|
})
|
||||||
async function initPhotosphere() {
|
async function initPhotosphere() {
|
||||||
const imageInfo: Feature<Point, PanoramaView> = await image.provider.getPanoramaInfo(image)
|
const imageInfo: Feature<Point, PanoramaView> = await image.provider.getPanoramaInfo(image)
|
||||||
if (imageInfo === undefined) {
|
if (imageInfo?.properties?.url === undefined) {
|
||||||
console.error("Image info is apparently undefined for", image)
|
console.error("Image info (or url) is apparently undefined for", image)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const viewer = new PhotoSphereViewerWrapper(viewerEl, imageInfo)
|
const viewer = new PhotoSphereViewerWrapper(viewerEl, imageInfo)
|
||||||
viewer.imageInfo.addCallbackAndRunD(panoramaInfo => {
|
viewer.imageInfo.addCallbackAndRunD(panoramaInfo => {
|
||||||
let provider: ImageProvider
|
console.log("Sending imagechange with", panoramaInfo.properties.p4c, "from", imageInfo)
|
||||||
if (typeof panoramaInfo.properties.provider === "string") {
|
dispatch("imagechange", panoramaInfo.properties.p4c)
|
||||||
provider = AllImageProviders.byName(panoramaInfo.properties.provider)
|
|
||||||
} else {
|
|
||||||
provider = panoramaInfo.properties.provider
|
|
||||||
}
|
|
||||||
console.log(">>> Got:", panoramaInfo, "by", provider.name)
|
|
||||||
//actuallyDisplayed.set(image.properties.imageMeta)
|
|
||||||
})
|
})
|
||||||
if (Array.isArray(nearbyFeatures)) {
|
if (Array.isArray(nearbyFeatures)) {
|
||||||
viewer.setNearbyFeatures(nearbyFeatures)
|
viewer.setNearbyFeatures(nearbyFeatures)
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,23 @@
|
||||||
|
|
||||||
export let tags: UIEventSource<OsmTags>
|
export let tags: UIEventSource<OsmTags>
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
export let image: P4CPicture
|
export let image: UIEventSource<P4CPicture>
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
|
|
||||||
export let highlighted: UIEventSource<string> = undefined
|
export let highlighted: UIEventSource<string> = undefined
|
||||||
export let nearbyFeatures: Feature<Point, HotspotProperties>[] | Store<Feature<Point, HotspotProperties>[]> = []
|
export let nearbyFeatures: Feature<Point, HotspotProperties>[] | Store<Feature<Point, HotspotProperties>[]> = []
|
||||||
export let linkable = true
|
export let linkable = true
|
||||||
let targetValue = Object.values(image.osmTags)[0]
|
let targetValue: Store<string> = image.mapD(image => {
|
||||||
let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v))
|
console.log("Calculating new targetValue", image.osmTags)
|
||||||
|
return Object.values(image.osmTags)[0]
|
||||||
|
})
|
||||||
|
targetValue.addCallbackAndRunD(tv => console.log("New target value is", tv))
|
||||||
|
let isLinked = new UIEventSource<boolean>(undefined)
|
||||||
|
targetValue.mapD(targetValue => {
|
||||||
|
Object.values(tags.data).some((v) => targetValue === v)
|
||||||
|
}, [tags])
|
||||||
|
|
||||||
isLinked.addCallbackAndRun((linked) => {
|
isLinked.addCallbackAndRun((linked) => {
|
||||||
if (linked) {
|
if (linked) {
|
||||||
MenuState.previewedImage.set(undefined)
|
MenuState.previewedImage.set(undefined)
|
||||||
|
|
@ -38,23 +46,22 @@
|
||||||
})
|
})
|
||||||
const t = Translations.t.image.nearby
|
const t = Translations.t.image.nearby
|
||||||
|
|
||||||
|
|
||||||
|
let providedImage: Store<ProvidedImage> = image.mapD(image => {
|
||||||
let date: Date
|
let date: Date
|
||||||
if (image.date) {
|
|
||||||
try {
|
try {
|
||||||
date = new Date(image.date)
|
date = new Date(image.date)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Could not parse image date", image.date, "for", image.detailsUrl)
|
console.warn("Could not parse image date", image.date, "for", image.detailsUrl)
|
||||||
}
|
}
|
||||||
}
|
const license: LicenseInfo = <LicenseInfo>{
|
||||||
|
|
||||||
let license: LicenseInfo = {
|
|
||||||
artist: image.author,
|
artist: image.author,
|
||||||
license: image.license,
|
license: image.license,
|
||||||
informationLocation: image.detailsUrl ?? image.pictureUrl ?? image.thumbUrl,
|
informationLocation: image.detailsUrl ?? image.pictureUrl ?? image.thumbUrl,
|
||||||
date,
|
date
|
||||||
}
|
}
|
||||||
|
|
||||||
let providedImage: ProvidedImage = {
|
return <ProvidedImage>{
|
||||||
url: image.thumbUrl ?? image.pictureUrl,
|
url: image.thumbUrl ?? image.pictureUrl,
|
||||||
url_hd: image.pictureUrl,
|
url_hd: image.pictureUrl,
|
||||||
key: undefined,
|
key: undefined,
|
||||||
|
|
@ -62,60 +69,73 @@
|
||||||
date,
|
date,
|
||||||
id: Object.values(image.osmTags)[0],
|
id: Object.values(image.osmTags)[0],
|
||||||
isSpherical: image.details.isSpherical,
|
isSpherical: image.details.isSpherical,
|
||||||
license,
|
license
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function applyLink(isLinked: boolean) {
|
async function applyLink(isLinked: boolean) {
|
||||||
console.log("Applying linked image", isLinked, targetValue)
|
|
||||||
const currentTags = tags.data
|
const currentTags = tags.data
|
||||||
const key = Object.keys(image.osmTags)[0]
|
const key = Object.keys(image.data.osmTags)[0]
|
||||||
const url = targetValue
|
const url = image.data.osmTags[key]
|
||||||
|
console.log("Applying linked image", isLinked, url)
|
||||||
if (isLinked) {
|
if (isLinked) {
|
||||||
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
||||||
theme: tags.data._orig_theme ?? state.theme.id,
|
theme: tags.data._orig_theme ?? state.theme.id,
|
||||||
changeType: "link-image",
|
changeType: "link-image"
|
||||||
})
|
})
|
||||||
await state.changes.applyAction(action)
|
await state.changes.applyAction(action)
|
||||||
} else {
|
} else {
|
||||||
for (const k in currentTags) {
|
for (const k in currentTags) {
|
||||||
const v = currentTags[k]
|
const v: string = currentTags[k]
|
||||||
if (v === url) {
|
if (v === url) {
|
||||||
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
||||||
theme: tags.data._orig_theme ?? state.theme.id,
|
theme: tags.data._orig_theme ?? state.theme.id,
|
||||||
changeType: "remove-image",
|
changeType: "remove-image"
|
||||||
})
|
})
|
||||||
state.changes.applyAction(action)
|
await state.changes.applyAction(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onImageChanged(p4c: P4CPicture) {
|
||||||
|
console.log("Viewer changed panorama, now got", p4c)
|
||||||
|
if (p4c) {
|
||||||
|
image.set(p4c)
|
||||||
|
} else {
|
||||||
|
console.trace("Got a null!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isLinked.addCallback((isLinked) => applyLink(isLinked))
|
isLinked.addCallback((isLinked) => applyLink(isLinked))
|
||||||
|
|
||||||
let element: HTMLDivElement
|
let element: HTMLDivElement
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
onDestroy(
|
onDestroy(
|
||||||
highlighted.addCallbackD((highlightedUrl) => {
|
highlighted.addCallbackD((highlightedUrl) => {
|
||||||
if (highlightedUrl === image.pictureUrl) {
|
if (highlightedUrl === image.data.pictureUrl) {
|
||||||
Utils.scrollIntoView(element)
|
Utils.scrollIntoView(element)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex w-fit shrink-0 flex-col overflow-hidden rounded-lg"
|
class="flex w-fit shrink-0 flex-col overflow-hidden rounded-lg"
|
||||||
class:border-interactive={$isLinked || $highlighted === image.pictureUrl}
|
class:border-interactive={$isLinked || $highlighted === $image.pictureUrl}
|
||||||
style="border-width: 2px"
|
style="border-width: 2px"
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
>
|
>
|
||||||
<AttributedImage
|
<AttributedImage
|
||||||
{state}
|
{state}
|
||||||
image={providedImage}
|
image={$providedImage}
|
||||||
{nearbyFeatures}
|
{nearbyFeatures}
|
||||||
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
|
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
|
||||||
attributionFormat="minimal"
|
attributionFormat="minimal"
|
||||||
|
on:imagechange={(ev) => onImageChanged(ev.detail) }
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="dot-menu-actions">
|
<svelte:fragment slot="dot-menu-actions">
|
||||||
<LoginToggle {state} silentFail={true} hiddenFail={true}>
|
<LoginToggle {state} silentFail={true} hiddenFail={true}>
|
||||||
|
|
@ -123,6 +143,7 @@
|
||||||
<label>
|
<label>
|
||||||
<input bind:checked={$isLinked} type="checkbox" />
|
<input bind:checked={$isLinked} type="checkbox" />
|
||||||
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
|
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
|
||||||
|
{$image.author} {$image.osmTags["panoramax"]}
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
</LoginToggle>
|
</LoginToggle>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||||
import LinkableImage from "./LinkableImage.svelte"
|
import LinkableImage from "./LinkableImage.svelte"
|
||||||
import type { Feature, Geometry, Point } from "geojson"
|
import type { Feature, Point } from "geojson"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||||
|
|
@ -53,9 +53,9 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
// Panorama-views get a geojson feature to browse around
|
// Panorama-views get a geojson feature to browse around
|
||||||
let asFeatures = result.map((p4cs) =>
|
let asFeatures = result.map((p4cs: P4CPicture[]) =>
|
||||||
p4cs.map(
|
p4cs.map(
|
||||||
(p4c) =>
|
(p4c: P4CPicture) =>
|
||||||
<Feature<Point, PanoramaView>>{
|
<Feature<Point, PanoramaView>>{
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {
|
geometry: {
|
||||||
|
|
@ -68,7 +68,8 @@
|
||||||
northOffset: p4c.direction,
|
northOffset: p4c.direction,
|
||||||
rotation: p4c.direction,
|
rotation: p4c.direction,
|
||||||
spherical: p4c.details.isSpherical ? "yes" : "no",
|
spherical: p4c.details.isSpherical ? "yes" : "no",
|
||||||
provider: p4c.provider
|
provider: p4c.provider,
|
||||||
|
p4c
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -150,7 +151,7 @@
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let nearbyFeatures: Store<Feature<Geometry, HotspotProperties>[]> = asFeatures.map(
|
let nearbyFeatures: Store<Feature<Point, HotspotProperties>[]> = asFeatures.map(
|
||||||
(nearbyPoints) => {
|
(nearbyPoints) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -166,11 +167,12 @@
|
||||||
.map((f) => ({
|
.map((f) => ({
|
||||||
...f,
|
...f,
|
||||||
properties: <HotspotProperties>{
|
properties: <HotspotProperties>{
|
||||||
name: "Nearby panorama",
|
name: "Nearby panorama by " + f.properties.p4c.author,
|
||||||
pitch: "auto",
|
pitch: "auto",
|
||||||
type: "scene",
|
type: "scene",
|
||||||
gotoPanorama: f,
|
gotoPanorama: f,
|
||||||
focus: false,
|
focus: false,
|
||||||
|
originalP4C: f.properties.p4c
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
|
|
@ -216,7 +218,7 @@
|
||||||
>
|
>
|
||||||
<LinkableImage
|
<LinkableImage
|
||||||
{tags}
|
{tags}
|
||||||
{image}
|
image={new UIEventSource(image)}
|
||||||
{state}
|
{state}
|
||||||
{feature}
|
{feature}
|
||||||
{layer}
|
{layer}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ export class PhotoSphereViewerWrapper {
|
||||||
imageInfo: Feature<Point, PanoramaView>,
|
imageInfo: Feature<Point, PanoramaView>,
|
||||||
nearbyFeatures?: Feature<Geometry, HotspotProperties>[]
|
nearbyFeatures?: Feature<Geometry, HotspotProperties>[]
|
||||||
) {
|
) {
|
||||||
this._imageInfo.set(imageInfo)
|
console.log(">>>", imageInfo.properties.url)
|
||||||
|
|
||||||
this.viewer = pannellum.viewer(container, <any>{
|
this.viewer = pannellum.viewer(container, <any>{
|
||||||
default: {
|
default: {
|
||||||
firstScene: imageInfo.properties.url,
|
firstScene: imageInfo.properties.url,
|
||||||
|
|
@ -35,7 +36,7 @@ export class PhotoSphereViewerWrapper {
|
||||||
panorama: imageInfo.properties.url,
|
panorama: imageInfo.properties.url,
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
hotSpots: [],
|
hotSpots: [],
|
||||||
sceneFadeDuration: 250,
|
// sceneFadeDuration: 250,
|
||||||
compass: true,
|
compass: true,
|
||||||
showControls: false,
|
showControls: false,
|
||||||
northOffset: imageInfo.properties.northOffset,
|
northOffset: imageInfo.properties.northOffset,
|
||||||
|
|
@ -43,7 +44,7 @@ export class PhotoSphereViewerWrapper {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
this._imageInfo.set(imageInfo)
|
||||||
this.setNearbyFeatures(nearbyFeatures)
|
this.setNearbyFeatures(nearbyFeatures)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -80,17 +81,22 @@ export class PhotoSphereViewerWrapper {
|
||||||
// Already the current scene
|
// Already the current scene
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!imageInfo?.properties?.url) {
|
||||||
|
return // Not a panorama
|
||||||
|
}
|
||||||
|
console.log(">>><<<", imageInfo.properties.url)
|
||||||
this.clearHotspots()
|
this.clearHotspots()
|
||||||
this.viewer.addScene(imageInfo.properties.url, <any>{
|
this.viewer.addScene(imageInfo.properties.url, <any>{
|
||||||
panorama: imageInfo.properties.url,
|
panorama: imageInfo.properties.url,
|
||||||
northOffset: imageInfo.properties.northOffset,
|
northOffset: imageInfo.properties.northOffset,
|
||||||
type: "equirectangular",
|
type: "equirectangular",
|
||||||
showControls: false,
|
showControls: false,
|
||||||
|
compass: true
|
||||||
})
|
})
|
||||||
|
|
||||||
this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset)
|
this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset)
|
||||||
this.setNearbyFeatures(this.nearbyFeatures)
|
|
||||||
this._imageInfo.set(imageInfo)
|
this._imageInfo.set(imageInfo)
|
||||||
|
this.setNearbyFeatures(this.nearbyFeatures) // Depends on 'imageInfo', must be set _after_
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue