forked from MapComplete/MapComplete
chore: automated housekeeping...
This commit is contained in:
parent
79b6927b56
commit
42ded4c1b1
328 changed files with 4062 additions and 1284 deletions
|
|
@ -132,7 +132,7 @@ export default class AllImageProviders {
|
|||
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
||||
allSources.push(singleSource)
|
||||
}
|
||||
const source = Stores.fromStoresArray(allSources).map(result => {
|
||||
const source = Stores.fromStoresArray(allSources).map((result) => {
|
||||
const all = [].concat(...result)
|
||||
return Utils.DedupOnId(all, (i) => i?.id ?? i?.url)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ export interface ProvidedImage {
|
|||
}
|
||||
|
||||
export interface PanoramaView {
|
||||
url: string,
|
||||
url: string
|
||||
/**
|
||||
* 0 - 359
|
||||
* Degrees in which the picture is taken, with north = 0; going clockwise
|
||||
*/
|
||||
northOffset?: number,
|
||||
northOffset?: number
|
||||
pitchOffset?: number
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,6 @@ export interface HotspotProperties {
|
|||
pitch: number | "auto"
|
||||
|
||||
gotoPanorama: Feature<Point, PanoramaView>
|
||||
|
||||
}
|
||||
|
||||
export default abstract class ImageProvider {
|
||||
|
|
@ -125,7 +124,9 @@ export default abstract class ImageProvider {
|
|||
|
||||
public abstract apiUrls(): string[]
|
||||
|
||||
public abstract getPanoramaInfo(image: { id: string }): Promise<Feature<Point, PanoramaView>> | undefined;
|
||||
public abstract getPanoramaInfo(image: {
|
||||
id: string
|
||||
}): Promise<Feature<Point, PanoramaView>> | undefined
|
||||
|
||||
public static async offerImageAsDownload(image: ProvidedImage) {
|
||||
const response = await fetch(image.url_hd ?? image.url)
|
||||
|
|
@ -134,5 +135,4 @@ export default abstract class ImageProvider {
|
|||
mimetype: "image/jpg",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,12 +36,16 @@ export class ImageUploadManager {
|
|||
* Keeps track of the _features_ for which an upload failed. Only used to give an indication to the user.
|
||||
* Every time an image upload fails, the featureID is added to the list. Not persisted (and should not be)
|
||||
*/
|
||||
private readonly _fails: UIEventSource<ImageUploadArguments[]> = new UIEventSource<ImageUploadArguments[]>([])
|
||||
public readonly fails: Store<string[]> = this._fails.map(args => args.map(a => a.featureId))
|
||||
private readonly _fails: UIEventSource<ImageUploadArguments[]> = new UIEventSource<
|
||||
ImageUploadArguments[]
|
||||
>([])
|
||||
public readonly fails: Store<string[]> = this._fails.map((args) => args.map((a) => a.featureId))
|
||||
/**
|
||||
* FeatureIDs of queued items
|
||||
*/
|
||||
public readonly queued: Store<string[]> = this._queue.imagesInQueue.map(queue => queue.map(q => q.featureId))
|
||||
public readonly queued: Store<string[]> = this._queue.imagesInQueue.map((queue) =>
|
||||
queue.map((q) => q.featureId)
|
||||
)
|
||||
public readonly queuedArgs = this._queue.imagesInQueue
|
||||
/**
|
||||
* The feature for which an upload is currently running
|
||||
|
|
@ -79,7 +83,7 @@ export class ImageUploadManager {
|
|||
if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
|
||||
const error = Translations.t.image.toBig.Subs({
|
||||
actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
|
||||
max_size: this._uploader.maxFileSizeInMegabytes + "MB"
|
||||
max_size: this._uploader.maxFileSizeInMegabytes + "MB",
|
||||
})
|
||||
return { error }
|
||||
}
|
||||
|
|
@ -118,7 +122,6 @@ export class ImageUploadManager {
|
|||
const tags: OsmTags = tagsStore.data
|
||||
const featureId = <OsmId | NoteId>tags.id
|
||||
|
||||
|
||||
const author = this._osmConnection?.userDetails?.data?.name ?? "Anonymous" // Might be a note upload
|
||||
|
||||
/**
|
||||
|
|
@ -134,13 +137,16 @@ export class ImageUploadManager {
|
|||
location,
|
||||
date: new Date().getTime(),
|
||||
layoutId: this._theme.id,
|
||||
author, blob: file, featureId, noblur, targetKey
|
||||
author,
|
||||
blob: file,
|
||||
featureId,
|
||||
noblur,
|
||||
targetKey,
|
||||
}
|
||||
console.log("Args are", args)
|
||||
|
||||
this._queue.add(args)
|
||||
this.uploadQueue()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -201,23 +207,29 @@ export class ImageUploadManager {
|
|||
this._fails.ping()
|
||||
return
|
||||
}
|
||||
this._fails.set(this._fails.data.filter(a => a !== args))
|
||||
let properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(args.featureId)
|
||||
this._fails.set(this._fails.data.filter((a) => a !== args))
|
||||
let properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(
|
||||
args.featureId
|
||||
)
|
||||
|
||||
if (args.featureId.startsWith("note/")) {
|
||||
// This is an OSM-note
|
||||
const url = result.absoluteUrl
|
||||
await this._osmConnection.addCommentToNote(args.featureId, url)
|
||||
const properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(args.featureId)
|
||||
const properties: UIEventSource<Record<string, string>> =
|
||||
this._featureProperties.getStore(args.featureId)
|
||||
if (properties) {
|
||||
// Properties will not be defined if the note isn't loaded, but that is no problem as the below code is only relevant if the note is shown
|
||||
NoteCommentElement.addCommentTo(url, properties, {
|
||||
osmConnection: this._osmConnection
|
||||
osmConnection: this._osmConnection,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (properties === undefined) {
|
||||
const downloaded = await new OsmObjectDownloader(this._osmConnection.Backend(), this._changes).DownloadObjectAsync(args.featureId)
|
||||
const downloaded = await new OsmObjectDownloader(
|
||||
this._osmConnection.Backend(),
|
||||
this._changes
|
||||
).DownloadObjectAsync(args.featureId)
|
||||
if (downloaded === "deleted") {
|
||||
this._queue.delete(args)
|
||||
return
|
||||
|
|
@ -232,7 +244,7 @@ export class ImageUploadManager {
|
|||
properties,
|
||||
{
|
||||
theme: properties?.data?.["_orig_theme"] ?? this._theme.id,
|
||||
changeType: "add-image"
|
||||
changeType: "add-image",
|
||||
}
|
||||
)
|
||||
await this._changes.applyAction(action)
|
||||
|
|
@ -240,7 +252,6 @@ export class ImageUploadManager {
|
|||
}
|
||||
|
||||
this._queue.delete(args)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,23 +270,15 @@ export class ImageUploadManager {
|
|||
* @private
|
||||
*/
|
||||
private async attemptSingleUpload(
|
||||
{
|
||||
featureId,
|
||||
author,
|
||||
blob,
|
||||
targetKey,
|
||||
noblur,
|
||||
location
|
||||
}: ImageUploadArguments,
|
||||
{ featureId, author, blob, targetKey, noblur, location }: ImageUploadArguments,
|
||||
reportOnFail: boolean
|
||||
): Promise<UploadResult | undefined> {
|
||||
|
||||
let key: string
|
||||
let value: string
|
||||
let absoluteUrl: string
|
||||
|
||||
try {
|
||||
({ key, value, absoluteUrl } = await this._uploader.uploadImage(
|
||||
;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
|
||||
blob,
|
||||
location,
|
||||
author,
|
||||
|
|
@ -284,14 +287,13 @@ export class ImageUploadManager {
|
|||
} catch (e) {
|
||||
console.error("Could again not upload image due to", e)
|
||||
if (reportOnFail) {
|
||||
|
||||
await this._reportError(
|
||||
e,
|
||||
JSON.stringify({
|
||||
ctx: "While uploading an image in the Image Upload Manager",
|
||||
featureId,
|
||||
author,
|
||||
targetKey
|
||||
targetKey,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
@ -304,5 +306,4 @@ export class ImageUploadManager {
|
|||
}
|
||||
return { key, absoluteUrl, value }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { IdbLocalStorage } from "../Web/IdbLocalStorage"
|
|||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
|
||||
export interface ImageUploadArguments {
|
||||
featureId: string,
|
||||
readonly author: string,
|
||||
readonly blob: File,
|
||||
readonly targetKey: string | undefined,
|
||||
readonly noblur: boolean,
|
||||
readonly location: [number, number],
|
||||
featureId: string
|
||||
readonly author: string
|
||||
readonly blob: File
|
||||
readonly targetKey: string | undefined
|
||||
readonly noblur: boolean
|
||||
readonly location: [number, number]
|
||||
readonly layoutId: string
|
||||
readonly date: number
|
||||
}
|
||||
|
|
@ -17,14 +17,15 @@ export interface ImageUploadArguments {
|
|||
* It is backed up in the indexedDB as to not drop images in case of connection problems
|
||||
*/
|
||||
export default class ImageUploadQueue {
|
||||
|
||||
public static readonly singleton = new ImageUploadQueue()
|
||||
private readonly _imagesInQueue: UIEventSource<ImageUploadArguments[]>
|
||||
|
||||
public readonly imagesInQueue: Store<ImageUploadArguments[]>
|
||||
|
||||
private constructor() {
|
||||
this._imagesInQueue = IdbLocalStorage.Get<ImageUploadArguments[]>("failed-images-backup", { defaultValue: [] })
|
||||
this._imagesInQueue = IdbLocalStorage.Get<ImageUploadArguments[]>("failed-images-backup", {
|
||||
defaultValue: [],
|
||||
})
|
||||
this.imagesInQueue = this._imagesInQueue
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +45,6 @@ export default class ImageUploadQueue {
|
|||
}
|
||||
|
||||
applyRemapping(oldId: string, newId: string) {
|
||||
|
||||
let hasChange = false
|
||||
for (const img of this._imagesInQueue.data) {
|
||||
if (img.featureId === oldId) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export class Imgur extends ImageProvider {
|
|||
key: key,
|
||||
provider: this,
|
||||
id: value,
|
||||
isSpherical: false
|
||||
isSpherical: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export class Mapillary extends ImageProvider {
|
|||
"http://mapillary.com",
|
||||
"https://mapillary.com",
|
||||
"http://www.mapillary.com",
|
||||
"https://www.mapillary.com"
|
||||
"https://www.mapillary.com",
|
||||
]
|
||||
defaultKeyPrefixes = ["mapillary", "image"]
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export class Mapillary extends ImageProvider {
|
|||
lat: location?.lat,
|
||||
lng: location?.lon,
|
||||
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||
pKey
|
||||
pKey,
|
||||
}
|
||||
const baselink = `https://www.mapillary.com/app/?`
|
||||
const paramsStr = Utils.NoNull(
|
||||
|
|
@ -140,41 +140,39 @@ export class Mapillary extends ImageProvider {
|
|||
return [img]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download data necessary for the 360°-viewer
|
||||
* @param pkey
|
||||
* @constructor
|
||||
*/
|
||||
public async getPanoramaInfo(image: { id: number | string }): Promise<Feature<Point, PanoramaView>> {
|
||||
public async getPanoramaInfo(image: {
|
||||
id: number | string
|
||||
}): Promise<Feature<Point, PanoramaView>> {
|
||||
const pkey = image.id
|
||||
const metadataUrl =
|
||||
"https://graph.mapillary.com/" +
|
||||
pkey +
|
||||
"?fields=computed_compass_angle,geometry,is_pano,thumb_2048_url,thumb_original_url&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<
|
||||
{
|
||||
computed_compass_angle: number,
|
||||
geometry: Point,
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
computed_compass_angle: number
|
||||
geometry: Point
|
||||
|
||||
is_pano: boolean,
|
||||
thumb_2048_url: string,
|
||||
thumb_original_url: string,
|
||||
id: string,
|
||||
|
||||
}>(metadataUrl, 60 * 60)
|
||||
is_pano: boolean
|
||||
thumb_2048_url: string
|
||||
thumb_original_url: string
|
||||
id: string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: response.geometry,
|
||||
properties: {
|
||||
url: response.thumb_2048_url,
|
||||
northOffset: response.computed_compass_angle
|
||||
}
|
||||
northOffset: response.computed_compass_angle,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
|
||||
const mapillaryId = providedImage.id
|
||||
const metadataUrl =
|
||||
|
|
@ -183,7 +181,10 @@ export class Mapillary extends ImageProvider {
|
|||
"?fields=thumb_1024_url,thumb_original_url,captured_at,creator&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
thumb_1024_url: string, thumb_original_url: string, captured_at, creator: string
|
||||
thumb_1024_url: string
|
||||
thumb_original_url: string
|
||||
captured_at
|
||||
creator: string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
|
||||
const license = new LicenseInfo()
|
||||
|
|
@ -207,13 +208,13 @@ export class Mapillary extends ImageProvider {
|
|||
"?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,computed_geometry,creator,camera_type&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
thumb_1024_url: string,
|
||||
thumb_original_url: string,
|
||||
captured_at,
|
||||
compass_angle: number,
|
||||
creator: string,
|
||||
computed_geometry: Point,
|
||||
geometry: Point,
|
||||
thumb_1024_url: string
|
||||
thumb_original_url: string
|
||||
captured_at
|
||||
compass_angle: number
|
||||
creator: string
|
||||
computed_geometry: Point
|
||||
geometry: Point
|
||||
camera_type: "equirectangular" | "spherical" | string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
const url = <string>response["thumb_1024_url"]
|
||||
|
|
@ -230,9 +231,10 @@ export class Mapillary extends ImageProvider {
|
|||
date,
|
||||
key,
|
||||
rotation,
|
||||
isSpherical: response.camera_type === "spherical" || response.camera_type === "equirectangular",
|
||||
isSpherical:
|
||||
response.camera_type === "spherical" || response.camera_type === "equirectangular",
|
||||
lat: geometry.coordinates[1],
|
||||
lon: geometry.coordinates[0]
|
||||
lon: geometry.coordinates[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,9 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
|||
return new Panoramax(host)
|
||||
}
|
||||
|
||||
public async getPanoramaInfo(image: { id: string }): Promise<Feature<Point, PanoramaView>> | undefined {
|
||||
public async getPanoramaInfo(image: {
|
||||
id: string
|
||||
}): Promise<Feature<Point, PanoramaView>> | undefined {
|
||||
const imageInfo = await PanoramaxImageProvider.xyz.imageInfo(image.id)
|
||||
const url = (imageInfo.assets.sd ?? imageInfo.assets.thumb ?? imageInfo.assets.hd).href
|
||||
const northOffset = imageInfo.properties["view:azimuth"]
|
||||
|
|
@ -200,8 +202,10 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
|||
type: "Feature",
|
||||
geometry: imageInfo.geometry,
|
||||
properties: {
|
||||
url, northOffset, pitchOffset
|
||||
}
|
||||
url,
|
||||
northOffset,
|
||||
pitchOffset,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ import { Utils } from "../../Utils"
|
|||
import { Feature, Point } from "geojson"
|
||||
|
||||
export class WikidataImageProvider extends ImageProvider {
|
||||
|
||||
|
||||
public static readonly singleton = new WikidataImageProvider()
|
||||
public readonly defaultKeyPrefixes = ["wikidata"]
|
||||
public readonly name = "Wikidata"
|
||||
private static readonly keyBlacklist: ReadonlySet<string> = new Set([
|
||||
"mapillary",
|
||||
...Utils.Times((i) => "mapillary:" + i, 10)
|
||||
...Utils.Times((i) => "mapillary:" + i, 10),
|
||||
])
|
||||
|
||||
private constructor() {
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
key: undefined,
|
||||
provider: this,
|
||||
id: image,
|
||||
isSpherical: false
|
||||
isSpherical: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue