Repository: streetcomplete/StreetComplete (language: kotlin)

Issue:
Support new street parking tagging scheme
The proposal for a [new street parking schema](https://wiki.openstreetmap.org/wiki/Proposed_features/street_parking_revision) will very likely be approved.

Part of the proposal is to deprecate the old schema. Hence, in order to not use deprecated tags, the app must use the new schema too.


**Affected files**
- [StreetParkingParser.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParser.kt) + [tests](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParserKtTest.kt)
- [StreetParkingCreator.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreator.kt) + [tests](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreatorKtTest.kt)
- maybe [StreetParking.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParking.kt) + [StreetParkingItem.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingItem.kt) + [tests](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingKtTest.kt) and [StreetParkingOverlay.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlay.kt) + [StreetParkingOverlayForm.kt](https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlayForm.kt) in case new selections should be added or changed

**Implementation**
The parser that parses the old scheme should first be renamed to `OldStreetParkingParser` and be used as a fallback for a while as long as roads that are tagged with the old schema are still around.

<strike>
<b>Coordination required</b>

In the proposal, there was a mention about that a mechanical edit could be done in order to replace the old tags in one go so that data consumers (such as StreetComplete, too) do not need to be able to parse both the new and old schema.

If this is done, implementation & release of a StreetComplete version with parsing for and tagging the old schema replaced must be done in concert with those that do organize the mechanical edit (ping @SupaplexOSM). The mechanical edit should be only done after most users of StreetComplete updated to the new version that tags with the new schema.

If in the end **no** mechanical edit is done, it means that </strike> [ see [comment by @SupaplexOSM](https://github.com/streetcomplete/StreetComplete/issues/4664#issuecomment-1336277631) ] the `OldStreetParkingParser` must stick around probably indefinitely. StreetComplete would then only re-tag to the new tags if the user touches, that is, _changes_ the parking situation on the street. So as long as the parking situation does not change, those streets will not be re-tagged with the new schema.

Whether or not and when a mechanical edit will be done does not block the implementation of this ticket though.


Discussion hints:
I just posted the main reasons in #4562 that prevent a / prevents us from a mechanical edit. However, if someone feels challenged to support us in this, we would of course be very happy.

Hopefully, the numbers of the old tagging decrease significantly in near future and may be erased completely after some time (but years, I think) - we are trying to support this development with some [tools](https://wiki.openstreetmap.org/wiki/Proposed_features/street_parking_revision#Call_for_participation:_Updating_parking_lane_data) ([Tag Translator](https://osmberlin.github.io/osm-tag-updater/), [JOSM stylesheet](https://github.com/SupaplexOSM/JOSM-Parking-Lane-Style), probably MapRoulette Challenge...).

So reading old and writing new schema seems to be necessary.
I looked at the code recently with @SupaplexOSM and tried to add notes on what / how to update the parking overlay.

We hope that this will help someone who knows more about the code to going on this faster.

## Draft PR with notes in the code

https://github.com/streetcomplete/StreetComplete/pull/4717

## What needs to happen to support the old tagging (interpret and show it, but not write it)

The goal is

- SC interprets the old tags
- SC displays old/new the same (other tools/processes are used to update old to new like MapRoulette)
- SC only writes new tags

### `String.toParkingPosition()`

```kotlin
private fun String.toParkingPosition() = when (this) {
    "lane", "on_street", "on-street" -> LANE // add 'lane'
    "half_on_kerb" -> HALF_ON_KERB
    "on_kerb" -> ON_KERB
    // "painted_area_only", "marked" -> PAINTED_AREA_ONLY // remove
    "lay_by", "street_side", "bays" -> STREET_SIDE
    "shoulder" -> SHOULDER // added
    else -> null
}
```

### Translation table

| old tag                                                      | new tag                                                                                                                                  |
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `parking:lane:$side=*`                                       | => `parking:$side:orientation=` (x-transformation)                                                                                       |
| `parking:lane:$side:$orientation=*`                          | => `parking:$side=` (x-transformation)                                                                                                   |
| `parking:condition:$side=no`                                 | => omit (`parking:$side=no` is enough; there is `:restriction` and `parking:$side:reason=narrow/dual_carriadeway/…` to tag more details) |
| `parking:condition:$side=no_stopping/no_standing/no_parking` | => `parking:$side:restriction=no_stopping/no_standing/no_parking`                                                                        |

See below about `painted_area_only` and `marked`.

## What needs to happen to support the new schema

### remove `parking:lane:$side:$orientation=painted_area_only` and `parking:lane:$side=marked`

This is the most complex one since the new schema remove this value and the old schema was kind of broken in this case. We cannot tell which position a segment tagged `painted_area_only` or `marked` has.

Our recommendation is to not interpret this value at all and mark those segments as "not mapped" in terms of parking. So users need to re-tag them. (The UI could show the tagged orientation based on `parking:lane:$side=parallel|etc`.)

SC should then remove the old tagging.

Luckily this value was not used a lot: 5.665 <details><summary>Detailed numbers…</summary>

* 1779 https://taginfo.openstreetmap.org/tags/parking:lane:right:parallel=painted_area_only#overview
* 1443 https://taginfo.openstreetmap.org/tags/parking:lane:left:parallel=painted_area_only#overview
* 1133 https://taginfo.openstreetmap.org/tags/parking:lane:both:parallel=painted_area_only#overview
* 191 https://taginfo.openstreetmap.org/tags/parking:lane:right:diagonal=painted_area_only#overview
* 228 https://taginfo.openstreetmap.org/tags/parking:lane:left:diagonal=painted_area_only#overview
* 44 https://taginfo.openstreetmap.org/tags/parking:lane:both:diagonal=painted_area_only#overview
* 280 https://taginfo.openstreetmap.org/tags/parking:lane:right:perpendicular=painted_area_only#overview
* 299 https://taginfo.openstreetmap.org/tags/parking:lane:left:perpendicular=painted_area_only#overview
* 268 https://taginfo.openstreetmap.org/tags/parking:lane:both:perpendicular=painted_area_only#overview

</details> 

## rename `ON_STREET` => `LANE`

A simply 1:1 rename. (No change to `ParkingPosition.estimatedWidthOnRoadFactor` etc. needed)

## consider `shoulder`

This is not a new value `parking:lane:$side:$orientation=shoulder`. There was a reason to omit it in the initial release. And the value is used very little ([new schema](https://taginfo.openstreetmap.org/keys/parking%3Aright#values) 0.44%, [old schema](https://taginfo.openstreetmap.org/keys/parking%3Alane%3Aright%3Aparallel#values) 0.17%). And it is super similar to `on_kerb`, so hard to describe in the answer panel.

We recommend to continue omitting it from the answers. Or at least to add it in an separate update, so this does not get overburdened.

However, it would be good to visually display `shoulder` as valid and tagged segment – the same way as `street_side` (or `ON_KERB`) is displayed.

It looks like parking:$side:markings=yes is in the final version of the schema, so it should still be supported in the new scheme even though the old tags are ambiguous. And if the old tags on a way have enough information to also indicate the position, they ought to be translated.
Is anyone already working on this? I have to admit that I'm a bit impatient for this, as SC is by far the most convenient editor for street parking editing.
Yes, I will together with @tordans.
The necessary work on it is by the way financed by @gislars who is part of the Berlin OSM community and quite into the Verkehrswende topic.

So much in fact that he is working together with @SupaplexOSM and @tordans to create a map visualizing street parking (in Berlin), see https://parkraum.osm-verkehrswende.org/. I.e. it is the first data consumer to comprehensively visualize parking data, supporting of course the new street parking tagging scheme (it is based on the "Parkraumkarte Neukölln" by @SupaplexOSM ). 

This [osm-parking-processing](https://github.com/osmberlin/osm-parking-processing) project is funded by the Prototypefund, but for the changes to be made on StreetComplete he pays out of his own pocket. :-)

Pull request: Support new parking schema (and add staggered parking)
Fixes #4664

Support new parking tagging schema:
- understand the tags from the new `parking:<side>` schema
- still understand old tags from `parking:lane` schema. If both old and new are tagged, new tags take precedence
- when (re)tagging, always tag according to the new schema and clear any tags of the old schema
- all tested of course

<img align="left" src="https://user-images.githubusercontent.com/4661658/215157396-9092c21e-e8f0-4376-9300-1a535b730cff.png" width="360">
<img align="left" src="https://user-images.githubusercontent.com/4661658/215158792-7a0310f0-2a90-4378-a2e7-bd283336d8fe.png" width="360">

<br clear="left">

### More details

- `parking:lane:<side>:<type>=painted_area_only` becomes `parking:<side>=lane` + `parking:<side>:markings=yes` + `parking:<side>:staggered=yes` in the new schema. It is also still available as a selection because the description text for staggered parking would become too long ("On street on alternating sides **or** only in dedicated spaces"), IMO
- added two selectable options (per orientation) for ["staggered" parking](https://wiki.openstreetmap.org/wiki/Street_parking#Staggered_parking_on_narrow_roads). Did not use the word "staggered" for the source (en-US) wording after some discussion in the #language channel on [slack US](https://osmus.slack.com/archives/C01BU04GW7L/p1674827093328859). Other suggested wordings included _"room for parking on one side at a time", "haphazard parking"_. Note in particular this comment: _“Staggered left- and right-side parking” isn’t terrible, but it implies a degree of planning that just isn’t there with the unmarked case you brought up."_
- the short descriptions of the selections are now shown in the preview WYSIWYG view because especially the "staggered" stuff is not that self explanatory
- `shoulder` as a value is (currently) not supported. If it will be supported, then only as a synonym to `on_kerb` with the original tagging being preserved if it is already tagged with `shoulder`. Reasons summarized here: https://wiki.openstreetmap.org/wiki/Talk:Street_parking#Suggestion_to_remove_parking:side=shoulder

Test command: ./gradlew :app:testDebugUnitTest --tests "de.westnordost.streetcomplete.osm.street_parking.StreetParkingParserKtTest" --tests "de.westnordost.streetcomplete.osm.street_parking.StreetParkingCreatorKtTest"

Fix patch:
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
index 21d189e8c8a..cdf179e8927 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt
@@ -79,7 +79,7 @@ private class Rotate180Degrees(drawable: Drawable) : DrawableWrapperCompat(drawa
 
 private fun Cycleway.getFloatingIconResId(isContraflowInOneway: Boolean, noEntrySignDrawableResId: Int): Int? = when (this) {
     NONE ->     if (isContraflowInOneway) noEntrySignDrawableResId else null
-    SEPARATE -> R.drawable.ic_sidewalk_floating_separate
+    SEPARATE -> R.drawable.ic_floating_separate
     else ->     null
 }
 
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/sidewalk/SidewalkItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/sidewalk/SidewalkItem.kt
index 8a68031cfc4..59a77640e0c 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/sidewalk/SidewalkItem.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/sidewalk/SidewalkItem.kt
@@ -33,7 +33,7 @@ private val Sidewalk.imageResId get() = when (this) {
 }
 
 private val Sidewalk.floatingIconResId get() = when (this) {
-    SEPARATE -> R.drawable.ic_sidewalk_floating_separate
+    SEPARATE -> R.drawable.ic_floating_separate
     else -> null
 }
 
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParking.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParking.kt
index 73e1d864212..2c8ccdb9bf2 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParking.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParking.kt
@@ -1,11 +1,7 @@
 package de.westnordost.streetcomplete.osm.street_parking
 
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.DIAGONAL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PARALLEL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PERPENDICULAR
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.*
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.*
 import kotlinx.serialization.Serializable
 
 data class LeftAndRightStreetParking(val left: StreetParking?, val right: StreetParking?)
@@ -31,9 +27,11 @@ enum class ParkingOrientation {
 
 enum class ParkingPosition {
     ON_STREET,
-    HALF_ON_KERB,
-    ON_KERB,
+    HALF_ON_STREET,
+    OFF_STREET,
     STREET_SIDE,
+    STAGGERED_ON_STREET,
+    STAGGERED_HALF_ON_STREET,
     PAINTED_AREA_ONLY
 }
 
@@ -47,6 +45,11 @@ private val StreetParking.isValid: Boolean get() = when (this) {
     else -> true
 }
 
+val ParkingPosition.isStaggered: Boolean get() = when (this) {
+    STAGGERED_HALF_ON_STREET, STAGGERED_ON_STREET, PAINTED_AREA_ONLY -> true
+    else -> false
+}
+
 val StreetParking.estimatedWidthOnRoad: Float get() = when (this) {
     is StreetParkingPositionAndOrientation -> orientation.estimatedWidth * position.estimatedWidthOnRoadFactor
     else -> 0f // otherwise let's assume it's not on the street itself
@@ -65,7 +68,9 @@ private val ParkingOrientation.estimatedWidth: Float get() = when (this) {
 
 private val ParkingPosition.estimatedWidthOnRoadFactor: Float get() = when (this) {
     ON_STREET -> 1f
-    HALF_ON_KERB -> 0.5f
-    ON_KERB -> 0f
+    HALF_ON_STREET -> 0.5f
+    STAGGERED_ON_STREET -> 0.5f // because cars park not left and right at the same time
+    STAGGERED_HALF_ON_STREET -> 0.25f // because cars park not left and right at the same time
+    OFF_STREET -> 0f
     else -> 0.5f // otherwise let's assume it is somehow on the street
 }
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreator.kt
index 87d1a9a34d5..ac605ac5f8a 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreator.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreator.kt
@@ -4,59 +4,94 @@ import de.westnordost.streetcomplete.osm.Tags
 import de.westnordost.streetcomplete.osm.expandSides
 import de.westnordost.streetcomplete.osm.hasCheckDateForKey
 import de.westnordost.streetcomplete.osm.mergeSides
+import de.westnordost.streetcomplete.osm.removeCheckDatesForKey
+import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.*
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.*
 import de.westnordost.streetcomplete.osm.updateCheckDateForKey
 
 fun LeftAndRightStreetParking.applyTo(tags: Tags) {
     if (left == null && right == null) return
     /* for being able to modify only one side (e.g. `left` is null while `right` is not null),
-       the sides conflated in `:both` keys need to be separated first. E.g. `parking:lane=no`
+       the sides conflated in `:both` keys need to be separated first. E.g. `parking:both=no`
        when left side is made `separate´ should become
-       - parking:lane:right=no
-       - parking:lane:left=separate
+       - parking:right=no
+       - parking:left=separate
        First separating the values and then later conflating them again, if possible, solves this.
      */
-    tags.expandSides("parking:lane")
-    ParkingOrientation.values().forEach { tags.expandSides("parking:lane", it.osmValue, true) }
+    tags.expandSides("parking", includeBareTag = false)
+    tags.expandSides("parking", "orientation", includeBareTag = false)
+    tags.expandSides("parking", "markings", includeBareTag = false)
+    tags.expandSides("parking", "staggered", includeBareTag = false)
 
-    // parking:lane:<left/right>
+    // parking:<left/right>
     left?.applyTo(tags, "left")
     right?.applyTo(tags, "right")
 
-    tags.mergeSides("parking:lane")
-    ParkingOrientation.values().forEach { tags.mergeSides("parking:lane", it.osmValue) }
+    tags.mergeSides("parking")
+    tags.mergeSides("parking", "orientation")
+    tags.mergeSides("parking", "markings")
+    tags.mergeSides("parking", "staggered")
 
-    if (!tags.hasChanges || tags.hasCheckDateForKey("parking:lane")) {
-        tags.updateCheckDateForKey("parking:lane")
+    if (!tags.hasChanges || tags.hasCheckDateForKey("parking")) {
+        tags.updateCheckDateForKey("parking")
     }
+
+    for (side in listOf(":left", ":right", ":both", "")) {
+        tags.remove("parking:lane$side")
+        tags.remove("parking:lane$side:perpendicular")
+        tags.remove("parking:lane$side:diagonal")
+        tags.remove("parking:lane$side:parallel")
+    }
+    tags.removeCheckDatesForKey("parking:lane")
 }
 
 private fun StreetParking.applyTo(tags: Tags, side: String) {
-    tags["parking:lane:$side"] = osmLaneValue
-    // clear previous orientation(s)
-    ParkingOrientation.values().forEach { tags.remove("parking:lane:$side:${it.osmValue}") }
+    tags["parking:$side"] =
+        // "shoulder" as value is not supported but instead interpreted as "off street", i.e. the
+        // same as "on_kerb". However, since "shoulder" is an approved value, it will not be over-
+        // written by StreetComplete if "adjacent to street" is selected
+        // https://wiki.openstreetmap.org/wiki/Talk:Street_parking#Suggestion_to_remove_parking:side=shoulder
+        if (osmPositionValue == "on_kerb" && tags["parking:$side"] == "shoulder") "shoulder"
+        else osmPositionValue
+
     if (this is StreetParkingPositionAndOrientation) {
-        tags["parking:lane:$side:$osmLaneValue"] = position.osmValue
+        tags["parking:$side:orientation"] = orientation.osmValue
+        if (position == PAINTED_AREA_ONLY) {
+            tags["parking:$side:markings"] = "yes"
+        }
+        if (position.isStaggered) {
+            tags["parking:$side:staggered"] = "yes"
+        } else {
+            tags.remove("parking:$side:staggered")
+        }
+    } else {
+        tags.remove("parking:$side:orientation")
+        tags.remove("parking:$side:markings")
+        tags.remove("parking:$side:staggered")
     }
 }
 
-/** get the OSM value for the parking:lane key */
-private val StreetParking.osmLaneValue get() = when (this) {
-    is StreetParkingPositionAndOrientation -> orientation.osmValue
+/** get the OSM value for the parking:<side> key */
+private val StreetParking.osmPositionValue get() = when (this) {
+    is StreetParkingPositionAndOrientation -> position.osmValue
     NoStreetParking -> "no"
     StreetParkingSeparate -> "separate"
     UnknownStreetParking, IncompleteStreetParking -> throw IllegalArgumentException("Attempting to tag invalid parking lane")
 }
 
 private val ParkingPosition.osmValue get() = when (this) {
-    ParkingPosition.ON_STREET ->         "on_street"
-    ParkingPosition.HALF_ON_KERB ->      "half_on_kerb"
-    ParkingPosition.ON_KERB ->           "on_kerb"
-    ParkingPosition.STREET_SIDE ->       "street_side"
-    ParkingPosition.PAINTED_AREA_ONLY -> "painted_area_only"
+    ON_STREET, STAGGERED_ON_STREET, PAINTED_AREA_ONLY ->
+        "lane"
+    HALF_ON_STREET, STAGGERED_HALF_ON_STREET ->
+        "half_on_kerb"
+    OFF_STREET ->
+        "on_kerb"
+    STREET_SIDE ->
+        "street_side"
 }
 
 private val ParkingOrientation.osmValue get() = when (this) {
-    ParkingOrientation.PARALLEL ->      "parallel"
-    ParkingOrientation.DIAGONAL ->      "diagonal"
-    ParkingOrientation.PERPENDICULAR -> "perpendicular"
+    PARALLEL ->      "parallel"
+    DIAGONAL ->      "diagonal"
+    PERPENDICULAR -> "perpendicular"
 }
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingDrawable.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingDrawable.kt
index 6ea25c3df07..796ff561232 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingDrawable.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingDrawable.kt
@@ -11,10 +11,12 @@ import de.westnordost.streetcomplete.R
 import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.DIAGONAL
 import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PARALLEL
 import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PERPENDICULAR
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.OFF_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.PAINTED_AREA_ONLY
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STREET_SIDE
 import de.westnordost.streetcomplete.util.ktx.isApril1st
 import kotlin.math.ceil
@@ -41,6 +43,13 @@ class StreetParkingDrawable(
     override fun getIntrinsicWidth(): Int = round(width * context.resources.displayMetrics.density).toInt()
     override fun getIntrinsicHeight(): Int = round(height * context.resources.displayMetrics.density).toInt()
 
+    /** Y-Offset in percent (0..<1) */
+    var phase: Float = 0f
+    set(value) {
+        field = value
+        invalidateSelf()
+    }
+
     override fun draw(canvas: Canvas) {
         if (!isVisible) return
 
@@ -59,35 +68,39 @@ class StreetParkingDrawable(
         val carCount = parkingOrientation.carCount
         val backgroundResId = getStreetDrawableResId(parkingOrientation, parkingPosition)
         val nyanResId = if (isApril1st()) R.drawable.car_nyan else null
-
-        for (y in 0 until ceil(bounds.height() / height).toInt()) {
-
-            // drawing the street background
-            if (backgroundResId != null) {
-                val background = context.getDrawable(backgroundResId)!!
-                val backgroundHeight = (background.intrinsicHeight.toDouble() / background.intrinsicWidth * width).toInt()
-                background.setBounds(0, 0, width, backgroundHeight)
+        val repeats = ceil(bounds.height() / height).toInt()
+
+        // drawing the street background
+        if (backgroundResId != null) {
+            val background = context.getDrawable(backgroundResId)!!
+            val backgroundHeight = (background.intrinsicHeight.toDouble() / background.intrinsicWidth * width).toInt()
+            val offsetY = (phase * height).toInt()
+            for (i in 0 until repeats) {
+                val y = (i * height).toInt() + offsetY
+                background.setBounds(0, y, width, backgroundHeight + y)
                 background.draw(canvas)
             }
-
-            // drawing the cars
-            for (i in 0 until carCount) {
-                if (i in omittedCarIndices) continue
-                val carResId = nyanResId ?: staticCarDrawableResId ?: CAR_RES_IDS[Random.nextInt(
-                    CAR_RES_IDS.size)]
-                val car = context.getDrawable(carResId)!!
-                val carHeight = car.intrinsicHeight * carWidth / car.intrinsicWidth
-                val paddingY = (height / carCount - carHeight) / 2
-                val carY = 1f * height / carCount * i + paddingY
-                canvas.withSave {
-                    canvas.translate(carX, carY)
-                    canvas.rotate(carRotation, carWidth / 2, carHeight / 2)
-                    car.setBounds(0, 0, carWidth.toInt(), carHeight.toInt())
-                    car.draw(canvas)
-                }
+            if (offsetY != 0) {
+                background.setBounds(0, -backgroundHeight + offsetY, width, offsetY)
+                background.draw(canvas)
             }
+        }
 
-            canvas.translate(0f, height)
+        // drawing the cars
+        for (i in 0 until carCount * repeats) {
+            if (i % carCount in omittedCarIndices) continue
+            val carResId =
+                nyanResId ?: staticCarDrawableResId ?: CAR_RES_IDS[Random.nextInt(CAR_RES_IDS.size)]
+            val car = context.getDrawable(carResId)!!
+            val carHeight = car.intrinsicHeight * carWidth / car.intrinsicWidth
+            val paddingY = (height / carCount - carHeight) / 2
+            val carY = (1f * height / carCount * i + paddingY + phase * height) % (height * repeats)
+            canvas.withSave {
+                canvas.translate(carX, carY)
+                canvas.rotate(carRotation, carWidth / 2, carHeight / 2)
+                car.setBounds(0, 0, carWidth.toInt(), carHeight.toInt())
+                car.draw(canvas)
+            }
         }
     }
 }
@@ -95,15 +108,15 @@ class StreetParkingDrawable(
 /** drawable res id of the street in the background */
 private fun getStreetDrawableResId(orientation: ParkingOrientation, position: ParkingPosition?): Int? =
     when (position) {
-        ON_STREET -> when (orientation) {
+        ON_STREET, STAGGERED_ON_STREET -> when (orientation) {
             PARALLEL -> R.drawable.ic_street
             else -> R.drawable.ic_street_broad
         }
-        HALF_ON_KERB -> when (orientation) {
+        HALF_ON_STREET, STAGGERED_HALF_ON_STREET -> when (orientation) {
             PARALLEL -> R.drawable.ic_street_narrow
             else -> R.drawable.ic_street
         }
-        ON_KERB -> R.drawable.ic_street_very_narrow
+        OFF_STREET -> R.drawable.ic_street_very_narrow
         STREET_SIDE -> when (orientation) {
             PARALLEL -> R.drawable.ic_street_parking_bays_parallel
             DIAGONAL -> R.drawable.ic_street_parking_bays_diagonal
@@ -132,10 +145,10 @@ private fun getOmittedCarIndices(orientation: ParkingOrientation, position: Park
             DIAGONAL -> listOf(2, 3)
             PERPENDICULAR -> listOf(0, 3, 4, 7)
         }
-        PAINTED_AREA_ONLY -> when (orientation) {
+        PAINTED_AREA_ONLY, STAGGERED_ON_STREET, STAGGERED_HALF_ON_STREET -> when (orientation) {
             PARALLEL -> listOf(0, 3)
-            DIAGONAL -> listOf(0, 1, 4, 5)
-            PERPENDICULAR -> listOf(0, 1, 5, 6, 7)
+            DIAGONAL -> listOf(0, 1, 5)
+            PERPENDICULAR -> listOf(0, 1, 6, 7)
         }
         else -> emptyList()
     }
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingItem.kt
index 57f11b37c6d..fc228422391 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingItem.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingItem.kt
@@ -2,10 +2,12 @@ package de.westnordost.streetcomplete.osm.street_parking
 
 import android.content.Context
 import de.westnordost.streetcomplete.R
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.OFF_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.PAINTED_AREA_ONLY
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STREET_SIDE
 import de.westnordost.streetcomplete.view.DrawableImage
 import de.westnordost.streetcomplete.view.Image
@@ -29,11 +31,12 @@ fun StreetParking.asItem(
 
 fun StreetParking.asStreetSideItem(
     context: Context,
-    isUpsideDown: Boolean
+    isUpsideDown: Boolean,
+    isRightSide: Boolean
 ): StreetSideDisplayItem<StreetParking> = StreetSideItem2(
     this,
-    getIcon(context, isUpsideDown),
-    null,
+    getIcon(context, isUpsideDown, isRightSide),
+    titleResId?.let { ResText(it) },
     getDialogIcon(context, isUpsideDown),
     getFloatingIcon()
 )
@@ -46,9 +49,9 @@ private val StreetParking.titleResId: Int? get() = when (this) {
 }
 
 /** Image that should be shown in the street side select puzzle */
-private fun StreetParking.getIcon(context: Context, isUpsideDown: Boolean): Image = when (this) {
+private fun StreetParking.getIcon(context: Context, isUpsideDown: Boolean, isRightSide: Boolean): Image = when (this) {
     is StreetParkingPositionAndOrientation ->
-        getIcon(context, isUpsideDown)
+        getIcon(context, isUpsideDown, isRightSide)
     NoStreetParking, StreetParkingSeparate ->
         ResImage(R.drawable.ic_street_none)
     UnknownStreetParking, IncompleteStreetParking ->
@@ -60,19 +63,18 @@ private fun StreetParking.getDialogIcon(context: Context, isUpsideDown: Boolean)
     is StreetParkingPositionAndOrientation ->
         getDialogIcon(context, isUpsideDown)
     NoStreetParking ->
-        ResImage(R.drawable.ic_parking_no)
+        ResImage(R.drawable.ic_floating_no)
     StreetParkingSeparate ->
-        ResImage(R.drawable.ic_parking_separate)
+        ResImage(R.drawable.ic_floating_separate)
     IncompleteStreetParking, UnknownStreetParking ->
         ResImage(if (isUpsideDown) R.drawable.ic_street_side_unknown_l else R.drawable.ic_street_side_unknown)
 }
 
 /** Icon that should be shown as the floating icon in the street side select puzzle */
 private fun StreetParking.getFloatingIcon(): Image? = when (this) {
-    StreetParkingSeparate -> R.drawable.ic_parking_separate
-    NoStreetParking -> R.drawable.ic_parking_no
+    StreetParkingSeparate -> ResImage(R.drawable.ic_floating_separate)
     else -> null
-}?.let { ResImage(it) }
+}
 
 fun StreetParkingPositionAndOrientation.asItem(context: Context, isUpsideDown: Boolean) =
     Item2(this, getDialogIcon(context, isUpsideDown), ResText(position.titleResId))
@@ -84,21 +86,29 @@ private fun StreetParkingPositionAndOrientation.getDialogIcon(context: Context,
 
 /** An image for a street parking to be displayed shows a wide variety of different cars so that
  *  it looks nicer and/or closer to reality */
-private fun StreetParkingPositionAndOrientation.getIcon(context: Context, isUpsideDown: Boolean): Image =
-    DrawableImage(StreetParkingDrawable(context, orientation, position, isUpsideDown, 128, 512))
+private fun StreetParkingPositionAndOrientation.getIcon(context: Context, isUpsideDown: Boolean, isRightSide: Boolean): Image {
+    val drawable = StreetParkingDrawable(context, orientation, position, isUpsideDown, 128, 512)
+    // show left and right side staggered to each other
+    if (isRightSide) drawable.phase = 0.5f
+    return DrawableImage(drawable)
+}
 
 private val ParkingPosition.titleResId: Int get() = when (this) {
     ON_STREET -> R.string.street_parking_on_street
-    HALF_ON_KERB -> R.string.street_parking_half_on_kerb
-    ON_KERB -> R.string.street_parking_on_kerb
+    HALF_ON_STREET -> R.string.street_parking_half_on_kerb
+    OFF_STREET -> R.string.street_parking_on_kerb
     STREET_SIDE -> R.string.street_parking_street_side
     PAINTED_AREA_ONLY -> R.string.street_parking_painted_area_only
+    STAGGERED_ON_STREET -> R.string.street_parking_staggered_on_street
+    STAGGERED_HALF_ON_STREET -> R.string.street_parking_staggered_half_on_kerb
 }
 
 val DISPLAYED_PARKING_POSITIONS: List<ParkingPosition> = listOf(
     ON_STREET,
-    HALF_ON_KERB,
-    ON_KERB,
+    HALF_ON_STREET,
+    OFF_STREET,
     STREET_SIDE,
+    STAGGERED_ON_STREET,
+    STAGGERED_HALF_ON_STREET,
     PAINTED_AREA_ONLY
 )
diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParser.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParser.kt
index c226f3a1b4e..2d9421767bc 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParser.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParser.kt
@@ -1,29 +1,69 @@
 package de.westnordost.streetcomplete.osm.street_parking
 
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.DIAGONAL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PARALLEL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PERPENDICULAR
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.PAINTED_AREA_ONLY
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STREET_SIDE
+import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.*
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.*
 
 fun createStreetParkingSides(tags: Map<String, String>): LeftAndRightStreetParking? {
+    // expand tags first so we do not need to deal with :both and naked tags
     val expandedTags = expandRelevantSidesTags(tags)
-    // then get the values for left and right
-    val left = createParkingForSide(expandedTags, "left")
-    val right = createParkingForSide(expandedTags, "right")
+    // first try to parse new schema
+    var left = createParkingForSide(expandedTags, "left")
+    var right = createParkingForSide(expandedTags, "right")
+    if (left == null && right == null) {
+        // otherwise parse the old schema
+        left = createParkingForSideOldSchema(expandedTags, "left")
+        right = createParkingForSideOldSchema(expandedTags, "right")
+    }
 
     if (left == null && right == null) return null
 
     return LeftAndRightStreetParking(left, right)
 }
 
-private fun createParkingForSide(tags: Map<String, String>, side: String?): StreetParking? {
-    val sideVal = if (side != null) ":$side" else ""
+/** Parsing new schema:
+ *  https://wiki.openstreetmap.org/wiki/Street_parking */
+private fun createParkingForSide(tags: Map<String, String>, side: String): StreetParking? {
+    val isStaggered = tags["parking:$side:staggered"] == "yes"
+    val markings = tags["parking:$side:markings"]
+    val hasMarkings = markings != "no" && markings != null
+
+    val position = when (tags["parking:$side"]) {
+        "lane" -> when {
+            isStaggered && hasMarkings -> PAINTED_AREA_ONLY
+            isStaggered -> STAGGERED_ON_STREET
+            else -> ON_STREET
+        }
+        "half_on_kerb" -> if (isStaggered) STAGGERED_HALF_ON_STREET else HALF_ON_STREET
+        "on_kerb" -> OFF_STREET
+        // interpret "shoulder" as "off street", i.e. the same as "on_kerb". Reasons given in
+        // https://wiki.openstreetmap.org/wiki/Talk:Street_parking#Suggestion_to_remove_parking:side=shoulder
+        "shoulder" -> OFF_STREET
+        "street_side" -> STREET_SIDE
+        "no" -> return NoStreetParking
+        "separate" -> return StreetParkingSeparate
+        "yes" -> return IncompleteStreetParking
+        null -> null
+        else -> return UnknownStreetParking
+    }
+
+    val orientation = when(tags["parking:$side:orientation"]) {
+        "parallel" -> PARALLEL
+        "diagonal" -> DIAGONAL
+        "perpendicular" -> PERPENDICULAR
+        null -> null
+        else -> return UnknownStreetParking
+    }
+
+    if (position == null && orientation == null) return null
+    if (position == null || orientation == null) return IncompleteStreetParking
 
-    val parkingValue = tags["parking:lane$sideVal"] ?: return null
+    return StreetParkingPositionAndOrientation(orientation, position)
+}
+
+/** Parsing old parking schema:
+ *  https://wiki.openstreetmap.org/wiki/Key:parking:lane */
+private fun createParkingForSideOldSchema(tags: Map<String, String>, side: String): StreetParking? {
+    val parkingValue = tags["parking:lane:$side"] ?: return null
 
     when (parkingValue) {
         "no_parking", "no_standing", "no_stopping", "no" ->
@@ -33,51 +73,57 @@ private fun createParkingForSide(tags: Map<String, String>, side: String?): Stre
         "separate" ->
             return StreetParkingSeparate
         else -> {
-            val parkingOrientation = parkingValue.toParkingOrientation()
-                // regard parking:lanes:*=marked as incomplete (because position is missing implicitly)
-                ?: return if (parkingValue == "marked") IncompleteStreetParking else UnknownStreetParking
+            // regard parking:lanes:*=marked as incomplete (because position is missing implicitly)
+            val parkingOrientation = when (parkingValue) {
+                "parallel" -> PARALLEL
+                "diagonal" -> DIAGONAL
+                "perpendicular" -> PERPENDICULAR
+                else -> null
+            } ?: return if (parkingValue == "marked") IncompleteStreetParking else UnknownStreetParking
 
-            val parkingPositionValue = tags["parking:lane$sideVal:$parkingValue"]
-                // parking position is mandatory to be regarded as complete
+            val parkingPositionValue = tags["parking:lane:$side:$parkingValue"]
+            // parking position is mandatory to be regarded as complete
                 ?: return IncompleteStreetParking
 
-            val parkingPosition = parkingPositionValue.toParkingPosition() ?: return UnknownStreetParking
+            val parkingPosition = when (parkingPositionValue) {
+                "on_street" -> ON_STREET
+                "half_on_kerb" -> HALF_ON_STREET
+                "on_kerb", "shoulder" -> OFF_STREET
+                "painted_area_only" -> PAINTED_AREA_ONLY
+                "lay_by", "street_side" -> STREET_SIDE
+
+                else -> null
+            } ?: return UnknownStreetParking
 
             return StreetParkingPositionAndOrientation(parkingOrientation, parkingPosition)
         }
     }
 }
 
-private fun String.toParkingOrientation() = when (this) {
-    "parallel" -> PARALLEL
-    "diagonal" -> DIAGONAL
-    "perpendicular" -> PERPENDICULAR
-    else -> null
-}
-
-private fun String.toParkingPosition() = when (this) {
-    "on_street", "on-street" -> ON_STREET
-    "half_on_kerb" -> HALF_ON_KERB
-    "on_kerb" -> ON_KERB
-    "painted_area_only", "marked" -> PAINTED_AREA_ONLY
-    "lay_by", "street_side", "bays" -> STREET_SIDE
-    else -> null
-}
-
 private fun expandRelevantSidesTags(tags: Map<String, String>): Map<String, String> {
     val result = tags.toMutableMap()
-    expandSidesTag("parking:lane", "", result)
-    expandSidesTag("parking:lane", "parallel", result)
-    expandSidesTag("parking:lane", "diagonal", result)
-    expandSidesTag("parking:lane", "perpendicular", result)
+    expandSidesTag("parking:lane", "", result, true)
+    expandSidesTag("parking:lane", "parallel", result, true)
+    expandSidesTag("parking:lane", "diagonal", result, true)
+    expandSidesTag("parking:lane", "perpendicular", result, true)
+    expandSidesTag("parking", "", result, false)
+    expandSidesTag("parking", "orientation", result, false)
+    expandSidesTag("parking", "staggered", result, false)
+    expandSidesTag("parking", "markings", result, false)
     return result
 }
 
 /** Expand my_tag:both and my_tag into my_tag:left and my_tag:right etc */
-private fun expandSidesTag(keyPrefix: String, keyPostfix: String, tags: MutableMap<String, String>) {
+private fun expandSidesTag(
+    keyPrefix: String,
+    keyPostfix: String,
+    tags: MutableMap<String, String>,
+    useNakedTag: Boolean
+) {
     val pre = keyPrefix
     val post = if (keyPostfix.isEmpty()) "" else ":$keyPostfix"
-    val value = tags["$pre:both$post"] ?: tags["$pre$post"]
+    var value = tags["$pre:both$post"]
+    if (value == null && useNakedTag) value = tags["$pre$post"]
     if (value != null) {
         if (!tags.containsKey("$pre:left$post")) tags["$pre:left$post"] = value
         if (!tags.containsKey("$pre:right$post")) tags["$pre:right$post"] = value
diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlay.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlay.kt
index 14a3d6118ee..1f6d2f8b829 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlay.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlay.kt
@@ -13,10 +13,12 @@ import de.westnordost.streetcomplete.osm.isPrivateOnFoot
 import de.westnordost.streetcomplete.osm.street_parking.IncompleteStreetParking
 import de.westnordost.streetcomplete.osm.street_parking.NoStreetParking
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.OFF_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.PAINTED_AREA_ONLY
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_HALF_ON_STREET
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STAGGERED_ON_STREET
 import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STREET_SIDE
 import de.westnordost.streetcomplete.osm.street_parking.StreetParking
 import de.westnordost.streetcomplete.osm.street_parking.StreetParkingPositionAndOrientation
@@ -95,14 +97,17 @@ private fun getStreetParkingStyle(element: Element): Style {
 }
 
 private val ParkingPosition.isDashed: Boolean get() = when (this) {
-    STREET_SIDE, PAINTED_AREA_ONLY -> true
+    STREET_SIDE, PAINTED_AREA_ONLY, STAGGERED_ON_STREET, STAGGERED_HALF_ON_STREET -> true
     else -> false
 }
 
 private val ParkingPosition.color: String get() = when (this) {
-    ON_STREET, PAINTED_AREA_ONLY -> Color.GOLD
-    HALF_ON_KERB ->                 Color.AQUAMARINE
-    ON_KERB, STREET_SIDE ->         Color.BLUE
+    ON_STREET, PAINTED_AREA_ONLY, STAGGERED_ON_STREET ->
+        Color.GOLD
+    HALF_ON_STREET, STAGGERED_HALF_ON_STREET ->
+        Color.AQUAMARINE
+    OFF_STREET, STREET_SIDE ->
+        Color.BLUE
 }
 
 private val StreetParking?.style: StrokeStyle get() = when (this) {
diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlayForm.kt
index 6e7a1c55fe9..c7055991b72 100644
--- a/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlayForm.kt
+++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/street_parking/StreetParkingOverlayForm.kt
@@ -73,8 +73,8 @@ class StreetParkingOverlayForm : AStreetSideSelectOverlayForm<StreetParking>() {
     }
 
     private fun initStateFromTags() {
-        streetSideSelect.setPuzzleSide(originalParking?.left?.asStreetSideItem(requireContext(), isUpsideDown(false)), false)
-        streetSideSelect.setPuzzleSide(originalParking?.right?.asStreetSideItem(requireContext(), isUpsideDown(true)), true)
+        streetSideSelect.setPuzzleSide(originalParking?.left?.asStreetSideItem(requireContext(), isUpsideDown(false), false), false)
+        streetSideSelect.setPuzzleSide(originalParking?.right?.asStreetSideItem(requireContext(), isUpsideDown(true), true), true)
     }
 
     override fun hasChanges(): Boolean =
@@ -84,7 +84,7 @@ class StreetParkingOverlayForm : AStreetSideSelectOverlayForm<StreetParking>() {
     override fun serialize(item: StreetParking) = Json.encodeToString(item)
     override fun deserialize(str: String) = Json.decodeFromString<StreetParking>(str)
     override fun asStreetSideItem(item: StreetParking, isRight: Boolean) =
-        item.asStreetSideItem(requireContext(), isUpsideDown(isRight))
+        item.asStreetSideItem(requireContext(), isUpsideDown(isRight), isRight)
 
     private fun isUpsideDown(isRight: Boolean) =
         if (isRight) isRightSideUpsideDown else isLeftSideUpsideDown
@@ -115,7 +115,7 @@ class StreetParkingOverlayForm : AStreetSideSelectOverlayForm<StreetParking>() {
 
     private fun onSelectedSide(parking: StreetParking, isRight: Boolean) {
         val ctx = context ?: return
-        streetSideSelect.replacePuzzleSide(parking.asStreetSideItem(ctx, isUpsideDown(isRight)), isRight)
+        streetSideSelect.replacePuzzleSide(parking.asStreetSideItem(ctx, isUpsideDown(isRight), isRight), isRight)
     }
 
     private fun getParkingItems(context: Context): List<DisplayItem<ParkingSelection>> =
@@ -153,8 +153,8 @@ private fun ParkingSelection.getDialogIcon(context: Context, isUpsideDown: Boole
     PARALLEL -> createParkingOrientationImage(context, isUpsideDown, ParkingOrientation.PARALLEL)
     DIAGONAL -> createParkingOrientationImage(context, isUpsideDown, ParkingOrientation.DIAGONAL)
     PERPENDICULAR -> createParkingOrientationImage(context, isUpsideDown, ParkingOrientation.PERPENDICULAR)
-    SEPARATE -> ResImage(R.drawable.ic_parking_separate)
-    NO -> ResImage(R.drawable.ic_parking_no)
+    SEPARATE -> ResImage(R.drawable.ic_floating_separate)
+    NO -> ResImage(R.drawable.ic_floating_no)
 }
 
 private fun ParkingSelection.asItem(context: Context, isUpsideDown: Boolean) =
diff --git a/app/src/main/res/drawable/ic_floating_no.xml b/app/src/main/res/drawable/ic_floating_no.xml
new file mode 100644
index 00000000000..15f79f8cc08
--- /dev/null
+++ b/app/src/main/res/drawable/ic_floating_no.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="128dp"
+    android:height="128dp"
+    android:viewportWidth="128"
+    android:viewportHeight="128">
+  <path
+      android:pathData="m94,94 l-60,-60m0,60 l60,-60"
+      android:strokeLineJoin="bevel"
+      android:strokeWidth="15"
+      android:strokeColor="#c30"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_sidewalk_floating_separate.xml b/app/src/main/res/drawable/ic_floating_separate.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_sidewalk_floating_separate.xml
rename to app/src/main/res/drawable/ic_floating_separate.xml
diff --git a/app/src/main/res/drawable/ic_parking_no.xml b/app/src/main/res/drawable/ic_parking_no.xml
deleted file mode 100644
index a8533180a28..00000000000
--- a/app/src/main/res/drawable/ic_parking_no.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="128dp"
-    android:height="128dp"
-    android:viewportWidth="128"
-    android:viewportHeight="128">
-  <path
-      android:pathData="m28.044,20.211l49.668,0c4.234,0 7.763,3.474 7.763,7.708l0,56.887c0,4.234 -3.528,6.985 -7.763,6.985l-49.668,0c-4.234,0 -7.833,-2.751 -7.833,-6.985l0,-56.887c0,-4.234 3.599,-7.708 7.833,-7.708z"
-      android:strokeWidth="8"
-      android:fillColor="#495aad"
-      android:strokeColor="#fff"/>
-  <path
-      android:pathData="m40.691,77.054l0,-41.908l12.34,0s12.504,-0.397 12.504,12.294c0,12.69 -12.504,12.385 -12.504,12.385l-12.34,0"
-      android:strokeWidth="8"
-      android:strokeColor="#fff"
-      android:strokeLineCap="square"/>
-  <path
-      android:pathData="m60.008,60.356 l48.762,48.762m-48.762,0 l48.762,-48.762"
-      android:strokeWidth="15"
-      android:strokeColor="#c30"
-      android:strokeLineCap="round"/>
-</vector>
diff --git a/app/src/main/res/drawable/ic_parking_separate.xml b/app/src/main/res/drawable/ic_parking_separate.xml
deleted file mode 100644
index b575f06f175..00000000000
--- a/app/src/main/res/drawable/ic_parking_separate.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="128dp"
-    android:height="128dp"
-    android:viewportWidth="128"
-    android:viewportHeight="128">
-  <path
-      android:pathData="m28.044,20.211l49.668,0c4.234,0 7.763,3.474 7.763,7.708l0,56.887c0,4.234 -3.528,6.985 -7.763,6.985l-49.668,0c-4.234,0 -7.833,-2.751 -7.833,-6.985l0,-56.887c0,-4.234 3.599,-7.708 7.833,-7.708z"
-      android:strokeWidth="8"
-      android:fillColor="#495aad"
-      android:strokeColor="#fff"/>
-  <path
-      android:pathData="m40.691,77.054l0,-41.908l12.34,0s12.504,-0.397 12.504,12.294c0,12.69 -12.504,12.385 -12.504,12.385l-12.34,0"
-      android:strokeWidth="8"
-      android:strokeColor="#fff"
-      android:strokeLineCap="square"/>
-  <path
-      android:pathData="m112.7,87.563 l-32.054,-22.588m-51.594,40.039c21.961,-21.72 51.071,-20.578 83.647,-17.45l-32.698,20.436"
-      android:strokeLineJoin="round"
-      android:strokeWidth="12"
-      android:strokeColor="#e48f00"
-      android:strokeLineCap="round"/>
-</vector>
diff --git a/app/src/main/res/drawable/ic_street_marked_parking_diagonal.xml b/app/src/main/res/drawable/ic_street_marked_parking_diagonal.xml
index 6a6fc0253e1..c146399b07b 100644
--- a/app/src/main/res/drawable/ic_street_marked_parking_diagonal.xml
+++ b/app/src/main/res/drawable/ic_street_marked_parking_diagonal.xml
@@ -13,6 +13,6 @@
       android:pathData="M0,0h92v256h-92z"
       android:fillColor="#808080"/>
   <path
-      android:pathData="m87.162,66.506 l-3.26,2.318 1.16,1.631 3.258,-2.32 -1.158,-1.629zM80.644,71.145 L77.385,73.465 78.545,75.094 81.805,72.774 80.644,71.145zM74.127,75.783 L70.867,78.104 72.027,79.733 75.287,77.414 74.127,75.783zM67.609,80.424 L64.351,82.742 65.51,84.373 68.769,82.053 67.609,80.424zM61.092,85.063 L57.834,87.383 58.994,89.012 62.252,86.692 61.092,85.063zM54.574,89.703 L51.316,92.022 52.476,93.651 55.734,91.332 54.574,89.703zM48.056,94.342 L44.799,96.662 45.959,98.291 49.217,95.971 48.056,94.342zM41.541,98.981 L38.281,101.301 39.441,102.93 42.699,100.612 41.541,98.981zM35.023,103.621 L31.763,105.94 32.924,107.571 36.183,105.25 35.023,103.621zM90.076,104.43 L86.816,106.75 87.977,108.379 91.236,106.061 90.076,104.43zM83.559,109.071 L80.301,111.389 81.459,113.02 84.719,110.7 83.559,109.071zM31,110.578v4h2v-4h-2zM77.041,113.709 L73.783,116.03 74.943,117.658 78.201,115.338 77.041,113.709zM70.523,118.348 L67.265,120.668 68.425,122.297 71.683,119.979 70.523,118.348zM31,118.578v4h2v-4h-2zM64.006,122.989 L60.748,125.309 61.908,126.938 65.166,124.617 64.006,122.989zM31,126.578v4h2v-4h-2zM57.49,127.627 L54.23,129.947 55.39,131.576 58.648,129.256 57.49,127.627zM50.972,132.268 L47.713,134.586 48.873,136.217 52.133,133.897 50.972,132.268zM30.999,134.578v4h2v-4h-2zM44.454,136.906 L41.195,139.227 42.355,140.856 45.615,138.535 44.454,136.906zM37.937,141.545 L34.679,143.865 35.837,145.494 39.097,143.176 37.937,141.545zM30.999,142.578v4h2v-4h-2zM88.917,146.256 L85.658,148.574 86.818,150.205 90.077,147.885 88.917,146.256zM30.999,150.578v4h2v-4h-2zM82.399,150.895 L79.139,153.215 80.3,154.844 83.559,152.524 82.399,150.895zM75.882,155.533 L72.622,157.854 73.782,159.483 77.042,157.164 75.882,155.533zM30.999,158.578v4h2v-4h-2zM69.364,160.174 L66.106,162.492 67.264,164.123 70.524,161.803 69.364,160.174zM62.846,164.813 L59.588,167.133 60.748,168.762 64.006,166.442 62.846,164.813zM30.998,166.578v4h2v-4h-2zM56.328,169.453 L53.07,171.772 54.23,173.401 57.488,171.082 56.328,169.453zM49.811,174.092 L46.553,176.412 47.713,178.041 50.971,175.721 49.811,174.092zM30.999,174.578v4h2v-4h-2zM43.296,178.731 L40.036,181.051 41.196,182.68 44.454,180.362 43.296,178.731zM30.999,182.578v4h2v-4h-2zM36.778,183.371 L33.518,185.69 34.678,187.321 37.938,185 36.778,183.371z"
+      android:pathData="m87.16,66.51 l-3.26,2.32 1.16,1.63 3.26,-2.32zM80.64,71.14 L77.38,73.46 78.54,75.09 81.8,72.77zM74.13,75.78 L70.87,78.1 72.03,79.73 75.29,77.41zM67.61,80.42 L64.35,82.74 65.51,84.37 68.77,82.05zM61.09,85.06 L57.83,87.38 58.99,89.01 62.25,86.69zM54.57,89.7 L51.32,92.02 52.48,93.65 55.73,91.33zM48.06,94.34 L44.8,96.66 45.96,98.29 49.22,95.97zM41.54,98.98 L38.28,101.3 39.44,102.93 42.7,100.61zM35.02,103.62 L31.76,105.94 32.92,107.57 36.18,105.25zM90.08,104.43 L86.82,106.75 87.98,108.38 91.24,106.06zM83.56,109.07 L80.3,111.39 81.46,113.02 84.72,110.7zM31,110.58v4h2v-4zM77.04,113.71 L73.78,116.03 74.94,117.66 78.2,115.34zM70.52,118.35 L67.27,120.67 68.43,122.3 71.68,119.98zM31,118.58v4h2v-4zM64.01,122.99 L60.75,125.31 61.91,126.94 65.17,124.62zM31,126.58v4h2v-4zM57.49,127.63 L54.23,129.95 55.39,131.58 58.65,129.26zM50.97,132.27 L47.71,134.59 48.87,136.22 52.13,133.9zM31,134.58v4h2v-4zM44.45,136.91 L41.19,139.23 42.35,140.86 45.61,138.54zM37.94,141.55 L34.68,143.87 35.84,145.49 39.1,143.18zM31,142.58v4h2v-4zM88.92,146.26 L85.66,148.57 86.82,150.21 90.08,147.88zM31,150.58v4h2v-4zM82.4,150.89 L79.14,153.21 80.3,154.84 83.56,152.52zM75.88,155.53 L72.62,157.85 73.78,159.48 77.04,157.16zM31,158.58v4h2v-4zM69.36,160.17 L66.11,162.49 67.26,164.12 70.52,161.8 69.36,160.17zM62.85,164.81 L59.59,167.13 60.75,168.76 64.01,166.44zM31,166.58v4h2v-4zM56.33,169.45 L53.07,171.77 54.23,173.4 57.49,171.08zM49.81,174.09 L46.55,176.41 47.71,178.04 50.97,175.72zM31,174.58v4h2v-4zM43.3,178.73 L40.04,181.05 41.2,182.68 44.45,180.36zM31,182.58v4h2v-4zM36.78,183.37 L33.52,185.69 34.68,187.32 37.94,185zM88.88,186.22 L85.62,188.54 86.78,190.17 90.04,187.85zM30.96,190.54v4h2v-4zM82.36,190.86 L79.1,193.18 80.26,194.81 83.52,192.49zM75.84,195.5 L72.58,197.82 73.74,199.44 77,197.13zM30.96,198.54v4h2v-4zM69.33,200.14 L66.07,202.45 67.23,204.09 70.49,201.76 69.33,200.14zM62.81,204.77 L59.55,207.09 60.71,208.72 63.97,206.4zM30.96,206.54v4h2v-4zM56.29,209.41 L53.03,211.73 54.19,213.36 57.45,211.04zM49.77,214.05 L46.52,216.37 47.68,218 50.93,215.68zM30.96,214.54v4h2v-4zM43.26,218.69 L40,221.01 41.16,222.64 44.42,220.32zM30.96,222.54v4h2v-4zM36.74,223.33 L33.48,225.65 34.64,227.28 37.9,224.96z"
       android:fillColor="#fff"/>
 </vector>
diff --git a/app/src/main/res/drawable/ic_street_marked_parking_perpendicular.xml b/app/src/main/res/drawable/ic_street_marked_parking_perpendicular.xml
index 67b50d226d8..3fcbcf66f8b 100644
--- a/app/src/main/res/drawable/ic_street_marked_parking_perpendicular.xml
+++ b/app/src/main/res/drawable/ic_street_marked_parking_perpendicular.xml
@@ -13,6 +13,6 @@
       android:pathData="M0,0h92v256h-92z"
       android:fillColor="#808080"/>
   <path
-      android:pathData="m31,63v3h2v-1h1v-2zM38,63v2h4v-2zM46,63v2h4v-2zM54,63v2h4v-2zM62,63v2h4v-2zM70,63v2h4v-2zM78,63v2h4v-2zM86,63v2h4v-2zM31,70v4h2v-4zM31,78v4h2v-4zM31,86v4h2v-4zM31,94v4h2v-1h1v-2h-1v-1zM38,95v2h4v-2zM46,95v2h4v-2zM54,95v2h4v-2zM62,95v2h4v-2zM70,95v2h4v-2zM78,95v2h4v-2zM86,95v2h4v-2zM31,102v4h2v-4zM31,110v4h2v-4zM31,118v4h2v-4zM31,126v4h2v-1h1v-2h-1v-1zM38,127v2h4v-2zM46,127v2h4v-2zM54,127v2h4v-2zM62,127v2h4v-2zM70,127v2h4v-2zM78,127v2h4v-2zM86,127v2h4v-2zM31,134v4h2v-4zM31,142v4h2v-4zM31,150v4h2v-4zM31,158v3h3v-2h-1v-1zM38,159v2h4v-2zM46,159v2h4v-2zM54,159v2h4v-2zM62,159v2h4v-2zM70,159v2h4v-2zM78,159v2h4v-2zM86,159v2h4v-2z"
+      android:pathData="m31,63v3h2v-1h1v-2zM38,63v2h4v-2zM46,63v2h4v-2zM54,63v2h4v-2zM62,63v2h4v-2zM70,63v2h4v-2zM78,63v2h4v-2zM86,63v2h4v-2zM31,70v4h2v-4zM31,78v4h2v-4zM31,86v4h2v-4zM31,94v4h2v-1h1v-2h-1v-1zM38,95v2h4v-2zM46,95v2h4v-2zM54,95v2h4v-2zM62,95v2h4v-2zM70,95v2h4v-2zM78,95v2h4v-2zM86,95v2h4v-2zM31,102v4h2v-4zM31,110v4h2v-4zM31,118v4h2v-4zM31,126v4h2v-1h1v-2h-1v-1zM38,127v2h4v-2zM46,127v2h4v-2zM54,127v2h4v-2zM62,127v2h4v-2zM70,127v2h4v-2zM78,127v2h4v-2zM86,127v2h4v-2zM31,134v4h2v-4zM31,142v4h2v-4zM31,150v4h2v-4zM38,159v2h4v-2zM46,159v2h4v-2zM54,159v2h4v-2zM62,159v2h4v-2zM70,159v2h4v-2zM78,159v2h4v-2zM86,159v2h4v-2zM31,158v4h2v-1h1v-2h-1v-1zM31,166v4h2v-4zM31,174v4h2v-4zM31,182v4h2v-4zM31,190v3h3v-2h-1v-1zM38,191v2h4v-2zM46,191v2h4v-2zM54,191v2h4v-2zM62,191v2h4v-2zM70,191v2h4v-2zM78,191v2h4v-2zM86,191v2h4v-2z"
       android:fillColor="#fff"/>
 </vector>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bf0a31f8047..5fb2a773a82 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1637,6 +1637,8 @@ Partially means that a wheelchair can enter and use the restroom, but no handrai
     <string name="street_parking_on_kerb">"Adjacent to street"</string>
     <string name="street_parking_street_side">"Adjacent to street only in dedicated spaces"</string>
     <string name="street_parking_painted_area_only">"On street only in dedicated spaces"</string>
+    <string name="street_parking_staggered_on_street">"On street on alternating sides"</string>
+    <string name="street_parking_staggered_half_on_kerb">"Partially on street on alternating sides"</string>
     <string name="street_parking_separate">Parking displayed separately on map</string>
     <string name="street_parking_no">No parking here / Not allowed</string>
     <string name="street_parking_prohibited">No parking</string>
diff --git a/res/graphics/sidewalk/no_floating.svg b/res/graphics/sidewalk/no_floating.svg
new file mode 100644
index 00000000000..e23722a38f6
--- /dev/null
+++ b/res/graphics/sidewalk/no_floating.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
+ <path d="m94.001 94-60-60m0 60 60-60" fill="#00ff6d" stroke="#c30" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="15"/>
+</svg>
diff --git a/res/graphics/street parking/street_marked_parking_diagonal.svg b/res/graphics/street parking/street_marked_parking_diagonal.svg
index 74bfbd0572d..8f5e98600c8 100644
--- a/res/graphics/street parking/street_marked_parking_diagonal.svg
+++ b/res/graphics/street parking/street_marked_parking_diagonal.svg
@@ -3,5 +3,5 @@
  <path d="m94 1e-6h2v256h-2z" fill="#999"/>
  <path d="m92 1e-6h2v256h-2z" fill="#666"/>
  <rect y="1.3767e-6" width="92" height="256" fill="#808080"/>
- <path d="m87.162 66.506-3.2598 2.3184 1.1602 1.6309 3.2578-2.3203-1.1582-1.6289zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184-1.1602-1.6309zm-6.5176 4.6406-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-6.5176 4.6406-3.2578 2.3184 1.1602 1.6289 3.2578-2.3184-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-6.5156 4.6387-3.2598 2.3203 1.1602 1.6289 3.2578-2.3184-1.1582-1.6309zm-6.5176 4.6406-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203-1.1602-1.6289zm55.053 0.8086-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184-1.1602-1.6309zm-6.5176 4.6406-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203-1.1602-1.6289zm-52.559 1.5078v4h2v-4h-2zm46.041 3.1309-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3184-1.1602-1.6309zm-39.523 0.23046v4h2v-4h-2zm33.006 4.4102-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-33.006 3.5898v4h2v-4h-2zm26.49 1.0488-3.2598 2.3203 1.1602 1.6289 3.2578-2.3203-1.1582-1.6289zm-6.5176 4.6406-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203-1.1602-1.6289zm-19.973 2.3105v4h2v-4h-2zm13.455 2.3281-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1582 1.6289 3.2598-2.3184-1.1602-1.6309zm-6.9375 1.0332v4h2v-4h-2zm57.918 3.6777-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203-1.1602-1.6289zm-57.918 4.3223v4h2v-4h-2zm51.4 0.31641-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184-1.1602-1.6309zm-44.883 3.0449v4h2v-4h-2zm38.365 1.5957-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-31.848 1.7656v4h2v-4h-2zm25.33 2.875-3.2578 2.3184 1.1602 1.6289 3.2578-2.3184-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203-1.1602-1.6289zm-18.812 0.48632v4h2v-4h-2zm12.297 4.1524-3.2598 2.3203 1.1602 1.6289 3.2578-2.3184-1.1582-1.6309zm-12.297 3.8476v4h2v-4h-2zm5.7793 0.79297-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203-1.1602-1.6289z" fill="#fff"/>
+ <path d="m87.162 66.506-3.2598 2.3184 1.1602 1.6309 3.2578-2.3203zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184zm-6.5176 4.6406-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-6.5176 4.6406-3.2578 2.3184 1.1602 1.6289 3.2578-2.3184zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-6.5156 4.6387-3.2598 2.3203 1.1602 1.6289 3.2578-2.3184zm-6.5176 4.6406-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203zm55.053 0.8086-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184zm-6.5176 4.6406-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203zm-52.559 1.5078v4h2v-4zm46.041 3.1309-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3184zm-39.523 0.23046v4h2v-4zm33.006 4.4102-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-33.006 3.5898v4h2v-4zm26.49 1.0488-3.2598 2.3203 1.1602 1.6289 3.2578-2.3203zm-6.5176 4.6406-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203zm-19.973 2.3105v4h2v-4zm13.455 2.3281-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203zm-6.5176 4.6387-3.2578 2.3203 1.1582 1.6289 3.2598-2.3184zm-6.9375 1.0332v4h2v-4zm57.918 3.6777-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203zm-57.918 4.3223v4h2v-4zm51.4 0.31641-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184zm-44.883 3.0449v4h2v-4zm38.365 1.5957-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-31.848 1.7656v4h2v-4zm25.33 2.875-3.2578 2.3184 1.1602 1.6289 3.2578-2.3184zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-18.812 0.48632v4h2v-4zm12.297 4.1524-3.2598 2.3203 1.1602 1.6289 3.2578-2.3184zm-12.297 3.8476v4h2v-4zm5.7793 0.79297-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203zm52.102 2.8464-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203zm-57.918 4.3223v4h2v-4zm51.4 0.31641-3.2598 2.3203 1.1602 1.6289 3.2598-2.3203zm-6.5176 4.6387-3.2598 2.3203 1.1602 1.6289 3.2598-2.3184zm-44.883 3.0449v4h2v-4zm38.365 1.5957-3.2578 2.3184 1.1582 1.6309 3.2598-2.3203-1.1602-1.6289zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-31.848 1.7656v4h2v-4zm25.33 2.875-3.2578 2.3184 1.1602 1.6289 3.2578-2.3184zm-6.5176 4.6387-3.2578 2.3203 1.1602 1.6289 3.2578-2.3203zm-18.812 0.48632v4h2v-4zm12.297 4.1524-3.2598 2.3203 1.1602 1.6289 3.2578-2.3184zm-12.297 3.8476v4h2v-4zm5.7793 0.79297-3.2598 2.3184 1.1602 1.6309 3.2598-2.3203z" fill="#fff"/>
 </svg>
diff --git a/res/graphics/street parking/street_marked_parking_perpendicular.svg b/res/graphics/street parking/street_marked_parking_perpendicular.svg
index 46300411527..5c0b7cd52af 100644
--- a/res/graphics/street parking/street_marked_parking_perpendicular.svg
+++ b/res/graphics/street parking/street_marked_parking_perpendicular.svg
@@ -3,5 +3,5 @@
  <path d="m94 1e-6h2v256h-2z" fill="#999"/>
  <path d="m92 1e-6h2v256h-2z" fill="#666"/>
  <rect y="1.3767e-6" width="92" height="256" fill="#808080"/>
- <path d="m31 63v3h2v-1h1v-2zm7 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-1h1v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-1h1v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v3h3v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2z" fill="#fff"/>
+ <path d="m31 63v3h2v-1h1v-2zm7 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-1h1v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-1h1v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55 7v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm7 9v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm-55-1v4h2v-1h1v-2h-1v-1zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v4h2v-4zm0 8v3h3v-2h-1v-1zm7 1v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2zm8 0v2h4v-2z" fill="#fff"/>
 </svg>


Test patch:
diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreatorKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreatorKtTest.kt
index 2b282b4b730..d0e1461dc93 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreatorKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingCreatorKtTest.kt
@@ -5,6 +5,8 @@ import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryAd
 import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryChange
 import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryDelete
 import de.westnordost.streetcomplete.data.osm.edits.update_tags.StringMapEntryModify
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.*
+import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.*
 import de.westnordost.streetcomplete.osm.nowAsCheckDateString
 import org.assertj.core.api.Assertions
 import org.junit.Test
@@ -23,48 +25,230 @@ class StreetParkingCreatorKtTest {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
-            arrayOf(StringMapEntryAdd("parking:lane:both", "no"))
+            arrayOf(StringMapEntryAdd("parking:both", "no"))
         )
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(NoStreetParking, null),
-            arrayOf(StringMapEntryAdd("parking:lane:left", "no"))
+            arrayOf(StringMapEntryAdd("parking:left", "no"))
         )
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(null, NoStreetParking),
-            arrayOf(StringMapEntryAdd("parking:lane:right", "no"))
+            arrayOf(StringMapEntryAdd("parking:right", "no"))
         )
     }
 
-    @Test fun `apply separate parking answer`() {
+    @Test fun `apply parking`() {
+        val orientations = listOf(
+            "parallel" to PARALLEL,
+            "diagonal" to DIAGONAL,
+            "perpendicular" to PERPENDICULAR
+        )
+
+        val positions = listOf(
+            "lane" to ON_STREET,
+            "half_on_kerb" to HALF_ON_STREET,
+            "on_kerb" to OFF_STREET,
+            "street_side" to STREET_SIDE
+        )
+
+        for ((orientationStr, orientationValue) in orientations) {
+
+            for ((positionStr, positionValue) in positions) {
+
+                val parking = StreetParkingPositionAndOrientation(orientationValue, positionValue)
+
+                // tag new
+                verifyAnswer(
+                    mapOf(),
+                    LeftAndRightStreetParking(parking, parking),
+                    arrayOf(
+                        StringMapEntryAdd("parking:both", positionStr),
+                        StringMapEntryAdd("parking:both:orientation",orientationStr )
+                    )
+                )
+
+                verifyAnswer(
+                    mapOf(),
+                    LeftAndRightStreetParking(parking, null),
+                    arrayOf(
+                        StringMapEntryAdd("parking:left", positionStr),
+                        StringMapEntryAdd("parking:left:orientation", orientationStr)
+                    )
+                )
+
+                verifyAnswer(
+                    mapOf(),
+                    LeftAndRightStreetParking(null, parking),
+                    arrayOf(
+                        StringMapEntryAdd("parking:right", positionStr),
+                        StringMapEntryAdd("parking:right:orientation", orientationStr)
+                    )
+                )
+
+                // complement tags
+                verifyAnswer(
+                    mapOf(
+                        "parking:right" to positionStr,
+                        "parking:right:orientation" to orientationStr
+                    ),
+                    LeftAndRightStreetParking(parking, null),
+                    arrayOf(
+                        StringMapEntryAdd("parking:both", positionStr),
+                        StringMapEntryAdd("parking:both:orientation", orientationStr),
+                        StringMapEntryDelete("parking:right", positionStr),
+                        StringMapEntryDelete("parking:right:orientation", orientationStr)
+                    )
+                )
+
+                verifyAnswer(
+                    mapOf(
+                        "parking:left" to positionStr,
+                        "parking:left:orientation" to orientationStr
+                    ),
+                    LeftAndRightStreetParking(null, parking),
+                    arrayOf(
+                        StringMapEntryAdd("parking:both", positionStr),
+                        StringMapEntryAdd("parking:both:orientation", orientationStr),
+                        StringMapEntryDelete("parking:left", positionStr),
+                        StringMapEntryDelete("parking:left:orientation", orientationStr)
+                    )
+                )
+            }
+        }
+    }
+
+    @Test fun `apply separate parking`() {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(StreetParkingSeparate, StreetParkingSeparate),
-            arrayOf(StringMapEntryAdd("parking:lane:both", "separate"))
+            arrayOf(StringMapEntryAdd("parking:both", "separate"))
         )
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(StreetParkingSeparate, null),
-            arrayOf(StringMapEntryAdd("parking:lane:left", "separate"))
+            arrayOf(StringMapEntryAdd("parking:left", "separate"))
         )
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(null, StreetParkingSeparate),
-            arrayOf(StringMapEntryAdd("parking:lane:right", "separate"))
+            arrayOf(StringMapEntryAdd("parking:right", "separate"))
+        )
+    }
+
+    @Test fun `apply painted area only`() {
+        val parking = StreetParkingPositionAndOrientation(PARALLEL, PAINTED_AREA_ONLY)
+        verifyAnswer(
+            mapOf(),
+            LeftAndRightStreetParking(parking, parking),
+            arrayOf(
+                StringMapEntryAdd("parking:both", "lane"),
+                StringMapEntryAdd("parking:both:orientation", "parallel"),
+                StringMapEntryAdd("parking:both:markings", "yes"),
+                StringMapEntryAdd("parking:both:staggered", "yes"),
+            )
+        )
+        verifyAnswer(
+            mapOf(),
+            LeftAndRightStreetParking(parking, null),
+            arrayOf(
+                StringMapEntryAdd("parking:left", "lane"),
+                StringMapEntryAdd("parking:left:orientation", "parallel"),
+                StringMapEntryAdd("parking:left:markings", "yes"),
+                StringMapEntryAdd("parking:left:staggered", "yes"),
+            )
+        )
+        verifyAnswer(
+            mapOf(),
+            LeftAndRightStreetParking(null, parking),
+            arrayOf(
+                StringMapEntryAdd("parking:right", "lane"),
+                StringMapEntryAdd("parking:right:orientation", "parallel"),
+                StringMapEntryAdd("parking:right:markings", "yes"),
+                StringMapEntryAdd("parking:right:staggered", "yes"),
+            )
+        )
+    }
+
+    @Test fun `apply on_kerb parking does not replace shoulder parking`() {
+        val parking = StreetParkingPositionAndOrientation(PARALLEL, OFF_STREET)
+
+        verifyAnswer(
+            mapOf("parking:both" to "shoulder"),
+            LeftAndRightStreetParking(parking, parking),
+            arrayOf(
+                StringMapEntryModify("parking:both", "shoulder", "shoulder"),
+                StringMapEntryAdd("parking:both:orientation", "parallel"),
+            )
+        )
+        verifyAnswer(
+            mapOf("parking:left" to "shoulder"),
+            LeftAndRightStreetParking(parking, null),
+            arrayOf(
+                StringMapEntryModify("parking:left", "shoulder", "shoulder"),
+                StringMapEntryAdd("parking:left:orientation", "parallel"),
+            )
+        )
+        verifyAnswer(
+            mapOf("parking:right" to "shoulder"),
+            LeftAndRightStreetParking(null, parking),
+            arrayOf(
+                StringMapEntryModify("parking:right", "shoulder", "shoulder"),
+                StringMapEntryAdd("parking:right:orientation", "parallel"),
+            )
         )
     }
 
+    @Test fun `apply staggered parking on road`() {
+
+        val positions = listOf(
+            "lane" to STAGGERED_ON_STREET,
+            "half_on_kerb" to STAGGERED_HALF_ON_STREET
+        )
+        for ((positionStr, positionValue) in positions) {
+            val parking = StreetParkingPositionAndOrientation(PARALLEL, positionValue)
+
+            verifyAnswer(
+                mapOf(),
+                LeftAndRightStreetParking(parking, parking),
+                arrayOf(
+                    StringMapEntryAdd("parking:both", positionStr),
+                    StringMapEntryAdd("parking:both:orientation", "parallel"),
+                    StringMapEntryAdd("parking:both:staggered", "yes"),
+                )
+            )
+            verifyAnswer(
+                mapOf(),
+                LeftAndRightStreetParking(parking, null),
+                arrayOf(
+                    StringMapEntryAdd("parking:left", positionStr),
+                    StringMapEntryAdd("parking:left:orientation", "parallel"),
+                    StringMapEntryAdd("parking:left:staggered", "yes"),
+                )
+            )
+            verifyAnswer(
+                mapOf(),
+                LeftAndRightStreetParking(null, parking),
+                arrayOf(
+                    StringMapEntryAdd("parking:right", positionStr),
+                    StringMapEntryAdd("parking:right:orientation", "parallel"),
+                    StringMapEntryAdd("parking:right:staggered", "yes"),
+                )
+            )
+        }
+    }
+
     @Test fun `apply parallel parking answer on both sides`() {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.HALF_ON_KERB),
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.HALF_ON_KERB)
+                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET),
+                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET)
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:both", "parallel"),
-                StringMapEntryAdd("parking:lane:both:parallel", "half_on_kerb"),
+                StringMapEntryAdd("parking:both", "half_on_kerb"),
+                StringMapEntryAdd("parking:both:orientation", "parallel"),
             )
         )
     }
@@ -73,13 +257,13 @@ class StreetParkingCreatorKtTest {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.ON_STREET),
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.HALF_ON_KERB)
+                StreetParkingPositionAndOrientation(PARALLEL, ON_STREET),
+                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET)
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:both", "parallel"),
-                StringMapEntryAdd("parking:lane:left:parallel", "on_street"),
-                StringMapEntryAdd("parking:lane:right:parallel", "half_on_kerb"),
+                StringMapEntryAdd("parking:both:orientation", "parallel"),
+                StringMapEntryAdd("parking:left", "lane"),
+                StringMapEntryAdd("parking:right", "half_on_kerb"),
             )
         )
     }
@@ -88,14 +272,13 @@ class StreetParkingCreatorKtTest {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.PERPENDICULAR, ParkingPosition.STREET_SIDE),
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.STREET_SIDE)
+                StreetParkingPositionAndOrientation(PERPENDICULAR, STREET_SIDE),
+                StreetParkingPositionAndOrientation(PARALLEL, STREET_SIDE)
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:left", "perpendicular"),
-                StringMapEntryAdd("parking:lane:right", "parallel"),
-                StringMapEntryAdd("parking:lane:left:perpendicular", "street_side"),
-                StringMapEntryAdd("parking:lane:right:parallel", "street_side"),
+                StringMapEntryAdd("parking:both", "street_side"),
+                StringMapEntryAdd("parking:left:orientation", "perpendicular"),
+                StringMapEntryAdd("parking:right:orientation", "parallel"),
             )
         )
     }
@@ -104,67 +287,67 @@ class StreetParkingCreatorKtTest {
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.DIAGONAL, ParkingPosition.STREET_SIDE),
-                StreetParkingPositionAndOrientation(ParkingOrientation.PERPENDICULAR, ParkingPosition.PAINTED_AREA_ONLY)
+                StreetParkingPositionAndOrientation(DIAGONAL, STREET_SIDE),
+                StreetParkingPositionAndOrientation(PERPENDICULAR, OFF_STREET)
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:left", "diagonal"),
-                StringMapEntryAdd("parking:lane:left:diagonal", "street_side"),
-                StringMapEntryAdd("parking:lane:right", "perpendicular"),
-                StringMapEntryAdd("parking:lane:right:perpendicular", "painted_area_only"),
+                StringMapEntryAdd("parking:left", "street_side"),
+                StringMapEntryAdd("parking:left:orientation", "diagonal"),
+                StringMapEntryAdd("parking:right", "on_kerb"),
+                StringMapEntryAdd("parking:right:orientation", "perpendicular"),
             )
         )
     }
 
     @Test fun `applying one side combines with previous tagging of the other side to both-tag`() {
         verifyAnswer(
-            mapOf("parking:lane:left" to "separate"),
+            mapOf("parking:left" to "separate"),
             LeftAndRightStreetParking(null, StreetParkingSeparate),
             arrayOf(
-                StringMapEntryAdd("parking:lane:both", "separate"),
-                StringMapEntryDelete("parking:lane:left", "separate"),
+                StringMapEntryAdd("parking:both", "separate"),
+                StringMapEntryDelete("parking:left", "separate"),
             )
         )
         verifyAnswer(
-            mapOf("parking:lane:right" to "separate"),
+            mapOf("parking:right" to "separate"),
             LeftAndRightStreetParking(StreetParkingSeparate, null),
             arrayOf(
-                StringMapEntryAdd("parking:lane:both", "separate"),
-                StringMapEntryDelete("parking:lane:right", "separate"),
+                StringMapEntryAdd("parking:both", "separate"),
+                StringMapEntryDelete("parking:right", "separate"),
             )
         )
     }
 
     @Test fun `apply for one side does not touch the other side`() {
         verifyAnswer(
-            mapOf("parking:lane:left" to "separate"),
+            mapOf("parking:left" to "separate"),
             LeftAndRightStreetParking(null, NoStreetParking),
             arrayOf(
-                StringMapEntryAdd("parking:lane:right", "no")
+                StringMapEntryAdd("parking:right", "no")
             )
         )
         verifyAnswer(
-            mapOf("parking:lane:right" to "no"),
+            mapOf("parking:right" to "no"),
             LeftAndRightStreetParking(StreetParkingSeparate, null),
             arrayOf(
-                StringMapEntryAdd("parking:lane:left", "separate")
+                StringMapEntryAdd("parking:left", "separate")
             )
         )
     }
 
     @Test fun `apply for one side does not touch the other side even if it is invalid`() {
         verifyAnswer(
-            mapOf("parking:lane:left" to "narrow"),
+            mapOf("parking:left" to "narrow"),
             LeftAndRightStreetParking(null, NoStreetParking),
             arrayOf(
-                StringMapEntryAdd("parking:lane:right", "no")
+                StringMapEntryAdd("parking:right", "no")
             )
         )
         verifyAnswer(
-            mapOf("parking:lane:right" to "narrow"),
+            mapOf("parking:right" to "narrow"),
             LeftAndRightStreetParking(StreetParkingSeparate, null),
             arrayOf(
-                StringMapEntryAdd("parking:lane:left", "separate")
+                StringMapEntryAdd("parking:left", "separate")
             )
         )
     }
@@ -172,45 +355,44 @@ class StreetParkingCreatorKtTest {
     @Test fun `apply for one side does not change values for the other side even if it was defined for both sides before and invalid`() {
         verifyAnswer(
             mapOf(
-                "parking:lane" to "hexagonal",
-                "parking:lane:diagonal" to "on_kerb",
+                "parking:both" to "on_kerb",
+                "parking:both:orientation" to "hexagonal",
             ),
             LeftAndRightStreetParking(null, NoStreetParking),
             arrayOf(
-                StringMapEntryAdd("parking:lane:right", "no"),
-                StringMapEntryDelete("parking:lane", "hexagonal"),
-                StringMapEntryDelete("parking:lane:diagonal", "on_kerb"),
-                StringMapEntryAdd("parking:lane:left", "hexagonal"),
-                StringMapEntryAdd("parking:lane:left:diagonal", "on_kerb"),
+                StringMapEntryAdd("parking:right", "no"),
+                StringMapEntryDelete("parking:both", "on_kerb"),
+                StringMapEntryDelete("parking:both:orientation", "hexagonal"),
+                StringMapEntryAdd("parking:left", "on_kerb"),
+                StringMapEntryAdd("parking:left:orientation", "hexagonal"),
             )
         )
     }
 
     @Test fun `updates check date`() {
         verifyAnswer(
-            mapOf("parking:lane:both" to "no"),
+            mapOf("parking:both" to "no"),
             LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
             arrayOf(
-                StringMapEntryModify("parking:lane:both", "no", "no"),
-                StringMapEntryAdd("check_date:parking:lane", nowAsCheckDateString())
+                StringMapEntryModify("parking:both", "no", "no"),
+                StringMapEntryAdd("check_date:parking", nowAsCheckDateString())
             )
         )
         verifyAnswer(
             mapOf(
-                "parking:lane:both" to "parallel",
-                "parking:lane:left:parallel" to "half_on_kerb",
-                "parking:lane:right:parallel" to "on_kerb",
-                "parking:condition:left" to "free",
+                "parking:both:orientation" to "parallel",
+                "parking:left" to "half_on_kerb",
+                "parking:right" to "on_kerb",
             ),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.HALF_ON_KERB),
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.ON_KERB)
+                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET),
+                StreetParkingPositionAndOrientation(PARALLEL, OFF_STREET)
             ),
             arrayOf(
-                StringMapEntryModify("parking:lane:both", "parallel", "parallel"),
-                StringMapEntryModify("parking:lane:left:parallel", "half_on_kerb", "half_on_kerb"),
-                StringMapEntryModify("parking:lane:right:parallel", "on_kerb", "on_kerb"),
-                StringMapEntryAdd("check_date:parking:lane", nowAsCheckDateString())
+                StringMapEntryModify("parking:both:orientation", "parallel", "parallel"),
+                StringMapEntryModify("parking:left", "half_on_kerb", "half_on_kerb"),
+                StringMapEntryModify("parking:right", "on_kerb", "on_kerb"),
+                StringMapEntryAdd("check_date:parking", nowAsCheckDateString())
             )
         )
     }
@@ -218,21 +400,124 @@ class StreetParkingCreatorKtTest {
     @Test fun `clean up previous tagging when applying value for each side`() {
         verifyAnswer(
             mapOf(
-                "parking:lane:both" to "parallel",
-                "parking:lane:left:parallel" to "half_on_kerb",
-                "parking:lane:right:parallel" to "on_kerb",
+                "parking:both:orientation" to "parallel",
+                "parking:left" to "half_on_kerb",
+                "parking:right" to "on_kerb",
             ),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.PARALLEL, ParkingPosition.ON_STREET),
-                StreetParkingPositionAndOrientation(ParkingOrientation.DIAGONAL, ParkingPosition.ON_STREET)
+                StreetParkingPositionAndOrientation(PARALLEL, ON_STREET),
+                StreetParkingPositionAndOrientation(DIAGONAL, ON_STREET)
+            ),
+            arrayOf(
+                StringMapEntryDelete("parking:both:orientation", "parallel"),
+                StringMapEntryAdd("parking:left:orientation", "parallel"),
+                StringMapEntryAdd("parking:right:orientation", "diagonal"),
+                StringMapEntryDelete("parking:left", "half_on_kerb"),
+                StringMapEntryDelete("parking:right", "on_kerb"),
+                StringMapEntryAdd("parking:both", "lane"),
+            )
+        )
+    }
+
+    @Test fun `clean up parking orientation if new parking does not have orientation`() {
+        verifyAnswer(
+            mapOf(
+                "parking:both:orientation" to "parallel",
+                "parking:both" to "half_on_kerb"
+            ),
+            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
+            arrayOf(
+                StringMapEntryDelete("parking:both:orientation", "parallel"),
+                StringMapEntryModify("parking:both", "half_on_kerb", "no"),
+            )
+        )
+    }
+
+    @Test fun `clean up markings tagging if new parking does not have markings`() {
+        verifyAnswer(
+            mapOf(
+                "parking:both:orientation" to "parallel",
+                "parking:both" to "half_on_kerb",
+                "parking:both:markings" to "yes"
+            ),
+            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
+            arrayOf(
+                StringMapEntryDelete("parking:both:orientation", "parallel"),
+                StringMapEntryDelete("parking:both:markings", "yes"),
+                StringMapEntryModify("parking:both", "half_on_kerb", "no"),
+            )
+        )
+    }
+
+    @Test fun `clean up staggered tagging if new parking is not staggered`() {
+        val parking = StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET)
+        verifyAnswer(
+            mapOf(
+                "parking:both:orientation" to "parallel",
+                "parking:both" to "half_on_kerb",
+                "parking:both:staggered" to "yes"
             ),
+            LeftAndRightStreetParking(parking, parking),
+            arrayOf(
+                StringMapEntryModify("parking:both:orientation", "parallel", "parallel"),
+                StringMapEntryModify("parking:both", "half_on_kerb", "half_on_kerb"),
+
+                StringMapEntryDelete("parking:both:staggered", "yes"),
+            )
+        )
+    }
+
+    @Test fun `clean up any old schema tagging when changing any tags`() {
+        verifyAnswer(
+            mapOf(
+                "parking:lane" to "parallel",
+                "parking:lane:both" to "perpendicular",
+                "parking:lane:left" to "diagonal",
+                "parking:lane:right" to "hexagonal",
+                "parking:lane:parallel" to "1",
+                "parking:lane:both:parallel" to "2",
+                "parking:lane:left:parallel" to "3",
+                "parking:lane:right:parallel" to "4",
+                "parking:lane:diagonal" to "5",
+                "parking:lane:both:diagonal" to "6",
+                "parking:lane:left:diagonal" to "7",
+                "parking:lane:right:diagonal" to "8",
+                "parking:lane:perpendicular" to "9",
+                "parking:lane:both:perpendicular" to "10",
+                "parking:lane:left:perpendicular" to "11",
+                "parking:lane:right:perpendicular" to "12",
+                "check_date:parking:lane" to "123",
+                "parking:lane:check_date" to "123",
+                "lastcheck:parking:lane" to "123",
+                "parking:lane:lastcheck" to "123",
+                "last_checked:parking:lane" to "123",
+                "parking:lane:last_checked" to "123",
+            ),
+            LeftAndRightStreetParking(null, NoStreetParking),
             arrayOf(
-                StringMapEntryDelete("parking:lane:both", "parallel"),
-                StringMapEntryAdd("parking:lane:left", "parallel"),
-                StringMapEntryAdd("parking:lane:right", "diagonal"),
-                StringMapEntryModify("parking:lane:left:parallel", "half_on_kerb", "on_street"),
-                StringMapEntryDelete("parking:lane:right:parallel", "on_kerb"),
-                StringMapEntryAdd("parking:lane:right:diagonal", "on_street"),
+                StringMapEntryAdd("parking:right", "no"),
+                StringMapEntryDelete("parking:lane", "parallel"),
+                StringMapEntryDelete("parking:lane:both", "perpendicular"),
+                StringMapEntryDelete("parking:lane:left", "diagonal"),
+                StringMapEntryDelete("parking:lane:right", "hexagonal"),
+                StringMapEntryDelete("parking:lane:parallel", "1"),
+                StringMapEntryDelete("parking:lane:both:parallel", "2"),
+                StringMapEntryDelete("parking:lane:left:parallel", "3"),
+                StringMapEntryDelete("parking:lane:right:parallel", "4"),
+                StringMapEntryDelete("parking:lane:diagonal", "5"),
+                StringMapEntryDelete("parking:lane:both:diagonal", "6"),
+                StringMapEntryDelete("parking:lane:left:diagonal", "7"),
+                StringMapEntryDelete("parking:lane:right:diagonal", "8"),
+                StringMapEntryDelete("parking:lane:perpendicular", "9"),
+                StringMapEntryDelete("parking:lane:both:perpendicular", "10"),
+                StringMapEntryDelete("parking:lane:left:perpendicular", "11"),
+                StringMapEntryDelete("parking:lane:right:perpendicular", "12"),
+                StringMapEntryDelete("check_date:parking:lane", "123"),
+                StringMapEntryDelete("parking:lane:check_date", "123"),
+                StringMapEntryDelete("lastcheck:parking:lane", "123"),
+                StringMapEntryDelete("parking:lane:lastcheck", "123"),
+                StringMapEntryDelete("last_checked:parking:lane", "123"),
+                StringMapEntryDelete("parking:lane:last_checked", "123"),
             )
         )
     }
@@ -242,22 +527,22 @@ class StreetParkingCreatorKtTest {
             mapOf(),
             LeftAndRightStreetParking(
                 null,
-                StreetParkingPositionAndOrientation(ParkingOrientation.DIAGONAL, ParkingPosition.ON_STREET)
+                StreetParkingPositionAndOrientation(DIAGONAL, ON_STREET)
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:right", "diagonal"),
-                StringMapEntryAdd("parking:lane:right:diagonal", "on_street")
+                StringMapEntryAdd("parking:right", "lane"),
+                StringMapEntryAdd("parking:right:orientation", "diagonal")
             )
         )
         verifyAnswer(
             mapOf(),
             LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(ParkingOrientation.DIAGONAL, ParkingPosition.ON_STREET),
+                StreetParkingPositionAndOrientation(DIAGONAL, ON_STREET),
                 null
             ),
             arrayOf(
-                StringMapEntryAdd("parking:lane:left", "diagonal"),
-                StringMapEntryAdd("parking:lane:left:diagonal", "on_street")
+                StringMapEntryAdd("parking:left", "lane"),
+                StringMapEntryAdd("parking:left:orientation", "diagonal")
             )
         )
     }
diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParserKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParserKtTest.kt
index 9c9c41192ad..3de826f6a47 100644
--- a/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParserKtTest.kt
+++ b/app/src/test/java/de/westnordost/streetcomplete/osm/street_parking/StreetParkingParserKtTest.kt
@@ -1,18 +1,46 @@
 package de.westnordost.streetcomplete.osm.street_parking
 
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.DIAGONAL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PARALLEL
-import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.PERPENDICULAR
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.HALF_ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_KERB
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.ON_STREET
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.PAINTED_AREA_ONLY
-import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.STREET_SIDE
+import de.westnordost.streetcomplete.osm.street_parking.ParkingOrientation.*
+import de.westnordost.streetcomplete.osm.street_parking.ParkingPosition.*
 import org.junit.Assert.assertEquals
 import org.junit.Test
 
 class StreetParkingParserKtTest {
 
+    @Test fun `new parking schema is preferred over old parking schema`() {
+        assertEquals(
+            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
+            createStreetParkingSides(mapOf(
+                "parking:lane:both" to "parallel",
+                "parking:both" to "no"
+            ))
+        )
+
+        assertEquals(
+            LeftAndRightStreetParking(NoStreetParking, null),
+            createStreetParkingSides(mapOf(
+                "parking:lane:both" to "parallel",
+                "parking:left" to "no"
+            ))
+        )
+
+        assertEquals(
+            LeftAndRightStreetParking(null, NoStreetParking),
+            createStreetParkingSides(mapOf(
+                "parking:lane:both" to "parallel",
+                "parking:right" to "no"
+            ))
+        )
+    }
+
+    @Test fun `all unknown`() {
+        assertEquals(
+            null,
+            createStreetParkingSides(mapOf()))
+    }
+
+    //region new schema
+
     @Test fun parking() {
 
         val orientations = listOf(
@@ -22,14 +50,11 @@ class StreetParkingParserKtTest {
         )
 
         val positions = listOf(
-            "on_street" to ON_STREET,
-            "half_on_kerb" to HALF_ON_KERB,
-            "on_kerb" to ON_KERB,
+            "lane" to ON_STREET,
+            "half_on_kerb" to HALF_ON_STREET,
+            "on_kerb" to OFF_STREET,
+            "shoulder" to OFF_STREET,
             "street_side" to STREET_SIDE,
-            "lay_by" to STREET_SIDE,
-            "bays" to STREET_SIDE,
-            "painted_area_only" to PAINTED_AREA_ONLY,
-            "marked" to PAINTED_AREA_ONLY,
         )
 
         for ((orientationStr, orientationValue) in orientations) {
@@ -41,189 +66,397 @@ class StreetParkingParserKtTest {
                 assertEquals(
                     LeftAndRightStreetParking(parking, null),
                     createStreetParkingSides(mapOf(
-                        "parking:lane:left" to orientationStr,
-                        "parking:lane:left:$orientationStr" to positionStr
+                        "parking:left" to positionStr,
+                        "parking:left:orientation" to orientationStr
                     ))
                 )
 
                 assertEquals(
                     LeftAndRightStreetParking(null, parking),
                     createStreetParkingSides(mapOf(
-                        "parking:lane:right" to orientationStr,
-                        "parking:lane:right:$orientationStr" to positionStr
+                        "parking:right" to positionStr,
+                        "parking:right:orientation" to orientationStr
                     ))
                 )
 
                 assertEquals(
                     LeftAndRightStreetParking(parking, parking),
                     createStreetParkingSides(mapOf(
-                        "parking:lane:both" to orientationStr,
-                        "parking:lane:both:$orientationStr" to positionStr
+                        "parking:both" to positionStr,
+                        "parking:both:orientation" to orientationStr
                     ))
                 )
 
                 assertEquals(
                     LeftAndRightStreetParking(parking, parking),
                     createStreetParkingSides(mapOf(
-                        "parking:lane:both" to orientationStr,
-                        "parking:lane:left:$orientationStr" to positionStr,
-                        "parking:lane:right:$orientationStr" to positionStr,
+                        "parking:both" to positionStr,
+                        "parking:left:orientation" to orientationStr,
+                        "parking:right:orientation" to orientationStr,
                     ))
                 )
 
                 assertEquals(
                     LeftAndRightStreetParking(parking, parking),
                     createStreetParkingSides(mapOf(
-                        "parking:lane:left" to orientationStr,
-                        "parking:lane:right" to orientationStr,
-                        "parking:lane:both:$orientationStr" to positionStr
+                        "parking:left" to positionStr,
+                        "parking:right" to positionStr,
+                        "parking:both:orientation" to orientationStr,
                     ))
                 )
 
-                // without explicit sides
+                // does not accept it without explicit sides
 
                 assertEquals(
-                    LeftAndRightStreetParking(parking, parking),
+                    null,
                     createStreetParkingSides(mapOf(
-                        "parking:lane" to orientationStr,
-                        "parking:lane:$orientationStr" to positionStr
+                        "parking" to positionStr,
+                        "parking:orientation" to orientationStr
                     ))
                 )
             }
         }
     }
 
-    @Test fun `parking where position differs per side`() {
-        assertEquals(
-            LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(PERPENDICULAR, STREET_SIDE),
-                StreetParkingPositionAndOrientation(PERPENDICULAR, HALF_ON_KERB)
-            ),
-            createStreetParkingSides(mapOf(
-                "parking:lane:both" to "perpendicular",
-                "parking:lane:left:perpendicular" to "street_side",
-                "parking:lane:right:perpendicular" to "half_on_kerb",
-            ))
+    @Test fun `special values for parking`() {
+
+        val values = listOf(
+            "no" to NoStreetParking,
+            "separate" to StreetParkingSeparate,
+            "yes" to IncompleteStreetParking,
+            "foobar" to UnknownStreetParking
         )
+
+        for ((str, value) in values) {
+            assertEquals(
+                LeftAndRightStreetParking(value, null),
+                createStreetParkingSides(mapOf("parking:left" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(null, value),
+                createStreetParkingSides(mapOf("parking:right" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(value, value),
+                createStreetParkingSides(mapOf("parking:both" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(value, value),
+                createStreetParkingSides(mapOf(
+                    "parking:left" to str,
+                    "parking:right" to str,
+                ))
+            )
+
+            assertEquals(
+                null,
+                createStreetParkingSides(mapOf("parking" to str))
+            )
+        }
     }
 
-    @Test fun `parking where orientation differs per side`() {
-        assertEquals(
-            LeftAndRightStreetParking(
-                StreetParkingPositionAndOrientation(PERPENDICULAR, STREET_SIDE),
-                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_KERB)
-            ),
-            createStreetParkingSides(mapOf(
-                "parking:lane:left" to "perpendicular",
-                "parking:lane:right" to "parallel",
-                "parking:lane:left:perpendicular" to "street_side",
-                "parking:lane:right:parallel" to "half_on_kerb",
-            ))
+    @Test fun `staggered parking`() {
+
+        val values = listOf(
+            "lane" to STAGGERED_ON_STREET,
+            "half_on_kerb" to STAGGERED_HALF_ON_STREET,
         )
+
+        for ((str, value) in values) {
+            val parking = StreetParkingPositionAndOrientation(PARALLEL, value)
+
+            assertEquals(
+                LeftAndRightStreetParking(parking, null),
+                createStreetParkingSides(mapOf(
+                    "parking:left" to str,
+                    "parking:left:orientation" to "parallel",
+                    "parking:left:staggered" to "yes"
+                ))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(null, parking),
+                createStreetParkingSides(mapOf(
+                    "parking:right" to str,
+                    "parking:right:orientation" to "parallel",
+                    "parking:right:staggered" to "yes"
+                ))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(parking, parking),
+                createStreetParkingSides(mapOf(
+                    "parking:both" to str,
+                    "parking:both:orientation" to "parallel",
+                    "parking:both:staggered" to "yes"
+                ))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(parking, parking),
+                createStreetParkingSides(mapOf(
+                    "parking:left" to str,
+                    "parking:right" to str,
+                    "parking:both:orientation" to "parallel",
+                    "parking:both:staggered" to "yes"
+                ))
+            )
+        }
     }
 
-    @Test fun `no parking`() {
-        // old style tagging
+    @Test fun `painted area only parking`() {
 
-        assertEquals(
-            LeftAndRightStreetParking(NoStreetParking, null),
-            createStreetParkingSides(mapOf("parking:lane:left" to "no"))
-        )
+        val parking = StreetParkingPositionAndOrientation(PARALLEL, PAINTED_AREA_ONLY)
 
         assertEquals(
-            LeftAndRightStreetParking(null, NoStreetParking),
-            createStreetParkingSides(mapOf("parking:lane:right" to "no"))
+            LeftAndRightStreetParking(parking, null),
+            createStreetParkingSides(mapOf(
+                "parking:left" to "lane",
+                "parking:left:orientation" to "parallel",
+                "parking:left:staggered" to "yes",
+                "parking:left:markings" to "yes"
+            ))
         )
 
         assertEquals(
-            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
-            createStreetParkingSides(mapOf("parking:lane:both" to "no"))
+            LeftAndRightStreetParking(null, parking),
+            createStreetParkingSides(mapOf(
+                "parking:right" to "lane",
+                "parking:right:orientation" to "parallel",
+                "parking:right:staggered" to "yes",
+                "parking:right:markings" to "yes"
+            ))
         )
 
         assertEquals(
-            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
+            LeftAndRightStreetParking(parking, parking),
             createStreetParkingSides(mapOf(
-                "parking:lane:left" to "no",
-                "parking:lane:right" to "no",
+                "parking:both" to "lane",
+                "parking:both:orientation" to "parallel",
+                "parking:both:staggered" to "yes",
+                "parking:both:markings" to "yes"
             ))
         )
 
-        // old style tagging without explicit sides
-
         assertEquals(
-            LeftAndRightStreetParking(NoStreetParking, NoStreetParking),
-            createStreetParkingSides(mapOf("parking:lane" to "no"))
+            LeftAndRightStreetParking(parking, parking),
+            createStreetParkingSides(mapOf(
+                "parking:left" to "lane",
+                "parking:right" to "lane",
+                "parking:both:orientation" to "parallel",
+                "parking:both:staggered" to "yes",
+                "parking:both:markings" to "yes"
+            ))
         )
     }
 
-    @Test fun `parking separate`() {
+    @Test fun `unknown orientation leads to unknown`() {
         assertEquals(
-            LeftAndRightStreetParking(StreetParkingSeparate, null),
-            createStreetParkingSides(mapOf("parking:lane:left" to "separate"))
+            LeftAndRightStreetParking(UnknownStreetParking, null),
+            createStreetParkingSides(mapOf(
+                "parking:left" to "lane",
+                "parking:left:orientation" to "something"
+            ))
         )
-
         assertEquals(
-            LeftAndRightStreetParking(null, StreetParkingSeparate),
-            createStreetParkingSides(mapOf("parking:lane:right" to "separate"))
+            LeftAndRightStreetParking(UnknownStreetParking, null),
+            createStreetParkingSides(mapOf("parking:left:orientation" to "something"))
         )
+    }
 
+    @Test fun `unknown position leads to unknown`() {
         assertEquals(
-            LeftAndRightStreetParking(StreetParkingSeparate, StreetParkingSeparate),
-            createStreetParkingSides(mapOf("parking:lane:both" to "separate"))
+            LeftAndRightStreetParking(UnknownStreetParking, null),
+            createStreetParkingSides(mapOf(
+                "parking:left" to "something",
+                "parking:left:orientation" to "parallel"
+            ))
+        )
+        assertEquals(
+            LeftAndRightStreetParking(UnknownStreetParking, null),
+            createStreetParkingSides(mapOf("parking:left" to "something"))
         )
+    }
 
+    @Test fun `either position or orientation is unknown leads to incomplete`() {
         assertEquals(
-            LeftAndRightStreetParking(StreetParkingSeparate, StreetParkingSeparate),
+            LeftAndRightStreetParking(IncompleteStreetParking, null),
             createStreetParkingSides(mapOf(
-                "parking:lane:left" to "separate",
-                "parking:lane:right" to "separate",
+                "parking:left:orientation" to "parallel"
             ))
         )
 
         assertEquals(
-            LeftAndRightStreetParking(StreetParkingSeparate, StreetParkingSeparate),
-            createStreetParkingSides(mapOf("parking:lane" to "separate"))
+            LeftAndRightStreetParking(IncompleteStreetParking, null),
+            createStreetParkingSides(mapOf(
+                "parking:left" to "lane"
+            ))
         )
     }
 
-    @Test fun `parking yes`() {
-        assertEquals(
-            LeftAndRightStreetParking(IncompleteStreetParking, null),
-            createStreetParkingSides(mapOf("parking:lane:left" to "yes"))
+    //endregion
+
+    //region old schema
+
+    @Test fun `parking with old schema`() {
+
+        val orientations = listOf(
+            "parallel" to PARALLEL,
+            "diagonal" to DIAGONAL,
+            "perpendicular" to PERPENDICULAR
         )
 
-        assertEquals(
-            LeftAndRightStreetParking(null, IncompleteStreetParking),
-            createStreetParkingSides(mapOf("parking:lane:right" to "yes"))
+        val positions = listOf(
+            "on_street" to ON_STREET,
+            "half_on_kerb" to HALF_ON_STREET,
+            "on_kerb" to OFF_STREET,
+            "shoulder" to OFF_STREET,
+            "street_side" to STREET_SIDE,
+            "lay_by" to STREET_SIDE,
+            "painted_area_only" to PAINTED_AREA_ONLY,
         )
 
+        for ((orientationStr, orientationValue) in orientations) {
+
+            for ((positionStr, positionValue) in positions) {
+
+                val parking = StreetParkingPositionAndOrientation(orientationValue, positionValue)
+
+                assertEquals(
+                    LeftAndRightStreetParking(parking, null),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane:left" to orientationStr,
+                        "parking:lane:left:$orientationStr" to positionStr
+                    ))
+                )
+
+                assertEquals(
+                    LeftAndRightStreetParking(null, parking),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane:right" to orientationStr,
+                        "parking:lane:right:$orientationStr" to positionStr
+                    ))
+                )
+
+                assertEquals(
+                    LeftAndRightStreetParking(parking, parking),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane:both" to orientationStr,
+                        "parking:lane:both:$orientationStr" to positionStr
+                    ))
+                )
+
+                assertEquals(
+                    LeftAndRightStreetParking(parking, parking),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane:both" to orientationStr,
+                        "parking:lane:left:$orientationStr" to positionStr,
+                        "parking:lane:right:$orientationStr" to positionStr,
+                    ))
+                )
+
+                assertEquals(
+                    LeftAndRightStreetParking(parking, parking),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane:left" to orientationStr,
+                        "parking:lane:right" to orientationStr,
+                        "parking:lane:both:$orientationStr" to positionStr
+                    ))
+                )
+
+                // without explicit sides
+
+                assertEquals(
+                    LeftAndRightStreetParking(parking, parking),
+                    createStreetParkingSides(mapOf(
+                        "parking:lane" to orientationStr,
+                        "parking:lane:$orientationStr" to positionStr
+                    ))
+                )
+            }
+        }
+    }
+
+    @Test fun `parking with old schema where position differs per side`() {
         assertEquals(
-            LeftAndRightStreetParking(IncompleteStreetParking, IncompleteStreetParking),
-            createStreetParkingSides(mapOf("parking:lane:both" to "yes"))
+            LeftAndRightStreetParking(
+                StreetParkingPositionAndOrientation(PERPENDICULAR, STREET_SIDE),
+                StreetParkingPositionAndOrientation(PERPENDICULAR, HALF_ON_STREET)
+            ),
+            createStreetParkingSides(mapOf(
+                "parking:lane:both" to "perpendicular",
+                "parking:lane:left:perpendicular" to "street_side",
+                "parking:lane:right:perpendicular" to "half_on_kerb",
+            ))
         )
+    }
 
+    @Test fun `parking with old schema where orientation differs per side`() {
         assertEquals(
-            LeftAndRightStreetParking(IncompleteStreetParking, IncompleteStreetParking),
+            LeftAndRightStreetParking(
+                StreetParkingPositionAndOrientation(PERPENDICULAR, STREET_SIDE),
+                StreetParkingPositionAndOrientation(PARALLEL, HALF_ON_STREET)
+            ),
             createStreetParkingSides(mapOf(
-                "parking:lane:left" to "yes",
-                "parking:lane:right" to "yes",
+                "parking:lane:left" to "perpendicular",
+                "parking:lane:right" to "parallel",
+                "parking:lane:left:perpendicular" to "street_side",
+                "parking:lane:right:parallel" to "half_on_kerb",
             ))
         )
+    }
 
-        assertEquals(
-            LeftAndRightStreetParking(IncompleteStreetParking, IncompleteStreetParking),
-            createStreetParkingSides(mapOf("parking:lane" to "yes"))
+    @Test fun `special values for parking with old schema`() {
+
+        val values = listOf(
+            "no" to NoStreetParking,
+            "separate" to StreetParkingSeparate,
+            "yes" to IncompleteStreetParking,
+            "foobar" to UnknownStreetParking
         )
+
+        for ((str, value) in values) {
+            assertEquals(
+                LeftAndRightStreetParking(value, null),
+                createStreetParkingSides(mapOf("parking:lane:left" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(null, value),
+                createStreetParkingSides(mapOf("parking:lane:right" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(value, value),
+                createStreetParkingSides(mapOf("parking:lane:both" to str))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(value, value),
+                createStreetParkingSides(mapOf(
+                    "parking:lane:left" to str,
+                    "parking:lane:right" to str,
+                ))
+            )
+
+            assertEquals(
+                LeftAndRightStreetParking(value, value),
+                createStreetParkingSides(mapOf("parking:lane" to str))
+            )
+        }
     }
 
-    @Test fun `unknown orientation leads to unknown`() {
+    @Test fun `unknown orientation leads to unknown with old schema`() {
         assertEquals(
             LeftAndRightStreetParking(UnknownStreetParking, null),
             createStreetParkingSides(mapOf("parking:lane:left" to "something"))
         )
     }
 
-    @Test fun `unknown position leads to unknown`() {
+    @Test fun `unknown position leads to unknown with old schema`() {
         assertEquals(
             LeftAndRightStreetParking(UnknownStreetParking, null),
             createStreetParkingSides(mapOf(
@@ -233,7 +466,7 @@ class StreetParkingParserKtTest {
         )
     }
 
-    @Test fun `marked is interpreted as incomplete`() {
+    @Test fun `marked is interpreted as incomplete with old schema`() {
         assertEquals(
             LeftAndRightStreetParking(IncompleteStreetParking, null),
             createStreetParkingSides(mapOf(
@@ -242,15 +475,11 @@ class StreetParkingParserKtTest {
             )))
     }
 
-    @Test fun `orientation without position is interpreted as incomplete`() {
+    @Test fun `orientation without position is interpreted as incomplete with old schema`() {
         assertEquals(
             LeftAndRightStreetParking(IncompleteStreetParking, null),
             createStreetParkingSides(mapOf("parking:lane:left" to "diagonal")))
     }
 
-    @Test fun `all unknown`() {
-        assertEquals(
-            null,
-            createStreetParkingSides(mapOf()))
-    }
+    //endregion
 }