Chore: housekeeping

This commit is contained in:
Pieter Vander Vennet 2025-02-10 02:04:58 +01:00
parent cd9e03dd6f
commit b300fffdc5
156 changed files with 4436 additions and 1318 deletions

View file

@ -13,7 +13,6 @@ import StaticFeatureSource, {
} from "../FeatureSource/Sources/StaticFeatureSource"
import { MapProperties } from "../../Models/MapProperties"
import { Orientation } from "../../Sensors/Orientation"
;("use strict")
/**
* The geolocation-handler takes a map-location and a geolocation state.

View file

@ -6,7 +6,7 @@ import { bbox } from "@turf/bbox"
export class BBox {
static global: BBox = new BBox([
[-180, -90],
[180, 90]
[180, 90],
])
readonly maxLat: number
readonly maxLon: number
@ -62,7 +62,7 @@ export class BBox {
static fromLeafletBounds(bounds) {
return new BBox([
[bounds.getWest(), bounds.getNorth()],
[bounds.getEast(), bounds.getSouth()]
[bounds.getEast(), bounds.getSouth()],
])
}
@ -101,7 +101,7 @@ export class BBox {
}
return new BBox([
[maxLon, maxLat],
[minLon, minLat]
[minLon, minLat],
])
}
@ -128,7 +128,7 @@ export class BBox {
public unionWith(other: BBox) {
return new BBox([
[Math.max(this.maxLon, other.maxLon), Math.max(this.maxLat, other.maxLat)],
[Math.min(this.minLon, other.minLon), Math.min(this.minLat, other.minLat)]
[Math.min(this.minLon, other.minLon), Math.min(this.minLat, other.minLat)],
])
}
@ -181,7 +181,7 @@ export class BBox {
return new BBox([
[lon - s / 2, lat - s / 2],
[lon + s / 2, lat + s / 2]
[lon + s / 2, lat + s / 2],
])
}
@ -238,21 +238,21 @@ export class BBox {
const lonDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor)
return new BBox([
[this.minLon - lonDiff, this.minLat - latDiff],
[this.maxLon + lonDiff, this.maxLat + latDiff]
[this.maxLon + lonDiff, this.maxLat + latDiff],
])
}
padAbsolute(degrees: number): BBox {
return new BBox([
[this.minLon - degrees, this.minLat - degrees],
[this.maxLon + degrees, this.maxLat + degrees]
[this.maxLon + degrees, this.maxLat + degrees],
])
}
toLngLat(): [[number, number], [number, number]] {
return [
[this.minLon, this.minLat],
[this.maxLon, this.maxLat]
[this.maxLon, this.maxLat],
]
}
@ -271,7 +271,7 @@ export class BBox {
return {
type: "Feature",
properties: properties,
geometry: this.asGeometry()
geometry: this.asGeometry(),
}
}
@ -284,9 +284,9 @@ export class BBox {
[this.maxLon, this.minLat],
[this.maxLon, this.maxLat],
[this.minLon, this.maxLat],
[this.minLon, this.minLat]
]
]
[this.minLon, this.minLat],
],
],
}
}
@ -316,7 +316,7 @@ export class BBox {
minLon,
maxLon,
minLat,
maxLat
maxLat,
}
}

View file

@ -77,21 +77,34 @@ export default class SaveFeatureSourceToLocalStorage {
this.storage = storage
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
features.features.addCallbackAndRunD((features) => {
if (features.some(f => {
let totalPoints = 0
if (f.geometry.type === "MultiPolygon") {
totalPoints = f.geometry.coordinates.map(rings => rings.map(ring => ring.length).reduce((a, b) => a + b)).reduce((a, b) => a + b)
} else if (f.geometry.type === "Polygon" || f.geometry.type === "MultiLineString") {
totalPoints = f.geometry.coordinates.map(ring => ring.length).reduce((a, b) => a + b)
} else if (f.geometry.type === "LineString") {
totalPoints = f.geometry.coordinates.length
}
if (totalPoints > 1000) {
console.warn(`Not caching tiles, detected a big object (${totalPoints} points for ${f.properties.id})`)
return true
}
return false
})) {
if (
features.some((f) => {
let totalPoints = 0
if (f.geometry.type === "MultiPolygon") {
totalPoints = f.geometry.coordinates
.map((rings) =>
rings.map((ring) => ring.length).reduce((a, b) => a + b)
)
.reduce((a, b) => a + b)
} else if (
f.geometry.type === "Polygon" ||
f.geometry.type === "MultiLineString"
) {
totalPoints = f.geometry.coordinates
.map((ring) => ring.length)
.reduce((a, b) => a + b)
} else if (f.geometry.type === "LineString") {
totalPoints = f.geometry.coordinates.length
}
if (totalPoints > 1000) {
console.warn(
`Not caching tiles, detected a big object (${totalPoints} points for ${f.properties.id})`
)
return true
}
return false
})
) {
// Has big objects
return
}

View file

@ -37,8 +37,11 @@ export default class DynamicGeoJsonTileSource extends UpdatableDynamicTileSource
if (DynamicGeoJsonTileSource.whitelistCache.has(whitelistUrl)) {
whitelist = DynamicGeoJsonTileSource.whitelistCache.get(whitelistUrl)
} else {
Utils.downloadJsonCached<Record<string | number, number[]>>(whitelistUrl, 1000 * 60 * 60)
.then(json => {
Utils.downloadJsonCached<Record<string | number, number[]>>(
whitelistUrl,
1000 * 60 * 60
)
.then((json) => {
const data = new Map<number, Set<number>>()
for (const x in json) {
if (x === "zoom") {

View file

@ -10,12 +10,12 @@ import {
MultiPolygon,
Point,
Polygon,
Position
Position,
} from "geojson"
import { Tiles } from "../Models/TileRange"
import { Utils } from "../Utils"
("use strict")
;("use strict")
export class GeoOperations {
private static readonly _earthRadius = 6378137
@ -29,7 +29,7 @@ export class GeoOperations {
"behind",
"sharp_left",
"left",
"slight_left"
"slight_left",
] as const
private static reverseBearing = {
N: 0,
@ -47,7 +47,7 @@ export class GeoOperations {
W: 270,
WNW: 292.5,
NW: 315,
NNW: 337.5
NNW: 337.5,
}
/**
@ -309,7 +309,7 @@ export class GeoOperations {
bufferSizeInMeter: number
): Feature<Polygon | MultiPolygon> | FeatureCollection<Polygon | MultiPolygon> {
return turf.buffer(feature, bufferSizeInMeter / 1000, {
units: "kilometers"
units: "kilometers",
})
}
@ -325,9 +325,9 @@ export class GeoOperations {
[lon0, lat],
[lon0, lat0],
[lon, lat0],
[lon, lat]
]
}
[lon, lat],
],
},
}
}
@ -345,7 +345,10 @@ export class GeoOperations {
public static nearestPoint(
way: Feature<LineString>,
point: [number, number]
): Feature<Point, { dist: number; index: number; multiFeatureIndex: number; location: number }> {
): Feature<
Point,
{ dist: number; index: number; multiFeatureIndex: number; location: number }
> {
return turf.nearestPointOnLine(<Feature<LineString>>way, point, { units: "kilometers" })
}
@ -368,9 +371,9 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "LineString",
coordinates: way.geometry.coordinates[0]
coordinates: way.geometry.coordinates[0],
},
properties: way.properties
properties: way.properties,
}
}
if (way.geometry.type === "MultiPolygon") {
@ -378,9 +381,9 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "MultiLineString",
coordinates: way.geometry.coordinates[0]
coordinates: way.geometry.coordinates[0],
},
properties: way.properties
properties: way.properties,
}
}
if (way.geometry.type === "LineString") {
@ -542,7 +545,10 @@ export class GeoOperations {
* Note: IDs are rewritten
* Also @see spreadIntoBBoxes
*/
public static clipAllInBox(features: ReadonlyArray<Readonly<Feature>>, tileIndex: number): Feature[] {
public static clipAllInBox(
features: ReadonlyArray<Readonly<Feature>>,
tileIndex: number
): Feature[] {
const bbox = Tiles.asGeojson(tileIndex)
const newFeatures: Feature[] = []
for (const f of features) {
@ -555,7 +561,7 @@ export class GeoOperations {
}
const properties = {
...f.properties,
id
id,
}
intersectionPart.properties = properties
newFeatures.push(intersectionPart)
@ -587,8 +593,8 @@ export class GeoOperations {
properties: {},
geometry: {
type: "Point",
coordinates: p
}
coordinates: p,
},
}
)
}
@ -604,7 +610,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
return (
header +
"\n<name>" +
@ -643,7 +649,7 @@ export class GeoOperations {
trackPoints.push(trkpt)
}
const header =
"<gpx version=\"1.1\" creator=\"mapcomplete.org\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">"
'<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
return (
header +
"\n<name>" +
@ -669,7 +675,7 @@ export class GeoOperations {
const copy = {
...feature,
geometry: { ...feature.geometry }
geometry: { ...feature.geometry },
}
let coordinates: [number, number][]
if (feature.geometry.type === "LineString") {
@ -727,8 +733,8 @@ export class GeoOperations {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [a, b]
}
coordinates: [a, b],
},
},
distanceMeter,
{ units: "meters" }
@ -769,13 +775,17 @@ export class GeoOperations {
possiblyEnclosingFeature: Readonly<Feature<Polygon | MultiPolygon>>
): boolean {
if (feature.geometry.type === "MultiPolygon") {
const polygons = feature.geometry.coordinates.map(coordinates =>
<Feature<Polygon>>{
type: "Feature", geometry: {
type: "Polygon", coordinates
const polygons = feature.geometry.coordinates.map(
(coordinates) =>
<Feature<Polygon>>{
type: "Feature",
geometry: {
type: "Polygon",
coordinates,
},
}
})
return !polygons.some(polygon => !booleanWithin(polygon, possiblyEnclosingFeature))
)
return !polygons.some((polygon) => !booleanWithin(polygon, possiblyEnclosingFeature))
}
return booleanWithin(feature, possiblyEnclosingFeature)
}
@ -784,7 +794,10 @@ export class GeoOperations {
* Create an intersection between two features.
* One or multiple new feature are returned based on 'toSplit', which'll have a geometry that is completely withing boundary
*/
public static clipWith(toSplit: Readonly<Feature>, boundary: Readonly<Feature<Polygon>>): Feature[] {
public static clipWith(
toSplit: Readonly<Feature>,
boundary: Readonly<Feature<Polygon>>
): Feature[] {
if (toSplit.geometry.type === "Point") {
const p = <Feature<Point>>toSplit
if (GeoOperations.inside(<[number, number]>p.geometry.coordinates, boundary)) {
@ -795,7 +808,10 @@ export class GeoOperations {
}
if (toSplit.geometry.type === "LineString") {
const splitup: Feature<LineString>[] = turf.lineSplit(<Feature<LineString>>toSplit, boundary).features
const splitup: Feature<LineString>[] = turf.lineSplit(
<Feature<LineString>>toSplit,
boundary
).features
const kept: Feature[] = []
for (const f of splitup) {
if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) {
@ -825,21 +841,29 @@ export class GeoOperations {
return kept
}
if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") {
const splitup = turf.intersect(turf.featureCollection([<Feature<Polygon | MultiPolygon>>toSplit, boundary]))
const splitup = turf.intersect(
turf.featureCollection([<Feature<Polygon | MultiPolygon>>toSplit, boundary])
)
if (splitup === null) {
// No intersection found.
// Either: the boundary is contained fully in 'toSplit', 'toSplit' is contained fully in 'boundary' or they are unrelated at all
if (GeoOperations.completelyWithin(toSplit, boundary)) {
return [toSplit]
}
if (GeoOperations.completelyWithin(boundary, <Feature<Polygon | MultiPolygon>>toSplit)) {
return [{
type: "Feature",
properties: { ...toSplit.properties },
geometry: boundary.geometry,
bbox: boundary.bbox
}]
if (
GeoOperations.completelyWithin(
boundary,
<Feature<Polygon | MultiPolygon>>toSplit
)
) {
return [
{
type: "Feature",
properties: { ...toSplit.properties },
geometry: boundary.geometry,
bbox: boundary.bbox,
},
]
}
return []
}
@ -911,7 +935,9 @@ export class GeoOperations {
return undefined
case "end":
if (feature.geometry.type === "LineString") {
return <[number, number]>(<Feature<LineString>>feature).geometry.coordinates.at(-1)
return <[number, number]>(
(<Feature<LineString>>feature).geometry.coordinates.at(-1)
)
}
return undefined
default:
@ -934,8 +960,8 @@ export class GeoOperations {
properties: p.properties,
geometry: {
type: "LineString",
coordinates: p.geometry.coordinates[0]
}
coordinates: p.geometry.coordinates[0],
},
}
}
@ -963,7 +989,7 @@ export class GeoOperations {
console.debug("Splitting way", feature.properties.id)
result.push(<Feature>{
...feature,
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) }
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) },
})
coors = coors.slice(0, i + 1)
break
@ -972,7 +998,7 @@ export class GeoOperations {
}
result.push(<Feature>{
...feature,
geometry: { ...feature.geometry, coordinates: coors }
geometry: { ...feature.geometry, coordinates: coors },
})
}
}
@ -1146,8 +1172,8 @@ export class GeoOperations {
properties: multiLineStringFeature.properties,
geometry: {
type: "LineString",
coordinates: coors[0]
}
coordinates: coors[0],
},
}
}
return {
@ -1155,8 +1181,8 @@ export class GeoOperations {
properties: multiLineStringFeature.properties,
geometry: {
type: "MultiLineString",
coordinates: coors
}
coordinates: coors,
},
}
}
@ -1306,9 +1332,10 @@ export class GeoOperations {
}
if (e.message.indexOf("SweepLine tree") >= 0) {
console.log("Applying fallback intersection...")
const intersection = turf.intersect(turf.featureCollection([
const intersection = turf.intersect(
turf.featureCollection([
turf.truncate(feature),
turf.truncate(otherFeature)
turf.truncate(otherFeature),
])
)
if (intersection == null) {

View file

@ -83,11 +83,15 @@ export default class AllImageProviders {
): number {
let count = 0
const sources = [Imgur.singleton,
const sources = [
Imgur.singleton,
Mapillary.singleton,
Panoramax.singleton,
AllImageProviders.genericImageProvider]
const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...sources.map(s => s.defaultKeyPrefixes)))
AllImageProviders.genericImageProvider,
]
const allPrefixes = Utils.Dedup(
prefixes ?? [].concat(...sources.map((s) => s.defaultKeyPrefixes))
)
for (const prefix of allPrefixes) {
for (const k in tags) {
if (!tags[k]) {

View file

@ -32,7 +32,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
lat: number
}
): BaseUIElement {
const host = ("https://" + new URL(img.url).host)
const host = "https://" + new URL(img.url).host
const p = new Panoramax(host)
return new Link(
new SvelteUIElement(Panoramax_bw),

View file

@ -53,7 +53,7 @@ export class ChangesetHandler {
| { addAlias: (id0: string, id1: string) => void }
| undefined,
changes: Changes,
reportError: (e: string | Error, extramessage: string) => void,
reportError: (e: string | Error, extramessage: string) => void
) {
this.osmConnection = osmConnection
this._reportError = reportError
@ -114,7 +114,7 @@ export class ChangesetHandler {
private async UploadWithNew(
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
openChangeset: UIEventSource<number>,
extraMetaTags: ChangesetTag[],
extraMetaTags: ChangesetTag[]
) {
const csId = await this.OpenChangeset(extraMetaTags)
openChangeset.setData(csId)
@ -122,7 +122,7 @@ export class ChangesetHandler {
console.log(
"Opened a new changeset (openChangeset.data is undefined):",
changeset,
extraMetaTags,
extraMetaTags
)
const changes = await this.UploadChange(csId, changeset)
const hasSpecialMotivationChanges = ChangesetHandler.rewriteMetaTags(extraMetaTags, changes)
@ -145,7 +145,7 @@ export class ChangesetHandler {
public async UploadChangeset(
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
extraMetaTags: ChangesetTag[],
openChangeset: UIEventSource<number>,
openChangeset: UIEventSource<number>
): Promise<void> {
if (
!extraMetaTags.some((tag) => tag.key === "comment") ||
@ -180,13 +180,13 @@ export class ChangesetHandler {
try {
const rewritings = await this.UploadChange(
csId,
generateChangeXML(csId, this._remappings),
generateChangeXML(csId, this._remappings)
)
const rewrittenTags = this.RewriteTagsOf(
extraMetaTags,
rewritings,
oldChangesetMeta,
oldChangesetMeta
)
await this.UpdateTags(csId, rewrittenTags)
return // We are done!
@ -197,7 +197,7 @@ export class ChangesetHandler {
} catch (e) {
this._reportError(
e,
"While getting metadata from a changeset " + openChangeset.data,
"While getting metadata from a changeset " + openChangeset.data
)
}
}
@ -225,7 +225,7 @@ export class ChangesetHandler {
console.warn(
"Could not open/upload changeset due to ",
e,
"trying again with a another fresh changeset ",
"trying again with a another fresh changeset "
)
openChangeset.setData(undefined)
@ -251,7 +251,7 @@ export class ChangesetHandler {
uid: number // User ID
changes_count: number
tags: any
},
}
): ChangesetTag[] {
// Note: extraMetaTags is where all the tags are collected into
@ -388,7 +388,7 @@ export class ChangesetHandler {
tag.key !== undefined &&
tag.value !== undefined &&
tag.key !== "" &&
tag.value !== "",
tag.value !== ""
)
const metadata = tags.map((kv) => `<tag k="${kv.key}" v="${escapeHtml(kv.value)}"/>`)
const content = [`<osm><changeset>`, metadata, `</changeset></osm>`].join("")
@ -434,7 +434,7 @@ export class ChangesetHandler {
const csId = await this.osmConnection.put(
"changeset/create",
[`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
{ "Content-Type": "text/xml" },
{ "Content-Type": "text/xml" }
)
return Number(csId)
}
@ -444,12 +444,12 @@ export class ChangesetHandler {
*/
private async UploadChange(
changesetId: number,
changesetXML: string,
changesetXML: string
): Promise<Map<string, string>> {
const response = await this.osmConnection.post<XMLDocument>(
"changeset/" + changesetId + "/upload",
changesetXML,
{ "Content-Type": "text/xml" },
{ "Content-Type": "text/xml" }
)
const changes = this.parseUploadChangesetResponse(response)
console.log("Uploaded changeset ", changesetId)

View file

@ -10,44 +10,43 @@ import { AndroidPolyfill } from "../Web/AndroidPolyfill"
import { QueryParameters } from "../Web/QueryParameters"
interface OsmUserInfo {
"id": number,
"display_name": string,
"account_created": string,
"description": string,
"contributor_terms": {
"agreed": boolean,
"pd": boolean
id: number
display_name: string
account_created: string
description: string
contributor_terms: {
agreed: boolean
pd: boolean
}
"img"?: {
"href": string,
img?: {
href: string
}
"roles": string[]
"changesets": {
"count": number
roles: string[]
changesets: {
count: number
}
traces: {
count: number
}
"blocks": {
"received": {
"count": number,
"active": number
blocks: {
received: {
count: number
active: number
}
}
home?: {
lat: number,
lon: number,
lat: number
lon: number
zoom: number
}
"languages": string[]
"messages": {
"received": {
"count": number,
"unread": number
},
"sent": {
"count": number
languages: string[]
messages: {
received: {
count: number
unread: number
}
sent: {
count: number
}
id: number
@ -60,12 +59,10 @@ interface OsmUserInfo {
traces: { count: number }
blocks: { received: { count: number; active: number } }
img?: { href: string }
home: { lat: number, lon: number }
home: { lat: number; lon: number }
languages?: string[]
messages: { received: { count: number, unread: number }, sent: { count: number } }
}
messages: { received: { count: number; unread: number }; sent: { count: number } }
}
}
export default interface UserDetails {
@ -81,7 +78,6 @@ export default interface UserDetails {
tracesCount: number
description?: string
languages: string[]
}
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
@ -126,14 +122,14 @@ export class OsmConnection {
public userDetails: UIEventSource<UserDetails | undefined>
public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown",
"unknown"
)
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown",
"unknown"
)
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
"not-attempted",
"not-attempted"
)
public preferencesHandler: OsmPreferences
public readonly _oauth_config: AuthConfig
@ -193,7 +189,7 @@ export class OsmConnection {
(user) =>
!!user &&
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
[this.apiIsOnline],
[this.apiIsOnline]
)
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
@ -232,7 +228,7 @@ export class OsmConnection {
defaultValue: string = undefined,
options?: {
prefix?: string
},
}
): UIEventSource<T | undefined> {
const prefix = options?.prefix ?? "mapcomplete-"
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
@ -241,7 +237,7 @@ export class OsmConnection {
public getPreference<T extends string = string>(
key: string,
defaultValue: string = undefined,
prefix: string = "mapcomplete-",
prefix: string = "mapcomplete-"
): UIEventSource<T | undefined> {
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
}
@ -275,7 +271,7 @@ export class OsmConnection {
}
this.updateAuthObject(true)
LocalStorageSource.get("location_before_login").setData(
Utils.runningFromConsole ? undefined : window.location.href,
Utils.runningFromConsole ? undefined : window.location.href
)
this.auth.authenticate((err) => {
@ -292,11 +288,13 @@ export class OsmConnection {
this.loadingStatus.setData("error")
return
}
const data = <{
"version": "0.6",
"license": "http://opendatacommons.org/licenses/odbl/1-0/",
"user": OsmUserInfo
}>JSON.parse(result)
const data = <
{
version: "0.6"
license: "http://opendatacommons.org/licenses/odbl/1-0/"
user: OsmUserInfo
}
>JSON.parse(result)
const user = data.user
const userdetails: UserDetails = {
uid: user.id,
@ -343,7 +341,7 @@ export class OsmConnection {
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
header?: Record<string, string>,
content?: string,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<string> {
const connection: osmAuth = this.auth
if (allowAnonymous && !this.auth.authenticated()) {
@ -351,7 +349,7 @@ export class OsmConnection {
`${this.Backend()}/api/0.6/${path}`,
header,
method,
content,
content
)
if (possibleResult["content"]) {
return possibleResult["content"]
@ -371,15 +369,15 @@ 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 {
ok(response)
}
},
}
)
})
}
@ -388,7 +386,7 @@ export class OsmConnection {
path: string,
content?: string,
header?: Record<string, string>,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<T> {
return <T>await this.interact(path, "POST", header, content, allowAnonymous)
}
@ -396,7 +394,7 @@ export class OsmConnection {
public async put<T extends string>(
path: string,
content?: string,
header?: Record<string, string>,
header?: Record<string, string>
): Promise<T> {
return <T>await this.interact(path, "PUT", header, content)
}
@ -404,7 +402,7 @@ export class OsmConnection {
public async get(
path: string,
header?: Record<string, string>,
allowAnonymous: boolean = false,
allowAnonymous: boolean = false
): Promise<string> {
return await this.interact(path, "GET", header, undefined, allowAnonymous)
}
@ -443,7 +441,7 @@ export class OsmConnection {
return new Promise<{ id: number }>((ok) => {
window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000,
Math.random() * 5000
)
})
}
@ -453,9 +451,9 @@ 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,
true
)
const parsed = JSON.parse(response)
console.log("Got result:", parsed)
@ -482,14 +480,14 @@ export class OsmConnection {
* Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words.
*/
labels: string[]
},
}
): Promise<{ id: number }> {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
return new Promise<{ id: number }>((ok) => {
window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000,
Math.random() * 5000
)
})
}
@ -498,7 +496,7 @@ export class OsmConnection {
file: gpx,
description: options.description,
tags: options.labels?.join(",") ?? "",
visibility: options.visibility
visibility: options.visibility,
}
if (!contents.description) {
@ -506,9 +504,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"
@ -516,7 +514,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]
}
@ -527,7 +525,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)
@ -548,15 +546,15 @@ 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 {
ok()
}
},
}
)
})
}
@ -576,15 +574,18 @@ export class OsmConnection {
public getToken(): string {
// https://www.openstreetmap.orgoauth2_access_token
let prefix = this.Backend()
while(prefix.endsWith("/")){
prefix = prefix.substring(0, prefix.length-2)
while (prefix.endsWith("/")) {
prefix = prefix.substring(0, prefix.length - 2)
}
return QueryParameters.GetQueryParameter(prefix+ "oauth_token", undefined).data ?? window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token")
return (
QueryParameters.GetQueryParameter(prefix + "oauth_token", undefined).data ??
window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token")
)
}
private async loginAndroidPolyfill() {
const key = "https://www.openstreetmap.orgoauth2_access_token"
if(localStorage.getItem(key)){
if (localStorage.getItem(key)) {
// We are probably already logged in
return
}
@ -595,7 +596,6 @@ export class OsmConnection {
console.log("Logged in!")
}
await this.loadUserInfo()
}
private updateAuthObject(autoLogin: boolean) {
let redirect_uri = Utils.runningFromConsole
@ -614,7 +614,7 @@ export class OsmConnection {
*/
singlepage: !this._iframeMode && !AndroidPolyfill.inAndroid.data,
auto: autoLogin,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
})
if (AndroidPolyfill.inAndroid.data) {
this.loginAndroidPolyfill() // NO AWAIT!
@ -658,7 +658,8 @@ export class OsmConnection {
if (this.fakeUser) {
return
}
this.fetchCapabilities().then(({ api, gpx }) => {
this.fetchCapabilities()
.then(({ api, gpx }) => {
this.apiIsOnline.setData(api)
this.gpxServiceIsOnline.setData(gpx)
})
@ -695,7 +696,7 @@ export class OsmConnection {
}
try {
const result = await Utils.downloadJson<CapabilityResult>(
this.Backend() + "/api/0.6/capabilities.json",
this.Backend() + "/api/0.6/capabilities.json"
)
if (result?.api?.status === undefined) {
console.log("Something went wrong:", result)

View file

@ -1,4 +1,8 @@
import GeocodingProvider, { GeocodeResult, GeocodingOptions, SearchResult } from "./GeocodingProvider"
import GeocodingProvider, {
GeocodeResult,
GeocodingOptions,
SearchResult,
} from "./GeocodingProvider"
import { Utils } from "../../Utils"
import { Store, Stores } from "../UIEventSource"

View file

@ -5,7 +5,7 @@ import GeocodingProvider, {
GeocodingOptions,
GeocodingUtils,
ReverseGeocodingProvider,
ReverseGeocodingResult
ReverseGeocodingResult,
} from "./GeocodingProvider"
import { Utils } from "../../Utils"
import { Feature, FeatureCollection } from "geojson"

View file

@ -83,7 +83,10 @@ export default class ThemeSearch {
}
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
if ((location.hostname === "localhost" && !AndroidPolyfill.inAndroid.data) || location.hostname === "127.0.0.1") {
if (
(location.hostname === "localhost" && !AndroidPolyfill.inAndroid.data) ||
location.hostname === "127.0.0.1"
) {
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
}

View file

@ -80,7 +80,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger {
super({
keys: ["_referencing_ways"],
isLazy: true,
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. "
doc: "_referencing_ways contains - for a node - which ways use this node as point in their geometry. ",
})
}
@ -116,7 +116,7 @@ class CountryTagger extends SimpleMetaTagger {
super({
keys: ["_country"],
doc: "The country codes of the of the country/countries that the feature is located in (with latlon2country). Might contain _multiple_ countries, separated by a `;`",
includesDates: false
includesDates: false,
})
}
@ -213,9 +213,9 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
"_last_edit:changeset",
"_last_edit:timestamp",
"_version_number",
"_backend"
"_backend",
],
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass"
doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass",
})
}
@ -246,16 +246,20 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
class NormalizePanoramax extends SimpleMetaTagger {
constructor() {
super(
{
keys: ["panoramax"],
doc: "Converts a `panoramax=hash1;hash2;hash3;...` into `panoramax=hash1`,`panoramax:0=hash1`...",
isLazy: false,
cleanupRetagger: true
})
super({
keys: ["panoramax"],
doc: "Converts a `panoramax=hash1;hash2;hash3;...` into `panoramax=hash1`,`panoramax:0=hash1`...",
isLazy: false,
cleanupRetagger: true,
})
}
private addValue(comesFromKey: string, tags: Record<string, string>, hashesToAdd: string[], postfix?: string) {
private addValue(
comesFromKey: string,
tags: Record<string, string>,
hashesToAdd: string[],
postfix?: string
) {
let basekey = "panoramax"
if (postfix) {
basekey = "panoramax:" + postfix
@ -279,7 +283,11 @@ class NormalizePanoramax extends SimpleMetaTagger {
* new NormalizePanoramax().applyMetaTagsOnFeature(_, _, tags, _)
* tags.data // => {"panoramax": "abc", "panoramax:0" : "def", "panoramax:1": "ghi", "panoramax:2":"xyz", "panoramax:3":"uvw", "panoramax:streetsign":"a", "panoramax:streetsign:0":"b","panoramax:streetsign:1": "c"}
*/
applyMetaTagsOnFeature(feature: Feature, layer: LayerConfig, tags: UIEventSource<Record<string, string>>): boolean {
applyMetaTagsOnFeature(
feature: Feature,
layer: LayerConfig,
tags: UIEventSource<Record<string, string>>
): boolean {
const tgs = tags.data
let somethingChanged = false
for (const key in tgs) {
@ -295,7 +303,6 @@ class NormalizePanoramax extends SimpleMetaTagger {
this.addValue(key, tgs, parts)
somethingChanged = true
} else {
const postfix = key.match(/panoramax:([^:]+)(:[0-9]+)?/)?.[1]
if (postfix) {
this.addValue(key, tgs, parts, postfix)
@ -316,7 +323,7 @@ export default class SimpleMetaTaggers {
public static geometryType = new InlineMetaTagger(
{
keys: ["_geometry:type"],
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`"
doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`",
},
(feature) => {
const changed = feature.properties["_geometry:type"] === feature.geometry.type
@ -342,12 +349,12 @@ export default class SimpleMetaTaggers {
W: 270,
WNW: 292.5,
NW: 315,
NNW: 337.5
NNW: 337.5,
}
private static latlon = new InlineMetaTagger(
{
keys: ["_lat", "_lon"],
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)"
doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)",
},
(feature) => {
const centerPoint = GeoOperations.centerpoint(feature)
@ -362,7 +369,7 @@ export default class SimpleMetaTaggers {
{
doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.",
keys: ["_layer"],
includesDates: false
includesDates: false,
},
(feature, layer) => {
if (feature.properties._layer === layer.id) {
@ -378,11 +385,11 @@ export default class SimpleMetaTaggers {
"sidewalk:left",
"sidewalk:right",
"generic_key:left:property",
"generic_key:right:property"
"generic_key:right:property",
],
doc: "Rewrites tags from 'generic_key:both:property' as 'generic_key:left:property' and 'generic_key:right:property' (and similar for sidewalk tagging). Note that this rewritten tags _will be reuploaded on a change_. To prevent to much unrelated retagging, this is only enabled if the layer has at least some lineRenderings with offset defined",
includesDates: false,
cleanupRetagger: true
cleanupRetagger: true,
},
(feature, layer) => {
if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) {
@ -396,7 +403,7 @@ export default class SimpleMetaTaggers {
{
keys: ["_surface"],
doc: "The surface area of the feature in square meters. Not set on points and ways",
isLazy: true
isLazy: true,
},
(feature) => {
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
@ -414,7 +421,7 @@ export default class SimpleMetaTaggers {
{
keys: ["_surface:ha"],
doc: "The surface area of the feature in hectare. Not set on points and ways",
isLazy: true
isLazy: true,
},
(feature) => {
if (feature.geometry.type !== "Polygon" && feature.geometry.type !== "MultiPolygon") {
@ -432,7 +439,7 @@ export default class SimpleMetaTaggers {
private static levels = new InlineMetaTagger(
{
doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
keys: ["_level"]
keys: ["_level"],
},
(feature) => {
let somethingChanged = false
@ -467,7 +474,7 @@ export default class SimpleMetaTaggers {
private static canonicalize = new InlineMetaTagger(
{
doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)",
keys: ["Theme-defined keys"]
keys: ["Theme-defined keys"],
},
(feature, _, __, state) => {
const units = Utils.NoNull(
@ -524,7 +531,7 @@ export default class SimpleMetaTaggers {
private static lngth = new InlineMetaTagger(
{
keys: ["_length", "_length:km"],
doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter"
doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter",
},
(feature) => {
const l = GeoOperations.lengthInMeters(feature)
@ -540,7 +547,7 @@ export default class SimpleMetaTaggers {
keys: ["_isOpen"],
doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
includesDates: true,
isLazy: true
isLazy: true,
},
(feature) => {
if (Utils.runningFromConsole) {
@ -579,8 +586,8 @@ export default class SimpleMetaTaggers {
lon: lon,
address: {
country_code: tags._country.toLowerCase(),
state: undefined
}
state: undefined,
},
},
<any>{ tag_key: "opening_hours" }
)
@ -592,14 +599,14 @@ export default class SimpleMetaTaggers {
delete tags._isOpen
tags["_isOpen"] = "parse_error"
}
}
},
})
}
)
private static directionSimplified = new InlineMetaTagger(
{
keys: ["_direction:numerical", "_direction:leftright"],
doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map"
doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map",
},
(feature) => {
const tags = feature.properties
@ -624,7 +631,7 @@ export default class SimpleMetaTaggers {
{
keys: ["_direction:centerpoint"],
isLazy: true,
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint."
doc: "_direction:centerpoint is the direction of the linestring (in degrees) if one were standing at the projected centerpoint.",
},
(feature: Feature) => {
if (feature.geometry.type !== "LineString") {
@ -647,7 +654,7 @@ export default class SimpleMetaTaggers {
delete feature.properties["_direction:centerpoint"]
feature.properties["_direction:centerpoint"] = bearing
return bearing
}
},
})
return true
@ -657,7 +664,7 @@ export default class SimpleMetaTaggers {
{
keys: ["_now:date", "_now:datetime"],
doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely",
includesDates: true
includesDates: true,
},
(feature) => {
const now = new Date()
@ -681,7 +688,7 @@ export default class SimpleMetaTaggers {
keys: ["_last_edit:passed_time"],
doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first",
isLazy: true,
includesDates: true
includesDates: true,
},
(feature) => {
Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => {
@ -700,7 +707,7 @@ export default class SimpleMetaTaggers {
{
keys: ["_currency"],
doc: "Adds the currency valid for the object, based on country or explicit tagging. Can be a single currency or a semicolon-separated list of currencies. Empty if no currency is found.",
isLazy: true
isLazy: true,
},
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
if (tagsStore === undefined) {
@ -742,7 +749,6 @@ export default class SimpleMetaTaggers {
}
)
public static metatags: SimpleMetaTagger[] = [
SimpleMetaTaggers.latlon,
SimpleMetaTaggers.layerInfo,
@ -762,7 +768,7 @@ export default class SimpleMetaTaggers {
SimpleMetaTaggers.referencingWays,
SimpleMetaTaggers.timeSinceLastEdit,
SimpleMetaTaggers.currency,
SimpleMetaTaggers.normalizePanoramax
SimpleMetaTaggers.normalizePanoramax,
]
/**
@ -844,8 +850,8 @@ export default class SimpleMetaTaggers {
[
"Metatags are extra tags available, in order to display more data or to give better questions.",
"They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object"
].join("\n")
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object",
].join("\n"),
]
subElements.push("## Metatags calculated by MapComplete")

View file

@ -182,7 +182,9 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
let testingDefaultValue = false
if (
!Constants.osmAuthConfig.url.startsWith("https://master.apis.dev.openstreetmap.org") && !Utils.runningFromConsole && location.hostname === "127.0.0.1"
!Constants.osmAuthConfig.url.startsWith("https://master.apis.dev.openstreetmap.org") &&
!Utils.runningFromConsole &&
location.hostname === "127.0.0.1"
) {
testingDefaultValue = true
}

View file

@ -25,7 +25,7 @@ export class GeoLocationState {
* 'denied' means that we don't have access
*/
public readonly permission: UIEventSource<GeolocationPermissionState> = new UIEventSource(
"prompt",
"prompt"
)
/**
@ -130,7 +130,7 @@ export class GeoLocationState {
}
return Translations.t.general.waitingForLocation
},
[this.allowMoving, this.permission, this.currentGPSLocation],
[this.allowMoving, this.permission, this.currentGPSLocation]
)
}
@ -165,15 +165,15 @@ export class GeoLocationState {
return
}
if(AndroidPolyfill.inAndroid.data){
if (AndroidPolyfill.inAndroid.data) {
this.permission.setData("requested")
this.permission.addCallbackAndRunD(p => {
if(p === "granted"){
this.permission.addCallbackAndRunD((p) => {
if (p === "granted") {
this.startWatching()
return true
}
})
AndroidPolyfill.requestGeoPermission().then(state => {
AndroidPolyfill.requestGeoPermission().then((state) => {
const granted = state.value === "true"
this.permission.set(granted ? "granted" : "denied")
})
@ -218,15 +218,14 @@ export class GeoLocationState {
* @private
*/
private async startWatching() {
if(AndroidPolyfill.inAndroid.data){
AndroidPolyfill.watchLocation( this.currentGPSLocation, location => {
if (AndroidPolyfill.inAndroid.data) {
AndroidPolyfill.watchLocation(this.currentGPSLocation, (location) => {
console.log(JSON.stringify(location))
})
}
navigator.geolocation.watchPosition(
(position: GeolocationPosition) => {
(position: GeolocationPosition) => {
this._gpsAvailable.set(true)
this.currentGPSLocation.setData(position.coords)
this._previousLocationGrant.setData(true)
@ -249,5 +248,4 @@ export class GeoLocationState {
}
)
}
}

View file

@ -350,7 +350,9 @@ export default class UserRelatedState {
* List of all hidden themes that have been seen before
* @param osmConnection
*/
public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<undefined | string[]> {
public static initDiscoveredHiddenThemes(
osmConnection: OsmConnection
): Store<undefined | string[]> {
const prefix = "mapcomplete-hidden-theme-"
const userPreferences = osmConnection.preferencesHandler.allPreferences
return userPreferences.mapD((preferences) =>

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

@ -7,7 +7,9 @@ import { Store, UIEventSource } from "../UIEventSource"
import { OsmConnection } from "../Osm/OsmConnection"
export interface DatabridgePlugin {
request<T extends (string | object) = string | object>(options: { key: string }): Promise<{ value: T }>;
request<T extends string | object = string | object>(options: {
key: string
}): Promise<{ value: T }>
}
const DatabridgePluginSingleton = registerPlugin<DatabridgePlugin>("Databridge", {
@ -28,17 +30,25 @@ export class AndroidPolyfill {
private static readonly databridgePlugin: DatabridgePlugin = DatabridgePluginSingleton
private static readonly _inAndroid: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public static readonly inAndroid: Store<boolean> = AndroidPolyfill._inAndroid
private static readonly _geolocationPermission: UIEventSource<"granted" | "denied" | "prompt"> = new UIEventSource("prompt")
public static readonly geolocationPermission: Store<"granted" | "denied" | "prompt"> = this._geolocationPermission
private static readonly _geolocationPermission: UIEventSource<"granted" | "denied" | "prompt"> =
new UIEventSource("prompt")
public static readonly geolocationPermission: Store<"granted" | "denied" | "prompt"> =
this._geolocationPermission
/**
* Registers 'navigator.'
* @private
*/
private static backfillGeolocation(databridgePlugin: DatabridgePlugin) {
const src = UIEventSource.FromPromise(databridgePlugin.request({ key: "location:has-permission" }))
src.addCallbackAndRunD(permission => {
console.log("> Checking geopermission gave: ", JSON.stringify(permission), permission.value)
const src = UIEventSource.FromPromise(
databridgePlugin.request({ key: "location:has-permission" })
)
src.addCallbackAndRunD((permission) => {
console.log(
"> Checking geopermission gave: ",
JSON.stringify(permission),
permission.value
)
const granted = permission.value === "true"
AndroidPolyfill._geolocationPermission.set(granted ? "granted" : "denied")
})
@ -61,17 +71,25 @@ export class AndroidPolyfill {
}
public static async requestLoginCodes() {
const result = await DatabridgePluginSingleton.request<{ oauth_token: string }>({ key: "request:login" })
const result = await DatabridgePluginSingleton.request<{ oauth_token: string }>({
key: "request:login",
})
const token: string = result.value.oauth_token
console.log("AndroidPolyfill: received oauth_token; trying to pass them to the oauth lib", token)
console.log(
"AndroidPolyfill: received oauth_token; trying to pass them to the oauth lib",
token
)
return token
}
public static onBackButton(callback: () => boolean, options: {
returnToIndex: Store<boolean>
}) {
public static onBackButton(
callback: () => boolean,
options: {
returnToIndex: Store<boolean>
}
) {
console.log("Registering back button callback", callback)
DatabridgePluginSingleton.request({ key: "backbutton" }).then(ev => {
DatabridgePluginSingleton.request({ key: "backbutton" }).then((ev) => {
console.log("AndroidPolyfill: received backbutton: ", ev)
if (ev === null) {
// Probably in web environment
@ -88,28 +106,38 @@ export class AndroidPolyfill {
window.location.href = "/"
}
})
}
public static watchLocation(writeInto: UIEventSource<GeolocationCoordinates>, callback: (location) => void) {
public static watchLocation(
writeInto: UIEventSource<GeolocationCoordinates>,
callback: (location) => void
) {
DatabridgePluginSingleton.request({
key: "location:watch",
}).then((l: {
value: { latitude: number, longitude: number, accuraccy: number, altidude: number, heading: number, speed:number }
}) => {
// example l: {"value":{"latitude":51.0618627,"longitude":3.730468566666667,"accuracy":2.0393495559692383,"altitude":46.408,"heading":168.2969970703125}}
console.log("Received location from Android:", JSON.stringify(l))
const loc = l.value
writeInto.set({
latitude: loc.latitude,
longitude: loc.longitude,
heading: loc.heading,
accuracy: loc.accuraccy,
altitude: loc.altidude,
altitudeAccuracy: undefined,
speed: loc.speed,
})
})
}).then(
(l: {
value: {
latitude: number
longitude: number
accuraccy: number
altidude: number
heading: number
speed: number
}
}) => {
// example l: {"value":{"latitude":51.0618627,"longitude":3.730468566666667,"accuracy":2.0393495559692383,"altitude":46.408,"heading":168.2969970703125}}
console.log("Received location from Android:", JSON.stringify(l))
const loc = l.value
writeInto.set({
latitude: loc.latitude,
longitude: loc.longitude,
heading: loc.heading,
accuracy: loc.accuraccy,
altitude: loc.altidude,
altitudeAccuracy: undefined,
speed: loc.speed,
})
}
)
}
}

View file

@ -2,22 +2,21 @@
* Various tools and types to work with the community index (https://openstreetmap.community/; https://github.com/osmlab/osm-community-index)
*/
export interface CommunityResource {
/**
* A unique identifier for the resource
* "pattern": "^[-_.A-Za-z0-9]+$"
*/
id: string,
id: string
/**
* Type of community resource (thus: platform)
*/
type: string,
type: string
/**
* included and excluded locations for this item
* See location-conflation documentation for compatible values: https://github.com/rapideditor/location-conflation#readme
*/
locationSet?,
locationSet?
/** Array of ISO-639-1 (2 letter) or ISO-639-3 (3 letter) codes in lowercase
* */
@ -27,7 +26,5 @@ export interface CommunityResource {
*/
account?: string
resolved?: { url: string, name: string, description: string } & Record<string, string>
resolved?: { url: string; name: string; description: string } & Record<string, string>
}

View file

@ -36,14 +36,14 @@ export default class ThemeViewStateHashActor {
*
*/
constructor(state: {
featureSwitches: {featureSwitchBackToThemeOverview: Store<boolean>}
indexedFeatures: IndexedFeatureSource,
selectedElement: UIEventSource<Feature>,
guistate: MenuState,
featureSwitches: { featureSwitchBackToThemeOverview: Store<boolean> }
indexedFeatures: IndexedFeatureSource
selectedElement: UIEventSource<Feature>
guistate: MenuState
}) {
this._state = state
AndroidPolyfill.onBackButton(() => this.back(), {
returnToIndex: state.featureSwitches.featureSwitchBackToThemeOverview
returnToIndex: state.featureSwitches.featureSwitchBackToThemeOverview,
})
const hashOnLoad = Hash.hash.data
@ -81,7 +81,6 @@ export default class ThemeViewStateHashActor {
// When all is done, set the hash. This must happen last to give the code above correct info
this.setHash()
}
/**