diff --git a/.forgejo/workflows/on_release.yml b/.forgejo/workflows/on_release.yml index 83e4c6631f..8a6092e4a8 100644 --- a/.forgejo/workflows/on_release.yml +++ b/.forgejo/workflows/on_release.yml @@ -1,4 +1,5 @@ on: + workflow_dispatch: push: tags: - 'v*' @@ -42,7 +43,10 @@ jobs: run: npm run android:prepare - name: Decode keystore - run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > ~/.gradle/release-key.jks + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > ./android/app/release-key.jks + pwd + echo "Saved release key to ./android/app/release-key.jks" - name: Grant execute permission to gradlew run: cd android && chmod +x ./gradlew @@ -52,7 +56,8 @@ jobs: cd android export ANDROID_SDK_HOME=/home/runner/.android/sdk/ export PATH=$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools:$ANDROID_SDK_HOME/:$ANDROID_SDK_HOME/cmdline-tools/latest/tools/bin:$PATH - export storeFile=my-release-key.jks + # Those variables are used in MapComplete/android/app/build.gradle + export storeFile="./release-key.jks" export storePassword=${{ secrets.KEYSTORE_PASSWORD }} export keyAlias=${{ secrets.KEY_ALIAS }} export keyPassword=${{ secrets.KEY_PASSWORD }} diff --git a/.forgejo/workflows/update_database.yml b/.forgejo/workflows/update_database.yml index 39ed860920..b16b80619f 100644 --- a/.forgejo/workflows/update_database.yml +++ b/.forgejo/workflows/update_database.yml @@ -29,7 +29,7 @@ jobs: DATE=$(echo $TIMESTAMP | sed "s/T.*//") echo $DATE # Create a new database in postgres - npm run create:database -- -- $DATE + npm run create:database -- -- $DATE --overwrite echo "Seeding database '$DATE'" osm2pgsql -O flex -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi.${DATE} /data/planet-latest.osm.pbf npm run delete:database:old diff --git a/android b/android index 921863589c..fc597bf3c9 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit 921863589c14e1a3002a6be491337de6fe8778dd +Subproject commit fc597bf3c9ebf1280af4e991557d66f5d5838a24 diff --git a/assets/layers/fitness_station/Trimm-Dich-Pfad_Grünwalder_Forst_Klimmzüge.jpg b/assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg similarity index 100% rename from assets/layers/fitness_station/Trimm-Dich-Pfad_Grünwalder_Forst_Klimmzüge.jpg rename to assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg diff --git a/assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg.license b/assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg.license new file mode 100644 index 0000000000..9f9bc3b1b2 --- /dev/null +++ b/assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Zeitlupe +SPDX-License-Identifier: CC-BY-SA-4.0 \ No newline at end of file diff --git a/assets/layers/fitness_station/fitness_station.json b/assets/layers/fitness_station/fitness_station.json index 3d934c2988..be3a3b8a2c 100644 --- a/assets/layers/fitness_station/fitness_station.json +++ b/assets/layers/fitness_station/fitness_station.json @@ -166,7 +166,7 @@ "it": "Questa stazione fitness ha una sbarra orizzontale, abbastanza alta per le trazioni." }, "icon": { - "path": "./assets/layers/fitness_station/Trimm-Dich-Pfad_Grünwalder_Forst_Klimmzüge.jpg", + "path": "./assets/layers/fitness_station/Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg", "class": "large" } }, diff --git a/assets/layers/fitness_station/license_info.json b/assets/layers/fitness_station/license_info.json index 89f250c60c..3f64204d2a 100644 --- a/assets/layers/fitness_station/license_info.json +++ b/assets/layers/fitness_station/license_info.json @@ -242,7 +242,7 @@ ] }, { - "path": "Trimm-Dich-Pfad_Grünwalder_Forst_Klimmzüge.jpg", + "path": "Trimm-Dich-Pfad_Grunwalder_Forst_Klimmzuge.jpg", "license": "CC-BY-SA-4.0", "authors": [ "Zeitlupe" diff --git a/scripts/osm2pgsql/createNewDatabase.ts b/scripts/osm2pgsql/createNewDatabase.ts index b22aa80ef0..a4afd31cd7 100644 --- a/scripts/osm2pgsql/createNewDatabase.ts +++ b/scripts/osm2pgsql/createNewDatabase.ts @@ -9,8 +9,18 @@ class CreateNewDatabase extends Script { } async main(args: string[]): Promise { + const targetName = args[0] + const overwrite = args[1] === "--overwrite" const db = new OsmPoiDatabase("postgresql://user:password@localhost:5444") - await db.createNew(args[0]) + const knownDatabases = await db.findSuitableDatabases() + if (knownDatabases.indexOf(OsmPoiDatabase.databaseNameFor(targetName)) > 0) { + if (overwrite) { + await db.deleteDatabase(targetName) + } else { + throw "ERROR: the target database " + targetName + " already exists" + } + } + await db.createNew(targetName) } } diff --git a/scripts/osm2pgsql/osmPoiDatabase.ts b/scripts/osm2pgsql/osmPoiDatabase.ts index 4789f75876..8b55bcac8c 100644 --- a/scripts/osm2pgsql/osmPoiDatabase.ts +++ b/scripts/osm2pgsql/osmPoiDatabase.ts @@ -104,8 +104,12 @@ export class OsmPoiDatabase { return "osm-poi." + latest } + public static databaseNameFor(date: string) { + return `${OsmPoiDatabase._prefix}.${date}` + } + async createNew(date: string) { - const dbname = `${OsmPoiDatabase._prefix}.${date}` + const dbname = OsmPoiDatabase.databaseNameFor(date) console.log("Attempting to create a new database with name", dbname) const metaclient = this.getMetaClient() await metaclient.connect() diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index 99b23f01b3..d3d14b705e 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -36,6 +36,7 @@ export interface PanoramaView { */ northOffset?: number pitchOffset?: number + provider: ImageProvider | string } /** @@ -123,7 +124,6 @@ export default abstract class ImageProvider { ): undefined | ProvidedImage[] | Promise public abstract DownloadAttribution(providedImage: { - url: string id: string }): Promise @@ -141,7 +141,7 @@ export default abstract class ImageProvider { id: string }): Promise> | undefined - public static async offerImageAsDownload(image: ProvidedImage) { + public static async offerImageAsDownload(image: { url_hd?: string, url: string }) { 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), { diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index 8b18be9ff7..f7239b2cc3 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -75,27 +75,27 @@ export class Imgur extends ImageProvider { * * const data = {"data":{"id":"I9t6B7B","title":"Station Knokke","description":"author:Pieter Vander Vennet\r\nlicense:CC-BY 4.0\r\nosmid:node\/9812712386","datetime":1655052078,"type":"image\/jpeg","animated":false,"width":2400,"height":1795,"size":910872,"views":2,"bandwidth":1821744,"vote":null,"favorite":false,"nsfw":false,"section":null,"account_url":null,"account_id":null,"is_ad":false,"in_most_viral":false,"has_sound":false,"tags":[],"ad_type":0,"ad_url":"","edited":"0","in_gallery":false,"link":"https:\/\/i.imgur.com\/I9t6B7B.jpg","ad_config":{"safeFlags":["not_in_gallery","share"],"highRiskFlags":[],"unsafeFlags":["sixth_mod_unsafe"],"wallUnsafeFlags":[],"showsAds":false,"showAdLevel":1}},"success":true,"status":200} * Utils.injectJsonDownloadForTests("https://api.imgur.com/3/image/E0RuAK3", data) - * const licenseInfo = await Imgur.singleton.DownloadAttribution({url: "https://i.imgur.com/E0RuAK3.jpg"}) + * const licenseInfo = await Imgur.singleton.DownloadAttribution({id: "https://i.imgur.com/E0RuAK3.jpg"}) * const expected = new LicenseInfo() * expected.licenseShortName = "CC-BY 4.0" * expected.artist = "Pieter Vander Vennet" * expected.date = new Date(1655052078000) * expected.views = 2 * licenseInfo // => expected - * const licenseInfoJpeg = await Imgur.singleton.DownloadAttribution({url:"https://i.imgur.com/E0RuAK3.jpeg"}) + * const licenseInfoJpeg = await Imgur.singleton.DownloadAttribution({id:"https://i.imgur.com/E0RuAK3.jpeg"}) * licenseInfoJpeg // => expected - * const licenseInfoUpperCase = await Imgur.singleton.DownloadAttribution({url: "https://i.imgur.com/E0RuAK3.JPEG"}) + * const licenseInfoUpperCase = await Imgur.singleton.DownloadAttribution({id: "https://i.imgur.com/E0RuAK3.JPEG"}) * licenseInfoUpperCase // => expected * * */ public async DownloadAttribution( providedImage: { - url: string + id: string }, withResponse?: (obj) => void ): Promise { - const url = providedImage.url + const url = providedImage.id const hash = url.substr("https://i.imgur.com/".length).split(/(\.jpe?g)|(\.png)/i)[0] const apiUrl = "https://api.imgur.com/3/image/" + hash diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 64b477bd6c..44b95ec55e 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -169,6 +169,8 @@ export class Mapillary extends ImageProvider { properties: { url: response.thumb_2048_url, northOffset: response.computed_compass_angle, + provider: this, + imageMeta: image }, } } diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts index 4bcf26742d..8227c78d02 100644 --- a/src/Logic/ImageProviders/Panoramax.ts +++ b/src/Logic/ImageProviders/Panoramax.ts @@ -208,7 +208,6 @@ export default class PanoramaxImageProvider extends ImageProvider { } public async DownloadAttribution(providedImage: { - url: string id: string }): Promise { const meta = await this.getInfoFor(providedImage.id) @@ -245,10 +244,12 @@ export default class PanoramaxImageProvider extends ImageProvider { return >{ type: "Feature", geometry: imageInfo.geometry, - properties: { + properties: { url, northOffset, pitchOffset, + provider: this, + imageMeta: imageInfo }, } } diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index 3d587e5499..5d247f1ff0 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -155,9 +155,9 @@ export class WikimediaImageProvider extends ImageProvider { return [this.UrlForImage("File:" + value)] } - public async DownloadAttribution(img: { url: string }): Promise { - const filename = "File:" + WikimediaImageProvider.extractFileName(img.url) - console.log("Downloading attribution for", filename, img.url) + public async DownloadAttribution(img: { id: string }): Promise { + const filename = "File:" + WikimediaImageProvider.extractFileName(img.id) + console.log("Downloading attribution for", filename, img.id) if (filename === "") { return undefined } diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 7e32404093..89d5f749cc 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -17,7 +17,7 @@ interface ImageFetcher { * @param lat * @param lon */ - fetchImages(lat: number, lon: number): Promise + fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> readonly name: string } @@ -25,9 +25,9 @@ interface ImageFetcher { class CachedFetcher implements ImageFetcher { private readonly _fetcher: ImageFetcher private readonly _zoomlevel: number - private readonly cache: Map> = new Map< + private readonly cache: Map> = new Map< number, - Promise + Promise<(P4CPicture & { id: string })[]> >() public readonly name: string @@ -37,7 +37,7 @@ class CachedFetcher implements ImageFetcher { this.name = fetcher.name } - fetchImages(lat: number, lon: number): Promise { + fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> { const tile = Tiles.embedded_tile(lat, lon, this._zoomlevel) const tileIndex = Tiles.tile_index(tile.z, tile.x, tile.y) if (this.cache.has(tileIndex)) { @@ -80,7 +80,7 @@ class NearbyImageUtils { } class P4CImageFetcher implements ImageFetcher { - public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const + public static readonly services = ["flickr", "kartaview", "wikicommons"] as const public static readonly apiUrls = ["https://api.flickr.com"] private _options: { maxDaysOld: number; searchRadius: number } public readonly name: P4CService @@ -90,7 +90,7 @@ class P4CImageFetcher implements ImageFetcher { this._options = options } - async fetchImages(lat: number, lon: number): Promise { + async fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> { const picManager = new P4C.PicturesManager({ usefetchers: [this.name] }) const maxAgeSeconds = (this._options?.maxDaysOld ?? 3 * 365) * 24 * 60 * 60 * 1000 const searchRadius = this._options?.searchRadius ?? 100 @@ -124,8 +124,8 @@ class ImagesInLoadedDataFetcher implements ImageFetcher { this._searchRadius = searchRadius } - async fetchImages(lat: number, lon: number): Promise { - const foundImages: P4CPicture[] = [] + async fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> { + const foundImages: (P4CPicture & { id: string })[] = [] this.indexedFeatures.features.data.forEach((feature) => { const props = feature.properties const images = [] @@ -149,6 +149,7 @@ class ImagesInLoadedDataFetcher implements ImageFetcher { foundImages.push({ pictureUrl: image, thumbUrl: image, + id: image, coordinates: { lng: centerpoint[0], lat: centerpoint[1] }, provider: "OpenStreetMap", details: { @@ -182,9 +183,10 @@ class ImagesFromPanoramaxFetcher implements ImageFetcher { } } - private static convert(imageData: ImageData): P4CPicture { + private static convert(imageData: ImageData): P4CPicture & { id: string } { const [lng, lat] = imageData.geometry.coordinates return { + id: imageData.id, pictureUrl: imageData.assets.sd.href, coordinates: { lng, lat }, @@ -205,7 +207,7 @@ class ImagesFromPanoramaxFetcher implements ImageFetcher { } } - public async fetchImages(lat: number, lon: number): Promise { + public async fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> { const radiusSettings = [ { place_fov_tolerance: 180, @@ -272,7 +274,7 @@ class MapillaryFetcher implements ImageFetcher { this.end_captured_at = options?.end_captured_at } - async fetchImages(lat: number, lon: number): Promise { + async fetchImages(lat: number, lon: number): Promise<(P4CPicture & { id: string })[]> { const boundingBox = new BBox([[lon, lat]]).padAbsolute(0.003) let url = "https://graph.mapillary.com/images?fields=geometry,computed_geometry,creator,id,captured_at,thumb_256_url,thumb_original_url,compass_angle&bbox=" + @@ -313,7 +315,7 @@ class MapillaryFetcher implements ImageFetcher { captured_at: number }[] }>(url) - const pics: P4CPicture[] = [] + const pics: (P4CPicture & { id: string })[] = [] for (const img of response.data) { const c = img.computed_geometry?.coordinates ?? img.geometry.coordinates if (img.thumb_original_url === undefined) { @@ -322,6 +324,7 @@ class MapillaryFetcher implements ImageFetcher { const [lon, lat] = img.computed_geometry.coordinates pics.push({ pictureUrl: img.thumb_original_url, + id: img.id, provider: "Mapillary", coordinates: { lng: c[0], lat: c[1] }, thumbUrl: img.thumb_256_url, @@ -371,7 +374,7 @@ export class CombinedFetcher { start_captured_at: maxage, panoramas: "no", }), - new P4CImageFetcher("mapillary"), + // new P4CImageFetcher("mapillary"), new P4CImageFetcher("wikicommons"), ].map((f) => new CachedFetcher(f)) } @@ -411,7 +414,7 @@ export class CombinedFetcher { lon: number, lat: number ): { - images: Store + images: Store<(P4CPicture & { provider })[]> state: Store> } { const sink = new UIEventSource([]) diff --git a/src/UI/Image/ImageOperations.svelte b/src/UI/Image/ImageOperations.svelte index 5d4450921b..46b8d2dd36 100644 --- a/src/UI/Image/ImageOperations.svelte +++ b/src/UI/Image/ImageOperations.svelte @@ -8,7 +8,7 @@ import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import ImageAttribution from "./ImageAttribution.svelte" import ImagePreview from "./ImagePreview.svelte" - import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid" + import { DownloadIcon, ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" import { twMerge } from "tailwind-merge" import { UIEventSource } from "../../Logic/UIEventSource" import Loading from "../Base/Loading.svelte" @@ -23,7 +23,7 @@ export let nearbyFeatures: | Feature[] | Store[]> = [] - + let visitUrl = image.provider?.visitUrl(image) let isLoaded = new UIEventSource(false) @@ -39,22 +39,28 @@ {#if $$slots["dot-menu-actions"]} - - - + + + + {#if visitUrl !== undefined} + + + + + {/if} {/if}
- +
diff --git a/src/UI/Image/ImagePreview.svelte b/src/UI/Image/ImagePreview.svelte index f7eb0d62f8..e9cdc0029c 100644 --- a/src/UI/Image/ImagePreview.svelte +++ b/src/UI/Image/ImagePreview.svelte @@ -3,24 +3,22 @@ * The image preview allows to drag and zoom in to the image */ import panzoom from "panzoom" - import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" - import { UIEventSource } from "../../Logic/UIEventSource" + import type { HotspotProperties, PanoramaView, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" + import ImageProvider from "../../Logic/ImageProviders/ImageProvider" + import { Store, UIEventSource } from "../../Logic/UIEventSource" import Zoomcontrol from "../Zoomcontrol" import { onDestroy } from "svelte" - import type { PanoramaView } from "../../Logic/ImageProviders/ImageProvider" import { PhotoSphereViewerWrapper } from "./photoSphereViewerWrapper" - import type { Feature, Geometry, Point } from "geojson" - import { Store } from "../../Logic/UIEventSource" + import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" export let nearbyFeatures: | Feature[] | Store[]> = [] - export let image: Partial + export let image: Partial & { url: string, id: string } let panzoomInstance = undefined let panzoomEl: HTMLElement let viewerEl: HTMLElement - export let isLoaded: UIEventSource = undefined onDestroy(Zoomcontrol.createLock()) @@ -32,10 +30,20 @@ async function initPhotosphere() { const imageInfo: Feature = await image.provider.getPanoramaInfo(image) if (imageInfo === undefined) { - console.error("Image info is apperently undefined for", image) + console.error("Image info is apparently undefined for", image) return } const viewer = new PhotoSphereViewerWrapper(viewerEl, imageInfo) + viewer.imageInfo.addCallbackAndRunD(panoramaInfo => { + let provider: ImageProvider + if (typeof panoramaInfo.properties.provider === "string") { + 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)) { viewer.setNearbyFeatures(nearbyFeatures) } else { @@ -77,5 +85,6 @@ isLoaded?.setData(true) }} src={image.url_hd ?? image.url} + alt="" /> {/if} diff --git a/src/UI/Image/LinkableImage.svelte b/src/UI/Image/LinkableImage.svelte index e9971b2537..3bdd50542f 100644 --- a/src/UI/Image/LinkableImage.svelte +++ b/src/UI/Image/LinkableImage.svelte @@ -7,10 +7,10 @@ import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" - import type { Feature } from "geojson" + import type { Feature, Point } from "geojson" import Translations from "../i18n/Translations" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" - import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" + import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import AttributedImage from "./AttributedImage.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" import LoginToggle from "../Base/LoginToggle.svelte" @@ -30,7 +30,7 @@ export let highlighted: UIEventSource = undefined - export let nearbyFeatures: Feature[] | Store = [] + export let nearbyFeatures: Feature[] | Store[]> = [] export let linkable = true let targetValue = Object.values(image.osmTags)[0] let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v)) diff --git a/src/UI/Image/NearbyImages.svelte b/src/UI/Image/NearbyImages.svelte index 484b561690..7c1cb52dfa 100644 --- a/src/UI/Image/NearbyImages.svelte +++ b/src/UI/Image/NearbyImages.svelte @@ -68,6 +68,7 @@ northOffset: p4c.direction, rotation: p4c.direction, spherical: p4c.details.isSpherical ? "yes" : "no", + provider: p4c.provider }, } ) diff --git a/src/UI/Image/photoSphereViewerWrapper.ts b/src/UI/Image/photoSphereViewerWrapper.ts index bdbfb6f64e..fc31fd90e7 100644 --- a/src/UI/Image/photoSphereViewerWrapper.ts +++ b/src/UI/Image/photoSphereViewerWrapper.ts @@ -3,18 +3,26 @@ import "pannellum" import { Feature, Geometry, Point } from "geojson" import { GeoOperations } from "../../Logic/GeoOperations" import { HotspotProperties, PanoramaView } from "../../Logic/ImageProviders/ImageProvider" +import { Store, UIEventSource } from "../../Logic/UIEventSource" export class PhotoSphereViewerWrapper { - private imageInfo: Feature + private _imageInfo: UIEventSource>> = new UIEventSource(undefined) + public imageInfo: Store>> = this._imageInfo private readonly viewer: Pannellum.Viewer private nearbyFeatures: Feature[] = [] + /** + * + * @param container The HTML-element to bind to + * @param imageInfo An eventSource containing the panorama-info. Might be changed by this component if walking around; + * @param nearbyFeatures Nearby features to show a point about, e.g. to walk around + */ constructor( container: HTMLElement, imageInfo: Feature, nearbyFeatures?: Feature[] ) { - this.imageInfo = imageInfo + this._imageInfo.set(imageInfo) this.viewer = pannellum.viewer(container, { default: { firstScene: imageInfo.properties.url, @@ -31,16 +39,17 @@ export class PhotoSphereViewerWrapper { compass: true, showControls: false, northOffset: imageInfo.properties.northOffset, - horizonPitch: imageInfo.properties.pitchOffset, + horizonPitch: imageInfo.properties.pitchOffset }, }, }) this.setNearbyFeatures(nearbyFeatures) + } public calculatePitch(feature: Feature): number { - const coors = this.imageInfo.geometry.coordinates + const coors = this.imageInfo.data.geometry.coordinates const distance = GeoOperations.distanceBetween( coors, GeoOperations.centerpointCoordinates(feature) @@ -72,7 +81,6 @@ export class PhotoSphereViewerWrapper { return } this.clearHotspots() - this.imageInfo = imageInfo this.viewer.addScene(imageInfo.properties.url, { panorama: imageInfo.properties.url, northOffset: imageInfo.properties.northOffset, @@ -82,27 +90,29 @@ export class PhotoSphereViewerWrapper { this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset) this.setNearbyFeatures(this.nearbyFeatures) + this._imageInfo.set(imageInfo) + } private clearHotspots() { - const hotspots = - this.viewer.getConfig()["scenes"][this.imageInfo.properties.url].hotSpots ?? [] + const currentUrl = this.imageInfo.data.properties.url + const hotspots = this.viewer.getConfig()["scenes"][currentUrl].hotSpots ?? [] for (const hotspot of hotspots) { - this.viewer.removeHotSpot(hotspot?.id, this.imageInfo.properties.url) + this.viewer.removeHotSpot(hotspot?.id, currentUrl) } } public setNearbyFeatures(nearbyFeatures: Feature[]) { - const imageInfo = this.imageInfo + const imageInfo = this.imageInfo.data if (!this.imageInfo) { return } const northOffs = imageInfo.properties.northOffset this.nearbyFeatures = nearbyFeatures this.clearHotspots() - const centralImageLocation = this.imageInfo.geometry.coordinates + const centralImageLocation = imageInfo.geometry.coordinates for (const f of nearbyFeatures ?? []) { - if (f.properties.gotoPanorama?.properties?.url === this.imageInfo.properties.url) { + if (f.properties.gotoPanorama?.properties?.url === imageInfo.properties.url) { continue // This is the current panorama, no need to show it } const yaw = GeoOperations.bearing(imageInfo, GeoOperations.centerpoint(f)) @@ -128,7 +138,7 @@ export class PhotoSphereViewerWrapper { this.setPanorama(f.properties.gotoPanorama) }, }, - this.imageInfo.properties.url + imageInfo.properties.url ) if (f.properties.focus) { this.viewer.setYaw(yaw - northOffs)