Furhter improvements to velopark: better icons, improvements to loading
This commit is contained in:
parent
5a48a2e19c
commit
8bcc8820ac
10 changed files with 560 additions and 381 deletions
|
@ -25,40 +25,33 @@
|
||||||
"defaultBackgroundId": "photo",
|
"defaultBackgroundId": "photo",
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"builtin": "maproulette_challenge",
|
"id": "velopark_maproulette",
|
||||||
"override": {
|
"description": "Maproulette challenge containing velopark data",
|
||||||
"=name": {
|
"source": {
|
||||||
|
"osmTags": "mr_taskId~*",
|
||||||
|
"geoJson": "https://maproulette.org/api/v2/challenge/view/43282",
|
||||||
|
"isOsmCache": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"render": "Velopark parking <b>{mr_velopark_id}</b>"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
"en": "Velopark data",
|
"en": "Velopark data",
|
||||||
"nl": "Velopark data"
|
"nl": "Velopark data"
|
||||||
},
|
},
|
||||||
"=filter": [
|
"titleIcons": [
|
||||||
{
|
{
|
||||||
"id": "created-only",
|
"id": "maproulette",
|
||||||
"options": [
|
"render": "<a href='https://maproulette.org/challenge/{mr_challengeId}/task/{mr_taskId}' target='_blank'><img src='./assets/layers/maproulette/logomark.svg'/></a>"
|
||||||
{
|
|
||||||
"question": {
|
|
||||||
"en": "Only unfinished tasks",
|
|
||||||
"nl": "Enkel onafgewerkte taken"
|
|
||||||
},
|
|
||||||
"osmTags": "mr_taskStatus=Created",
|
|
||||||
"default": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"calculatedTags+": [
|
"tagRenderings": [
|
||||||
"mr_velopark_id=feat.properties['ref:velopark']?.split('/')?.at(-1)",
|
{
|
||||||
"_nearby_bicycle_parkings=closestn(feat)(['bike_parking','bike_parking_with_velopark_ref'], 100, undefined, 25)",
|
"id": "velopark-id-display",
|
||||||
"_nearby_bicycle_parkings:count=get(feat)('_nearby_bicycle_parkings').length",
|
"render": {
|
||||||
"_nearby_bicycle_parkings:props=get(feat)('_nearby_bicycle_parkings').map(f => ({_distance: Math.round(f.distance), _ref: feat.properties['ref:velopark'], _mr_id: feat.properties.id, '_velopark:id': (f.feat.properties['_velopark:id'] ?? 'unlinked') /*Explicit copy to trigger lazy loading*/, ...f.feat.properties}))"
|
"*": "<span class='literal-code'>{ref:velopark}</span>"
|
||||||
],
|
}
|
||||||
"=title": {
|
|
||||||
"render": "Velopark parking <b>{mr_velopark_id}</b>"
|
|
||||||
},
|
},
|
||||||
"source": {
|
|
||||||
"geoJson": "https://maproulette.org/api/v2/challenge/view/43282"
|
|
||||||
},
|
|
||||||
"=tagRenderings": [
|
|
||||||
{
|
{
|
||||||
"id": "velopark-link",
|
"id": "velopark-link",
|
||||||
"render": {
|
"render": {
|
||||||
|
@ -146,14 +139,72 @@
|
||||||
"en": "Mark this item as incorrect (duplicate, does not exist anymore, contradictory data)",
|
"en": "Mark this item as incorrect (duplicate, does not exist anymore, contradictory data)",
|
||||||
"nl": "Markeer dit object als incorrect (duplicaatin, incorrect of tegenstrijdige data, ...)"
|
"nl": "Markeer dit object als incorrect (duplicaatin, incorrect of tegenstrijdige data, ...)"
|
||||||
},
|
},
|
||||||
"image": "bug",
|
"image": "invalid",
|
||||||
"status": 6
|
"status": 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"{nearby_images()}"
|
"{nearby_images(open,readonly)}"
|
||||||
|
],
|
||||||
|
"lineRendering": [],
|
||||||
|
"filter": [
|
||||||
|
{
|
||||||
|
"id": "created-only",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"question": {
|
||||||
|
"en": "Only unfinished tasks",
|
||||||
|
"nl": "Enkel onafgewerkte taken"
|
||||||
|
},
|
||||||
|
"osmTags": "mr_taskStatus=Created",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "too-hard-only",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"question": {
|
||||||
|
"en": "Only too-hard tasks",
|
||||||
|
"nl": "Enkel foutieve taken"
|
||||||
|
},
|
||||||
|
"osmTags": "mr_taskStatus=Too_hard" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"calculatedTags": [
|
||||||
|
"mr_velopark_id=feat.properties['ref:velopark']?.split('/')?.at(-1)",
|
||||||
|
"_nearby_bicycle_parkings=closestn(feat)(['bike_parking','bike_parking_with_velopark_ref'], 100, undefined, 25)",
|
||||||
|
"_nearby_bicycle_parkings:count=get(feat)('_nearby_bicycle_parkings').length",
|
||||||
|
"_nearby_bicycle_parkings:props=get(feat)('_nearby_bicycle_parkings').map(f => ({_distance: Math.round(f.distance), _ref: feat.properties['ref:velopark'], _mr_id: feat.properties.id, '_velopark:id': (f.feat.properties['_velopark:id'] ?? 'unlinked') /*Explicit copy to trigger lazy loading*/, ...f.feat.properties}))"
|
||||||
|
],
|
||||||
|
"pointRendering": [
|
||||||
|
{
|
||||||
|
"location": [
|
||||||
|
"point",
|
||||||
|
"centroid"
|
||||||
|
],
|
||||||
|
"marker": [
|
||||||
|
{
|
||||||
|
"icon": "square_rounded",
|
||||||
|
"color": "#ffffff88"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/themes/velopark/velopark.svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"iconSize": "40,40",
|
||||||
|
"anchor": "bottom",
|
||||||
|
"iconBadges": [{
|
||||||
|
"if": "mr_taskStatus=Too_Hard",
|
||||||
|
"then": "invalid"
|
||||||
|
},{
|
||||||
|
"if": "mr_taskStatus=Fixed",
|
||||||
|
"then": "confirm"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"builtin": [
|
"builtin": [
|
||||||
|
|
|
@ -82,6 +82,7 @@ function genImages(dryrun = false) {
|
||||||
"SocialImageForeground",
|
"SocialImageForeground",
|
||||||
"speech_bubble_black_outline",
|
"speech_bubble_black_outline",
|
||||||
"square",
|
"square",
|
||||||
|
"square_rounded",
|
||||||
"star",
|
"star",
|
||||||
"star_half",
|
"star_half",
|
||||||
"star_outline",
|
"star_outline",
|
||||||
|
|
|
@ -2,15 +2,17 @@ import Script from "../Script"
|
||||||
import { Utils } from "../../src/Utils"
|
import { Utils } from "../../src/Utils"
|
||||||
import VeloparkLoader, { VeloparkData } from "../../src/Logic/Web/VeloparkLoader"
|
import VeloparkLoader, { VeloparkData } from "../../src/Logic/Web/VeloparkLoader"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import OverpassFeatureSource from "../../src/Logic/FeatureSource/Sources/OverpassFeatureSource"
|
|
||||||
import { Overpass } from "../../src/Logic/Osm/Overpass"
|
import { Overpass } from "../../src/Logic/Osm/Overpass"
|
||||||
import { RegexTag } from "../../src/Logic/Tags/RegexTag"
|
import { RegexTag } from "../../src/Logic/Tags/RegexTag"
|
||||||
import Constants from "../../src/Models/Constants"
|
import Constants from "../../src/Models/Constants"
|
||||||
import { ImmutableStore } from "../../src/Logic/UIEventSource"
|
import { ImmutableStore } from "../../src/Logic/UIEventSource"
|
||||||
import { BBox } from "../../src/Logic/BBox"
|
import { BBox } from "../../src/Logic/BBox"
|
||||||
|
|
||||||
class VeloParkToGeojson extends Script {
|
class VeloParkToGeojson extends Script {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory")
|
super(
|
||||||
|
"Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async main(args: string[]): Promise<void> {
|
async main(args: string[]): Promise<void> {
|
||||||
|
@ -19,37 +21,48 @@ class VeloParkToGeojson extends Script {
|
||||||
const url = "https://www.velopark.be/api/parkings/1000"
|
const url = "https://www.velopark.be/api/parkings/1000"
|
||||||
const data = <VeloparkData[]>await Utils.downloadJson(url)
|
const data = <VeloparkData[]>await Utils.downloadJson(url)
|
||||||
|
|
||||||
const bboxBelgium = new BBox([[2.51357303225, 49.5294835476],[ 6.15665815596, 51.4750237087]])
|
const bboxBelgium = new BBox([
|
||||||
const alreadyLinkedQuery = new Overpass(new RegexTag("ref:velopark", /.+/),
|
[2.51357303225, 49.5294835476],
|
||||||
|
[6.15665815596, 51.4750237087],
|
||||||
|
])
|
||||||
|
const alreadyLinkedQuery = new Overpass(
|
||||||
|
new RegexTag("ref:velopark", /.+/),
|
||||||
[],
|
[],
|
||||||
Constants.defaultOverpassUrls[0],
|
Constants.defaultOverpassUrls[0],
|
||||||
new ImmutableStore(60 * 5),
|
new ImmutableStore(60 * 5),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
const alreadyLinkedFeatures = await alreadyLinkedQuery.queryGeoJson(bboxBelgium)
|
const alreadyLinkedFeatures = await alreadyLinkedQuery.queryGeoJson(bboxBelgium)
|
||||||
const seenIds = new Set<string>(alreadyLinkedFeatures[0].features.map(f => f.properties["ref:velopark"]))
|
const seenIds = new Set<string>(
|
||||||
const features = data.map(f => VeloparkLoader.convert(f))
|
alreadyLinkedFeatures[0].features.map((f) => f.properties["ref:velopark"])
|
||||||
.filter(f => !seenIds.has(f.properties["ref:velopark"]))
|
)
|
||||||
|
console.log("OpenStreetMap contains", seenIds.size, "bicycle parkings with a velopark ref")
|
||||||
|
const allVelopark = data.map((f) => VeloparkLoader.convert(f))
|
||||||
|
const features = allVelopark.filter((f) => !seenIds.has(f.properties["ref:velopark"]))
|
||||||
|
|
||||||
const allProperties = new Set<string>()
|
const allProperties = new Set<string>()
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
Object.keys(feature.properties).forEach(k => allProperties.add(k))
|
Object.keys(feature.properties).forEach((k) => allProperties.add(k))
|
||||||
}
|
}
|
||||||
allProperties.delete("ref:velopark")
|
allProperties.delete("ref:velopark")
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
allProperties.forEach(k => {
|
allProperties.forEach((k) => {
|
||||||
delete feature.properties[k]
|
delete feature.properties[k]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync("velopark_id_only_export_" + new Date().toISOString() + ".geojson", JSON.stringify({
|
fs.writeFileSync(
|
||||||
"type": "FeatureCollection",
|
"velopark_id_only_export_" + new Date().toISOString() + ".geojson",
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
type: "FeatureCollection",
|
||||||
features,
|
features,
|
||||||
}, null, " "))
|
},
|
||||||
|
null,
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new VeloParkToGeojson().run()
|
new VeloParkToGeojson().run()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Feature, Geometry, Point } from "geojson"
|
import { Feature, Geometry } from "geojson"
|
||||||
import { OH } from "../../UI/OpeningHours/OpeningHours"
|
import { OH } from "../../UI/OpeningHours/OpeningHours"
|
||||||
import EmailValidator from "../../UI/InputElement/Validators/EmailValidator"
|
import EmailValidator from "../../UI/InputElement/Validators/EmailValidator"
|
||||||
import PhoneValidator from "../../UI/InputElement/Validators/PhoneValidator"
|
import PhoneValidator from "../../UI/InputElement/Validators/PhoneValidator"
|
||||||
|
@ -12,39 +12,49 @@ import { Utils } from "../../Utils"
|
||||||
* Reads a velopark-json, converts it to a geojson
|
* Reads a velopark-json, converts it to a geojson
|
||||||
*/
|
*/
|
||||||
export default class VeloparkLoader {
|
export default class VeloparkLoader {
|
||||||
|
|
||||||
private static readonly emailReformatting = new EmailValidator()
|
private static readonly emailReformatting = new EmailValidator()
|
||||||
private static readonly phoneValidator = new PhoneValidator()
|
private static readonly phoneValidator = new PhoneValidator()
|
||||||
|
|
||||||
private static readonly coder = new CountryCoder(
|
private static readonly coder = new CountryCoder(
|
||||||
Constants.countryCoderEndpoint,
|
Constants.countryCoderEndpoint,
|
||||||
Utils.downloadJson,
|
Utils.downloadJson
|
||||||
)
|
)
|
||||||
|
|
||||||
public static convert(veloparkData: VeloparkData): Feature {
|
public static convert(veloparkData: VeloparkData): Feature {
|
||||||
|
console.log("Converting", veloparkData)
|
||||||
const properties: {
|
const properties: {
|
||||||
"ref:velopark":string,
|
"ref:velopark": string
|
||||||
"operator:email"?: string,
|
"operator:email"?: string
|
||||||
"operator:phone"?: string,
|
"operator:phone"?: string
|
||||||
fee?: string,
|
fee?: string
|
||||||
opening_hours?: string
|
opening_hours?: string
|
||||||
access?: string
|
access?: string
|
||||||
maxstay?: string
|
maxstay?: string
|
||||||
operator?: string
|
operator?: string
|
||||||
} = {
|
} = {
|
||||||
"ref:velopark": veloparkData["id"] ?? veloparkData["@id"]
|
"ref:velopark": veloparkData["id"] ?? veloparkData["@id"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const k of ["_id", "url", "dateModified", "name", "address"]) {
|
||||||
|
delete veloparkData[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
VeloparkLoader.cleanup(veloparkData["properties"])
|
||||||
|
VeloparkLoader.cleanupEmtpy(veloparkData)
|
||||||
|
|
||||||
properties.operator = veloparkData.operatedBy?.companyName
|
properties.operator = veloparkData.operatedBy?.companyName
|
||||||
|
|
||||||
if (veloparkData.contactPoint?.email) {
|
if (veloparkData.contactPoint?.email) {
|
||||||
properties["operator:email"] = VeloparkLoader.emailReformatting.reformat(veloparkData.contactPoint?.email)
|
properties["operator:email"] = VeloparkLoader.emailReformatting.reformat(
|
||||||
|
veloparkData.contactPoint?.email
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (veloparkData.contactPoint?.telephone) {
|
if (veloparkData.contactPoint?.telephone) {
|
||||||
properties["operator:phone"] = VeloparkLoader.phoneValidator.reformat(veloparkData.contactPoint?.telephone, () => "be")
|
properties["operator:phone"] = VeloparkLoader.phoneValidator.reformat(
|
||||||
|
veloparkData.contactPoint?.telephone,
|
||||||
|
() => "be"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
veloparkData.photos?.forEach((p, i) => {
|
veloparkData.photos?.forEach((p, i) => {
|
||||||
|
@ -52,130 +62,198 @@ export default class VeloparkLoader {
|
||||||
properties["image"] = p.image
|
properties["image"] = p.image
|
||||||
} else {
|
} else {
|
||||||
properties["image:" + i] = p.image
|
properties["image:" + i] = p.image
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let geometry = veloparkData.geometry
|
let geometry = veloparkData.geometry
|
||||||
for (const g of veloparkData["@graph"]) {
|
for (const g of veloparkData["@graph"]) {
|
||||||
|
VeloparkLoader.cleanup(g)
|
||||||
|
VeloparkLoader.cleanupEmtpy(g)
|
||||||
if (g.geo[0]) {
|
if (g.geo[0]) {
|
||||||
geometry = { type: "Point", coordinates: [g.geo[0].longitude, g.geo[0].latitude] }
|
geometry = { type: "Point", coordinates: [g.geo[0].longitude, g.geo[0].latitude] }
|
||||||
}
|
}
|
||||||
if (g.maximumParkingDuration?.endsWith("D") && g.maximumParkingDuration?.startsWith("P")) {
|
if (
|
||||||
const duration = g.maximumParkingDuration.substring(1, g.maximumParkingDuration.length - 1)
|
g.maximumParkingDuration?.endsWith("D") &&
|
||||||
|
g.maximumParkingDuration?.startsWith("P")
|
||||||
|
) {
|
||||||
|
const duration = g.maximumParkingDuration.substring(
|
||||||
|
1,
|
||||||
|
g.maximumParkingDuration.length - 1
|
||||||
|
)
|
||||||
properties.maxstay = duration + " days"
|
properties.maxstay = duration + " days"
|
||||||
}
|
}
|
||||||
properties.access = g.publicAccess ? "yes" : "no"
|
properties.access = g.publicAccess ? "yes" : "no"
|
||||||
const prefix = "http://schema.org/"
|
const prefix = "http://schema.org/"
|
||||||
if (g.openingHoursSpecification) {
|
if (g.openingHoursSpecification) {
|
||||||
const oh = OH.simplify(g.openingHoursSpecification.map(spec => {
|
const oh = OH.simplify(
|
||||||
const dayOfWeek = spec.dayOfWeek.substring(prefix.length, prefix.length + 2).toLowerCase()
|
g.openingHoursSpecification
|
||||||
|
.map((spec) => {
|
||||||
|
const dayOfWeek = spec.dayOfWeek
|
||||||
|
.substring(prefix.length, prefix.length + 2)
|
||||||
|
.toLowerCase()
|
||||||
const startHour = spec.opens
|
const startHour = spec.opens
|
||||||
const endHour = spec.closes === "23:59" ? "24:00" : spec.closes
|
const endHour = spec.closes === "23:59" ? "24:00" : spec.closes
|
||||||
const merged = OH.MergeTimes(OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour))
|
const merged = OH.MergeTimes(
|
||||||
|
OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour)
|
||||||
|
)
|
||||||
return OH.ToString(merged)
|
return OH.ToString(merged)
|
||||||
}).join("; "))
|
})
|
||||||
|
.join("; ")
|
||||||
|
)
|
||||||
properties.opening_hours = oh
|
properties.opening_hours = oh
|
||||||
}
|
}
|
||||||
if (g.priceSpecification?.[0]) {
|
if (g.priceSpecification?.[0]) {
|
||||||
properties.fee = g.priceSpecification[0].freeOfCharge ? "no" : "yes"
|
properties.fee = g.priceSpecification[0].freeOfCharge ? "no" : "yes"
|
||||||
}
|
}
|
||||||
|
const types = {
|
||||||
|
"https://data.velopark.be/openvelopark/terms#RegularBicycle": "_",
|
||||||
|
"https://data.velopark.be/openvelopark/terms#ElectricBicycle":
|
||||||
|
"capacity:electric_bicycle",
|
||||||
|
"https://data.velopark.be/openvelopark/terms#CargoBicycle": "capacity:cargo_bike",
|
||||||
}
|
}
|
||||||
|
let totalCapacity = 0
|
||||||
|
for (let i = (g.allows ?? []).length - 1; i >= 0; i--) {
|
||||||
|
const capacity = g.allows[i]
|
||||||
|
const type: string = capacity["@type"]
|
||||||
|
if (type === undefined) {
|
||||||
|
console.warn("No type found for", capacity.bicycleType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const count = capacity["amount"]
|
||||||
|
if (!isNaN(count)) {
|
||||||
|
totalCapacity += Number(count)
|
||||||
|
} else {
|
||||||
|
console.warn("Not a valid number while loading velopark data:", count)
|
||||||
|
}
|
||||||
|
if (type !== "_") {
|
||||||
|
// properties[type] = count
|
||||||
|
}
|
||||||
|
g.allows.splice(i, 1)
|
||||||
|
}
|
||||||
|
if (totalCapacity > 0) {
|
||||||
|
properties["capacity"] = totalCapacity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify(properties, null, " "))
|
||||||
|
|
||||||
return { type: "Feature", properties, geometry }
|
return { type: "Feature", properties, geometry }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static cleanup(data: any) {
|
||||||
|
if (!data?.attributes) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const k of ["NIS_CODE", "name_NL", "name_DE", "name_EN", "name_FR"]) {
|
||||||
|
delete data.attributes[k]
|
||||||
|
}
|
||||||
|
VeloparkLoader.cleanupEmtpy(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static cleanupEmtpy(data: any) {
|
||||||
|
for (const key in data) {
|
||||||
|
if (data[key] === null) {
|
||||||
|
delete data[key]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (Object.keys(data[key]).length === 0) {
|
||||||
|
delete data[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VeloparkData {
|
export interface VeloparkData {
|
||||||
geometry?: Geometry
|
geometry?: Geometry
|
||||||
"@context": any,
|
"@context": any
|
||||||
"@id": string // "https://data.velopark.be/data/NMBS_541",
|
"@id": string // "https://data.velopark.be/data/NMBS_541",
|
||||||
"@type": "BicycleParkingStation",
|
"@type": "BicycleParkingStation"
|
||||||
"dateModified": string,
|
dateModified: string
|
||||||
"identifier": number,
|
identifier: number
|
||||||
"name": [
|
name: [
|
||||||
{
|
{
|
||||||
"@value": string,
|
"@value": string
|
||||||
"@language": "nl"
|
"@language": "nl"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"ownedBy": {
|
ownedBy: {
|
||||||
"@id": string,
|
"@id": string
|
||||||
"@type": "BusinessEntity",
|
"@type": "BusinessEntity"
|
||||||
"companyName": string
|
companyName: string
|
||||||
},
|
}
|
||||||
"operatedBy": {
|
operatedBy: {
|
||||||
"@type": "BusinessEntity",
|
"@type": "BusinessEntity"
|
||||||
"companyName": string
|
companyName: string
|
||||||
},
|
}
|
||||||
"address": any,
|
address: any
|
||||||
"hasMap": any,
|
hasMap: any
|
||||||
"contactPoint": {
|
contactPoint: {
|
||||||
"@type": "ContactPoint",
|
"@type": "ContactPoint"
|
||||||
"email": string,
|
email: string
|
||||||
"telephone": string
|
telephone: string
|
||||||
},
|
}
|
||||||
"photos": {
|
photos: {
|
||||||
"@type": "Photograph",
|
"@type": "Photograph"
|
||||||
"image": string
|
image: string
|
||||||
}[],
|
}[]
|
||||||
"interactionService": {
|
interactionService: {
|
||||||
"@type": "WebSite",
|
"@type": "WebSite"
|
||||||
"url": string
|
url: string
|
||||||
},
|
}
|
||||||
/**
|
/**
|
||||||
* Contains various extra pieces of data, e.g. services or opening hours
|
* Contains various extra pieces of data, e.g. services or opening hours
|
||||||
*/
|
*/
|
||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@type": "https://data.velopark.be/openvelopark/terms#PublicBicycleParking",
|
"@type": "https://data.velopark.be/openvelopark/terms#PublicBicycleParking"
|
||||||
"openingHoursSpecification": {
|
openingHoursSpecification: {
|
||||||
"@type": "OpeningHoursSpecification",
|
"@type": "OpeningHoursSpecification"
|
||||||
/**
|
/**
|
||||||
* Ends with 'Monday', 'Tuesday', ...
|
* Ends with 'Monday', 'Tuesday', ...
|
||||||
*/
|
*/
|
||||||
"dayOfWeek": "http://schema.org/Monday"
|
dayOfWeek:
|
||||||
|
| "http://schema.org/Monday"
|
||||||
| "http://schema.org/Tuesday"
|
| "http://schema.org/Tuesday"
|
||||||
| "http://schema.org/Wednesday"
|
| "http://schema.org/Wednesday"
|
||||||
| "http://schema.org/Thursday"
|
| "http://schema.org/Thursday"
|
||||||
| "http://schema.org/Friday"
|
| "http://schema.org/Friday"
|
||||||
| "http://schema.org/Saturday"
|
| "http://schema.org/Saturday"
|
||||||
| "http://schema.org/Sunday",
|
| "http://schema.org/Sunday"
|
||||||
/**
|
/**
|
||||||
* opens: 00:00 and closes 23:59 for the entire day
|
* opens: 00:00 and closes 23:59 for the entire day
|
||||||
*/
|
*/
|
||||||
"opens": string,
|
opens: string
|
||||||
"closes": string
|
closes: string
|
||||||
}[],
|
}[]
|
||||||
/**
|
/**
|
||||||
* P30D = 30 days
|
* P30D = 30 days
|
||||||
*/
|
*/
|
||||||
"maximumParkingDuration": "P30D",
|
maximumParkingDuration: "P30D"
|
||||||
"publicAccess": true,
|
publicAccess: true
|
||||||
"totalCapacity": 110,
|
totalCapacity: 110
|
||||||
"allows": [
|
allows: [
|
||||||
{
|
{
|
||||||
"@type": "AllowedBicycle",
|
"@type": "AllowedBicycle"
|
||||||
/* TODO is cargo bikes etc also available?*/
|
/* TODO is cargo bikes etc also available?*/
|
||||||
"bicycleType": "https://data.velopark.be/openvelopark/terms#RegularBicycle",
|
bicycleType:
|
||||||
"bicyclesAmount": number
|
| string
|
||||||
|
| "https://data.velopark.be/openvelopark/terms#RegularBicycle"
|
||||||
|
bicyclesAmount: number
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"geo": [
|
geo: [
|
||||||
{
|
{
|
||||||
"@type": "GeoCoordinates",
|
"@type": "GeoCoordinates"
|
||||||
"latitude": number,
|
latitude: number
|
||||||
"longitude": number
|
longitude: number
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"priceSpecification": [
|
priceSpecification: [
|
||||||
{
|
{
|
||||||
"@type": "PriceSpecification",
|
"@type": "PriceSpecification"
|
||||||
"freeOfCharge": boolean
|
freeOfCharge: boolean
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class Constants {
|
||||||
"import_candidate",
|
"import_candidate",
|
||||||
"usersettings",
|
"usersettings",
|
||||||
"icons",
|
"icons",
|
||||||
"filters"
|
"filters",
|
||||||
] as const
|
] as const
|
||||||
/**
|
/**
|
||||||
* Layer IDs of layers which have special properties through built-in hooks
|
* Layer IDs of layers which have special properties through built-in hooks
|
||||||
|
@ -117,7 +117,9 @@ export default class Constants {
|
||||||
*/
|
*/
|
||||||
private static readonly _defaultPinIcons = [
|
private static readonly _defaultPinIcons = [
|
||||||
"pin",
|
"pin",
|
||||||
|
"bug",
|
||||||
"square",
|
"square",
|
||||||
|
"square_rounded",
|
||||||
"circle",
|
"circle",
|
||||||
"checkmark",
|
"checkmark",
|
||||||
"clock",
|
"clock",
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
|
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
|
||||||
{#each $images as image (image.pictureUrl)}
|
{#each $images as image (image.pictureUrl)}
|
||||||
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
||||||
<LinkableImage {tags} {image} {state} {lon} {lat} {feature} {layer} {linkable} />
|
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
|
||||||
</span>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
import Party from "../../assets/svg/Party.svelte"
|
import Party from "../../assets/svg/Party.svelte"
|
||||||
import AddSmall from "../../assets/svg/AddSmall.svelte"
|
import AddSmall from "../../assets/svg/AddSmall.svelte"
|
||||||
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
|
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
|
||||||
|
import Square_rounded from "../../assets/svg/Square_rounded.svelte"
|
||||||
|
import Bug from "../../assets/svg/Bug.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a single icon.
|
* Renders a single icon.
|
||||||
|
@ -50,6 +52,11 @@
|
||||||
<Pin {color} class={clss} />
|
<Pin {color} class={clss} />
|
||||||
{:else if icon === "square"}
|
{:else if icon === "square"}
|
||||||
<Square {color} class={clss} />
|
<Square {color} class={clss} />
|
||||||
|
{:else if icon === "square_rounded"}
|
||||||
|
<Square_rounded {color} class={clss} />
|
||||||
|
{:else if icon === "bug"}
|
||||||
|
<Bug {color} class={clss} />
|
||||||
|
|
||||||
{:else if icon === "circle"}
|
{:else if icon === "circle"}
|
||||||
<Circle {color} class={clss} />
|
<Circle {color} class={clss} />
|
||||||
{:else if icon === "checkmark"}
|
{:else if icon === "checkmark"}
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<Tr t={Translations.t.general.loading} />
|
<Tr t={Translations.t.general.loading} />
|
||||||
</Loading>
|
</Loading>
|
||||||
{:else if $status === Maproulette.STATUS_OPEN}
|
{:else if $status === Maproulette.STATUS_OPEN}
|
||||||
<button class="no-image-background w-full p-4" on:click={() => apply()}>
|
<button class="no-image-background w-full p-4 m-0" on:click={() => apply()}>
|
||||||
<Icon clss="w-8 h-8 mr-2" icon={image} />
|
<Icon clss="w-8 h-8 mr-2" icon={image} />
|
||||||
{message}
|
{message}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -3,7 +3,11 @@ import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import BaseUIElement from "./BaseUIElement"
|
import BaseUIElement from "./BaseUIElement"
|
||||||
import Title from "./Base/Title"
|
import Title from "./Base/Title"
|
||||||
import Table from "./Base/Table"
|
import Table from "./Base/Table"
|
||||||
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
import {
|
||||||
|
RenderingSpecification,
|
||||||
|
SpecialVisualization,
|
||||||
|
SpecialVisualizationState,
|
||||||
|
} from "./SpecialVisualization"
|
||||||
import { HistogramViz } from "./Popup/HistogramViz"
|
import { HistogramViz } from "./Popup/HistogramViz"
|
||||||
import { MinimapViz } from "./Popup/MinimapViz"
|
import { MinimapViz } from "./Popup/MinimapViz"
|
||||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||||
|
@ -94,6 +98,11 @@ class NearbyImageVis implements SpecialVisualization {
|
||||||
defaultValue: "closed",
|
defaultValue: "closed",
|
||||||
doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown",
|
doc: "Either `open` or `closed`. If `open`, then the image carousel will always be shown",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "readonly",
|
||||||
|
required: false,
|
||||||
|
doc: "If 'readonly', will not show the 'link'-button",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
docs =
|
docs =
|
||||||
"A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature"
|
"A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature"
|
||||||
|
@ -106,9 +115,10 @@ class NearbyImageVis implements SpecialVisualization {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const isOpen = args[0] === "open"
|
const isOpen = args[0] === "open"
|
||||||
|
const readonly = args[1] === "readonly"
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
return new SvelteUIElement(isOpen ? NearbyImages : NearbyImagesCollapsed, {
|
return new SvelteUIElement(isOpen ? NearbyImages : NearbyImagesCollapsed, {
|
||||||
tags,
|
tags,
|
||||||
|
@ -117,6 +127,7 @@ class NearbyImageVis implements SpecialVisualization {
|
||||||
lat,
|
lat,
|
||||||
feature,
|
feature,
|
||||||
layer,
|
layer,
|
||||||
|
linkable: !readonly,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +182,7 @@ class StealViz implements SpecialVisualization {
|
||||||
selectedElement: otherFeature,
|
selectedElement: otherFeature,
|
||||||
state,
|
state,
|
||||||
layer,
|
layer,
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
|
@ -179,8 +190,8 @@ class StealViz implements SpecialVisualization {
|
||||||
}
|
}
|
||||||
return new Combine(elements).SetClass("flex flex-col")
|
return new Combine(elements).SetClass("flex flex-col")
|
||||||
},
|
},
|
||||||
[state.indexedFeatures.featuresById],
|
[state.indexedFeatures.featuresById]
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +230,7 @@ export class QuestionViz implements SpecialVisualization {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const labels = args[0]
|
const labels = args[0]
|
||||||
?.split(";")
|
?.split(";")
|
||||||
|
@ -273,8 +284,10 @@ export default class SpecialVisualizations {
|
||||||
* templ.args[0] = "{email}"
|
* templ.args[0] = "{email}"
|
||||||
*/
|
*/
|
||||||
public static constructSpecification(
|
public static constructSpecification(
|
||||||
template: string | { special: Record<string, string | Record<string, string>> & { type: string } },
|
template:
|
||||||
extraMappings: SpecialVisualization[] = [],
|
| string
|
||||||
|
| { special: Record<string, string | Record<string, string>> & { type: string } },
|
||||||
|
extraMappings: SpecialVisualization[] = []
|
||||||
): RenderingSpecification[] {
|
): RenderingSpecification[] {
|
||||||
if (template === "") {
|
if (template === "") {
|
||||||
return []
|
return []
|
||||||
|
@ -283,7 +296,7 @@ export default class SpecialVisualizations {
|
||||||
if (typeof template !== "string") {
|
if (typeof template !== "string") {
|
||||||
console.trace(
|
console.trace(
|
||||||
"Got a non-expanded template while constructing the specification, it still has a 'special-key':",
|
"Got a non-expanded template while constructing the specification, it still has a 'special-key':",
|
||||||
template,
|
template
|
||||||
)
|
)
|
||||||
throw "Got a non-expanded template while constructing the specification"
|
throw "Got a non-expanded template while constructing the specification"
|
||||||
}
|
}
|
||||||
|
@ -291,20 +304,20 @@ export default class SpecialVisualizations {
|
||||||
for (const knownSpecial of allKnownSpecials) {
|
for (const knownSpecial of allKnownSpecials) {
|
||||||
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||||
const matched = template.match(
|
const matched = template.match(
|
||||||
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s"),
|
new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s")
|
||||||
)
|
)
|
||||||
if (matched != null) {
|
if (matched != null) {
|
||||||
// We found a special component that should be brought to live
|
// We found a special component that should be brought to live
|
||||||
const partBefore = SpecialVisualizations.constructSpecification(
|
const partBefore = SpecialVisualizations.constructSpecification(
|
||||||
matched[1],
|
matched[1],
|
||||||
extraMappings,
|
extraMappings
|
||||||
)
|
)
|
||||||
const argument =
|
const argument =
|
||||||
matched[2] /* .trim() // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/
|
matched[2] /* .trim() // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/
|
||||||
const style = matched[3]?.substring(1) ?? ""
|
const style = matched[3]?.substring(1) ?? ""
|
||||||
const partAfter = SpecialVisualizations.constructSpecification(
|
const partAfter = SpecialVisualizations.constructSpecification(
|
||||||
matched[4],
|
matched[4],
|
||||||
extraMappings,
|
extraMappings
|
||||||
)
|
)
|
||||||
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
||||||
if (argument.length > 0) {
|
if (argument.length > 0) {
|
||||||
|
@ -350,7 +363,7 @@ export default class SpecialVisualizations {
|
||||||
defaultArg = "_empty string_"
|
defaultArg = "_empty string_"
|
||||||
}
|
}
|
||||||
return [arg.name, defaultArg, arg.doc]
|
return [arg.name, defaultArg, arg.doc]
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
new Title("Example usage of " + viz.funcName, 4),
|
new Title("Example usage of " + viz.funcName, 4),
|
||||||
|
@ -360,14 +373,14 @@ export default class SpecialVisualizations {
|
||||||
viz.funcName +
|
viz.funcName +
|
||||||
"(" +
|
"(" +
|
||||||
viz.args.map((arg) => arg.defaultValue).join(",") +
|
viz.args.map((arg) => arg.defaultValue).join(",") +
|
||||||
")}`",
|
")}`"
|
||||||
).SetClass("literal-code"),
|
).SetClass("literal-code"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HelpMessage() {
|
public static HelpMessage() {
|
||||||
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
||||||
SpecialVisualizations.DocumentationFor(viz),
|
SpecialVisualizations.DocumentationFor(viz)
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
|
@ -401,10 +414,10 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" ",
|
" "
|
||||||
),
|
)
|
||||||
).SetClass("code"),
|
).SetClass("code"),
|
||||||
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
|
'In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "argname": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)',
|
||||||
]).SetClass("flex flex-col"),
|
]).SetClass("flex flex-col"),
|
||||||
...helpTexts,
|
...helpTexts,
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col")
|
||||||
|
@ -413,7 +426,7 @@ export default class SpecialVisualizations {
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
public static renderExampleOfSpecial(
|
public static renderExampleOfSpecial(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
s: SpecialVisualization,
|
s: SpecialVisualization
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const examples =
|
const examples =
|
||||||
s.structuredExamples === undefined
|
s.structuredExamples === undefined
|
||||||
|
@ -424,7 +437,7 @@ export default class SpecialVisualizations {
|
||||||
new UIEventSource<Record<string, string>>(e.feature.properties),
|
new UIEventSource<Record<string, string>>(e.feature.properties),
|
||||||
e.args,
|
e.args,
|
||||||
e.feature,
|
e.feature,
|
||||||
undefined,
|
undefined
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
||||||
|
@ -466,7 +479,7 @@ export default class SpecialVisualizations {
|
||||||
assignTo: state.userRelatedState.language,
|
assignTo: state.userRelatedState.language,
|
||||||
availableLanguages: state.layout.language,
|
availableLanguages: state.layout.language,
|
||||||
preferredLanguages: state.osmConnection.userDetails.map(
|
preferredLanguages: state.osmConnection.userDetails.map(
|
||||||
(ud) => ud.languages,
|
(ud) => ud.languages
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -491,7 +504,7 @@ export default class SpecialVisualizations {
|
||||||
|
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
tagSource
|
||||||
|
@ -501,7 +514,7 @@ export default class SpecialVisualizations {
|
||||||
return new SplitRoadWizard(<WayId>id, state)
|
return new SplitRoadWizard(<WayId>id, state)
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -515,7 +528,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (feature.geometry.type !== "Point") {
|
if (feature.geometry.type !== "Point") {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -538,7 +551,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (!layer.deletion) {
|
if (!layer.deletion) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -566,7 +579,7 @@ export default class SpecialVisualizations {
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
return new SvelteUIElement(CreateNewNote, {
|
return new SvelteUIElement(CreateNewNote, {
|
||||||
|
@ -630,7 +643,7 @@ export default class SpecialVisualizations {
|
||||||
.map((tags) => tags[args[0]])
|
.map((tags) => tags[args[0]])
|
||||||
.map((wikidata) => {
|
.map((wikidata) => {
|
||||||
wikidata = Utils.NoEmpty(
|
wikidata = Utils.NoEmpty(
|
||||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
|
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
||||||
)[0]
|
)[0]
|
||||||
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -640,9 +653,9 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
const response = <WikidataResponse>e["success"]
|
const response = <WikidataResponse>e["success"]
|
||||||
return Translation.fromMap(response.labels)
|
return Translation.fromMap(response.labels)
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
new MapillaryLinkVis(),
|
new MapillaryLinkVis(),
|
||||||
|
@ -674,7 +687,7 @@ export default class SpecialVisualizations {
|
||||||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
||||||
tags,
|
tags,
|
||||||
state,
|
state,
|
||||||
feature,
|
feature
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -730,7 +743,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(StarsBarIcon, {
|
return new SvelteUIElement(StarsBarIcon, {
|
||||||
score: reviews.average,
|
score: reviews.average,
|
||||||
|
@ -763,7 +776,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -795,7 +808,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -853,7 +866,7 @@ export default class SpecialVisualizations {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): SvelteUIElement {
|
): SvelteUIElement {
|
||||||
const keyToUse = args[0]
|
const keyToUse = args[0]
|
||||||
const prefix = args[1]
|
const prefix = args[1]
|
||||||
|
@ -890,10 +903,10 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const allUnits: Unit[] = [].concat(
|
const allUnits: Unit[] = [].concat(
|
||||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []),
|
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
||||||
)
|
)
|
||||||
const unit = allUnits.filter((unit) =>
|
const unit = allUnits.filter((unit) =>
|
||||||
unit.isApplicableToKey(key),
|
unit.isApplicableToKey(key)
|
||||||
)[0]
|
)[0]
|
||||||
if (unit === undefined) {
|
if (unit === undefined) {
|
||||||
return value
|
return value
|
||||||
|
@ -901,7 +914,7 @@ export default class SpecialVisualizations {
|
||||||
const getCountry = () => tagSource.data._country
|
const getCountry = () => tagSource.data._country
|
||||||
const [v, denom] = unit.findDenomination(value, getCountry)
|
const [v, denom] = unit.findDenomination(value, getCountry)
|
||||||
return unit.asHumanLongValue(v, getCountry)
|
return unit.asHumanLongValue(v, getCountry)
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -918,7 +931,7 @@ export default class SpecialVisualizations {
|
||||||
new Combine([
|
new Combine([
|
||||||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||||
]).SetClass("flex flex-col"),
|
]).SetClass("flex flex-col")
|
||||||
)
|
)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
console.log("Exporting as Geojson")
|
console.log("Exporting as Geojson")
|
||||||
|
@ -931,7 +944,7 @@ export default class SpecialVisualizations {
|
||||||
title + "_mapcomplete_export.geojson",
|
title + "_mapcomplete_export.geojson",
|
||||||
{
|
{
|
||||||
mimetype: "application/vnd.geo+json",
|
mimetype: "application/vnd.geo+json",
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.SetClass("w-full")
|
.SetClass("w-full")
|
||||||
|
@ -967,7 +980,7 @@ export default class SpecialVisualizations {
|
||||||
constr: (state) => {
|
constr: (state) => {
|
||||||
return new SubtleButton(
|
return new SubtleButton(
|
||||||
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
||||||
Translations.t.general.removeLocationHistory,
|
Translations.t.general.removeLocationHistory
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
state.historicalUserLocations.features.setData([])
|
state.historicalUserLocations.features.setData([])
|
||||||
state.selectedElement.setData(undefined)
|
state.selectedElement.setData(undefined)
|
||||||
|
@ -1005,10 +1018,10 @@ export default class SpecialVisualizations {
|
||||||
.filter((c) => c.text !== "")
|
.filter((c) => c.text !== "")
|
||||||
.map(
|
.map(
|
||||||
(c, i) =>
|
(c, i) =>
|
||||||
new NoteCommentElement(c, state, i, comments.length),
|
new NoteCommentElement(c, state, i, comments.length)
|
||||||
),
|
)
|
||||||
).SetClass("flex flex-col")
|
).SetClass("flex flex-col")
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1049,9 +1062,9 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return new SubstitutedTranslation(title, tagsSource, state).SetClass(
|
return new SubstitutedTranslation(title, tagsSource, state).SetClass(
|
||||||
"px-1",
|
"px-1"
|
||||||
)
|
)
|
||||||
}),
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1067,8 +1080,8 @@ export default class SpecialVisualizations {
|
||||||
let challenge = Stores.FromPromise(
|
let challenge = Stores.FromPromise(
|
||||||
Utils.downloadJsonCached(
|
Utils.downloadJsonCached(
|
||||||
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
||||||
24 * 60 * 60 * 1000,
|
24 * 60 * 60 * 1000
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1093,7 +1106,7 @@ export default class SpecialVisualizations {
|
||||||
} else {
|
} else {
|
||||||
return [title, new List(listItems)]
|
return [title, new List(listItems)]
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
|
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
|
||||||
|
@ -1107,15 +1120,15 @@ export default class SpecialVisualizations {
|
||||||
"\n" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
" \"id\": \"mark_duplicate\",\n" +
|
' "id": "mark_duplicate",\n' +
|
||||||
" \"render\": {\n" +
|
' "render": {\n' +
|
||||||
" \"special\": {\n" +
|
' "special": {\n' +
|
||||||
" \"type\": \"maproulette_set_status\",\n" +
|
' "type": "maproulette_set_status",\n' +
|
||||||
" \"message\": {\n" +
|
' "message": {\n' +
|
||||||
" \"en\": \"Mark as not found or false positive\"\n" +
|
' "en": "Mark as not found or false positive"\n' +
|
||||||
" },\n" +
|
" },\n" +
|
||||||
" \"status\": \"2\",\n" +
|
' "status": "2",\n' +
|
||||||
" \"image\": \"close\"\n" +
|
' "image": "close"\n' +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
|
@ -1181,8 +1194,8 @@ export default class SpecialVisualizations {
|
||||||
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
||||||
return new StatisticsPanel(fsBboxed)
|
return new StatisticsPanel(fsBboxed)
|
||||||
},
|
},
|
||||||
[state.mapProperties.bounds],
|
[state.mapProperties.bounds]
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1248,7 +1261,7 @@ export default class SpecialVisualizations {
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[]
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
let [text, href, classnames, download, ariaLabel] = args
|
let [text, href, classnames, download, ariaLabel] = args
|
||||||
if (download === "") {
|
if (download === "") {
|
||||||
|
@ -1265,8 +1278,8 @@ export default class SpecialVisualizations {
|
||||||
download: Utils.SubstituteKeys(download, tags),
|
download: Utils.SubstituteKeys(download, tags),
|
||||||
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
||||||
newTab,
|
newTab,
|
||||||
}),
|
})
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1288,7 +1301,7 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" ",
|
" "
|
||||||
) +
|
) +
|
||||||
"\n```",
|
"\n```",
|
||||||
args: [
|
args: [
|
||||||
|
@ -1310,26 +1323,30 @@ export default class SpecialVisualizations {
|
||||||
featureTags.map((tags) => {
|
featureTags.map((tags) => {
|
||||||
try {
|
try {
|
||||||
const data = tags[key]
|
const data = tags[key]
|
||||||
const properties: object[] = typeof data === "string" ? JSON.parse(tags[key]) : data
|
const properties: object[] =
|
||||||
|
typeof data === "string" ? JSON.parse(tags[key]) : data
|
||||||
const elements = []
|
const elements = []
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
const subsTr = new SubstitutedTranslation(
|
const subsTr = new SubstitutedTranslation(
|
||||||
translation,
|
translation,
|
||||||
new UIEventSource<any>(property),
|
new UIEventSource<any>(property),
|
||||||
state,
|
state
|
||||||
)
|
)
|
||||||
elements.push(subsTr)
|
elements.push(subsTr)
|
||||||
}
|
}
|
||||||
return new List(elements)
|
return new List(elements)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Something went wrong while generating the elements for a multi", {
|
console.log(
|
||||||
|
"Something went wrong while generating the elements for a multi",
|
||||||
|
{
|
||||||
e,
|
e,
|
||||||
tags,
|
tags,
|
||||||
key,
|
key,
|
||||||
loaded: tags[key],
|
loaded: tags[key],
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}),
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1349,7 +1366,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource.map((tags) => {
|
tagSource.map((tags) => {
|
||||||
|
@ -1361,7 +1378,7 @@ export default class SpecialVisualizations {
|
||||||
console.error("Cannot create a translation for", v, "due to", e)
|
console.error("Cannot create a translation for", v, "due to", e)
|
||||||
return JSON.stringify(v)
|
return JSON.stringify(v)
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1381,7 +1398,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0]
|
const key = argument[0]
|
||||||
const validator = new FediverseValidator()
|
const validator = new FediverseValidator()
|
||||||
|
@ -1391,14 +1408,14 @@ export default class SpecialVisualizations {
|
||||||
.map((fediAccount) => {
|
.map((fediAccount) => {
|
||||||
fediAccount = validator.reformat(fediAccount)
|
fediAccount = validator.reformat(fediAccount)
|
||||||
const [_, username, host] = fediAccount.match(
|
const [_, username, host] = fediAccount.match(
|
||||||
FediverseValidator.usernameAtServer,
|
FediverseValidator.usernameAtServer
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(Link, {
|
return new SvelteUIElement(Link, {
|
||||||
text: fediAccount,
|
text: fediAccount,
|
||||||
url: "https://" + host + "/@" + username,
|
url: "https://" + host + "/@" + username,
|
||||||
newTab: true,
|
newTab: true,
|
||||||
})
|
})
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1418,7 +1435,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new FixedUiElement("{" + args[0] + "}")
|
return new FixedUiElement("{" + args[0] + "}")
|
||||||
},
|
},
|
||||||
|
@ -1439,7 +1456,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0] ?? "value"
|
const key = argument[0] ?? "value"
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1459,10 +1476,10 @@ export default class SpecialVisualizations {
|
||||||
"Could not parse this tag: " +
|
"Could not parse this tag: " +
|
||||||
JSON.stringify(value) +
|
JSON.stringify(value) +
|
||||||
" due to " +
|
" due to " +
|
||||||
e,
|
e
|
||||||
).SetClass("alert")
|
).SetClass("alert")
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1483,7 +1500,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const giggityUrl = argument[0]
|
const giggityUrl = argument[0]
|
||||||
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
||||||
|
@ -1499,12 +1516,12 @@ export default class SpecialVisualizations {
|
||||||
_: UIEventSource<Record<string, string>>,
|
_: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const tags = (<ThemeViewState>(
|
const tags = (<ThemeViewState>(
|
||||||
state
|
state
|
||||||
)).geolocation.currentUserLocation.features.map(
|
)).geolocation.currentUserLocation.features.map(
|
||||||
(features) => features[0]?.properties,
|
(features) => features[0]?.properties
|
||||||
)
|
)
|
||||||
return new Combine([
|
return new Combine([
|
||||||
new SvelteUIElement(OrientationDebugPanel, {}),
|
new SvelteUIElement(OrientationDebugPanel, {}),
|
||||||
|
@ -1526,7 +1543,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavourite, {
|
return new SvelteUIElement(MarkAsFavourite, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1546,7 +1563,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1566,7 +1583,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||||
},
|
},
|
||||||
|
@ -1581,7 +1598,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
tagSource
|
||||||
|
@ -1603,9 +1620,9 @@ export default class SpecialVisualizations {
|
||||||
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
||||||
`#${id}`
|
`#${id}`
|
||||||
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
||||||
"width: 75px",
|
"width: 75px"
|
||||||
)
|
)
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1625,7 +1642,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig,
|
layer: LayerConfig
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1636,11 +1653,11 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
.mapD((value) => {
|
.mapD((value) => {
|
||||||
const dir = GeoOperations.bearingToHuman(
|
const dir = GeoOperations.bearingToHuman(
|
||||||
GeoOperations.parseBearing(value),
|
GeoOperations.parseBearing(value)
|
||||||
)
|
)
|
||||||
console.log("Human dir", dir)
|
console.log("Human dir", dir)
|
||||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1666,11 +1683,17 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
name: "readonly",
|
name: "readonly",
|
||||||
required: false,
|
required: false,
|
||||||
doc: "If 'yes', will not show 'apply'-buttons"
|
doc: "If 'yes', will not show 'apply'-buttons",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
docs: "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM",
|
docs: "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM",
|
||||||
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
|
constr(
|
||||||
|
state: SpecialVisualizationState,
|
||||||
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
|
args: string[],
|
||||||
|
feature: Feature,
|
||||||
|
layer: LayerConfig
|
||||||
|
): BaseUIElement {
|
||||||
const url = args[0]
|
const url = args[0]
|
||||||
const postprocessVelopark = args[2] === "velopark"
|
const postprocessVelopark = args[2] === "velopark"
|
||||||
const readonly = args[3] === "yes"
|
const readonly = args[3] === "yes"
|
||||||
|
@ -1681,7 +1704,7 @@ export default class SpecialVisualizations {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
layer,
|
layer,
|
||||||
feature,
|
feature,
|
||||||
readonly
|
readonly,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1696,7 +1719,7 @@ export default class SpecialVisualizations {
|
||||||
throw (
|
throw (
|
||||||
"Invalid special visualisation found: funcName is undefined for " +
|
"Invalid special visualisation found: funcName is undefined for " +
|
||||||
invalid.map((sp) => sp.i).join(", ") +
|
invalid.map((sp) => sp.i).join(", ") +
|
||||||
". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
|
'. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
src/assets/svg/Square_rounded.svelte
Normal file
4
src/assets/svg/Square_rounded.svelte
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<script>
|
||||||
|
export let color = "#000000"
|
||||||
|
</script>
|
||||||
|
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown width="500.94501" height="500.94501" viewBox="0 0 500.94501 500.94501" version="1.1" id="svg5" sodipodi:docname="square_rounded.svg" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g id="surface1" transform="translate(0.4725,0.4725)" /> <path class="selectable" id="rect1" style="fill:{color}" d="m 75.4725,0.4725 h 350 c 41.55,0 75,33.45 75,75 v 350 c 0,41.55 -33.45,75 -75,75 h -350 c -41.55,0 -75,-33.45 -75,-75 v -350 c 0,-41.55 33.45,-75 75,-75 z" /> </svg>
|
Loading…
Add table
Reference in a new issue