UI: don't allow cylindrical images for now, see #2424

This commit is contained in:
Pieter Vander Vennet 2025-06-02 16:08:55 +02:00
parent 10e0262a0d
commit b9293dc2c9
5 changed files with 80 additions and 75 deletions

View file

@ -13,6 +13,7 @@ import ImageUploadQueue, { ImageUploadArguments } from "./ImageUploadQueue"
import { GeoOperations } from "../GeoOperations"
import NoteCommentElement from "../../UI/Popup/Notes/NoteCommentElement"
import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
import ExifReader from "exifreader"
/**
* The ImageUploadManager has a
@ -81,7 +82,7 @@ export class ImageUploadManager {
this._reportError = reportError
}
public canBeUploaded(file: File): true | { error: Translation } {
public async canBeUploaded(file: File): Promise<true | { error: Translation }> {
const sizeInBytes = file.size
if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
const error = Translations.t.image.toBig.Subs({
@ -94,12 +95,19 @@ export class ImageUploadManager {
if (ext !== "jpg" && ext !== "jpeg") {
return { error: new Translation({ en: "Only JPG-files are allowed" }) }
}
const tags = await ExifReader.load(file)
if (tags.ProjectionType.value === "cylindrical") {
return { error: new Translation({ en: "Cylindrical images (typically created by a Panorama-app) are not supported" }) }
}
return true
}
/**
* Uploads the given image, applies the correct title and license for the known user.
* Will then add this image to the OSM-feature or the OSM-note automatically, based on the ID of the feature.
* Does _not_ check 'canBeUploaded'
* Note: the image will actually be added to the queue. If the image-upload fails, this will be attempted when visiting MC again
* @param file a jpg file to upload
* @param tagsStore The tags of the feature
@ -117,10 +125,6 @@ export class ImageUploadManager {
ignoreGPS: boolean | false
}
): void {
const canBeUploaded = this.canBeUploaded(file)
if (canBeUploaded !== true) {
throw canBeUploaded.error
}
const tags: OsmTags = tagsStore.data
const featureId = <OsmId | NoteId>tags.id
@ -286,7 +290,7 @@ export class ImageUploadManager {
let absoluteUrl: string
try {
;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob,
location,
author,

View file

@ -51,7 +51,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
new SvelteUIElement(Panoramax_bw),
p.createViewLink({
imageId: img?.id,
location,
location
}),
true
)
@ -65,14 +65,14 @@ export default class PanoramaxImageProvider extends ImageProvider {
const p = new Panoramax(host)
return p.createViewLink({
imageId: img?.id,
location,
location
})
}
public addKnownMeta(meta: ImageData, url?: string) {
PanoramaxImageProvider.knownMeta[meta.id] = {
data: Promise.resolve({ data: meta, url }),
time: new Date(),
time: new Date()
}
}
@ -125,7 +125,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
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),
date: new Date(meta.properties.datetime)
}
}
@ -156,7 +156,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
const promise: Promise<{ data: ImageData; url: string }> = this.getInfoForUncached(id)
PanoramaxImageProvider.knownMeta[id] = {
time: new Date(),
data: promise,
data: promise
}
return await promise
}
@ -215,7 +215,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
return {
artist: meta.data.providers.at(-1).name, // We take the last provider, as that one probably contain the username of the uploader
date: new Date(meta.data.properties["datetime"]),
licenseShortName: meta.data.properties["geovisio:license"],
licenseShortName: meta.data.properties["geovisio:license"]
}
}
@ -247,8 +247,8 @@ export default class PanoramaxImageProvider extends ImageProvider {
properties: {
url,
northOffset,
pitchOffset,
},
pitchOffset
}
}
}
}
@ -263,6 +263,7 @@ export class PanoramaxUploader implements ImageUploader {
this.panoramax = new AuthorizedPanoramax(url, token)
}
async uploadImage(
blob: File,
currentGps: [number, number],
@ -282,53 +283,63 @@ export class PanoramaxUploader implements ImageUploader {
datetime ??= new Date().toISOString()
try {
const tags = await ExifReader.load(blob)
const [[latD], [latM], [latS, latSDenom]] = <
[[number, number], [number, number], [number, number]]
>tags?.GPSLatitude?.value
const [[lonD], [lonM], [lonS, lonSDenom]] = <
[[number, number], [number, number], [number, number]]
>tags?.GPSLongitude?.value
if (tags.ProjectionType.value === "cylindrical") {
throw "Unsupported image format: cylindrical images (panorama images) are currently not supported"
}
if (tags?.GPSLatitude?.value && tags?.GPSLongitude?.value) {
const exifLat = latD + latM / 60 + latS / (3600 * latSDenom)
const exifLon = lonD + lonM / 60 + lonS / (3600 * lonSDenom)
if (
typeof exifLat === "number" &&
!isNaN(exifLat) &&
typeof exifLon === "number" &&
!isNaN(exifLon) &&
!(exifLat === 0 && exifLon === 0)
) {
lat = exifLat
lon = exifLon
if (tags?.GPSLatitudeRef?.value?.[0] === "S") {
lat *= -1
}
if (tags?.GPSLongitudeRef?.value?.[0] === "W") {
lon *= -1
const [[latD], [latM], [latS, latSDenom]] = <
[[number, number], [number, number], [number, number]]
>tags?.GPSLatitude?.value
const [[lonD], [lonM], [lonS, lonSDenom]] = <
[[number, number], [number, number], [number, number]]
>tags?.GPSLongitude?.value
const exifLat = latD + latM / 60 + latS / (3600 * latSDenom)
const exifLon = lonD + lonM / 60 + lonS / (3600 * lonSDenom)
if (
typeof exifLat === "number" &&
!isNaN(exifLat) &&
typeof exifLon === "number" &&
!isNaN(exifLon) &&
!(exifLat === 0 && exifLon === 0)
) {
lat = exifLat
lon = exifLon
if (tags?.GPSLatitudeRef?.value?.[0] === "S") {
lat *= -1
}
if (tags?.GPSLongitudeRef?.value?.[0] === "W") {
lon *= -1
}
}
}
const [date, time] = (
const dateTime = (
tags.DateTime.value[0] ??
tags.DateTimeOriginal.value[0] ??
tags.GPSDateStamp ??
tags.CreateDate ??
tags["Date Created"]
).split(" ")
const exifDatetime = new Date(date.replaceAll(":", "-") + "T" + time)
if (exifDatetime.getFullYear() === 1970) {
// The data probably got reset to the epoch
// we don't use the value
console.log(
"Datetime from picture is probably invalid:",
exifDatetime,
"using 'now' instead"
)
} else {
datetime = exifDatetime.toISOString()
)?.split(" ")
if (dateTime) {
const [date, time] = dateTime
const exifDatetime = new Date(date.replaceAll(":", "-") + "T" + time)
if (exifDatetime.getFullYear() === 1970) {
// The data probably got reset to the epoch
// we don't use the value
console.log(
"Datetime from picture is probably invalid:",
exifDatetime,
"using 'now' instead"
)
} else {
datetime = exifDatetime.toISOString()
}
}
console.log("Tags are", tags)
} catch (e) {
console.warn("Could not read EXIF-tags")
console.warn("Could not read EXIF-tags due to", e)
}
const p = this.panoramax
@ -345,7 +356,7 @@ export class PanoramaxUploader implements ImageUploader {
indexInSequence: sequence["stats:items"].count + 1, // stats:items is '1'-indexed, so .count is also the last index
exifOverride: {
Artist: author,
},
}
}
if (progress) {
options.onProgress = (e: ProgressEvent) => {
@ -362,7 +373,7 @@ export class PanoramaxUploader implements ImageUploader {
return {
key: "panoramax",
value: img.id,
absoluteUrl: img.assets.hd.href,
absoluteUrl: img.assets.hd.href
}
}
}