Skip to content
Open
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
4501ce5
Rename README.md to README.adoc
NicodeH Sep 3, 2025
b901e47
Update README.adoc
NicodeH Sep 3, 2025
2ca0855
Update README.adoc
NicodeH Sep 3, 2025
933971a
Update README.adoc
NicodeH Sep 3, 2025
2c2511a
Update README.adoc
NicodeH Sep 3, 2025
523de1d
Initialisation des docs
NicodeH Sep 4, 2025
9007f3f
Update doc_tech.adoc
NicodeH Sep 4, 2025
279d069
Update doc_user.adoc
NicodeH Sep 4, 2025
b9a4260
Changement du badge de licence
ElPoraz Sep 4, 2025
5ba5e00
Adding cr and odj folder
ElPoraz Sep 4, 2025
e877446
Ajout du lien vers le CR
ElPoraz Sep 4, 2025
8cb190a
Correction du lien vers cr de télétravail
ElPoraz Sep 4, 2025
4c9b2c4
Ajout du cr de tt du 04 septembre
ElPoraz Sep 4, 2025
1f61a05
Add the Devis to the docs
HugoTHOLLON Sep 5, 2025
abae682
Update du README.adoc
ElPoraz Sep 5, 2025
8a4f681
Adding CR and ODJ Sprint 1
ElPoraz Sep 11, 2025
bd2d066
Update ODJ/CR section with links and notes
ElPoraz Sep 11, 2025
8e0dd92
Merge branch 'streetcomplete:master' into master
HugoTHOLLON Sep 15, 2025
650926e
Create sprint 1 backlog
NariaReynhard Sep 15, 2025
accd989
Add links for CR AND ODJ part2 for Sprint 1 in README
ElPoraz Sep 15, 2025
2476c62
Adding the files
ElPoraz Sep 15, 2025
f22cb00
Update telework register with new entry and add last update date on t…
ElPoraz Sep 17, 2025
4036224
copy of oneway to adapt to bothway & updating of the first file
NariaReynhard Sep 18, 2025
d09a0c1
Quest should be done, but testing is not done yet
NariaReynhard Sep 18, 2025
fb196f7
Adding the motorcycle_parking_fee quest number 180
ElPoraz Sep 19, 2025
0cd4572
Update README.adoc
ElPoraz Sep 19, 2025
ed0fc84
V1 without SVG pictures
RaphaLLamothe Sep 19, 2025
fc1b951
Added AddBikeChargingStationCapacity quest suggested in #6457
HugoTHOLLON Sep 20, 2025
0edf053
Quest added : Where is this first aid kit located? (Version 1 + new i…
NicodeH Sep 20, 2025
cd6ebd9
Added AddScooterChargingStationCapacity quest suggested in #6457, the…
HugoTHOLLON Sep 21, 2025
bb10ec2
Merge branch 'streetcomplete:master' into bothways-quest
NariaReynhard Sep 21, 2025
cd279b4
Merge pull request #30 from HugoTHOLLON/when-operates
RaphaLLamothe Sep 21, 2025
8fff4c9
Merge branch 'master' into bothways-quest
RaphaLLamothe Sep 21, 2025
8233b85
Merge pull request #31 from HugoTHOLLON/bothways-quest
RaphaLLamothe Sep 21, 2025
4b43959
Merge pull request #32 from HugoTHOLLON/motorcycle_parking_fee
ElPoraz Sep 21, 2025
8bd7d53
Merge pull request #33 from HugoTHOLLON/first-aid-kit-183
RaphaLLamothe Sep 21, 2025
8571bb4
Merge pull request #34 from HugoTHOLLON/charging_station_quest
NariaReynhard Sep 21, 2025
76a1dcc
Saving for pull
NariaReynhard Sep 21, 2025
4c249c1
Saving for pull
NariaReynhard Sep 21, 2025
c34b39a
Solving QuestModule duplicate id problem.
HugoTHOLLON Sep 21, 2025
efb59f9
Saving for pull
NariaReynhard Sep 21, 2025
3d233f4
Saving for pull
NariaReynhard Sep 21, 2025
1ab1702
fixed unexepected refactoring side effect
NariaReynhard Sep 21, 2025
755857c
Solved another duplicate quest id problem
HugoTHOLLON Sep 21, 2025
ecaa5be
changing writing on answers picture
RaphaLLamothe Sep 21, 2025
4df1b04
Renamed bothway for more intuitivity
NariaReynhard Sep 21, 2025
f59e051
Updating bothway name for more intuitivity
NariaReynhard Sep 21, 2025
bc3cf44
Update doc_user.adoc
NicodeH Sep 22, 2025
ba7164e
Update doc_user.adoc
NicodeH Sep 22, 2025
34e91d7
Initialisation du document install
NariaReynhard Sep 23, 2025
e0a2912
Update doc_install.md
NariaReynhard Sep 23, 2025
7225958
MAJ doc_install.md sur la partie install sur PC
NariaReynhard Sep 23, 2025
addd052
Modify README with installation and links
ElPoraz Sep 24, 2025
13e9cc9
Update last update date in README
ElPoraz Sep 24, 2025
f88a416
Update links for ODJ and CR in README
ElPoraz Sep 24, 2025
c2e78ca
Adding ODJ 22 september
ElPoraz Sep 24, 2025
dc722b8
Add CR 22 september
ElPoraz Sep 24, 2025
0892c21
Changing 'location' to 'description' in AddFirstAidKitLocation quest …
HugoTHOLLON Sep 24, 2025
4c7490c
Changing achievement from AddAerialBothWay quest from Car to Pedestrian.
HugoTHOLLON Sep 24, 2025
34a919e
set up of an extractable work folder for new polylabel algorithm
NariaReynhard Sep 29, 2025
7a01cb2
completed logic files, mostly patch coordinates <-> latlon logic. in …
NariaReynhard Sep 29, 2025
c81961b
removed modifications made from other branches of the fork to keep th…
NariaReynhard Sep 29, 2025
e001ac3
refreshed readme.md
NariaReynhard Sep 29, 2025
2d8cb78
added priorityQueue heap to avoid java dependencies
NariaReynhard Oct 5, 2025
aef8921
Algorithm complete but not plug nor tested yet
NariaReynhard Oct 5, 2025
661a0fc
mandatory save before pull
NariaReynhard Oct 6, 2025
efd3b1a
fixed merge conflict
NariaReynhard Oct 7, 2025
d200db3
pushing the slippery file
NariaReynhard Oct 8, 2025
84aab01
merge before push
NariaReynhard Oct 8, 2025
f354b06
Handled holed shapes and precision
NariaReynhard Oct 20, 2025
7883cb2
Fixed undesired changes
NariaReynhard Oct 22, 2025
add9e59
Reuploaded missing files from parking_fee
NariaReynhard Oct 22, 2025
16b4173
Fixed a last issue with application name
NariaReynhard Oct 22, 2025
a8743ac
Merge branch 'master' into update_polygone_center_algorithm
NariaReynhard Oct 22, 2025
d7272d5
Fixed indentation issue
NariaReynhard Oct 22, 2025
9a86857
refixed unexpected file changes
NariaReynhard Nov 18, 2025
719b83d
refixed unexpected file changes
NariaReynhard Nov 18, 2025
571cf8c
Deleted a comment that came from another PR of my group and should no…
NariaReynhard Dec 1, 2025
a143411
Merge branch 'update_polygone_center_algorithm' of https://github.com…
NariaReynhard Dec 1, 2025
db07680
similar to previous push, string.xml values changed that appear on th…
NariaReynhard Dec 1, 2025
c9a615a
Added reference to mapbox's work on polylabel for proper credits
NariaReynhard Dec 1, 2025
e51b633
Added more documentation at the start of the priorityQueue and fixed …
NariaReynhard Dec 1, 2025
551a3a0
adding unit tests for polylabel
NariaReynhard Dec 1, 2025
6c9b906
fixed a few logic issues
NariaReynhard Dec 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ fun questTypeRegistry(
101 to AddFerryAccessPedestrian(),
102 to AddFerryAccessMotorVehicle(),

// aerial way: usually visible from looking at the aerial way, but not always...

103 to AddProhibitedForPedestrians(), // need to understand the pedestrian situation

104 to MarkCompletedHighwayConstruction(), // need to look the whole way
Expand Down
26 changes: 26 additions & 0 deletions app/src/androidMain/res/drawable/ic_quest_first_aid_kit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<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="M64,0A64,64 0 1,0 64,128A64,64 0 1,0 64,0Z"
android:fillColor="#9BBE55"/>

<path
android:pathData="M32,40h64a4,4 0 0 1 4,4v40a4,4 0 0 1 -4,4H32a4,4 0 0 1 -4,-4V44a4,4 0 0 1 4,-4z"
android:fillColor="#EF4444"/>

<path
android:pathData="M48,32h32a4,4 0 0 1 4,4v4h-8v-4H52v4h-8v-4a4,4 0 0 1 4,-4z"
android:fillColor="#D1D5DB"/>

<path
android:pathData="M64,48a16,16 0 1,0 0,32a16,16 0 1,0 0,-32z"
android:fillColor="#E5E7EB"/>

<path
android:pathData="M68,56h-8v8h-8v8h8v8h8v-8h8v-8h-8z"
android:fillColor="#EF4444"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@
android:pathData="m73.168,66.28a1.218,1.218 0,1 0,0 2.436h4.054l1.33,3.251c-0.611,-0.056 -1.345,0.059 -2.192,0.36 -2.248,1.065 -4.606,2.688 -5.149,4.914h-2.224c-1.412,-1.494 -3.083,-2.387 -4.773,-2.804 -1.921,-0.475 -3.84,-0.457 -5.671,-0.443a1.624,1.624 0,0 0,-1.464 2.336c-0.102,0.039 -0.204,0.077 -0.306,0.12 -2.366,0.987 -4.637,3.214 -5.557,6.892l2.413,0.603c-0.096,0.457 -0.148,0.93 -0.148,1.414 0,3.795 3.106,6.901 6.901,6.901 3.379,0 6.205,-2.465 6.786,-5.683h7.625a1.218,1.218 0,0 0,1.038 -0.58l1.499,-2.439 1.282,0.32c-0.106,0.478 -0.166,0.973 -0.166,1.481 0,3.795 3.106,6.901 6.901,6.901 3.795,0 6.901,-3.106 6.901,-6.901 0,-2.494 -1.346,-4.684 -3.343,-5.897l1.546,-1.842c-1.404,-1.179 -3.339,-1.643 -5.394,-1.469 -0.662,0.056 -1.336,0.18 -2.01,0.366l-0.97,-2.371c1.148,0.608 2.789,0.993 3.614,0.993v-4.871c-1.078,0 -3.551,0.744 -4.491,1.732L79.167,67.036A1.218,1.218 0,0 0,78.039 66.28ZM60.381,81.218c1.878,0 3.446,1.222 3.961,2.922h-4.164a1.218,1.218 0,1 0,0 2.436h4.164c-0.515,1.7 -2.083,2.922 -3.961,2.922 -2.303,0 -4.14,-1.837 -4.14,-4.14 0,-2.303 1.837,-4.14 4.14,-4.14zM85.346,81.218c2.303,0 4.14,1.837 4.14,4.14 0,2.303 -1.837,4.14 -4.14,4.14 -2.303,0 -4.14,-1.837 -4.14,-4.14 0,-1.301 0.586,-2.453 1.511,-3.209l1.502,3.671a1.218,1.218 0,1 0,2.255 -0.923l-1.498,-3.66c0.122,-0.011 0.245,-0.018 0.37,-0.018z"
android:fillColor="#fff"/>
<path
android:pathData="m41.485,79.247a19.479,19.452 90,0 0,-19.452 19.479,19.479 19.452,90 0,0 19.452,19.479 19.479,19.452 90,0 0,19.452 -19.479,19.479 19.452,90 0,0 -19.452,-19.479z"
android:pathData="m86.485,79.247a19.479,19.452 90,0 0,-19.452 19.479,19.479 19.452,90 0,0 19.452,19.479 19.479,19.452 90,0 0,19.452 -19.479,19.479 19.452,90 0,0 -19.452,-19.479z"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:strokeWidth="4.9318"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="m60.937,95.447a19.479,19.452 90,0 1,-19.452 19.479,19.479 19.452,90 0,1 -19.452,-19.479 19.479,19.452 90,0 1,19.452 -19.479,19.479 19.452,90 0,1 19.452,19.479"
android:pathData="m105.937,95.447a19.479,19.452 90,0 1,-19.452 19.479,19.479 19.452,90 0,1 -19.452,-19.479 19.479,19.452 90,0 1,19.452 -19.479,19.479 19.452,90 0,1 19.452,19.479"
android:strokeLineJoin="round"
android:strokeWidth="4.9318"
android:fillColor="#dbdbdb"
android:strokeColor="#a6a6a6"
android:strokeLineCap="round"/>
<path
android:pathData="m39.054,81.682l0,2.364c-1.242,0.284 -2.31,0.802 -3.204,1.555 -1.596,1.344 -2.394,3.094 -2.394,5.25 0,1.53 0.355,2.756 1.064,3.678s1.608,1.606 2.698,2.054c1.097,0.44 2.474,0.833 4.128,1.179 1.283,0.287 2.258,0.579 2.925,0.875 0.675,0.296 1.013,0.85 1.013,1.661 0,0.845 -0.355,1.458 -1.064,1.839 -0.709,0.372 -1.528,0.558 -2.457,0.558 -2.524,0 -4.082,-1.057 -4.673,-3.17l-4.825,1.141c0.473,2.071 1.532,3.623 3.179,4.654 1.073,0.667 2.278,1.112 3.609,1.344l0,2.612l4.863,0l0,-2.638c1.568,-0.273 2.876,-0.806 3.913,-1.611 1.832,-1.437 2.748,-3.306 2.748,-5.605 0,-2.257 -0.967,-4.024 -2.9,-5.301 -0.692,-0.473 -1.418,-0.816 -2.178,-1.027 -0.751,-0.211 -1.98,-0.507 -3.685,-0.888 -2.178,-0.364 -3.267,-1.112 -3.267,-2.245 0,-1.42 1.038,-2.131 3.115,-2.131 1.959,0 3.255,0.884 3.888,2.651l4.407,-1.446c-0.977,-2.813 -2.992,-4.493 -6.041,-5.046l0,-2.31z"
android:pathData="m84.054,81.682l0,2.364c-1.242,0.284 -2.31,0.802 -3.204,1.555 -1.596,1.344 -2.394,3.094 -2.394,5.25 0,1.53 0.355,2.756 1.064,3.678s1.608,1.606 2.698,2.054c1.097,0.44 2.474,0.833 4.128,1.179 1.283,0.287 2.258,0.579 2.925,0.875 0.675,0.296 1.013,0.85 1.013,1.661 0,0.845 -0.355,1.458 -1.064,1.839 -0.709,0.372 -1.528,0.558 -2.457,0.558 -2.524,0 -4.082,-1.057 -4.673,-3.17l-4.825,1.141c0.473,2.071 1.532,3.623 3.179,4.654 1.073,0.667 2.278,1.112 3.609,1.344l0,2.612l4.863,0l0,-2.638c1.568,-0.273 2.876,-0.806 3.913,-1.611 1.832,-1.437 2.748,-3.306 2.748,-5.605 0,-2.257 -0.967,-4.024 -2.9,-5.301 -0.692,-0.473 -1.418,-0.816 -2.178,-1.027 -0.751,-0.211 -1.98,-0.507 -3.685,-0.888 -2.178,-0.364 -3.267,-1.112 -3.267,-2.245 0,-1.42 1.038,-2.131 3.115,-2.131 1.959,0 3.255,0.884 3.888,2.651l4.407,-1.446c-0.977,-2.813 -2.992,-4.493 -6.041,-5.046l0,-2.31z"
android:strokeWidth="1.5202"
android:fillColor="#a6a6a6"/>
</vector>
19 changes: 19 additions & 0 deletions app/src/androidMain/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,11 @@ Before uploading your changes, the app checks with a &lt;a href="https://www.wes
<string name="quest_address_street_street_name_label">Street name:</string>
<string name="quest_address_street_hint2">Tap street on map to select it</string>

<string name="quest_aerialway_bicycle_title">Does this aerialway transport bicycles?</string>
<string name="quest_aerialway_bicycle_yes">Yes</string>
<string name="quest_aerialway_bicycle_summer">During summer</string>
<string name="quest_aerialway_bicycle_no">No</string>

<string name="quest_airConditioning_title">Is this place air-conditioned?</string>

<string name="quest_atm_cashin_title">Can you deposit cash at this ATM?</string>
Expand Down Expand Up @@ -777,6 +782,8 @@ Before uploading your changes, the app checks with a &lt;a href="https://www.wes
<string name="quest_barrier_bicycle_installation_openable">Openable</string>
<string name="quest_barrier_bicycle_installation_removable">Removable</string>

<string name="quest_bicycle_charging_station_capacity_title">How many bicycle can be charged here at the same time?</string>

<string name="quest_bicycle_incline_title">Which direction leads upwards here?</string>
<string name="quest_bicycle_incline_up_and_down">It’s up and down hops</string>

Expand Down Expand Up @@ -863,6 +870,11 @@ Before uploading your changes, the app checks with a &lt;a href="https://www.wes
<string name="quest_bollard_type_fixed">Fixed (not removable)</string>
<string name="quest_bollard_type_not_bollard">Not a bollard, but some other barrier</string>

<string name="quest_bothway_title">In what direction can you ride this ?</string>
<string name="quest_bothway_answer_bothway">This can be used bothway.</string>
<string name="quest_bothway_answer_upwards">This can only lead upwards.</string>
<string name="quest_bothway_answer_downwards">This can only lead downwards.</string>

<string name="quest_bridge_structure_title">What’s the structure of this bridge?</string>

<string name="quest_building_entrance_title">What kind of building entrance is this?</string>
Expand Down Expand Up @@ -1169,6 +1181,9 @@ A level counts as a roof level when its windows are in the roof. Subsequently, r
<string name="quest_defibrillator_location">Where is this defibrillator located?</string>
<string name="quest_defibrillator_location_description">Please provide a concise description of its position (e.g. “in the porter’s lounge”).</string>

<string name="quest_first_aid_kit_location">Where is the first aid kit located?</string>
<string name="quest_first_aid_kit_location_description">Please provide a concise description of its position (e.g. “in the porter’s lounge”).</string>

<string name="quest_kerb_height_title">What’s the height of this curb?</string>
<string name="quest_kerb_height_flush">Same level as road surface</string>
<string name="quest_kerb_height_lowered">A bit higher than road surface</string>
Expand Down Expand Up @@ -1257,6 +1272,8 @@ If there are no signs along the whole street which apply for the highlighted sec

<string name="quest_motorcycleParkingCoveredStatus_title">Is this motorcycle parking covered (protected from rain)?</string>

<string name="quest_motorcycle_parking_fee_title">Do you have to pay to park your motorcycle here?</string>

<string name="quest_noteDiscussion_title">Can you contribute anything to this note?</string>
<string name="quest_noteDiscussion_no">No, hide</string>
<!-- first placeholder is the person, second placeholder is the date -->
Expand Down Expand Up @@ -1491,6 +1508,8 @@ If there are no signs along the whole street which apply for the highlighted sec
<string name="quest_roofShape_answer_many">It has several different shapes</string>
<string name="default_disabled_msg_roofShape">This quest type is disabled by default because roof shapes are often not easily visible from the street. This quest type is also quite time-consuming; in most cases it is easier and more efficient to map this from aerial imagery at home.</string>

<string name="quest_scooter_charging_station_capacity_title">How many scooters can be charged here at the same time?</string>

<string name="quest_seating_name_title">What kind of seating does this place have?</string>
<string name="quest_seating_takeaway">No seating (takeout only)</string>
<string name="quest_seating_indoor_only">Indoor seating only</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.westnordost.streetcomplete.data.osm.geometry

import de.westnordost.streetcomplete.data.osm.geometry.polygons.PolygonAlgorithms
import de.westnordost.streetcomplete.data.osm.geometry.polygons.PolygonUtils
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.ElementType
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
Expand Down Expand Up @@ -61,7 +63,13 @@ class ElementGeometryCreator {
/* ElementGeometry considers polygons that are defined clockwise holes, so ensure that
it is defined CCW here. */
if (polyline.isRingDefinedClockwise()) polyline.reverse()
ElementPolygonsGeometry(arrayListOf(polyline), polyline.centerPointOfPolygon())
/* Current in progress conversion from centroid to visual center */
val outer = polyline
val holes = emptyList<List<LatLon>>() // Way as area has no explicit holes
val poly = PolygonUtils.fromLatLon(outer, holes)
val best = PolygonAlgorithms.polylabel(poly, precision = 0.0001) //Precision will be upgraded later to depend on zoom level
ElementPolygonsGeometry(arrayListOf(polyline), LatLon(best.y, best.x))
/* Current in progress conversion from centroid to visual center */
} else {
ElementPolylinesGeometry(arrayListOf(polyline), polyline.centerPointOfPolyline())
}
Expand Down Expand Up @@ -97,7 +105,15 @@ class ElementGeometryCreator {

/* only use first ring that is not a hole if there are multiple
this is the same behavior as Leaflet or Tangram */
return ElementPolygonsGeometry(rings, outer.first().centerPointOfPolygon())
val outerRing = outer.first()

/* Current in progress conversion from centroid to visual center */
val holes = if (rings.size > 1) rings.drop(1) else emptyList()
val poly = PolygonUtils.fromLatLon(outerRing, holes)
val best = PolygonAlgorithms.polylabel(poly, precision = 0.0001) //Precision will be upgraded later to depend on zoom level
return ElementPolygonsGeometry(rings, LatLon(best.y, best.x))
/* Current in progress conversion from centroid to visual center */

}

private fun createPolylinesGeometry(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

import kotlin.math.sqrt

class Cell(
val centerX: Double,
val centerY: Double,
val half: Double, // half of the cell size
val distance: Double, // distance between cell center and polygon. Positive if inside
) : Comparable<Cell> {

/* max distance to expect, optimistic bound */
val max: Double = distance + half * SQRT2

/* Looking for the most promising cell */
override fun compareTo(other: Cell): Int = other.max.compareTo(this.max)

companion object {
private val SQRT2 = sqrt(2.0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

/* Simple 2D point for algorithm logic */
class Point(val x: Double, val y: Double)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

/**
* Representation of a polygon with
* a list of points that represents the outer shape
* (optional) a list composed of lists of points that each represents a hole in the polygon
*/
class Polygon (val shape: List<Point>, val holes: List<List<Point>> = emptyList())
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

import kotlin.math.min

object PolygonAlgorithms {

/* Simple centroid algorithm */
fun centroid(polygon: Polygon): Point {
val points = polygon.shape
var sumX = 0.0
var sumY = 0.0
for (p in points) {
sumX += p.x
sumY += p.y
}
return Point(sumX / points.size, sumY / points.size)
}

/* Core of the problem : visual center (within the polygon) */
/* Set up of the variables */
fun polylabel(polygon: Polygon, precision: Double = 1.0): Point {
var minX = polygon.shape.first().x
var maxX = polygon.shape.first().x
var minY = polygon.shape.first().y
var maxY = polygon.shape.first().y
for (pts in polygon.shape) {
if (pts.x < minX) minX = pts.x
if (pts.x > maxX) maxX = pts.x
if (pts.y < minY) minY = pts.y
if (pts.y > maxY) maxY = pts.y
}
var width = maxX - minX
var height = maxY - minY
val precision = (min(width, height) / 100.0).coerceAtLeast(0.5)

val cellSize = minOf(width, height)
val halfCellSize = cellSize / 2
val queue = PriorityQueue<Cell>()

/* Set up of the initial working grid */
var x = minX
while (x < maxX) {
var y = minY
while (y < maxY) {
val centerX = x + (halfCellSize)
val centerY = y + (halfCellSize)
val distance = pointToPolygonDist(Point(centerX, centerY), polygon)
queue.add(Cell(centerX, centerY, halfCellSize, distance))
y += cellSize
}
x += cellSize
}

/* Heart of the beast : processing where is the visual center */
var best = queue.poll()!!
val centroid = centroid(polygon)
val centroidCell = Cell(centroid.x, centroid.y, 0.0, pointToPolygonDist(centroid, polygon))
if (centroidCell.distance > best.distance) best = centroidCell

while (!queue.isEmpty) {
val cell = queue.poll()

if (cell.distance > best.distance) {
best = cell
}

if (cell.max - best.distance <= precision) continue

val half = cell.half / 2
val children = listOf(
Cell(cell.centerX - half, cell.centerY - half, half, pointToPolygonDist(Point(cell.centerX - half, cell.centerY - half), polygon)),
Cell(cell.centerX + half, cell.centerY - half, half, pointToPolygonDist(Point(cell.centerX + half, cell.centerY - half), polygon)),
Cell(cell.centerX - half, cell.centerY + half, half, pointToPolygonDist(Point(cell.centerX - half, cell.centerY + half), polygon)),
Cell(cell.centerX + half, cell.centerY + half, half, pointToPolygonDist(Point(cell.centerX + half, cell.centerY + half), polygon))
)
for (c in children) {
queue.add(c)
}
}

return Point(best.centerX, best.centerY)
}

fun pointToPolygonDist(pointToObserve: Point, polygon: Polygon): Double {
// Distance and inside state for the outer ring
var inside = isPointInRing(pointToObserve, polygon.shape)
var minDistSq = ringDistanceSq(pointToObserve, polygon.shape)

// Check holes (inner rings)
for (hole in polygon.holes) {
// If point is inside a hole, it's considered outside overall
if (isPointInRing(pointToObserve, hole)) inside = false

// Still check the distance — holes can affect nearest boundary
val distSq = ringDistanceSq(pointToObserve, hole)
if (distSq < minDistSq) minDistSq = distSq
}

val dist = kotlin.math.sqrt(minDistSq)
return if (inside) dist else -dist
}


private fun pointToSegmentDistSq(pointToObserve: Point, pointA: Point, pointB: Point): Double {
var x = pointA.x
var y = pointA.y
var dx = pointB.x - x
var dy = pointB.y - y

if (dx != 0.0 || dy != 0.0) {
val t = ((pointToObserve.x - x) * dx + (pointToObserve.y - y) * dy) / (dx * dx + dy * dy)
when {
t > 1 -> { x = pointB.x; y = pointB.y }
t > 0 -> { x += dx * t; y += dy * t }
}
}

dx = pointToObserve.x - x
dy = pointToObserve.y - y
return dx * dx + dy * dy
}

private fun isPointInRing(point: Point, ring: List<Point>): Boolean {
var inside = false
for (i in ring.indices) {
val pointA = ring[i]
val pointB = ring[(i + 1) % ring.size]

val intersects = ((pointA.y > point.y) != (pointB.y > point.y)) &&
(point.x < (pointB.x - pointA.x) * (point.y - pointA.y) / (pointB.y - pointA.y) + pointA.x)

if (intersects) inside = !inside
}
return inside
}

private fun ringDistanceSq(point: Point, ring: List<Point>): Double {
var minDistSq = Double.POSITIVE_INFINITY
for (i in ring.indices) {
val pointA = ring[i]
val pointB = ring[(i + 1) % ring.size]
val distSq = pointToSegmentDistSq(point, pointA, pointB)
if (distSq < minDistSq) minDistSq = distSq
}
return minDistSq
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

import de.westnordost.streetcomplete.data.osm.mapdata.LatLon

/* patch to bridge generic polygon logic to osm map logic */

object PolygonUtils {

/* convert LatLon polygon to generic polygon */
fun fromLatLon(shape: List<LatLon>, holes: List<List<LatLon>> = emptyList()): Polygon {
val outerPts = shape.map { Point(it.longitude, it.latitude) }
val holePts = holes.map { ring -> ring.map { Point (it.longitude, it.latitude) } }
return Polygon(outerPts, holePts)
}

/* Provide a visual center (within the polygon) with the OSM LatLon logic */
fun representativeCenter(polygon: Polygon): LatLon {
val center = PolygonAlgorithms.polylabel(polygon)
return LatLon(center.x, center.y)
}
}
Loading