Merge branch 'develop' into Robin-patch-1

This commit is contained in:
Robin van der Linde 2025-04-19 16:50:40 +02:00
commit 217d206a06
Signed by untrusted user: Robin-van-der-Linde
GPG key ID: 53956B3252478F0D
23 changed files with 327 additions and 769698 deletions

View file

@ -401,6 +401,13 @@
"uk": "Кронштейн з можливістю використання замка через вушко. Підсідельна труба може утримуватися на стійці за допомогою анкера",
"ca": "Un suport inclinat amb possibilitat d'utilitzar un cadenat a través de l'ullet. El tub del seient es pot sostenir al suport amb un anclatge"
}
},
{
"if": "bicycle_parking=anchors",
"then": {
"en": "An anchor - a metal loop wide enough for a bike lock attached to a wall, the floor or a boulder.",
"nl": "Een anker - een metalen lus waar een fietsslot door kan en vastgemaakt aan de muur of vloer"
}
}
],
"id": "Bicycle parking type"

View file

@ -102,7 +102,13 @@
"render": "#000000",
"mappings": [
{
"if": "maxspeed=",
"if": {
"and": [
"maxspeed=",
"maxspeed:forward=",
"maxspeed:backward="
]
},
"then": "#ff0000"
}
]
@ -112,6 +118,7 @@
],
"tagRenderings": [
{
"id": "maxspeed-maxspeed",
"render": {
"en": "The maximum allowed speed on this road is {canonical(maxspeed)}",
"de": "Die zulässige Höchstgeschwindigkeit auf dieser Straße beträgt {canonical(maxspeed)}",
@ -135,7 +142,10 @@
},
"freeform": {
"key": "maxspeed",
"type": "pnat"
"type": "pnat",
"addExtraTags": [
"_forward_backward=no"
]
},
"mappings": [
{
@ -162,9 +172,70 @@
"maxspeed=20"
],
"hideInAnswer": "_country!=be"
},
{
"if": {
"and": [
"maxspeed=",
"_forward_backward=yes"
]
},
"then": {
"en": "The maximum allowed speed on this road depends on the direction a vehicle goes"
}
}
],
"id": "maxspeed-maxspeed"
"condition": {
"or": [
"maxspeed~*",
{
"and": [
"maxspeed:forward=",
"maxspeed:backward="
]
}
]
}
},
{
"id": "maxspeed-forward",
"condition": {
"or": [
"maxspeed:backward~*",
"maxspeed:forward~*",
"_forward_backward=yes"
]
},
"question": {
"en": "What is the maximum allowed speed when travelling {direction_absolute()}?"
},
"render": {
"en": "The maximum allowed speed when travelling {direction_absolute()} on this road is {canonical(maxspeed:forward)}"
},
"freeform": {
"key": "maxspeed:forward",
"type": "pnat"
}
},
{
"id": "maxspeed-backward",
"condition": {
"or": [
"maxspeed:forward~*",
"maxspeed:backward~*",
"_forward_backward=yes"
]
},
"question": {
"en": "What is the maximum allowed speed when travelling {direction_absolute(,180)}?"
},
"render": {
"en": "The maximum allowed speed when travelling {direction_absolute(,180)} on this road is {canonical(maxspeed:backward)}"
},
"freeform": {
"key": "maxspeed:backward",
"type": "pnat"
}
}
],
"allowMove": false,
@ -177,6 +248,20 @@
"denominations": [
"mph"
]
},
"maxspeed:forward": {
"quantity": "speed",
"canonical": "kmh",
"denominations": [
"mph"
]
},
"maxspeed:backward": {
"quantity": "speed",
"canonical": "kmh",
"denominations": [
"mph"
]
}
}
]

View file

@ -58,7 +58,6 @@
"ko": "감시 카메라 및 기타 감시 수단"
},
"icon": "./assets/themes/surveillance/logo.svg",
"defaultBackgroundId": "maptiler.carto",
"layers": [
"surveillance_camera",
{

View file

@ -391,6 +391,7 @@
"recentThemes": "Recently visited maps",
"recents": "Recently seen places",
"search": "Search a location",
"searchShort": "Search…",
"searching": "Searching…"
},
"searchAnswer": "Search an option",

View file

@ -7542,6 +7542,65 @@
"render": "Memorial plaque"
}
},
"mobility_hub": {
"description": "Mobility hubs are places where different kinds of transit meet, making it easy to switch between them. These places are usually part of a larger network or system.",
"name": "Mobility Hubs",
"presets": {
"0": {
"description": "A mobility hub which is marked by a physical sign, usually with a logo.",
"title": "a mobility hub"
}
},
"tagRenderings": {
"name": {
"freeform": {
"placeholder": "Name of the mobility hub"
},
"question": "What is the name of this mobility hub?",
"render": "This mobility hub is called {name}"
},
"network": {
"freeform": {
"placeholder": "Network for this mobility hub"
},
"mappings": {
"0": {
"then": "This mobility hub belongs to the Groningen-Drenthe network"
},
"1": {
"then": "This mobility hub belongs to the Hoppin network"
},
"2": {
"then": "This mobility hub belongs to the Jelbi network"
}
},
"question": "To which network does this mobility hub belong to?",
"render": "This mobility hub belongs to the network {network}"
},
"physical_marker": {
"mappings": {
"0": {
"then": "This mobility hub is marked by a board, containing information about the hub"
},
"1": {
"then": "This mobility hub is marked by a sign with an electronic display"
},
"2": {
"then": "This mobility hub is marked by a simple sign showing only basic information like the logo or name"
}
},
"question": "What kind of physical marker is used to mark this mobility hub?"
}
},
"title": {
"mappings": {
"0": {
"then": "Mobility hub {name}"
}
},
"render": "Mobility hub"
}
},
"mountain_rescue": {
"description": "A building where first aid responders store material and might be on watch",
"name": "Mountain rescue stations",

View file

@ -6566,6 +6566,65 @@
}
}
},
"mobility_hub": {
"description": "Mobiliteitshubs zijn plaatsen waar verschillende soorten vervoer bij elkaar komen, waardoor het makkelijk is om te wisselen van vervoer. Deze plaatsen maken meestal deel uit van een groter netwerk of systeem.",
"name": "Mobiliteitshubs",
"presets": {
"0": {
"description": "Een mobiliteitshub die gemarkeerd is door een fysiek bord, meestal met een logo.",
"title": "een mobiliteitshub"
}
},
"tagRenderings": {
"name": {
"freeform": {
"placeholder": "Naam van de mobiliteitshub"
},
"question": "Wat is de naam van deze mobiliteitshub?",
"render": "Deze mobiliteitshub heet {name}"
},
"network": {
"freeform": {
"placeholder": "Netwerk van deze mobiliteitshub"
},
"mappings": {
"0": {
"then": "Deze mobiliteitshub hoort bij het netwerk Groningen-Drenthe"
},
"1": {
"then": "Deze mobiliteitshub hoort bij het Hoppin netwerk"
},
"2": {
"then": "Deze mobiliteitshub hoort bij het Jelbi netwerk"
}
},
"question": "Bij welk netwerk hoort deze mobiliteitshub?",
"render": "Deze mobiliteitshub hoort bij het netwerk {network}"
},
"physical_marker": {
"mappings": {
"0": {
"then": "Deze mobiliteitshub is gemarkeerd door een bord, met informatie over de hub"
},
"1": {
"then": "Deze mobiliteitshub is gemarkeerd door een bord met een elektronisch display"
},
"2": {
"then": "Deze mobiliteitshub is gemarkeerd door een eenvoudig bord met alleen simpele informatie zoals het logo of de naam"
}
},
"question": "Wat voor fysieke markering is er voor deze mobiliteitshub?"
}
},
"title": {
"mappings": {
"0": {
"then": "Mobiliteitshub {name}"
}
},
"render": "Mobiliteitshub"
}
},
"nature_reserve": {
"description": "Een natuurgebied is een gebied waar actief ruimte gemaakt word voor de natuur. Typisch zijn deze in beheer van Natuurpunt of het Agentschap Natuur en Bos of zijn deze erkend door de overheid.",
"filter": {

File diff suppressed because it is too large Load diff

View file

@ -362,11 +362,7 @@ class NsiLogos extends Script {
private async patchNsiFile() {
const files = NsiLogos.downloadedFiles()
let path = "./public/assets/data/nsi/nsi.min.json"
const otherPath = "./assets/data/nsi/nsi.min.json"
if (existsSync(otherPath) && !existsSync(path)) {
path = otherPath
}
const path = "./public/assets/data/nsi/nsi.min.json"
const nsi = JSON.parse(readFileSync(path, "utf8"))
const types = nsi.nsi

View file

@ -21,24 +21,8 @@ class ServerLdScrape extends Script {
/* {
"User-Agent": "MapComplete/openstreetmap scraper; pietervdvn@posteo.net; https://source.mapcomplete.org/MapComplete",
"accept": "application/html"
},
{
Host: host,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:122.0) Gecko/20100101 Firefox/122.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,* /*;q=0.8", TODO remove space in * /*
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Alt-Used": host,
DNT: 1,
"Sec-GPC": 1,
"Upgrade-Insecure-Requests": 1,
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "cross-site",
"Sec-Fetch-User":"?1",
"TE": "trailers",
Connection: "keep-alive"
}*/
},*/
]
for (let i = 0; i < headers.length; i++) {
try {

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: number = 6378137
@ -1051,6 +1051,8 @@ export class GeoOperations {
}
/**
* Converts various types of possible bearings into degrees, with 0 being north, 90 being east.
*
* GeoOperations.parseBearing("N") // => 0
* GeoOperations.parseBearing("E") // => 90
* GeoOperations.parseBearing("NE") // => 45
@ -1064,7 +1066,7 @@ export class GeoOperations {
* GeoOperations.parseBearing(-270) // => 90
*
*/
public static parseBearing(str: string | number) {
public static parseBearing(str: string | number): number {
let n: number
if (typeof str === "string") {
str = str.trim()

View file

@ -11,15 +11,14 @@ import ChangeTagAction from "./ChangeTagAction"
import { And } from "../../Tags/And"
import { Utils } from "../../../Utils"
import { OsmConnection } from "../OsmConnection"
import { Feature } from "@turf/turf"
import { Geometry, LineString, Point } from "geojson"
import { Feature, Geometry, LineString, Point } from "geojson"
import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
export default class ReplaceGeometryAction extends OsmChangeAction implements PreviewableAction {
/**
* The target feature - mostly used for the metadata
*/
private readonly feature: any
private readonly feature: Feature
private readonly state: {
osmConnection: OsmConnection
fullNodeDatabase?: FullNodeDatabaseSource
@ -48,7 +47,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
osmConnection: OsmConnection
fullNodeDatabase?: FullNodeDatabaseSource
},
feature: any,
feature: Feature,
wayToReplaceId: string,
options: {
theme: string
@ -65,13 +64,13 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
const geom = this.feature.geometry
let coordinates: [number, number][]
if (geom.type === "LineString") {
coordinates = geom.coordinates
coordinates = <[number, number][]>geom.coordinates
} else if (geom.type === "Polygon") {
coordinates = geom.coordinates[0]
coordinates = <[number, number][]>geom.coordinates[0]
}
this.targetCoordinates = coordinates
this.identicalTo = coordinates.map((_) => undefined)
this.identicalTo = coordinates.map(() => undefined)
for (let i = 0; i < coordinates.length; i++) {
if (this.identicalTo[i] !== undefined) {
@ -204,7 +203,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
if (nodeDb === undefined) {
throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)"
}
const self = this
let parsed: OsmObject[]
{
// Gather the needed OsmObjects
@ -214,6 +212,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
if (idN < 0 || type !== "way") {
throw "Invalid ID to conflate: " + this.wayToReplaceId
}
const url = `${
this.state.osmConnection?._oauth_config?.url ?? "https://api.openstreetmap.org"
}/api/0.6/${this.wayToReplaceId}/full`
@ -270,7 +269,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
const partOfSomeWay = parentWayIds.length > 0
const hasTags = Object.keys(node.tags).length > 1
const nodeDistances = this.targetCoordinates.map((_) => undefined)
const nodeDistances = this.targetCoordinates.map(() => undefined)
for (let i = 0; i < this.targetCoordinates.length; i++) {
if (this.identicalTo[i] !== undefined) {
continue
@ -295,7 +294,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
})
}
const closestIds = this.targetCoordinates.map((_) => undefined)
const closestIds = this.targetCoordinates.map(() => undefined)
const unusedIds = new Map<
number,
{
@ -330,7 +329,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
// We found a candidate... Search the corresponding target id:
let targetId: number = undefined
let lowestDistance = Number.MAX_VALUE
let nodeDistances = distances.get(candidate)
const nodeDistances = distances.get(candidate)
for (let i = 0; i < nodeDistances.length; i++) {
const d = nodeDistances[i]
if (d !== undefined && d < lowestDistance) {
@ -400,7 +399,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
properties: {},
geometry: {
type: "LineString",
coordinates: self.targetCoordinates,
coordinates: this.targetCoordinates
},
}
const projected = GeoOperations.nearestPoint(way, [node.lon, node.lat])
@ -528,7 +527,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
// Some nodes might need to be deleted
if (detachedNodes.size > 0) {
detachedNodes.forEach(({ hasTags, reason }, nodeId) => {
detachedNodes.forEach(({ hasTags }, nodeId) => {
const parentWays = nodeDb.GetParentWays(nodeId)
const index = parentWays.data.map((w) => w.id).indexOf(osmWay.id)
if (index < 0) {

View file

@ -233,7 +233,6 @@ export class Changes {
...addSource(DeleteAction.metatags, "DeleteAction"),
// TODO
/*
...DeleteAction.metatags,
...LinkImageAction.metatags,
...OsmChangeAction.metatags,
...RelationSplitHandler.metatags,
@ -446,6 +445,10 @@ export class Changes {
// Apply tag changes
for (const kv of change.tags ?? []) {
const k = kv.k
if (k.startsWith("_")) {
// These values are ignored later on anyways
continue
}
let v = kv.v
if (v === "") {

View file

@ -149,16 +149,14 @@ export class ValidateTheme extends DesugaringStep<ThemeConfigJson> {
}
if (json.defaultBackgroundId) {
/*
TODO re-enable this check
const backgroundId = json.defaultBackgroundId
const isCategory =
backgroundId === "photo" || backgroundId === "map" || backgroundId === "osmbasedmap"
if (!isCategory && !ValidateTheme._availableLayers.has(backgroundId)) {
const options = Array.from(ValidateTheme._availableLayers)
const nearby = Utils.sortedByLevenshteinDistance(backgroundId, options, (t) => t)
const knownIds = Array.from(AvailableRasterLayers.allAvailableGlobalLayers).map(l => l.properties.id)
const available = new Set(knownIds)
if (!isCategory && !available.has(backgroundId)) {
const nearby = Utils.sortedByLevenshteinDistance(backgroundId, knownIds, (t) => t)
context
.enter("defaultBackgroundId")
.err(
@ -166,7 +164,7 @@ export class ValidateTheme extends DesugaringStep<ThemeConfigJson> {
.slice(0, 5)
.join(", ")}`,
)
}*/
}
}
for (let i = 0; i < theme.layers.length; i++) {

View file

@ -363,7 +363,7 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
addAll(mappingKey, neededKeys)
}
neededKeys.delete("fixme") // fixme gets a free pass
neededKeys.delete("fixme") // gets a free pass
if (json.freeform) {
for (const neededKey of neededKeys) {

View file

@ -35,7 +35,7 @@ export interface DeleteConfigJson {
*/
explanation: string | any
/**
* The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion
* The text that will be uploaded into the changeset or will be used in the `fix me` in case of a soft deletion
* Should be a few words, in english
*
* question: What should be added to the changeset as delete reason?

View file

@ -46,7 +46,7 @@
<label
class="neutral-label normal-background box-shadow flex w-full items-center rounded-full border border-black"
>
<SearchIcon aria-hidden="true" class="ml-2 h-8 w-8" />
<SearchIcon aria-hidden="true" class="ml-2 h-6 w-6 shrink-0" />
<input
bind:this={inputElement}
@ -59,8 +59,11 @@
type="search"
style=" --tw-ring-color: rgb(0 0 0 / 0) !important;"
class="ml-1 w-full border-none px-0 outline-none"
on:keypress={(keypr) => {
return keypr.key === "Enter" ? dispatch("search") : undefined
on:keypress={(event) => {
if( event.key === "Enter"){
dispatch("search")
event.preventDefault()
}
}}
bind:value={_value}
use:set_placeholder={placeholder}
@ -79,5 +82,6 @@
{:else}
<div class="mr-3 w-6" />
{/if}
<slot name="button-right" />
</label>
</form>

View file

@ -64,7 +64,7 @@
undefined
)
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
export let mapProperties: Partial<MapProperties> & { location } = {
zoom: new UIEventSource<number>(19),
maxbounds: new UIEventSource(undefined),

View file

@ -9,7 +9,8 @@ import FilteredLayer from "../../../Models/FilteredLayer"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../../Models/ThemeConfig/Json/LayerConfigJson"
import conflation_json from "../../../../assets/layers/conflation/conflation.json"
import ThemeViewState from "../../../Models/ThemeViewState"
import { SpecialVisualizationState } from "../../SpecialVisualization"
import { OsmTags } from "../../../Models/OsmFeature"
export interface ImportFlowArguments {
readonly text: string
@ -65,7 +66,7 @@ ${Utils.special_visualizations_importRequirementDocs}
* Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point,
*/
public static getTagsToApply(
originalFeatureTags: UIEventSource<any>,
originalFeatureTags: UIEventSource<OsmTags>,
args: { tags: string }
): Store<Tag[]> {
if (originalFeatureTags === undefined) {
@ -109,9 +110,7 @@ ${Utils.special_visualizations_importRequirementDocs}
* Others (e.g.: snapOnto-layers) are not to be handled here
*/
public static getLayerDependencies(argsRaw: string[], argSpec?): string[] {
const args: ImportFlowArguments = <any>(
Utils.ParseVisArgs(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
)
const args = Utils.ParseVisArgs<ImportFlowArguments>(argSpec ?? ImportFlowUtils.generalArguments, argsRaw)
return args.targetLayer.split(" ")
}
@ -139,14 +138,14 @@ ${Utils.special_visualizations_importRequirementDocs}
* This class works together closely with ImportFlow.svelte
*/
export default abstract class ImportFlow<ArgT extends ImportFlowArguments> {
public readonly state: ThemeViewState
public readonly state: SpecialVisualizationState
public readonly args: ArgT
public readonly targetLayer: FilteredLayer[]
public readonly tagsToApply: Store<Tag[]>
protected readonly _originalFeatureTags: UIEventSource<Record<string, string>>
constructor(
state: ThemeViewState,
state: SpecialVisualizationState,
args: ArgT,
tagsToApply: Store<Tag[]>,
originalTags: UIEventSource<Record<string, string>>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { MoveReason } from "./MoveWizardState"
import { MoveWizardState } from "./MoveWizardState"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
@ -15,11 +15,18 @@
import Constants from "../../Models/Constants"
import LoginToggle from "../Base/LoginToggle.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
import ThemeViewState from "../../Models/ThemeViewState"
import Icon from "../Map/Icon.svelte"
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
import type { WayId } from "../../Models/OsmFeature"
import Searchbar from "../Base/Searchbar.svelte"
import { NominatimGeocoding } from "../../Logic/Search/NominatimGeocoding"
import Loading from "../Base/Loading.svelte"
import { Map as MlMap } from "maplibre-gl"
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
export let state: ThemeViewState
@ -37,8 +44,8 @@
let snappedTo = new UIEventSource<WayId | undefined>(undefined)
function initMapProperties(reason: MoveReason) {
return <any>{
function initMapProperties(reason: MoveReason): Partial<MapProperties> & { location } {
return {
allowMoving: new UIEventSource(true),
allowRotating: new UIEventSource(false),
allowZooming: new UIEventSource(true),
@ -55,7 +62,30 @@
reason.setData(moveWizardState.reasons[0])
}
let notAllowed = moveWizardState.moveDisallowedReason
let currentMapProperties: MapProperties = undefined
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD(r => initMapProperties(r))
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let searchValue = new UIEventSource<string>("")
let isSearching = new UIEventSource<boolean>(false)
const searcher = new NominatimGeocoding(1)
async function searchPressed() {
const v = searchValue.data
console.log("Search is pressed", v)
isSearching.set(true)
const result: GeocodeResult[] = await searcher.search(v)
isSearching.set(false)
if (result.length == 0 || currentMapProperties.data === undefined) {
return
}
const loc = result[0]
console.log("Found", result, "flying")
map.data.flyTo({ zoom: 18, center: [loc.lon, loc.lat] })
}
</script>
{#if moveWizardState.reasons.length > 0}
@ -94,9 +124,23 @@
</button>
{/each}
{:else if currentStep === "pick_location" || currentStep === "reason"}
{#if $reason.includeSearch}
<Searchbar value={searchValue} on:search={() => searchPressed()}>
<svelte:fragment slot="button-right">
{#if $isSearching}
<Loading />
{:else}
<button class="primary" class:disabled={$searchValue.length === 0} on:click={() => searchPressed()}>
<Tr t={Translations.t.general.search.searchShort} />
</button>
{/if}
</svelte:fragment>
</Searchbar>
{/if}
<div class="relative h-64 w-full">
<NewPointLocationInput
mapProperties={(currentMapProperties = initMapProperties($reason))}
{map}
mapProperties={$currentMapProperties}
value={newLocation}
{state}
coordinate={{ lon, lat }}
@ -112,13 +156,9 @@
</div>
</div>
{#if $reason.includeSearch}
<!-- TODO -->
{/if}
<div class="flex flex-wrap">
<If
condition={currentMapProperties.zoom.mapD(
condition={$currentMapProperties.zoom.mapD(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)}
>

View file

@ -14,6 +14,8 @@
</script>
{#if tags?.length > 0}
<div class="flex gap-x-4">
{#each tags as tag}
<div class="break-words" style="word-break: break-word">
{#if tag["value"] === ""}
@ -25,6 +27,7 @@
{/if}
</div>
{/each}
</div>
{:else}
<slot name="no-tags">
<Tr cls="subtle" t={Translations.t.general.noTagsSelected} />

View file

@ -137,7 +137,6 @@
}
unseenFreeformValues.splice(index, 1)
}
// TODO this has _to much_ values
freeformInput.set(unseenFreeformValues.join(";"))
if (checkedMappings.length + 1 < mappings.length) {
checkedMappings.push(unseenFreeformValues.length > 0)

View file

@ -1,11 +1,7 @@
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
import { default as FeatureTitle } from "./Popup/Title.svelte"
import {
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz"
@ -40,8 +36,11 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
import TagrenderingManipulationSpecialVisualisations
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
import {
WebAndCommunicationSpecialVisualisations
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"
@ -524,6 +523,11 @@ export default class SpecialVisualizations {
doc: "The attribute containing the degrees",
defaultValue: "_direction:centerpoint",
},
{
name: "offset",
doc: "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction",
defaultValue: "0"
}
],
constr(
@ -532,6 +536,8 @@ export default class SpecialVisualizations {
args: string[]
): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
const offset = args[1] === "" ? 0 : Number(args[1])
return new VariableUiElement(
tagSource
.map((tags) => {
@ -540,7 +546,7 @@ export default class SpecialVisualizations {
})
.mapD((value) => {
const dir = GeoOperations.bearingToHuman(
GeoOperations.parseBearing(value)
GeoOperations.parseBearing(value) + offset
)
console.log("Human dir", dir)
return Translations.t.general.visualFeedback.directionsAbsolute[dir]

View file

@ -81,7 +81,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
/**
* Parses the arguments for special visualisations
*/
public static ParseVisArgs<T extends Record<string, string>>(
public static ParseVisArgs<T = Record<string, string>>(
specs: { name: string; defaultValue?: string }[],
args: string[]
): T {