forked from MapComplete/MapComplete
		
	UI: don't allow cylindrical images for now, see #2424
This commit is contained in:
		
							parent
							
								
									10e0262a0d
								
							
						
					
					
						commit
						b9293dc2c9
					
				
					 5 changed files with 80 additions and 75 deletions
				
			
		|  | @ -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, | ||||
|  |  | |||
|  | @ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue