diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json
index 9a6714190..8543720f2 100644
--- a/assets/svg/license_info.json
+++ b/assets/svg/license_info.json
@@ -877,6 +877,26 @@
"https://www.OpenStreetMap.org"
]
},
+ {
+ "path": "panoramax.svg",
+ "license": "LOGO",
+ "authors": [
+ "Panoramax"
+ ],
+ "sources": [
+ "https://commons.wikimedia.org/wiki/File:Panoramax.svg"
+ ]
+ },
+ {
+ "path": "panoramax_bw.svg",
+ "license": "LOGO",
+ "authors": [
+ "Panoramax"
+ ],
+ "sources": [
+ "https://commons.wikimedia.org/wiki/File:Panoramax.svg"
+ ]
+ },
{
"path": "party.svg",
"license": "CC-BY-4.0",
diff --git a/assets/svg/panoramax.svg b/assets/svg/panoramax.svg
new file mode 100644
index 000000000..ed1746c88
--- /dev/null
+++ b/assets/svg/panoramax.svg
@@ -0,0 +1,187 @@
+
+
+
+image/svg+xml
diff --git a/assets/svg/panoramax.svg.license b/assets/svg/panoramax.svg.license
new file mode 100644
index 000000000..f0263b4cb
--- /dev/null
+++ b/assets/svg/panoramax.svg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: Panoramax
+SPDX-License-Identifier: LicenseRef-LOGO
\ No newline at end of file
diff --git a/assets/svg/panoramax_bw.svg b/assets/svg/panoramax_bw.svg
new file mode 100644
index 000000000..e50264ff3
--- /dev/null
+++ b/assets/svg/panoramax_bw.svg
@@ -0,0 +1,217 @@
+
+
+
+image/svg+xml
+
+
+
+
+
+
diff --git a/assets/svg/panoramax_bw.svg.license b/assets/svg/panoramax_bw.svg.license
new file mode 100644
index 000000000..f0263b4cb
--- /dev/null
+++ b/assets/svg/panoramax_bw.svg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: Panoramax
+SPDX-License-Identifier: LicenseRef-LOGO
\ No newline at end of file
diff --git a/langs/en.json b/langs/en.json
index 300dca8a2..7e5907479 100644
--- a/langs/en.json
+++ b/langs/en.json
@@ -202,7 +202,9 @@
"openMapillary": "Open Mapillary here",
"openOsmcha": "See latest edits made with {theme}",
"openOsmchaLastWeek": "See edits from the last 7 days",
+ "openPanoramax": "Open Panoramax here",
"openThemeDocumentation": "Open the documentation for thematic map {name}",
+ "panoramaxHelp": "Panoramax is an online service which gathers street-level pictures and offers them under a free license. Contributors are allowed to use these pictures to improve OpenStreetMap",
"seeOnMapillary": "See this image on Mapillary",
"themeBy": "Theme maintained by {author}",
"title": "Copyright and attribution",
diff --git a/langs/layers/en.json b/langs/layers/en.json
index 9158b0c40..60daa8113 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -4142,6 +4142,9 @@
},
"2": {
"then": "This drinking water is closed"
+ },
+ "3": {
+ "then": "This drinking water is permanently closed"
}
},
"question": "Is this drinking water spot still operational?",
diff --git a/package-lock.json b/package-lock.json
index cea1831a3..ef5e1ba5d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,13 +58,13 @@
"maplibre-gl": "^4.1.1",
"marked": "^12.0.2",
"monaco-editor": "^0.46.0",
- "mvt-to-geojson": "^0.0.2",
+ "mvt-to-geojson": "^0.0.5",
"name-suggestion-index": "^6.0.20240422",
"npm": "^10.7.0",
"opening_hours": "^3.6.0",
"osm-auth": "^2.5.0",
"osmtogeojson": "^3.0.0-beta.5",
- "panoramax-js": "^0.1.7",
+ "panoramax-js": "^0.3.6",
"panzoom": "^9.4.3",
"papaparse": "^5.3.1",
"pg": "^8.11.3",
@@ -13146,9 +13146,9 @@
"license": "MIT"
},
"node_modules/mvt-to-geojson": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/mvt-to-geojson/-/mvt-to-geojson-0.0.2.tgz",
- "integrity": "sha512-DLi1Hgowqy6ygOUIuOhtI5RLdph9VpQSP6Ct3qbOY/y0dSTBjTvpIXgbK3J0XSO9feBRrhjcQAMkzOVD/KkuDA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/mvt-to-geojson/-/mvt-to-geojson-0.0.5.tgz",
+ "integrity": "sha512-IG4bJJP9nxY/9LenWBY0yi5SEv9a1G06trP5b4Tg01LCrzB32US/FO9bygJr7c+ZVbt175AzBwJiiawaxfLTbA==",
"dependencies": {
"@types/geojson": "^7946.0.14",
"pbf": "^3.2.1"
@@ -16003,9 +16003,9 @@
"license": "MIT"
},
"node_modules/panoramax-js": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.1.7.tgz",
- "integrity": "sha512-G5m0cs+kODGyxmuLI06m+BcQecey2d6zhk+bajUMTkeXqvIIxP+CUS6NKnmN0WyIntd4q91gdGJeOR0Q2I/jeg==",
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.3.6.tgz",
+ "integrity": "sha512-CRdXwh91H6chd1PYptG070ukx+S6IkVaUeQVG91ySevoJoCYOuBT65qkMhRo49X2um1nGcs9UqolW90R57875g==",
"dependencies": {
"@ogcapi-js/features": "^1.1.1",
"@ogcapi-js/shared": "^1.1.1",
@@ -30241,9 +30241,9 @@
"version": "1.0.0"
},
"mvt-to-geojson": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/mvt-to-geojson/-/mvt-to-geojson-0.0.2.tgz",
- "integrity": "sha512-DLi1Hgowqy6ygOUIuOhtI5RLdph9VpQSP6Ct3qbOY/y0dSTBjTvpIXgbK3J0XSO9feBRrhjcQAMkzOVD/KkuDA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/mvt-to-geojson/-/mvt-to-geojson-0.0.5.tgz",
+ "integrity": "sha512-IG4bJJP9nxY/9LenWBY0yi5SEv9a1G06trP5b4Tg01LCrzB32US/FO9bygJr7c+ZVbt175AzBwJiiawaxfLTbA==",
"requires": {
"@types/geojson": "^7946.0.14",
"pbf": "^3.2.1"
@@ -32074,9 +32074,9 @@
"version": "1.0.0"
},
"panoramax-js": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.1.7.tgz",
- "integrity": "sha512-G5m0cs+kODGyxmuLI06m+BcQecey2d6zhk+bajUMTkeXqvIIxP+CUS6NKnmN0WyIntd4q91gdGJeOR0Q2I/jeg==",
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.3.6.tgz",
+ "integrity": "sha512-CRdXwh91H6chd1PYptG070ukx+S6IkVaUeQVG91ySevoJoCYOuBT65qkMhRo49X2um1nGcs9UqolW90R57875g==",
"requires": {
"@ogcapi-js/features": "^1.1.1",
"@ogcapi-js/shared": "^1.1.1",
diff --git a/package.json b/package.json
index 2f161e592..7137590a0 100644
--- a/package.json
+++ b/package.json
@@ -200,13 +200,13 @@
"maplibre-gl": "^4.1.1",
"marked": "^12.0.2",
"monaco-editor": "^0.46.0",
- "mvt-to-geojson": "^0.0.2",
+ "mvt-to-geojson": "^0.0.5",
"name-suggestion-index": "^6.0.20240422",
"npm": "^10.7.0",
"opening_hours": "^3.6.0",
"osm-auth": "^2.5.0",
"osmtogeojson": "^3.0.0-beta.5",
- "panoramax-js": "^0.1.7",
+ "panoramax-js": "^0.3.6",
"panzoom": "^9.4.3",
"papaparse": "^5.3.1",
"pg": "^8.11.3",
diff --git a/src/Logic/BBox.ts b/src/Logic/BBox.ts
index b54928418..5f98cb726 100644
--- a/src/Logic/BBox.ts
+++ b/src/Logic/BBox.ts
@@ -249,6 +249,13 @@ export class BBox {
]
}
+ toLngLatFlat(): [number, number, number, number] {
+ return [
+ this.minLon, this.minLat,
+ this.maxLon, this.maxLat,
+ ]
+ }
+
public asGeojsonCached() {
if (this["geojsonCache"] === undefined) {
this["geojsonCache"] = this.asGeoJson({})
diff --git a/src/Logic/FeatureSource/Sources/MvtSource.ts b/src/Logic/FeatureSource/Sources/MvtSource.ts
index b65595016..6342fea90 100644
--- a/src/Logic/FeatureSource/Sources/MvtSource.ts
+++ b/src/Logic/FeatureSource/Sources/MvtSource.ts
@@ -63,7 +63,28 @@ export default class MvtSource implements FeatureSourceForTile, UpdatableFeature
return
}
const buffer = await result.arrayBuffer()
- const features = await MvtToGeojson.fromBuffer(buffer, this.x, this.y, this.z)
+ const features = MvtToGeojson.fromBuffer(buffer, this.x, this.y, this.z)
+ for (const feature of features) {
+ const properties = feature.properties
+ if(!properties["osm_type"]){
+ continue
+ }
+ let type: string = "node"
+ switch (properties["osm_type"]) {
+ case "N":
+ type = "node"
+ break
+ case "W":
+ type = "way"
+ break
+ case "R":
+ type = "relation"
+ break
+ }
+ properties["id"] = type + "/" + properties["osm_id"]
+ delete properties["osm_id"]
+ delete properties["osm_type"]
+ }
this._features.setData(features)
} catch (e) {
console.error("Could not download MVT " + this._url + " tile due to", e)
diff --git a/src/Logic/GeoOperations.ts b/src/Logic/GeoOperations.ts
index 7c3dabb6c..ab3795182 100644
--- a/src/Logic/GeoOperations.ts
+++ b/src/Logic/GeoOperations.ts
@@ -92,6 +92,13 @@ export class GeoOperations {
return turf.distance(lonlat0, lonlat1, { units: "meters" })
}
+ /**
+ * Starting on `from`, travels `distance` meters in the direction of the `bearing` (default: 90)
+ */
+ static destination(from: Coord | [number,number],distance: number, bearing: number = 90): [number,number]{
+ return <[number,number]> turf.destination(from, distance, bearing, {units: "meters"}).geometry.coordinates
+ }
+
static convexHull(featureCollection, options: { concavity?: number }) {
return turf.convex(featureCollection, options)
}
diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts
index 50878a94a..4588297c8 100644
--- a/src/Logic/ImageProviders/ImageProvider.ts
+++ b/src/Logic/ImageProviders/ImageProvider.ts
@@ -17,7 +17,8 @@ export interface ProvidedImage {
*/
rotation?: number
lat?: number,
- lon?: number
+ lon?: number,
+ host?: string
}
export default abstract class ImageProvider {
@@ -25,7 +26,7 @@ export default abstract class ImageProvider {
public abstract readonly name: string
- public abstract SourceIcon(id?: string, location?: { lon: number; lat: number }): BaseUIElement
+ public abstract SourceIcon(img?: {id: string, url: string, host?: string}, location?: { lon: number; lat: number }): BaseUIElement
/**
diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts
index 5a26e31c2..2cc370f32 100644
--- a/src/Logic/ImageProviders/Mapillary.ts
+++ b/src/Logic/ImageProviders/Mapillary.ts
@@ -118,13 +118,14 @@ export class Mapillary extends ImageProvider {
}
SourceIcon(
- id: string,
+ img: {id: string, url: string},
location?: {
lon: number
lat: number
}
): BaseUIElement {
let url: string = undefined
+ const id = img.id
if (id) {
url = Mapillary.createLink(location, 16, "" + id)
}
diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts
index 9536facb6..0fc4da425 100644
--- a/src/Logic/ImageProviders/Panoramax.ts
+++ b/src/Logic/ImageProviders/Panoramax.ts
@@ -7,6 +7,9 @@ import { LicenseInfo } from "./LicenseInfo"
import { GeoOperations } from "../GeoOperations"
import Constants from "../../Models/Constants"
import { Store, Stores, UIEventSource } from "../UIEventSource"
+import SvelteUIElement from "../../UI/Base/SvelteUIElement"
+import Panoramax_bw from "../../assets/svg/Panoramax_bw.svelte"
+import Link from "../../UI/Base/Link"
export default class PanoramaxImageProvider extends ImageProvider {
@@ -14,13 +17,18 @@ export default class PanoramaxImageProvider extends ImageProvider {
public static readonly singleton = new PanoramaxImageProvider()
private static readonly xyz = new PanoramaxXYZ()
private static defaultPanoramax = new AuthorizedPanoramax(Constants.panoramax.url, Constants.panoramax.token)
+
public defaultKeyPrefixes: string[] = ["panoramax"]
public readonly name: string = "panoramax"
private static knownMeta: Record = {}
- public SourceIcon(id?: string, location?: { lon: number; lat: number; }): BaseUIElement {
- return undefined
+ public SourceIcon(img?: { id: string, url: string, host?: string }, location?: { lon: number; lat: number; }): BaseUIElement {
+ const p = new Panoramax(img.host)
+ return new Link(new SvelteUIElement(Panoramax_bw), p.createViewLink({
+ imageId: img?.id,
+ location
+ }), true)
}
public addKnownMeta(meta: ImageData) {
@@ -35,7 +43,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
private async getInfoFromMapComplete(id: string): Promise<{ data: ImageData, url: string }> {
const sequence = "6e702976-580b-419c-8fb3-cf7bd364e6f8" // We always reuse this sequence
const url = `https://panoramax.mapcomplete.org/`
- const data = await PanoramaxImageProvider.defaultPanoramax.imageInfo(sequence, id)
+ const data = await PanoramaxImageProvider.defaultPanoramax.imageInfo(id, sequence)
return { url, data }
}
@@ -67,10 +75,14 @@ export default class PanoramaxImageProvider extends ImageProvider {
}
const [lon, lat] = GeoOperations.centerpointCoordinates(meta)
+ const hd = meta.properties
+ console.log(">>>",meta)
+ // const hdUrl = new URL(hd)
return {
id: meta.id,
url: makeAbsolute(meta.assets.sd.href),
url_hd: makeAbsolute(meta.assets.hd.href),
+ host: meta["links"].find(l => l.rel === "root")?.href,
lon, lat,
key: "panoramax",
provider: this,
@@ -103,8 +115,9 @@ export default class PanoramaxImageProvider extends ImageProvider {
}
return undefined
}
+
public async ExtractUrls(key: string, value: string): Promise {
- if(!Panoramax.isId(value)){
+ if (!Panoramax.isId(value)) {
return undefined
}
return [await this.getInfoFor(value).then(r => this.featureToImage(r))]
diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts
index 49853485d..bf9f571f4 100644
--- a/src/Logic/Web/NearbyImagesSearch.ts
+++ b/src/Logic/Web/NearbyImagesSearch.ts
@@ -10,6 +10,7 @@ import { Point } from "geojson"
import MvtSource from "../FeatureSource/Sources/MvtSource"
import AllImageProviders from "../ImageProviders/AllImageProviders"
import { Imgur } from "../ImageProviders/Imgur"
+import { Panoramax, PanoramaxXYZ } from "panoramax-js/dist"
interface ImageFetcher {
/**
@@ -102,7 +103,7 @@ class P4CImageFetcher implements ImageFetcher {
{
mindate: new Date().getTime() - maxAgeSeconds,
towardscenter: false,
- }
+ },
)
} catch (e) {
console.log("P4C image fetcher failed with", e)
@@ -163,6 +164,55 @@ class ImagesInLoadedDataFetcher implements ImageFetcher {
}
}
+class ImagesFromPanoramaxFetcher implements ImageFetcher {
+ private readonly _radius: number
+ private readonly _panoramax: Panoramax
+ name: string = "panoramax"
+
+ constructor(url?: string, radius: number = 100) {
+ this._radius = radius
+ if (url) {
+
+ this._panoramax = new Panoramax(url)
+ } else {
+ this._panoramax = new PanoramaxXYZ()
+ }
+ }
+
+
+ public async fetchImages(lat: number, lon: number): Promise {
+
+ const bboxObj = new BBox([
+ GeoOperations.destination([lon, lat], this._radius * Math.sqrt(2), -45),
+ GeoOperations.destination([lon, lat], this._radius * Math.sqrt(2), 135),
+ ])
+ const bbox: [number, number, number, number] = bboxObj.toLngLatFlat()
+ const images = await this._panoramax.search({ bbox, limit: 1000 })
+
+ return images.map(i => {
+ const [lng, lat] = i.geometry.coordinates
+ return ({
+ pictureUrl: i.assets.sd.href,
+ coordinates: { lng, lat },
+
+ provider: "panoramax",
+ direction: i.properties["view:azimuth"],
+ osmTags: {
+ "panoramax": i.id,
+ },
+ thumbUrl: i.assets.thumb.href,
+ date: new Date(i.properties.datetime).getTime(),
+ license: i.properties["geovisio:license"],
+ author: i.providers.at(-1).name,
+ detailsUrl: i.id,
+ details: {
+ isSpherical: i.properties["exif"]["Xmp.GPano.ProjectionType"] === "equirectangular",
+ },
+ })
+ })
+ }
+}
+
class ImagesFromCacheServerFetcher implements ImageFetcher {
private readonly _searchRadius: number
public readonly name = "fromCacheServer"
@@ -186,7 +236,7 @@ class ImagesFromCacheServerFetcher implements ImageFetcher {
async fetchImagesForType(
targetlat: number,
targetlon: number,
- type: "lines" | "pois" | "polygons"
+ type: "lines" | "pois" | "polygons",
): Promise {
const { x, y, z } = Tiles.embedded_tile(targetlat, targetlon, 14)
@@ -203,7 +253,7 @@ class ImagesFromCacheServerFetcher implements ImageFetcher {
}),
x,
y,
- z
+ z,
)
await src.updateAsync()
return src.features.data
@@ -360,6 +410,8 @@ export class CombinedFetcher {
this.sources = [
new ImagesInLoadedDataFetcher(indexedFeatures, radius),
new ImagesFromCacheServerFetcher(radius),
+ new ImagesFromPanoramaxFetcher(),
+ new ImagesFromPanoramaxFetcher(Constants.panoramax.url),
new MapillaryFetcher({
panoramas: "no",
max_images: 25,
@@ -375,7 +427,7 @@ export class CombinedFetcher {
lat: number,
lon: number,
state: UIEventSource>,
- sink: UIEventSource
+ sink: UIEventSource,
): Promise {
try {
const pics = await source.fetchImages(lat, lon)
@@ -408,7 +460,7 @@ export class CombinedFetcher {
public getImagesAround(
lon: number,
- lat: number
+ lat: number,
): {
images: Store
state: Store>
diff --git a/src/UI/BigComponents/MenuDrawer.svelte b/src/UI/BigComponents/MenuDrawer.svelte
index 989e19ebe..8e95253db 100644
--- a/src/UI/BigComponents/MenuDrawer.svelte
+++ b/src/UI/BigComponents/MenuDrawer.svelte
@@ -48,6 +48,7 @@
import Copyright from "../../assets/svg/Copyright.svelte"
import Pencil from "../../assets/svg/Pencil.svelte"
import Squares2x2 from "@babeard/svelte-heroicons/mini/Squares2x2"
+ import PanoramaxLink from "./PanoramaxLink.svelte"
export let state: ThemeViewState
let userdetails = state.osmConnection.userDetails
@@ -229,6 +230,7 @@
+
diff --git a/src/UI/BigComponents/PanoramaxLink.svelte b/src/UI/BigComponents/PanoramaxLink.svelte
new file mode 100644
index 000000000..058674c06
--- /dev/null
+++ b/src/UI/BigComponents/PanoramaxLink.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+ {#if large}
+
+
+
+
+ {:else}
+
+ {/if}
+
diff --git a/src/UI/Image/ImageAttribution.svelte b/src/UI/Image/ImageAttribution.svelte
index 9594c87d8..46bb525fc 100644
--- a/src/UI/Image/ImageAttribution.svelte
+++ b/src/UI/Image/ImageAttribution.svelte
@@ -16,7 +16,7 @@
let license: Store = UIEventSource.FromPromise(
image.provider?.DownloadAttribution(image)
)
- let icon = image.provider?.SourceIcon(image.id)
+ let icon = image.provider?.SourceIcon(image)
{#if $license !== undefined}
diff --git a/src/UI/Image/NearbyImages.svelte b/src/UI/Image/NearbyImages.svelte
index d70dc2ba5..ca9f3ceb8 100644
--- a/src/UI/Image/NearbyImages.svelte
+++ b/src/UI/Image/NearbyImages.svelte
@@ -130,7 +130,7 @@
for (const f of features) {
bbox = bbox.unionWith(BBox.get(f))
}
- mapProperties.maxbounds.set(bbox.pad(1.1))
+ mapProperties.maxbounds.set(bbox.pad(4))
})
)
diff --git a/src/Utils.ts b/src/Utils.ts
index b2b7f879f..421c6c5cf 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1488,7 +1488,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (!element) {
return
}
- console.log("Scrolling into view:", element)
// Is the element completely in the view?
const parentRect = Utils.findParentWithScrolling(element)?.getBoundingClientRect()
if (!parentRect) {
diff --git a/src/assets/svg/Circle.svelte b/src/assets/svg/Circle.svelte
index f1926471a..510ddce25 100644
--- a/src/assets/svg/Circle.svelte
+++ b/src/assets/svg/Circle.svelte
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/assets/svg/Panoramax.svelte b/src/assets/svg/Panoramax.svelte
new file mode 100644
index 000000000..6a20baf15
--- /dev/null
+++ b/src/assets/svg/Panoramax.svelte
@@ -0,0 +1,4 @@
+
+ image/svg+xml
\ No newline at end of file
diff --git a/src/assets/svg/Panoramax_bw.svelte b/src/assets/svg/Panoramax_bw.svelte
new file mode 100644
index 000000000..71900d7f0
--- /dev/null
+++ b/src/assets/svg/Panoramax_bw.svelte
@@ -0,0 +1,4 @@
+
+ image/svg+xml
\ No newline at end of file