Compare commits

...

20 commits

Author SHA1 Message Date
Pieter Vander Vennet
a16bfac530 Merge branch 'master' of github.com:pietervdvn/MapComplete
All checks were successful
Deploy develop on dev.mapcomplete.org / deploy_on_hosted (push) Successful in 33m33s
2024-12-17 15:07:23 +01:00
Pieter Vander Vennet
23ac96251c Fix: Add velopark link 2024-12-17 15:05:15 +01:00
Supaplex
bb09b77965 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (651 of 651 strings)

Translation: MapComplete/core
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/core/zh_Hant/
2024-12-17 10:01:44 +01:00
Weblate
84900e5cbb Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/
2024-12-17 04:40:11 +01:00
Weblate
0f11996ef7 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/themes
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/themes/
2024-12-17 04:40:02 +01:00
Pieter Vander Vennet
5b43316f10 Fix: tests 2024-12-17 04:39:38 +01:00
Pieter Vander Vennet
01cc2f4170 Merge branch 'master' of github.com:pietervdvn/MapComplete 2024-12-17 04:28:33 +01:00
Pieter Vander Vennet
39a98ed4a1 chore: automated housekeeping... 2024-12-17 04:23:24 +01:00
Weblate
7b9dd41083 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/layers
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/layers/
2024-12-17 04:15:44 +01:00
Weblate
916d9d77f9 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: MapComplete/themes
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/themes/
2024-12-17 04:15:22 +01:00
Pieter Vander Vennet
047d741b1d chore(release): 0.47.13 2024-12-17 04:15:12 +01:00
Pieter Vander Vennet
56efd11429 Merge branch 'develop' 2024-12-17 04:13:28 +01:00
Pieter Vander Vennet
926ea0b6e4 Fix: remove 'id' attribute 2024-12-17 04:13:05 +01:00
Pieter Vander Vennet
74618c2282 UX: fix image carousel in comparisonTable 2024-12-17 04:12:50 +01:00
Pieter Vander Vennet
b2915bdc4d UX: change styling of dotmenu to make it more visible 2024-12-17 04:12:31 +01:00
Pieter Vander Vennet
b272c1cda7 Fix: attributed image doesn't open fullscreen if no ID given 2024-12-17 04:06:03 +01:00
Pieter Vander Vennet
565e92a37d Fix: typing 2024-12-17 04:05:15 +01:00
Pieter Vander Vennet
ef1d2c9f56 Fix(linkeddata): velopark deals with sections, fix image loading 2024-12-17 03:31:28 +01:00
Supaplex
43ee589370 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 95.3% (621 of 651 strings)

Translation: MapComplete/core
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/core/zh_Hant/
2024-12-16 17:58:36 +01:00
Supaplex
60881d4252 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 90.9% (592 of 651 strings)

Translation: MapComplete/core
Translate-URL: https://translate.mapcomplete.org/projects/mapcomplete/core/zh_Hant/
2024-12-16 04:25:54 +01:00
43 changed files with 1569 additions and 1814 deletions

View file

@ -20,6 +20,6 @@
{"type": "perf", "hidden": true}, {"type": "perf", "hidden": true},
{"type": "test", "hidden": true} {"type": "test", "hidden": true}
], ],
"commitUrlFormat": "https://github.com/pietervdvn/mapcomplete/commits{{hash}}", "commitUrlFormat": "https://github.com/pietervdvn/mapcomplete/commits/{{hash}}",
"compareUrlFormat": "https://github.com/pietervdvn/mapcomplete/compare/{{previousTag}}...{{currentTag}}" "compareUrlFormat": "https://github.com/pietervdvn/mapcomplete/compare/{{previousTag}}...{{currentTag}}"
} }

View file

@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.47.13](https://github.com/pietervdvn/mapcomplete/compare/v0.47.12...v0.47.13) (2024-12-17)
### Bug Fixes
* attributed image doesn't open fullscreen if no ID given ([b272c1c](https://github.com/pietervdvn/mapcomplete/commits/b272c1cda7352a4a78e4f61dbd39fe2f80ae204d))
* don't report an unneeded error ([79314e1](https://github.com/pietervdvn/mapcomplete/commits/79314e1747ee74544975948ba35fb499c203a061))
* fix [#2309](https://github.com/pietervdvn/MapComplete/issues/2309) ([83a9184](https://github.com/pietervdvn/mapcomplete/commits/83a918477f24fd86bc2783e266ac59a4c687020f))
* fix [#2309](https://github.com/pietervdvn/MapComplete/issues/2309) ([fe2a6ca](https://github.com/pietervdvn/mapcomplete/commits/fe2a6ca065b8ace2f466a6cac549f6d84ee35044))
* fix changelog links ([0471fd7](https://github.com/pietervdvn/mapcomplete/commits/0471fd7779329da051400a62d16162bd21dc0324))
* fix possible failing upload (error report postmortem) ([22c348a](https://github.com/pietervdvn/mapcomplete/commits/22c348af27677cd97f124dc45f63276d91b40152))
* linked data vis for velopark does no longer rely on country codes ([c26b9ae](https://github.com/pietervdvn/mapcomplete/commits/c26b9ae7f7fa3de69d499451db14be61e4698721))
* **linkeddata:** velopark deals with sections, fix image loading ([ef1d2c9](https://github.com/pietervdvn/mapcomplete/commits/ef1d2c9f56fc07f87255c557b05daf5e0451fdc5))
* remove 'id' attribute ([926ea0b](https://github.com/pietervdvn/mapcomplete/commits/926ea0b6e46f9966d1b1b9a4157c6ca6708b82fa))
* server config (CORS) ([2c877dd](https://github.com/pietervdvn/mapcomplete/commits/2c877dd52532b24066907a5ae9676e26371e48f0))
* typing ([565e92a](https://github.com/pietervdvn/mapcomplete/commits/565e92a37dde751e21d7f3e2abc69fa57b27d30f))
### [0.47.12](https://github.com/pietervdvn/mapcomplete/compare/v0.47.11...v0.47.12) (2024-12-11) ### [0.47.12](https://github.com/pietervdvn/mapcomplete/compare/v0.47.11...v0.47.12) (2024-12-11)

View file

@ -273,7 +273,7 @@ This tagrendering is only visible in the popup if the following condition is met
### max_bolts ### max_bolts
The question is `How many bolts do routes in {title()} have at most?` The question is `How many bolts do routes in {title()} have at most?`
*The sport climbing routes here have at most {climbing:bolts:max} bolts.<div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>* is shown if `climbing:bolts:max` is set *The sport climbing routes here have at most {climbing:bolts:max} bolts. <div class='subtle'>This is without belay stations and indicates how much quickdraws a climber needs.</div>* is shown if `climbing:bolts:max` is set
### Speed climbing? ### Speed climbing?

View file

@ -80,7 +80,7 @@ The question is `What is the grade of this climbing route according to the frenc
### bolts ### bolts
The question is `How many bolts does this route have before reaching the anchor?` The question is `How many bolts does this route have before reaching the anchor?`
*This route has {climbing:bolts} bolts <div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>* is shown if `climbing:bolts` is set *This route has {climbing:bolts} bolts. <div class='subtle'>This is without belay stations and indicates how much quickdraws a climber needs.</div>* is shown if `climbing:bolts` is set
- *This route is not bolted* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:climbing:bolted' target='_blank'>climbing:bolted</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:climbing:bolted%3Dno' target='_blank'>no</a> - *This route is not bolted* is shown if with <a href='https://wiki.openstreetmap.org/wiki/Key:climbing:bolted' target='_blank'>climbing:bolted</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:climbing:bolted%3Dno' target='_blank'>no</a>

View file

@ -426,10 +426,11 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
| max_snap_distance | 5 | The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete | | max_snap_distance | 5 | The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete |
| note_id | _undefined_ | If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported' | | note_id | _undefined_ | If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported' |
| maproulette_id | _undefined_ | The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer. | | maproulette_id | _undefined_ | The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer. |
| to_point | _undefined_ | If set, a feature will be converted to a centerpoint |
#### Example usage of import_button #### Example usage of import_button
<code>`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,)}`</code> <code>`{import_button(,,Import this data into OpenStreetMap,./assets/svg/addSmall.svg,,5,,,)}`</code>
### import_way_button ### import_way_button

View file

@ -125,7 +125,7 @@ This tagrendering is only visible in the popup if the following condition is met
### uk_addresses_import_button ### uk_addresses_import_button
_This tagrendering has no question and is thus read-only_ _This tagrendering has no question and is thus read-only_
*{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,)}* *{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,,)}*
### leftover-questions ### leftover-questions

View file

@ -98,7 +98,7 @@ Maproulette challenge containing velopark data
This layer is loaded from an external source, namely This layer is loaded from an external source, namely
`https://maproulette.org/api/v2/challenge/view/43282` `https://maproulette.org/api/v2/challenge/view/50552`
No themes use this layer No themes use this layer
@ -176,7 +176,7 @@ This tagrendering is only visible in the popup if the following condition is met
### import_point ### import_point
_This tagrendering has no question and is thus read-only_ _This tagrendering has no question and is thus read-only_
*{import_button(bike_parking_with_velopark_ref bike_parking,amenity=bicycle_parking;ref:velopark=$ref:velopark,Create a new bicycle parking in OSM. This parking will have the link&COMMA you'll be able to copy the attributes in the next step,,,,,mr_taskId)}* *{import_button(bike_parking_with_velopark_ref bike_parking,amenity=bicycle_parking;ref:velopark=$ref:velopark,Create a new bicycle parking in OSM. This parking will have the link&COMMA you'll be able to copy the attributes in the next step,,,,,mr_taskId,yes)}*
This tagrendering is only visible in the popup if the following condition is met: <a href='https://wiki.openstreetmap.org/wiki/Key:mr_taskStatus' target='_blank'>mr_taskStatus</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:mr_taskStatus%3DCreated' target='_blank'>Created</a> This tagrendering is only visible in the popup if the following condition is met: <a href='https://wiki.openstreetmap.org/wiki/Key:mr_taskStatus' target='_blank'>mr_taskStatus</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:mr_taskStatus%3DCreated' target='_blank'>Created</a>

View file

@ -35,10 +35,10 @@ This document gives an overview of which URL-parameters can be used to influence
26. [background](#background) 26. [background](#background)
+ [Selecting a category](#selecting-a-category) + [Selecting a category](#selecting-a-category)
+ [Selecting a specific layer](#selecting-a-specific-layer) + [Selecting a specific layer](#selecting-a-specific-layer)
27. [z](#z) 27. [oauth_token](#oauth_token)
28. [lat](#lat) 28. [z](#z)
29. [lon](#lon) 29. [lat](#lat)
30. [oauth_token](#oauth_token) 30. [lon](#lon)
31. [layer-public_bookcase](#layer-public_bookcase) 31. [layer-public_bookcase](#layer-public_bookcase)
32. [filter-public_bookcase-kid-books](#filter-public_bookcase-kid-books) 32. [filter-public_bookcase-kid-books](#filter-public_bookcase-kid-books)
33. [filter-public_bookcase-adult-books](#filter-public_bookcase-adult-books) 33. [filter-public_bookcase-adult-books](#filter-public_bookcase-adult-books)
@ -334,11 +334,19 @@ This documentation is defined in the source code at [FeatureSwitchState.ts](/src
No default value set No default value set
## oauth_token
Used to complete the login
This documentation is defined in the source code at [ThemeViewState.ts](/src/Models/ThemeViewState.ts#L177)
No default value set
## z ## z
The initial/current zoom level The initial/current zoom level
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _1_ The default value is _1_
@ -346,7 +354,7 @@ The default value is _1_
The initial/current latitude The initial/current latitude
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _0_ The default value is _0_
@ -354,18 +362,10 @@ The default value is _0_
The initial/current longitude of the app The initial/current longitude of the app
This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L37) This documentation is defined in the source code at [InitialMapPositioning.ts](/src/Logic/Actors/InitialMapPositioning.ts#L39)
The default value is _0_ The default value is _0_
## oauth_token
Used to complete the login
This documentation is defined in the source code at [ThemeViewState.ts](/src/Models/ThemeViewState.ts#L189)
No default value set
## layer-public_bookcase ## layer-public_bookcase
Whether or not layer public_bookcase is shown Whether or not layer public_bookcase is shown
@ -410,7 +410,7 @@ The default value is _0_
The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics' The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'
This documentation is defined in the source code at [generateDocs.ts](ervdvn/git2/MapComplete/scripts/generateDocs.ts#L436) This documentation is defined in the source code at [generateDocs.ts](ervdvn/git/MapComplete/scripts/generateDocs.ts#L436)
The default value is _map_ The default value is _map_

View file

@ -17,7 +17,7 @@
"nl": "Een hulpmiddel om data van velopark.be in OpenStreetMap in te laden" "nl": "Een hulpmiddel om data van velopark.be in OpenStreetMap in te laden"
}, },
"descriptionTail": { "descriptionTail": {
"*": "<h3>Maintainer tools</h3><ul class='link-underline'><li><a target='_blank' href='https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/velopark.md'>See documentation and links to Overpass</a></li><li><a href='https://maproulette.org/api/v2/challenge/view/43282' download='Velopark_sync_2024-01-15.geojson'>Download the first sync results</a></li><li><a href='http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B%28%20%20%20%20nwr%5B%22amenity%22%3D%22bicycle_parking%22%5D%5B%22ref%3Avelopark%22%5D%28%7B%7Bbbox%7D%7D%29%3B%0A%29%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B' target='_blank'>See all bicycle parkings with a velopark ref</a>To export: visit this link, click 'run' and then 'export'; 'export as geojson'</ul>" "*": "<h3>Maintainer tools</h3><ul class='link-underline'><li><a target='_blank' href='https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/velopark.md'>See documentation and links to Overpass</a></li><li><a href='https://maproulette.org/api/v2/challenge/view/43282' download='Velopark_sync_2024-01-15.geojson'>Download the first batch results</a></li><li><a href='https://maproulette.org/api/v2/challenge/view/50552'>Download the second batch results</a></li><li><a href='http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B%28%20%20%20%20nwr%5B%22amenity%22%3D%22bicycle_parking%22%5D%5B%22ref%3Avelopark%22%5D%28%7B%7Bbbox%7D%7D%29%3B%0A%29%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B' target='_blank'>See all bicycle parkings with a velopark ref</a>To export: visit this link, click 'run' and then 'export'; 'export as geojson'</ul>"
}, },
"icon": "./assets/themes/velopark/velopark.svg", "icon": "./assets/themes/velopark/velopark.svg",
"startZoom": 18, "startZoom": 18,
@ -31,7 +31,7 @@
"description": "Maproulette challenge containing velopark data", "description": "Maproulette challenge containing velopark data",
"source": { "source": {
"osmTags": "mr_taskId~*", "osmTags": "mr_taskId~*",
"geoJson": "https://maproulette.org/api/v2/challenge/view/43282", "geoJson": "https://maproulette.org/api/v2/challenge/view/50552",
"idKey": "mr_taskId" "idKey": "mr_taskId"
}, },
"title": { "title": {
@ -161,6 +161,7 @@
"en": "Create a new bicycle parking in OSM. This parking will have the link, you'll be able to copy the attributes in the next step", "en": "Create a new bicycle parking in OSM. This parking will have the link, you'll be able to copy the attributes in the next step",
"nl": "Maak een nieuwe parking aan in OSM. Deze parking zal gelinkt zijn met Velopark en je kan in de volgende stap de attributen overzetten" "nl": "Maak een nieuwe parking aan in OSM. Deze parking zal gelinkt zijn met Velopark en je kan in de volgende stap de attributen overzetten"
}, },
"to_point": "yes",
"maproulette_id": "mr_taskId" "maproulette_id": "mr_taskId"
} }
} }
@ -238,7 +239,12 @@
} }
} }
], ],
"lineRendering": [], "lineRendering": [
{
"color": "#bb9922",
"lineWidth": 2
}
],
"filter": [ "filter": [
{ {
"id": "created-only", "id": "created-only",

View file

@ -2036,9 +2036,6 @@
}, },
"title": { "title": {
"mappings": { "mappings": {
"0": {
"then": "{name}"
},
"1": { "1": {
"then": "Vogelkijkhut {name}" "then": "Vogelkijkhut {name}"
}, },
@ -6343,11 +6340,6 @@
} }
}, },
"title": { "title": {
"mappings": {
"0": {
"then": "{name}"
}
},
"render": "Natuurgebied" "render": "Natuurgebied"
} }
}, },
@ -6879,21 +6871,6 @@
"render": "Picknicktafel" "render": "Picknicktafel"
} }
}, },
"play_forest": {
"description": "Een speelbos is een vrij toegankelijke zone in een bos",
"name": "Speelbossen",
"title": {
"mappings": {
"0": {
"then": "{name}"
},
"1": {
"then": "Speelbos {name}"
}
},
"render": "Speelbos"
}
},
"playground": { "playground": {
"deletion": { "deletion": {
"nonDeleteMappings": { "nonDeleteMappings": {
@ -8451,9 +8428,6 @@
}, },
"title": { "title": {
"mappings": { "mappings": {
"0": {
"then": "{name}"
},
"1": { "1": {
"then": "Voetpad" "then": "Voetpad"
}, },
@ -10606,25 +10580,13 @@
} }
}, },
"village_green": { "village_green": {
"description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)", "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)"
"name": "Speelweide",
"title": {
"mappings": {
"0": {
"then": "{name}"
}
},
"render": "Speelweide"
}
}, },
"visitor_information_centre": { "visitor_information_centre": {
"description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.", "description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.",
"name": "Bezoekerscentrum", "name": "Bezoekerscentrum",
"title": { "title": {
"mappings": { "mappings": {
"0": {
"then": "{name:nl}"
},
"1": { "1": {
"then": "{name}" "then": "{name}"
} }

View file

@ -653,37 +653,8 @@
"building type": { "building type": {
"question": "Wat voor soort gebouw is dit?" "question": "Wat voor soort gebouw is dit?"
}, },
"grb-fixme": {
"mappings": {
"0": {
"then": "Geen fixme"
}
},
"question": "Wat zegt de fixme?",
"render": "De fixme is <b>{fixme}</b>"
},
"grb-housenumber": {
"mappings": {
"0": {
"then": "Geen huisnummer"
}
},
"question": "Wat is het huisnummer?",
"render": "Het huisnummer is <b>{addr:housenumber}</b>"
},
"grb-min-level": {
"question": "Hoeveel verdiepingen ontbreken?",
"render": "Dit gebouw begint maar op de {building:min_level} verdieping"
},
"grb-reference": { "grb-reference": {
"render": "Werd geïmporteerd vanuit GRB, het referentienummer is {source:geometry:ref}" "render": "Werd geïmporteerd vanuit GRB, het referentienummer is {source:geometry:ref}"
},
"grb-street": {
"question": "Wat is de straat?",
"render": "De straat is <b>{addr:street}</b>"
},
"grb-unit": {
"render": "De wooneenheid-aanduiding is <b>{addr:unit}</b> "
} }
} }
}, },
@ -700,36 +671,9 @@
} }
} }
} }
},
"5": {
"override": {
"tagRenderings+": {
"0": {
"mappings": {
"0": {
"then": "Geen omliggend OSM-gebouw gevonden"
} }
} }
}, },
"3": {
"mappings": {
"0": {
"then": "Geen omliggend OSM-gebouw gevonden. Een omliggend gebouw is nodig om dit punt als adres punt toe te voegen. <div class=subtle>Importeer eerst de gebouwen. Vernieuw dan de pagina om losse adressen toe te voegen</div>"
}
},
"render": {
"special": {
"text": "Voeg dit adres als een nieuw adrespunt toe"
}
}
}
}
}
}
},
"shortDescription": "Grb import helper tool",
"title": "GRB import helper"
},
"guideposts": { "guideposts": {
"description": "Wegwijzers (ook wel handwijzer genoemd) zijn vaak te vinden langs officiële wandel-, fiets-, ski- of paardrijroutes om de richtingen naar verschillende bestemmingen aan te geven. Vaak zijn ze vernoemd naar een regio of plaats en geven ze de hoogte aan.\n\nDe positie van een wegwijzer kan door een wandelaar/fietser/renner/skiër worden gebruikt als bevestiging van de huidige positie, vooral als ze een gedrukte kaart zonder GPS-ontvanger gebruiken. ", "description": "Wegwijzers (ook wel handwijzer genoemd) zijn vaak te vinden langs officiële wandel-, fiets-, ski- of paardrijroutes om de richtingen naar verschillende bestemmingen aan te geven. Vaak zijn ze vernoemd naar een regio of plaats en geven ze de hoogte aan.\n\nDe positie van een wegwijzer kan door een wandelaar/fietser/renner/skiër worden gebruikt als bevestiging van de huidige positie, vooral als ze een gedrukte kaart zonder GPS-ontvanger gebruiken. ",
"title": "Wegwijzers" "title": "Wegwijzers"
@ -1164,11 +1108,6 @@
}, },
"title": "Dierenartsen, hondenloopzones en andere huisdiervriendelijke plaatsen" "title": "Dierenartsen, hondenloopzones en andere huisdiervriendelijke plaatsen"
}, },
"play_forests": {
"description": "Een speelbos is een zone in een bos die vrij toegankelijk is voor spelende kinderen. Deze wordt in bossen van het Agentschap Natuur en bos altijd aangeduid met het overeenkomstige bord.",
"shortDescription": "Deze kaart toont speelbossen",
"title": "Speelbossen"
},
"playgrounds": { "playgrounds": {
"description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen", "description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen",
"shortDescription": "Een kaart met speeltuinen", "shortDescription": "Een kaart met speeltuinen",
@ -1242,47 +1181,6 @@
"description": "Alles om te skiën", "description": "Alles om te skiën",
"title": "Skipistes en kabelbanen" "title": "Skipistes en kabelbanen"
}, },
"speelplekken": {
"description": "<h3>Welkom bij de Groendoener!</h3>De Zuidrand dat is spelen, ravotten, chillen, wandelen,… in het groen. Meer dan <b>200 grote en kleine speelplekken</b> liggen er in parken, in bossen en op pleintjes te wachten om ontdekt te worden. De verschillende speelplekken werden getest én goedgekeurd door kinder- en jongerenreporters uit de Zuidrand. Met leuke challenges dagen de reporters jou uit om ook op ontdekking te gaan. Klik op een speelplek op de kaart, bekijk het filmpje en ga op verkenning!<br/><br/>Het project groendoener kadert binnen het strategisch project <a href='https://www.provincieantwerpen.be/aanbod/dlm/samenwerkingsverbanden/zuidrand/projecten/strategisch-project-beleefbare-open-ruimte.html' target='_blank'>Beleefbare Open Ruimte in de Antwerpse Zuidrand</a> en is een samenwerking tussen het departement Leefmilieu van provincie Antwerpen, Sportpret vzw, een OpenStreetMap-België Consultent en Createlli vzw. Het project kwam tot stand met steun van Departement Omgeving van de Vlaamse Overheid.<br/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/provincie_antwerpen.jpg'/><img class='w-full md:w-1/2' src='./assets/themes/speelplekken/Departement_Omgeving_Vlaanderen.png'/>",
"layers": {
"6": {
"name": "Wandelroutes van provincie Antwerpen",
"tagRenderings": {
"walk-description": {
"render": "<h3>Korte beschrijving:</h3>{description}"
},
"walk-length": {
"render": "Deze wandeling is <b>{_length:km}km</b> lang"
},
"walk-operator": {
"question": "Wie beheert deze wandeling en plaatst dus de signalisatiebordjes?"
},
"walk-operator-email": {
"question": "Naar wie kan men emailen bij problemen rond signalisatie?",
"render": "Bij problemen met signalisatie kan men emailen naar <a href='mailto:{operator:email}'>{operator:email}</a>"
},
"walk-type": {
"mappings": {
"0": {
"then": "Dit is een internationale wandelroute"
},
"1": {
"then": "Dit is een nationale wandelroute"
},
"2": {
"then": "Dit is een regionale wandelroute"
},
"3": {
"then": "Dit is een lokale wandelroute"
}
}
}
}
}
},
"shortDescription": "Speelplekken in de Antwerpse Zuidrand",
"title": "Welkom bij de groendoener!"
},
"sport_pitches": { "sport_pitches": {
"description": "Een sportveld is een ingerichte plaats met infrastructuur om een sport te beoefenen", "description": "Een sportveld is een ingerichte plaats met infrastructuur om een sport te beoefenen",
"shortDescription": "Deze kaart toont sportvelden", "shortDescription": "Deze kaart toont sportvelden",
@ -1403,10 +1301,6 @@
}, },
"title": "Straatverlichting" "title": "Straatverlichting"
}, },
"street_lighting_assen": {
"description": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen",
"title": "Straatverlichting - Assen"
},
"surveillance": { "surveillance": {
"description": "Op deze open kaart kan je bewakingscamera's vinden.", "description": "Op deze open kaart kan je bewakingscamera's vinden.",
"shortDescription": "Bewakingscameras en dergelijke", "shortDescription": "Bewakingscameras en dergelijke",
@ -1520,10 +1414,6 @@
"description": "Kaart met afvalbakken en recyclingfaciliteiten.", "description": "Kaart met afvalbakken en recyclingfaciliteiten.",
"title": "Afval" "title": "Afval"
}, },
"waste_assen": {
"description": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen.",
"title": "Afval - Assen"
},
"waste_basket": { "waste_basket": {
"description": "Op deze kaart vind je afvalbakken bij jou in de buurt. Als er een afvalbak ontbreekt op deze kaart, kun je deze zelf toevoegen", "description": "Op deze kaart vind je afvalbakken bij jou in de buurt. Als er een afvalbak ontbreekt op deze kaart, kun je deze zelf toevoegen",
"shortDescription": "Een kaart met vuilnisbakken", "shortDescription": "Een kaart met vuilnisbakken",

View file

@ -196,7 +196,9 @@
"seeOnMapillary": "在 Mapillary 觀看這張影像", "seeOnMapillary": "在 Mapillary 觀看這張影像",
"themeBy": "由 {author} 維護主題", "themeBy": "由 {author} 維護主題",
"title": "版權與署名", "title": "版權與署名",
"translatedBy": "MapComplete 由 {contributors} 翻譯,而且還有 <a href=\"https://github.com/pietervdvn/MapComplete/graphs/contributors\" target=\"_blank\">{hiddenCount} 更多貢獻者</a>" "translatedBy": "MapComplete 由 {contributors} 翻譯,而且還有 <a href=\"https://github.com/pietervdvn/MapComplete/graphs/contributors\" target=\"_blank\">{hiddenCount} 更多貢獻者</a>",
"panoramaxHelp": "<b>Panoramax</b> 是收集街景照片的線上服務,並且以自由授權釋出。貢獻者能夠使用這些照片來改進開放街圖",
"panoramaxLicenseCCBYSA": "你的圖片會以 CC-BY-SA 釋出 - 每個人都能夠在提及你名字的情形下再利用你的圖片"
}, },
"back": "返回", "back": "返回",
"backToIndex": "回到所有主題地圖的總覽頁面", "backToIndex": "回到所有主題地圖的總覽頁面",
@ -239,7 +241,8 @@
}, },
"title": "下載", "title": "下載",
"toMuch": "有很多圖徵可以下載了", "toMuch": "有很多圖徵可以下載了",
"uploadGpx": "上傳軌跡到開放街圖" "uploadGpx": "上傳軌跡到開放街圖",
"downloadImage": "下載圖片"
}, },
"enableGeolocationForSafari": "你沒有看到要求地理位置權限的跳出視窗?", "enableGeolocationForSafari": "你沒有看到要求地理位置權限的跳出視窗?",
"enableGeolocationForSafariLink": "學習如何在設定當中啟用地理位置權限", "enableGeolocationForSafariLink": "學習如何在設定當中啟用地理位置權限",
@ -249,7 +252,8 @@
"examples": "例子", "examples": "例子",
"filterPanel": { "filterPanel": {
"disableAll": "關閉所有", "disableAll": "關閉所有",
"enableAll": "啟用所有" "enableAll": "啟用所有",
"allTypes": "所有類型"
}, },
"geopermissionDenied": "使用地理位置要求已經被拒絕", "geopermissionDenied": "使用地理位置要求已經被拒絕",
"histogram": { "histogram": {
@ -261,7 +265,8 @@
"jumpToLocation": "到你目前的位置", "jumpToLocation": "到你目前的位置",
"menu": "選單", "menu": "選單",
"zoomIn": "放大", "zoomIn": "放大",
"zoomOut": "縮小" "zoomOut": "縮小",
"locationNotAvailable": "無法取得 GPS 位置,裝置有取得位置資訊還是在隧道內?"
}, },
"layerSelection": { "layerSelection": {
"title": "選擇圖層", "title": "選擇圖層",
@ -282,8 +287,13 @@
"logout": "登出", "logout": "登出",
"mappingsAreHidden": "有些選項已經隱藏,搜尋來顯示更多選項。", "mappingsAreHidden": "有些選項已經隱藏,搜尋來顯示更多選項。",
"menu": { "menu": {
"aboutCurrentThemeTitle": "關於地圖",
"aboutMapComplete": "關於 MapComplete", "aboutMapComplete": "關於 MapComplete",
"filter": "篩選資料" "filter": "篩選資料",
"moreUtilsTitle": "探索更多",
"openHereDifferentApp": "在其他應用程式開啟目前位置",
"showIntroduction": "顯示指引",
"title": "選單"
}, },
"morescreen": { "morescreen": {
"createYourOwnTheme": "從零開始建立你的 MapComplete 主題", "createYourOwnTheme": "從零開始建立你的 MapComplete 主題",
@ -349,18 +359,39 @@
"skippedMultiple": "你跳過 {skipped} 問題", "skippedMultiple": "你跳過 {skipped} 問題",
"skippedOne": "你跳過一個問題" "skippedOne": "你跳過一個問題"
}, },
"questions": {
"disable": "不要再問這個問題",
"disabledIntro": "你關閉一些類型的問題,要再次啟用問題,請在這邊點一下",
"disabledTitle": "關閉問題",
"enable": "針對所有問題啟用",
"noneDisabled": "如果你沒有對特定問題有興趣的話,請關閉。要關閉問題,請按右上角的三個點然後選擇'關閉'"
},
"removeLocationHistory": "刪除位置歷史", "removeLocationHistory": "刪除位置歷史",
"retry": "重試", "retry": "重試",
"returnToTheMap": "回到地圖", "returnToTheMap": "回到地圖",
"save": "儲存", "save": "儲存",
"screenToSmall": "在新視窗中開啟 <i>{theme}</i>", "screenToSmall": "在新視窗中開啟 <i>{theme}</i>",
"search": { "search": {
"error": "有狀況發生了…", "error": "有狀況發生了。",
"nothing": "沒有找到…", "nothing": "沒有找到。",
"recents": "最近看到的地方",
"search": "搜尋地點", "search": "搜尋地點",
"searching": "搜尋中…" "searching": "搜尋中…",
"editSearchSyncSettings": "編輯同步設定",
"editThemeSync": "編輯同步設定",
"instructions": "使用搜尋欄位來搜尋位置、篩選或是其他主題地圖",
"locations": "位置",
"nMoreFilters": "{n} 更多",
"nothingFor": "尋找 {term} 沒有結果",
"otherMaps": "其他地圖",
"pickFilter": "選擇篩選",
"recentThemes": "最近觀看的地圖",
"activeFilters": "啟用篩選",
"clearFilters": "清除篩選",
"deleteSearchHistory": "刪除位置歷史",
"deleteThemeHistory": "刪除先前觀看的主題"
}, },
"searchAnswer": "搜尋選項…", "searchAnswer": "搜尋選項",
"seeIndex": "查看所有專題地圖的概覽", "seeIndex": "查看所有專題地圖的概覽",
"share": "分享", "share": "分享",
"sharescreen": { "sharescreen": {
@ -488,7 +519,8 @@
"readMore": "閱讀剩下的條目內容", "readMore": "閱讀剩下的條目內容",
"searchToShort": "你的搜尋檢索太短了,請輸入長一點的文字", "searchToShort": "你的搜尋檢索太短了,請輸入長一點的文字",
"searchWikidata": "在 Wikidata 搜尋" "searchWikidata": "在 Wikidata 搜尋"
} },
"clearPendingChanges": "清除待處理的變動"
}, },
"hotkeyDocumentation": { "hotkeyDocumentation": {
"action": "行動", "action": "行動",
@ -513,7 +545,7 @@
"translationMode": "啟用翻譯模組的開關" "translationMode": "啟用翻譯模組的開關"
}, },
"image": { "image": {
"addPicture": "新增圖片", "addPicture": "照相",
"doDelete": "移除圖片", "doDelete": "移除圖片",
"isDeleted": "已刪除", "isDeleted": "已刪除",
"nearby": { "nearby": {
@ -524,9 +556,30 @@
"seeNearby": "瀏覽與連結附近圖片", "seeNearby": "瀏覽與連結附近圖片",
"title": "附近的街景影像" "title": "附近的街景影像"
}, },
"panoramax": {
"deletionRequested": "報告已經送出,管理員不久會觀看",
"freeform": "還有其他相關資訊嗎?",
"otherFreeform": "請指明為何需要移除這一圖片:",
"placeholder": "請解釋為何這圖片需要刪除",
"report": {
"copyright": "圖片內含有版權內容",
"inappropriate": "這圖片不洽當(有裸露、仇恨內容或是並非街景)",
"other": "如果是其他原因請指明",
"privacy": "圖片顯示私人產權"
},
"requestDeletion": "請求刪除圖片",
"title": "為什麼要永久刪除圖片?"
},
"pleaseLogin": "請登入以新增圖片", "pleaseLogin": "請登入以新增圖片",
"processing": "伺服器正在處理你的圖片",
"respectPrivacy": "請別照人像或是車牌,不要上傳 Google 地圖、Google 街景或其他受版權保護的資料來源。", "respectPrivacy": "請別照人像或是車牌,不要上傳 Google 地圖、Google 街景或其他受版權保護的資料來源。",
"selectFile": "從你的裝置選取圖片",
"toBig": "{actual_size} 因此照片太大,請使用最大 {max_size} 的照片", "toBig": "{actual_size} 因此照片太大,請使用最大 {max_size} 的照片",
"unlink": {
"button": "解除連結圖片",
"explanation": "圖片解除連結之後,這個圖片不會與這個物件一同顯示。但仍會在附近圖片或其他物件時顯示。",
"title": "解除連結這一圖片?"
},
"upload": { "upload": {
"failReasons": "你也許已經失去網路連線", "failReasons": "你也許已經失去網路連線",
"failReasonsAdvanced": "另一個方式,請確認瀏覽器與外掛沒有擋掉第三方 API。", "failReasonsAdvanced": "另一個方式,請確認瀏覽器與外掛沒有擋掉第三方 API。",
@ -536,10 +589,11 @@
"someFailed": "抱歉,我們無法上傳 {count} 影像", "someFailed": "抱歉,我們無法上傳 {count} 影像",
"uploading": "{count} 影像已經上傳…" "uploading": "{count} 影像已經上傳…"
}, },
"noBlur": "圖片不會模糊化,請不要照人",
"one": { "one": {
"done": "你的影像已經成功上傳,謝謝你!", "done": "你的影像已經成功上傳,謝謝你!",
"failed": "抱歉,我們無法上傳你的影像", "failed": "抱歉,我們無法上傳你的影像",
"retrying": "你的影像再次上傳 …", "retrying": "再次上傳你的影像中 …",
"uploading": "你的影像已經上傳了…" "uploading": "你的影像已經上傳了…"
} }
}, },
@ -561,6 +615,9 @@
"logIn": "登入來看其他你先前查看的主題", "logIn": "登入來看其他你先前查看的主題",
"title": "MapComplete" "title": "MapComplete"
}, },
"inspector": {
"menu": "檢核貢獻者"
},
"move": { "move": {
"cancel": "選擇不同的原因", "cancel": "選擇不同的原因",
"cannotBeMoved": "這個圖徵無法移動。", "cannotBeMoved": "這個圖徵無法移動。",
@ -578,7 +635,8 @@
"pointIsMoved": "這個點已經被移動了", "pointIsMoved": "這個點已經被移動了",
"reasons": { "reasons": {
"reasonInaccurate": "位置不準確,誤差幾公尺", "reasonInaccurate": "位置不準確,誤差幾公尺",
"reasonRelocation": "你的物件已經移動到完全不同的位置" "reasonRelocation": "你的物件已經移動到完全不同的位置",
"reasonSnapTo": "這應該移到{name}"
}, },
"zoomInFurther": "放更大來確認移動" "zoomInFurther": "放更大來確認移動"
}, },
@ -634,6 +692,11 @@
"takeImages": "拍攝樹木照片來自動偵測樹木類型", "takeImages": "拍攝樹木照片來自動偵測樹木類型",
"tryAgain": "選擇不同物種" "tryAgain": "選擇不同物種"
}, },
"preset_type": {
"question": "這個物件屬於什麼類型?",
"typeDescription": "這是 <b>{title}</b>. <div class='subtle'>{description}</div>",
"typeTitle": "這是 <b>{title}</b>"
},
"privacy": { "privacy": {
"editingIntro": "當你對地圖變動時,這些變動會存在開放街圖並且是公開給所有人。採用 MapComplete 的編輯變動包括以下資料:", "editingIntro": "當你對地圖變動時,這些變動會存在開放街圖並且是公開給所有人。採用 MapComplete 的編輯變動包括以下資料:",
"editingOutro": "請參考<a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>OpenStreetMap.org的隱私政策</a>來取得更多資訊。我們也提醒你註冊帳號時能夠採用假名。", "editingOutro": "請參考<a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>OpenStreetMap.org的隱私政策</a>來取得更多資訊。我們也提醒你註冊帳號時能夠採用假名。",
@ -696,6 +759,14 @@
"activateButton": "協助翻譯 MapComplete", "activateButton": "協助翻譯 MapComplete",
"missing": "{count} 未翻譯字串" "missing": "{count} 未翻譯字串"
}, },
"unknown": {
"clear": "清除答案",
"keep": "保留答案",
"markUnknown": "標示為未知",
"removedKeys": "下列鍵會被移除:",
"title": "要標記為未知嗎?",
"explanation": "如果答案不正確但實際數值不清楚的話,則清除這一些資訊。不會清除其他資訊。"
},
"userinfo": { "userinfo": {
"notLoggedIn": "你已經登出了" "notLoggedIn": "你已經登出了"
}, },
@ -768,12 +839,17 @@
"tooLong": "文字太長了,最多允許 255 字元,你現在還有 {count} 字元。", "tooLong": "文字太長了,最多允許 255 字元,你現在還有 {count} 字元。",
"url": { "url": {
"description": "連接到網站", "description": "連接到網站",
"feedback": "這不是有效的網址" "feedback": "這不是有效的網址",
"aggregator": "{host} 是第三方網站,如果可能請搜尋官方網站。",
"spamSite": "{host} 被視為低品質網站,並不被允許使用。"
}, },
"wikidata": { "wikidata": {
"description": "Wikidata 編號", "description": "Wikidata 編號",
"empty": "請輸入一些 Wikidata 項目", "empty": "請輸入一些 Wikidata 項目",
"startsWithQ": "維基數據編號以 Q 開頭後面接數字" "startsWithQ": "維基數據編號以 Q 開頭後面接數字"
},
"regex": {
"description": "正規表示式"
} }
} }
} }

16
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.47.12", "version": "0.47.13",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.47.12", "version": "0.47.13",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@comunica/core": "^3.0.1", "@comunica/core": "^3.0.1",
@ -8020,9 +8020,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001687", "version": "1.0.30001689",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz",
"integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -26969,9 +26969,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001687", "version": "1.0.30001689",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz",
"integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==",
"dev": true "dev": true
}, },
"canonicalize": { "canonicalize": {

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.47.12", "version": "0.47.13",
"repository": "https://github.com/pietervdvn/MapComplete", "repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues", "bugs": "https://github.com/pietervdvn/MapComplete/issues",

View file

@ -1324,20 +1324,20 @@ input[type="range"].range-lg::-moz-range-thumb {
margin: 0px; margin: 0px;
} }
.m-8 { .m-1 {
margin: 2rem; margin: 0.25rem;
} }
.m-2 { .m-2 {
margin: 0.5rem; margin: 0.5rem;
} }
.m-0\.5 { .m-8 {
margin: 0.125rem; margin: 2rem;
} }
.m-1 { .m-0\.5 {
margin: 0.25rem; margin: 0.125rem;
} }
.m-11 { .m-11 {
@ -1698,14 +1698,14 @@ input[type="range"].range-lg::-moz-range-thumb {
height: 6rem; height: 6rem;
} }
.h-screen {
height: 100vh;
}
.h-full { .h-full {
height: 100%; height: 100%;
} }
.h-screen {
height: 100vh;
}
.h-fit { .h-fit {
height: -webkit-fit-content; height: -webkit-fit-content;
height: -moz-fit-content; height: -moz-fit-content;
@ -2028,6 +2028,10 @@ input[type="range"].range-lg::-moz-range-thumb {
width: max-content; width: max-content;
} }
.w-48 {
width: 12rem;
}
.w-auto { .w-auto {
width: auto; width: auto;
} }
@ -2121,10 +2125,6 @@ input[type="range"].range-lg::-moz-range-thumb {
width: 0.25rem; width: 0.25rem;
} }
.w-48 {
width: 12rem;
}
.w-9\/12 { .w-9\/12 {
width: 75%; width: 75%;
} }
@ -2244,14 +2244,14 @@ input[type="range"].range-lg::-moz-range-thumb {
flex: 1 1 0%; flex: 1 1 0%;
} }
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 { .flex-shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
.flex-shrink {
flex-shrink: 1;
}
.shrink-0 { .shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
@ -2509,6 +2509,10 @@ input[type="range"].range-lg::-moz-range-thumb {
row-gap: 0.25rem; row-gap: 0.25rem;
} }
.gap-x-2 {
column-gap: 0.5rem;
}
.gap-y-2 { .gap-y-2 {
row-gap: 0.5rem; row-gap: 0.5rem;
} }
@ -2517,10 +2521,6 @@ input[type="range"].range-lg::-moz-range-thumb {
row-gap: 1rem; row-gap: 1rem;
} }
.gap-x-2 {
column-gap: 0.5rem;
}
.gap-x-1 { .gap-x-1 {
column-gap: 0.25rem; column-gap: 0.25rem;
} }
@ -3045,11 +3045,6 @@ input[type="range"].range-lg::-moz-range-thumb {
border-color: rgb(107 114 128 / var(--tw-border-opacity)); border-color: rgb(107 114 128 / var(--tw-border-opacity));
} }
.border-subtle {
--tw-border-opacity: 1;
border-color: rgb(219 234 254 / var(--tw-border-opacity));
}
.border-black { .border-black {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity)); border-color: rgb(0 0 0 / var(--tw-border-opacity));
@ -3065,6 +3060,11 @@ input[type="range"].range-lg::-moz-range-thumb {
border-color: rgb(209 213 219 / var(--tw-border-opacity)); border-color: rgb(209 213 219 / var(--tw-border-opacity));
} }
.border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
}
.border-gray-800 { .border-gray-800 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(31 41 55 / var(--tw-border-opacity)); border-color: rgb(31 41 55 / var(--tw-border-opacity));
@ -4045,18 +4045,14 @@ input[type="range"].range-lg::-moz-range-thumb {
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.pl-4 { .pt-2 {
padding-left: 1rem; padding-top: 0.5rem;
} }
.pr-4 { .pr-4 {
padding-right: 1rem; padding-right: 1rem;
} }
.pt-2 {
padding-top: 0.5rem;
}
.pl-1 { .pl-1 {
padding-left: 0.25rem; padding-left: 0.25rem;
} }
@ -5067,6 +5063,8 @@ input[type="range"].range-lg::-moz-range-thumb {
@font-face { @font-face {
font-family: "Source Sans Pro"; font-family: "Source Sans Pro";
/*This path might seem incorrect. However, 'index.css' will be compiled and placed in 'public/css', from where this path _is_ correct*/
src: url("../assets/fonts/source-sans-pro.regular.ttf") format("woff"); src: url("../assets/fonts/source-sans-pro.regular.ttf") format("woff");
} }

View file

@ -341,6 +341,7 @@ class GenerateLayouts extends Script {
"https://pietervdvn.goatcounter.com", "https://pietervdvn.goatcounter.com",
"https://api.panoramax.xyz", "https://api.panoramax.xyz",
"https://panoramax.mapcomplete.org", "https://panoramax.mapcomplete.org",
"https://data.velopark.be"
].concat(...(await this.eliUrls())) ].concat(...(await this.eliUrls()))
SpecialVisualizations.specialVisualizations.forEach((sv) => { SpecialVisualizations.specialVisualizations.forEach((sv) => {

View file

@ -2,12 +2,13 @@ import Script from "../Script"
import fs from "fs" import fs from "fs"
import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader" import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader"
import { Utils } from "../../src/Utils" import { Utils } from "../../src/Utils"
import { Feature } from "geojson" import { Feature, FeatureCollection, Point } from "geojson"
import { BBox } from "../../src/Logic/BBox" import { BBox } from "../../src/Logic/BBox"
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 { ImmutableStore } from "../../src/Logic/UIEventSource" import { ImmutableStore } from "../../src/Logic/UIEventSource"
import Constants from "../../src/Models/Constants" import Constants from "../../src/Models/Constants"
import { MaprouletteStatus } from "../../src/Logic/Maproulette"
class VeloParkToGeojson extends Script { class VeloParkToGeojson extends Script {
constructor() { constructor() {
@ -44,12 +45,15 @@ class VeloParkToGeojson extends Script {
const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url) const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url)
const allVelopark: Feature[] = [] const allVelopark: Feature[] = []
if (linkedData.length > 1) {
console.log("Detected multiple sections in:", url)
}
for (const sectionId in linkedData) { for (const sectionId in linkedData) {
const sectionInfo = linkedData[sectionId] const sectionInfo = linkedData[sectionId]
if (Object.keys(sectionInfo).length === 0) { if (Object.keys(sectionInfo).length === 0) {
console.warn("No result for", url) console.warn("No result for", url)
} }
if (!sectionInfo.geometry?.coordinates) { if (!sectionInfo.geometry?.["coordinates"]) {
throw "Invalid properties!" throw "Invalid properties!"
} }
allVelopark.push(sectionInfo) allVelopark.push(sectionInfo)
@ -62,8 +66,8 @@ class VeloParkToGeojson extends Script {
console.log("Downloading velopark data") console.log("Downloading velopark data")
// Download data for NIS-code 1000. 1000 means: all of belgium // Download data for NIS-code 1000. 1000 means: all of belgium
const url = "https://www.velopark.be/api/parkings/1000" const url = "https://www.velopark.be/api/parkings/1000"
const allVeloparkRaw: { url: string }[] = <{ url: string }[]>await Utils.downloadJson(url) const allVeloparkRaw = await Utils.downloadJson<{ url: string }[]>(url)
// Example multi-entry: https://data.velopark.be/data/Stad-Izegem_IZE_015
let failed = 0 let failed = 0
console.log("Got", allVeloparkRaw.length, "items") console.log("Got", allVeloparkRaw.length, "items")
const allVelopark: Feature[] = [] const allVelopark: Feature[] = []
@ -84,6 +88,7 @@ class VeloParkToGeojson extends Script {
} }
}) })
) )
console.log("Batch complete:", i)
} }
console.log( console.log(
"Fetching data done, got ", "Fetching data done, got ",
@ -135,7 +140,47 @@ class VeloParkToGeojson extends Script {
} }
} }
private static async fetchMapRouletteClosedItems() {
const challenges = ["https://maproulette.org/api/v2/challenge/view/43282"]
const solvedRefs: Set<string> = new Set<string>()
for (const url of challenges) {
const data = await Utils.downloadJson<
FeatureCollection<
Point,
{
mr_taskId: string
"ref:velopark": string
mr_taskStatus: MaprouletteStatus
mr_responses: string | undefined
}
>
>(url)
for (const challenge of data.features) {
const status = challenge.properties.mr_taskStatus
const isClosed =
status === "Fixed" ||
status === "False_positive" ||
status === "Already fixed" ||
status === "Too_Hard" ||
status === "Deleted"
if (isClosed) {
const ref = challenge.properties["ref:velopark"]
solvedRefs.add(ref)
}
}
}
console.log("Detected", solvedRefs, "as closed on mapRoulette")
return solvedRefs
}
/**
* Creates an extra version where all bicycle parkings which are already linked are removed.
* Fetches the latest OSM-data from overpass
* @param allVelopark
* @private
*/
private static async createDiff(allVelopark: Feature[]) { private static async createDiff(allVelopark: Feature[]) {
console.log("Creating diff...")
const bboxBelgium = new BBox([ const bboxBelgium = new BBox([
[2.51357303225, 49.5294835476], [2.51357303225, 49.5294835476],
[6.15665815596, 51.4750237087], [6.15665815596, 51.4750237087],
@ -160,21 +205,51 @@ class VeloParkToGeojson extends Script {
) )
VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced", features) VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced", features)
const synced = await this.fetchMapRouletteClosedItems()
const featuresMoreFiltered = features.filter(
(f) => !synced.has(f.properties["ref:velopark"])
)
VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced_nonclosed", featuresMoreFiltered)
const featuresMoreFilteredFailed = features.filter((f) =>
synced.has(f.properties["ref:velopark"])
)
VeloParkToGeojson.exportGeojsonTo(
"velopark_nonsynced_human_import_failed",
featuresMoreFilteredFailed
)
const allProperties = new Set<string>() const allProperties = new Set<string>()
for (const feature of features) { for (const feature of featuresMoreFiltered) {
Object.keys(feature).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 featuresMoreFiltered) {
allProperties.forEach((k) => { allProperties.forEach((k) => {
delete feature[k] if (k === "ref:velopark") {
return
}
delete feature.properties[k]
}) })
} }
this.exportGeojsonTo("velopark_nonsynced_id_only", features) this.exportGeojsonTo("velopark_nonsynced_nonclosed_id_only", featuresMoreFiltered)
}
public static async findMultiSection(): Promise<string[]> {
const url = "https://www.velopark.be/api/parkings/1000"
const raw = await Utils.downloadJson<{ "@graph": {}[]; url: string }[]>(url)
const multiEntries: string[] = []
for (const entry of raw) {
if (entry["@graph"].length > 1) {
multiEntries.push(entry.url)
}
}
return multiEntries
} }
async main(): Promise<void> { async main(): Promise<void> {
// const multiEntries = new Set(await VeloParkToGeojson.findMultiSection())
const allVelopark = const allVelopark =
VeloParkToGeojson.loadFromFile() ?? (await VeloParkToGeojson.downloadData()) VeloParkToGeojson.loadFromFile() ?? (await VeloParkToGeojson.downloadData())
console.log("Got", allVelopark.length, " items") console.log("Got", allVelopark.length, " items")

View file

@ -8,11 +8,13 @@ import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSo
import { LocalStorageSource } from "../Web/LocalStorageSource" import { LocalStorageSource } from "../Web/LocalStorageSource"
import { GeoOperations } from "../GeoOperations" import { GeoOperations } from "../GeoOperations"
import { OsmTags } from "../../Models/OsmFeature" import { OsmTags } from "../../Models/OsmFeature"
import StaticFeatureSource, { WritableStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource" import StaticFeatureSource, {
WritableStaticFeatureSource,
} from "../FeatureSource/Sources/StaticFeatureSource"
import { MapProperties } from "../../Models/MapProperties" import { MapProperties } from "../../Models/MapProperties"
import { Orientation } from "../../Sensors/Orientation" import { Orientation } from "../../Sensors/Orientation"
"use strict" ;("use strict")
/** /**
* The geolocation-handler takes a map-location and a geolocation state. * The geolocation-handler takes a map-location and a geolocation state.
* It'll move the map as appropriate given the state of the geolocation-API * It'll move the map as appropriate given the state of the geolocation-API

View file

@ -9,7 +9,7 @@ import { Utils } from "../../Utils"
import { GeoLocationState } from "../State/GeoLocationState" import { GeoLocationState } from "../State/GeoLocationState"
import { OsmConnection } from "../Osm/OsmConnection" import { OsmConnection } from "../Osm/OsmConnection"
"use strict" ;("use strict")
/** /**
* This actor is responsible to set the map location. * This actor is responsible to set the map location.
@ -27,7 +27,11 @@ export default class InitialMapPositioning {
public location: UIEventSource<{ lon: number; lat: number }> public location: UIEventSource<{ lon: number; lat: number }>
public useTerrain: Store<boolean> public useTerrain: Store<boolean>
constructor(layoutToUse: ThemeConfig, geolocationState: GeoLocationState, osmConnection: OsmConnection) { constructor(
layoutToUse: ThemeConfig,
geolocationState: GeoLocationState,
osmConnection: OsmConnection
) {
function localStorageSynced( function localStorageSynced(
key: string, key: string,
deflt: number, deflt: number,
@ -49,7 +53,6 @@ export default class InitialMapPositioning {
return src return src
} }
// -- Location control initialization // -- Location control initialization
this.zoom = localStorageSynced( this.zoom = localStorageSynced(
"z", "z",
@ -94,12 +97,11 @@ export default class InitialMapPositioning {
console.log("Loading note", initialHash) console.log("Loading note", initialHash)
const noteId = Number(initialHash) const noteId = Number(initialHash)
if (osmConnection.isLoggedIn.data) { if (osmConnection.isLoggedIn.data) {
osmConnection.getNote(noteId).then(note => { osmConnection.getNote(noteId).then((note) => {
const [lon, lat] = note.geometry.coordinates const [lon, lat] = note.geometry.coordinates
console.log("Got note:", note) console.log("Got note:", note)
this.location.set({ lon, lat }) this.location.set({ lon, lat })
} })
)
} }
} else if ( } else if (
Constants.GeoIpServer && Constants.GeoIpServer &&

View file

@ -2,7 +2,7 @@ import { FeatureSource, WritableFeatureSource } from "../FeatureSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import { Feature } from "geojson" import { Feature } from "geojson"
"use strict" ;("use strict")
/** /**
* A simple, read only feature store. * A simple, read only feature store.
*/ */
@ -32,7 +32,9 @@ export default class StaticFeatureSource<T extends Feature = Feature> implements
} }
} }
export class WritableStaticFeatureSource<T extends Feature = Feature> implements WritableFeatureSource<T> { export class WritableStaticFeatureSource<T extends Feature = Feature>
implements WritableFeatureSource<T>
{
public readonly features: UIEventSource<T[]> = undefined public readonly features: UIEventSource<T[]> = undefined
constructor(features: UIEventSource<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) { constructor(features: UIEventSource<T[]> | T[] | { features: T[] } | { features: Store<T[]> }) {
@ -53,6 +55,5 @@ export class WritableStaticFeatureSource<T extends Feature = Feature> implements
} else { } else {
this.features = feats this.features = feats
} }
} }
} }

View file

@ -10,13 +10,13 @@ import {
MultiPolygon, MultiPolygon,
Point, Point,
Polygon, Polygon,
Position Position,
} from "geojson" } from "geojson"
import { Tiles } from "../Models/TileRange" import { Tiles } from "../Models/TileRange"
import { Utils } from "../Utils" import { Utils } from "../Utils"
import { NearestPointOnLine } from "@turf/nearest-point-on-line" import { NearestPointOnLine } from "@turf/nearest-point-on-line"
("use strict") ;("use strict")
export class GeoOperations { export class GeoOperations {
private static readonly _earthRadius = 6378137 private static readonly _earthRadius = 6378137

View file

@ -190,7 +190,7 @@ export class ImageUploadManager {
} }
} }
try { try {
({ key, value, absoluteUrl } = await this._uploader.uploadImage( ;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob, blob,
location, location,
author, author,
@ -200,7 +200,7 @@ export class ImageUploadManager {
this.increaseCountFor(this._uploadRetried, featureId) this.increaseCountFor(this._uploadRetried, featureId)
console.error("Could not upload image, trying again:", e) console.error("Could not upload image, trying again:", e)
try { try {
({ key, value, absoluteUrl } = await this._uploader.uploadImage( ;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob, blob,
location, location,
author, author,

View file

@ -6,6 +6,18 @@ export interface MaprouletteTask {
description: string description: string
instruction: string instruction: string
} }
export const maprouletteStatus = [
"Open",
"Fixed",
"False_positive",
"Skipped",
"Deleted",
"Already fixed",
"Too_Hard",
"Disabled",
] as const
export type MaprouletteStatus = (typeof maprouletteStatus)[number]
export default class Maproulette { export default class Maproulette {
public static readonly defaultEndpoint = "https://maproulette.org/api/v2" public static readonly defaultEndpoint = "https://maproulette.org/api/v2"
@ -19,16 +31,6 @@ export default class Maproulette {
public static readonly STATUS_TOO_HARD = 6 public static readonly STATUS_TOO_HARD = 6
public static readonly STATUS_DISABLED = 9 public static readonly STATUS_DISABLED = 9
public static readonly STATUS_MEANING = {
0: "Open",
1: "Fixed",
2: "False_positive",
3: "Skipped",
4: "Deleted",
5: "Already fixed",
6: "Too_Hard",
9: "Disabled"
}
public static singleton = new Maproulette() public static singleton = new Maproulette()
/* /*
* The API endpoint to use * The API endpoint to use
@ -62,13 +64,12 @@ export default class Maproulette {
if (code === "Created") { if (code === "Created") {
return Maproulette.STATUS_OPEN return Maproulette.STATUS_OPEN
} }
for (let i = 0; i < 9; i++) { const i = maprouletteStatus.indexOf(<any>code)
if (Maproulette.STATUS_MEANING["" + i] === code) { if (i < 0) {
return i
}
}
return undefined return undefined
} }
return i
}
/** /**
* Close a task; might throw an error * Close a task; might throw an error
@ -97,9 +98,9 @@ export default class Maproulette {
method: "PUT", method: "PUT",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
apiKey: this.apiKey apiKey: this.apiKey,
}, },
body: JSON.stringify(options) body: JSON.stringify(options),
}) })
if (response.status !== 204) { if (response.status !== 204) {
console.log(`Failed to close task: ${response.status}`) console.log(`Failed to close task: ${response.status}`)

View file

@ -42,31 +42,34 @@ export default class UserDetails {
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
interface CapabilityResult { interface CapabilityResult {
"version": "0.6" | string, version: "0.6" | string
"generator": "OpenStreetMap server" | string, generator: "OpenStreetMap server" | string
"copyright": "OpenStreetMap and contributors" | string, copyright: "OpenStreetMap and contributors" | string
"attribution": "http://www.openstreetmap.org/copyright" | string, attribution: "http://www.openstreetmap.org/copyright" | string
"license": "http://opendatacommons.org/licenses/odbl/1-0/" | string, license: "http://opendatacommons.org/licenses/odbl/1-0/" | string
"api": { api: {
"version": { "minimum": "0.6", "maximum": "0.6" }, version: { minimum: "0.6"; maximum: "0.6" }
"area": { "maximum": 0.25 | number }, area: { maximum: 0.25 | number }
"note_area": { "maximum": 25 | number }, note_area: { maximum: 25 | number }
"tracepoints": { "per_page": 5000 | number }, tracepoints: { per_page: 5000 | number }
"waynodes": { "maximum": 2000 | number }, waynodes: { maximum: 2000 | number }
"relationmembers": { "maximum": 32000 | number }, relationmembers: { maximum: 32000 | number }
"changesets": { "maximum_elements": 10000 | number, changesets: {
"default_query_limit": 100 | number, maximum_elements: 10000 | number
"maximum_query_limit": 100 |number}, default_query_limit: 100 | number
"notes": { "default_query_limit": 100 | number, "maximum_query_limit": 10000 |number}, maximum_query_limit: 100 | number
"timeout": { "seconds": 300 |number}, }
"status": { notes: { default_query_limit: 100 | number; maximum_query_limit: 10000 | number }
"database": OsmServiceState, timeout: { seconds: 300 | number }
"api": OsmServiceState, status: {
"gpx": OsmServiceState } database: OsmServiceState
}, api: OsmServiceState
"policy": { gpx: OsmServiceState
"imagery": { }
"blacklist":{regex: string}[] }
policy: {
imagery: {
blacklist: { regex: string }[]
} }
} }
} }
@ -76,14 +79,14 @@ export class OsmConnection {
public userDetails: UIEventSource<UserDetails> public userDetails: UIEventSource<UserDetails>
public isLoggedIn: Store<boolean> public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown", "unknown"
) )
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>( public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown", "unknown"
) )
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
"not-attempted", "not-attempted"
) )
public preferencesHandler: OsmPreferences public preferencesHandler: OsmPreferences
public readonly _oauth_config: AuthConfig public readonly _oauth_config: AuthConfig
@ -127,7 +130,7 @@ export class OsmConnection {
this.userDetails = new UIEventSource<UserDetails>( this.userDetails = new UIEventSource<UserDetails>(
new UserDetails(this._oauth_config.url), new UserDetails(this._oauth_config.url),
"userDetails", "userDetails"
) )
if (options.fakeUser) { if (options.fakeUser) {
const ud = this.userDetails.data const ud = this.userDetails.data
@ -148,7 +151,7 @@ export class OsmConnection {
(user) => (user) =>
user.loggedIn && user.loggedIn &&
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
[this.apiIsOnline], [this.apiIsOnline]
) )
this.isLoggedIn.addCallback((isLoggedIn) => { this.isLoggedIn.addCallback((isLoggedIn) => {
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) { if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
@ -191,7 +194,7 @@ export class OsmConnection {
defaultValue: string = undefined, defaultValue: string = undefined,
options?: { options?: {
prefix?: string prefix?: string
}, }
): UIEventSource<T | undefined> { ): UIEventSource<T | undefined> {
const prefix = options?.prefix ?? "mapcomplete-" const prefix = options?.prefix ?? "mapcomplete-"
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
@ -200,7 +203,7 @@ export class OsmConnection {
public getPreference<T extends string = string>( public getPreference<T extends string = string>(
key: string, key: string,
defaultValue: string = undefined, defaultValue: string = undefined,
prefix: string = "mapcomplete-", prefix: string = "mapcomplete-"
): UIEventSource<T | undefined> { ): UIEventSource<T | undefined> {
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix) return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
} }
@ -244,12 +247,12 @@ export class OsmConnection {
this.updateAuthObject() this.updateAuthObject()
LocalStorageSource.get("location_before_login").setData( LocalStorageSource.get("location_before_login").setData(
Utils.runningFromConsole ? undefined : window.location.href, Utils.runningFromConsole ? undefined : window.location.href
) )
this.auth.xhr( this.auth.xhr(
{ {
method: "GET", method: "GET",
path: "/api/0.6/user/details" path: "/api/0.6/user/details",
}, },
(err, details: XMLDocument) => { (err, details: XMLDocument) => {
if (err != null) { if (err != null) {
@ -282,13 +285,13 @@ export class OsmConnection {
data.account_created = userInfo.getAttribute("account_created") data.account_created = userInfo.getAttribute("account_created")
data.uid = Number(userInfo.getAttribute("id")) data.uid = Number(userInfo.getAttribute("id"))
data.languages = Array.from( data.languages = Array.from(
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"), userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
).map((l) => l.textContent) ).map((l) => l.textContent)
data.csCount = Number.parseInt( data.csCount = Number.parseInt(
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0", userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
) )
data.tracesCount = Number.parseInt( data.tracesCount = Number.parseInt(
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0", userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
) )
data.img = undefined data.img = undefined
@ -320,7 +323,7 @@ export class OsmConnection {
action(this.userDetails.data) action(this.userDetails.data)
} }
this._onLoggedIn = [] this._onLoggedIn = []
}, }
) )
} }
@ -338,7 +341,7 @@ export class OsmConnection {
method: "GET" | "POST" | "PUT" | "DELETE", method: "GET" | "POST" | "PUT" | "DELETE",
header?: Record<string, string>, header?: Record<string, string>,
content?: string, content?: string,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<string> { ): Promise<string> {
const connection: osmAuth = this.auth const connection: osmAuth = this.auth
if (allowAnonymous && !this.auth.authenticated()) { if (allowAnonymous && !this.auth.authenticated()) {
@ -346,7 +349,7 @@ export class OsmConnection {
`${this.Backend()}/api/0.6/${path}`, `${this.Backend()}/api/0.6/${path}`,
header, header,
method, method,
content, content
) )
if (possibleResult["content"]) { if (possibleResult["content"]) {
return possibleResult["content"] return possibleResult["content"]
@ -361,7 +364,7 @@ export class OsmConnection {
method, method,
headers: header, headers: header,
content, content,
path: `/api/0.6/${path}` path: `/api/0.6/${path}`,
}, },
function (err, response) { function (err, response) {
if (err !== null) { if (err !== null) {
@ -369,7 +372,7 @@ export class OsmConnection {
} else { } else {
ok(response) ok(response)
} }
}, }
) )
}) })
} }
@ -378,7 +381,7 @@ export class OsmConnection {
path: string, path: string,
content?: string, content?: string,
header?: Record<string, string>, header?: Record<string, string>,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<T> { ): Promise<T> {
return <T>await this.interact(path, "POST", header, content, allowAnonymous) return <T>await this.interact(path, "POST", header, content, allowAnonymous)
} }
@ -386,7 +389,7 @@ export class OsmConnection {
public async put<T extends string>( public async put<T extends string>(
path: string, path: string,
content?: string, content?: string,
header?: Record<string, string>, header?: Record<string, string>
): Promise<T> { ): Promise<T> {
return <T>await this.interact(path, "PUT", header, content) return <T>await this.interact(path, "PUT", header, content)
} }
@ -394,7 +397,7 @@ export class OsmConnection {
public async get( public async get(
path: string, path: string,
header?: Record<string, string>, header?: Record<string, string>,
allowAnonymous: boolean = false, allowAnonymous: boolean = false
): Promise<string> { ): Promise<string> {
return await this.interact(path, "GET", header, undefined, allowAnonymous) return await this.interact(path, "GET", header, undefined, allowAnonymous)
} }
@ -433,7 +436,7 @@ export class OsmConnection {
return new Promise<{ id: number }>((ok) => { return new Promise<{ id: number }>((ok) => {
window.setTimeout( window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }), () => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000, Math.random() * 5000
) )
}) })
} }
@ -443,9 +446,9 @@ export class OsmConnection {
"notes.json", "notes.json",
content, 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) const parsed = JSON.parse(response)
console.log("Got result:", parsed) console.log("Got result:", parsed)
@ -455,9 +458,7 @@ export class OsmConnection {
} }
public async getNote(id: number): Promise<Feature<Point>> { public async getNote(id: number): Promise<Feature<Point>> {
return JSON.parse(await this.get( return JSON.parse(await this.get("notes/" + id + ".json"))
"notes/" + id + ".json"
))
} }
public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const
@ -474,14 +475,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. * 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[] labels: string[]
}, }
): Promise<{ id: number }> { ): Promise<{ id: number }> {
if (this._dryRun.data) { if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually uploading GPX ", gpx) console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
return new Promise<{ id: number }>((ok) => { return new Promise<{ id: number }>((ok) => {
window.setTimeout( window.setTimeout(
() => ok({ id: Math.floor(Math.random() * 1000) }), () => ok({ id: Math.floor(Math.random() * 1000) }),
Math.random() * 5000, Math.random() * 5000
) )
}) })
} }
@ -490,7 +491,7 @@ export class OsmConnection {
file: gpx, file: gpx,
description: options.description, description: options.description,
tags: options.labels?.join(",") ?? "", tags: options.labels?.join(",") ?? "",
visibility: options.visibility visibility: options.visibility,
} }
if (!contents.description) { if (!contents.description) {
@ -498,7 +499,7 @@ export class OsmConnection {
} }
const extras = { const extras = {
file: file:
"; filename=\"" + '; filename="' +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
'"\r\nContent-Type: application/gpx+xml', '"\r\nContent-Type: application/gpx+xml',
} }
@ -508,7 +509,7 @@ user
let body = "" let body = ""
for (const key in contents) { for (const key in contents) {
body += "--" + boundary + "\r\n" body += "--" + boundary + "\r\n"
body += "Content-Disposition: form-data; name=\"" + key + "\"" body += 'Content-Disposition: form-data; name="' + key + '"'
if (extras[key] !== undefined) { if (extras[key] !== undefined) {
body += extras[key] body += extras[key]
} }
@ -519,7 +520,7 @@ user
const response = await this.post("gpx/create", body, { const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary, "Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": "" + body.length "Content-Length": "" + body.length,
}) })
const parsed = JSON.parse(response) const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed) console.log("Uploaded GPX track", parsed)
@ -540,7 +541,7 @@ user
{ {
method: "POST", 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) { if (err !== null) {
@ -548,7 +549,7 @@ user
} else { } else {
ok() ok()
} }
}, }
) )
}) })
} }
@ -578,7 +579,7 @@ user
*/ */
singlepage: !this._iframeMode, singlepage: !this._iframeMode,
auto: true, auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url apiUrl: this._oauth_config.api_url ?? this._oauth_config.url,
}) })
} }
@ -637,13 +638,18 @@ user
return parsed return parsed
} }
private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState; database: OsmServiceState }> { private async FetchCapabilities(): Promise<{
api: OsmServiceState
gpx: OsmServiceState
database: OsmServiceState
}> {
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {
return { api: "online", gpx: "online", database: "online" } return { api: "online", gpx: "online", database: "online" }
} }
try { try {
const result = await Utils.downloadJson<CapabilityResult>(
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) { if (result?.api?.status === undefined) {
console.log("Something went wrong:", result) console.log("Something went wrong:", result)
return { api: "unreachable", gpx: "unreachable", database: "unreachable" } return { api: "unreachable", gpx: "unreachable", database: "unreachable" }
@ -652,7 +658,6 @@ user
} catch (e) { } catch (e) {
console.error("Could not fetch capabilities") console.error("Could not fetch capabilities")
return { api: "offline", gpx: "offline", database: "online" } return { api: "offline", gpx: "offline", database: "online" }
} }
} }
} }

View file

@ -421,6 +421,7 @@ export default class LinkedDataLoader {
delete output["chargeEnd"] delete output["chargeEnd"]
delete output["chargeStart"] delete output["chargeStart"]
delete output["timeUnit"] delete output["timeUnit"]
delete output["id"]
asBoolean("covered") asBoolean("covered")
asBoolean("fee", true) asBoolean("fee", true)
@ -518,6 +519,9 @@ export default class LinkedDataLoader {
property: string, property: string,
variable?: string variable?: string
): Promise<SparqlResult<T, G>> { ): Promise<SparqlResult<T, G>> {
if (property === "schema:photos") {
console.log(">> Getting photos")
}
const results = await new TypedSparql().typedSparql<T, G>( const results = await new TypedSparql().typedSparql<T, G>(
{ {
schema: "http://schema.org/", schema: "http://schema.org/",
@ -531,15 +535,23 @@ export default class LinkedDataLoader {
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>", " ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
"?parking " + property + " " + (variable ?? "") "?parking " + property + " " + (variable ?? "")
) )
return results return results
} }
/**
*
* @param url
* @param property
* @param subExpr
* @private
*/
private static async fetchVeloparkGraphProperty<T extends string>( private static async fetchVeloparkGraphProperty<T extends string>(
url: string, url: string,
property: string, property: string,
subExpr?: string subExpr?: string
): Promise<SparqlResult<T, "g">> { ): Promise<SparqlResult<T, "g">> {
return await new TypedSparql().typedSparql<T, "g">( const result = await new TypedSparql().typedSparql<T, "g">(
{ {
schema: "http://schema.org/", schema: "http://schema.org/",
mv: "http://schema.mobivoc.org/", mv: "http://schema.mobivoc.org/",
@ -551,8 +563,15 @@ export default class LinkedDataLoader {
"g", "g",
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>", " ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
S.graph("g", "?section " + property + " " + (subExpr ?? ""), "?section a ?type") S.graph(
"g",
"?section " + property + " " + (subExpr ?? ""),
"?section a ?type",
"BIND(STR(?section) AS ?id)"
) )
)
return result
} }
/** /**
@ -569,26 +588,69 @@ export default class LinkedDataLoader {
continue continue
} }
for (const sectionKey in subResult) { for (const sectionKey in subResult) {
if (!r[sectionKey]) { if (sectionKey === "default") {
r[sectionKey] = {} r["default"] ??= {}
} const section = subResult["default"]
const section = subResult[sectionKey]
for (const key in section) { for (const key in section) {
r[sectionKey][key] ??= section[key] r["default"][key] ??= section[key]
}
} else {
const section = subResult[sectionKey]
const actualId = Array.from(section["id"] ?? [])[0] ?? sectionKey
r[actualId] ??= {}
for (const key in section) {
r[actualId][key] ??= section[key]
}
} }
} }
} }
if (r["default"] !== undefined && Object.keys(r).length > 1) { /**
* Copy all values from the section with name "key" into the other sections,
* remove section "key" afterwards
* @param key
*/
function spreadSection(key: string) {
for (const section in r) { for (const section in r) {
if (section === "default") { if (section === key) {
continue continue
} }
for (const k in r.default) { for (const k in r[key]) {
r[section][k] ??= r.default[k] r[section][k] ??= r[key][k]
} }
} }
delete r.default delete r[key]
}
// The "default" part of the result contains all general info
// The other 'sections' need to get those copied! Then, we delete the "default"-section
if (r["default"] !== undefined && Object.keys(r).length > 1) {
spreadSection("default")
}
if (Object.keys(r).length > 1) {
// This result has multiple sections
// We should check that the naked URL got distributed and scrapped
const keys = Object.keys(r)
if (Object.keys(r).length > 2) {
console.log("Multiple sections detected: ", JSON.stringify(keys))
}
const shortestKeyLength: number = Math.min(...keys.map((k) => k.length))
const key = keys.find((k) => k.length === shortestKeyLength)
if (keys.some((k) => !k.startsWith(key))) {
throw (
"Invalid multi-object: the shortest key is not the start of all the others: " +
JSON.stringify(keys)
)
}
spreadSection(key)
}
if (Object.keys(r).length == 1) {
const key = Object.keys(r)[0]
if (key.indexOf("#") > 0) {
const newKey = key.split("#")[0]
r[newKey] = r[key]
delete r[key]
}
} }
return r return r
} }
@ -675,6 +737,7 @@ export default class LinkedDataLoader {
/** /**
* Fetches all data relevant to velopark. * Fetches all data relevant to velopark.
* The id will be saved as `ref:velopark` * The id will be saved as `ref:velopark`
* If the entry has multiple sections, this will return multiple items
* @param url * @param url
*/ */
public static async fetchVeloparkEntry( public static async fetchVeloparkEntry(
@ -685,6 +748,7 @@ export default class LinkedDataLoader {
if (this.veloparkCache[cacheKey]) { if (this.veloparkCache[cacheKey]) {
return this.veloparkCache[cacheKey] return this.veloparkCache[cacheKey]
} }
// Note: the proxy doesn't make any changes in this case
const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url)) const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
const optionalPaths: Record<string, string | Record<string, string>> = { const optionalPaths: Record<string, string | Record<string, string>> = {
"schema:interactionService": { "schema:interactionService": {
@ -697,6 +761,7 @@ export default class LinkedDataLoader {
"schema:email": "email", "schema:email": "email",
"schema:telephone": "phone", "schema:telephone": "phone",
}, },
// "schema:photos": "images",
"schema:dateModified": "_last_edit_timestamp", "schema:dateModified": "_last_edit_timestamp",
} }
if (includeExtras) { if (includeExtras) {
@ -740,9 +805,22 @@ export default class LinkedDataLoader {
graphOptionalPaths, graphOptionalPaths,
extra extra
) )
for (const unpatchedKey in unpatched) {
// Dirty hack
const rawData = await Utils.downloadJsonCached<object>(url, 1000 * 60 * 60)
const images = rawData["photos"]?.map((ph) => <string>ph.image)
if (images) {
unpatched[unpatchedKey].images = new Set<string>(images)
}
}
console.log("Got unpatched:", unpatched)
const patched: Feature[] = [] const patched: Feature[] = []
for (const section in unpatched) { for (let section in unpatched) {
const p = LinkedDataLoader.patchVeloparkProperties(unpatched[section]) const p = LinkedDataLoader.patchVeloparkProperties(unpatched[section])
if (Object.keys(unpatched).length === 1 && section.endsWith("#section1")) {
section = section.split("#")[0]
}
p["ref:velopark"] = [section] p["ref:velopark"] = [section]
patched.push(LinkedDataLoader.asGeojson(p)) patched.push(LinkedDataLoader.asGeojson(p))
} }

View file

@ -67,13 +67,14 @@ export default class TypedSparql {
bindings.forEach((item) => { bindings.forEach((item) => {
const result = <Record<VARS | G, Set<string>>>{} const result = <Record<VARS | G, Set<string>>>{}
item.forEach((value, key) => { item.forEach((value, key) => {
if (!result[key.value]) { result[key.value] ??= new Set()
result[key.value] = new Set()
}
result[key.value].add(value.value) result[key.value].add(value.value)
}) })
if (graphVariable && result[graphVariable]?.size > 0) { if (graphVariable && result[graphVariable]?.size > 0) {
const id = Array.from(result[graphVariable])?.[0] ?? "default" const id: string =
<string>Array.from(result["id"] ?? [])?.[0] ??
Array.from(result[graphVariable] ?? [])?.[0] ??
"default"
resultAllGraphs[id] = result resultAllGraphs[id] = result
} else { } else {
resultAllGraphs["default"] = result resultAllGraphs["default"] = result

View file

@ -2,7 +2,11 @@ import ThemeConfig from "./ThemeConfig/ThemeConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource"
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import {
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties" import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState" import LayerState from "../Logic/State/LayerState"
@ -46,7 +50,9 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector" import NoElementsInViewDetector, {
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer" import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
@ -57,7 +63,7 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import Zoomcontrol from "../UI/Zoomcontrol" import Zoomcontrol from "../UI/Zoomcontrol"
import { import {
SummaryTileSource, SummaryTileSource,
SummaryTileSourceRewriter SummaryTileSourceRewriter,
} from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import summaryLayer from "../assets/generated/layers/summary.json" import summaryLayer from "../assets/generated/layers/summary.json"
import last_click_layerconfig from "../assets/generated/layers/last_click.json" import last_click_layerconfig from "../assets/generated/layers/last_click.json"
@ -178,7 +184,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
"oauth_token", "oauth_token",
undefined, undefined,
"Used to complete the login" "Used to complete the login"
) ),
}) })
const initial = new InitialMapPositioning(layout, geolocationState, this.osmConnection) const initial = new InitialMapPositioning(layout, geolocationState, this.osmConnection)
this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 }) this.mapProperties = new MapLibreAdaptor(this.map, initial, { correctClick: 20 })
@ -186,7 +192,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout, layout,
@ -783,7 +788,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const layers = this.theme.layers.filter( const layers = this.theme.layers.filter(
(l) => (l) =>
(<string[]><unknown>Constants.priviliged_layers).indexOf(l.id) < 0 && (<string[]>(<unknown>Constants.priviliged_layers)).indexOf(l.id) < 0 &&
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount
) )

View file

@ -53,6 +53,8 @@
fill: var(--button-background-hover); fill: var(--button-background-hover);
transition: fill 350ms linear; transition: fill 350ms linear;
cursor: pointer; cursor: pointer;
stroke-width: 0.8;
stroke: white;
} }
:global(.dots-menu:hover > path, .dots-menu-opened > path) { :global(.dots-menu:hover > path, .dots-menu-opened > path) {

View file

@ -128,17 +128,20 @@
{#if $unknownImages.length > 0} {#if $unknownImages.length > 0}
{#if readonly} {#if readonly}
<div class="w-full overflow-x-auto"> <div
<div class="flex h-32 w-max gap-x-2"> class="flex w-full space-x-2 overflow-x-auto border border-gray-600 p-1"
style="scroll-snap-type: x proximity; border: 1px solid black"
>
{#each $unknownImages as image (image)} {#each $unknownImages as image (image)}
<div class="relative flex w-fit items-center bg-gray-200">
<AttributedImage <AttributedImage
{state} {state}
imgClass="h-32 w-max shrink-0" imgClass="h-32 shrink-0"
image={{ url: image }} image={{ url: image }}
previewedImage={state.previewedImage} previewedImage={state.previewedImage}
/> />
{/each}
</div> </div>
{/each}
</div> </div>
{:else} {:else}
{#each $unknownImages as image (image)} {#each $unknownImages as image (image)}

View file

@ -42,7 +42,7 @@
if (previewedImage) { if (previewedImage) {
onDestroy( onDestroy(
previewedImage.addCallbackAndRun((previewedImage) => { previewedImage.addCallbackAndRun((previewedImage) => {
showBigPreview.set(previewedImage?.id === image.id) showBigPreview.set(previewedImage !== undefined && previewedImage?.id === image.id)
}) })
) )
} }

View file

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { Store } from "../../Logic/UIEventSource.js" import { Store, UIEventSource } from "../../Logic/UIEventSource.js"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import DeletableImage from "./DeletableImage.svelte" import DeletableImage from "./DeletableImage.svelte"
export let images: Store<ProvidedImage[]> export let images: Store<ProvidedImage[]>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let tags: Store<Record<string, string>> export let tags: UIEventSource<Record<string, string>>
</script> </script>
<div class="flex w-full space-x-2 overflow-x-auto" style="scroll-snap-type: x proximity"> <div class="flex w-full space-x-2 overflow-x-auto" style="scroll-snap-type: x proximity">

View file

@ -5,7 +5,7 @@
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Icon from "../Map/Icon.svelte" import Icon from "../Map/Icon.svelte"
import Maproulette from "../../Logic/Maproulette" import Maproulette, { maprouletteStatus } from "../../Logic/Maproulette"
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte"
/** /**
@ -38,10 +38,11 @@
async function apply() { async function apply() {
const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id
try { try {
await Maproulette.singleton.closeTask(Number(maproulette_id), Number(statusToSet), state, { const statusIndex = Maproulette.codeToIndex(statusToSet) ?? Number(statusToSet)
await Maproulette.singleton.closeTask(Number(maproulette_id), statusIndex, state, {
comment: feedback, comment: feedback,
}) })
tags.data["mr_taskStatus"] = Maproulette.STATUS_MEANING[Number(statusToSet)] tags.data["mr_taskStatus"] = maprouletteStatus[statusIndex]
tags.data.status = statusToSet tags.data.status = statusToSet
tags.ping() tags.ping()
} catch (e) { } catch (e) {

View file

@ -9,6 +9,7 @@ import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlo
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
import { ImportFlowUtils } from "./ImportFlow" import { ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import { GeoOperations } from "../../../Logic/GeoOperations"
/** /**
* The wrapper to make the special visualisation for the PointImportFlow * The wrapper to make the special visualisation for the PointImportFlow
@ -44,6 +45,10 @@ export class PointImportButtonViz implements SpecialVisualization {
name: "maproulette_id", name: "maproulette_id",
doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.", doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.",
}, },
{
name: "to_point",
doc: "If set, a feature will be converted to a centerpoint",
},
] ]
} }
@ -53,9 +58,15 @@ export class PointImportButtonViz implements SpecialVisualization {
argument: string[], argument: string[],
feature: Feature feature: Feature
): BaseUIElement { ): BaseUIElement {
const to_point_index = this.args.findIndex((arg) => arg.name === "to_point")
const summarizePointArg = argument[to_point_index].toLowerCase()
if (feature.geometry.type !== "Point") { if (feature.geometry.type !== "Point") {
if (summarizePointArg !== "no" && summarizePointArg !== "false") {
feature = GeoOperations.centerpoint(feature)
} else {
return Translations.t.general.add.import.wrongType.SetClass("alert") return Translations.t.general.add.import.wrongType.SetClass("alert")
} }
}
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument) const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs) const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs)
const importFlow = new PointImportFlowState( const importFlow = new PointImportFlowState(

View file

@ -88,7 +88,11 @@ export class PointImportFlowState extends ImportFlow<PointImportFlowArguments> {
) )
} else { } else {
console.log("Marking maproulette task as fixed") console.log("Marking maproulette task as fixed")
await Maproulette.singleton.closeTask(Number(maproulette_id), Maproulette.STATUS_FIXED, this.state) await Maproulette.singleton.closeTask(
Number(maproulette_id),
Maproulette.STATUS_FIXED,
this.state
)
originalFeatureTags.data["mr_taskStatus"] = "Fixed" originalFeatureTags.data["mr_taskStatus"] = "Fixed"
originalFeatureTags.ping() originalFeatureTags.ping()
} }

View file

@ -159,9 +159,14 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
const maproulette_id = tags.data[maproulette_id_key] const maproulette_id = tags.data[maproulette_id_key]
const maproulette_feature = state.indexedFeatures.featuresById.data.get(maproulette_id) const maproulette_feature = state.indexedFeatures.featuresById.data.get(maproulette_id)
const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId) const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId)
await Maproulette.singleton.closeTask(maproulette_task_id, Maproulette.STATUS_FIXED, state, { await Maproulette.singleton.closeTask(
maproulette_task_id,
Maproulette.STATUS_FIXED,
state,
{
comment: "Tags are copied onto " + targetId + " with MapComplete", comment: "Tags are copied onto " + targetId + " with MapComplete",
}) }
)
maproulette_feature.properties["mr_taskStatus"] = "Fixed" maproulette_feature.properties["mr_taskStatus"] = "Fixed"
state.featureProperties.getStore(maproulette_id).ping() state.featureProperties.getStore(maproulette_id).ping()
} }

View file

@ -2,7 +2,11 @@ import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement" import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement" import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title" import Title from "./Base/Title"
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.svelte" import MinimapViz from "./Popup/MinimapViz.svelte"
import { ShareLinkViz } from "./Popup/ShareLinkViz" import { ShareLinkViz } from "./Popup/ShareLinkViz"
@ -750,7 +754,7 @@ export default class SpecialVisualizations {
feature, feature,
labelText: args[1], labelText: args[1],
image: args[2], image: args[2],
noBlur: noBlur === "true" || noBlur === "yes" noBlur: noBlur === "true" || noBlur === "yes",
}) })
}, },
}, },
@ -1862,7 +1866,7 @@ export default class SpecialVisualizations {
}) })
const externalData: Store<{ success: GeoJsonProperties } | { error: any }> = const externalData: Store<{ success: GeoJsonProperties } | { error: any }> =
sourceUrl.bindD( sourceUrl.bindD(
url => { (url) => {
const country = countryStore.data const country = countryStore.data
if (url.startsWith("https://data.velopark.be/")) { if (url.startsWith("https://data.velopark.be/")) {
return Stores.FromPromiseWithErr( return Stores.FromPromiseWithErr(

View file

@ -206,13 +206,15 @@
{ {
const summaryTileServer = Constants.VectorTileServer const summaryTileServer = Constants.VectorTileServer
// "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf", // "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf",
const status = testDownload(Utils.SubstituteKeys(summaryTileServer, { const status = testDownload(
Utils.SubstituteKeys(summaryTileServer, {
type: "pois", type: "pois",
layer: "food", layer: "food",
z: 14, z: 14,
x: 8848, x: 8848,
y: 5828 y: 5828,
})) })
)
services.push({ services.push({
name: summaryTileServer, name: summaryTileServer,
status: status.mapD((s) => { status: status.mapD((s) => {
@ -221,11 +223,10 @@
} }
return "online" return "online"
}), }),
message: new ImmutableStore("See SettingUpPSQL.md to fix") message: new ImmutableStore("See SettingUpPSQL.md to fix"),
}) })
} }
{ {
const s = Constants.countryCoderEndpoint const s = Constants.countryCoderEndpoint
const status = testDownload(s + "/0.0.0.json") const status = testDownload(s + "/0.0.0.json")

View file

@ -105,11 +105,11 @@
let canZoomIn = mapproperties.maxzoom.map( let canZoomIn = mapproperties.maxzoom.map(
(mz) => mapproperties.zoom.data < mz, (mz) => mapproperties.zoom.data < mz,
[mapproperties.zoom], [mapproperties.zoom]
) )
let canZoomOut = mapproperties.minzoom.map( let canZoomOut = mapproperties.minzoom.map(
(mz) => mapproperties.zoom.data > mz, (mz) => mapproperties.zoom.data > mz,
[mapproperties.zoom], [mapproperties.zoom]
) )
let rasterLayerName = let rasterLayerName =
@ -118,7 +118,7 @@
onDestroy( onDestroy(
rasterLayer.addCallbackAndRunD((l) => { rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name rasterLayerName = l.properties.name
}), })
) )
debug.addCallbackAndRun((dbg) => { debug.addCallbackAndRun((dbg) => {

View file

@ -1,7 +1,7 @@
{ {
"contributors": [ "contributors": [
{ {
"commits": 8729, "commits": 8779,
"contributor": "Pieter Vander Vennet" "contributor": "Pieter Vander Vennet"
}, },
{ {

View file

@ -1,5 +1,6 @@
{ {
"ca": "català", "ca": "català",
"cs": "čeština",
"da": "dansk", "da": "dansk",
"de": "Deutsch", "de": "Deutsch",
"en": "English", "en": "English",
@ -23,7 +24,6 @@
"sl": "slovenščina", "sl": "slovenščina",
"sv": "svenska", "sv": "svenska",
"uk": "українська мова", "uk": "українська мова",
"zgh": "ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ ⵜⴰⵎⵖⵔⵉⴱⵉⵜ",
"zh_Hans": "简体中文", "zh_Hans": "简体中文",
"zh_Hant": "繁體中文" "zh_Hant": "繁體中文"
} }

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@
"contributor": "paunofu" "contributor": "paunofu"
}, },
{ {
"commits": 150, "commits": 154,
"contributor": "Anonymous" "contributor": "Anonymous"
}, },
{ {
@ -49,17 +49,17 @@
"contributor": "gallegonovato" "contributor": "gallegonovato"
}, },
{ {
"commits": 45, "commits": 47,
"contributor": "Babos Gábor"
},
{
"commits": 44,
"contributor": "Supaplex" "contributor": "Supaplex"
}, },
{ {
"commits": 42, "commits": 46,
"contributor": "Midgard" "contributor": "Midgard"
}, },
{
"commits": 45,
"contributor": "Babos Gábor"
},
{ {
"commits": 38, "commits": 38,
"contributor": "Lucas" "contributor": "Lucas"
@ -364,6 +364,10 @@
"commits": 4, "commits": 4,
"contributor": "Jan Zabel" "contributor": "Jan Zabel"
}, },
{
"commits": 3,
"contributor": "Gábor"
},
{ {
"commits": 3, "commits": 3,
"contributor": "Michal Čermák" "contributor": "Michal Čermák"
@ -448,10 +452,6 @@
"commits": 3, "commits": 3,
"contributor": "SiegbjornSitumeang" "contributor": "SiegbjornSitumeang"
}, },
{
"commits": 2,
"contributor": "Gábor"
},
{ {
"commits": 2, "commits": 2,
"contributor": "Héctor Ochoa Ortiz" "contributor": "Héctor Ochoa Ortiz"

View file

@ -128,7 +128,7 @@ describe("RewriteSpecial", function () {
expect(r).toEqual({ expect(r).toEqual({
id: "uk_addresses_import_button", id: "uk_addresses_import_button",
render: { render: {
"*": "{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,)}", "*": "{import_button(address,urpn_count=$urpn_count;ref:GB:uprn=$ref:GB:uprn$,Add this address,./assets/themes/uk_addresses/housenumber_add.svg,,,,,)}",
}, },
}) })
}) })