chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2024-07-21 10:52:51 +02:00
parent 14b2799f08
commit 4add2d1aff
151 changed files with 4561 additions and 3315 deletions

View file

@ -521,12 +521,14 @@ export class ExtraFunctions {
public static HelpText(): string {
const elems: string[] = []
for (const func of ExtraFunctions.allFuncs) {
elems.push("### "+func._name, func._doc, MarkdownUtils.list(func._args))
elems.push("### " + func._name, func._doc, MarkdownUtils.list(func._args))
}
return [
ExtraFunctions.intro,
MarkdownUtils.list(ExtraFunctions.allFuncs.map((func) => `[${func._name}](#${func._name})`)),
MarkdownUtils.list(
ExtraFunctions.allFuncs.map((func) => `[${func._name}](#${func._name})`)
),
...elems,
].join("\n")
}

View file

@ -57,9 +57,7 @@ export class LastClickFeatureSource implements FeatureSource {
this._features = new UIEventSource<Feature[]>([])
this.features = this._features
clickSource.addCallbackAndRunD(({ lon, lat, mode }) => {
this._features.setData([
this.createFeature(lon, lat, mode)
])
this._features.setData([this.createFeature(lon, lat, mode)])
})
}

View file

@ -68,7 +68,10 @@ export default abstract class ImageProvider {
public abstract ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]>
public abstract DownloadAttribution(providedImage: {url: string, id: string}): Promise<LicenseInfo>
public abstract DownloadAttribution(providedImage: {
url: string
id: string
}): Promise<LicenseInfo>
public abstract apiUrls(): string[]
}

View file

@ -129,8 +129,6 @@ export class ImageUploadManager {
tags?.data?.["_orig_theme"]
)
if (!action) {
return
}

View file

@ -97,7 +97,9 @@ export class Imgur extends ImageProvider implements ImageUploader {
const hash = url.substr("https://i.imgur.com/".length).split(/\.jpe?g/i)[0]
const apiUrl = "https://api.imgur.com/3/image/" + hash
const response = await Utils.downloadJsonCached<{data: {description: string, datetime: string, views: number}}>(apiUrl, 365 * 24 * 60 * 60, {
const response = await Utils.downloadJsonCached<{
data: { description: string; datetime: string; views: number }
}>(apiUrl, 365 * 24 * 60 * 60, {
Authorization: "Client-ID " + Constants.ImgurApiKey,
})

View file

@ -133,7 +133,7 @@ export class Mapillary extends ImageProvider {
return [this.PrepareUrlAsync(key, value)]
}
public async DownloadAttribution(providedImage: {id: string}): Promise<LicenseInfo> {
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
const mapillaryId = providedImage.id
const metadataUrl =
"https://graph.mapillary.com/" +

View file

@ -82,8 +82,8 @@ export class WikimediaImageProvider extends ImageProvider {
public PrepUrl(value: undefined): undefined
public PrepUrl(value: string): ProvidedImage
public PrepUrl(value: string | undefined): ProvidedImage | undefined{
if(value === undefined){
public PrepUrl(value: string | undefined): ProvidedImage | undefined {
if (value === undefined) {
return undefined
}
value = WikimediaImageProvider.removeCommonsPrefix(value)
@ -120,7 +120,7 @@ export class WikimediaImageProvider extends ImageProvider {
return [Promise.resolve(this.UrlForImage("File:" + value))]
}
public async DownloadAttribution(img: {url: string}): Promise<LicenseInfo> {
public async DownloadAttribution(img: { url: string }): Promise<LicenseInfo> {
const filename = WikimediaImageProvider.ExtractFileName(img.url)
if (filename === "") {

View file

@ -203,6 +203,6 @@ export class ChangeDescriptionTools {
changes: ChangeDescription[],
mappings: Map<string, string>
): ChangeDescription[] {
return changes.map(c =>ChangeDescriptionTools.rewriteIds(c, mappings))
return changes.map((c) => ChangeDescriptionTools.rewriteIds(c, mappings))
}
}

View file

@ -55,6 +55,4 @@ export default class LinkImageAction extends OsmChangeAction {
this._currentTags.ping()
return tagChangeAction.CreateChangeDescriptions()
}
}

View file

@ -133,50 +133,50 @@ export class Changes {
[
{
key: "comment",
docs: "The changeset comment. Will be a fixed string, mentioning the theme"
docs: "The changeset comment. Will be a fixed string, mentioning the theme",
},
{
key: "theme",
docs: "The name of the theme that was used to create this change. "
docs: "The name of the theme that was used to create this change. ",
},
{
key: "source",
value: "survey",
docs: "The contributor had their geolocation enabled while making changes"
docs: "The contributor had their geolocation enabled while making changes",
},
{
key: "change_within_{distance}",
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping"
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping",
},
{
key: "change_over_{distance}",
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping"
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping",
},
{
key: "created_by",
value: "MapComplete <version>",
docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number"
docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number",
},
{
key: "locale",
value: "en|nl|de|...",
docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks."
docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks.",
},
{
key: "host",
value: "https://mapcomplete.org/<theme>",
docs: "The URL that the contributor used to make changes. One can see the used instance with this"
docs: "The URL that the contributor used to make changes. One can see the used instance with this",
},
{
key: "imagery",
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)"
}
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)",
},
],
"default"
),
...addSource(ChangeTagAction.metatags, "ChangeTag"),
...addSource(ChangeLocationAction.metatags, "ChangeLocation"),
...addSource(DeleteAction.metatags, "DeleteAction")
...addSource(DeleteAction.metatags, "DeleteAction"),
// TODO
/*
...DeleteAction.metatags,
@ -198,11 +198,11 @@ export class Changes {
docs,
specialMotivation
? "This might give a reason per modified node or way"
: ""
: "",
].join("\n"),
source
source,
])
)
),
].join("\n\n")
}
@ -248,12 +248,12 @@ export class Changes {
public async applyAction(action: OsmChangeAction): Promise<void> {
const changeDescriptions = await action.Perform(this)
const remapped = ChangeDescriptionTools.rewriteAllIds(changeDescriptions, this._changesetHandler._remappings)
remapped[0].meta.distanceToObject = this.calculateDistanceToChanges(
action,
remapped
const remapped = ChangeDescriptionTools.rewriteAllIds(
changeDescriptions,
this._changesetHandler._remappings
)
remapped[0].meta.distanceToObject = this.calculateDistanceToChanges(action, remapped)
this.applyChanges(remapped)
}
@ -420,12 +420,11 @@ export class Changes {
}
}
// ----------------- SORT OBJECTS -------------------
const result = {
newObjects: [],
modifiedObjects: [],
deletedObjects: []
deletedObjects: [],
}
objects.forEach((v, id) => {
@ -534,12 +533,12 @@ export class Changes {
private async getOsmObject(id: string, downloader: OsmObjectDownloader) {
try {
try {
// Important: we do **not** cache this request, we _always_ need a fresh version!
const osmObj = await downloader.DownloadObjectAsync(id, 0)
return { id, osmObj }
} catch (e) {
const msg = "Could not download OSM-object " +
const msg =
"Could not download OSM-object " +
id +
" trying again before dropping it from the changes (" +
e +
@ -549,11 +548,8 @@ export class Changes {
return { id, osmObj }
}
} catch (e) {
const msg = "Could not download OSM-object " +
id +
" dropping it from the changes (" +
e +
")"
const msg =
"Could not download OSM-object " + id + " dropping it from the changes (" + e + ")"
this._reportError(msg)
this.errors.data.push(e)
this.errors.ping()
@ -561,26 +557,33 @@ export class Changes {
}
}
public static fragmentChanges(pending: ChangeDescription[], objects: OsmObject[]): {
refused: ChangeDescription[];
public static fragmentChanges(
pending: ChangeDescription[],
objects: OsmObject[]
): {
refused: ChangeDescription[]
toUpload: ChangeDescription[]
} {
const refused: ChangeDescription[] = []
const toUpload: ChangeDescription[] = []
// All ids which have an 'update'
const createdIds =
new Set(pending.filter(cd => cd.changes !== undefined).map(cd => cd.id))
pending.forEach(c => {
const createdIds = new Set(
pending.filter((cd) => cd.changes !== undefined).map((cd) => cd.id)
)
pending.forEach((c) => {
if (c.id < 0) {
if (createdIds.has(c.id)) {
toUpload.push(c)
} else {
reportError(`Got an orphaned change. The 'creation'-change description for ${c.type}/${c.id} got lost. Permanently dropping this change:`+JSON.stringify(c))
reportError(
`Got an orphaned change. The 'creation'-change description for ${c.type}/${c.id} got lost. Permanently dropping this change:` +
JSON.stringify(c)
)
}
return
}
const matchFound = !!objects.find(o => o.id === c.id && o.type === c.type)
const matchFound = !!objects.find((o) => o.id === c.id && o.type === c.type)
if (matchFound) {
toUpload.push(c)
} else {
@ -588,8 +591,7 @@ export class Changes {
}
})
return {refused, toUpload}
return { refused, toUpload }
}
/**
@ -604,7 +606,7 @@ export class Changes {
// We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes
const downloader = new OsmObjectDownloader(this.backend, undefined)
let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>(
neededIds.map(id => this.getOsmObject(id, downloader))
neededIds.map((id) => this.getOsmObject(id, downloader))
)
osmObjects = Utils.NoNull(osmObjects)
@ -640,14 +642,14 @@ export class Changes {
([key, count]) => ({
key: key,
value: count,
aggregate: true
aggregate: true,
})
)
const motivations = pending
.filter((descr) => descr.meta.specialMotivation !== undefined)
.map((descr) => ({
key: descr.meta.changeType + ":" + descr.type + "/" + descr.id,
value: descr.meta.specialMotivation
value: descr.meta.specialMotivation,
}))
const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject))
@ -678,7 +680,7 @@ export class Changes {
return {
key,
value: count,
aggregate: true
aggregate: true,
}
})
)
@ -693,26 +695,25 @@ export class Changes {
const metatags: ChangesetTag[] = [
{
key: "comment",
value: comment
value: comment,
},
{
key: "theme",
value: theme
value: theme,
},
...perType,
...motivations,
...perBinMessage
...perBinMessage,
]
let {toUpload, refused} = Changes.fragmentChanges(
pending, objects
)
let { toUpload, refused } = Changes.fragmentChanges(pending, objects)
await this._changesetHandler.UploadChangeset(
(csId, remappings) => {
if (remappings.size > 0) {
toUpload = toUpload.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings))
toUpload = toUpload.map((ch) =>
ChangeDescriptionTools.rewriteIds(ch, remappings)
)
}
const changes: {
@ -756,9 +757,9 @@ export class Changes {
)
console.log(
"Using current-open-changeset-" +
theme +
" from the preferences, got " +
openChangeset.data
theme +
" from the preferences, got " +
openChangeset.data
)
const refused = await self.flushSelectChanges(pendingChanges, openChangeset)
@ -777,7 +778,7 @@ export class Changes {
)
// We keep all the refused changes to try them again
this.pendingChanges.setData(refusedChanges.flatMap(c => c))
this.pendingChanges.setData(refusedChanges.flatMap((c) => c))
} catch (e) {
console.error(
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",

View file

@ -94,7 +94,6 @@ export class ChangesetHandler {
return hasChange
}
/**
* The full logic to upload a change to one or more elements.
*
@ -155,7 +154,11 @@ export class ChangesetHandler {
if (this._reportError) {
this._reportError(e)
}
console.warn("Could not open/upload changeset due to ", e, "trying again with a another fresh changeset ")
console.warn(
"Could not open/upload changeset due to ",
e,
"trying again with a another fresh changeset "
)
openChangeset.setData(undefined)
throw e
@ -188,7 +191,12 @@ export class ChangesetHandler {
await this.UpdateTags(csId, rewrittenTags)
} catch (e) {
if (this._reportError) {
this._reportError("Could not reuse changeset " + csId + ", might be closed: " + (e.stacktrace ?? ("" + e)))
this._reportError(
"Could not reuse changeset " +
csId +
", might be closed: " +
(e.stacktrace ?? "" + e)
)
}
console.warn("Could not upload, changeset is probably closed: ", e)
openChangeset.setData(undefined)
@ -237,7 +245,7 @@ export class ChangesetHandler {
if (newMetaTag === undefined) {
extraMetaTags.push({
key: key,
value: oldCsTags[key]
value: oldCsTags[key],
})
continue
}
@ -374,11 +382,11 @@ export class ChangesetHandler {
["locale", Locale.language.data],
["host", `${window.location.origin}${window.location.pathname}`],
["source", setSourceAsSurvey ? "survey" : undefined],
["imagery", this.changes.state["backgroundLayer"]?.data?.id]
["imagery", this.changes.state["backgroundLayer"]?.data?.id],
].map(([key, value]) => ({
key,
value,
aggregate: false
aggregate: false,
}))
}

View file

@ -214,7 +214,7 @@ export class OsmConnection {
this.auth.xhr(
{
method: "GET",
path: "/api/0.6/user/details"
path: "/api/0.6/user/details",
},
(err, details: XMLDocument) => {
if (err != null) {
@ -326,9 +326,9 @@ export class OsmConnection {
method,
headers: header,
content,
path: `/api/0.6/${path}`
path: `/api/0.6/${path}`,
},
function(err, response) {
function (err, response) {
if (err !== null) {
error(err)
} else {
@ -408,7 +408,7 @@ export class OsmConnection {
"notes.json",
content,
{
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
true
)
@ -449,7 +449,7 @@ export class OsmConnection {
file: gpx,
description: options.description,
tags: options.labels?.join(",") ?? "",
visibility: options.visibility
visibility: options.visibility,
}
if (!contents.description) {
@ -457,9 +457,9 @@ export class OsmConnection {
}
const extras = {
file:
"; filename=\"" +
'; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
"\"\r\nContent-Type: application/gpx+xml"
'"\r\nContent-Type: application/gpx+xml',
}
const boundary = "987654"
@ -467,7 +467,7 @@ export class OsmConnection {
let body = ""
for (const key in contents) {
body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\""
body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) {
body += extras[key]
}
@ -478,7 +478,7 @@ export class OsmConnection {
const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": "" + body.length
"Content-Length": "" + body.length,
})
const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed)
@ -499,9 +499,9 @@ export class OsmConnection {
{
method: "POST",
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
},
function(err) {
function (err) {
if (err !== null) {
error(err)
} else {
@ -516,7 +516,7 @@ export class OsmConnection {
* To be called by land.html
*/
public finishLogin(callback: (previousURL: string) => void) {
this.auth.authenticate(function() {
this.auth.authenticate(function () {
// Fully authed at this point
console.log("Authentication successful!")
const previousLocation = LocalStorageSource.Get("location_before_login")
@ -534,7 +534,7 @@ export class OsmConnection {
: window.location.protocol + "//" + window.location.host + "/land.html",
singlepage: true, // We always use 'singlePage', it is the most stable - including in PWA
auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
})
}

View file

@ -635,7 +635,7 @@ export default class SimpleMetaTaggers {
isLazy: true,
},
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
if(tagsStore === undefined){
if (tagsStore === undefined) {
return
}
Utils.AddLazyPropertyAsync(feature.properties, "_currency", async () => {
@ -780,11 +780,11 @@ export default class SimpleMetaTaggers {
subElements.push("## Metatags calculated by MapComplete")
subElements.push(
"The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme"
"The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme"
)
for (const metatag of SimpleMetaTaggers.metatags) {
subElements.push(
"### "+metatag.keys.join(", "),
"### " + metatag.keys.join(", "),
metatag.doc,
metatag.isLazy ? "This is a lazy metatag and is only calculated when needed" : ""
)

View file

@ -1,14 +1,42 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}

View file

@ -17,14 +17,15 @@ interface ImageFetcher {
fetchImages(lat: number, lon: number): Promise<P4CPicture[]>
readonly name: string
}
class CachedFetcher implements ImageFetcher {
private readonly _fetcher: ImageFetcher
private readonly _zoomlevel: number
private readonly cache: Map<number, Promise<P4CPicture[]>> = new Map<number, Promise<P4CPicture[]>>()
private readonly cache: Map<number, Promise<P4CPicture[]>> = new Map<
number,
Promise<P4CPicture[]>
>()
public readonly name: string
constructor(fetcher: ImageFetcher, zoomlevel: number = 19) {
@ -71,20 +72,17 @@ class NearbyImageUtils {
const da = GeoOperations.distanceBetween([a.coordinates.lng, a.coordinates.lat], c)
const db = GeoOperations.distanceBetween([b.coordinates.lng, b.coordinates.lat], c)
return da - db
})
}
}
class P4CImageFetcher implements ImageFetcher {
public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const
public static readonly apiUrls = ["https://api.flickr.com"]
private _options: { maxDaysOld: number, searchRadius: number }
private _options: { maxDaysOld: number; searchRadius: number }
public readonly name: P4CService
constructor(service: P4CService, options?: { maxDaysOld: number, searchRadius: number }) {
constructor(service: P4CService, options?: { maxDaysOld: number; searchRadius: number }) {
this.name = service
this._options = options
}
@ -95,22 +93,19 @@ class P4CImageFetcher implements ImageFetcher {
const searchRadius = this._options?.searchRadius ?? 100
try {
return await picManager.startPicsRetrievalAround(
new P4C.LatLng(lat, lon),
searchRadius,
{
mindate: new Date().getTime() - maxAgeSeconds,
towardscenter: false,
},
}
)
} catch (e) {
console.log("P4C image fetcher failed with", e)
throw e
}
}
}
/**
@ -165,9 +160,7 @@ class ImagesInLoadedDataFetcher implements ImageFetcher {
}
}
class MapillaryFetcher implements ImageFetcher {
public readonly name = "mapillary_new"
private readonly _panoramas: "only" | "no" | undefined
private readonly _max_images: 100 | number
@ -176,9 +169,9 @@ class MapillaryFetcher implements ImageFetcher {
private readonly end_captured_at?: Date
constructor(options?: {
panoramas: undefined | "only" | "no",
max_images?: 100 | number,
start_captured_at?: Date,
panoramas: undefined | "only" | "no"
max_images?: 100 | number
start_captured_at?: Date
end_captured_at?: Date
}) {
this._panoramas = options?.panoramas
@ -188,12 +181,19 @@ class MapillaryFetcher implements ImageFetcher {
}
async fetchImages(lat: number, lon: number): Promise<P4CPicture[]> {
const boundingBox = new BBox([[lon, lat]]).padAbsolute(0.003)
let url = "https://graph.mapillary.com/images?fields=computed_geometry,creator,id,thumb_256_url,thumb_original_url,compass_angle&bbox="
+ [boundingBox.getWest(), boundingBox.getSouth(), boundingBox.getEast(), boundingBox.getNorth()].join(",")
+ "&access_token=" + encodeURIComponent(Constants.mapillary_client_token_v4)
+ "&limit=" + this._max_images
let url =
"https://graph.mapillary.com/images?fields=computed_geometry,creator,id,thumb_256_url,thumb_original_url,compass_angle&bbox=" +
[
boundingBox.getWest(),
boundingBox.getSouth(),
boundingBox.getEast(),
boundingBox.getNorth(),
].join(",") +
"&access_token=" +
encodeURIComponent(Constants.mapillary_client_token_v4) +
"&limit=" +
this._max_images
{
if (this._panoramas === "no") {
url += "&is_pano=false"
@ -201,21 +201,28 @@ class MapillaryFetcher implements ImageFetcher {
url += "&is_pano=true"
}
if (this.start_captured_at) {
url += "&start_captured_at="+ this.start_captured_at?.toISOString()
url += "&start_captured_at=" + this.start_captured_at?.toISOString()
}
if (this.end_captured_at) {
url += "&end_captured_at="+ this.end_captured_at?.toISOString()
url += "&end_captured_at=" + this.end_captured_at?.toISOString()
}
}
const response = await Utils.downloadJson<{
data: { id: string, creator: string, computed_geometry: Point, is_pano: boolean,thumb_256_url: string, thumb_original_url: string, compass_angle: number }[]
data: {
id: string
creator: string
computed_geometry: Point
is_pano: boolean
thumb_256_url: string
thumb_original_url: string
compass_angle: number
}[]
}>(url)
const pics: P4CPicture[] = []
for (const img of response.data) {
const c = img.computed_geometry.coordinates
if(img.thumb_original_url === undefined){
if (img.thumb_original_url === undefined) {
continue
}
pics.push({
@ -224,7 +231,7 @@ class MapillaryFetcher implements ImageFetcher {
coordinates: { lng: c[0], lat: c[1] },
thumbUrl: img.thumb_256_url,
osmTags: {
"mapillary":img.id
mapillary: img.id,
},
details: {
isSpherical: img.is_pano,
@ -241,22 +248,24 @@ export class CombinedFetcher {
private readonly sources: ReadonlyArray<CachedFetcher>
public static apiUrls = P4CImageFetcher.apiUrls
constructor(radius: number, maxage: Date, indexedFeatures: IndexedFeatureSource) {
this.sources = [
new ImagesInLoadedDataFetcher(indexedFeatures, radius),
new MapillaryFetcher({
panoramas: "no",
max_images: 25,
start_captured_at : maxage
start_captured_at: maxage,
}),
new P4CImageFetcher("mapillary"),
new P4CImageFetcher("wikicommons"),
].map(f => new CachedFetcher(f))
].map((f) => new CachedFetcher(f))
}
public getImagesAround(lon: number, lat: number): {
images: Store<P4CPicture[]>,
public getImagesAround(
lon: number,
lat: number
): {
images: Store<P4CPicture[]>
state: Store<Record<string, "loading" | "done" | "error">>
} {
const src = new UIEventSource<P4CPicture[]>([])
@ -264,9 +273,9 @@ export class CombinedFetcher {
for (const source of this.sources) {
state.data[source.name] = "loading"
state.ping()
source.fetchImages(lat, lon)
.then(pics => {
console.log(source.name,"==>>",pics)
source.fetchImages(lat, lon).then(
(pics) => {
console.log(source.name, "==>>", pics)
state.data[source.name] = "done"
state.ping()
if (src.data === undefined) {
@ -276,27 +285,26 @@ export class CombinedFetcher {
const seenIds = new Set<string>()
for (const p4CPicture of [...src.data, ...pics]) {
const id = p4CPicture.pictureUrl
if(seenIds.has(id)){
if (seenIds.has(id)) {
continue
}
newList.push(p4CPicture)
seenIds.add(id)
if(id === undefined){
console.log("Img:", p4CPicture)
if (id === undefined) {
console.log("Img:", p4CPicture)
}
}
NearbyImageUtils.sortByDistance(newList, lon, lat)
src.setData(newList)
}
}, err => {
},
(err) => {
console.error("Could not load images from", source.name, "due to", err)
state.data[source.name] = "error"
state.ping()
})
}
)
}
return { images: src, state }
}
}

View file

@ -119,7 +119,9 @@ export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions {
notInstanceOf?: number[]
}
interface SparqlResult {results: { bindings: {item, label, description, num}[] }}
interface SparqlResult {
results: { bindings: { item; label; description; num }[] }
}
/**
* Utility functions around wikidata
@ -422,7 +424,7 @@ export default class Wikidata {
}
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + id + ".json"
const entities = (await Utils.downloadJsonCached<{entities}>(url, 10000)).entities
const entities = (await Utils.downloadJsonCached<{ entities }>(url, 10000)).entities
const firstKey = <string>Array.from(Object.keys(entities))[0] // Roundabout way to fetch the entity; it might have been a redirect
const response = entities[firstKey]