Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,71 @@
package de.westnordost.streetcomplete.data.osm.geometry

import de.westnordost.streetcomplete.data.osm.geometry.polygons.Point
import de.westnordost.streetcomplete.data.osm.geometry.polygons.Polygon
import de.westnordost.streetcomplete.data.osm.geometry.polygons.PolygonAlgorithms
import kotlin.test.Test
import kotlin.test.assertTrue

class PolylabelHoleTest {

@Test
fun testSquareDonut() {
val outer = listOf(
Point(0.0, 0.0),
Point(20.0, 0.0),
Point(20.0, 20.0),
Point(0.0, 20.0),
Point(0.0, 0.0)
)

val hole = listOf(
Point(8.0, 8.0),
Point(12.0, 8.0),
Point(12.0, 12.0),
Point(8.0, 12.0),
Point(8.0, 8.0)
)

val polygon = Polygon(outer, holes = listOf(hole))
val result = PolygonAlgorithms.polylabel(polygon, precision = 0.5)

// The result must be inside the outer polygon
assertTrue(isPointInRing(result, outer), "Result should be inside the outer polygon")

// The result must be outside the hole
assertTrue(!isPointInRing(result, hole), "Result should be outside the hole")

// Optional: approximate distance to nearest boundary
val dist = PolygonAlgorithms.run {
privatePointToPolygonDist(result, polygon)
}
assertTrue(dist > 3.5, "Result should be reasonably far from edges")
}

// Helper (copy of existing point-in-ring logic)
private fun isPointInRing(p: Point, ring: List<Point>): Boolean {
var inside = false
var j = ring.lastIndex
for (i in ring.indices) {
val xi = ring[i].x
val yi = ring[i].y
val xj = ring[j].x
val yj = ring[j].y
if ((yi > p.y) != (yj > p.y) &&
(p.x < (xj - xi) * (p.y - yi) / (yj - yi + 0.0) + xi)) {
inside = !inside
}
j = i
}
return inside
}

// Expose pointToPolygonDist for distance check (since private in PolygonAlgorithms)
private fun PolygonAlgorithms.privatePointToPolygonDist(p: Point, polygon: Polygon): Double {
val method = PolygonAlgorithms::class.java.getDeclaredMethod(
"pointToPolygonDist", Point::class.java, Polygon::class.java
)
method.isAccessible = true
return method.invoke(this, p, polygon) as Double
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.westnordost.streetcomplete.data.osm.geometry

import de.westnordost.streetcomplete.data.osm.geometry.polygons.Point
import de.westnordost.streetcomplete.data.osm.geometry.polygons.Polygon
import de.westnordost.streetcomplete.data.osm.geometry.polygons.PolygonAlgorithms
import kotlin.test.Test
import kotlin.test.assertEquals

class PolylabelSimpleTest {

@Test
fun testSimpleSquare() {
val poly = Polygon(
shape = listOf(
Point(0.0, 0.0),
Point(10.0, 0.0),
Point(10.0, 10.0),
Point(0.0, 10.0),
Point(0.0, 0.0)
)
)

val result = PolygonAlgorithms.polylabel(poly, precision = 1.0)

assertEquals(5.0, result.x, 1.0)
assertEquals(5.0, result.y, 1.0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.westnordost.streetcomplete.data.osm.geometry

import de.westnordost.streetcomplete.data.osm.geometry.polygons.Point
import de.westnordost.streetcomplete.data.osm.geometry.polygons.Polygon
import de.westnordost.streetcomplete.data.osm.geometry.polygons.PolygonAlgorithms
import kotlin.test.Test
import kotlin.test.assertTrue

class PolylabelTest {

@Test
fun testIrregularPolygon() {
val shape = listOf(
Point(100.0, 100.0),
Point(500.0, 120.0),
Point(480.0, 400.0),
Point(200.0, 450.0),
Point(120.0, 300.0),
Point(100.0, 100.0)
)

val polygon = Polygon(shape)

val result = PolygonAlgorithms.polylabel(polygon, precision = 5.0)

// We don’t hardcode a value — we test geometric properties.
// 1. It must be inside the bounding box:
assertTrue(result.x in 100.0..500.0)
assertTrue(result.y in 100.0..450.0)

// 2. It must be inside polygon (distance > 0)
val dist = PolygonAlgorithms.pointToPolygonDist(result, polygon)
assertTrue(dist > 0.0)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.westnordost.streetcomplete.data.osm.geometry

import PriorityQueue
import de.westnordost.streetcomplete.data.osm.geometry.polygons.Cell
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class PriorityQueueTest {

@Test
fun testPriorityQueueSortsByCellMaxDescending() {
val q = PriorityQueue<Cell>()

// distance = distance to polygon center; half=half size
val c1 = Cell(0.0, 0.0, 1.0, 1.0) // max = 1 + 1.414
val c2 = Cell(0.0, 0.0, 1.0, 5.0) // max = 5 + 1.414
val c3 = Cell(0.0, 0.0, 1.0, 3.0) // max = 3 + 1.414

q.add(c1)
q.add(c2)
q.add(c3)

// Should extract in descending max order (c2 > c3 > c1)
assertEquals(c2, q.poll())
assertEquals(c3, q.poll())
assertEquals(c1, q.poll())
assertTrue(q.isEmpty)
}
}
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,22 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

import kotlin.math.sqrt

data 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 =
this.max.compareTo(other.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,151 @@
package de.westnordost.streetcomplete.data.osm.geometry.polygons

import PriorityQueue
import kotlin.math.min
import kotlin.math.sqrt

/*
Implementation of the polylabel algorithm inspired by mapbox's implementation in java.
See here : https://github.com/mapbox/polylabel
*/
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 {
val minX = polygon.shape.minOf { it.x }
val maxX = polygon.shape.maxOf { it.x }
val minY = polygon.shape.minOf { it.y }
val maxY = polygon.shape.maxOf { it.y }

val width = maxX - minX
val height = maxY - minY
val cellSize = (min(width, height) / 10.0)
val halfCell = cellSize / 2.0

val queue = PriorityQueue<Cell>()

// Initialize grid, skip cells inside holes
var x = minX
while (x < maxX) {
var y = minY
while (y < maxY) {
val center = Point(x + halfCell, y + halfCell)
if (isPointInRing(center, polygon.shape) &&
polygon.holes.none { isPointInRing(center, it) }) {
val distance = pointToPolygonDist(center, polygon)
queue.add(Cell(center.x, center.y, halfCell, distance))
}
y += cellSize
}
x += cellSize
}

var best: Cell? = null

while (queue.isNotEmpty()) {
val cell = queue.poll()

if (best == null || cell.distance > best.distance) best = cell

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

val h = cell.half / 2.0
val children = listOf(
Point(cell.centerX - h, cell.centerY - h),
Point(cell.centerX + h, cell.centerY - h),
Point(cell.centerX - h, cell.centerY + h),
Point(cell.centerX + h, cell.centerY + h)
)

for (c in children) {
if (isPointInRing(c, polygon.shape) &&
polygon.holes.none { isPointInRing(c, it) }) {
queue.add(Cell(c.x, c.y, h, pointToPolygonDist(c, polygon)))
}
}
}

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

fun pointToPolygonDist(point: Point, polygon: Polygon): Double {
// Is the point inside the outer shape?
val insideOuter = isPointInRing(point, polygon.shape)

// Distance to outer shape
var minDistSq = ringDistanceSq(point, polygon.shape)

// Distance to holes
for (hole in polygon.holes) {
val insideHole = isPointInRing(point, hole)
val distSqHole = ringDistanceSq(point, hole)

if (insideHole) {
// Inside a hole → consider outside polygon
return -sqrt(distSqHole)
}

// Outside hole, may be closer than outer
minDistSq = minOf(minDistSq, distSqHole)
}

return if (insideOuter) sqrt(minDistSq) else -sqrt(minDistSq)
}

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
}
}
Loading