From 8264eeb07573ed0a9a44e442a9c30d33a83681be Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 12:49:33 +0200 Subject: [PATCH 01/63] performance improvement in TagFilterExpression --- .../data/tagfilters/FiltersParser.kt | 15 +++++++++++---- .../data/tagfilters/TagFilterExpression.kt | 3 ++- .../data/tagfilters/TagFilterExpressionTest.kt | 14 ++++++++++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/FiltersParser.kt b/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/FiltersParser.kt index 4726ced3bb..d162d755d4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/FiltersParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/FiltersParser.kt @@ -1,8 +1,7 @@ package de.westnordost.streetcomplete.data.tagfilters import java.text.ParseException -import java.util.ArrayList -import java.util.Locale +import java.util.* import kotlin.math.min /** @@ -32,7 +31,7 @@ private val OPERATORS = arrayOf("=", "!=", "~", "!~") private fun String.stripQuotes() = replace("^[\"']|[\"']$".toRegex(), "") -private fun StringWithCursor.parseElementsDeclaration(): List { +private fun StringWithCursor.parseElementsDeclaration(): EnumSet { val result = ArrayList() result.add(parseElementDeclaration()) while (nextIsAndAdvance(',')) { @@ -42,7 +41,15 @@ private fun StringWithCursor.parseElementsDeclaration(): List EnumSet.of(result[0]) + 2 -> EnumSet.of(result[0], result[1]) + 3 -> EnumSet.of(result[0], result[1], result[2]) + else -> throw IllegalStateException() + } } private fun StringWithCursor.parseElementDeclaration(): ElementsTypeFilter { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpression.kt b/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpression.kt index acdaa0f8e7..90f5d57be6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpression.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpression.kt @@ -4,11 +4,12 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.data.tagfilters.ElementsTypeFilter.NODES import de.westnordost.streetcomplete.data.tagfilters.ElementsTypeFilter.WAYS import de.westnordost.streetcomplete.data.tagfilters.ElementsTypeFilter.RELATIONS +import java.util.* /** Represents a parse result of a string in filter syntax, i.e. * "ways with (highway = residential or highway = tertiary) and !name" */ class TagFilterExpression( - private val elementsTypes: List, + private val elementsTypes: EnumSet, private val tagExprRoot: BooleanExpression? ) { private val overpassQuery: String diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpressionTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpressionTest.kt index ac861af503..623e9d87b9 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpressionTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/tagfilters/TagFilterExpressionTest.kt @@ -8,6 +8,7 @@ import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.on import org.junit.Assert.* +import java.util.* class TagFilterExpressionTest { // Tests for toOverpassQLString are in FiltersParserTest @@ -50,7 +51,7 @@ class TagFilterExpressionTest { @Test fun `matches filter`() { val tagFilter: TagFilter = mock() - val expr = TagFilterExpression(listOf(ElementsTypeFilter.NODES), Leaf(tagFilter)) + val expr = TagFilterExpression(EnumSet.of(ElementsTypeFilter.NODES), Leaf(tagFilter)) on(tagFilter.matches(any())).thenReturn(true) assertTrue(expr.matches(node)) @@ -67,6 +68,15 @@ class TagFilterExpressionTest { private fun createMatchExpression(vararg elementsTypeFilter: ElementsTypeFilter): TagFilterExpression { val tagFilter: TagFilter = mock() on(tagFilter.matches(any())).thenReturn(true) - return TagFilterExpression(elementsTypeFilter.asList(), Leaf(tagFilter)) + return TagFilterExpression(createEnumSet(*elementsTypeFilter), Leaf(tagFilter)) + } + + private fun createEnumSet(vararg filters: ElementsTypeFilter): EnumSet { + return when (filters.size) { + 1 -> EnumSet.of(filters[0]) + 2 -> EnumSet.of(filters[0], filters[1]) + 3 -> EnumSet.of(filters[0], filters[1], filters[2]) + else -> throw IllegalStateException() + } } } From 8b2d513fa7f8ebe7c587bed50e04c07d874691b9 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 13:08:55 +0200 Subject: [PATCH 02/63] replace OsmApiElementGeometryCreator --- .../java/de/westnordost/osmapi/map/MapData.kt | 18 +++ .../de/westnordost/osmapi/map/MapDataDao.kt | 21 ++++ .../elementgeometry/ElementGeometryCreator.kt | 45 ++++++++ .../OsmApiElementGeometryCreator.kt | 80 ------------- .../data/osm/osmquest/OsmQuestsUploader.kt | 8 +- .../osmquest/undo/UndoOsmQuestsUploader.kt | 8 +- .../data/osm/splitway/SplitWaysUploader.kt | 8 +- .../osm/upload/OsmInChangesetsUploader.kt | 44 ++++++- .../ElementGeometryCreatorTest.kt | 56 +++++++++ .../OsmApiElementGeometryCreatorTest.kt | 108 ------------------ .../osm/osmquest/OsmQuestsUploaderTest.kt | 9 +- .../undo/UndoOsmQuestsUploaderTest.kt | 9 +- .../osm/splitway/SplitWaysUploaderTest.kt | 9 +- 13 files changed, 212 insertions(+), 211 deletions(-) create mode 100644 app/src/main/java/de/westnordost/osmapi/map/MapData.kt create mode 100644 app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreator.kt delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreatorTest.kt diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt new file mode 100644 index 0000000000..45e1ffebcd --- /dev/null +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -0,0 +1,18 @@ +package de.westnordost.osmapi.map + +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.Node +import de.westnordost.osmapi.map.data.Relation +import de.westnordost.osmapi.map.data.Way +import de.westnordost.osmapi.map.handler.MapDataHandler + +data class MapData( + val nodes: MutableMap = mutableMapOf(), + val ways: MutableMap = mutableMapOf(), + val relations: MutableMap = mutableMapOf()) : MapDataHandler { + + override fun handle(bounds: BoundingBox) {} + override fun handle(node: Node) { nodes[node.id] = node } + override fun handle(way: Way) { ways[way.id] = way } + override fun handle(relation: Relation) { relations[relation.id] = relation } +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt b/app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt new file mode 100644 index 0000000000..0624caf8a1 --- /dev/null +++ b/app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt @@ -0,0 +1,21 @@ +package de.westnordost.osmapi.map + +import de.westnordost.osmapi.map.data.BoundingBox + +fun MapDataDao.getMap(bounds: BoundingBox): MapData { + val result = MapData() + getMap(bounds, result) + return result +} + +fun MapDataDao.getWayComplete(id: Long): MapData { + val result = MapData() + getWayComplete(id, result) + return result +} + +fun MapDataDao.getRelationComplete(id: Long): MapData { + val result = MapData() + getRelationComplete(id, result) + return result +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt index efb6da09cc..0b76b86236 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.osm.elementgeometry +import de.westnordost.osmapi.map.MapData import de.westnordost.osmapi.map.data.* import de.westnordost.streetcomplete.ktx.isArea import de.westnordost.streetcomplete.util.centerPointOfPolygon @@ -11,6 +12,31 @@ import kotlin.collections.ArrayList /** Creates an ElementGeometry from an element and a collection of positions. */ class ElementGeometryCreator @Inject constructor() { + /** Create an ElementGeometry from any element, using the given MapData to find the positions + * of the nodes. + * + * @param element the element to create the geometry for + * @param mapData the MapData that contains the elements with the necessary + * + * @return an ElementGeometry or null if any necessary element to create the geometry is not + * in the given MapData */ + fun create(element: Element, mapData: MapData): ElementGeometry? { + when(element) { + is Node -> { + return create(element) + } + is Way -> { + val positions = mapData.getNodePositions(element) ?: return null + return create(element, positions) + } + is Relation -> { + val positionsByWayId = mapData.getWaysNodePositions(element) ?: return null + return create(element, positionsByWayId) + } + else -> return null + } + } + /** Create an ElementPointGeometry for a node. */ fun create(node: Node) = ElementPointGeometry(node.position) @@ -204,3 +230,22 @@ private fun MutableList.eliminateDuplicates() { } } } + +private fun MapData.getNodePositions(way: Way): List? { + return way.nodeIds.map { nodeId -> + val node = nodes[nodeId] ?: return null + node.position + } +} + +private fun MapData.getWaysNodePositions(relation: Relation): Map>? { + val wayMembers = relation.members.filter { it.type == Element.Type.WAY } + return wayMembers.associate { wayMember -> + val way = ways[wayMember.ref] ?: return null + val wayPositions = way.nodeIds.map { nodeId -> + val node = nodes[nodeId] ?: return null + node.position + } + way.id to wayPositions + } +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreator.kt deleted file mode 100644 index d390574a01..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreator.kt +++ /dev/null @@ -1,80 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.elementgeometry - -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.osmapi.common.errors.OsmNotFoundException - -import de.westnordost.osmapi.map.data.* -import de.westnordost.osmapi.map.handler.DefaultMapDataHandler -import javax.inject.Inject - -/** Creates the geometry for an element by fetching the necessary geometry data from the OSM API */ -class OsmApiElementGeometryCreator @Inject constructor( - private val mapDataApi: MapDataApi, - private val elementCreator: ElementGeometryCreator) { - - fun create(element: Element): ElementGeometry? { - when(element) { - is Node -> { - return elementCreator.create(element) - } - is Way -> { - val positions = getNodePositions(element.id) ?: return null - return elementCreator.create(element, positions) - } - is Relation -> { - val positionsByWayId = getWaysNodePositions(element.id) ?: return null - return elementCreator.create(element, positionsByWayId) - } - else -> return null - } - } - - private fun getNodePositions(wayId: Long): List? { - var way: Way? = null - val nodes = mutableMapOf() - val handler = object : DefaultMapDataHandler() { - override fun handle(n: Node) { nodes[n.id] = n } - override fun handle(w: Way) { way = w } - } - - try { - mapDataApi.getWayComplete(wayId, handler) - } catch (e : OsmNotFoundException) { - return null - } - val wayNodeIds = way?.nodeIds ?: return null - return wayNodeIds.map { nodeId -> - val node = nodes[nodeId] ?: return null - node.position - } - } - - private fun getWaysNodePositions(relationId: Long): Map>? { - val nodes = mutableMapOf() - val ways = mutableMapOf() - var relation: Relation? = null - - val handler = object : DefaultMapDataHandler() { - override fun handle(n: Node) { nodes[n.id] = n } - override fun handle(w: Way) { ways[w.id] = w } - override fun handle(r: Relation) { relation = r } - } - - try { - mapDataApi.getRelationComplete(relationId, handler) - } catch (e : OsmNotFoundException) { - return null - } - - val members = relation?.members ?: return null - val wayMembers = members.filter { it.type == Element.Type.WAY } - return wayMembers.associate { wayMember -> - val way = ways[wayMember.ref] ?: return null - val wayPositions = way.nodeIds.map { nodeId -> - val node = nodes[nodeId] ?: return null - node.position - } - way.id to wayPositions - } - } -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt index b9af99d69c..3ff728b43a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt @@ -2,9 +2,10 @@ package de.westnordost.streetcomplete.data.osm.osmquest import android.util.Log import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import javax.inject.Inject -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager @@ -19,12 +20,13 @@ class OsmQuestsUploader @Inject constructor( elementGeometryDB: ElementGeometryDao, changesetManager: OpenQuestChangesetsManager, questGiver: OsmQuestGiver, - osmApiElementGeometryCreator: OsmApiElementGeometryCreator, + elementGeometryCreator: ElementGeometryCreator, + mapDataApi: MapDataApi, private val osmQuestController: OsmQuestController, private val singleChangeUploader: SingleOsmElementTagChangesUploader, private val statisticsUpdater: StatisticsUpdater ) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - osmApiElementGeometryCreator) { + elementGeometryCreator, mapDataApi) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Applying quest changes") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt index cfd19364df..195b7205e6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt @@ -2,8 +2,9 @@ package de.westnordost.streetcomplete.data.osm.osmquest.undo import android.util.Log import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import javax.inject.Inject @@ -21,12 +22,13 @@ class UndoOsmQuestsUploader @Inject constructor( elementGeometryDB: ElementGeometryDao, changesetManager: OpenQuestChangesetsManager, questGiver: OsmQuestGiver, - osmApiElementGeometryCreator: OsmApiElementGeometryCreator, + elementGeometryCreator: ElementGeometryCreator, + mapDataApi: MapDataApi, private val undoQuestDB: UndoOsmQuestDao, private val singleChangeUploader: SingleOsmElementTagChangesUploader, private val statisticsUpdater: StatisticsUpdater ) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - osmApiElementGeometryCreator) { + elementGeometryCreator, mapDataApi) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Undoing quest changes") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt index e75f4c50a5..744acf311b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt @@ -3,8 +3,9 @@ package de.westnordost.streetcomplete.data.osm.splitway import android.util.Log import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Way +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager @@ -19,12 +20,13 @@ class SplitWaysUploader @Inject constructor( elementGeometryDB: ElementGeometryDao, changesetManager: OpenQuestChangesetsManager, questGiver: OsmQuestGiver, - osmApiElementGeometryCreator: OsmApiElementGeometryCreator, + elementGeometryCreator: ElementGeometryCreator, + mapDataApi: MapDataApi, private val splitWayDB: OsmQuestSplitWayDao, private val splitSingleOsmWayUploader: SplitSingleWayUploader, private val statisticsUpdater: StatisticsUpdater ) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, - questGiver, osmApiElementGeometryCreator) { + questGiver, elementGeometryCreator, mapDataApi) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Splitting ways") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt index 01d2fbc4a4..07555c519b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt @@ -1,13 +1,19 @@ package de.westnordost.streetcomplete.data.osm.upload import androidx.annotation.CallSuper +import de.westnordost.osmapi.common.errors.OsmNotFoundException +import de.westnordost.osmapi.map.MapData import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Node +import de.westnordost.osmapi.map.data.Relation +import de.westnordost.osmapi.map.data.Way +import de.westnordost.osmapi.map.getRelationComplete +import de.westnordost.osmapi.map.getWayComplete +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.* import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryEntry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener @@ -22,7 +28,8 @@ abstract class OsmInChangesetsUploader( private val elementGeometryDB: ElementGeometryDao, private val changesetManager: OpenQuestChangesetsManager, private val questGiver: OsmQuestGiver, - private val osmApiElementGeometryCreator: OsmApiElementGeometryCreator + private val elementGeometryCreator: ElementGeometryCreator, + private val mapDataApi: MapDataApi ): Uploader { override var uploadedChangeListener: OnUploadedChangeListener? = null @@ -75,7 +82,7 @@ abstract class OsmInChangesetsUploader( * * This will remove the dependencies to elementGeometryDB, questGiver etc */ private fun updateElement(newElement: Element) { - val geometry = osmApiElementGeometryCreator.create(newElement) + val geometry = createGeometry(newElement) if (geometry != null) { elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, geometry)) elementDB.put(newElement) @@ -99,6 +106,33 @@ abstract class OsmInChangesetsUploader( } } + private fun createGeometry(element: Element): ElementGeometry? { + when(element) { + is Node -> { + return elementGeometryCreator.create(element) + } + is Way -> { + val mapData: MapData + try { + mapData = mapDataApi.getWayComplete(element.id) + } catch (e: OsmNotFoundException) { + return null + } + return elementGeometryCreator.create(element, mapData) + } + is Relation -> { + val mapData: MapData + try { + mapData = mapDataApi.getRelationComplete(element.id) + } catch (e: OsmNotFoundException) { + return null + } + return elementGeometryCreator.create(element, mapData) + } + else -> return null + } + } + protected abstract fun getAll() : Collection /** Upload the changes for a single quest and element. * Returns the updated element(s) for which it should be checked whether they are eligible diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt index d334895498..d03dff9a41 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.osm.elementgeometry +import de.westnordost.osmapi.map.MapData import de.westnordost.osmapi.map.data.* import org.junit.Test @@ -116,6 +117,58 @@ class ElementGeometryCreatorTest { val geom = create(relation(member(SIMPLE_WAY1, SIMPLE_WAY2, SIMPLE_WAY3, WAY_DUPLICATE_NODES))) as ElementPolylinesGeometry assertTrue(geom.polylines.containsAll(listOf(CCW_RING, listOf(P0, P1, P2)))) } + + @Test fun `positions for way`() { + val nodes = listOf( + OsmNode(0, 1, P0, null, null, null), + OsmNode(1, 1, P1, null, null, null) + ) + val nodesById = nodes.associateBy { it.id }.toMutableMap() + val mapData = MapData(nodesById) + val geom = create(SIMPLE_WAY1, mapData) as ElementPolylinesGeometry + assertEquals(listOf(nodes.map { it.position }), geom.polylines) + } + + @Test fun `returns null for non-existent way`() { + val way = OsmWay(1L, 1, listOf(1,2,3), null) + assertNull(create(way, MapData())) + } + + @Test fun `positions for relation`() { + val relation = OsmRelation(1L, 1, listOf( + OsmRelationMember(0L, "", Element.Type.WAY), + OsmRelationMember(1L, "", Element.Type.WAY), + OsmRelationMember(1L, "", Element.Type.NODE) + ), null) + + val ways = listOf(SIMPLE_WAY1, SIMPLE_WAY2) + val nodesByWayId = mapOf>( + 0L to listOf( + OsmNode(0, 1, P0, null, null, null), + OsmNode(1, 1, P1, null, null, null) + ), + 1L to listOf( + OsmNode(1, 1, P1, null, null, null), + OsmNode(2, 1, P2, null, null, null), + OsmNode(3, 1, P3, null, null, null) + ) + ) + val nodesById = nodesByWayId.flatMap { it.value }.associateBy { it.id }.toMutableMap() + val waysById = ways.associateBy { it.id }.toMutableMap() + val mapData = MapData(nodesById, waysById) + val positions = listOf(P0, P1, P2, P3) + val geom = create(relation, mapData) as ElementPolylinesGeometry + assertEquals(listOf(positions), geom.polylines) + } + + @Test fun `returns null for non-existent relation`() { + val relation = OsmRelation(1L, 1, listOf( + OsmRelationMember(1L, "", Element.Type.WAY), + OsmRelationMember(2L, "", Element.Type.WAY), + OsmRelationMember(1L, "", Element.Type.NODE) + ), null) + assertNull(create(relation, MapData())) + } } private fun create(node: Node) = @@ -127,6 +180,9 @@ private fun create(way: Way) = private fun create(relation: Relation) = ElementGeometryCreator().create(relation, WAY_GEOMETRIES) +private fun create(element: Element, mapData: MapData) = + ElementGeometryCreator().create(element, mapData) + private val WAY_AREA = mapOf("area" to "yes") private val O: LatLon = OsmLatLon(1.0, 1.0) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreatorTest.kt deleted file mode 100644 index 455077b2e3..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/OsmApiElementGeometryCreatorTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.elementgeometry - -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.osmapi.common.errors.OsmNotFoundException -import de.westnordost.osmapi.map.data.* -import de.westnordost.osmapi.map.handler.MapDataHandler -import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.eq -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on -import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -class OsmApiElementGeometryCreatorTest { - - private lateinit var api: MapDataApi - private lateinit var elementCreator: ElementGeometryCreator - private lateinit var creator: OsmApiElementGeometryCreator - - @Before fun setUp() { - api = mock() - elementCreator = mock() - creator = OsmApiElementGeometryCreator(api, elementCreator) - } - - @Test fun `creates for node`() { - val node = OsmNode(1L, 1, 2.0, 3.0, null) - creator.create(node) - verify(elementCreator).create(node) - } - - @Test fun `creates for way`() { - val way = OsmWay(1L, 1, listOf(1,2,3), null) - val positions = listOf( - OsmLatLon(1.0, 2.0), - OsmLatLon(2.0, 4.0), - OsmLatLon(5.0, 6.0) - ) - on(api.getWayComplete(eq(1L), any())).thenAnswer { invocation -> - val handler = (invocation.arguments[1]) as MapDataHandler - handler.handle(way) - way.nodeIds.forEachIndexed { i, nodeId -> - handler.handle(OsmNode(nodeId, 1, positions[i], null)) - } - Any() - } - creator.create(way) - verify(elementCreator).create(way, positions) - } - - @Test fun `returns null for non-existent way`() { - val way = OsmWay(1L, 1, listOf(1,2,3), null) - on(api.getWayComplete(eq(1L), any())).thenThrow(OsmNotFoundException(404, "", "")) - assertNull(creator.create(way)) - verify(elementCreator, never()).create(eq(way), any()) - } - - @Test fun `creates for relation`() { - val relation = OsmRelation(1L, 1, listOf( - OsmRelationMember(1L, "", Element.Type.WAY), - OsmRelationMember(2L, "", Element.Type.WAY), - OsmRelationMember(1L, "", Element.Type.NODE) - ), null) - - val ways = listOf( - OsmWay(1L, 1, listOf(1,2,3), null), - OsmWay(2L, 1, listOf(4,5), null) - ) - val positions = mapOf>( - 1L to listOf( - OsmLatLon(1.0, 2.0), - OsmLatLon(2.0, 4.0), - OsmLatLon(5.0, 6.0) - ), - 2L to listOf( - OsmLatLon(2.0, 1.0), - OsmLatLon(0.0, -1.0) - ) - ) - on(api.getRelationComplete(eq(1L), any())).thenAnswer { invocation -> - val handler = (invocation.arguments[1]) as MapDataHandler - handler.handle(relation) - for (way in ways) { - handler.handle(way) - way.nodeIds.forEachIndexed { i, nodeId -> - handler.handle(OsmNode(nodeId, 1, positions[way.id]!![i], null)) - } - } - Any() - } - creator.create(relation) - verify(elementCreator).create(relation, positions) - } - - @Test fun `returns null for non-existent relation`() { - val relation = OsmRelation(1L, 1, listOf( - OsmRelationMember(1L, "", Element.Type.WAY), - OsmRelationMember(2L, "", Element.Type.WAY), - OsmRelationMember(1L, "", Element.Type.NODE) - ), null) - on(api.getRelationComplete(eq(1L), any())).thenThrow(OsmNotFoundException(404, "", "")) - assertNull(creator.create(relation)) - verify(elementCreator, never()).create(eq(relation), any()) - } -} diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt index c4cc9a4591..743e1e4fe8 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt @@ -4,10 +4,11 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.quest.QuestStatus import de.westnordost.streetcomplete.data.osm.changes.StringMapChanges import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao @@ -30,7 +31,8 @@ class OsmQuestsUploaderTest { private lateinit var changesetManager: OpenQuestChangesetsManager private lateinit var elementGeometryDB: ElementGeometryDao private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: OsmApiElementGeometryCreator + private lateinit var elementGeometryCreator: ElementGeometryCreator + private lateinit var mapDataApi: MapDataApi private lateinit var singleChangeUploader: SingleOsmElementTagChangesUploader private lateinit var statisticsUpdater: StatisticsUpdater private lateinit var uploader: OsmQuestsUploader @@ -43,11 +45,12 @@ class OsmQuestsUploaderTest { singleChangeUploader = mock() elementGeometryDB = mock() questGiver = mock() + mapDataApi = mock() elementGeometryCreator = mock() statisticsUpdater = mock() on(elementGeometryCreator.create(any())).thenReturn(mock()) uploader = OsmQuestsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, osmQuestController, singleChangeUploader, statisticsUpdater) + elementGeometryCreator, mapDataApi, osmQuestController, singleChangeUploader, statisticsUpdater) } @Test fun `cancel upload works`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt index 36e8ab53ec..34397245e1 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt @@ -4,9 +4,10 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChanges import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao @@ -30,7 +31,8 @@ class UndoOsmQuestsUploaderTest { private lateinit var changesetManager: OpenQuestChangesetsManager private lateinit var elementGeometryDB: ElementGeometryDao private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: OsmApiElementGeometryCreator + private lateinit var elementGeometryCreator: ElementGeometryCreator + private lateinit var mapDataApi: MapDataApi private lateinit var singleChangeUploader: SingleOsmElementTagChangesUploader private lateinit var statisticsUpdater: StatisticsUpdater private lateinit var uploader: UndoOsmQuestsUploader @@ -44,10 +46,11 @@ class UndoOsmQuestsUploaderTest { elementGeometryDB = mock() questGiver = mock() elementGeometryCreator = mock() + mapDataApi = mock() statisticsUpdater = mock() on(elementGeometryCreator.create(any())).thenReturn(mock()) uploader = UndoOsmQuestsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, undoQuestDB, singleChangeUploader, statisticsUpdater) + elementGeometryCreator, mapDataApi, undoQuestDB, singleChangeUploader, statisticsUpdater) } @Test fun `cancel upload works`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt index 2228d1ad89..991ff9dc31 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt @@ -4,8 +4,9 @@ import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.streetcomplete.on import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.OsmApiElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.ChangesetConflictException @@ -25,7 +26,8 @@ class SplitWaysUploaderTest { private lateinit var changesetManager: OpenQuestChangesetsManager private lateinit var elementGeometryDB: ElementGeometryDao private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: OsmApiElementGeometryCreator + private lateinit var elementGeometryCreator: ElementGeometryCreator + private lateinit var mapDataApi: MapDataApi private lateinit var splitSingleOsmWayUploader: SplitSingleWayUploader private lateinit var statisticsUpdater: StatisticsUpdater private lateinit var uploader: SplitWaysUploader @@ -39,10 +41,11 @@ class SplitWaysUploaderTest { elementGeometryDB = mock() questGiver = mock() elementGeometryCreator = mock() + mapDataApi = mock() statisticsUpdater = mock() on(elementGeometryCreator.create(any())).thenReturn(mock()) uploader = SplitWaysUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, splitWayDB, splitSingleOsmWayUploader, statisticsUpdater) + elementGeometryCreator, mapDataApi, splitWayDB, splitSingleOsmWayUploader, statisticsUpdater) } @Test fun `cancel upload works`() { From be49de0e42c2a33c8ac5dcf4862102725a4af10c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 15:14:11 +0200 Subject: [PATCH 03/63] remove wrong isApplicableTo --- .../de/westnordost/streetcomplete/quests/oneway/AddOneway.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index 68d51bf598..ceb8cc2dbf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -40,8 +40,7 @@ class AddOneway( override fun getTitle(tags: Map) = R.string.quest_oneway_title - override fun isApplicableTo(element: Element) = - filter.matches(element) && db.isForward(element.id) != null + override fun isApplicableTo(element: Element): Boolean? = null override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { val trafficDirectionMap: Map> From e028bc065a51b4d051c6ec6a8a96046eb61f2681 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 15:17:02 +0200 Subject: [PATCH 04/63] add putAll function to DownloadedTilesDao --- .../download/tiles/DownloadedTilesDaoTest.kt | 8 ++++- .../data/download/tiles/DownloadedTilesDao.kt | 33 +++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt index a366ce1f1c..6df89a44b1 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt @@ -76,6 +76,12 @@ class DownloadedTilesDaoTest : ApplicationDbTestCase() { check = dao.get(r(0, 0, 6, 6), 0) assertTrue(check.isEmpty()) } - + + @Test fun putMultipleQuestTypes() { + val rect = r(0, 0, 5, 5) + dao.putAll(rect, listOf("Huhu", "Haha")) + assertEquals(listOf("Huhu", "Haha"), dao.get(rect, 0)) + } + private fun r(left: Int, top: Int, right: Int, bottom: Int) = TilesRect(left, top, right, bottom) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt index 67f48d203c..d7e35533c7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt @@ -20,19 +20,32 @@ class DownloadedTilesDao @Inject constructor(private val dbHelper: SQLiteOpenHel private val db get() = dbHelper.writableDatabase + /** Persist that the given quest types have been downloaded in every tile in the given tile range */ + fun putAll(tilesRect: TilesRect, questTypeNames: List) { + db.transaction { + for (questTypeName in questTypeNames) { + putQuestType(tilesRect, questTypeName) + } + } + } + /** Persist that the given quest type has been downloaded in every tile in the given tile range */ fun put(tilesRect: TilesRect, questTypeName: String) { db.transaction { - val time = System.currentTimeMillis() - for (tile in tilesRect.asTileSequence()) { - val values = contentValuesOf( - X to tile.x, - Y to tile.y, - QUEST_TYPE to questTypeName, - DATE to time - ) - db.replaceOrThrow(NAME, null, values) - } + putQuestType(tilesRect, questTypeName) + } + } + + private fun putQuestType(tilesRect: TilesRect, questTypeName: String) { + val time = System.currentTimeMillis() + for (tile in tilesRect.asTileSequence()) { + val values = contentValuesOf( + X to tile.x, + Y to tile.y, + QUEST_TYPE to questTypeName, + DATE to time + ) + db.replaceOrThrow(NAME, null, values) } } From 9cc0603391dc25877e2e4630053091a1c2e5cc8a Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 15:17:38 +0200 Subject: [PATCH 05/63] MapData implements Iterable --- .../java/de/westnordost/osmapi/map/MapData.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt index 45e1ffebcd..d90703791b 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -1,18 +1,24 @@ package de.westnordost.osmapi.map -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Node -import de.westnordost.osmapi.map.data.Relation -import de.westnordost.osmapi.map.data.Way +import de.westnordost.osmapi.map.data.* import de.westnordost.osmapi.map.handler.MapDataHandler +import de.westnordost.streetcomplete.util.MultiIterable data class MapData( val nodes: MutableMap = mutableMapOf(), val ways: MutableMap = mutableMapOf(), - val relations: MutableMap = mutableMapOf()) : MapDataHandler { + val relations: MutableMap = mutableMapOf()) : MapDataHandler, Iterable { override fun handle(bounds: BoundingBox) {} override fun handle(node: Node) { nodes[node.id] = node } override fun handle(way: Way) { ways[way.id] = way } override fun handle(relation: Relation) { relations[relation.id] = relation } + + override fun iterator(): Iterator { + val elements = MultiIterable() + elements.add(nodes.values) + elements.add(ways.values) + elements.add(relations.values) + return elements.iterator() + } } \ No newline at end of file From 67f81036f8fae0dba56de0f840ddab79cec4c974 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 15:18:50 +0200 Subject: [PATCH 06/63] change signature of OsmQuestController.replaceInBBox --- .../streetcomplete/data/osm/osmquest/OsmQuestController.kt | 4 ++-- .../streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt index 7a011d6bc1..e186012f01 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt @@ -92,14 +92,14 @@ import javax.inject.Singleton /** Replace all quests of the given type in the given bounding box with the given quests, * including their geometry. Called on download of a quest type for a bounding box. */ - fun replaceInBBox(quests: List, bbox: BoundingBox, questType: String): UpdateResult { + fun replaceInBBox(quests: List, bbox: BoundingBox, questTypes: List): UpdateResult { /* All quests in the given bounding box and of the given type should be replaced by the * input list. So, there may be 1. new quests that are added and 2. there may be previous * quests that have been there before but now not anymore, these need to be removed. */ val previousQuestIdsByElement = dao.getAll( bounds = bbox, - questTypes = listOf(questType) + questTypes = questTypes ).associate { ElementKey(it.elementType, it.elementId) to it.id!! }.toMutableMap() val addedQuests = mutableListOf() diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index 379f75da0e..e09edc89a0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -55,7 +55,7 @@ class OsmQuestDownloader @Inject constructor( // elements must be put into DB first because quests have foreign keys on it elementDB.putAll(elements) - val replaceResult = osmQuestController.replaceInBBox(quests, bbox, questTypeName) + val replaceResult = osmQuestController.replaceInBBox(quests, bbox, listOf(questTypeName)) // note: this could be done after ALL osm quest types have been downloaded if this // turns out to be slow if done for every quest type From d9058c273f0859a091296a9a9d53de6b63101e78 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 17:01:58 +0200 Subject: [PATCH 07/63] generalize DownloadProgressListener --- .../streetcomplete/ApplicationComponent.kt | 2 +- .../streetcomplete/MainActivity.java | 15 +++++----- ...ragment.kt => DownloadProgressFragment.kt} | 28 +++++++++---------- .../data/download/DownloadModule.kt | 2 +- .../data/download/DownloadProgressListener.kt | 14 ++++++++++ .../data/download/DownloadProgressRelay.kt | 22 +++++++++++++++ .../data/download/DownloadProgressSource.kt | 10 +++++++ .../data/download/QuestDownloadController.kt | 15 +++++----- .../download/QuestDownloadProgressListener.kt | 12 -------- .../download/QuestDownloadProgressRelay.kt | 23 --------------- .../download/QuestDownloadProgressSource.kt | 12 -------- .../data/download/QuestDownloadService.kt | 23 ++++++++------- .../data/download/QuestDownloader.kt | 13 +++++---- .../data/quest/QuestAutoSyncer.kt | 8 +++--- ...ess.xml => fragment_download_progress.xml} | 0 app/src/main/res/layout/fragment_main.xml | 4 +-- 16 files changed, 101 insertions(+), 102 deletions(-) rename app/src/main/java/de/westnordost/streetcomplete/controls/{QuestDownloadProgressFragment.kt => DownloadProgressFragment.kt} (77%) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressListener.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressRelay.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressSource.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressListener.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressRelay.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressSource.kt rename app/src/main/res/layout/{fragment_quest_download_progress.xml => fragment_download_progress.xml} (100%) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ApplicationComponent.kt b/app/src/main/java/de/westnordost/streetcomplete/ApplicationComponent.kt index f5ef6dbe5a..b1867f1773 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ApplicationComponent.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/ApplicationComponent.kt @@ -89,7 +89,7 @@ interface ApplicationComponent { fun inject(undoButtonFragment: UndoButtonFragment) fun inject(uploadButtonFragment: UploadButtonFragment) fun inject(answersCounterFragment: AnswersCounterFragment) - fun inject(questDownloadProgressFragment: QuestDownloadProgressFragment) + fun inject(downloadProgressFragment: DownloadProgressFragment) fun inject(questStatisticsByCountryFragment: QuestStatisticsByCountryFragment) fun inject(questStatisticsByQuestTypeFragment: QuestStatisticsByQuestTypeFragment) fun inject(aboutActivity: AboutActivity) diff --git a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java index 3c534a664c..4e36ae90a1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java +++ b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java @@ -39,12 +39,13 @@ import de.westnordost.osmapi.map.data.LatLon; import de.westnordost.osmapi.map.data.OsmLatLon; import de.westnordost.streetcomplete.controls.NotificationButtonFragment; +import de.westnordost.streetcomplete.data.download.DownloadItem; import de.westnordost.streetcomplete.data.notifications.Notification; import de.westnordost.streetcomplete.data.notifications.NotificationsSource; import de.westnordost.streetcomplete.data.quest.Quest; import de.westnordost.streetcomplete.data.quest.QuestAutoSyncer; import de.westnordost.streetcomplete.data.quest.QuestController; -import de.westnordost.streetcomplete.data.download.QuestDownloadProgressListener; +import de.westnordost.streetcomplete.data.download.DownloadProgressListener; import de.westnordost.streetcomplete.data.download.QuestDownloadController; import de.westnordost.streetcomplete.data.quest.QuestType; import de.westnordost.streetcomplete.data.quest.UnsyncedChangesCountSource; @@ -192,7 +193,7 @@ private void handleGeoUri() questDownloadController.setShowNotification(false); uploadController.addUploadProgressListener(uploadProgressListener); - questDownloadController.addQuestDownloadProgressListener(downloadProgressListener); + questDownloadController.addDownloadProgressListener(downloadProgressListener); if(!hasAskedForLocation && !prefs.getBoolean(Prefs.LAST_LOCATION_REQUEST_DENIED, false)) { @@ -252,7 +253,7 @@ public boolean dispatchKeyEvent(KeyEvent event) { questDownloadController.setShowNotification(true); uploadController.removeUploadProgressListener(uploadProgressListener); - questDownloadController.removeQuestDownloadProgressListener(downloadProgressListener); + questDownloadController.removeDownloadProgressListener(downloadProgressListener); } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { @@ -345,14 +346,14 @@ else if(e instanceof OsmAuthorizationException) /* ----------------------------- Download Progress listener -------------------------------- */ - private final QuestDownloadProgressListener downloadProgressListener - = new QuestDownloadProgressListener() + private final DownloadProgressListener downloadProgressListener + = new DownloadProgressListener() { @AnyThread @Override public void onStarted() {} - @Override public void onFinished(@NotNull QuestType questType) {} + @Override public void onFinished(@NonNull DownloadItem item) {} - @Override public void onStarted(@NotNull QuestType questType) {} + @Override public void onStarted(@NonNull DownloadItem item) {} @AnyThread @Override public void onError(@NonNull final Exception e) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/controls/QuestDownloadProgressFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/controls/DownloadProgressFragment.kt similarity index 77% rename from app/src/main/java/de/westnordost/streetcomplete/controls/QuestDownloadProgressFragment.kt rename to app/src/main/java/de/westnordost/streetcomplete/controls/DownloadProgressFragment.kt index d6feadc413..8e8afe60cf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/controls/QuestDownloadProgressFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/controls/DownloadProgressFragment.kt @@ -6,9 +6,9 @@ import android.view.View import androidx.fragment.app.Fragment import de.westnordost.streetcomplete.Injector import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.download.QuestDownloadProgressListener -import de.westnordost.streetcomplete.data.download.QuestDownloadProgressSource -import de.westnordost.streetcomplete.data.quest.QuestType +import de.westnordost.streetcomplete.data.download.DownloadItem +import de.westnordost.streetcomplete.data.download.DownloadProgressListener +import de.westnordost.streetcomplete.data.download.DownloadProgressSource import de.westnordost.streetcomplete.ktx.toPx import de.westnordost.streetcomplete.ktx.toast import kotlinx.coroutines.CoroutineScope @@ -17,10 +17,10 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import javax.inject.Inject -class QuestDownloadProgressFragment : Fragment(R.layout.fragment_quest_download_progress), +class DownloadProgressFragment : Fragment(R.layout.fragment_download_progress), CoroutineScope by CoroutineScope(Dispatchers.Main) { - @Inject internal lateinit var downloadProgressSource: QuestDownloadProgressSource + @Inject internal lateinit var downloadProgressSource: DownloadProgressSource private val mainHandler = Handler(Looper.getMainLooper()) @@ -28,7 +28,7 @@ class QuestDownloadProgressFragment : Fragment(R.layout.fragment_quest_download_ private val animateOutRunnable = Runnable { animateOutProgressView() } - private val downloadProgressListener = object : QuestDownloadProgressListener { + private val downloadProgressListener = object : DownloadProgressListener { private var startedButNoQuestsYet = false override fun onStarted() { @@ -36,12 +36,12 @@ class QuestDownloadProgressFragment : Fragment(R.layout.fragment_quest_download_ launch(Dispatchers.Main) { animateInProgressView() } } - override fun onStarted(questType: QuestType<*>) { + override fun onStarted(item: DownloadItem) { startedButNoQuestsYet = false - launch(Dispatchers.Main) { progressView.enqueueIcon(resources.getDrawable(questType.icon)) } + launch(Dispatchers.Main) { progressView.enqueueIcon(resources.getDrawable(item.iconResId)) } } - override fun onFinished(questType: QuestType<*>) { + override fun onFinished(item: DownloadItem) { launch(Dispatchers.Main) { progressView.pollIcon() } } @@ -63,12 +63,12 @@ class QuestDownloadProgressFragment : Fragment(R.layout.fragment_quest_download_ override fun onStart() { super.onStart() updateDownloadProgress() - downloadProgressSource.addQuestDownloadProgressListener(downloadProgressListener) + downloadProgressSource.addDownloadProgressListener(downloadProgressListener) } override fun onStop() { super.onStop() - downloadProgressSource.removeQuestDownloadProgressListener(downloadProgressListener) + downloadProgressSource.removeDownloadProgressListener(downloadProgressListener) } override fun onDestroy() { @@ -101,9 +101,9 @@ class QuestDownloadProgressFragment : Fragment(R.layout.fragment_quest_download_ private fun updateDownloadProgress() { if (downloadProgressSource.isDownloadInProgress) { showProgressView() - val questType = downloadProgressSource.currentDownloadingQuestType - if (questType != null) { - progressView.setIcon(resources.getDrawable(questType.icon)) + val item = downloadProgressSource.currentDownloadItem + if (item != null) { + progressView.setIcon(resources.getDrawable(item.iconResId)) } } else { hideProgressView() diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadModule.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadModule.kt index 3ff3f54c89..ed2c952088 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadModule.kt @@ -6,6 +6,6 @@ import dagger.Provides @Module object DownloadModule { @Provides - fun downloadProgressSource(downloadController: QuestDownloadController): QuestDownloadProgressSource = + fun downloadProgressSource(downloadController: QuestDownloadController): DownloadProgressSource = downloadController } \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressListener.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressListener.kt new file mode 100644 index 0000000000..46a692b003 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressListener.kt @@ -0,0 +1,14 @@ +package de.westnordost.streetcomplete.data.download + +import androidx.annotation.DrawableRes + +interface DownloadProgressListener { + fun onStarted() {} + fun onStarted(item: DownloadItem) {} + fun onFinished(item: DownloadItem) {} + fun onError(e: Exception) {} + fun onFinished() {} + fun onSuccess() {} +} + +data class DownloadItem(@DrawableRes val iconResId: Int, val title: String) \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressRelay.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressRelay.kt new file mode 100644 index 0000000000..5a04766281 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressRelay.kt @@ -0,0 +1,22 @@ +package de.westnordost.streetcomplete.data.download + +import java.util.concurrent.CopyOnWriteArrayList + +class DownloadProgressRelay : DownloadProgressListener { + + private val listeners = CopyOnWriteArrayList() + + override fun onStarted() { listeners.forEach { it.onStarted() } } + override fun onError(e: Exception) { listeners.forEach { it.onError(e) } } + override fun onSuccess() { listeners.forEach { it.onSuccess() } } + override fun onFinished() { listeners.forEach { it.onFinished() } } + override fun onStarted(item: DownloadItem) { listeners.forEach { it.onStarted(item) } } + override fun onFinished(item: DownloadItem) {listeners.forEach { it.onFinished(item) } } + + fun addListener(listener: DownloadProgressListener) { + listeners.add(listener) + } + fun removeListener(listener: DownloadProgressListener) { + listeners.remove(listener) + } +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressSource.kt new file mode 100644 index 0000000000..af1e068754 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/DownloadProgressSource.kt @@ -0,0 +1,10 @@ +package de.westnordost.streetcomplete.data.download + +interface DownloadProgressSource { + val isPriorityDownloadInProgress: Boolean + val isDownloadInProgress: Boolean + val currentDownloadItem: DownloadItem? + + fun addDownloadProgressListener(listener: DownloadProgressListener) + fun removeDownloadProgressListener(listener: DownloadProgressListener) +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt index 8e17982a15..4fda5b48d1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt @@ -7,7 +7,6 @@ import android.content.ServiceConnection import android.os.IBinder import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.streetcomplete.ApplicationConstants -import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.util.enclosingTilesRect import javax.inject.Inject import javax.inject.Singleton @@ -15,7 +14,7 @@ import javax.inject.Singleton /** Controls quest downloading */ @Singleton class QuestDownloadController @Inject constructor( private val context: Context -): QuestDownloadProgressSource { +): DownloadProgressSource { private var downloadServiceIsBound: Boolean = false private var downloadService: QuestDownloadService.Interface? = null @@ -29,7 +28,7 @@ import javax.inject.Singleton downloadService = null } } - private val downloadProgressRelay = QuestDownloadProgressRelay() + private val downloadProgressRelay = DownloadProgressRelay() /** @return true if a quest download triggered by the user is running */ override val isPriorityDownloadInProgress: Boolean get() = @@ -39,9 +38,9 @@ import javax.inject.Singleton override val isDownloadInProgress: Boolean get() = downloadService?.isDownloadInProgress == true - /** @return the quest type that is currently being downloaded or null if nothing is downloaded */ - override val currentDownloadingQuestType: QuestType<*>? get() = - downloadService?.currentDownloadingQuestType + /** @return the item that is currently being downloaded or null if nothing is downloaded */ + override val currentDownloadItem: DownloadItem? get() = + downloadService?.currentDownloadItem var showNotification: Boolean get() = downloadService?.showDownloadNotification == true @@ -77,10 +76,10 @@ import javax.inject.Singleton downloadServiceIsBound = false } - override fun addQuestDownloadProgressListener(listener: QuestDownloadProgressListener) { + override fun addDownloadProgressListener(listener: DownloadProgressListener) { downloadProgressRelay.addListener(listener) } - override fun removeQuestDownloadProgressListener(listener: QuestDownloadProgressListener) { + override fun removeDownloadProgressListener(listener: DownloadProgressListener) { downloadProgressRelay.removeListener(listener) } } \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressListener.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressListener.kt deleted file mode 100644 index b77e46e193..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressListener.kt +++ /dev/null @@ -1,12 +0,0 @@ -package de.westnordost.streetcomplete.data.download - -import de.westnordost.streetcomplete.data.quest.QuestType - -interface QuestDownloadProgressListener { - fun onStarted() {} - fun onStarted(questType: QuestType<*>) {} - fun onFinished(questType: QuestType<*>) {} - fun onError(e: Exception) {} - fun onFinished() {} - fun onSuccess() {} -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressRelay.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressRelay.kt deleted file mode 100644 index 2bfa617f30..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressRelay.kt +++ /dev/null @@ -1,23 +0,0 @@ -package de.westnordost.streetcomplete.data.download - -import de.westnordost.streetcomplete.data.quest.QuestType -import java.util.concurrent.CopyOnWriteArrayList - -class QuestDownloadProgressRelay : QuestDownloadProgressListener { - - private val listeners = CopyOnWriteArrayList() - - override fun onStarted() { listeners.forEach { it.onStarted() } } - override fun onError(e: Exception) { listeners.forEach { it.onError(e) } } - override fun onSuccess() { listeners.forEach { it.onSuccess() } } - override fun onFinished() { listeners.forEach { it.onFinished() } } - override fun onStarted(questType: QuestType<*>) { listeners.forEach { it.onStarted(questType) } } - override fun onFinished(questType: QuestType<*>) {listeners.forEach { it.onFinished(questType) } } - - fun addListener(listener: QuestDownloadProgressListener) { - listeners.add(listener) - } - fun removeListener(listener: QuestDownloadProgressListener) { - listeners.remove(listener) - } -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressSource.kt deleted file mode 100644 index 1e4dea32d0..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadProgressSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package de.westnordost.streetcomplete.data.download - -import de.westnordost.streetcomplete.data.quest.QuestType - -interface QuestDownloadProgressSource { - val isPriorityDownloadInProgress: Boolean - val isDownloadInProgress: Boolean - val currentDownloadingQuestType: QuestType<*>? - - fun addQuestDownloadProgressListener(listener: QuestDownloadProgressListener) - fun removeQuestDownloadProgressListener(listener: QuestDownloadProgressListener) -} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt index 2a5ba72792..416eb3c906 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt @@ -7,7 +7,6 @@ import android.os.IBinder import android.util.Log import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.Injector -import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.util.TilesRect import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -37,21 +36,21 @@ class QuestDownloadService : SingleIntentService(TAG) { private val binder: IBinder = Interface() // listener - private var progressListenerRelay = object : QuestDownloadProgressListener { + private var progressListenerRelay = object : DownloadProgressListener { override fun onStarted() { progressListener?.onStarted() } override fun onError(e: Exception) { progressListener?.onError(e) } override fun onSuccess() { progressListener?.onSuccess() } override fun onFinished() { progressListener?.onFinished() } - override fun onStarted(questType: QuestType<*>) { - currentQuestType = questType - progressListener?.onStarted(questType) + override fun onStarted(item: DownloadItem) { + currentDownloadItem = item + progressListener?.onStarted(item) } - override fun onFinished(questType: QuestType<*>) { - currentQuestType = null - progressListener?.onFinished(questType) + override fun onFinished(item: DownloadItem) { + currentDownloadItem = null + progressListener?.onFinished(item) } } - private var progressListener: QuestDownloadProgressListener? = null + private var progressListener: DownloadProgressListener? = null // state private var isPriorityDownload: Boolean = false @@ -69,7 +68,7 @@ class QuestDownloadService : SingleIntentService(TAG) { else notificationController.show() } - private var currentQuestType: QuestType<*>? = null + private var currentDownloadItem: DownloadItem? = null init { Injector.applicationComponent.inject(this) @@ -115,7 +114,7 @@ class QuestDownloadService : SingleIntentService(TAG) { /** Public interface to classes that are bound to this service */ inner class Interface : Binder() { - fun setProgressListener(listener: QuestDownloadProgressListener?) { + fun setProgressListener(listener: DownloadProgressListener?) { progressListener = listener } @@ -123,7 +122,7 @@ class QuestDownloadService : SingleIntentService(TAG) { val isDownloadInProgress: Boolean get() = isDownloading - val currentDownloadingQuestType: QuestType<*>? get() = currentQuestType + val currentDownloadItem: DownloadItem? get() = this@QuestDownloadService.currentDownloadItem var showDownloadNotification: Boolean get() = showNotification diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index f43e15a298..56052333f5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -30,7 +30,7 @@ class QuestDownloader @Inject constructor( private val questTypesProvider: OrderedVisibleQuestTypesProvider, private val userStore: UserStore ) { - var progressListener: QuestDownloadProgressListener? = null + var progressListener: DownloadProgressListener? = null @Synchronized fun download(tiles: TilesRect, maxQuestTypes: Int?, cancelState: AtomicBoolean) { if (cancelState.get()) return @@ -94,26 +94,27 @@ class QuestDownloader @Inject constructor( val userId: Long = userStore.userId.takeIf { it != -1L } ?: return // do not download notes if not logged in because notes shall only be downloaded if logged in val noteQuestType = getOsmNoteQuestType() - progressListener?.onStarted(noteQuestType) + progressListener?.onStarted(noteQuestType.toDownloadItem()) val maxNotes = 10000 notesDownload.download(bbox, userId, maxNotes) downloadedTilesDao.put(tiles, OsmNoteQuestType::class.java.simpleName) - progressListener?.onFinished(noteQuestType) + progressListener?.onFinished(noteQuestType.toDownloadItem()) } private fun downloadQuestType(bbox: BoundingBox, tiles: TilesRect, questType: QuestType<*>, notesPositions: Set) { if (questType is OsmElementQuestType<*>) { - progressListener?.onStarted(questType) val questDownload = osmQuestDownloaderProvider.get() if (questDownload.download(questType, bbox, notesPositions)) { downloadedTilesDao.put(tiles, questType.javaClass.simpleName) } - progressListener?.onFinished(questType) + progressListener?.onStarted(questType.toDownloadItem()) } + progressListener?.onFinished(questType.toDownloadItem()) } companion object { private const val TAG = "QuestDownload" } - } + +private fun QuestType<*>.toDownloadItem(): DownloadItem = DownloadItem(icon, javaClass.simpleName) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt index f5e0a2c3d6..4feec62a75 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt @@ -37,7 +37,7 @@ import javax.inject.Singleton private val context: Context, private val visibleQuestsSource: VisibleQuestsSource, private val unsyncedChangesCountSource: UnsyncedChangesCountSource, - private val downloadProgressSource: QuestDownloadProgressSource, + private val downloadProgressSource: DownloadProgressSource, private val prefs: SharedPreferences, private val userController: UserController ) : LifecycleObserver, CoroutineScope by CoroutineScope(Dispatchers.Default) { @@ -81,7 +81,7 @@ import javax.inject.Singleton } // on download finished, should recheck conditions for download - private val downloadProgressListener = object : QuestDownloadProgressListener { + private val downloadProgressListener = object : DownloadProgressListener { override fun onSuccess() { triggerAutoDownload() } @@ -98,7 +98,7 @@ import javax.inject.Singleton init { visibleQuestsSource.addListener(visibleQuestListener) unsyncedChangesCountSource.addListener(unsyncedChangesListener) - downloadProgressSource.addQuestDownloadProgressListener(downloadProgressListener) + downloadProgressSource.addDownloadProgressListener(downloadProgressListener) } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun onResume() { @@ -118,7 +118,7 @@ import javax.inject.Singleton @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { visibleQuestsSource.removeListener(visibleQuestListener) unsyncedChangesCountSource.removeListener(unsyncedChangesListener) - downloadProgressSource.removeQuestDownloadProgressListener(downloadProgressListener) + downloadProgressSource.removeDownloadProgressListener(downloadProgressListener) coroutineContext.cancel() } diff --git a/app/src/main/res/layout/fragment_quest_download_progress.xml b/app/src/main/res/layout/fragment_download_progress.xml similarity index 100% rename from app/src/main/res/layout/fragment_quest_download_progress.xml rename to app/src/main/res/layout/fragment_download_progress.xml diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 3d3189c0aa..9c37a51845 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -157,10 +157,10 @@ Date: Wed, 17 Jun 2020 17:02:46 +0200 Subject: [PATCH 08/63] generalize DownloadProgressListener (fix) --- .../streetcomplete/data/download/QuestDownloader.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index 56052333f5..51415fa650 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -103,13 +103,13 @@ class QuestDownloader @Inject constructor( private fun downloadQuestType(bbox: BoundingBox, tiles: TilesRect, questType: QuestType<*>, notesPositions: Set) { if (questType is OsmElementQuestType<*>) { + progressListener?.onStarted(questType.toDownloadItem()) val questDownload = osmQuestDownloaderProvider.get() if (questDownload.download(questType, bbox, notesPositions)) { downloadedTilesDao.put(tiles, questType.javaClass.simpleName) } - progressListener?.onStarted(questType.toDownloadItem()) + progressListener?.onFinished(questType.toDownloadItem()) } - progressListener?.onFinished(questType.toDownloadItem()) } companion object { From d18b77bf648dcef93cce21d45ed323624659ec3e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 17 Jun 2020 17:09:13 +0200 Subject: [PATCH 09/63] multi quest downloader WIP (see todos) --- .../data/download/QuestDownloader.kt | 80 ++++++++++---- .../data/osm/osmquest/OsmQuestDownloader.kt | 104 +++++++++++++++--- .../osm/osmquest/OsmQuestDownloaderTest.kt | 8 +- 3 files changed, 154 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index 51415fa650..5c591f5784 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -4,6 +4,7 @@ import android.util.Log import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.LatLon import de.westnordost.streetcomplete.ApplicationConstants +import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType @@ -36,7 +37,7 @@ class QuestDownloader @Inject constructor( if (cancelState.get()) return progressListener?.onStarted() - val questTypes = getQuestTypesToDownload(tiles, maxQuestTypes) + val questTypes = getQuestTypesToDownload(tiles, maxQuestTypes).toMutableList() if (questTypes.isEmpty()) { progressListener?.onSuccess() progressListener?.onFinished() @@ -49,20 +50,7 @@ class QuestDownloader @Inject constructor( Log.i(TAG, "Quest types to download: ${questTypes.joinToString { it.javaClass.simpleName }}") try { - // always first download notes, because note positions are blockers for creating other - // quests - val noteQuestType = getOsmNoteQuestType() - if (questTypes.contains(noteQuestType)) { - downloadNotes(bbox, tiles) - - } - - val notesPositions = notePositionsSource.getAllPositions(bbox).toSet() - - for (questType in questTypes) { - if (cancelState.get()) break - downloadQuestType(bbox, tiles, questType, notesPositions) - } + downloadQuestTypes(tiles, bbox, questTypes, cancelState) progressListener?.onSuccess() } finally { progressListener?.onFinished() @@ -70,9 +58,43 @@ class QuestDownloader @Inject constructor( } } + private fun downloadQuestTypes( + tiles: TilesRect, + bbox: BoundingBox, + questTypes: MutableList>, + cancelState: AtomicBoolean) + { + // always first download notes, because note positions are blockers for creating other + // osm quests + val noteQuestType = getOsmNoteQuestType() + if (questTypes.remove(noteQuestType)) { + downloadNotes(bbox, tiles) + } + val notesPositions = notePositionsSource.getAllPositions(bbox).toSet() + + if (questTypes.isEmpty()) return + if (cancelState.get()) return + + // download multiple quests at once + val downloadedQuestTypes = downloadMultipleOsmQuestTypes(bbox, tiles, notesPositions) + questTypes.removeAll(downloadedQuestTypes) + + if (questTypes.isEmpty()) return + if (cancelState.get()) return + + // download remaining quests that haven't been downloaded in the previous step + val remainingOsmElementQuestTypes = questTypes.filterIsInstance>() + for (questType in remainingOsmElementQuestTypes) { + if (cancelState.get()) break + downloadOsmQuestType(bbox, tiles, questType, notesPositions) + questTypes.remove(questType) + } + } + private fun getOsmNoteQuestType() = questTypeRegistry.getByName(OsmNoteQuestType::class.java.simpleName)!! + private fun getQuestTypesToDownload(tiles: TilesRect, maxQuestTypes: Int?): List> { val result = questTypesProvider.get().toMutableList() val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER @@ -101,15 +123,25 @@ class QuestDownloader @Inject constructor( progressListener?.onFinished(noteQuestType.toDownloadItem()) } - private fun downloadQuestType(bbox: BoundingBox, tiles: TilesRect, questType: QuestType<*>, notesPositions: Set) { - if (questType is OsmElementQuestType<*>) { - progressListener?.onStarted(questType.toDownloadItem()) - val questDownload = osmQuestDownloaderProvider.get() - if (questDownload.download(questType, bbox, notesPositions)) { - downloadedTilesDao.put(tiles, questType.javaClass.simpleName) - } - progressListener?.onFinished(questType.toDownloadItem()) + private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmElementQuestType<*>, notesPositions: Set) { + progressListener?.onStarted(questType.toDownloadItem()) + val questDownload = osmQuestDownloaderProvider.get() + if (questDownload.download(questType, bbox, notesPositions)) { + downloadedTilesDao.put(tiles, questType.javaClass.simpleName) } + progressListener?.onFinished(questType.toDownloadItem()) + } + + private fun downloadMultipleOsmQuestTypes(bbox: BoundingBox, tiles: TilesRect, notesPositions: Set): List> { + val downloadItem = DownloadItem(R.drawable.ic_motorcycle, "Multi download") + progressListener?.onStarted(downloadItem) + // Since we query all the data at once, we can also do the downloading for quests not on our list. + val questTypes = questTypesProvider.get().filterIsInstance>() + val questDownload = osmQuestDownloaderProvider.get() + val downloadedQuestTypes = questDownload.downloadMultiple(questTypes, bbox, notesPositions) + downloadedTilesDao.putAll(tiles, downloadedQuestTypes.map { it.javaClass.simpleName }) + progressListener?.onFinished(downloadItem) + return downloadedQuestTypes } companion object { @@ -117,4 +149,4 @@ class QuestDownloader @Inject constructor( } } -private fun QuestType<*>.toDownloadItem(): DownloadItem = DownloadItem(icon, javaClass.simpleName) +private fun QuestType<*>.toDownloadItem(): DownloadItem = DownloadItem(icon, javaClass.simpleName) \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index e09edc89a0..27e45391f3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -2,7 +2,6 @@ package de.westnordost.streetcomplete.data.osm.osmquest import android.util.Log -import java.util.Locale import java.util.concurrent.FutureTask import javax.inject.Inject @@ -15,19 +14,98 @@ import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Element.Type.* import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.osmapi.map.getMap +import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.util.measuredLength +import java.util.* +import kotlin.collections.ArrayList /** Takes care of downloading one quest type in a bounding box and persisting the downloaded quests */ class OsmQuestDownloader @Inject constructor( private val elementDB: MergedElementDao, private val osmQuestController: OsmQuestController, - private val countryBoundariesFuture: FutureTask + private val countryBoundariesFuture: FutureTask, + private val mapDataApi: MapDataApi, + private val elementGeometryCreator: ElementGeometryCreator ) { private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() + // TODO TEST + // TODO maybe move/merge to QuestGiver? + fun downloadMultiple(questTypes: List>, bbox: BoundingBox, blacklistedPositions: Set): List> { + val skippedQuestTypes = mutableSetOf>() + + var time = System.currentTimeMillis() + + // TODO what if API returns that the area is too big? + val mapData = mapDataApi.getMap(bbox) + + val elementGeometries = EnumMap>(Element.Type::class.java) + elementGeometries[NODE] = mutableMapOf() + elementGeometries[WAY] = mutableMapOf() + elementGeometries[RELATION] = mutableMapOf() + + val quests = ArrayList() + val questElements = HashSet() + + val secondsSpentDownloading = (System.currentTimeMillis() - time) / 1000 + Log.i(TAG,"Downloaded ${mapData.nodes.size} nodes, ${mapData.ways.size} ways and ${mapData.relations.size} relations in ${secondsSpentDownloading}s") + time = System.currentTimeMillis() + + for (questType in questTypes) { + val questTypeName = questType.getName() + + val countries = questType.enabledInCountries + if (!countryBoundaries.intersects(bbox, countries)) { + Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") + continue + } + + for (element in mapData) { + val appliesToElement = questType.isApplicableTo(element) + if (appliesToElement == null) { + skippedQuestTypes.add(questType) + break + } + if (!appliesToElement) continue + if (!elementGeometries[element.type]!!.containsKey(element.id)) { + val geometry = elementGeometryCreator.create(element, mapData) ?: continue + elementGeometries[element.type]!![element.id] = geometry + } + val geometry = elementGeometries[element.type]!![element.id] + if (!mayCreateQuestFrom(questType, element, geometry, blacklistedPositions)) continue + + val quest = OsmQuest(questType, element.type, element.id, geometry!!) + + quests.add(quest) + questElements.add(element) + } + } + val downloadedQuestTypes = questTypes.filterNot { skippedQuestTypes.contains(it) } + val downloadedQuestTypeNames = downloadedQuestTypes.map { it.getName() } + + // elements must be put into DB first because quests have foreign keys on it + elementDB.putAll(questElements) + + val replaceResult = osmQuestController.replaceInBBox(quests, bbox, downloadedQuestTypeNames) + + elementDB.deleteUnreferenced() + + for(questType in downloadedQuestTypes) { + questType.cleanMetadata() + } + + val secondsSpentAnalyzing = (System.currentTimeMillis() - time) / 1000 + Log.i(TAG,"${downloadedQuestTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") + + return downloadedQuestTypes + } + fun download(questType: OsmElementQuestType<*>, bbox: BoundingBox, blacklistedPositions: Set): Boolean { val questTypeName = questType.getName() @@ -82,28 +160,28 @@ class OsmQuestDownloader @Inject constructor( } val pos = geometry.center - // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey - if (geometry is ElementPolylinesGeometry) { - val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } - if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} at ${pos.toLogString()} because the geometry is too long") - return false - } - } - // do not create quests whose marker is at/near a blacklisted position if (blacklistedPositions.contains(pos.truncateTo5Decimals())) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} at ${pos.toLogString()} because there is a note at that position") + Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because there is a note at that position") return false } // do not create quests in countries where the quest is not activated val countries = questType.enabledInCountries if (!countryBoundaries.isInAny(pos, countries)) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} at ${pos.toLogString()} because the quest is disabled in this country") + Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because the quest is disabled in this country") return false } + // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey + if (geometry is ElementPolylinesGeometry) { + val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } + if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { + Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because the geometry is too long") + return false + } + } + return true } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt index f5fafe05c0..5c2d462d02 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt @@ -6,8 +6,10 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.quest.AllCountries @@ -26,6 +28,8 @@ class OsmQuestDownloaderTest { private lateinit var elementDb: MergedElementDao private lateinit var osmQuestController: OsmQuestController private lateinit var countryBoundaries: CountryBoundaries + private lateinit var mapDataApi: MapDataApi + private lateinit var elementGeometryCreator: ElementGeometryCreator private lateinit var downloader: OsmQuestDownloader @Before fun setUp() { @@ -33,9 +37,11 @@ class OsmQuestDownloaderTest { osmQuestController = mock() on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) countryBoundaries = mock() + elementGeometryCreator = mock() + mapDataApi = mock() val countryBoundariesFuture = FutureTask { countryBoundaries } countryBoundariesFuture.run() - downloader = OsmQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture) + downloader = OsmQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, mapDataApi, elementGeometryCreator) } @Test fun `ignore element with invalid geometry`() { From e2be08472c9f129365d0e252b09752c697951735 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 16 Oct 2020 14:16:51 +0200 Subject: [PATCH 10/63] merge wip --- .../data/elementfilter/ElementFilterExpression.kt | 3 ++- .../data/elementfilter/ElementFiltersParser.kt | 13 +++++++++++-- .../data/elementfilter/OverpassQueryCreator.kt | 14 +++++++------- .../data/osm/splitway/SplitWaysUploader.kt | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt index 4263bd622b..50cd8e8204 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt @@ -5,11 +5,12 @@ import de.westnordost.streetcomplete.data.elementfilter.ElementsTypeFilter.NODES import de.westnordost.streetcomplete.data.elementfilter.ElementsTypeFilter.WAYS import de.westnordost.streetcomplete.data.elementfilter.ElementsTypeFilter.RELATIONS import de.westnordost.streetcomplete.data.elementfilter.filters.ElementFilter +import java.util.* /** Represents a parse result of a string in filter syntax, i.e. * "ways with (highway = residential or highway = tertiary) and !name" */ class ElementFilterExpression( - private val elementsTypes: List, + private val elementsTypes: EnumSet, private val elementExprRoot: BooleanExpression? ) { /** returns whether the given element is found through (=matches) this expression */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt index d896e2ea1b..d13ee8852b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt @@ -5,6 +5,8 @@ import de.westnordost.streetcomplete.data.elementfilter.filters.* import de.westnordost.streetcomplete.data.meta.toCheckDate import java.lang.NumberFormatException import java.text.ParseException +import java.util.* +import kotlin.collections.ArrayList import kotlin.math.min /** @@ -71,7 +73,7 @@ private val NUMBER_WORD_REGEX = Regex("(?:([0-9]+(?:\\.[0-9]*)?)|(\\.[0-9]+))(?: private fun String.stripQuotes() = replace("^[\"']|[\"']$".toRegex(), "") -private fun StringWithCursor.parseElementsDeclaration(): List { +private fun StringWithCursor.parseElementsDeclaration(): EnumSet { val result = ArrayList() result.add(parseElementDeclaration()) while (nextIsAndAdvance(',')) { @@ -81,7 +83,14 @@ private fun StringWithCursor.parseElementsDeclaration(): List EnumSet.of(result[0]) + 2 -> EnumSet.of(result[0], result[1]) + 3 -> EnumSet.of(result[0], result[1], result[2]) + else -> throw IllegalStateException() + } + } private fun StringWithCursor.parseElementDeclaration(): ElementsTypeFilter { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt index 3588f4741d..e8dc03464d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt @@ -1,13 +1,13 @@ package de.westnordost.streetcomplete.data.elementfilter import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.data.elementfilter.ElementsTypeFilter.* import de.westnordost.streetcomplete.data.elementfilter.filters.ElementFilter +import java.util.* /** Create an overpass query from the given element filter expression */ class OverpassQueryCreator( - elementTypes: List, + elementTypes: EnumSet, private val expr: BooleanExpression?) { private val elementTypes = elementTypes.toOqlNames() @@ -37,11 +37,11 @@ class OverpassQueryCreator( } } - private fun List.toOqlNames(): List = when { - containsExactlyInAnyOrder(listOf(NODES, WAYS)) -> listOf("nw") - containsExactlyInAnyOrder(listOf(WAYS, RELATIONS)) -> listOf("wr") - containsExactlyInAnyOrder(listOf(NODES,WAYS, RELATIONS)) -> listOf("nwr") - else -> map { when (it) { + private fun EnumSet.toOqlNames(): List = when { + containsAll(listOf(NODES, WAYS)) -> listOf("nw") + containsAll(listOf(WAYS, RELATIONS)) -> listOf("wr") + containsAll(listOf(NODES,WAYS, RELATIONS)) -> listOf("nwr") + else -> map { when (it!!) { NODES -> "node" WAYS -> "way" RELATIONS -> "rel" diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt index e188f8c505..9942221696 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt @@ -41,7 +41,7 @@ class SplitWaysUploader @Inject constructor( } override fun updateElement(newElement: Element, quest: OsmQuestSplitWay) { - val geometry = elementGeometryCreator.create(newElement) + val geometry = elementGeometryCreator.create(newElement, ) if (geometry != null) { elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, geometry)) elementDB.put(newElement) From 6ee3c7d1ae45915fd659c6c7941f909c558364f2 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 16 Oct 2020 21:08:03 +0200 Subject: [PATCH 11/63] rename extension functions on MapDataApi --- .../osmapi/map/{MapDataDao.kt => MapDataApi.kt} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename app/src/main/java/de/westnordost/osmapi/map/{MapDataDao.kt => MapDataApi.kt} (60%) diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt similarity index 60% rename from app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt rename to app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt index 0624caf8a1..c407d3d256 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapDataDao.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt @@ -1,20 +1,21 @@ package de.westnordost.osmapi.map import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.streetcomplete.data.MapDataApi -fun MapDataDao.getMap(bounds: BoundingBox): MapData { +fun MapDataApi.getMap(bounds: BoundingBox): MapData { val result = MapData() getMap(bounds, result) return result } -fun MapDataDao.getWayComplete(id: Long): MapData { +fun MapDataApi.getWayComplete(id: Long): MapData { val result = MapData() getWayComplete(id, result) return result } -fun MapDataDao.getRelationComplete(id: Long): MapData { +fun MapDataApi.getRelationComplete(id: Long): MapData { val result = MapData() getRelationComplete(id, result) return result From 819b1514c13e8e2e55e1f1faf361331824b2abfb Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 16 Oct 2020 22:58:10 +0200 Subject: [PATCH 12/63] fix tests --- .../data/elementfilter/OverpassQueryCreator.kt | 6 +++--- .../data/osm/splitway/SplitWaysUploader.kt | 12 ++++++------ .../data/osm/upload/OsmInChangesetsUploader.kt | 11 +++++------ ...lementFiltersParserAndOverpassQueryCreatorTest.kt | 2 +- .../data/osm/splitway/SplitWaysUploaderTest.kt | 2 ++ .../quests/bus_stop_name/AddBusStopNameTest.kt | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt index e8dc03464d..42029e231a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQueryCreator.kt @@ -38,9 +38,9 @@ class OverpassQueryCreator( } private fun EnumSet.toOqlNames(): List = when { - containsAll(listOf(NODES, WAYS)) -> listOf("nw") - containsAll(listOf(WAYS, RELATIONS)) -> listOf("wr") - containsAll(listOf(NODES,WAYS, RELATIONS)) -> listOf("nwr") + containsAll(listOf(NODES, WAYS, RELATIONS)) -> listOf("nwr") + containsAll(listOf(NODES, WAYS)) -> listOf("nw") + containsAll(listOf(WAYS, RELATIONS)) -> listOf("wr") else -> map { when (it!!) { NODES -> "node" WAYS -> "way" diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt index 9942221696..57ff427f35 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt @@ -4,6 +4,7 @@ import android.util.Log import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao @@ -21,7 +22,7 @@ class SplitWaysUploader @Inject constructor( private val elementGeometryDB: ElementGeometryDao, changesetManager: OpenQuestChangesetsManager, private val questGiver: OsmQuestGiver, - private val elementGeometryCreator: ElementGeometryCreator, + elementGeometryCreator: ElementGeometryCreator, mapDataApi: MapDataApi, private val splitWayDB: OsmQuestSplitWayDao, private val splitSingleOsmWayUploader: SplitSingleWayUploader, @@ -40,12 +41,11 @@ class SplitWaysUploader @Inject constructor( return splitSingleOsmWayUploader.upload(changesetId, element as Way, quest.splits) } - override fun updateElement(newElement: Element, quest: OsmQuestSplitWay) { - val geometry = elementGeometryCreator.create(newElement, ) - if (geometry != null) { - elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, geometry)) + override fun updateElement(newElement: Element, newGeometry: ElementGeometry?, quest: OsmQuestSplitWay) { + if (newGeometry != null) { + elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, newGeometry)) elementDB.put(newElement) - questGiver.recreateQuests(newElement, geometry, quest.questTypesOnWay) + questGiver.recreateQuests(newElement, newGeometry, quest.questTypesOnWay) } else { // new element has invalid geometry elementDB.delete(newElement.type, newElement.id) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt index d52745dff6..8a3ac6b77e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt @@ -45,7 +45,7 @@ abstract class OsmInChangesetsUploader( try { val uploadedElements = uploadSingle(quest) for (element in uploadedElements) { - updateElement(element, quest) + updateElement(element, createGeometry(element), quest) } uploadedQuestTypes.add(quest.osmElementQuestType) onUploadSuccessful(quest) @@ -81,12 +81,11 @@ abstract class OsmInChangesetsUploader( * ElementDao (or a controller in front of it) that takes care of that. * * This will remove the dependencies to elementGeometryDB, questGiver etc */ - protected open fun updateElement(newElement: Element, quest: T) { - val geometry = createGeometry(newElement) - if (geometry != null) { - elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, geometry)) + protected open fun updateElement(newElement: Element, newGeometry: ElementGeometry?, quest: T) { + if (newGeometry != null) { + elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, newGeometry)) elementDB.put(newElement) - questGiver.updateQuests(newElement, geometry) + questGiver.updateQuests(newElement, newGeometry) } else { // new element has invalid geometry deleteElement(newElement.type, newElement.id) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt index 59bc6851fb..028b46f7e8 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt @@ -196,7 +196,7 @@ class ElementFiltersParserAndOverpassQueryCreatorTest { check("nodes with check_date < today -2 days", "node[check_date](if:date(t['check_date']) < date('$twoDaysAgo'));") val twoDaysInFuture = dateDaysAgo(-2f).toCheckDateString() - check("nodes with check_date < today + 0.3 weeks", "node[check_date](if:date(t['check_date']) < date('$twoDaysInFuture'));") + check("nodes with check_date < today + 0.285 weeks", "node[check_date](if:date(t['check_date']) < date('$twoDaysInFuture'));") } @Test fun `element older x days`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt index 16b37be16f..2d43a4f2cc 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.osm.splitway +import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.streetcomplete.on @@ -95,6 +96,7 @@ class SplitWaysUploaderTest { on(splitSingleOsmWayUploader.upload(anyLong(), any(), anyList())) .thenThrow(ElementConflictException()) .thenReturn(listOf(createElement())) + on(elementGeometryCreator.create(any(), any())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt index 904cf98cc6..da09e69803 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt @@ -14,7 +14,7 @@ class AddBusStopNameTest { fun `apply no name answer`() { questType.verifyAnswer( NoBusStopName, - StringMapEntryAdd("noname", "yes") + StringMapEntryAdd("name:signed", "no") ) } From 299aa39c4e9123d8c6bf650ec7bda9cfd161af4b Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 17 Oct 2020 13:25:15 +0200 Subject: [PATCH 13/63] use neutral icon --- .../westnordost/streetcomplete/data/download/QuestDownloader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index 5c591f5784..953fc961ef 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -133,7 +133,7 @@ class QuestDownloader @Inject constructor( } private fun downloadMultipleOsmQuestTypes(bbox: BoundingBox, tiles: TilesRect, notesPositions: Set): List> { - val downloadItem = DownloadItem(R.drawable.ic_motorcycle, "Multi download") + val downloadItem = DownloadItem(R.drawable.ic_search_48dp, "Multi download") progressListener?.onStarted(downloadItem) // Since we query all the data at once, we can also do the downloading for quests not on our list. val questTypes = questTypesProvider.get().filterIsInstance>() From 44487d2584d3f1fc3d8a9ca3a37da0b8a2320430 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 17 Oct 2020 14:52:45 +0200 Subject: [PATCH 14/63] correct location of AddRoadName... --- .../quests/{localized_name => road_name}/AddRoadName.kt | 0 .../quests/{localized_name => road_name}/AddRoadNameForm.kt | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/de/westnordost/streetcomplete/quests/{localized_name => road_name}/AddRoadName.kt (100%) rename app/src/main/java/de/westnordost/streetcomplete/quests/{localized_name => road_name}/AddRoadNameForm.kt (100%) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt similarity index 100% rename from app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadName.kt rename to app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadNameForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt similarity index 100% rename from app/src/main/java/de/westnordost/streetcomplete/quests/localized_name/AddRoadNameForm.kt rename to app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt From fc96c40d7ed8a86348ae5aa147bb64a9f8f1dfa6 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 17 Oct 2020 22:32:20 +0200 Subject: [PATCH 15/63] finish implementing OsmApiQuestDownloader --- .../download/tiles/DownloadedTilesDaoTest.kt | 3 +- .../java/de/westnordost/osmapi/map/MapData.kt | 6 + .../de/westnordost/osmapi/map/MapDataApi.kt | 24 +++ .../data/download/QuestDownloader.kt | 22 ++- .../ElementEligibleForOsmQuestChecker.kt | 61 ++++++++ .../osm/osmquest/OsmApiQuestDownloader.kt | 122 +++++++++++++++ .../data/osm/osmquest/OsmQuestController.kt | 15 +- .../data/osm/osmquest/OsmQuestDao.kt | 6 +- .../data/osm/osmquest/OsmQuestDownloader.kt | 140 ++---------------- .../osmnotes/notequests/OsmNoteQuestDao.kt | 3 +- .../data/quest/VisibleQuestsSource.kt | 1 + .../res/drawable/ic_search_black_128dp.xml | 9 ++ .../osm/osmquest/OsmQuestDownloaderTest.kt | 19 ++- 13 files changed, 273 insertions(+), 158 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt create mode 100644 app/src/main/res/drawable/ic_search_black_128dp.xml diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt index 6df89a44b1..07e78b4c31 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt @@ -4,6 +4,7 @@ import org.junit.Before import org.junit.Test import de.westnordost.streetcomplete.data.ApplicationDbTestCase +import de.westnordost.streetcomplete.ktx.containsExactlyInAnyOrder import de.westnordost.streetcomplete.util.Tile import de.westnordost.streetcomplete.util.TilesRect @@ -80,7 +81,7 @@ class DownloadedTilesDaoTest : ApplicationDbTestCase() { @Test fun putMultipleQuestTypes() { val rect = r(0, 0, 5, 5) dao.putAll(rect, listOf("Huhu", "Haha")) - assertEquals(listOf("Huhu", "Haha"), dao.get(rect, 0)) + assertTrue(dao.get(rect, 0).containsExactlyInAnyOrder(listOf("Huhu", "Haha"))) } private fun r(left: Int, top: Int, right: Int, bottom: Int) = TilesRect(left, top, right, bottom) diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt index d90703791b..6a201c5512 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -21,4 +21,10 @@ data class MapData( elements.add(relations.values) return elements.iterator() } + + fun add(other: MapData) { + nodes.putAll(other.nodes) + ways.putAll(other.ways) + relations.putAll(other.relations) + } } \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt index c407d3d256..d5c135c281 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt @@ -1,6 +1,8 @@ package de.westnordost.osmapi.map +import de.westnordost.osmapi.common.errors.OsmQueryTooBigException import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.streetcomplete.data.MapDataApi fun MapDataApi.getMap(bounds: BoundingBox): MapData { @@ -20,3 +22,25 @@ fun MapDataApi.getRelationComplete(id: Long): MapData { getRelationComplete(id, result) return result } + +fun MapDataApi.getMapAndHandleTooBigQuery(bounds: BoundingBox): MapData { + val result = MapData() + try { + getMap(bounds, result) + } catch (e : OsmQueryTooBigException) { + for (subBounds in bounds.splitIntoFour()) { + result.add(getMap(subBounds)) + } + } + return result +} + +fun BoundingBox.splitIntoFour(): List { + val center = OsmLatLon((maxLatitude + minLatitude) / 2, (maxLongitude + minLongitude) / 2) + return listOf( + BoundingBox(minLatitude, minLongitude, center.latitude, center.longitude), + BoundingBox(minLatitude, center.longitude, center.latitude, maxLongitude), + BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), + BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) + ) +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index 953fc961ef..90f42e54a2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -2,7 +2,6 @@ package de.westnordost.streetcomplete.data.download import android.util.Log import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.LatLon import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.quest.QuestType @@ -12,7 +11,7 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestDownloader import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType import de.westnordost.streetcomplete.data.osmnotes.OsmNotesDownloader import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao -import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource +import de.westnordost.streetcomplete.data.osm.osmquest.OsmApiQuestDownloader import de.westnordost.streetcomplete.data.user.UserStore import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider import de.westnordost.streetcomplete.util.TilesRect @@ -25,8 +24,8 @@ import kotlin.math.max class QuestDownloader @Inject constructor( private val osmNotesDownloaderProvider: Provider, private val osmQuestDownloaderProvider: Provider, + private val osmApiQuestDownloaderProvider: Provider, private val downloadedTilesDao: DownloadedTilesDao, - private val notePositionsSource: NotePositionsSource, private val questTypeRegistry: QuestTypeRegistry, private val questTypesProvider: OrderedVisibleQuestTypesProvider, private val userStore: UserStore @@ -70,13 +69,12 @@ class QuestDownloader @Inject constructor( if (questTypes.remove(noteQuestType)) { downloadNotes(bbox, tiles) } - val notesPositions = notePositionsSource.getAllPositions(bbox).toSet() if (questTypes.isEmpty()) return if (cancelState.get()) return // download multiple quests at once - val downloadedQuestTypes = downloadMultipleOsmQuestTypes(bbox, tiles, notesPositions) + val downloadedQuestTypes = downloadMultipleOsmQuestTypes(bbox, tiles) questTypes.removeAll(downloadedQuestTypes) if (questTypes.isEmpty()) return @@ -86,7 +84,7 @@ class QuestDownloader @Inject constructor( val remainingOsmElementQuestTypes = questTypes.filterIsInstance>() for (questType in remainingOsmElementQuestTypes) { if (cancelState.get()) break - downloadOsmQuestType(bbox, tiles, questType, notesPositions) + downloadOsmQuestType(bbox, tiles, questType) questTypes.remove(questType) } } @@ -123,22 +121,22 @@ class QuestDownloader @Inject constructor( progressListener?.onFinished(noteQuestType.toDownloadItem()) } - private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmElementQuestType<*>, notesPositions: Set) { + private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmElementQuestType<*>) { progressListener?.onStarted(questType.toDownloadItem()) val questDownload = osmQuestDownloaderProvider.get() - if (questDownload.download(questType, bbox, notesPositions)) { + if (questDownload.download(questType, bbox)) { downloadedTilesDao.put(tiles, questType.javaClass.simpleName) } progressListener?.onFinished(questType.toDownloadItem()) } - private fun downloadMultipleOsmQuestTypes(bbox: BoundingBox, tiles: TilesRect, notesPositions: Set): List> { - val downloadItem = DownloadItem(R.drawable.ic_search_48dp, "Multi download") + private fun downloadMultipleOsmQuestTypes(bbox: BoundingBox, tiles: TilesRect): List> { + val downloadItem = DownloadItem(R.drawable.ic_search_black_128dp, "Multi download") progressListener?.onStarted(downloadItem) // Since we query all the data at once, we can also do the downloading for quests not on our list. val questTypes = questTypesProvider.get().filterIsInstance>() - val questDownload = osmQuestDownloaderProvider.get() - val downloadedQuestTypes = questDownload.downloadMultiple(questTypes, bbox, notesPositions) + val questDownload = osmApiQuestDownloaderProvider.get() + val downloadedQuestTypes = questDownload.downloadMultiple(questTypes, bbox) downloadedTilesDao.putAll(tiles, downloadedQuestTypes.map { it.javaClass.simpleName }) progressListener?.onFinished(downloadItem) return downloadedQuestTypes diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt new file mode 100644 index 0000000000..b544a5109d --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt @@ -0,0 +1,61 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.countryboundaries.CountryBoundaries +import de.westnordost.countryboundaries.isInAny +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry +import de.westnordost.streetcomplete.data.quest.QuestType +import de.westnordost.streetcomplete.util.measuredLength +import java.util.concurrent.FutureTask +import javax.inject.Inject + +class ElementEligibleForOsmQuestChecker @Inject constructor( + private val countryBoundariesFuture: FutureTask, +) { + fun mayCreateQuestFrom( + questType: OsmElementQuestType<*>, element: Element, geometry: ElementGeometry?, + blacklistedPositions: Set + ): Boolean { + val questTypeName = questType.getName() + + // invalid geometry -> can't show this quest, so skip it + if (geometry == null) { + return false + } + val pos = geometry.center + + // do not create quests whose marker is at/near a blacklisted position + if (blacklistedPositions.contains(pos.truncateTo5Decimals())) { + return false + } + + // do not create quests in countries where the quest is not activated + val countries = questType.enabledInCountries + if (!countryBoundariesFuture.get().isInAny(pos, countries)) { + return false + } + + // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey + if (geometry is ElementPolylinesGeometry) { + val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } + if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { + return false + } + } + + return true + } +} + + +private fun QuestType<*>.getName() = javaClass.simpleName + +const val MAX_GEOMETRY_LENGTH_IN_METERS = 500 + +// the resulting precision is about ~1 meter (see #1089) +private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) + +private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt new file mode 100644 index 0000000000..149b0bfe54 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -0,0 +1,122 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import android.util.Log +import de.westnordost.countryboundaries.CountryBoundaries +import de.westnordost.countryboundaries.intersects +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.getMapAndHandleTooBigQuery +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource +import de.westnordost.streetcomplete.data.quest.QuestType +import java.util.* +import java.util.concurrent.FutureTask +import javax.inject.Inject +import kotlin.collections.ArrayList + +/** Does one API call to get all the map data and generates quests from that. Calls isApplicable + * on all the quest types on all elements in the downloaded data. */ +class OsmApiQuestDownloader @Inject constructor( + private val elementDB: MergedElementDao, + private val osmQuestController: OsmQuestController, + private val countryBoundariesFuture: FutureTask, + private val notePositionsSource: NotePositionsSource, + private val mapDataApi: MapDataApi, + private val elementGeometryCreator: ElementGeometryCreator, + private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker +) { + private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() + + // TODO TEST + fun downloadMultiple(questTypes: List>, bbox: BoundingBox): List> { + val skippedQuestTypes = mutableSetOf>() + + var time = System.currentTimeMillis() + + val mapData = mapDataApi.getMapAndHandleTooBigQuery(bbox) + + val elementGeometries = EnumMap>(Element.Type::class.java) + elementGeometries[Element.Type.NODE] = mutableMapOf() + elementGeometries[Element.Type.WAY] = mutableMapOf() + elementGeometries[Element.Type.RELATION] = mutableMapOf() + + val quests = ArrayList() + val questElements = HashSet() + + val secondsSpentDownloading = (System.currentTimeMillis() - time) / 1000 + Log.i(TAG,"Downloaded ${mapData.nodes.size} nodes, ${mapData.ways.size} ways and ${mapData.relations.size} relations in ${secondsSpentDownloading}s") + time = System.currentTimeMillis() + + val truncatedBlacklistedPositions = notePositionsSource.getAllPositions(bbox).map { it.truncateTo5Decimals() }.toSet() + + for (questType in questTypes) { + val questTypeName = questType.getName() + + val countries = questType.enabledInCountries + if (!countryBoundaries.intersects(bbox, countries)) { + Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") + continue + } + + for (element in mapData) { + val appliesToElement = questType.isApplicableTo(element) + if (appliesToElement == null) { + skippedQuestTypes.add(questType) + break + } + if (!appliesToElement) continue + if (!elementGeometries[element.type]!!.containsKey(element.id)) { + val geometry = elementGeometryCreator.create(element, mapData) ?: continue + elementGeometries[element.type]!![element.id] = geometry + } + val geometry = elementGeometries[element.type]!![element.id] + if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, element, geometry, truncatedBlacklistedPositions)) continue + + val quest = OsmQuest(questType, element.type, element.id, geometry!!) + + quests.add(quest) + questElements.add(element) + } + } + val downloadedQuestTypes = questTypes.filterNot { skippedQuestTypes.contains(it) } + val downloadedQuestTypeNames = downloadedQuestTypes.map { it.getName() } + + val secondsSpentAnalyzing = (System.currentTimeMillis() - time) / 1000 + + if (downloadedQuestTypeNames.isNotEmpty()) { + + // elements must be put into DB first because quests have foreign keys on it + elementDB.putAll(questElements) + + val replaceResult = osmQuestController.replaceInBBox(quests, bbox, downloadedQuestTypeNames) + + elementDB.deleteUnreferenced() + + for (questType in downloadedQuestTypes) { + questType.cleanMetadata() + } + + Log.i(TAG,"${downloadedQuestTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") + } else { + Log.i(TAG,"Added and removed no quests because no quest types were downloaded, in ${secondsSpentAnalyzing}s") + } + + return downloadedQuestTypes + } + + companion object { + private const val TAG = "QuestDownload" + } +} + +private fun QuestType<*>.getName() = javaClass.simpleName + +// the resulting precision is about ~1 meter (see #1089) +private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) + +private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt index 7cca556635..fa172f137d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt @@ -93,10 +93,10 @@ import javax.inject.Singleton /** Replace all quests of the given type in the given bounding box with the given quests, * including their geometry. Called on download of a quest type for a bounding box. */ fun replaceInBBox(quests: List, bbox: BoundingBox, questTypes: List): UpdateResult { + require(questTypes.isNotEmpty()) { "questTypes must not be empty if not null" } /* All quests in the given bounding box and of the given type should be replaced by the * input list. So, there may be 1. new quests that are added and 2. there may be previous * quests that have been there before but now not anymore, these need to be removed. */ - val previousQuestIdsByElement = dao.getAll( bounds = bbox, questTypes = questTypes @@ -230,20 +230,25 @@ import javax.inject.Singleton /** Get count of all unanswered quests in given bounding box of given types */ - fun getAllVisibleInBBoxCount(bbox: BoundingBox, questTypes: Collection) : Int = - dao.getCount( + fun getAllVisibleInBBoxCount(bbox: BoundingBox, questTypes: Collection) : Int { + if (questTypes.isEmpty()) return 0 + return dao.getCount( statusIn = listOf(QuestStatus.NEW), bounds = bbox, questTypes = questTypes ) + } /** Get all unanswered quests in given bounding box of given types */ - fun getAllVisibleInBBox(bbox: BoundingBox, questTypes: Collection): List = - dao.getAll( + fun getAllVisibleInBBox(bbox: BoundingBox, questTypes: Collection): List { + if (questTypes.isEmpty()) return listOf() + return dao.getAll( statusIn = listOf(QuestStatus.NEW), bounds = bbox, questTypes = questTypes ) + } + /** Get single quest by id */ fun get(id: Long): OsmQuest? = dao.get(id) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDao.kt index 5b15dbc964..0ad77a3cdb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDao.kt @@ -149,7 +149,8 @@ private fun createQuery( add("$ELEMENT_TYPE = ?", element.elementType.name) add("$ELEMENT_ID = ?", element.elementId.toString()) } - if (statusIn != null && statusIn.isNotEmpty()) { + if (statusIn != null) { + require(statusIn.isNotEmpty()) { "statusIn must not be empty if not null" } if (statusIn.size == 1) { add("$QUEST_STATUS = ?", statusIn.single().name) } else { @@ -157,7 +158,8 @@ private fun createQuery( add("$QUEST_STATUS IN ($names)") } } - if (questTypes != null && questTypes.isNotEmpty()) { + if (questTypes != null) { + require(questTypes.isNotEmpty()) { "questTypes must not be empty if not null" } if (questTypes.size == 1) { add("$QUEST_TYPE = ?", questTypes.single()) } else { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index 27e45391f3..5a593e2e87 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -8,105 +8,28 @@ import javax.inject.Inject import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.countryboundaries.intersects -import de.westnordost.countryboundaries.isInAny import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.Element.Type.* import de.westnordost.osmapi.map.data.LatLon -import de.westnordost.osmapi.map.getMap -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.util.measuredLength +import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource import java.util.* import kotlin.collections.ArrayList -/** Takes care of downloading one quest type in a bounding box and persisting the downloaded quests */ +/** Takes care of downloading one quest type in a bounding box and persisting the downloaded quests. + * Calls download(bbox) on the quest type */ class OsmQuestDownloader @Inject constructor( - private val elementDB: MergedElementDao, - private val osmQuestController: OsmQuestController, - private val countryBoundariesFuture: FutureTask, - private val mapDataApi: MapDataApi, - private val elementGeometryCreator: ElementGeometryCreator + private val elementDB: MergedElementDao, + private val osmQuestController: OsmQuestController, + private val countryBoundariesFuture: FutureTask, + private val notePositionsSource: NotePositionsSource, + private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker ) { private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() - // TODO TEST - // TODO maybe move/merge to QuestGiver? - fun downloadMultiple(questTypes: List>, bbox: BoundingBox, blacklistedPositions: Set): List> { - val skippedQuestTypes = mutableSetOf>() - - var time = System.currentTimeMillis() - - // TODO what if API returns that the area is too big? - val mapData = mapDataApi.getMap(bbox) - - val elementGeometries = EnumMap>(Element.Type::class.java) - elementGeometries[NODE] = mutableMapOf() - elementGeometries[WAY] = mutableMapOf() - elementGeometries[RELATION] = mutableMapOf() - - val quests = ArrayList() - val questElements = HashSet() - - val secondsSpentDownloading = (System.currentTimeMillis() - time) / 1000 - Log.i(TAG,"Downloaded ${mapData.nodes.size} nodes, ${mapData.ways.size} ways and ${mapData.relations.size} relations in ${secondsSpentDownloading}s") - time = System.currentTimeMillis() - - for (questType in questTypes) { - val questTypeName = questType.getName() - - val countries = questType.enabledInCountries - if (!countryBoundaries.intersects(bbox, countries)) { - Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") - continue - } - - for (element in mapData) { - val appliesToElement = questType.isApplicableTo(element) - if (appliesToElement == null) { - skippedQuestTypes.add(questType) - break - } - if (!appliesToElement) continue - if (!elementGeometries[element.type]!!.containsKey(element.id)) { - val geometry = elementGeometryCreator.create(element, mapData) ?: continue - elementGeometries[element.type]!![element.id] = geometry - } - val geometry = elementGeometries[element.type]!![element.id] - if (!mayCreateQuestFrom(questType, element, geometry, blacklistedPositions)) continue - - val quest = OsmQuest(questType, element.type, element.id, geometry!!) - - quests.add(quest) - questElements.add(element) - } - } - val downloadedQuestTypes = questTypes.filterNot { skippedQuestTypes.contains(it) } - val downloadedQuestTypeNames = downloadedQuestTypes.map { it.getName() } - - // elements must be put into DB first because quests have foreign keys on it - elementDB.putAll(questElements) - - val replaceResult = osmQuestController.replaceInBBox(quests, bbox, downloadedQuestTypeNames) - - elementDB.deleteUnreferenced() - - for(questType in downloadedQuestTypes) { - questType.cleanMetadata() - } - - val secondsSpentAnalyzing = (System.currentTimeMillis() - time) / 1000 - Log.i(TAG,"${downloadedQuestTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") - - return downloadedQuestTypes - } - - fun download(questType: OsmElementQuestType<*>, bbox: BoundingBox, blacklistedPositions: Set): Boolean { + fun download(questType: OsmElementQuestType<*>, bbox: BoundingBox): Boolean { val questTypeName = questType.getName() val countries = questType.enabledInCountries @@ -117,11 +40,11 @@ class OsmQuestDownloader @Inject constructor( val elements = mutableListOf() val quests = ArrayList() - val truncatedBlacklistedPositions = blacklistedPositions.map { it.truncateTo5Decimals() }.toSet() + val truncatedBlacklistedPositions = notePositionsSource.getAllPositions(bbox).map { it.truncateTo5Decimals() }.toSet() val time = System.currentTimeMillis() val success = questType.download(bbox) { element, geometry -> - if (mayCreateQuestFrom(questType, element, geometry, truncatedBlacklistedPositions)) { + if (elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, element, geometry, truncatedBlacklistedPositions)) { val quest = OsmQuest(questType, element.type, element.id, geometry!!) quests.add(quest) @@ -146,52 +69,13 @@ class OsmQuestDownloader @Inject constructor( return true } - private fun mayCreateQuestFrom( - questType: OsmElementQuestType<*>, element: Element, geometry: ElementGeometry?, - blacklistedPositions: Set - ): Boolean { - val questTypeName = questType.getName() - // invalid geometry -> can't show this quest, so skip it - if (geometry == null) { - // classified as warning because it might very well be a bug on the geometry creation on our side - Log.w(TAG, "$questTypeName: Not adding a quest because the element ${element.toLogString()} has no valid geometry") - return false - } - val pos = geometry.center - - // do not create quests whose marker is at/near a blacklisted position - if (blacklistedPositions.contains(pos.truncateTo5Decimals())) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because there is a note at that position") - return false - } - - // do not create quests in countries where the quest is not activated - val countries = questType.enabledInCountries - if (!countryBoundaries.isInAny(pos, countries)) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because the quest is disabled in this country") - return false - } - - // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey - if (geometry is ElementPolylinesGeometry) { - val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } - if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { - Log.d(TAG, "$questTypeName: Not adding a quest for ${element.toLogString()} because the geometry is too long") - return false - } - } - - return true - } companion object { private const val TAG = "QuestDownload" } } -const val MAX_GEOMETRY_LENGTH_IN_METERS = 500 - private fun QuestType<*>.getName() = javaClass.simpleName // the resulting precision is about ~1 meter (see #1089) @@ -200,5 +84,3 @@ private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimal private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 private fun Element.toLogString() = "${type.name.toLowerCase(Locale.US)} #$id" - -private fun LatLon.toLogString() = "$latitude, $longitude" diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestDao.kt index c763982afb..8074d3d7ac 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/notequests/OsmNoteQuestDao.kt @@ -137,7 +137,8 @@ private fun createQuery( bounds: BoundingBox? = null, changedBefore: Long? = null ) = WhereSelectionBuilder().apply { - if (statusIn != null && statusIn.isNotEmpty()) { + if (statusIn != null) { + require(statusIn.isNotEmpty()) { "statusIn must not be empty if not null" } if (statusIn.size == 1) { add("$QUEST_STATUS = ?", statusIn.single().name) } else { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt index 3fcea7b8c4..585eb5247f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt @@ -77,6 +77,7 @@ import javax.inject.Singleton /** Retrieve all visible (=new) quests in the given bounding box from local database */ fun getAllVisible(bbox: BoundingBox, questTypes: Collection): List { + if (questTypes.isEmpty()) return listOf() val osmQuests = osmQuestController.getAllVisibleInBBox(bbox, questTypes) val osmNoteQuests = osmNoteQuestController.getAllVisibleInBBox(bbox) diff --git a/app/src/main/res/drawable/ic_search_black_128dp.xml b/app/src/main/res/drawable/ic_search_black_128dp.xml new file mode 100644 index 0000000000..524b3e43cf --- /dev/null +++ b/app/src/main/res/drawable/ic_search_black_128dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt index 5c2d462d02..6801db171b 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt @@ -6,12 +6,12 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource import de.westnordost.streetcomplete.data.quest.AllCountries import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.quest.Countries @@ -28,8 +28,9 @@ class OsmQuestDownloaderTest { private lateinit var elementDb: MergedElementDao private lateinit var osmQuestController: OsmQuestController private lateinit var countryBoundaries: CountryBoundaries - private lateinit var mapDataApi: MapDataApi + private lateinit var notePositionsSource: NotePositionsSource private lateinit var elementGeometryCreator: ElementGeometryCreator + private lateinit var elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker private lateinit var downloader: OsmQuestDownloader @Before fun setUp() { @@ -38,10 +39,11 @@ class OsmQuestDownloaderTest { on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) countryBoundaries = mock() elementGeometryCreator = mock() - mapDataApi = mock() + notePositionsSource = mock() + elementEligibleForOsmQuestChecker = mock() val countryBoundariesFuture = FutureTask { countryBoundaries } countryBoundariesFuture.run() - downloader = OsmQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, mapDataApi, elementGeometryCreator) + downloader = OsmQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, elementEligibleForOsmQuestChecker) } @Test fun `ignore element with invalid geometry`() { @@ -52,7 +54,7 @@ class OsmQuestDownloaderTest { val questType = ListBackedQuestType(listOf(invalidGeometryElement)) - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0), setOf()) + downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) } @Test fun `ignore at blacklisted position`() { @@ -61,10 +63,11 @@ class OsmQuestDownloaderTest { OsmNode(0, 0, blacklistPos, null), ElementPointGeometry(blacklistPos) ) + on(notePositionsSource.getAllPositions(any())).thenReturn(listOf(blacklistPos)) val questType = ListBackedQuestType(listOf(blacklistElement)) - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0), setOf(blacklistPos)) + downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) } @Test fun `ignore element in country for which this quest is disabled`() { @@ -80,7 +83,7 @@ class OsmQuestDownloaderTest { on(countryBoundaries.isInAny(anyDouble(),anyDouble(),any())).thenReturn(true) on(countryBoundaries.getContainingIds(anyDouble(),anyDouble(),anyDouble(),anyDouble())).thenReturn(setOf()) - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0), setOf()) + downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) } @Test fun `creates quest for element`() { @@ -94,7 +97,7 @@ class OsmQuestDownloaderTest { on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0), setOf()) + downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) verify(elementDb).putAll(any()) verify(osmQuestController).replaceInBBox(any(), any(), any()) From 41c06c1a8c974369eaefeebf3b29896369d2f7c3 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 17 Oct 2020 22:36:17 +0200 Subject: [PATCH 16/63] refactor --- .../data/download/QuestDownloader.kt | 2 +- .../osmquest/ElementEligibleForOsmQuestChecker.kt | 15 ++------------- .../data/osm/osmquest/OsmApiQuestDownloader.kt | 8 +++----- .../data/osm/osmquest/OsmQuestDownloader.kt | 2 +- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index 90f42e54a2..dde68c3755 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -136,7 +136,7 @@ class QuestDownloader @Inject constructor( // Since we query all the data at once, we can also do the downloading for quests not on our list. val questTypes = questTypesProvider.get().filterIsInstance>() val questDownload = osmApiQuestDownloaderProvider.get() - val downloadedQuestTypes = questDownload.downloadMultiple(questTypes, bbox) + val downloadedQuestTypes = questDownload.download(questTypes, bbox) downloadedTilesDao.putAll(tiles, downloadedQuestTypes.map { it.javaClass.simpleName }) progressListener?.onFinished(downloadItem) return downloadedQuestTypes diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt index b544a5109d..9cc9785b36 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt @@ -2,12 +2,10 @@ package de.westnordost.streetcomplete.data.osm.osmquest import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.countryboundaries.isInAny -import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.LatLon import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.util.measuredLength import java.util.concurrent.FutureTask import javax.inject.Inject @@ -16,16 +14,10 @@ class ElementEligibleForOsmQuestChecker @Inject constructor( private val countryBoundariesFuture: FutureTask, ) { fun mayCreateQuestFrom( - questType: OsmElementQuestType<*>, element: Element, geometry: ElementGeometry?, - blacklistedPositions: Set + questType: OsmElementQuestType<*>, geometry: ElementGeometry?, blacklistedPositions: Set ): Boolean { - val questTypeName = questType.getName() - // invalid geometry -> can't show this quest, so skip it - if (geometry == null) { - return false - } - val pos = geometry.center + val pos = geometry?.center ?: return false // do not create quests whose marker is at/near a blacklisted position if (blacklistedPositions.contains(pos.truncateTo5Decimals())) { @@ -50,9 +42,6 @@ class ElementEligibleForOsmQuestChecker @Inject constructor( } } - -private fun QuestType<*>.getName() = javaClass.simpleName - const val MAX_GEOMETRY_LENGTH_IN_METERS = 500 // the resulting precision is about ~1 meter (see #1089) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 149b0bfe54..d2b95a964c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -30,10 +30,8 @@ class OsmApiQuestDownloader @Inject constructor( private val elementGeometryCreator: ElementGeometryCreator, private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker ) { - private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() - // TODO TEST - fun downloadMultiple(questTypes: List>, bbox: BoundingBox): List> { + fun download(questTypes: List>, bbox: BoundingBox): List> { val skippedQuestTypes = mutableSetOf>() var time = System.currentTimeMillis() @@ -58,7 +56,7 @@ class OsmApiQuestDownloader @Inject constructor( val questTypeName = questType.getName() val countries = questType.enabledInCountries - if (!countryBoundaries.intersects(bbox, countries)) { + if (!countryBoundariesFuture.get().intersects(bbox, countries)) { Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") continue } @@ -75,7 +73,7 @@ class OsmApiQuestDownloader @Inject constructor( elementGeometries[element.type]!![element.id] = geometry } val geometry = elementGeometries[element.type]!![element.id] - if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, element, geometry, truncatedBlacklistedPositions)) continue + if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) continue val quest = OsmQuest(questType, element.type, element.id, geometry!!) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index 5a593e2e87..5858bf7cfe 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -44,7 +44,7 @@ class OsmQuestDownloader @Inject constructor( val time = System.currentTimeMillis() val success = questType.download(bbox) { element, geometry -> - if (elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, element, geometry, truncatedBlacklistedPositions)) { + if (elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) { val quest = OsmQuest(questType, element.type, element.id, geometry!!) quests.add(quest) From 918856ef4ba702232c79b7a3eeeb1e0c394ff13d Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 19 Oct 2020 02:17:19 +0200 Subject: [PATCH 17/63] added tests to refactor --- .../osmquest/OsmElementUpdateController.kt | 93 +++++++++++++++++++ .../data/osm/osmquest/OsmQuestsUploader.kt | 21 ++--- .../osmquest/undo/UndoOsmQuestsUploader.kt | 23 ++--- .../data/osm/splitway/SplitWaysUploader.kt | 40 +++----- .../osm/upload/OsmInChangesetsUploader.kt | 83 +++-------------- .../osm/osmquest/OsmQuestsUploaderTest.kt | 46 +++++---- .../undo/UndoOsmQuestsUploaderTest.kt | 47 +++++----- .../osm/splitway/SplitWaysUploaderTest.kt | 49 ++++------ 8 files changed, 192 insertions(+), 210 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt new file mode 100644 index 0000000000..32dc9a6c09 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt @@ -0,0 +1,93 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.common.errors.OsmNotFoundException +import de.westnordost.osmapi.map.MapData +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Node +import de.westnordost.osmapi.map.data.Relation +import de.westnordost.osmapi.map.data.Way +import de.westnordost.osmapi.map.getRelationComplete +import de.westnordost.osmapi.map.getWayComplete +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryEntry +import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import javax.inject.Inject + +/** When an element has been updated or deleted (from the API), this class takes care of updating + * the data that is dependent on the element - the geometry of the element and the quests based + * on the element */ +// TODO TEST +class OsmElementUpdateController @Inject constructor( + private val mapDataApi: MapDataApi, + private val elementGeometryCreator: ElementGeometryCreator, + private val elementGeometryDB: ElementGeometryDao, + private val elementDB: MergedElementDao, + private val questGiver: OsmQuestGiver, +){ + + /** The [element] has been updated. Persist that, determine its geometry and update the quests + * based on that element. If [recreateQuestTypes] is not null, always (re)create the given + * quest types on the element without checking for its eligibility */ + fun update(element: Element, recreateQuestTypes: List>?) { + val newGeometry = createGeometry(element) + if (newGeometry != null) { + elementDB.put(element) + /* need to update element geometry in any case because even if the quest giver unlocks + no new quests, there are still those that currently exist. They need the current + geometry too! */ + elementGeometryDB.put(ElementGeometryEntry(element.type, element.id, newGeometry)) + + if (recreateQuestTypes == null) { + questGiver.updateQuests(element, newGeometry) + } else { + questGiver.recreateQuests(element, newGeometry, recreateQuestTypes) + } + } else { + // new element has invalid geometry + delete(element.type, element.id) + } + } + + fun delete(elementType: Element.Type, elementId: Long) { + elementDB.delete(elementType, elementId) + questGiver.deleteQuests(elementType, elementId) + } + + fun get(elementType: Element.Type, elementId: Long): Element? { + return elementDB.get(elementType, elementId) + } + + fun cleanUp() { + elementDB.deleteUnreferenced() + } + + private fun createGeometry(element: Element): ElementGeometry? { + when(element) { + is Node -> { + return elementGeometryCreator.create(element) + } + is Way -> { + val mapData: MapData + try { + mapData = mapDataApi.getWayComplete(element.id) + } catch (e: OsmNotFoundException) { + return null + } + return elementGeometryCreator.create(element, mapData) + } + is Relation -> { + val mapData: MapData + try { + mapData = mapDataApi.getRelationComplete(element.id) + } catch (e: OsmNotFoundException) { + return null + } + return elementGeometryCreator.create(element, mapData) + } + else -> return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt index 3ff728b43a..4583928dc5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploader.kt @@ -2,12 +2,8 @@ package de.westnordost.streetcomplete.data.osm.osmquest import android.util.Log import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import javax.inject.Inject -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.osm.upload.OsmInChangesetsUploader import de.westnordost.streetcomplete.data.user.StatisticsUpdater @@ -16,17 +12,12 @@ import java.util.concurrent.atomic.AtomicBoolean /** Gets all answered osm quests from local DB and uploads them via the OSM API */ class OsmQuestsUploader @Inject constructor( - elementDB: MergedElementDao, - elementGeometryDB: ElementGeometryDao, - changesetManager: OpenQuestChangesetsManager, - questGiver: OsmQuestGiver, - elementGeometryCreator: ElementGeometryCreator, - mapDataApi: MapDataApi, - private val osmQuestController: OsmQuestController, - private val singleChangeUploader: SingleOsmElementTagChangesUploader, - private val statisticsUpdater: StatisticsUpdater -) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, mapDataApi) { + changesetManager: OpenQuestChangesetsManager, + elementUpdateController: OsmElementUpdateController, + private val osmQuestController: OsmQuestController, + private val singleChangeUploader: SingleOsmElementTagChangesUploader, + private val statisticsUpdater: StatisticsUpdater +) : OsmInChangesetsUploader(changesetManager, elementUpdateController) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Applying quest changes") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt index 195b7205e6..482f486bb8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploader.kt @@ -2,13 +2,9 @@ package de.westnordost.streetcomplete.data.osm.osmquest.undo import android.util.Log import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import javax.inject.Inject -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementUpdateController import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.osm.upload.OsmInChangesetsUploader import de.westnordost.streetcomplete.data.osm.osmquest.SingleOsmElementTagChangesUploader @@ -18,17 +14,12 @@ import java.util.concurrent.atomic.AtomicBoolean /** Gets all undo osm quests from local DB and uploads them via the OSM API */ class UndoOsmQuestsUploader @Inject constructor( - elementDB: MergedElementDao, - elementGeometryDB: ElementGeometryDao, - changesetManager: OpenQuestChangesetsManager, - questGiver: OsmQuestGiver, - elementGeometryCreator: ElementGeometryCreator, - mapDataApi: MapDataApi, - private val undoQuestDB: UndoOsmQuestDao, - private val singleChangeUploader: SingleOsmElementTagChangesUploader, - private val statisticsUpdater: StatisticsUpdater -) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, mapDataApi) { + changesetManager: OpenQuestChangesetsManager, + elementUpdateController: OsmElementUpdateController, + private val undoQuestDB: UndoOsmQuestDao, + private val singleChangeUploader: SingleOsmElementTagChangesUploader, + private val statisticsUpdater: StatisticsUpdater +) : OsmInChangesetsUploader(changesetManager, elementUpdateController) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Undoing quest changes") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt index 57ff427f35..781f6d03a0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploader.kt @@ -3,13 +3,7 @@ package de.westnordost.streetcomplete.data.osm.splitway import android.util.Log import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Way -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryEntry -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementUpdateController import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.osm.upload.OsmInChangesetsUploader import de.westnordost.streetcomplete.data.user.StatisticsUpdater @@ -18,17 +12,12 @@ import javax.inject.Inject /** Gets all split ways from local DB and uploads them via the OSM API */ class SplitWaysUploader @Inject constructor( - private val elementDB: MergedElementDao, - private val elementGeometryDB: ElementGeometryDao, - changesetManager: OpenQuestChangesetsManager, - private val questGiver: OsmQuestGiver, - elementGeometryCreator: ElementGeometryCreator, - mapDataApi: MapDataApi, - private val splitWayDB: OsmQuestSplitWayDao, - private val splitSingleOsmWayUploader: SplitSingleWayUploader, - private val statisticsUpdater: StatisticsUpdater -) : OsmInChangesetsUploader(elementDB, elementGeometryDB, changesetManager, - questGiver, elementGeometryCreator, mapDataApi) { + changesetManager: OpenQuestChangesetsManager, + private val elementUpdateController: OsmElementUpdateController, + private val splitWayDB: OsmQuestSplitWayDao, + private val splitSingleOsmWayUploader: SplitSingleWayUploader, + private val statisticsUpdater: StatisticsUpdater +) : OsmInChangesetsUploader(changesetManager, elementUpdateController) { @Synchronized override fun upload(cancelled: AtomicBoolean) { Log.i(TAG, "Splitting ways") @@ -41,16 +30,11 @@ class SplitWaysUploader @Inject constructor( return splitSingleOsmWayUploader.upload(changesetId, element as Way, quest.splits) } - override fun updateElement(newElement: Element, newGeometry: ElementGeometry?, quest: OsmQuestSplitWay) { - if (newGeometry != null) { - elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, newGeometry)) - elementDB.put(newElement) - questGiver.recreateQuests(newElement, newGeometry, quest.questTypesOnWay) - } else { - // new element has invalid geometry - elementDB.delete(newElement.type, newElement.id) - questGiver.deleteQuests(newElement.type, newElement.id) - } + override fun updateElement(element: Element, quest: OsmQuestSplitWay) { + /* We override this because in case of a split, the two (or more) sections of the way should + * actually get the same quests as the original way, there is no need to again check for + * the eligibility of the element for each quest which would be done normally */ + elementUpdateController.update(element, quest.questTypesOnWay) } override fun onUploadSuccessful(quest: OsmQuestSplitWay) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt index 8a3ac6b77e..31d5251544 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/upload/OsmInChangesetsUploader.kt @@ -1,20 +1,10 @@ package de.westnordost.streetcomplete.data.osm.upload import androidx.annotation.CallSuper -import de.westnordost.osmapi.common.errors.OsmNotFoundException -import de.westnordost.osmapi.map.MapData import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.Node -import de.westnordost.osmapi.map.data.Relation -import de.westnordost.osmapi.map.data.Way -import de.westnordost.osmapi.map.getRelationComplete -import de.westnordost.osmapi.map.getWayComplete -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.* import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementUpdateController import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.upload.OnUploadedChangeListener import de.westnordost.streetcomplete.data.upload.Uploader @@ -24,13 +14,9 @@ import java.util.concurrent.atomic.AtomicBoolean /** Base class for all uploaders which upload OSM data. They all have in common that they handle * OSM data (of course), and that the data is uploaded in changesets. */ abstract class OsmInChangesetsUploader( - private val elementDB: MergedElementDao, - private val elementGeometryDB: ElementGeometryDao, - private val changesetManager: OpenQuestChangesetsManager, - private val questGiver: OsmQuestGiver, - private val elementGeometryCreator: ElementGeometryCreator, - private val mapDataApi: MapDataApi - ): Uploader { + private val changesetManager: OpenQuestChangesetsManager, + private val elementUpdateController: OsmElementUpdateController +): Uploader { override var uploadedChangeListener: OnUploadedChangeListener? = null @@ -45,13 +31,13 @@ abstract class OsmInChangesetsUploader( try { val uploadedElements = uploadSingle(quest) for (element in uploadedElements) { - updateElement(element, createGeometry(element), quest) + updateElement(element, quest) } uploadedQuestTypes.add(quest.osmElementQuestType) onUploadSuccessful(quest) uploadedChangeListener?.onUploaded(quest.osmElementQuestType.name, quest.position) } catch (e: ElementIncompatibleException) { - deleteElement(quest.elementType, quest.elementId) + elementUpdateController.delete(quest.elementType, quest.elementId) onUploadFailed(quest, e) uploadedChangeListener?.onDiscarded(quest.osmElementQuestType.name, quest.position) } catch (e: ElementConflictException) { @@ -62,8 +48,12 @@ abstract class OsmInChangesetsUploader( cleanUp(uploadedQuestTypes) } + protected open fun updateElement(element: Element, quest: T) { + elementUpdateController.update(element, null) + } + private fun uploadSingle(quest: T) : List { - val element = elementDB.get(quest.elementType, quest.elementId) + val element = elementUpdateController.get(quest.elementType, quest.elementId) ?: throw ElementDeletedException("Element deleted") return try { @@ -75,63 +65,14 @@ abstract class OsmInChangesetsUploader( } } - /* TODO REFACTOR: It shouldn't be the duty of OsmInChangesetsUploader to do (or delegate) the - * work necessary that entails when updating an element (grant/remove quests, - * update element geometry). Instead, there should be an observer on the - * ElementDao (or a controller in front of it) that takes care of that. - * - * This will remove the dependencies to elementGeometryDB, questGiver etc */ - protected open fun updateElement(newElement: Element, newGeometry: ElementGeometry?, quest: T) { - if (newGeometry != null) { - elementGeometryDB.put(ElementGeometryEntry(newElement.type, newElement.id, newGeometry)) - elementDB.put(newElement) - questGiver.updateQuests(newElement, newGeometry) - } else { - // new element has invalid geometry - deleteElement(newElement.type, newElement.id) - } - } - - private fun deleteElement(elementType: Element.Type, elementId: Long) { - elementDB.delete(elementType, elementId) - questGiver.deleteQuests(elementType, elementId) - } - @CallSuper protected open fun cleanUp(questTypes: Set>) { - elementDB.deleteUnreferenced() + elementUpdateController.cleanUp() // must be after unreferenced elements have been deleted for (questType in questTypes) { questType.cleanMetadata() } } - private fun createGeometry(element: Element): ElementGeometry? { - when(element) { - is Node -> { - return elementGeometryCreator.create(element) - } - is Way -> { - val mapData: MapData - try { - mapData = mapDataApi.getWayComplete(element.id) - } catch (e: OsmNotFoundException) { - return null - } - return elementGeometryCreator.create(element, mapData) - } - is Relation -> { - val mapData: MapData - try { - mapData = mapDataApi.getRelationComplete(element.id) - } catch (e: OsmNotFoundException) { - return null - } - return elementGeometryCreator.create(element, mapData) - } - else -> return null - } - } - protected abstract fun getAll() : Collection /** Upload the changes for a single quest and element. * Returns the updated element(s) for which it should be checked whether they are eligible diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt index 743e1e4fe8..8325870ac2 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestsUploaderTest.kt @@ -4,17 +4,14 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.quest.QuestStatus import de.westnordost.streetcomplete.data.osm.changes.StringMapChanges import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.osm.upload.ChangesetConflictException import de.westnordost.streetcomplete.data.osm.upload.ElementConflictException +import de.westnordost.streetcomplete.data.osm.upload.ElementDeletedException import de.westnordost.streetcomplete.data.user.StatisticsUpdater import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.on @@ -27,41 +24,32 @@ import java.util.concurrent.atomic.AtomicBoolean class OsmQuestsUploaderTest { private lateinit var osmQuestController: OsmQuestController - private lateinit var elementDB: MergedElementDao private lateinit var changesetManager: OpenQuestChangesetsManager - private lateinit var elementGeometryDB: ElementGeometryDao - private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: ElementGeometryCreator - private lateinit var mapDataApi: MapDataApi private lateinit var singleChangeUploader: SingleOsmElementTagChangesUploader private lateinit var statisticsUpdater: StatisticsUpdater + private lateinit var elementUpdateController: OsmElementUpdateController private lateinit var uploader: OsmQuestsUploader @Before fun setUp() { osmQuestController = mock() - elementDB = mock() - on(elementDB.get(any(), anyLong())).thenReturn(createElement()) changesetManager = mock() singleChangeUploader = mock() - elementGeometryDB = mock() - questGiver = mock() - mapDataApi = mock() - elementGeometryCreator = mock() statisticsUpdater = mock() - on(elementGeometryCreator.create(any())).thenReturn(mock()) - uploader = OsmQuestsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, mapDataApi, osmQuestController, singleChangeUploader, statisticsUpdater) + elementUpdateController = mock() + uploader = OsmQuestsUploader(changesetManager, elementUpdateController, + osmQuestController, singleChangeUploader, statisticsUpdater) } @Test fun `cancel upload works`() { uploader.upload(AtomicBoolean(true)) - verifyZeroInteractions(changesetManager, singleChangeUploader, elementDB, osmQuestController) + verifyZeroInteractions(elementUpdateController, changesetManager, singleChangeUploader, statisticsUpdater, osmQuestController) } @Test fun `catches ElementConflict exception`() { on(osmQuestController.getAllAnswered()).thenReturn(listOf(createQuest())) on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ElementConflictException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) @@ -71,12 +59,15 @@ class OsmQuestsUploaderTest { @Test fun `discard if element was deleted`() { val q = createQuest() on(osmQuestController.getAllAnswered()).thenReturn(listOf(q)) - on(elementDB.get(any(), anyLong())).thenReturn(null) + on(singleChangeUploader.upload(anyLong(), any(), any())) + .thenThrow(ElementDeletedException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) verify(uploader.uploadedChangeListener)?.onDiscarded(q.osmElementQuestType.javaClass.simpleName, q.position) + verify(elementUpdateController).delete(any(), anyLong()) } @Test fun `catches ChangesetConflictException exception and tries again once`() { @@ -84,6 +75,7 @@ class OsmQuestsUploaderTest { on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ChangesetConflictException()) .thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) @@ -98,15 +90,14 @@ class OsmQuestsUploaderTest { on(osmQuestController.getAllAnswered()).thenReturn(quests) on(singleChangeUploader.upload(anyLong(), any(), any())).thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) verify(osmQuestController, times(2)).success(any()) verify(uploader.uploadedChangeListener, times(2))?.onUploaded(any(), any()) - verify(elementDB, times(2)).put(any()) - verify(elementGeometryDB, times(2)).put(any()) - verify(questGiver, times(2)).updateQuests(any(), any()) + verify(elementUpdateController, times(2)).update(any(), isNull()) verify(statisticsUpdater, times(2)).addOne(any(), any()) } @@ -116,13 +107,17 @@ class OsmQuestsUploaderTest { on(osmQuestController.getAllAnswered()).thenReturn(quests) on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ElementConflictException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) verify(osmQuestController, times(2)).fail(any()) verify(uploader.uploadedChangeListener,times(2))?.onDiscarded(any(), any()) - verifyZeroInteractions(questGiver, elementGeometryCreator, statisticsUpdater) + verify(elementUpdateController, times(2)).get(any(), anyLong()) + verify(elementUpdateController).cleanUp() + verifyNoMoreInteractions(elementUpdateController) + verifyZeroInteractions(statisticsUpdater) } @Test fun `delete unreferenced elements and clean metadata at the end`() { @@ -130,10 +125,11 @@ class OsmQuestsUploaderTest { on(osmQuestController.getAllAnswered()).thenReturn(listOf(quest)) on(singleChangeUploader.upload(anyLong(), any(), any())).thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) - verify(elementDB).deleteUnreferenced() + verify(elementUpdateController).cleanUp() verify(quest.osmElementQuestType).cleanMetadata() } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt index 34397245e1..74bc248f9c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/undo/UndoOsmQuestsUploaderTest.kt @@ -4,17 +4,14 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChanges import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementUpdateController import de.westnordost.streetcomplete.data.osm.osmquest.SingleOsmElementTagChangesUploader import de.westnordost.streetcomplete.data.osm.upload.ChangesetConflictException import de.westnordost.streetcomplete.data.osm.upload.ElementConflictException +import de.westnordost.streetcomplete.data.osm.upload.ElementDeletedException import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.user.StatisticsUpdater import de.westnordost.streetcomplete.mock @@ -27,42 +24,33 @@ import java.util.concurrent.atomic.AtomicBoolean class UndoOsmQuestsUploaderTest { private lateinit var undoQuestDB: UndoOsmQuestDao - private lateinit var elementDB: MergedElementDao private lateinit var changesetManager: OpenQuestChangesetsManager - private lateinit var elementGeometryDB: ElementGeometryDao - private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: ElementGeometryCreator - private lateinit var mapDataApi: MapDataApi private lateinit var singleChangeUploader: SingleOsmElementTagChangesUploader private lateinit var statisticsUpdater: StatisticsUpdater + private lateinit var elementUpdateController: OsmElementUpdateController private lateinit var uploader: UndoOsmQuestsUploader @Before fun setUp() { undoQuestDB = mock() - elementDB = mock() - on(elementDB.get(any(), anyLong())).thenReturn(createElement()) changesetManager = mock() singleChangeUploader = mock() - elementGeometryDB = mock() - questGiver = mock() - elementGeometryCreator = mock() - mapDataApi = mock() statisticsUpdater = mock() - on(elementGeometryCreator.create(any())).thenReturn(mock()) - uploader = UndoOsmQuestsUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, mapDataApi, undoQuestDB, singleChangeUploader, statisticsUpdater) + elementUpdateController = mock() + uploader = UndoOsmQuestsUploader(changesetManager, elementUpdateController, + undoQuestDB, singleChangeUploader, statisticsUpdater) } @Test fun `cancel upload works`() { val cancelled = AtomicBoolean(true) uploader.upload(cancelled) - verifyZeroInteractions(changesetManager, singleChangeUploader, elementDB, undoQuestDB) + verifyZeroInteractions(changesetManager, singleChangeUploader, statisticsUpdater, elementUpdateController, undoQuestDB) } @Test fun `catches ElementConflict exception`() { on(undoQuestDB.getAll()).thenReturn(listOf(createUndoQuest())) on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ElementConflictException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) @@ -72,12 +60,15 @@ class UndoOsmQuestsUploaderTest { @Test fun `discard if element was deleted`() { val q = createUndoQuest() on(undoQuestDB.getAll()).thenReturn(listOf(q)) - on(elementDB.get(any(), anyLong())).thenReturn(null) + on(singleChangeUploader.upload(anyLong(), any(), any())) + .thenThrow(ElementDeletedException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) verify(uploader.uploadedChangeListener)?.onDiscarded(q.osmElementQuestType.javaClass.simpleName, q.position) + verify(elementUpdateController).delete(any(), anyLong()) } @Test fun `catches ChangesetConflictException exception and tries again once`() { @@ -85,6 +76,7 @@ class UndoOsmQuestsUploaderTest { on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ChangesetConflictException()) .thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) @@ -96,10 +88,12 @@ class UndoOsmQuestsUploaderTest { @Test fun `delete each uploaded quest from local DB and calls listener`() { val quests = listOf(createUndoQuest(), createUndoQuest()) + on(undoQuestDB.getAll()).thenReturn(quests) on(singleChangeUploader.upload(anyLong(), any(), any())) .thenThrow(ElementConflictException()) .thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) @@ -108,11 +102,11 @@ class UndoOsmQuestsUploaderTest { verify(uploader.uploadedChangeListener)?.onUploaded(quests[0].osmElementQuestType.javaClass.simpleName, quests[0].position) verify(uploader.uploadedChangeListener)?.onDiscarded(quests[1].osmElementQuestType.javaClass.simpleName, quests[1].position) - verify(elementDB, times(1)).put(any()) - verify(elementGeometryDB, times(1)).put(any()) - verify(questGiver, times(1)).updateQuests(any(), any()) + verify(elementUpdateController, times(1)).update(any(), isNull()) + verify(elementUpdateController, times(1)).cleanUp() + verify(elementUpdateController, times(2)).get(any(), anyLong()) verify(statisticsUpdater).subtractOne(any(), any()) - verifyNoMoreInteractions(questGiver) + verifyNoMoreInteractions(elementUpdateController) } @Test fun `delete unreferenced elements and clean metadata at the end`() { @@ -120,10 +114,11 @@ class UndoOsmQuestsUploaderTest { on(undoQuestDB.getAll()).thenReturn(listOf(quest)) on(singleChangeUploader.upload(anyLong(), any(), any())).thenReturn(createElement()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(mock()) uploader.upload(AtomicBoolean(false)) - verify(elementDB).deleteUnreferenced() + verify(elementUpdateController).cleanUp() verify(quest.osmElementQuestType).cleanMetadata() } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt index 2d43a4f2cc..6f0288b177 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/splitway/SplitWaysUploaderTest.kt @@ -1,64 +1,50 @@ package de.westnordost.streetcomplete.data.osm.splitway -import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.streetcomplete.on import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestGiver -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementUpdateController import de.westnordost.streetcomplete.data.osm.upload.ChangesetConflictException import de.westnordost.streetcomplete.data.osm.upload.ElementConflictException +import de.westnordost.streetcomplete.data.osm.upload.ElementDeletedException import de.westnordost.streetcomplete.data.osm.upload.changesets.OpenQuestChangesetsManager import de.westnordost.streetcomplete.data.user.StatisticsUpdater import de.westnordost.streetcomplete.mock import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers import org.mockito.Mockito.* import java.util.concurrent.atomic.AtomicBoolean class SplitWaysUploaderTest { private lateinit var splitWayDB: OsmQuestSplitWayDao - private lateinit var elementDB: MergedElementDao private lateinit var changesetManager: OpenQuestChangesetsManager - private lateinit var elementGeometryDB: ElementGeometryDao - private lateinit var questGiver: OsmQuestGiver - private lateinit var elementGeometryCreator: ElementGeometryCreator - private lateinit var mapDataApi: MapDataApi private lateinit var splitSingleOsmWayUploader: SplitSingleWayUploader private lateinit var statisticsUpdater: StatisticsUpdater + private lateinit var elementUpdateController: OsmElementUpdateController private lateinit var uploader: SplitWaysUploader @Before fun setUp() { splitWayDB = mock() - elementDB = mock() - on(elementDB.get(any(), ArgumentMatchers.anyLong())).thenReturn(createElement()) changesetManager = mock() splitSingleOsmWayUploader = mock() - elementGeometryDB = mock() - questGiver = mock() - elementGeometryCreator = mock() - mapDataApi = mock() + elementUpdateController = mock() statisticsUpdater = mock() - on(elementGeometryCreator.create(any())).thenReturn(mock()) - uploader = SplitWaysUploader(elementDB, elementGeometryDB, changesetManager, questGiver, - elementGeometryCreator, mapDataApi, splitWayDB, splitSingleOsmWayUploader, statisticsUpdater) + uploader = SplitWaysUploader(changesetManager, elementUpdateController, splitWayDB, + splitSingleOsmWayUploader, statisticsUpdater) } @Test fun `cancel upload works`() { val cancelled = AtomicBoolean(true) uploader.upload(cancelled) - verifyZeroInteractions(changesetManager, splitSingleOsmWayUploader, elementDB, splitWayDB) + verifyZeroInteractions(changesetManager, splitSingleOsmWayUploader, elementUpdateController, statisticsUpdater, splitWayDB) } @Test fun `catches ElementConflict exception`() { on(splitWayDB.getAll()).thenReturn(listOf(createOsmSplitWay())) on(splitSingleOsmWayUploader.upload(anyLong(), any(), anyList())) .thenThrow(ElementConflictException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(createElement()) uploader.upload(AtomicBoolean(false)) @@ -68,7 +54,9 @@ class SplitWaysUploaderTest { @Test fun `discard if element was deleted`() { val q = createOsmSplitWay() on(splitWayDB.getAll()).thenReturn(listOf(q)) - on(elementDB.get(any(),anyLong())).thenReturn(null) + on(splitSingleOsmWayUploader.upload(anyLong(), any(), any())) + .thenThrow(ElementDeletedException()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(createElement()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) @@ -81,6 +69,7 @@ class SplitWaysUploaderTest { on(splitSingleOsmWayUploader.upload(anyLong(), any(), anyList())) .thenThrow(ChangesetConflictException()) .thenReturn(listOf(createElement())) + on(elementUpdateController.get(any(), anyLong())).thenReturn(createElement()) uploader.upload(AtomicBoolean(false)) @@ -92,11 +81,12 @@ class SplitWaysUploaderTest { @Test fun `delete each uploaded split from local DB and calls listener`() { val quests = listOf(createOsmSplitWay(), createOsmSplitWay()) + on(splitWayDB.getAll()).thenReturn(quests) on(splitSingleOsmWayUploader.upload(anyLong(), any(), anyList())) .thenThrow(ElementConflictException()) .thenReturn(listOf(createElement())) - on(elementGeometryCreator.create(any(), any())).thenReturn(mock()) + on(elementUpdateController.get(any(), anyLong())).thenReturn(createElement()) uploader.uploadedChangeListener = mock() uploader.upload(AtomicBoolean(false)) @@ -105,11 +95,11 @@ class SplitWaysUploaderTest { verify(uploader.uploadedChangeListener)?.onUploaded(quests[0].questType.javaClass.simpleName, quests[0].position) verify(uploader.uploadedChangeListener)?.onDiscarded(quests[1].questType.javaClass.simpleName,quests[1].position) - verify(elementDB, times(1)).put(any()) - verify(elementGeometryDB, times(1)).put(any()) - verify(questGiver, times(1)).recreateQuests(any(), any(), any()) + verify(elementUpdateController, times(1)).update(any(), any()) + verify(elementUpdateController).cleanUp() + verify(elementUpdateController, times(2)).get(any(), anyLong()) verify(statisticsUpdater).addOne(any(), any()) - verifyNoMoreInteractions(questGiver) + verifyNoMoreInteractions(elementUpdateController) } @Test fun `delete unreferenced elements and clean metadata at the end`() { @@ -118,10 +108,11 @@ class SplitWaysUploaderTest { on(splitWayDB.getAll()).thenReturn(listOf(quest)) on(splitSingleOsmWayUploader.upload(anyLong(), any(), any())) .thenReturn(listOf(createElement())) + on(elementUpdateController.get(any(), anyLong())).thenReturn(createElement()) uploader.upload(AtomicBoolean(false)) - verify(elementDB).deleteUnreferenced() + verify(elementUpdateController).cleanUp() verify(quest.osmElementQuestType).cleanMetadata() } } From 96f0138f764734901fbfa03b5ff2a1f4197ff237 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 19 Oct 2020 14:56:42 +0200 Subject: [PATCH 18/63] add tests --- .../osmquest/OsmElementUpdateController.kt | 12 +-- .../data/osm/osmquest/OsmQuestController.kt | 10 ++- .../data/osm/osmquest/OsmQuestGiver.kt | 4 +- .../OsmElementUpdateControllerTest.kt | 76 +++++++++++++++++++ .../data/osm/osmquest/OsmQuestGiverTest.kt | 18 ++--- 5 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateControllerTest.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt index 32dc9a6c09..352ec11934 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateController.kt @@ -11,19 +11,14 @@ import de.westnordost.osmapi.map.getWayComplete import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryEntry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import javax.inject.Inject /** When an element has been updated or deleted (from the API), this class takes care of updating - * the data that is dependent on the element - the geometry of the element and the quests based - * on the element */ -// TODO TEST + * the element and the data that is dependent on the element - the quests */ class OsmElementUpdateController @Inject constructor( private val mapDataApi: MapDataApi, private val elementGeometryCreator: ElementGeometryCreator, - private val elementGeometryDB: ElementGeometryDao, private val elementDB: MergedElementDao, private val questGiver: OsmQuestGiver, ){ @@ -35,10 +30,6 @@ class OsmElementUpdateController @Inject constructor( val newGeometry = createGeometry(element) if (newGeometry != null) { elementDB.put(element) - /* need to update element geometry in any case because even if the quest giver unlocks - no new quests, there are still those that currently exist. They need the current - geometry too! */ - elementGeometryDB.put(ElementGeometryEntry(element.type, element.id, newGeometry)) if (recreateQuestTypes == null) { questGiver.updateQuests(element, newGeometry) @@ -53,6 +44,7 @@ class OsmElementUpdateController @Inject constructor( fun delete(elementType: Element.Type, elementId: Long) { elementDB.delete(elementType, elementId) + // geometry is deleted by the osmQuestController questGiver.deleteQuests(elementType, elementId) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt index fa172f137d..d0adf9a490 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt @@ -4,6 +4,7 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.osm.changes.StringMapChanges +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryEntry import de.westnordost.streetcomplete.data.osm.mapdata.ElementKey @@ -130,7 +131,14 @@ import javax.inject.Singleton /** Add new unanswered quests and remove others for the given element, including their linked * geometry. Called when an OSM element is updated, so the quests that reference that element * need to be updated as well. */ - fun updateForElement(added: List, removedIds: List, elementType: Element.Type, elementId: Long): UpdateResult { + fun updateForElement( + added: List, + removedIds: List, + updatedGeometry: ElementGeometry, + elementType: Element.Type, + elementId: Long + ): UpdateResult { + geometryDao.put(ElementGeometryEntry(elementType, elementId, updatedGeometry)) // TODO test val e = ElementKey(elementType, elementId) var deletedCount = removeObsolete(removedIds) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiver.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiver.kt index 6567281835..d7b8a0f647 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiver.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiver.kt @@ -34,7 +34,7 @@ class OsmQuestGiver @Inject constructor( createdQuests.add(quest) createdQuestsLog.add(questType.javaClass.simpleName) } - val updates = osmQuestController.updateForElement(createdQuests, emptyList(), element.type, element.id) + val updates = osmQuestController.updateForElement(createdQuests, emptyList(), geometry, element.type, element.id) Log.d(TAG, "Recreated ${updates.added} quests for ${element.type.name}#${element.id}: ${createdQuestsLog.joinToString()}") } @@ -73,7 +73,7 @@ class OsmQuestGiver @Inject constructor( } } } - val updates = osmQuestController.updateForElement(createdQuests, removedQuestIds, element.type, element.id) + val updates = osmQuestController.updateForElement(createdQuests, removedQuestIds, geometry, element.type, element.id) if (updates.added > 0) { Log.d(TAG, "Created ${updates.added} new quests for ${element.type.name}#${element.id}: ${createdQuestsLog.joinToString()}") diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateControllerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateControllerTest.kt new file mode 100644 index 0000000000..4c82dbb78a --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementUpdateControllerTest.kt @@ -0,0 +1,76 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.on +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +class OsmElementUpdateControllerTest { + + private lateinit var mapDataApi: MapDataApi + private lateinit var elementGeometryCreator: ElementGeometryCreator + private lateinit var elementDB: MergedElementDao + private lateinit var questGiver: OsmQuestGiver + private lateinit var c: OsmElementUpdateController + + @Before fun setUp() { + mapDataApi = mock() + elementGeometryCreator = mock() + elementDB = mock() + questGiver = mock() + c = OsmElementUpdateController(mapDataApi, elementGeometryCreator, elementDB, questGiver) + } + + @Test fun delete() { + c.delete(Element.Type.NODE, 123L) + + verify(elementDB).delete(Element.Type.NODE, 123L) + verify(questGiver).deleteQuests(Element.Type.NODE, 123L) + } + + @Test fun update() { + val element = OsmNode(123L, 1, 0.0, 0.0, null) + val point = ElementPointGeometry(element.position) + + on(elementGeometryCreator.create(element)).thenReturn(point) + + c.update(element, null) + + verify(elementDB).put(element) + verify(elementGeometryCreator).create(element) + verify(questGiver).updateQuests(element, point) + } + + @Test fun `update deleted`() { + val element = OsmNode(123L, 1, 0.0, 0.0, null) + + on(elementGeometryCreator.create(element)).thenReturn(null) + + c.update(element, null) + + verify(elementDB).delete(element.type, element.id) + verify(questGiver).deleteQuests(element.type, element.id) + } + + @Test fun recreate() { + val element = OsmNode(123L, 1, 0.0, 0.0, null) + val point = ElementPointGeometry(element.position) + val questType: OsmElementQuestType = mock() + val questTypes = listOf(questType) + on(elementGeometryCreator.create(element)).thenReturn(point) + + c.update(element, questTypes) + + + verify(elementDB).put(element) + verify(elementGeometryCreator).create(element) + verify(questGiver).recreateQuests(element, point, questTypes) + } +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiverTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiverTest.kt index 5e5a863f4a..5664ddbb17 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiverTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestGiverTest.kt @@ -39,7 +39,7 @@ class OsmQuestGiverTest { osmQuestController = mock() on(osmQuestController.getAllForElement(Element.Type.NODE, 1)).thenReturn(emptyList()) - on(osmQuestController.updateForElement(any(), any(), any(), anyLong())).thenReturn(OsmQuestController.UpdateResult(0,0)) + on(osmQuestController.updateForElement(any(), any(), any(), any(), anyLong())).thenReturn(OsmQuestController.UpdateResult(0,0)) questType = mock() on(questType.enabledInCountries).thenReturn(AllCountries) @@ -61,7 +61,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) - verify(osmQuestController).updateForElement(emptyList(), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(emptyList(), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `previous quest blocks new quest`() { @@ -71,7 +71,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) - verify(osmQuestController).updateForElement(emptyList(), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(emptyList(), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `not applicable blocks new quest`() { @@ -80,7 +80,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) - verify(osmQuestController).updateForElement(emptyList(), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(emptyList(), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `not applicable removes previous quest`() { @@ -92,7 +92,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) - verify(osmQuestController).updateForElement(emptyList(), listOf(123L), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(emptyList(), listOf(123L), GEOM, NODE.type, NODE.id) } @Test fun `applicable adds new quest`() { @@ -100,7 +100,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) val expectedQuest = OsmQuest(questType, NODE.type, NODE.id, GEOM) - verify(osmQuestController).updateForElement(arrayListOf(expectedQuest), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(arrayListOf(expectedQuest), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `quest is only enabled in the country the element is in`() { @@ -110,7 +110,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) val expectedQuest = OsmQuest(questType, NODE.type, NODE.id, GEOM) - verify(osmQuestController).updateForElement(arrayListOf(expectedQuest), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(arrayListOf(expectedQuest), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `quest is disabled in a country the element is not in`() { @@ -119,7 +119,7 @@ class OsmQuestGiverTest { osmQuestGiver.updateQuests(NODE, GEOM) - verify(osmQuestController).updateForElement(emptyList(), emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(emptyList(), emptyList(), GEOM, NODE.type, NODE.id) } @Test fun `recreate quests`() { @@ -130,7 +130,7 @@ class OsmQuestGiverTest { OsmQuest(questType, NODE.type, NODE.id, GEOM), OsmQuest(questType2, NODE.type, NODE.id, GEOM) ) - verify(osmQuestController).updateForElement(expectedQuests, emptyList(), NODE.type, NODE.id) + verify(osmQuestController).updateForElement(expectedQuests, emptyList(), GEOM, NODE.type, NODE.id) } } From 61a45d0c4bd41ba2d6a3457705b290884c160969 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 19 Oct 2020 14:58:43 +0200 Subject: [PATCH 19/63] add comment --- .../data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt index 9cc9785b36..84b862ca72 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt @@ -10,6 +10,7 @@ import de.westnordost.streetcomplete.util.measuredLength import java.util.concurrent.FutureTask import javax.inject.Inject +/** Checks if the given quest type may be created based on its geometry and location */ class ElementEligibleForOsmQuestChecker @Inject constructor( private val countryBoundariesFuture: FutureTask, ) { From a998e1ecbac362064d37cb7f0d5d1854d69e90fb Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 21 Oct 2020 02:59:40 +0200 Subject: [PATCH 20/63] separate MapDataQuests from Downloader quests --- .../java/de/westnordost/osmapi/map/MapData.kt | 70 ++++++-- .../de/westnordost/osmapi/map/MapDataApi.kt | 30 +--- .../data/download/QuestDownloader.kt | 20 +-- .../elementfilter/ElementFilterExpression.kt | 18 +- .../elementgeometry/ElementGeometryCreator.kt | 6 +- .../data/osm/osmquest/OsmApiMapData.kt | 126 ++++++++++++++ .../osm/osmquest/OsmApiQuestDownloader.kt | 60 ++----- .../osm/osmquest/OsmDownloaderQuestType.kt | 13 ++ .../data/osm/osmquest/OsmElementQuestType.kt | 7 - .../data/osm/osmquest/OsmFilterQuestType.kt | 28 +++ .../data/osm/osmquest/OsmMapDataQuestType.kt | 10 ++ .../data/osm/osmquest/OsmQuestDownloader.kt | 2 +- .../osm/osmquest/SimpleOverpassQuestType.kt | 29 ---- .../streetcomplete/quests/QuestModule.kt | 162 +++++++++--------- .../quests/accepts_cash/AddAcceptsCash.kt | 67 ++++---- .../quests/address/AddAddressStreet.kt | 4 +- .../quests/atm_operator/AddAtmOperator.kt | 7 +- .../AddBabyChangingTable.kt | 7 +- .../quests/bench_backrest/AddBenchBackrest.kt | 7 +- .../AddBikeParkingCapacity.kt | 8 +- .../bike_parking_cover/AddBikeParkingCover.kt | 8 +- .../bike_parking_type/AddBikeParkingType.kt | 7 +- .../quests/bikeway/AddCycleway.kt | 3 +- .../quests/board_type/AddBoardType.kt | 7 +- .../bridge_structure/AddBridgeStructure.kt | 7 +- .../building_levels/AddBuildingLevels.kt | 7 +- .../quests/building_type/AddBuildingType.kt | 7 +- .../AddIsBuildingUnderground.kt | 7 +- .../bus_stop_bench/AddBenchStatusOnBusStop.kt | 7 +- .../quests/bus_stop_name/AddBusStopName.kt | 7 +- .../quests/bus_stop_ref/AddBusStopRef.kt | 7 +- .../bus_stop_shelter/AddBusStopShelter.kt | 8 +- .../quests/car_wash_type/AddCarWashType.kt | 7 +- .../AddChargingStationOperator.kt | 8 +- .../AddClothingBinOperator.kt | 23 +-- .../MarkCompletedBuildingConstruction.kt | 8 +- .../MarkCompletedHighwayConstruction.kt | 8 +- .../crossing_island/AddCrossingIsland.kt | 50 +++--- .../quests/crossing_type/AddCrossingType.kt | 8 +- .../defibrillator/AddIsDefibrillatorIndoor.kt | 7 +- .../quests/diet_type/AddVegan.kt | 8 +- .../quests/diet_type/AddVegetarian.kt | 8 +- .../ferry/AddFerryAccessMotorVehicle.kt | 9 +- .../quests/ferry/AddFerryAccessPedestrian.kt | 7 +- .../quests/fire_hydrant/AddFireHydrantType.kt | 7 +- .../foot/AddProhibitedForPedestrians.kt | 7 +- .../quests/general_fee/AddGeneralFee.kt | 7 +- .../quests/handrail/AddHandrail.kt | 8 +- .../quests/housenumber/AddHousenumber.kt | 4 +- .../internet_access/AddInternetAccess.kt | 8 +- .../quests/leaf_detail/AddForestLeafType.kt | 4 +- .../quests/max_height/AddMaxHeight.kt | 24 +-- .../quests/max_speed/AddMaxSpeed.kt | 7 +- .../quests/max_weight/AddMaxWeight.kt | 7 +- .../AddMotorcycleParkingCapacity.kt | 8 +- .../AddMotorcycleParkingCover.kt | 7 +- .../streetcomplete/quests/oneway/AddOneway.kt | 66 +++---- .../oneway_suspects/AddSuspectedOneway.kt | 4 +- .../quests/opening_hours/AddOpeningHours.kt | 21 +-- .../orchard_produce/AddOrchardProduce.kt | 7 +- .../quests/parking_access/AddParkingAccess.kt | 7 +- .../quests/parking_fee/AddParkingFee.kt | 8 +- .../quests/parking_type/AddParkingType.kt | 7 +- .../quests/place_name/AddPlaceName.kt | 22 +-- .../playground_access/AddPlaygroundAccess.kt | 7 +- .../AddPostboxCollectionTimes.kt | 8 +- .../quests/postbox_ref/AddPostboxRef.kt | 7 +- .../AddPowerPolesMaterial.kt | 7 +- .../AddRailwayCrossingBarrier.kt | 61 +++---- .../quests/recycling/AddRecyclingType.kt | 7 +- .../DetermineRecyclingGlass.kt | 8 +- .../AddRecyclingContainerMaterials.kt | 3 +- .../religion/AddReligionToPlaceOfWorship.kt | 7 +- .../religion/AddReligionToWaysideShrine.kt | 7 +- .../quests/road_name/AddRoadName.kt | 4 +- .../quests/roof_shape/AddRoofShape.kt | 7 +- .../segregated/AddCyclewaySegregation.kt | 8 +- .../self_service/AddSelfServiceLaundry.kt | 7 +- .../quests/sidewalk/AddSidewalk.kt | 4 +- .../streetcomplete/quests/sport/AddSport.kt | 7 +- .../quests/step_count/AddStepCount.kt | 8 +- .../quests/steps_incline/AddStepsIncline.kt | 7 +- .../quests/steps_ramp/AddStepsRamp.kt | 8 +- .../summit_register/AddSummitRegister.kt | 4 +- .../quests/surface/AddCyclewayPartSurface.kt | 8 +- .../quests/surface/AddFootwayPartSurface.kt | 8 +- .../quests/surface/AddPathSurface.kt | 8 +- .../quests/surface/AddRoadSurface.kt | 9 +- .../quests/surface/DetailRoadSurface.kt | 7 +- .../tactile_paving/AddTactilePavingBusStop.kt | 8 +- .../AddTactilePavingCrosswalk.kt | 76 ++++---- .../AddToiletAvailability.kt | 7 +- .../quests/toilets_fee/AddToiletsFee.kt | 7 +- .../AddInformationToTourism.kt | 7 +- .../quests/tracktype/AddTracktype.kt | 8 +- .../AddTrafficSignalsButton.kt | 7 +- .../AddTrafficSignalsSound.kt | 8 +- .../AddTrafficSignalsVibration.kt | 9 +- .../quests/way_lit/AddWayLit.kt | 8 +- .../AddWheelchairAccessBusiness.kt | 8 +- .../AddWheelchairAccessOutside.kt | 8 +- .../AddWheelchairAccessPublicTransport.kt | 8 +- .../AddWheelchairAccessToilets.kt | 8 +- .../AddWheelchairAccessToiletsPart.kt | 8 +- .../QuestsOverpassPerformanceMeasurement.kt | 4 +- .../streetcomplete/QuestsOverpassPrinter.kt | 14 +- .../ElementGeometryCreatorTest.kt | 14 +- .../osm/osmquest/OsmQuestDownloaderTest.kt | 2 +- .../SimpleOverpassQuestsValidityTest.kt | 28 --- .../OpenQuestChangesetsManagerTest.kt | 3 - .../quests/AddBuildingLevelsTest.kt | 3 +- .../quests/AddBusStopShelterTest.kt | 2 +- .../quests/AddCrossingTypeTest.kt | 2 +- .../streetcomplete/quests/AddMaxHeightTest.kt | 3 +- .../streetcomplete/quests/AddMaxSpeedTest.kt | 3 +- .../streetcomplete/quests/AddMaxWeightTest.kt | 3 +- .../quests/AddParkingFeeTest.kt | 2 +- .../streetcomplete/quests/AddPlaceNameTest.kt | 2 +- .../quests/AddPostboxCollectionTimesTest.kt | 2 +- .../quests/AddPostboxRefTest.kt | 3 +- .../quests/AddProhibitedForPedestriansTest.kt | 2 +- .../quests/AddRecyclingTypeTest.kt | 3 +- .../streetcomplete/quests/AddSportTest.kt | 2 +- .../quests/OsmDownloaderQuestType.kt | 19 ++ .../quests/OsmElementQuestType.kt | 17 -- .../bus_stop_name/AddBusStopNameTest.kt | 3 +- .../car_wash_type/AddCarWashTypeTest.kt | 3 +- .../AddClothingBinOperatorTest.kt | 3 +- .../quests/oneway/AddOnewayTest.kt | 89 +++++----- .../opening_hours/AddOpeningHoursTest.kt | 2 +- .../quests/steps_ramp/AddStepsRampTest.kt | 2 +- 131 files changed, 852 insertions(+), 948 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestType.kt delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestsValidityTest.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt index 6a201c5512..d58d92a28b 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -2,29 +2,63 @@ package de.westnordost.osmapi.map import de.westnordost.osmapi.map.data.* import de.westnordost.osmapi.map.handler.MapDataHandler +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.util.MultiIterable -data class MapData( - val nodes: MutableMap = mutableMapOf(), - val ways: MutableMap = mutableMapOf(), - val relations: MutableMap = mutableMapOf()) : MapDataHandler, Iterable { +interface MapDataWithGeometry : MapData { + fun getNodeGeometry(id: Long): ElementPointGeometry? + fun getWayGeometry(id: Long): ElementGeometry? + fun getRelationGeometry(id: Long): ElementGeometry? +} - override fun handle(bounds: BoundingBox) {} - override fun handle(node: Node) { nodes[node.id] = node } - override fun handle(way: Way) { ways[way.id] = way } - override fun handle(relation: Relation) { relations[relation.id] = relation } +interface MapData : Iterable { + val nodes: Collection + val ways: Collection + val relations: Collection + val boundingBox: BoundingBox? + + fun getNode(id: Long): Node? + fun getWay(id: Long): Way? + fun getRelation(id: Long): Relation? +} + +open class MutableMapData : MapData, MapDataHandler { + + protected val nodesById: MutableMap = mutableMapOf() + protected val waysById: MutableMap = mutableMapOf() + protected val relationsById: MutableMap = mutableMapOf() + override var boundingBox: BoundingBox? = null + protected set + + override fun handle(bounds: BoundingBox) { boundingBox = bounds } + override fun handle(node: Node) { nodesById[node.id] = node } + override fun handle(way: Way) { waysById[way.id] = way } + override fun handle(relation: Relation) { relationsById[relation.id] = relation } + + override val nodes get() = nodesById.values + override val ways get() = waysById.values + override val relations get() = relationsById.values + + override fun getNode(id: Long) = nodesById[id] + override fun getWay(id: Long) = waysById[id] + override fun getRelation(id: Long) = relationsById[id] + + fun addAll(elements: Iterable) { + for (element in elements) { + when(element) { + is Node -> nodesById[element.id] = element + is Way -> waysById[element.id] = element + is Relation -> relationsById[element.id] = element + } + } + } override fun iterator(): Iterator { val elements = MultiIterable() - elements.add(nodes.values) - elements.add(ways.values) - elements.add(relations.values) + elements.add(nodes) + elements.add(ways) + elements.add(relations) return elements.iterator() } - - fun add(other: MapData) { - nodes.putAll(other.nodes) - ways.putAll(other.ways) - relations.putAll(other.relations) - } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt index d5c135c281..4596e00808 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapDataApi.kt @@ -1,46 +1,22 @@ package de.westnordost.osmapi.map -import de.westnordost.osmapi.common.errors.OsmQueryTooBigException import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.streetcomplete.data.MapDataApi fun MapDataApi.getMap(bounds: BoundingBox): MapData { - val result = MapData() + val result = MutableMapData() getMap(bounds, result) return result } fun MapDataApi.getWayComplete(id: Long): MapData { - val result = MapData() + val result = MutableMapData() getWayComplete(id, result) return result } fun MapDataApi.getRelationComplete(id: Long): MapData { - val result = MapData() + val result = MutableMapData() getRelationComplete(id, result) return result } - -fun MapDataApi.getMapAndHandleTooBigQuery(bounds: BoundingBox): MapData { - val result = MapData() - try { - getMap(bounds, result) - } catch (e : OsmQueryTooBigException) { - for (subBounds in bounds.splitIntoFour()) { - result.add(getMap(subBounds)) - } - } - return result -} - -fun BoundingBox.splitIntoFour(): List { - val center = OsmLatLon((maxLatitude + minLatitude) / 2, (maxLongitude + minLongitude) / 2) - return listOf( - BoundingBox(minLatitude, minLongitude, center.latitude, center.longitude), - BoundingBox(minLatitude, center.longitude, center.latitude, maxLongitude), - BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), - BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) - ) -} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index dde68c3755..dadd015329 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -6,12 +6,12 @@ import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestDownloader import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType import de.westnordost.streetcomplete.data.osmnotes.OsmNotesDownloader import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao +import de.westnordost.streetcomplete.data.osm.osmquest.* import de.westnordost.streetcomplete.data.osm.osmquest.OsmApiQuestDownloader +import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestDownloader import de.westnordost.streetcomplete.data.user.UserStore import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider import de.westnordost.streetcomplete.util.TilesRect @@ -74,14 +74,14 @@ class QuestDownloader @Inject constructor( if (cancelState.get()) return // download multiple quests at once - val downloadedQuestTypes = downloadMultipleOsmQuestTypes(bbox, tiles) + val downloadedQuestTypes = downloadOsmMapDataQuestTypes(bbox, tiles) questTypes.removeAll(downloadedQuestTypes) if (questTypes.isEmpty()) return if (cancelState.get()) return // download remaining quests that haven't been downloaded in the previous step - val remainingOsmElementQuestTypes = questTypes.filterIsInstance>() + val remainingOsmElementQuestTypes = questTypes.filterIsInstance>() for (questType in remainingOsmElementQuestTypes) { if (cancelState.get()) break downloadOsmQuestType(bbox, tiles, questType) @@ -121,7 +121,7 @@ class QuestDownloader @Inject constructor( progressListener?.onFinished(noteQuestType.toDownloadItem()) } - private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmElementQuestType<*>) { + private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmDownloaderQuestType<*>) { progressListener?.onStarted(questType.toDownloadItem()) val questDownload = osmQuestDownloaderProvider.get() if (questDownload.download(questType, bbox)) { @@ -130,16 +130,16 @@ class QuestDownloader @Inject constructor( progressListener?.onFinished(questType.toDownloadItem()) } - private fun downloadMultipleOsmQuestTypes(bbox: BoundingBox, tiles: TilesRect): List> { + private fun downloadOsmMapDataQuestTypes(bbox: BoundingBox, tiles: TilesRect): List> { val downloadItem = DownloadItem(R.drawable.ic_search_black_128dp, "Multi download") progressListener?.onStarted(downloadItem) // Since we query all the data at once, we can also do the downloading for quests not on our list. - val questTypes = questTypesProvider.get().filterIsInstance>() + val questTypes = questTypesProvider.get().filterIsInstance>() val questDownload = osmApiQuestDownloaderProvider.get() - val downloadedQuestTypes = questDownload.download(questTypes, bbox) - downloadedTilesDao.putAll(tiles, downloadedQuestTypes.map { it.javaClass.simpleName }) + questDownload.download(questTypes, bbox) + downloadedTilesDao.putAll(tiles, questTypes.map { it.javaClass.simpleName }) progressListener?.onFinished(downloadItem) - return downloadedQuestTypes + return questTypes } companion object { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt index 50cd8e8204..a3bd9ee4b4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFilterExpression.kt @@ -14,17 +14,19 @@ class ElementFilterExpression( private val elementExprRoot: BooleanExpression? ) { /** returns whether the given element is found through (=matches) this expression */ - fun matches(element: Element) = - when (element.type) { - Element.Type.NODE -> elementsTypes.contains(NODES) - Element.Type.WAY -> elementsTypes.contains(WAYS) - Element.Type.RELATION -> elementsTypes.contains(RELATIONS) - else -> false - } && (elementExprRoot?.matches(element) ?: true) + fun matches(element: Element): Boolean = + includesElementType(element.type) && (elementExprRoot?.matches(element) ?: true) + + fun includesElementType(elementType: Element.Type): Boolean = when (elementType) { + Element.Type.NODE -> elementsTypes.contains(NODES) + Element.Type.WAY -> elementsTypes.contains(WAYS) + Element.Type.RELATION -> elementsTypes.contains(RELATIONS) + else -> false + } /** returns this expression as a Overpass query string */ fun toOverpassQLString(): String = OverpassQueryCreator(elementsTypes, elementExprRoot).create() } /** Enum that specifies which type(s) of elements to retrieve */ -enum class ElementsTypeFilter { NODES, WAYS, RELATIONS } \ No newline at end of file +enum class ElementsTypeFilter { NODES, WAYS, RELATIONS } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt index 0b76b86236..d486ec784e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt @@ -233,7 +233,7 @@ private fun MutableList.eliminateDuplicates() { private fun MapData.getNodePositions(way: Way): List? { return way.nodeIds.map { nodeId -> - val node = nodes[nodeId] ?: return null + val node = getNode(nodeId) ?: return null node.position } } @@ -241,9 +241,9 @@ private fun MapData.getNodePositions(way: Way): List? { private fun MapData.getWaysNodePositions(relation: Relation): Map>? { val wayMembers = relation.members.filter { it.type == Element.Type.WAY } return wayMembers.associate { wayMember -> - val way = ways[wayMember.ref] ?: return null + val way = getWay(wayMember.ref) ?: return null val wayPositions = way.nodeIds.map { nodeId -> - val node = nodes[nodeId] ?: return null + val node = getNode(nodeId) ?: return null node.position } way.id to wayPositions diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt new file mode 100644 index 0000000000..bd361f6fe8 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt @@ -0,0 +1,126 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.common.errors.OsmQueryTooBigException +import de.westnordost.osmapi.map.* +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Element.Type.* +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import javax.inject.Inject + +// TODO TEST + +// TODO thread safety! - should be safe if it is going to be accessed by a thread pool (which makes sense) +/** MapDataWithGeometry that fetches the necessary data from the OSM API itself */ +class OsmApiMapData @Inject constructor( + private val mapDataApi: MapDataApi, + private val elementGeometryCreator: ElementGeometryCreator +) : MutableMapData(), MapDataWithGeometry { + + private val nodeGeometriesById: MutableMap = mutableMapOf() + private val wayGeometriesById: MutableMap = mutableMapOf() + private val relationGeometriesById: MutableMap = mutableMapOf() + + /* caching which ways and relations are complete so they do not need to be checked every time + they are accessed */ + private val completeWays: MutableSet = mutableSetOf() + private val completeRelations: MutableSet = mutableSetOf() + + /* overridden because the publicly accessible bounding box should be the bbox of the initial + download, not of any consecutive ones */ + override var boundingBox: BoundingBox? = null + + fun initWith(boundingBox: BoundingBox) { + check(this.boundingBox == null) + this.boundingBox = boundingBox + + getMapAndHandleTooBigQuery(boundingBox) + // we know that all ways included in the initial download are complete + completeWays.addAll(waysById.keys) + } + + private fun getMapAndHandleTooBigQuery(bounds: BoundingBox) { + try { + mapDataApi.getMap(bounds, this) + } catch (e : OsmQueryTooBigException) { + for (subBounds in bounds.splitIntoFour()) { + getMapAndHandleTooBigQuery(subBounds) + } + } + } + + override fun getNodeGeometry(id: Long): ElementPointGeometry? { + if (!nodesById.containsKey(id)) return null + return nodeGeometriesById.getOrPut(id) { + elementGeometryCreator.create(nodesById.getValue(id)) + } + } + + override fun getWayGeometry(id: Long): ElementGeometry? { + if (!waysById.containsKey(id)) return null + return wayGeometriesById.getOrPut(id) { + ensureWayIsComplete(id) + elementGeometryCreator.create(waysById.getValue(id), this) + } + } + + override fun getRelationGeometry(id: Long): ElementGeometry? { + if (!relationsById.containsKey(id)) return null + return relationGeometriesById.getOrPut(id) { + ensureRelationIsComplete(id) + elementGeometryCreator.create(relationsById.getValue(id), this) + } + } + + fun getGeometry(elementType: Element.Type, id: Long): ElementGeometry? = when(elementType) { + NODE -> getNodeGeometry(id) + WAY -> getWayGeometry(id) + RELATION -> getRelationGeometry(id) + } + + private fun ensureRelationIsComplete(id: Long) { + /* conditionally need to fetch from OSM API here because the nodes and ways of relations + are not included in the normal map download call if not all are in the bbox */ + if (!completeRelations.contains(id)) { + if (!isRelationComplete(id)) mapDataApi.getRelationComplete(id, this) + completeRelations.add(id) + } + } + + private fun ensureWayIsComplete(id: Long) { + /* conditionally need to fetch additional data from OSM API here */ + if (!completeWays.contains(id)) { + if (!isWayComplete(id)) mapDataApi.getWayComplete(id, this) + completeWays.add(id) + } + } + + private fun isRelationComplete(id: Long): Boolean = + relationsById.getValue(id).members.all { + when (it.type!!) { + NODE -> nodesById.containsKey(it.ref) + WAY -> waysById.containsKey(it.ref) && isWayComplete(it.ref) + /* not being recursive here is deliberate. sub-relations are considered not relevant + for the element geometry in StreetComplete */ + RELATION -> relationsById.containsKey(it.ref) + } + } + + private fun isWayComplete(id: Long): Boolean = + waysById.getValue(id).nodeIds.all { nodesById.containsKey(it) } + +} + +private fun BoundingBox.splitIntoFour(): List { + val center = OsmLatLon((maxLatitude + minLatitude) / 2, (maxLongitude + minLongitude) / 2) + return listOf( + BoundingBox(minLatitude, minLongitude, center.latitude, center.longitude), + BoundingBox(minLatitude, center.longitude, center.latitude, maxLongitude), + BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), + BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) + ) +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index d2b95a964c..a0230c3dc3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -7,16 +7,13 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.LatLon import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.osmapi.map.getMapAndHandleTooBigQuery -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource import de.westnordost.streetcomplete.data.quest.QuestType import java.util.* import java.util.concurrent.FutureTask import javax.inject.Inject +import javax.inject.Provider import kotlin.collections.ArrayList /** Does one API call to get all the map data and generates quests from that. Calls isApplicable @@ -26,22 +23,17 @@ class OsmApiQuestDownloader @Inject constructor( private val osmQuestController: OsmQuestController, private val countryBoundariesFuture: FutureTask, private val notePositionsSource: NotePositionsSource, - private val mapDataApi: MapDataApi, - private val elementGeometryCreator: ElementGeometryCreator, + private val osmApiMapDataProvider: Provider, private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker ) { // TODO TEST - fun download(questTypes: List>, bbox: BoundingBox): List> { - val skippedQuestTypes = mutableSetOf>() + fun download(questTypes: List>, bbox: BoundingBox) { + if (questTypes.isEmpty()) return var time = System.currentTimeMillis() - val mapData = mapDataApi.getMapAndHandleTooBigQuery(bbox) - - val elementGeometries = EnumMap>(Element.Type::class.java) - elementGeometries[Element.Type.NODE] = mutableMapOf() - elementGeometries[Element.Type.WAY] = mutableMapOf() - elementGeometries[Element.Type.RELATION] = mutableMapOf() + val mapData = osmApiMapDataProvider.get() + mapData.initWith(bbox) val quests = ArrayList() val questElements = HashSet() @@ -53,6 +45,7 @@ class OsmApiQuestDownloader @Inject constructor( val truncatedBlacklistedPositions = notePositionsSource.getAllPositions(bbox).map { it.truncateTo5Decimals() }.toSet() for (questType in questTypes) { + // TODO multithreading! val questTypeName = questType.getName() val countries = questType.enabledInCountries @@ -61,18 +54,8 @@ class OsmApiQuestDownloader @Inject constructor( continue } - for (element in mapData) { - val appliesToElement = questType.isApplicableTo(element) - if (appliesToElement == null) { - skippedQuestTypes.add(questType) - break - } - if (!appliesToElement) continue - if (!elementGeometries[element.type]!!.containsKey(element.id)) { - val geometry = elementGeometryCreator.create(element, mapData) ?: continue - elementGeometries[element.type]!![element.id] = geometry - } - val geometry = elementGeometries[element.type]!![element.id] + for (element in questType.getApplicableElements(mapData)) { + val geometry = mapData.getGeometry(element.type, element.id) if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) continue val quest = OsmQuest(questType, element.type, element.id, geometry!!) @@ -81,30 +64,21 @@ class OsmApiQuestDownloader @Inject constructor( questElements.add(element) } } - val downloadedQuestTypes = questTypes.filterNot { skippedQuestTypes.contains(it) } - val downloadedQuestTypeNames = downloadedQuestTypes.map { it.getName() } - val secondsSpentAnalyzing = (System.currentTimeMillis() - time) / 1000 - if (downloadedQuestTypeNames.isNotEmpty()) { - - // elements must be put into DB first because quests have foreign keys on it - elementDB.putAll(questElements) + // elements must be put into DB first because quests have foreign keys on it + elementDB.putAll(questElements) - val replaceResult = osmQuestController.replaceInBBox(quests, bbox, downloadedQuestTypeNames) + val questTypeNames = questTypes.map { it.getName() } + val replaceResult = osmQuestController.replaceInBBox(quests, bbox, questTypeNames) - elementDB.deleteUnreferenced() + elementDB.deleteUnreferenced() - for (questType in downloadedQuestTypes) { - questType.cleanMetadata() - } - - Log.i(TAG,"${downloadedQuestTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") - } else { - Log.i(TAG,"Added and removed no quests because no quest types were downloaded, in ${secondsSpentAnalyzing}s") + for (questType in questTypes) { + questType.cleanMetadata() } - return downloadedQuestTypes + Log.i(TAG,"${questTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") } companion object { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt new file mode 100644 index 0000000000..5665c49460 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt @@ -0,0 +1,13 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry + +/** Quest type based on OSM data which downloads the necessary data to create quests itself */ +interface OsmDownloaderQuestType : OsmElementQuestType { + + /** Downloads map data for this quest type for the given [bbox] and puts the received data into + * the [handler]. Returns whether the download was successful */ + fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt index ac9c6f1a54..ecf7e2fd14 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt @@ -1,11 +1,9 @@ package de.westnordost.streetcomplete.data.osm.osmquest -import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.quest.AllCountries import de.westnordost.streetcomplete.data.quest.Countries -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder /** Quest type where each quest refers to an OSM element */ @@ -38,11 +36,6 @@ interface OsmElementQuestType : QuestType { override val title: Int get() = getTitle(emptyMap()) - /** Downloads map data for this quest type for the given [bbox] and puts the received data into - * the [handler]. Returns whether the download was successful - */ - fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean - /** returns whether a quest of this quest type could be created out of the given [element]. If the * element alone does not suffice to find this out (but e.g. an Overpass query would need to be * made to find this out), this should return null. diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt new file mode 100644 index 0000000000..eab4772243 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt @@ -0,0 +1,28 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.util.MultiIterable + +/** Quest type that's based on a simple element filter expression */ +abstract class OsmFilterQuestType : OsmMapDataQuestType { + + val filter by lazy { ElementFiltersParser().parse(elementFilter) } + + protected abstract val elementFilter: String + + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + /* this is a considerate performance improvement over just iterating over the whole MapData + * because for quests that only filter for one (or two) element types, any filter checks + * are completely avoided */ + val iterable = MultiIterable() + if (filter.includesElementType(Element.Type.NODE)) iterable.add(mapData.nodes) + if (filter.includesElementType(Element.Type.WAY)) iterable.add(mapData.ways) + if (filter.includesElementType(Element.Type.RELATION)) iterable.add(mapData.relations) + return iterable.filter { element -> filter.matches(element) } + + } + + override fun isApplicableTo(element: Element) = filter.matches(element) +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt new file mode 100644 index 0000000000..149319f685 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt @@ -0,0 +1,10 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.data.Element + +/** Quest type based on OSM data whose quests can be created by looking at a MapData */ +interface OsmMapDataQuestType : OsmElementQuestType { + /** return all elements within the given map data that are applicable to this quest type. */ + fun getApplicableElements(mapData: MapDataWithGeometry): List +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index 5858bf7cfe..d89a15c7c8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -29,7 +29,7 @@ class OsmQuestDownloader @Inject constructor( ) { private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() - fun download(questType: OsmElementQuestType<*>, bbox: BoundingBox): Boolean { + fun download(questType: OsmDownloaderQuestType<*>, bbox: BoundingBox): Boolean { val questTypeName = questType.getName() val countries = questType.enabledInCountries diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestType.kt deleted file mode 100644 index 6273671a9e..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestType.kt +++ /dev/null @@ -1,29 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox - -/** Quest type that simply makes a certain overpass query using tag filters and creates quests for - * every element received */ -abstract class SimpleOverpassQuestType( - private val overpassApi: OverpassMapDataAndGeometryApi -) : OsmElementQuestType { - - private val filter by lazy { ElementFiltersParser().parse(tagFilters) } - - abstract val tagFilters: String - - fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + filter.toOverpassQLString() + getQuestPrintStatement() - - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox), handler) - } - - override fun isApplicableTo(element: Element) = filter.matches(element) -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index ab685a90b7..e64306e38c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -113,108 +113,108 @@ object QuestModule // ↓ 2. important data that is used by many data consumers AddRoadName(o, roadNameSuggestionsDao), - AddPlaceName(o, featureDictionaryFuture), - AddOneway(o), + AddPlaceName(featureDictionaryFuture), + AddOneway(), AddSuspectedOneway(o, trafficFlowSegmentsApi, trafficFlowDao), - AddBusStopName(o), - AddBusStopRef(o), - AddIsBuildingUnderground(o), //to avoid asking AddHousenumber and other for underground buildings + AddBusStopName(), + AddBusStopRef(), + AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings AddHousenumber(o), AddAddressStreet(o, roadNameSuggestionsDao), - MarkCompletedHighwayConstruction(o, r), - AddReligionToPlaceOfWorship(o), // icons on maps are different - OSM Carto, mapy.cz, OsmAnd, Sputnik etc - AddParkingAccess(o), //OSM Carto, mapy.cz, OSMand, Sputnik etc + MarkCompletedHighwayConstruction(r), + AddReligionToPlaceOfWorship(), // icons on maps are different - OSM Carto, mapy.cz, OsmAnd, Sputnik etc + AddParkingAccess(), //OSM Carto, mapy.cz, OSMand, Sputnik etc // ↓ 3. useful data that is used by some data consumers - AddRecyclingType(o), + AddRecyclingType(), AddRecyclingContainerMaterials(o, r), - AddSport(o), - AddRoadSurface(o, r), // used by BRouter, OsmAnd, OSRM, graphhopper, HOT map style - AddMaxSpeed(o), // should best be after road surface because it excludes unpaved roads - AddMaxHeight(o), // OSRM and other routing engines - AddRailwayCrossingBarrier(o, r), // useful for routing - AddPostboxCollectionTimes(o, r), - AddOpeningHours(o, featureDictionaryFuture, r), - DetailRoadSurface(o), // used by BRouter, OsmAnd, OSRM, graphhopper - AddBikeParkingCapacity(o, r), // used by cycle map layer on osm.org, OsmAnd - AddOrchardProduce(o), - AddBuildingType(o), // because housenumber, building levels etc. depend on it + AddSport(), + AddRoadSurface(r), // used by BRouter, OsmAnd, OSRM, graphhopper, HOT map style + AddMaxSpeed(), // should best be after road surface because it excludes unpaved roads + AddMaxHeight(), // OSRM and other routing engines + AddRailwayCrossingBarrier(r), // useful for routing + AddPostboxCollectionTimes(r), + AddOpeningHours(featureDictionaryFuture, r), + DetailRoadSurface(), // used by BRouter, OsmAnd, OSRM, graphhopper + AddBikeParkingCapacity(r), // used by cycle map layer on osm.org, OsmAnd + AddOrchardProduce(), + AddBuildingType(), // because housenumber, building levels etc. depend on it AddCycleway(o,r), // SLOW QUERY AddSidewalk(o), // SLOW QUERY - AddProhibitedForPedestrians(o), // uses info from AddSidewalk quest, should be after it - AddCrossingType(o, r), - AddCrossingIsland(o), - AddBuildingLevels(o), - AddBusStopShelter(o, r), // at least OsmAnd - AddVegetarian(o, r), - AddVegan(o, r), - AddInternetAccess(o, r), // used by OsmAnd - AddParkingFee(o, r), // used by OsmAnd - AddMotorcycleParkingCapacity(o, r), - AddPathSurface(o, r), // used by OSM Carto, OsmAnd - AddTracktype(o, r), // widely used in map rendering - OSM Carto, OsmAnd... - AddMaxWeight(o), // used by OSRM and other routing engines + AddProhibitedForPedestrians(), // uses info from AddSidewalk quest, should be after it + AddCrossingType(r), + AddCrossingIsland(), + AddBuildingLevels(), + AddBusStopShelter(r), // at least OsmAnd + AddVegetarian(r), + AddVegan(r), + AddInternetAccess(r), // used by OsmAnd + AddParkingFee(r), // used by OsmAnd + AddMotorcycleParkingCapacity(r), + AddPathSurface(r), // used by OSM Carto, OsmAnd + AddTracktype(r), // widely used in map rendering - OSM Carto, OsmAnd... + AddMaxWeight(), // used by OSRM and other routing engines AddForestLeafType(o), // used by OSM Carto - AddBikeParkingType(o), // used by OsmAnd - AddStepsRamp(o, r), - AddWheelchairAccessToilets(o, r), // used by wheelmap, OsmAnd, MAPS.ME - AddPlaygroundAccess(o), //late as in many areas all needed access=private is already mapped - AddWheelchairAccessBusiness(o, featureDictionaryFuture), // used by wheelmap, OsmAnd, MAPS.ME - AddToiletAvailability(o), //OSM Carto, shown in OsmAnd descriptions - AddFerryAccessPedestrian(o), - AddFerryAccessMotorVehicle(o), - AddAcceptsCash(o, featureDictionaryFuture), + AddBikeParkingType(), // used by OsmAnd + AddStepsRamp(r), + AddWheelchairAccessToilets(r), // used by wheelmap, OsmAnd, MAPS.ME + AddPlaygroundAccess(), //late as in many areas all needed access=private is already mapped + AddWheelchairAccessBusiness(featureDictionaryFuture), // used by wheelmap, OsmAnd, MAPS.ME + AddToiletAvailability(), //OSM Carto, shown in OsmAnd descriptions + AddFerryAccessPedestrian(), + AddFerryAccessMotorVehicle(), + AddAcceptsCash(featureDictionaryFuture), // ↓ 4. definitely shown as errors in QA tools // ↓ 5. may be shown as missing in QA tools - DetermineRecyclingGlass(o), // because most recycling:glass=yes is a tagging mistake + DetermineRecyclingGlass(), // because most recycling:glass=yes is a tagging mistake // ↓ 6. may be shown as possibly missing in QA tools // ↓ 7. data useful for only a specific use case - AddWayLit(o, r), // used by OsmAnd if "Street lighting" is enabled. (Configure map, Map rendering, Details) - AddToiletsFee(o), // used by OsmAnd in the object description - AddBabyChangingTable(o), // used by OsmAnd in the object description - AddBikeParkingCover(o), // used by OsmAnd in the object description - AddTactilePavingCrosswalk(o, r), // Paving can be completed while waiting to cross - AddTrafficSignalsSound(o, r), // Sound needs to be done as or after you're crossing - AddTrafficSignalsVibration(o, r), - AddRoofShape(o), - AddWheelchairAccessPublicTransport(o, r), - AddWheelchairAccessOutside(o, r), - AddTactilePavingBusStop(o, r), - AddBridgeStructure(o), - AddReligionToWaysideShrine(o), - AddCyclewaySegregation(o, r), - MarkCompletedBuildingConstruction(o, r), - AddGeneralFee(o), - AddSelfServiceLaundry(o), - AddStepsIncline(o), // can be gathered while walking perpendicular to the way e.g. the other side of the road or when running/cycling past - AddHandrail(o, r), // for accessibility of pedestrian routing, can be gathered when walking past - AddStepCount(o), // can only be gathered when walking along this way, also needs the most effort and least useful - AddInformationToTourism(o), - AddAtmOperator(o), - AddChargingStationOperator(o), - AddClothingBinOperator(o), + AddWayLit(r), // used by OsmAnd if "Street lighting" is enabled. (Configure map, Map rendering, Details) + AddToiletsFee(), // used by OsmAnd in the object description + AddBabyChangingTable(), // used by OsmAnd in the object description + AddBikeParkingCover(), // used by OsmAnd in the object description + AddTactilePavingCrosswalk(r), // Paving can be completed while waiting to cross + AddTrafficSignalsSound(r), // Sound needs to be done as or after you're crossing + AddTrafficSignalsVibration(r), + AddRoofShape(), + AddWheelchairAccessPublicTransport(r), + AddWheelchairAccessOutside(r), + AddTactilePavingBusStop(r), + AddBridgeStructure(), + AddReligionToWaysideShrine(), + AddCyclewaySegregation(r), + MarkCompletedBuildingConstruction(r), + AddGeneralFee(), + AddSelfServiceLaundry(), + AddStepsIncline(), // can be gathered while walking perpendicular to the way e.g. the other side of the road or when running/cycling past + AddHandrail(r), // for accessibility of pedestrian routing, can be gathered when walking past + AddStepCount(), // can only be gathered when walking along this way, also needs the most effort and least useful + AddInformationToTourism(), + AddAtmOperator(), + AddChargingStationOperator(), + AddClothingBinOperator(), // ↓ 8. defined in the wiki, but not really used by anyone yet. Just collected for // the sake of mapping it in case it makes sense later - AddIsDefibrillatorIndoor(o), + AddIsDefibrillatorIndoor(), AddSummitRegister(o, r), - AddCyclewayPartSurface(o, r), - AddFootwayPartSurface(o, r), - AddMotorcycleParkingCover(o), - AddFireHydrantType(o), - AddParkingType(o), - AddPostboxRef(o), - AddWheelchairAccessToiletsPart(o, r), - AddBoardType(o), - AddPowerPolesMaterial(o), - AddCarWashType(o), - AddBenchStatusOnBusStop(o, r), - AddBenchBackrest(o), - AddTrafficSignalsButton(o) + AddCyclewayPartSurface(r), + AddFootwayPartSurface(r), + AddMotorcycleParkingCover(), + AddFireHydrantType(), + AddParkingType(), + AddPostboxRef(), + AddWheelchairAccessToiletsPart(r), + AddBoardType(), + AddPowerPolesMaterial(), + AddCarWashType(), + AddBenchStatusOnBusStop(r), + AddBenchBackrest(), + AddTrafficSignalsButton() )) @Provides @Singleton fun osmNoteQuestType(): OsmNoteQuestType = OsmNoteQuestType() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt index 74979a5f87..b76d742d28 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/accepts_cash/AddAcceptsCash.kt @@ -3,59 +3,56 @@ package de.westnordost.streetcomplete.quests.accepts_cash import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import java.util.concurrent.FutureTask class AddAcceptsCash( - o: OverpassMapDataAndGeometryApi, private val featureDictionaryFuture: FutureTask -) : SimpleOverpassQuestType(o) { - private val amenity = listOf( - "bar", "cafe", "fast_food", "food_court", "ice_cream", "pub", "biergarten", - "restaurant", "cinema", "nightclub", "planetarium", "theatre", "marketplace", - "internet_cafe", "car_wash", "fuel", "pharmacy", "telephone", "vending_machine" - ) - private val tourismWithImpliedFees = listOf( - "zoo", "aquarium", "theme_park", "hotel", "hostel", "motel", "guest_house", - "apartment", "camp_site" - ) - private val tourismWithoutImpliedFees = listOf( - "attraction", "museum", "gallery" - ) - private val leisure = listOf( - "adult_gaming_centre", "amusement_arcade", "bowling_alley", "escape_game", "miniature_golf", - "sauna", "trampoline_park", "tanning_salon" - ) - private val craft = listOf( - "carpenter", "shoemaker", "tailor", "photographer", "dressmaker", - "electronics_repair", "key_cutter", "stonemason" - ) +) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter: String get() { + val amenities = listOf( + "bar", "cafe", "fast_food", "food_court", "ice_cream", "pub", "biergarten", + "restaurant", "cinema", "nightclub", "planetarium", "theatre", "marketplace", + "internet_cafe", "car_wash", "fuel", "pharmacy", "telephone", "vending_machine" + ) + val tourismsWithImpliedFees = listOf( + "zoo", "aquarium", "theme_park", "hotel", "hostel", "motel", "guest_house", + "apartment", "camp_site" + ) + val tourismsWithoutImpliedFees = listOf( + "attraction", "museum", "gallery" + ) + val leisures = listOf( + "adult_gaming_centre", "amusement_arcade", "bowling_alley", "escape_game", "miniature_golf", + "sauna", "trampoline_park", "tanning_salon" + ) + val crafts = listOf( + "carpenter", "shoemaker", "tailor", "photographer", "dressmaker", + "electronics_repair", "key_cutter", "stonemason" + ) + return """ nodes, ways, relations with ( (shop and shop !~ no|vacant|mall) - or amenity ~ ${amenity.joinToString("|")} - or leisure ~ ${leisure.joinToString("|")} - or craft ~ ${craft.joinToString("|")} - or tourism ~ ${tourismWithImpliedFees.joinToString("|")} - or tourism ~ ${tourismWithoutImpliedFees.joinToString("|")} and fee = yes + or amenity ~ ${amenities.joinToString("|")} + or leisure ~ ${leisures.joinToString("|")} + or craft ~ ${crafts.joinToString("|")} + or tourism ~ ${tourismsWithImpliedFees.joinToString("|")} + or tourism ~ ${tourismsWithoutImpliedFees.joinToString("|")} and fee = yes ) and name and !payment:cash and !payment:coins and !payment:notes - """ + """} + override val commitMessage = "Add whether this place accepts cash as payment" override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside override val wikiLink = "Key:payment" override val icon = R.drawable.ic_quest_cash - override val enabledInCountries = NoCountriesExcept( - // Europe - "SE" - ) + override val enabledInCountries = NoCountriesExcept("SE") override fun getTitle(tags: Map) = if (hasFeatureName(tags) && !tags.containsKey("brand")) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index e6b7367b9d..eb7b3e8788 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -7,17 +7,17 @@ import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion class AddAddressStreet( private val overpassApi: OverpassMapDataAndGeometryApi, private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmElementQuestType { +) : OsmDownloaderQuestType { override val commitMessage = "Add street/place names to address" override val icon = R.drawable.ic_quest_housenumber_street diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperator.kt index 6d06d207bb..415884e00e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperator.kt @@ -2,12 +2,11 @@ package de.westnordost.streetcomplete.quests.atm_operator import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType -class AddAtmOperator(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddAtmOperator : OsmFilterQuestType() { - override val tagFilters = "nodes with amenity = atm and !operator and !name and !brand" + override val elementFilter = "nodes with amenity = atm and !operator and !name and !brand" override val commitMessage = "Add ATM operator" override val wikiLink = "Tag:amenity=atm" override val icon = R.drawable.ic_quest_money diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt index 3cfd4ab3f6..5e987d2b75 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/baby_changing_table/AddBabyChangingTable.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.baby_changing_table import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddBabyChangingTable(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBabyChangingTable : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with ( ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bench_backrest/AddBenchBackrest.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bench_backrest/AddBenchBackrest.kt index cc0ea6547e..19adf40e9f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bench_backrest/AddBenchBackrest.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bench_backrest/AddBenchBackrest.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.bench_backrest import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.bench_backrest.BenchBackrestAnswer.* -class AddBenchBackrest(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBenchBackrest : OsmFilterQuestType() { - override val tagFilters = "nodes with amenity = bench and !backrest" + override val elementFilter = "nodes with amenity = bench and !backrest" override val commitMessage = "Add backrest information to benches" override val wikiLink = "Tag:amenity=bench" override val icon = R.drawable.ic_quest_bench_poi diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt index e9995fc299..77bb1b5f8f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.bike_parking_capacity import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddBikeParkingCapacity(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddBikeParkingCapacity(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with amenity = bicycle_parking and access !~ private|no and ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_cover/AddBikeParkingCover.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_cover/AddBikeParkingCover.kt index bc4299e615..b3cf867791 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_cover/AddBikeParkingCover.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_cover/AddBikeParkingCover.kt @@ -1,14 +1,14 @@ package de.westnordost.streetcomplete.quests.bike_parking_cover import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddBikeParkingCover(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { - override val tagFilters = """ +class AddBikeParkingCover : OsmFilterQuestType() { + + override val elementFilter = """ nodes, ways with amenity = bicycle_parking and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_type/AddBikeParkingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_type/AddBikeParkingType.kt index 80def2eb92..bef97a925e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_type/AddBikeParkingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_type/AddBikeParkingType.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.bike_parking_type import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddBikeParkingType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBikeParkingType : OsmFilterQuestType() { - override val tagFilters = "nodes, ways with amenity = bicycle_parking and access !~ private|no and !bicycle_parking" + override val elementFilter = "nodes, ways with amenity = bicycle_parking and access !~ private|no and !bicycle_parking" override val commitMessage = "Add bicycle parking type" override val wikiLink = "Key:bicycle_parking" override val icon = R.drawable.ic_quest_bicycle_parking diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index 7b6bd70497..a52e987904 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -16,6 +16,7 @@ import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* @@ -24,7 +25,7 @@ import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore class AddCycleway( private val overpassApi: OverpassMapDataAndGeometryApi, private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +) : OsmDownloaderQuestType { override val commitMessage = "Add whether there are cycleways" override val wikiLink = "Key:cycleway" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/board_type/AddBoardType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/board_type/AddBoardType.kt index 2d509689d7..d4d1239d51 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/board_type/AddBoardType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/board_type/AddBoardType.kt @@ -2,12 +2,11 @@ package de.westnordost.streetcomplete.quests.board_type import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType -class AddBoardType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBoardType : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with information = board and access !~ private|no and (!board_type or board_type ~ yes|board) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bridge_structure/AddBridgeStructure.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bridge_structure/AddBridgeStructure.kt index 9b11e68a9b..a89604e0b6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bridge_structure/AddBridgeStructure.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bridge_structure/AddBridgeStructure.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.bridge_structure import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddBridgeStructure(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBridgeStructure : OsmFilterQuestType() { - override val tagFilters = "ways with man_made = bridge and !bridge:structure and !bridge:movable" + override val elementFilter = "ways with man_made = bridge and !bridge:structure and !bridge:movable" override val icon = R.drawable.ic_quest_bridge override val commitMessage = "Add bridge structures" override val wikiLink = "Key:bridge:structure" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/building_levels/AddBuildingLevels.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/building_levels/AddBuildingLevels.kt index b79d50a1e1..a738e9024b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/building_levels/AddBuildingLevels.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/building_levels/AddBuildingLevels.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.building_levels import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddBuildingLevels(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBuildingLevels : OsmFilterQuestType() { // building:height is undocumented, but used the same way as height and currently over 50k times - override val tagFilters = """ + override val elementFilter = """ ways, relations with building ~ ${BUILDINGS_WITH_LEVELS.joinToString("|")} and !building:levels and !height and !building:height and !man_made and location != underground and ruins != yes diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/building_type/AddBuildingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/building_type/AddBuildingType.kt index f08cd5c47e..0306697a7f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/building_type/AddBuildingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/building_type/AddBuildingType.kt @@ -1,16 +1,15 @@ package de.westnordost.streetcomplete.quests.building_type import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddBuildingType (o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBuildingType : OsmFilterQuestType() { // in the case of man_made, historic, military and power, these tags already contain // information about the purpose of the building, so no need to force asking it // same goes (more or less) for tourism, amenity, leisure. See #1854, #1891 - override val tagFilters = """ + override val elementFilter = """ ways, relations with building = yes and !man_made and !historic diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/building_underground/AddIsBuildingUnderground.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/building_underground/AddIsBuildingUnderground.kt index 4db101acf0..7540003b54 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/building_underground/AddIsBuildingUnderground.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/building_underground/AddIsBuildingUnderground.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.building_underground import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddIsBuildingUnderground(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddIsBuildingUnderground : OsmFilterQuestType() { - override val tagFilters = "ways, relations with building and !location and layer~-[0-9]+" + override val elementFilter = "ways, relations with building and !location and layer~-[0-9]+" override val commitMessage = "Determine whatever building is fully underground" override val wikiLink = "Key:location" override val icon = R.drawable.ic_quest_building_underground diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt index 308bb034fe..b9effbbaa4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt @@ -1,16 +1,15 @@ package de.westnordost.streetcomplete.quests.bus_stop_bench import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddBenchStatusOnBusStop(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) : SimpleOverpassQuestType(o) { +class AddBenchStatusOnBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with ( (public_transport = platform and ~bus|trolleybus|tram ~ yes) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopName.kt index ebbe452112..092b0b6c85 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopName.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.bus_stop_name import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -class AddBusStopName(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBusStopName : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with ( (public_transport = platform and ~bus|trolleybus|tram ~ yes) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_ref/AddBusStopRef.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_ref/AddBusStopRef.kt index 87b97d4107..c2a28fb7e2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_ref/AddBusStopRef.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_ref/AddBusStopRef.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.bus_stop_ref import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -class AddBusStopRef(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddBusStopRef : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with ( (public_transport = platform and ~bus|trolleybus|tram ~ yes) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_shelter/AddBusStopShelter.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_shelter/AddBusStopShelter.kt index 0610d166a1..6ce8b947d6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_shelter/AddBusStopShelter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_shelter/AddBusStopShelter.kt @@ -2,16 +2,14 @@ package de.westnordost.streetcomplete.quests.bus_stop_shelter import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.bus_stop_shelter.BusStopShelterAnswer.* import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddBusStopShelter(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddBusStopShelter(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with ( (public_transport = platform and ~bus|trolleybus|tram ~ yes) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashType.kt index d83f0ab6bf..f5d525da49 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashType.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.car_wash_type import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.car_wash_type.CarWashType.* -class AddCarWashType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType>(o) { +class AddCarWashType : OsmFilterQuestType>() { - override val tagFilters = "nodes, ways with amenity = car_wash and !automated and !self_service" + override val elementFilter = "nodes, ways with amenity = car_wash and !automated and !self_service" override val commitMessage = "Add car wash type" override val wikiLink = "Tag:amenity=car_wash" override val icon = R.drawable.ic_quest_car_wash diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperator.kt index e40e395c2d..2976f727d6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperator.kt @@ -2,12 +2,12 @@ package de.westnordost.streetcomplete.quests.charging_station_operator import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType -class AddChargingStationOperator(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { - override val tagFilters = "nodes with amenity = charging_station and !operator and !name and !brand" +class AddChargingStationOperator : OsmFilterQuestType() { + + override val elementFilter = "nodes with amenity = charging_station and !operator and !name and !brand" override val commitMessage = "Add charging station operator" override val wikiLink = "Tag:amenity=charging_station" override val icon = R.drawable.ic_quest_car_charger diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index 498d607e78..d8f8624179 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -1,18 +1,13 @@ package de.westnordost.streetcomplete.quests.clothing_bin_operator -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType -class AddClothingBinOperator(private val overpassApi: OverpassMapDataAndGeometryApi) - : OsmElementQuestType { +class AddClothingBinOperator : OsmMapDataQuestType { /* not the complete filter, see below: we want to filter out additionally all elements that contain any recycling:* = yes that is not shoes or clothes but this can neither be expressed @@ -27,16 +22,8 @@ class AddClothingBinOperator(private val overpassApi: OverpassMapDataAndGeometry override val wikiLink = "Tag:amenity=recycling" override val icon = R.drawable.ic_quest_recycling_clothes - fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + filter.toOverpassQLString() + getQuestPrintStatement() - - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox)) { element, geometry -> - if (element.tags?.hasNoOtherRecyclingTags() == true) { - handler(element, geometry) - } - } - } + override fun getApplicableElements(mapData: MapDataWithGeometry): List = + mapData.nodes.filter { filter.matches(it) && it.tags.hasNoOtherRecyclingTags() } override fun isApplicableTo(element: Element): Boolean = filter.matches(element) && element.tags.hasNoOtherRecyclingTags() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedBuildingConstruction.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedBuildingConstruction.kt index 3d97006126..d19b715a5b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedBuildingConstruction.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedBuildingConstruction.kt @@ -4,16 +4,14 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.SURVEY_MARK_KEY import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.* -class MarkCompletedBuildingConstruction(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class MarkCompletedBuildingConstruction(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with building = construction and (!opening_date or opening_date < today) and older today -${r * 6} months diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt index f4c08251c8..a1003a7fff 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt @@ -5,16 +5,14 @@ import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.meta.SURVEY_MARK_KEY import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.* -class MarkCompletedHighwayConstruction(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class MarkCompletedHighwayConstruction(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = construction and (!opening_date or opening_date < today) and older today -${r * 2} weeks diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt index 3a4a80473d..10bfce71d9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt @@ -1,19 +1,30 @@ package de.westnordost.streetcomplete.quests.crossing_island -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddCrossingIsland(private val overpass: OverpassMapDataAndGeometryApi) - : OsmElementQuestType { +class AddCrossingIsland : OsmMapDataQuestType { + + private val crossingFilter by lazy { ElementFiltersParser().parse(""" + nodes with + highway = crossing + and crossing + and crossing != island + and !crossing:island + """)} + + private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway and access ~ private|no + or highway and oneway and oneway != no + or highway ~ path|footway|cycleway|pedestrian + """)} override val commitMessage = "Add whether pedestrian crossing has an island" override val wikiLink = "Key:crossing:island" @@ -21,24 +32,17 @@ class AddCrossingIsland(private val overpass: OverpassMapDataAndGeometryApi) override fun getTitle(tags: Map) = R.string.quest_pedestrian_crossing_island - override fun isApplicableTo(element: Element): Boolean? = null + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val excludedWayNodeIds = mutableSetOf() + mapData.ways + .filter { excludedWaysFilter.matches(it) } + .flatMapTo(excludedWayNodeIds) { it.nodeIds } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpass.query(getOverpassQuery(bbox), handler) + return mapData.nodes + .filter { crossingFilter.matches(it) && it.id !in excludedWayNodeIds } } - private fun getOverpassQuery(bbox: BoundingBox): String = - bbox.toGlobalOverpassBBox() + """ - node[highway = crossing][crossing][crossing != island][!"crossing:island"] -> .crossings; - .crossings < -> .crossedWays; - ( - way.crossedWays[highway][access ~ "^(private|no)$"]; - way.crossedWays[highway][oneway][oneway != no]; - way.crossedWays[highway ~ "^(path|footway|cycleway|pedestrian)$"]; - ) -> .excludedWays; - - ( .crossings; - node(w.excludedWays); ); - """.trimIndent() + "\n" + getQuestPrintStatement() + override fun isApplicableTo(element: Element): Boolean? = null override fun createForm() = YesNoQuestAnswerFragment() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt index ba6010f56d..521a83ae53 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt @@ -3,15 +3,13 @@ package de.westnordost.streetcomplete.quests.crossing_type import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCrossingType(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddCrossingType(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with highway = crossing and foot != no and ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/defibrillator/AddIsDefibrillatorIndoor.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/defibrillator/AddIsDefibrillatorIndoor.kt index a4c49e05f0..a201c6ae33 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/defibrillator/AddIsDefibrillatorIndoor.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/defibrillator/AddIsDefibrillatorIndoor.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.defibrillator import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddIsDefibrillatorIndoor(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddIsDefibrillatorIndoor : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with emergency=defibrillator and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt index a4435187d4..e3cb8680d0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.diet_type import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddVegan(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddVegan(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with ( amenity ~ restaurant|cafe|fast_food and diet:vegetarian ~ yes|only diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt index c626eb1b54..5255bacea0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.diet_type import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddVegetarian(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddVegetarian(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with amenity ~ restaurant|cafe|fast_food and name and ( !diet:vegetarian diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt index a64a48cdf9..e0dbd0f51d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt @@ -1,17 +1,14 @@ package de.westnordost.streetcomplete.quests.ferry import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment +class AddFerryAccessMotorVehicle() : OsmFilterQuestType() { - -class AddFerryAccessMotorVehicle(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { - - override val tagFilters = "ways, relations with route = ferry and !motor_vehicle" + override val elementFilter = "ways, relations with route = ferry and !motor_vehicle" override val commitMessage = "Specify ferry access for motor vehicles" override val wikiLink = "Tag:route=ferry" override val icon = R.drawable.ic_quest_ferry diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessPedestrian.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessPedestrian.kt index 4acc268c09..cb555e5109 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessPedestrian.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessPedestrian.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.ferry import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddFerryAccessPedestrian(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddFerryAccessPedestrian : OsmFilterQuestType() { - override val tagFilters = "ways, relations with route = ferry and !foot" + override val elementFilter = "ways, relations with route = ferry and !foot" override val commitMessage = "Specify ferry access for pedestrians" override val wikiLink = "Tag:route=ferry" override val icon = R.drawable.ic_quest_ferry_pedestrian diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/fire_hydrant/AddFireHydrantType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/fire_hydrant/AddFireHydrantType.kt index a88fbfa3fe..e462ae1076 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/fire_hydrant/AddFireHydrantType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/fire_hydrant/AddFireHydrantType.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.fire_hydrant import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddFireHydrantType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddFireHydrantType : OsmFilterQuestType() { - override val tagFilters = "nodes with emergency = fire_hydrant and !fire_hydrant:type" + override val elementFilter = "nodes with emergency = fire_hydrant and !fire_hydrant:type" override val commitMessage = "Add fire hydrant type" override val wikiLink = "Tag:emergency=fire_hydrant" override val icon = R.drawable.ic_quest_fire_hydrant diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/foot/AddProhibitedForPedestrians.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/foot/AddProhibitedForPedestrians.kt index 3148943875..9c2481c0cd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/foot/AddProhibitedForPedestrians.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/foot/AddProhibitedForPedestrians.kt @@ -2,14 +2,13 @@ package de.westnordost.streetcomplete.quests.foot import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_PAVED -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.foot.ProhibitedForPedestriansAnswer.* -class AddProhibitedForPedestrians(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddProhibitedForPedestrians : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with ( ~'sidewalk(:both)?' ~ none|no or (sidewalk:left ~ none|no and sidewalk:right ~ none|no) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/general_fee/AddGeneralFee.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/general_fee/AddGeneralFee.kt index 2748c80b2f..110d24bb50 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/general_fee/AddGeneralFee.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/general_fee/AddGeneralFee.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.general_fee import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddGeneralFee(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddGeneralFee : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with (tourism = museum or leisure = beach_resort or tourism = gallery) and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt index 3eb572c2d6..9c37cb453a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt @@ -2,17 +2,15 @@ package de.westnordost.streetcomplete.quests.handrail import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddHandrail(overpassApi: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(overpassApi) { +class AddHandrail(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = steps and (!indoor or indoor = no) and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index d7ec259f34..d09b12b9c2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -15,12 +15,14 @@ import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.DEFAULT_MAX_QUESTS import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.elementfilter.toOverpassBboxFilter +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.util.FlattenIterable import de.westnordost.streetcomplete.util.LatLonRaster import de.westnordost.streetcomplete.util.enclosingBoundingBox import de.westnordost.streetcomplete.util.isInMultipolygon -class AddHousenumber(private val overpass: OverpassMapDataAndGeometryApi) : OsmElementQuestType { +class AddHousenumber(private val overpass: OverpassMapDataAndGeometryApi) : + OsmDownloaderQuestType { override val commitMessage = "Add housenumbers" override val wikiLink = "Key:addr" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt index dd92aa4fbd..00558229d9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.internet_access import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddInternetAccess(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddInternetAccess(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with ( amenity ~ library|community_centre|youth_centre diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt index ad69616169..58510e53c5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt @@ -4,13 +4,13 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType -class AddForestLeafType(private val overpassApi: OverpassMapDataAndGeometryApi) : OsmElementQuestType { +class AddForestLeafType(private val overpassApi: OverpassMapDataAndGeometryApi) : OsmDownloaderQuestType { override val commitMessage = "Add leaf type" override val wikiLink = "Key:leaf_type" override val icon = R.drawable.ic_quest_leaf diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt index 1fc3fc8093..ad7beb8f34 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt @@ -1,17 +1,13 @@ package de.westnordost.streetcomplete.quests.max_height -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType -class AddMaxHeight(private val overpassApi: OverpassMapDataAndGeometryApi) : OsmElementQuestType { +class AddMaxHeight : OsmMapDataQuestType { private val nodeFilter by lazy { ElementFiltersParser().parse(""" nodes with @@ -49,20 +45,12 @@ class AddMaxHeight(private val overpassApi: OverpassMapDataAndGeometryApi) : Osm } } + override fun getApplicableElements(mapData: MapDataWithGeometry): List = + mapData.nodes.filter { nodeFilter.matches(it) } + mapData.ways.filter { wayFilter.matches(it) } + override fun isApplicableTo(element: Element) = nodeFilter.matches(element) || wayFilter.matches(element) - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getNodeOverpassQuery(bbox), handler) - && overpassApi.query(getWayOverpassQuery(bbox), handler) - } - - private fun getNodeOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + nodeFilter.toOverpassQLString() + "\n" + getQuestPrintStatement() - - private fun getWayOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + wayFilter.toOverpassQLString() + "\n" + getQuestPrintStatement() - override fun createForm() = AddMaxHeightForm() override fun applyAnswerTo(answer: MaxHeightAnswer, changes: StringMapChangesBuilder) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.kt index 51cafa35af..63314d6136 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_speed/AddMaxSpeed.kt @@ -2,14 +2,13 @@ package de.westnordost.streetcomplete.quests.max_speed import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -class AddMaxSpeed(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddMaxSpeed : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway ~ motorway|trunk|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential and !maxspeed and !maxspeed:advisory and !maxspeed:forward and !maxspeed:backward and !source:maxspeed and !zone:maxspeed and !maxspeed:type and !zone:traffic diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_weight/AddMaxWeight.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_weight/AddMaxWeight.kt index 64c176b36b..45000b52d4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_weight/AddMaxWeight.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_weight/AddMaxWeight.kt @@ -1,19 +1,18 @@ package de.westnordost.streetcomplete.quests.max_weight import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.max_weight.MaxWeightSign.* -class AddMaxWeight(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddMaxWeight : OsmFilterQuestType() { override val commitMessage = "Add maximum allowed weight" override val wikiLink = "Key:maxweight" override val icon = R.drawable.ic_quest_max_weight override val hasMarkersAtEnds = true - override val tagFilters = """ + override val elementFilter = """ ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|service and service != driveway and !maxweight and maxweight:signed != no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt index 03871837f9..2a1550c4e4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.motorcycle_parking_capacity import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddMotorcycleParkingCapacity(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddMotorcycleParkingCapacity(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with amenity = motorcycle_parking and access !~ private|no and (!capacity or capacity older today -${r * 4} years) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_cover/AddMotorcycleParkingCover.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_cover/AddMotorcycleParkingCover.kt index 485474d1b2..21a8834efc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_cover/AddMotorcycleParkingCover.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_cover/AddMotorcycleParkingCover.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.motorcycle_parking_cover import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddMotorcycleParkingCover(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddMotorcycleParkingCover : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with amenity = motorcycle_parking and access !~ private|no and !covered diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index ed7440040b..21202855cf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -1,23 +1,17 @@ package de.westnordost.streetcomplete.quests.oneway -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.oneway.OnewayAnswer.* import de.westnordost.streetcomplete.quests.parking_lanes.* -class AddOneway( - private val overpassMapDataApi: OverpassMapDataAndGeometryApi -) : OsmElementQuestType { +class AddOneway : OsmMapDataQuestType { /** find all roads */ private val allRoadsFilter by lazy { ElementFiltersParser().parse(""" @@ -25,7 +19,7 @@ class AddOneway( """) } /** find only those roads eligible for asking for oneway */ - private val tagFilter by lazy { ElementFiltersParser().parse(""" + private val elementFilter by lazy { ElementFiltersParser().parse(""" ways with highway ~ living_street|residential|service|tertiary|unclassified and !oneway and area != yes and junction != roundabout and (access !~ private|no or (foot and foot !~ private|no)) @@ -40,49 +34,41 @@ class AddOneway( override fun getTitle(tags: Map) = R.string.quest_oneway2_title - override fun isApplicableTo(element: Element): Boolean? = null - /* return null because we also want to have a look at the surrounding geometry to filter out - * (some) dead ends */ - - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - val query = bbox.toGlobalOverpassBBox() + "\n" + allRoadsFilter.toOverpassQLString() + getQuestPrintStatement() + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val allRoads = mapData.ways.filter { allRoadsFilter.matches(it) && it.nodeIds.size >= 2 } val connectionCountByNodeIds = mutableMapOf() - val onewayCandidates = mutableListOf>() - val result = overpassMapDataApi.query(query) { element, geometry -> - if (element is Way && element.nodeIds.size >= 2) { - for (nodeId in element.nodeIds) { - val prevCount = connectionCountByNodeIds[nodeId] ?: 0 - connectionCountByNodeIds[nodeId] = prevCount + 1 - } + val onewayCandidates = mutableListOf() - if (element.tags != null && tagFilter.matches(element)) { - // check if the width of the road minus the space consumed by parking lanes is quite narrow - val width = element.tags["width"]?.toFloatOrNull() - val isNarrow = width != null && width <= estimateWidthConsumedByParkingLanes(element.tags) + 4f - if (isNarrow) { - onewayCandidates.add(element to geometry) - } + for (road in allRoads) { + for (nodeId in road.nodeIds) { + val prevCount = connectionCountByNodeIds[nodeId] ?: 0 + connectionCountByNodeIds[nodeId] = prevCount + 1 + } + if (elementFilter.matches(road)) { + // check if the width of the road minus the space consumed by parking lanes is quite narrow + val width = road.tags["width"]?.toFloatOrNull() + val isNarrow = width != null && width <= estimateWidthConsumedByParkingLanes(road.tags) + 4f + if (isNarrow) { + onewayCandidates.add(road) } } } - if (!result) return false - for ((way, geometry) in onewayCandidates) { + return onewayCandidates.filter { /* ways that are simply at the border of the download bounding box are treated as if they are dead ends. This is fine though, because it only leads to this quest not showing up for those streets (which is better than the other way round) */ - val hasConnectionOnBothEnds = - (connectionCountByNodeIds[way.nodeIds.first()] ?: 0) > 1 && - (connectionCountByNodeIds[way.nodeIds.last()] ?: 0) > 1 - - if (hasConnectionOnBothEnds) { - handler(way, geometry) - } + // check if the way has connections to other roads at both ends + (connectionCountByNodeIds[it.nodeIds.first()] ?: 0) > 1 && + (connectionCountByNodeIds[it.nodeIds.last()] ?: 0) > 1 } - return true } + override fun isApplicableTo(element: Element): Boolean? = null + /* return null because we also want to have a look at the surrounding geometry to filter out + * (some) dead ends */ + private fun estimateWidthConsumedByParkingLanes(tags: Map): Float { val sides = createParkingLaneSides(tags) ?: return 0f return (sides.left?.estimatedWidth ?: 0f) + (sides.right?.estimatedWidth ?: 0f) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index 9fd50b51c0..9a244fb3a2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -9,10 +9,10 @@ import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegment import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegmentsApi import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowDao @@ -21,7 +21,7 @@ class AddSuspectedOneway( private val overpassMapDataApi: OverpassMapDataAndGeometryApi, private val trafficFlowSegmentsApi: TrafficFlowSegmentsApi, private val db: WayTrafficFlowDao -) : OsmElementQuestType { +) : OsmDownloaderQuestType { private val tagFilters = """ ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 1b8bcc0c4e..5bf1096fc7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -1,17 +1,13 @@ package de.westnordost.streetcomplete.quests.opening_hours -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.updateWithCheckDate +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRows import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRules @@ -19,10 +15,9 @@ import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.concurrent.FutureTask class AddOpeningHours ( - private val overpassApi: OverpassMapDataAndGeometryApi, private val featureDictionaryFuture: FutureTask, private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +) : OsmMapDataQuestType { /* See also AddWheelchairAccessBusiness and AddPlaceName, which has a similar list and is/should be ordered in the same way for better overview */ @@ -132,11 +127,8 @@ class AddOpeningHours ( } } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox)) { element, geometry -> - if (isApplicableTo(element)) handler(element, geometry) - } - } + override fun getApplicableElements(mapData: MapDataWithGeometry): List = + mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) : Boolean { if (!filter.matches(element)) return false @@ -174,9 +166,6 @@ class AddOpeningHours ( } } - private fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + filter.toOverpassQLString() + getQuestPrintStatement() - private fun hasName(tags: Map?) = hasProperName(tags) || hasFeatureName(tags) private fun hasProperName(tags: Map?): Boolean = diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/orchard_produce/AddOrchardProduce.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/orchard_produce/AddOrchardProduce.kt index a4f5881f3f..c8a367069a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/orchard_produce/AddOrchardProduce.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/orchard_produce/AddOrchardProduce.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.orchard_produce import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddOrchardProduce(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType>(o) { +class AddOrchardProduce : OsmFilterQuestType>() { - override val tagFilters = """ + override val elementFilter = """ ways, relations with landuse = orchard and !trees and !produce and !crop and orchard != meadow_orchard diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_access/AddParkingAccess.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_access/AddParkingAccess.kt index a2131f4717..da3cca0e93 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_access/AddParkingAccess.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_access/AddParkingAccess.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.parking_access import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddParkingAccess(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddParkingAccess : OsmFilterQuestType() { - override val tagFilters = "nodes, ways, relations with amenity=parking and (!access or access=unknown)" + override val elementFilter = "nodes, ways, relations with amenity=parking and (!access or access=unknown)" override val commitMessage = "Add type of parking access" override val wikiLink = "Tag:amenity=parking" override val icon = R.drawable.ic_quest_parking_access diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt index 6859121894..b0d4f25552 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.parking_fee import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddParkingFee(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddParkingFee(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with amenity = parking and access ~ yes|customers|public and ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_type/AddParkingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_type/AddParkingType.kt index b3543e07c2..2f9d31a7c2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_type/AddParkingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_type/AddParkingType.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.parking_type import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddParkingType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddParkingType : OsmFilterQuestType() { - override val tagFilters = "nodes, ways, relations with amenity = parking and !parking" + override val elementFilter = "nodes, ways, relations with amenity = parking and !parking" override val commitMessage = "Add parking type" override val wikiLink = "Tag:amenity=parking" override val icon = R.drawable.ic_quest_parking diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt index 2df8bbfd5c..d66db001b4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt @@ -1,22 +1,17 @@ package de.westnordost.streetcomplete.quests.place_name -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import java.util.concurrent.FutureTask class AddPlaceName( - private val overpassApi: OverpassMapDataAndGeometryApi, private val featureDictionaryFuture: FutureTask -) : OsmElementQuestType { +) : OsmMapDataQuestType { private val filter by lazy { ElementFiltersParser().parse(""" nodes, ways, relations with @@ -108,12 +103,8 @@ class AddPlaceName( override fun getTitleArgs(tags: Map, featureName: Lazy) = featureName.value?.let { arrayOf(it) } ?: arrayOf() - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox)) { element, geometry -> - // only show places without names as quests for which a feature name is available - if (hasFeatureName(element.tags)) handler(element, geometry) - } - } + override fun getApplicableElements(mapData: MapDataWithGeometry): List = + mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) = filter.matches(element) && hasFeatureName(element.tags) @@ -127,9 +118,6 @@ class AddPlaceName( } } - private fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + filter.toOverpassQLString() + getQuestPrintStatement() - private fun hasFeatureName(tags: Map?): Boolean = tags?.let { featureDictionaryFuture.get().byTags(it).find().isNotEmpty() } ?: false } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/playground_access/AddPlaygroundAccess.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/playground_access/AddPlaygroundAccess.kt index ca4e602054..5e76e9050c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/playground_access/AddPlaygroundAccess.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/playground_access/AddPlaygroundAccess.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.playground_access import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddPlaygroundAccess(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddPlaygroundAccess : OsmFilterQuestType() { - override val tagFilters = "nodes, ways, relations with leisure = playground and (!access or access = unknown)" + override val elementFilter = "nodes, ways, relations with leisure = playground and (!access or access = unknown)" override val commitMessage = "Add playground access" override val wikiLink = "Tag:leisure=playground" override val icon = R.drawable.ic_quest_playground diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt index 0e91a924d5..98b8346da4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt @@ -2,17 +2,15 @@ package de.westnordost.streetcomplete.quests.postbox_collection_times import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddPostboxCollectionTimes(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddPostboxCollectionTimes(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with amenity = post_box and access !~ private|no and collection_times:signed != no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_ref/AddPostboxRef.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_ref/AddPostboxRef.kt index fd4b559880..f550ac3fe1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_ref/AddPostboxRef.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_ref/AddPostboxRef.kt @@ -2,14 +2,13 @@ package de.westnordost.streetcomplete.quests.postbox_ref import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.containsAny -class AddPostboxRef(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddPostboxRef : OsmFilterQuestType() { - override val tagFilters = "nodes with amenity = post_box and !ref and !ref:signed" + override val elementFilter = "nodes with amenity = post_box and !ref and !ref:signed" override val icon = R.drawable.ic_quest_mail_ref override val commitMessage = "Add postbox refs" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/powerpoles_material/AddPowerPolesMaterial.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/powerpoles_material/AddPowerPolesMaterial.kt index 3e2042a4ad..306151eeaf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/powerpoles_material/AddPowerPolesMaterial.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/powerpoles_material/AddPowerPolesMaterial.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.powerpoles_material import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddPowerPolesMaterial(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddPowerPolesMaterial : OsmFilterQuestType() { - override val tagFilters = "nodes with power = pole and !material" + override val elementFilter = "nodes with power = pole and !material" override val commitMessage = "Add powerpoles material type" override val wikiLink = "Tag:power=pole" override val icon = R.drawable.ic_quest_power diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt index 1f2d50c57a..9fc5948263 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt @@ -1,23 +1,27 @@ package de.westnordost.streetcomplete.quests.railway_crossing -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate -import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.updateWithCheckDate +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddRailwayCrossingBarrier( - private val overpassMapDataApi: OverpassMapDataAndGeometryApi, - private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +class AddRailwayCrossingBarrier(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { + + private val crossingFilter by lazy { ElementFiltersParser().parse(""" + nodes with + railway = level_crossing + and (!crossing:barrier or crossing:barrier older today -${r * 8} years) + """)} + + private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway and access ~ private|no + or railway ~ tram|abandoned + """)} override val commitMessage = "Add type of barrier for railway crossing" override val wikiLink = "Key:crossing:barrier" @@ -25,36 +29,21 @@ class AddRailwayCrossingBarrier( override fun getTitle(tags: Map) = R.string.quest_railway_crossing_barrier_title2 - override fun createForm() = AddRailwayCrossingBarrierForm() + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val excludedWayNodeIds = mutableSetOf() + mapData.ways + .filter { excludedWaysFilter.matches(it) } + .flatMapTo(excludedWayNodeIds) { it.nodeIds } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassMapDataApi.query(getOverpassQuery(bbox), handler) + return mapData.nodes + .filter { crossingFilter.matches(it) && it.id !in excludedWayNodeIds } } override fun isApplicableTo(element: Element): Boolean? = null + override fun createForm() = AddRailwayCrossingBarrierForm() + override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) { changes.updateWithCheckDate("crossing:barrier", answer) } - - private fun getOverpassQuery(bbox: BoundingBox) = """ - ${bbox.toGlobalOverpassBBox()} - - way[highway][access ~ '^(private|no)$']; node(w) -> .private_road_nodes; - way[railway ~ '^(tram|abandoned)$']; node(w) -> .excluded_railways_nodes; - (.private_road_nodes; .excluded_railways_nodes;) -> .excluded; - - node[railway = level_crossing] -> .crossings; - - node.crossings[!'crossing:barrier'] -> .crossings_with_unknown_barrier; - node.crossings${olderThan(8).toOverpassQLString()} -> .crossings_with_old_barrier; - - ((.crossings_with_unknown_barrier; .crossings_with_old_barrier;); - .excluded;); - - ${getQuestPrintStatement()} - """.trimIndent() - - private fun olderThan(years: Int) = - TagOlderThan("crossing:barrier", RelativeDate(-(r * 365 * years).toFloat())) - } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling/AddRecyclingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling/AddRecyclingType.kt index ab89977241..03ea4cf21c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling/AddRecyclingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling/AddRecyclingType.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.recycling import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.quests.recycling.RecyclingType.* -class AddRecyclingType(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddRecyclingType : OsmFilterQuestType() { - override val tagFilters = "nodes, ways, relations with amenity = recycling and !recycling_type" + override val elementFilter = "nodes, ways, relations with amenity = recycling and !recycling_type" override val commitMessage = "Add recycling type to recycling amenity" override val wikiLink = "Key:recycling_type" override val icon = R.drawable.ic_quest_recycling diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_glass/DetermineRecyclingGlass.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_glass/DetermineRecyclingGlass.kt index 0e3c6dc053..9fac65c5ef 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_glass/DetermineRecyclingGlass.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_glass/DetermineRecyclingGlass.kt @@ -1,16 +1,14 @@ package de.westnordost.streetcomplete.quests.recycling_glass import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.quests.recycling_glass.RecyclingGlass.* -class DetermineRecyclingGlass(overpassApi: OverpassMapDataAndGeometryApi) : - SimpleOverpassQuestType(overpassApi) { +class DetermineRecyclingGlass : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with amenity = recycling and recycling_type = container and recycling:glass = yes and !recycling:glass_bottles """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 9895153ab2..083c468ae0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -13,12 +13,13 @@ import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore class AddRecyclingContainerMaterials( private val overpassApi: OverpassMapDataAndGeometryApi, private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +) : OsmDownloaderQuestType { override val commitMessage = "Add recycled materials to container" override val wikiLink = "Key:recycling" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToPlaceOfWorship.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToPlaceOfWorship.kt index 546120bddd..36aa7598c4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToPlaceOfWorship.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToPlaceOfWorship.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.religion import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddReligionToPlaceOfWorship(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddReligionToPlaceOfWorship : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with ( amenity = place_of_worship diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToWaysideShrine.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToWaysideShrine.kt index 48df601b3c..141b48b88c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToWaysideShrine.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/religion/AddReligionToWaysideShrine.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.religion import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddReligionToWaysideShrine(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddReligionToWaysideShrine : OsmFilterQuestType() { - override val tagFilters = + override val elementFilter = "nodes, ways, relations with historic = wayside_shrine and !religion and (access !~ private|no)" override val commitMessage = "Add religion for wayside shrine" override val wikiLink = "Key:religion" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index f8958c615b..b214afd84e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -4,7 +4,6 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ALL_ROADS -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept @@ -12,6 +11,7 @@ import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.quests.LocalizedName import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion @@ -19,7 +19,7 @@ import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion class AddRoadName( private val overpassApi: OverpassMapDataAndGeometryApi, private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmElementQuestType { +) : OsmDownloaderQuestType { override val enabledInCountries = AllCountriesExcept("JP") override val commitMessage = "Determine road names and types" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShape.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShape.kt index 853d28c75c..ca027294e5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShape.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/roof_shape/AddRoofShape.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.roof_shape import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddRoofShape(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddRoofShape : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways, relations with roof:levels and roof:levels != 0 and !roof:shape and !3dr:type and !3dr:roof """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt index 7f74d0df8d..3fc606adf6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt @@ -3,16 +3,14 @@ package de.westnordost.streetcomplete.quests.segregated import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_PAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCyclewaySegregation(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddCyclewaySegregation(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with ( (highway = path and bicycle = designated and foot = designated) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt index 4ddc517fce..03b25fbe04 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/self_service/AddSelfServiceLaundry.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.self_service import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddSelfServiceLaundry(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddSelfServiceLaundry : OsmFilterQuestType() { - override val tagFilters = "nodes, ways with shop = laundry and !self_service" + override val elementFilter = "nodes, ways with shop = laundry and !self_service" override val commitMessage = "Add self service info" override val wikiLink = "Tag:shop=laundry" override val icon = R.drawable.ic_quest_laundry diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt index 46c4d1066a..7ff48cab8e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt @@ -10,8 +10,10 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType -class AddSidewalk(private val overpassApi: OverpassMapDataAndGeometryApi) : OsmElementQuestType { +class AddSidewalk(private val overpassApi: OverpassMapDataAndGeometryApi) : + OsmDownloaderQuestType { override val commitMessage = "Add whether there are sidewalks" override val wikiLink = "Key:sidewalk" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sport/AddSport.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sport/AddSport.kt index 6a6d5c30a0..11645744da 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sport/AddSport.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sport/AddSport.kt @@ -1,11 +1,10 @@ package de.westnordost.streetcomplete.quests.sport import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddSport(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType>(o) { +class AddSport : OsmFilterQuestType>() { private val ambiguousSportValues = listOf( "team_handball", // -> not really ambiguous but same as handball @@ -14,7 +13,7 @@ class AddSport(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType american_football, soccer or other *_football ) - override val tagFilters = """ + override val elementFilter = """ nodes, ways with leisure = pitch and (!sport or sport ~ ${ambiguousSportValues.joinToString("|")} ) and (access !~ private|no) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/step_count/AddStepCount.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/step_count/AddStepCount.kt index 1de4be9f7c..afb2456c47 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/step_count/AddStepCount.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/step_count/AddStepCount.kt @@ -1,14 +1,12 @@ package de.westnordost.streetcomplete.quests.step_count import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddStepCount(overpassApi: OverpassMapDataAndGeometryApi) - : SimpleOverpassQuestType(overpassApi) { +class AddStepCount : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = steps and (!indoor or indoor = no) and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/steps_incline/AddStepsIncline.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/steps_incline/AddStepsIncline.kt index 0331c2c227..ff27461dda 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/steps_incline/AddStepsIncline.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/steps_incline/AddStepsIncline.kt @@ -2,13 +2,12 @@ package de.westnordost.streetcomplete.quests.steps_incline import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.quests.steps_incline.StepsIncline.* -class AddStepsIncline(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddStepsIncline : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = steps and (!indoor or indoor = no) and area != yes diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRamp.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRamp.kt index 3b627d8bf9..ebb305d955 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRamp.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRamp.kt @@ -3,15 +3,13 @@ package de.westnordost.streetcomplete.quests.steps_ramp import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddStepsRamp(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddStepsRamp(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = steps and (!indoor or indoor = no) and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt index 08fddfd5f7..b89d9b6890 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt @@ -12,6 +12,7 @@ import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometry import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.updateWithCheckDate +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore @@ -19,7 +20,7 @@ import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore class AddSummitRegister( private val overpassMapDataApi: OverpassMapDataAndGeometryApi, private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +) : OsmDownloaderQuestType { override val commitMessage = "Add whether summit register is present" override val wikiLink = "Key:summit:register" @@ -56,7 +57,6 @@ class AddSummitRegister( private fun getOverpassQuery(bbox: BoundingBox) = """ ${bbox.toGlobalOverpassBBox()} - ( relation["route"="hiking"]; )->.hiking; diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt index bb1d1acde4..48cb5cf8b8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.surface import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCyclewayPartSurface(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddCyclewayPartSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with ( highway = cycleway diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddFootwayPartSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddFootwayPartSurface.kt index ef32c5754a..5140f9bf37 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddFootwayPartSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddFootwayPartSurface.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.surface import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddFootwayPartSurface(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddFootwayPartSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with ( highway = footway diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddPathSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddPathSurface.kt index d188464891..b064ae3a76 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddPathSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddPathSurface.kt @@ -3,15 +3,13 @@ package de.westnordost.streetcomplete.quests.surface import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddPathSurface(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddPathSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway ~ path|footway|cycleway|bridleway|steps and segregated != yes and access !~ private|no diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt index 1b7ac0207c..017626724c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt @@ -3,16 +3,13 @@ package de.westnordost.streetcomplete.quests.surface import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +class AddRoadSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { -class AddRoadSurface(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { - - override val tagFilters = """ + override val elementFilter = """ ways with highway ~ ${ROADS_WITH_SURFACES.joinToString("|")} and ( !surface diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/DetailRoadSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/DetailRoadSurface.kt index e4aff22029..dda8fe42bc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/DetailRoadSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/DetailRoadSurface.kt @@ -3,13 +3,12 @@ package de.westnordost.streetcomplete.quests.surface import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType -class DetailRoadSurface(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class DetailRoadSurface : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway ~ ${ALL_ROADS.joinToString("|")} and surface ~ paved|unpaved and !surface:note diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt index b85db1568a..9f08cc7468 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt @@ -2,17 +2,15 @@ package de.westnordost.streetcomplete.quests.tactile_paving import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTactilePavingBusStop(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddTactilePavingBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with ( (public_transport = platform and (bus = yes or trolleybus = yes or tram = yes)) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt index 857eb2cd98..ea636f172f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt @@ -1,24 +1,35 @@ package de.westnordost.streetcomplete.quests.tactile_paving -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate -import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTactilePavingCrosswalk( - private val overpassMapDataApi: OverpassMapDataAndGeometryApi, - private val r: ResurveyIntervalsStore -) : OsmElementQuestType { +class AddTactilePavingCrosswalk(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { + + private val crossingFilter by lazy { ElementFiltersParser().parse(""" + nodes with + ( + highway = traffic_signals and crossing = traffic_signals and foot != no + or highway = crossing and foot != no + ) + and ( + !tactile_paving + or tactile_paving = no and tactile_paving older today -${r * 4} years + or older today -${r * 8} years + ) + """)} + + private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway = cycleway and foot !~ yes|designated + or highway and access ~ private|no + """)} override val commitMessage = "Add tactile pavings on crosswalks" override val wikiLink = "Key:tactile_paving" @@ -42,8 +53,14 @@ class AddTactilePavingCrosswalk( override fun getTitle(tags: Map) = R.string.quest_tactilePaving_title_crosswalk - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassMapDataApi.query(getOverpassQuery(bbox), handler) + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val excludedWayNodeIds = mutableSetOf() + mapData.ways + .filter { excludedWaysFilter.matches(it) } + .flatMapTo(excludedWayNodeIds) { it.nodeIds } + + return mapData.nodes + .filter { crossingFilter.matches(it) && it.id !in excludedWayNodeIds } } override fun isApplicableTo(element: Element): Boolean? = null @@ -53,35 +70,4 @@ class AddTactilePavingCrosswalk( override fun applyAnswerTo(answer: Boolean, changes: StringMapChangesBuilder) { changes.add("tactile_paving", answer.toYesNo()) } - - private fun getOverpassQuery(bbox: BoundingBox) = """ - ${bbox.toGlobalOverpassBBox()} - - way[highway = cycleway][foot !~ '^(yes|designated)$']; node(w) -> .exclusive_cycleway_nodes; - way[highway][access ~ '^(private|no)$']; node(w) -> .private_road_nodes; - (.exclusive_cycleway_nodes; .private_road_nodes;) -> .excluded; - - ( - node[highway = traffic_signals][crossing = traffic_signals][foot != no]; - node[highway = crossing][foot != no]; - ) -> .crossings; - - node.crossings[!tactile_paving] -> .crossings_with_unknown_tactile_paving; - node.crossings[tactile_paving = no]${olderThan(4).toOverpassQLString()} -> .old_crossings_without_tactile_paving; - node.crossings${olderThan(8).toOverpassQLString()} -> .very_old_crossings; - - ( - ( - .crossings_with_unknown_tactile_paving; - .old_crossings_without_tactile_paving; - .very_old_crossings; - ); - - .excluded; - ); - - ${getQuestPrintStatement()} - """.trimIndent() - - private fun olderThan(years: Int) = - TagOlderThan("tactile_paving", RelativeDate(-(r * 365 * years).toFloat())) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/toilet_availability/AddToiletAvailability.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/toilet_availability/AddToiletAvailability.kt index 5fbeaed908..685fb9a679 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/toilet_availability/AddToiletAvailability.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/toilet_availability/AddToiletAvailability.kt @@ -1,17 +1,16 @@ package de.westnordost.streetcomplete.quests.toilet_availability import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddToiletAvailability(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddToiletAvailability : OsmFilterQuestType() { // only for malls, big stores and rest areas because users should not need to go inside a non-public // place to solve the quest. (Considering malls and department stores public enough) - override val tagFilters = """ + override val elementFilter = """ nodes, ways with ( (shop ~ mall|department_store and name) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/toilets_fee/AddToiletsFee.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/toilets_fee/AddToiletsFee.kt index 203d2a9915..3d2b3fe420 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/toilets_fee/AddToiletsFee.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/toilets_fee/AddToiletsFee.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.toilets_fee import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddToiletsFee(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddToiletsFee : OsmFilterQuestType() { - override val tagFilters = "nodes, ways with amenity = toilets and access !~ private|customers and !fee" + override val elementFilter = "nodes, ways with amenity = toilets and access !~ private|customers and !fee" override val commitMessage = "Add toilets fee" override val wikiLink = "Key:fee" override val icon = R.drawable.ic_quest_toilet_fee diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tourism_information/AddInformationToTourism.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tourism_information/AddInformationToTourism.kt index d6b6ed63a5..9f3bc0a18c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tourism_information/AddInformationToTourism.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tourism_information/AddInformationToTourism.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.tourism_information import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -class AddInformationToTourism(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddInformationToTourism : OsmFilterQuestType() { - override val tagFilters = "nodes, ways, relations with tourism = information and !information" + override val elementFilter = "nodes, ways, relations with tourism = information and !information" override val commitMessage = "Add information type to tourist information" override val wikiLink = "Tag:tourism=information" override val icon = R.drawable.ic_quest_information diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt index f25fb782fe..9c61005652 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt @@ -3,15 +3,13 @@ package de.westnordost.streetcomplete.quests.tracktype import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTracktype(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddTracktype(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ ways with highway = track and ( !tracktype diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_button/AddTrafficSignalsButton.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_button/AddTrafficSignalsButton.kt index 18cd7cccb8..db962baa01 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_button/AddTrafficSignalsButton.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_button/AddTrafficSignalsButton.kt @@ -1,15 +1,14 @@ package de.westnordost.streetcomplete.quests.traffic_signals_button import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddTrafficSignalsButton(o: OverpassMapDataAndGeometryApi) : SimpleOverpassQuestType(o) { +class AddTrafficSignalsButton : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with crossing = traffic_signals and highway ~ crossing|traffic_signals and !button_operated """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt index 9816d5ff7d..9725a89e48 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt @@ -2,17 +2,15 @@ package de.westnordost.streetcomplete.quests.traffic_signals_sound import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTrafficSignalsSound(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddTrafficSignalsSound(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with crossing = traffic_signals and highway ~ crossing|traffic_signals and ( !$SOUND_SIGNALS diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt index def3fed264..4393dd1f7b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt @@ -2,17 +2,14 @@ package de.westnordost.streetcomplete.quests.traffic_signals_vibrate import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.ktx.toYesNo -import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTrafficSignalsVibration(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddTrafficSignalsVibration(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes with crossing = traffic_signals and highway ~ crossing|traffic_signals and ( !$VIBRATING_BUTTON diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt index 857b3b9203..e7a47740ec 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt @@ -2,13 +2,11 @@ package de.westnordost.streetcomplete.quests.way_lit import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWayLit(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddWayLit(r: ResurveyIntervalsStore) : OsmFilterQuestType() { /* Using sidewalk as a tell-tale tag for (urban) streets which reached a certain level of development. I.e. non-urban streets will usually not even be lit in industrialized @@ -17,7 +15,7 @@ class AddWayLit(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) most hike paths and trails. See #427 for discussion. */ - override val tagFilters = """ + override val elementFilter = """ ways with ( ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt index 603465f368..02f4c5dda3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessBusiness.kt @@ -2,17 +2,15 @@ package de.westnordost.streetcomplete.quests.wheelchair_access import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import java.util.concurrent.FutureTask class AddWheelchairAccessBusiness( - o: OverpassMapDataAndGeometryApi, private val featureDictionaryFuture: FutureTask -) : SimpleOverpassQuestType(o) +) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with ( shop and shop !~ no|vacant diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt index 63147c96ed..b2f25f9f60 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.wheelchair_access import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessOutside(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddWheelchairAccessOutside(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with leisure = dog_park and (!wheelchair or wheelchair older today -${r * 8} years) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt index 2cd093654c..7d1b6e7ffb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.wheelchair_access import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessPublicTransport(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddWheelchairAccessPublicTransport(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with (amenity = bus_station or railway ~ station|subway_entrance) and ( !wheelchair diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt index fd48c1a8d5..af15a3086b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.wheelchair_access import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessToilets(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddWheelchairAccessToilets(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways with amenity = toilets and access !~ private|customers and ( diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt index 77a1f3a803..192e21a141 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt @@ -2,15 +2,13 @@ package de.westnordost.streetcomplete.quests.wheelchair_access import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessToiletsPart(o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore) - : SimpleOverpassQuestType(o) { +class AddWheelchairAccessToiletsPart(r: ResurveyIntervalsStore) : OsmFilterQuestType() { - override val tagFilters = """ + override val elementFilter = """ nodes, ways, relations with name and toilets = yes and ( !toilets:wheelchair diff --git a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt index cca13aa85e..07b15cfcc6 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt @@ -7,7 +7,7 @@ import de.westnordost.osmapi.ApiRequestWriter import de.westnordost.osmapi.OsmConnection import de.westnordost.osmapi.common.errors.OsmApiException import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.quests.QuestModule import java.io.OutputStream import java.lang.Thread.sleep @@ -29,7 +29,7 @@ fun main() { val hamburg = BoundingBox(53.5, 9.9, 53.6, 10.0) for (questType in registry.all) { - if (questType is OsmElementQuestType) { + if (questType is OsmDownloaderQuestType) { print(questType.javaClass.simpleName + ": ") questType.download(hamburg, mock()) println() diff --git a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt index 8cbb55394c..3022156a35 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt @@ -2,8 +2,9 @@ package de.westnordost.streetcomplete import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -import de.westnordost.streetcomplete.data.osm.osmquest.SimpleOverpassQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType import de.westnordost.streetcomplete.quests.QuestModule import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import org.mockito.ArgumentMatchers @@ -35,11 +36,14 @@ fun main() { for (questType in registry.all) { if (questType is OsmElementQuestType) { println("### " + questType.javaClass.simpleName) - if (questType is SimpleOverpassQuestType) { - val filters = questType.tagFilters.trimIndent() - println("
\nTag Filters\n\n```\n$filters\n```\n
\n") + if (questType is OsmFilterQuestType) { + val query = "[bbox:{{bbox}}];\n" + questType.filter.toOverpassQLString() + "\n out meta geom;" + println("```\n$query\n```") + } else if (questType is OsmDownloaderQuestType) { + questType.download(bbox, mock()) + } else { + println("Not available, see source code") } - questType.download(bbox, mock()) println() } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt index d03dff9a41..0ec9ecec28 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.osm.elementgeometry import de.westnordost.osmapi.map.MapData +import de.westnordost.osmapi.map.MutableMapData import de.westnordost.osmapi.map.data.* import org.junit.Test @@ -123,15 +124,15 @@ class ElementGeometryCreatorTest { OsmNode(0, 1, P0, null, null, null), OsmNode(1, 1, P1, null, null, null) ) - val nodesById = nodes.associateBy { it.id }.toMutableMap() - val mapData = MapData(nodesById) + val mapData = MutableMapData() + mapData.addAll(nodes) val geom = create(SIMPLE_WAY1, mapData) as ElementPolylinesGeometry assertEquals(listOf(nodes.map { it.position }), geom.polylines) } @Test fun `returns null for non-existent way`() { val way = OsmWay(1L, 1, listOf(1,2,3), null) - assertNull(create(way, MapData())) + assertNull(create(way, MutableMapData())) } @Test fun `positions for relation`() { @@ -153,9 +154,8 @@ class ElementGeometryCreatorTest { OsmNode(3, 1, P3, null, null, null) ) ) - val nodesById = nodesByWayId.flatMap { it.value }.associateBy { it.id }.toMutableMap() - val waysById = ways.associateBy { it.id }.toMutableMap() - val mapData = MapData(nodesById, waysById) + val mapData = MutableMapData() + mapData.addAll(nodesByWayId.values.flatten() + ways) val positions = listOf(P0, P1, P2, P3) val geom = create(relation, mapData) as ElementPolylinesGeometry assertEquals(listOf(positions), geom.polylines) @@ -167,7 +167,7 @@ class ElementGeometryCreatorTest { OsmRelationMember(2L, "", Element.Type.WAY), OsmRelationMember(1L, "", Element.Type.NODE) ), null) - assertNull(create(relation, MapData())) + assertNull(create(relation, MutableMapData())) } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt index 6801db171b..3460ee1d64 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt @@ -106,7 +106,7 @@ class OsmQuestDownloaderTest { private data class ElementWithGeometry(val element: Element, val geometry: ElementGeometry?) -private class ListBackedQuestType(private val list: List) : OsmElementQuestType { +private class ListBackedQuestType(private val list: List) : OsmDownloaderQuestType { override var enabledInCountries: Countries = AllCountries diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestsValidityTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestsValidityTest.kt deleted file mode 100644 index a40ac97493..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/SimpleOverpassQuestsValidityTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import org.junit.Test - -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.quests.QuestModule - -import org.junit.Assert.* - -class SimpleOverpassQuestsValidityTest { - - @Test fun `query valid`() { - val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) - val questTypes = QuestModule.questTypeRegistry(OsmNoteQuestType(), mock(), mock(), mock(), mock(), mock(), mock()).all - - for (questType in questTypes) { - if (questType is SimpleOverpassQuestType<*>) { - // if this fails and the returned exception is not informative, catch here and record - // the name of the SimpleOverpassQuestType - questType.getOverpassQuery(bbox) - } - } - // parsing the query threw no errors -> valid - assertTrue(true) - } -} diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt index 562f1cccea..2a60eaa9cc 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt @@ -2,11 +2,9 @@ package de.westnordost.streetcomplete.data.osm.upload.changesets import android.content.SharedPreferences import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.mock @@ -78,7 +76,6 @@ class OpenQuestChangesetsManagerTest { private class TestQuestType : OsmElementQuestType { override fun getTitle(tags: Map) = 0 - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit) = false override fun isApplicableTo(element: Element):Boolean? = null override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} override val icon = 0 diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBuildingLevelsTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBuildingLevelsTest.kt index e8ca776f32..95f89a0e0f 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBuildingLevelsTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBuildingLevelsTest.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.building_levels.AddBuildingLevels import de.westnordost.streetcomplete.quests.building_levels.BuildingLevelsAnswer import org.junit.Test class AddBuildingLevelsTest { - private val questType = AddBuildingLevels(mock()) + private val questType = AddBuildingLevels() @Test fun `apply building levels answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt index 51f30ed304..d89bfea858 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt @@ -11,7 +11,7 @@ import java.util.* class AddBusStopShelterTest { - private val questType = AddBusStopShelter(mock(), mock()) + private val questType = AddBusStopShelter(mock()) @Test fun `apply shelter yes answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt index 349c1e60be..8a5b6b9637 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt @@ -10,7 +10,7 @@ import java.util.* class AddCrossingTypeTest { - private val questType = AddCrossingType(mock(), mock()) + private val questType = AddCrossingType(mock()) @Test fun `apply normal answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxHeightTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxHeightTest.kt index 1d98d65929..aa1c30e294 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxHeightTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxHeightTest.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.max_height.* import org.junit.Test class AddMaxHeightTest { - private val questType = AddMaxHeight(mock()) + private val questType = AddMaxHeight() @Test fun `apply metric height answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxSpeedTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxSpeedTest.kt index d17f447854..4856a723ee 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxSpeedTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxSpeedTest.kt @@ -2,13 +2,12 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.max_speed.* import org.junit.Test class AddMaxSpeedTest { - private val questType = AddMaxSpeed(mock()) + private val questType = AddMaxSpeed() @Test fun `apply no sign answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxWeightTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxWeightTest.kt index 11a123362c..96a26c93ee 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxWeightTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddMaxWeightTest.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.max_weight.* import org.junit.Test class AddMaxWeightTest { - private val questType = AddMaxWeight(mock()) + private val questType = AddMaxWeight() @Test fun `apply metric weight answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt index 09e71089e8..de6de7d9ce 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt @@ -16,7 +16,7 @@ import java.util.* class AddParkingFeeTest { - private val questType = AddParkingFee(mock(), mock()) + private val questType = AddParkingFee(mock()) private val openingHours = OpeningHoursRuleList(listOf( Rule().apply { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPlaceNameTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPlaceNameTest.kt index fdaeb6d7bc..d3b3c908df 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPlaceNameTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPlaceNameTest.kt @@ -9,7 +9,7 @@ import org.junit.Test class AddPlaceNameTest { - private val questType = AddPlaceName(mock(), mock()) + private val questType = AddPlaceName(mock()) @Test fun `apply no name answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt index b8859a7b45..eea963acf6 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt @@ -8,7 +8,7 @@ import org.junit.Test class AddPostboxCollectionTimesTest { - private val questType = AddPostboxCollectionTimes(mock(), mock()) + private val questType = AddPostboxCollectionTimes(mock()) @Test fun `apply no signed times answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxRefTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxRefTest.kt index 704cdb727b..efc81625c0 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxRefTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxRefTest.kt @@ -1,7 +1,6 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.postbox_ref.AddPostboxRef import de.westnordost.streetcomplete.quests.postbox_ref.NoRefVisible import de.westnordost.streetcomplete.quests.postbox_ref.Ref @@ -9,7 +8,7 @@ import org.junit.Test class AddPostboxRefTest { - private val questType = AddPostboxRef(mock()) + private val questType = AddPostboxRef() @Test fun `apply no ref answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddProhibitedForPedestriansTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddProhibitedForPedestriansTest.kt index 79b8c48935..1658b9dedd 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddProhibitedForPedestriansTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddProhibitedForPedestriansTest.kt @@ -9,7 +9,7 @@ import org.junit.Test class AddProhibitedForPedestriansTest { - private val questType = AddProhibitedForPedestrians(mock()) + private val questType = AddProhibitedForPedestrians() @Test fun `apply yes answer`() { questType.verifyAnswer(YES, StringMapEntryAdd("foot", "no")) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingTypeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingTypeTest.kt index 13ac124ff7..d9eb644e80 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingTypeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingTypeTest.kt @@ -2,14 +2,13 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.recycling.AddRecyclingType import de.westnordost.streetcomplete.quests.recycling.RecyclingType import org.junit.Test class AddRecyclingTypeTest { - private val questType = AddRecyclingType(mock()) + private val questType = AddRecyclingType() @Test fun `apply recycling centre answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddSportTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddSportTest.kt index abf6cca3ba..a8b40d3198 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddSportTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddSportTest.kt @@ -8,7 +8,7 @@ import org.junit.Test class AddSportTest { - private val questType = AddSport(mock()) + private val questType = AddSport() @Test fun `replace hockey when applying answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt new file mode 100644 index 0000000000..2e94056d49 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt @@ -0,0 +1,19 @@ +package de.westnordost.streetcomplete.quests + +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import org.junit.Assert.fail + +fun OsmDownloaderQuestType<*>.verifyDownloadYieldsNoQuest(bbox: BoundingBox) { + download(bbox) { element, _ -> + fail("Expected zero elements. Element returned: ${element.type.name}#${element.id}") + } +} + +fun OsmDownloaderQuestType<*>.verifyDownloadYieldsQuest(bbox: BoundingBox) { + var hasQuest = false + download(bbox) { _, _ -> hasQuest = true } + if (!hasQuest) { + fail("Expected nonzero elements. Elements not returned") + } +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/OsmElementQuestType.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/OsmElementQuestType.kt index 58332e1b4d..9899cb3213 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/OsmElementQuestType.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/OsmElementQuestType.kt @@ -1,28 +1,11 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType - -import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryChange -import org.junit.Assert.fail import org.assertj.core.api.Assertions.* -fun OsmElementQuestType<*>.verifyDownloadYieldsNoQuest(bbox: BoundingBox) { - download(bbox) { element, _ -> - fail("Expected zero elements. Element returned: ${element.type.name}#${element.id}") - } -} - -fun OsmElementQuestType<*>.verifyDownloadYieldsQuest(bbox: BoundingBox) { - var hasQuest = false - download(bbox) { _, _ -> hasQuest = true } - if (!hasQuest) { - fail("Expected nonzero elements. Elements not returned") - } -} - fun OsmElementQuestType.verifyAnswer(tags:Map, answer:T, vararg expectedChanges: StringMapEntryChange) { val cb = StringMapChangesBuilder(tags) this.applyAnswerTo(answer, cb) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt index da09e69803..7cc0ce7874 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/bus_stop_name/AddBusStopNameTest.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.bus_stop_name import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.LocalizedName import de.westnordost.streetcomplete.quests.verifyAnswer import org.junit.Test class AddBusStopNameTest { - private val questType = AddBusStopName(mock()) + private val questType = AddBusStopName() @Test fun `apply no name answer`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashTypeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashTypeTest.kt index ac01641583..662a59c8eb 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashTypeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/car_wash_type/AddCarWashTypeTest.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests.car_wash_type import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.verifyAnswer import org.junit.Test import de.westnordost.streetcomplete.quests.car_wash_type.CarWashType.* class AddCarWashTypeTest { - private val questType = AddCarWashType(mock()) + private val questType = AddCarWashType() @Test fun `only self service`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorTest.kt index 6e4fd82cc2..0450322873 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorTest.kt @@ -1,13 +1,12 @@ package de.westnordost.streetcomplete.quests.clothing_bin_operator import de.westnordost.osmapi.map.data.OsmNode -import de.westnordost.streetcomplete.mock import org.junit.Assert.* import org.junit.Test class AddClothingBinOperatorTest { - private val questType = AddClothingBinOperator(mock()) + private val questType = AddClothingBinOperator() @Test fun `is not applicable to null tags`() { assertFalse(questType.isApplicableTo(create(null))) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt index 8bdd7d6bef..b4d5346a75 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt @@ -1,133 +1,128 @@ package de.westnordost.streetcomplete.quests.oneway -import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.MutableMapData import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.osmapi.map.data.Way -import de.westnordost.streetcomplete.any import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on -import de.westnordost.streetcomplete.quests.verifyDownloadYieldsNoQuest -import de.westnordost.streetcomplete.quests.verifyDownloadYieldsQuest +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test class AddOnewayTest { - private lateinit var overpassMock: OverpassMapDataAndGeometryApi private lateinit var questType: AddOneway @Before fun setUp() { - overpassMock = mock() - questType = AddOneway(overpassMock) + questType = AddOneway() } @Test fun `does not apply to element without tags`() { - setUpElements(noDeadEndWays(null)) - questType.verifyDownloadYieldsNoQuest(mock()) + val mapData = createMapData(noDeadEndWays(null)) + assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `applies to slim road`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = createMapData(noDeadEndWays(mapOf( "highway" to "residential", "width" to "4", "lanes" to "1" ))) - questType.verifyDownloadYieldsQuest(mock()) + assertEquals(1, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to wide road`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = createMapData(noDeadEndWays(mapOf( "highway" to "residential", "width" to "5", "lanes" to "1" ))) - questType.verifyDownloadYieldsNoQuest(mock()) + assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `applies to wider road that has parking lanes`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = createMapData(noDeadEndWays(mapOf( "highway" to "residential", "width" to "12", "lanes" to "1", "parking:lane:both" to "perpendicular", "parking:lane:both:perpendicular" to "on_street" ))) - questType.verifyDownloadYieldsQuest(mock()) + assertEquals(1, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to wider road that has parking lanes but not enough`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = createMapData(noDeadEndWays(mapOf( "highway" to "residential", "width" to "13", "lanes" to "1", "parking:lane:both" to "perpendicular", "parking:lane:both:perpendicular" to "on_street" ))) - questType.verifyDownloadYieldsNoQuest(mock()) + assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to slim road with more than one lane`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = createMapData(noDeadEndWays(mapOf( "highway" to "residential", "width" to "4", "lanes" to "2" ))) - questType.verifyDownloadYieldsNoQuest(mock()) + assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to dead end road #1`() { - setUpElements(listOf( - way(listOf(1,2), mapOf("highway" to "residential")), - way(listOf(2,3), mapOf( + val mapData = createMapData(listOf( + way(1,listOf(1,2), mapOf("highway" to "residential")), + way(2,listOf(2,3), mapOf( "highway" to "residential", "width" to "4", "lanes" to "1" )) )) - questType.verifyDownloadYieldsNoQuest(mock()) + assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to dead end road #2`() { - setUpElements(listOf( - way(listOf(2,3), mapOf( + val mapData = createMapData(listOf( + way(1,listOf(2,3), mapOf( "highway" to "residential", "width" to "4", "lanes" to "1" )), - way(listOf(3,4), mapOf("highway" to "residential")) + way(2,listOf(3,4), mapOf("highway" to "residential")) )) - questType.verifyDownloadYieldsNoQuest(mock()) + assertEquals(0, questType.getApplicableElements(mapData).size) } - @Test fun `does not apply to road that ends as an intersection in another`() { - setUpElements(listOf( - way(listOf(1,2), mapOf("highway" to "residential")), - way(listOf(2,3), mapOf( + @Test fun `applies to road that ends as an intersection in another`() { + val mapData = createMapData(listOf( + way(1,listOf(1,2), mapOf("highway" to "residential")), + way(2,listOf(2,3), mapOf( "highway" to "residential", "width" to "4", "lanes" to "1" )), - way(listOf(5,3,4), mapOf("highway" to "residential")) + way(3,listOf(5,3,4), mapOf("highway" to "residential")) )) - questType.verifyDownloadYieldsQuest(mock()) + assertEquals(1, questType.getApplicableElements(mapData).size) } - private fun setUpElements(ways: List) { - on(overpassMock.query(any(), any())).then { invocation -> - val callback = invocation.getArgument(1) as (element: Element, geometry: ElementGeometry?) -> Unit - for (way in ways) { - callback(way, null) - } - true + private fun createMapData(ways: List) : MapDataWithGeometry { + val mapData = object : MutableMapData(), MapDataWithGeometry { + override fun getNodeGeometry(id: Long): ElementPointGeometry? = null + override fun getWayGeometry(id: Long): ElementGeometry? = null + override fun getRelationGeometry(id: Long): ElementGeometry? = null } + mapData.addAll(ways) + return mapData } - private fun way(nodeIds: List, tags: Map?) = OsmWay(1,1, nodeIds, tags) + private fun way(id: Long, nodeIds: List, tags: Map?) = OsmWay(id,1, nodeIds, tags) private fun noDeadEndWays(tags: Map?): List = listOf( - way(listOf(1,2), mapOf("highway" to "residential")), - way(listOf(2,3), tags), - way(listOf(3,4), mapOf("highway" to "residential")) + way(1,listOf(1,2), mapOf("highway" to "residential")), + way(2,listOf(2,3), tags), + way(3,listOf(3,4), mapOf("highway" to "residential")) ) } diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt index 08b19e27a1..f3ed250654 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt @@ -22,7 +22,7 @@ import java.util.* class AddOpeningHoursTest { - private val questType = AddOpeningHours(mock(), mock(), mock()) + private val questType = AddOpeningHours(mock(), mock()) @Test fun `apply description answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt index 7edef92715..9be620f603 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt @@ -11,7 +11,7 @@ import java.util.* class AddStepsRampTest { - private val questType = AddStepsRamp(mock(), mock()) + private val questType = AddStepsRamp(mock()) @Test fun `apply bicycle ramp answer`() { questType.verifyAnswer( From f04131cb02362857ce902bd121f63fc5fad19277 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 21 Oct 2020 17:13:17 +0200 Subject: [PATCH 21/63] add tests --- .../data/osm/osmquest/TestQuestType.kt | 1 - .../osm/osmquest/OsmApiQuestDownloader.kt | 1 - .../data/osm/osmquest/OsmQuestController.kt | 2 +- .../QuestsOverpassPerformanceMeasurement.kt | 89 ------------------- .../osm/osmquest/OsmApiQuestDownloaderTest.kt | 89 +++++++++++++++++++ .../osm/osmquest/OsmQuestDownloaderTest.kt | 45 ++++------ .../quests/TestMapDataWithGeometry.kt | 22 +++++ .../crossing_island/AddCrossingIslandTest.kt | 35 ++++++++ .../quests/oneway/AddOnewayTest.kt | 40 +++------ .../AddRailwayCrossingBarrierTest.kt | 34 +++++++ .../AddTactilePavingCrosswalkTest.kt | 34 +++++++ 11 files changed, 245 insertions(+), 147 deletions(-) delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt index 80010c8548..e5cc07c500 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt @@ -9,7 +9,6 @@ import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment open class TestQuestType : OsmElementQuestType { override fun getTitle(tags: Map) = 0 - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit) = false override fun isApplicableTo(element: Element):Boolean? = null override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} override val icon = 0 diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index a0230c3dc3..a57c4fe045 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -26,7 +26,6 @@ class OsmApiQuestDownloader @Inject constructor( private val osmApiMapDataProvider: Provider, private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker ) { - // TODO TEST fun download(questTypes: List>, bbox: BoundingBox) { if (questTypes.isEmpty()) return diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt index d0adf9a490..49135a5caa 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt @@ -138,7 +138,7 @@ import javax.inject.Singleton elementType: Element.Type, elementId: Long ): UpdateResult { - geometryDao.put(ElementGeometryEntry(elementType, elementId, updatedGeometry)) // TODO test + geometryDao.put(ElementGeometryEntry(elementType, elementId, updatedGeometry)) val e = ElementKey(elementType, elementId) var deletedCount = removeObsolete(removedIds) diff --git a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt deleted file mode 100644 index 07b15cfcc6..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPerformanceMeasurement.kt +++ /dev/null @@ -1,89 +0,0 @@ -package de.westnordost.streetcomplete - -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.osmapi.overpass.OverpassStatus -import de.westnordost.osmapi.overpass.OverpassStatusParser -import de.westnordost.osmapi.ApiRequestWriter -import de.westnordost.osmapi.OsmConnection -import de.westnordost.osmapi.common.errors.OsmApiException -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType -import de.westnordost.streetcomplete.quests.QuestModule -import java.io.OutputStream -import java.lang.Thread.sleep -import java.net.URLEncoder -import kotlin.system.measureTimeMillis - -fun main() { - - val overpassMapDataDao = TestOverpassMapDataDao() - - val overpassMock: OverpassMapDataAndGeometryApi = mock() - on(overpassMock.query(any(), any())).then { invocation -> - overpassMapDataDao.get(invocation.getArgument(0) as String) - true - } - - val registry = QuestModule.questTypeRegistry(mock(), overpassMock, mock(), mock(), mock(), mock(), mock()) - - val hamburg = BoundingBox(53.5, 9.9, 53.6, 10.0) - - for (questType in registry.all) { - if (questType is OsmDownloaderQuestType) { - print(questType.javaClass.simpleName + ": ") - questType.download(hamburg, mock()) - println() - } - } - println("Total response time: ${overpassMapDataDao.totalTime/1000}s") - println("Total waiting time: ${overpassMapDataDao.totalWaitTime}s") -} - -private class TestOverpassMapDataDao { - - var totalTime = 0L - var totalWaitTime = 0L - - val osm = OsmConnection( - "https://lz4.overpass-api.de/api/", - "StreetComplete Overpass Query Performance Test", - null, - (180 + 4) * 1000) - - fun get(query: String) { - var time: Long - while(true) { - try { - time = measureTimeMillis { - osm.makeRequest("interpreter", "POST", false, getWriter(query), null) - } - break - } catch (e: OsmApiException) { - if (e.errorCode == 429) { - val status = getStatus() - if (status.availableSlots == 0) { - val waitInSeconds = status.nextAvailableSlotIn ?: 60 - totalWaitTime += waitInSeconds - sleep(waitInSeconds * 1000L) - } - continue - } else throw e - } - } - totalTime += time - val s = "%.1f".format(time/1000.0) - print("${s}s ") - } - - private fun getStatus(): OverpassStatus = osm.makeRequest("status", OverpassStatusParser()) - - private fun getWriter(query: String) = object : ApiRequestWriter { - override fun write(out: OutputStream) { - val utf8 = Charsets.UTF_8 - val request = "data=" + URLEncoder.encode(query, utf8.name()) - out.write(request.toByteArray(utf8)) - } - override fun getContentType() = "application/x-www-form-urlencoded" - } -} - diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt new file mode 100644 index 0000000000..f7527ea4c2 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt @@ -0,0 +1,89 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.countryboundaries.CountryBoundaries +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao +import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource +import de.westnordost.streetcomplete.data.quest.AllCountries +import de.westnordost.streetcomplete.data.quest.Countries +import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.on +import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify +import java.util.concurrent.FutureTask +import javax.inject.Provider + +class OsmApiQuestDownloaderTest { + private lateinit var elementDb: MergedElementDao + private lateinit var osmQuestController: OsmQuestController + private lateinit var countryBoundaries: CountryBoundaries + private lateinit var notePositionsSource: NotePositionsSource + private lateinit var osmApiMapData: OsmApiMapData + private lateinit var elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker + private lateinit var downloader: OsmApiQuestDownloader + + private val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) + + @Before fun setUp() { + elementDb = mock() + osmQuestController = mock() + on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) + countryBoundaries = mock() + osmApiMapData = mock() + notePositionsSource = mock() + elementEligibleForOsmQuestChecker = mock() + val countryBoundariesFuture = FutureTask { countryBoundaries } + countryBoundariesFuture.run() + val osmApiMapDataProvider = Provider { osmApiMapData } + downloader = OsmApiQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, osmApiMapDataProvider, elementEligibleForOsmQuestChecker) + } + + @Test fun `creates quest for element`() { + val pos = OsmLatLon(1.0, 1.0) + val node = OsmNode(5, 0, pos, null) + val geom = ElementPointGeometry(pos) + val questType = TestMapDataQuestType(listOf(node)) + + on(osmApiMapData.getGeometry(Element.Type.NODE, 5)).thenReturn(geom) + on(elementEligibleForOsmQuestChecker.mayCreateQuestFrom(any(), any(), any())).thenReturn(true) + on(osmQuestController.replaceInBBox(any(), any(), any())).thenAnswer { + val createdQuests = it.arguments[0] as List + assertEquals(1, createdQuests.size) + val quest = createdQuests[0] + assertEquals(5, quest.elementId) + assertEquals(Element.Type.NODE, quest.elementType) + assertEquals(geom, quest.geometry) + assertEquals(questType, quest.osmElementQuestType) + OsmQuestController.UpdateResult(1,0) + } + + downloader.download(listOf(questType), bbox) + + verify(elementDb).putAll(any()) + verify(elementDb).deleteUnreferenced() + verify(osmQuestController).replaceInBBox(any(), any(), any()) + } +} + +private class TestMapDataQuestType(private val list: List) : OsmMapDataQuestType { + + override var enabledInCountries: Countries = AllCountries + + override val icon = 0 + override val commitMessage = "" + override fun getTitle(tags: Map) = 0 + override fun createForm() = object : AbstractQuestAnswerFragment() {} + override fun isApplicableTo(element: Element) = false + override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} + override fun getApplicableElements(mapData: MapDataWithGeometry): List = list +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt index 3460ee1d64..ca82b0160b 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt @@ -47,37 +47,33 @@ class OsmQuestDownloaderTest { } @Test fun `ignore element with invalid geometry`() { - val invalidGeometryElement = ElementWithGeometry( - OsmNode(0, 0, OsmLatLon(1.0, 1.0), null), - null - ) - - val questType = ListBackedQuestType(listOf(invalidGeometryElement)) + val questType = TestDownloaderQuestType(mapOf( + OsmNode(0, 0, OsmLatLon(1.0, 1.0), null) to + null + )) downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) } @Test fun `ignore at blacklisted position`() { val blacklistPos = OsmLatLon(0.3, 0.4) - val blacklistElement = ElementWithGeometry( - OsmNode(0, 0, blacklistPos, null), - ElementPointGeometry(blacklistPos) - ) on(notePositionsSource.getAllPositions(any())).thenReturn(listOf(blacklistPos)) - val questType = ListBackedQuestType(listOf(blacklistElement)) + val questType = TestDownloaderQuestType(mapOf( + OsmNode(0, 0, blacklistPos, null) to + ElementPointGeometry(blacklistPos) + )) downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) } @Test fun `ignore element in country for which this quest is disabled`() { val pos = OsmLatLon(1.0, 1.0) - val inDisabledCountryElement = ElementWithGeometry( - OsmNode(0, 0, pos, null), - ElementPointGeometry(pos) - ) - val questType = ListBackedQuestType(listOf(inDisabledCountryElement)) + val questType = TestDownloaderQuestType(mapOf( + OsmNode(0, 0, pos, null) to + ElementPointGeometry(pos) + )) questType.enabledInCountries = AllCountriesExcept("AA") // country boundaries say that position is in AA on(countryBoundaries.isInAny(anyDouble(),anyDouble(),any())).thenReturn(true) @@ -88,12 +84,11 @@ class OsmQuestDownloaderTest { @Test fun `creates quest for element`() { val pos = OsmLatLon(1.0, 1.0) - val normalElement = ElementWithGeometry( - OsmNode(0, 0, pos, null), - ElementPointGeometry(pos) - ) - val questType = ListBackedQuestType(listOf(normalElement)) + val questType = TestDownloaderQuestType(mapOf( + OsmNode(0, 0, pos, null) to + ElementPointGeometry(pos) + )) on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) @@ -104,15 +99,13 @@ class OsmQuestDownloaderTest { } } -private data class ElementWithGeometry(val element: Element, val geometry: ElementGeometry?) - -private class ListBackedQuestType(private val list: List) : OsmDownloaderQuestType { +private class TestDownloaderQuestType(private val map: Map) : OsmDownloaderQuestType { override var enabledInCountries: Countries = AllCountries override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - for (e in list) { - handler(e.element, e.geometry) + for ((element, geometry) in map) { + handler(element, geometry) } return true } diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt new file mode 100644 index 0000000000..23375abb4a --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt @@ -0,0 +1,22 @@ +package de.westnordost.streetcomplete.quests + +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.MutableMapData +import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry + +class TestMapDataWithGeometry(elements: Iterable) : MutableMapData(), MapDataWithGeometry { + + init { + addAll(elements) + } + + val nodeGeometriesById: MutableMap = mutableMapOf() + val wayGeometriesById: MutableMap = mutableMapOf() + val relationGeometriesById: MutableMap = mutableMapOf() + + override fun getNodeGeometry(id: Long): ElementPointGeometry? = nodeGeometriesById[id] + override fun getWayGeometry(id: Long): ElementGeometry? = wayGeometriesById[id] + override fun getRelationGeometry(id: Long): ElementGeometry? = relationGeometriesById[id] +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt new file mode 100644 index 0000000000..22615a49b5 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt @@ -0,0 +1,35 @@ +package de.westnordost.streetcomplete.quests.crossing_island + +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.osmapi.map.data.OsmWay +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry +import org.junit.Assert.* +import org.junit.Test + +class AddCrossingIslandTest { + private val questType = AddCrossingIsland() + + @Test fun `applicable to crossing`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "highway" to "crossing", + "crossing" to "something" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to crossing with private road`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "highway" to "crossing", + "crossing" to "something" + )), + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "residential", + "access" to "private" + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt index b4d5346a75..4919d0d02c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt @@ -1,29 +1,21 @@ package de.westnordost.streetcomplete.quests.oneway -import de.westnordost.osmapi.map.MapDataWithGeometry -import de.westnordost.osmapi.map.MutableMapData import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.osmapi.map.data.Way -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry import org.junit.Assert.assertEquals -import org.junit.Before import org.junit.Test class AddOnewayTest { - private lateinit var questType: AddOneway - - @Before fun setUp() { - questType = AddOneway() - } + private val questType = AddOneway() @Test fun `does not apply to element without tags`() { - val mapData = createMapData(noDeadEndWays(null)) + val mapData = TestMapDataWithGeometry(noDeadEndWays(null)) assertEquals(0, questType.getApplicableElements(mapData).size) } @Test fun `applies to slim road`() { - val mapData = createMapData(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "4", "lanes" to "1" @@ -32,7 +24,7 @@ class AddOnewayTest { } @Test fun `does not apply to wide road`() { - val mapData = createMapData(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "5", "lanes" to "1" @@ -41,7 +33,7 @@ class AddOnewayTest { } @Test fun `applies to wider road that has parking lanes`() { - val mapData = createMapData(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "12", "lanes" to "1", @@ -52,7 +44,7 @@ class AddOnewayTest { } @Test fun `does not apply to wider road that has parking lanes but not enough`() { - val mapData = createMapData(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "13", "lanes" to "1", @@ -63,7 +55,7 @@ class AddOnewayTest { } @Test fun `does not apply to slim road with more than one lane`() { - val mapData = createMapData(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "4", "lanes" to "2" @@ -72,7 +64,7 @@ class AddOnewayTest { } @Test fun `does not apply to dead end road #1`() { - val mapData = createMapData(listOf( + val mapData = TestMapDataWithGeometry(listOf( way(1,listOf(1,2), mapOf("highway" to "residential")), way(2,listOf(2,3), mapOf( "highway" to "residential", @@ -84,7 +76,7 @@ class AddOnewayTest { } @Test fun `does not apply to dead end road #2`() { - val mapData = createMapData(listOf( + val mapData = TestMapDataWithGeometry(listOf( way(1,listOf(2,3), mapOf( "highway" to "residential", "width" to "4", @@ -96,7 +88,7 @@ class AddOnewayTest { } @Test fun `applies to road that ends as an intersection in another`() { - val mapData = createMapData(listOf( + val mapData = TestMapDataWithGeometry(listOf( way(1,listOf(1,2), mapOf("highway" to "residential")), way(2,listOf(2,3), mapOf( "highway" to "residential", @@ -108,16 +100,6 @@ class AddOnewayTest { assertEquals(1, questType.getApplicableElements(mapData).size) } - private fun createMapData(ways: List) : MapDataWithGeometry { - val mapData = object : MutableMapData(), MapDataWithGeometry { - override fun getNodeGeometry(id: Long): ElementPointGeometry? = null - override fun getWayGeometry(id: Long): ElementGeometry? = null - override fun getRelationGeometry(id: Long): ElementGeometry? = null - } - mapData.addAll(ways) - return mapData - } - private fun way(id: Long, nodeIds: List, tags: Map?) = OsmWay(id,1, nodeIds, tags) private fun noDeadEndWays(tags: Map?): List = listOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt new file mode 100644 index 0000000000..77dbea81ae --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt @@ -0,0 +1,34 @@ +package de.westnordost.streetcomplete.quests.railway_crossing + +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.osmapi.map.data.OsmWay +import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry +import org.junit.Assert.* +import org.junit.Test + +class AddRailwayCrossingBarrierTest { + private val questType = AddRailwayCrossingBarrier(mock()) + + @Test fun `applicable to crossing`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "railway" to "level_crossing" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to crossing with private road`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "railway" to "level_crossing" + )), + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "residential", + "access" to "private" + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt new file mode 100644 index 0000000000..596e3786f8 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt @@ -0,0 +1,34 @@ +package de.westnordost.streetcomplete.quests.tactile_paving + +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.osmapi.map.data.OsmWay +import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry +import org.junit.Assert.* +import org.junit.Test + +class AddTactilePavingCrosswalkTest { + private val questType = AddTactilePavingCrosswalk(mock()) + + @Test fun `applicable to crossing`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "highway" to "crossing" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to crossing with private road`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "highway" to "crossing" + )), + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "residential", + "access" to "private" + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } +} \ No newline at end of file From 4214deedd963c09e5040a744e83f2e5fc4b4e8a6 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 21 Oct 2020 23:50:32 +0200 Subject: [PATCH 22/63] AddHousenumber: analyze locally --- .../java/de/westnordost/osmapi/map/MapData.kt | 6 + .../data/osm/osmquest/OsmApiMapData.kt | 7 - .../streetcomplete/quests/QuestModule.kt | 2 +- .../quests/housenumber/AddHousenumber.kt | 273 ++++++++---------- .../streetcomplete/util/LatLonRaster.kt | 8 +- .../quests/housenumber/AddHousenumberTest.kt | 3 +- 6 files changed, 134 insertions(+), 165 deletions(-) diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt index d58d92a28b..8c5917051f 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -10,6 +10,12 @@ interface MapDataWithGeometry : MapData { fun getNodeGeometry(id: Long): ElementPointGeometry? fun getWayGeometry(id: Long): ElementGeometry? fun getRelationGeometry(id: Long): ElementGeometry? + + fun getGeometry(elementType: Element.Type, id: Long): ElementGeometry? = when(elementType) { + Element.Type.NODE -> getNodeGeometry(id) + Element.Type.WAY -> getWayGeometry(id) + Element.Type.RELATION -> getRelationGeometry(id) + } } interface MapData : Iterable { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt index bd361f6fe8..a12e179816 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt @@ -3,7 +3,6 @@ package de.westnordost.streetcomplete.data.osm.osmquest import de.westnordost.osmapi.common.errors.OsmQueryTooBigException import de.westnordost.osmapi.map.* import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Element.Type.* import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.streetcomplete.data.MapDataApi @@ -76,12 +75,6 @@ class OsmApiMapData @Inject constructor( } } - fun getGeometry(elementType: Element.Type, id: Long): ElementGeometry? = when(elementType) { - NODE -> getNodeGeometry(id) - WAY -> getWayGeometry(id) - RELATION -> getRelationGeometry(id) - } - private fun ensureRelationIsComplete(id: Long) { /* conditionally need to fetch from OSM API here because the nodes and ways of relations are not included in the normal map download call if not all are in the bbox */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index e64306e38c..0b5a452817 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -119,7 +119,7 @@ object QuestModule AddBusStopName(), AddBusStopRef(), AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings - AddHousenumber(o), + AddHousenumber(), AddAddressStreet(o, roadNameSuggestionsDao), MarkCompletedHighwayConstruction(r), AddReligionToPlaceOfWorship(), // icons on maps are different - OSM Carto, mapy.cz, OsmAnd, Sputnik etc diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index d09b12b9c2..3a46093de9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -1,28 +1,18 @@ package de.westnordost.streetcomplete.quests.housenumber -import android.util.Log +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.data.* -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.LatLon import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.elementfilter.DEFAULT_MAX_QUESTS -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.elementfilter.toOverpassBboxFilter -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType -import de.westnordost.streetcomplete.util.FlattenIterable +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.util.LatLonRaster -import de.westnordost.streetcomplete.util.enclosingBoundingBox import de.westnordost.streetcomplete.util.isInMultipolygon -class AddHousenumber(private val overpass: OverpassMapDataAndGeometryApi) : - OsmDownloaderQuestType { +class AddHousenumber : OsmMapDataQuestType { override val commitMessage = "Add housenumbers" override val wikiLink = "Key:addr" @@ -40,111 +30,90 @@ class AddHousenumber(private val overpass: OverpassMapDataAndGeometryApi) : override fun getTitle(tags: Map) = R.string.quest_address_title - override fun isApplicableTo(element: Element): Boolean? = null + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val bbox = mapData.boundingBox ?: return listOf() - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - var ms = System.currentTimeMillis() + val addressNodesById = mapData.nodes.filter { nodesWithAddressFilter.matches(it) }.associateBy { it.id } + val addressNodeIds = addressNodesById.keys - val buildings = downloadBuildingsWithoutAddresses(bbox) ?: return false - // empty result: We are done - if (buildings.isEmpty()) return true + /** filter: only buildings with no address that usually should have an address + * ...that do not have an address node on their outline */ - val addrAreas = downloadAreasWithAddresses(bbox) ?: return false + val buildings = mapData.filter { + buildingsWithMissingAddressFilter.matches(it) + && !it.containsAnyNode(addressNodeIds, mapData) + }.toMutableList() - val extendedBbox = getBoundingBoxThatIncludes(buildings.values) + if (buildings.isEmpty()) return listOf() - val addrPositions = downloadFreeFloatingPositionsWithAddresses(extendedBbox) ?: return false + /** exclude buildings which are included in relations that have an address */ - Log.d("AddHousenumber", "Downloaded ${buildings.size} buildings with no address, " + - "${addrAreas.size} areas with address and ${addrPositions.size} address nodes " + - "in ${System.currentTimeMillis() - ms}ms" - ) - ms = System.currentTimeMillis() + val relationsWithAddress = mapData.relations.filter { nonMultipolygonRelationsWithAddressFilter.matches(it) } - val buildingPositions = LatLonRaster(extendedBbox, 0.0005) - for (buildingCenter in buildings.keys) { - buildingPositions.insert(buildingCenter) + buildings.removeAll { building -> + relationsWithAddress.any { it.containsWay(building.id) } } - // exclude buildings that are contained in an area with a housenumber - for (addrArea in addrAreas) { - for (buildingPos in buildingPositions.getAll(addrArea.getBounds())) { - if (buildingPos.isInMultipolygon(addrArea.polygons)) { - buildings.remove(buildingPos) - } - } - } + if (buildings.isEmpty()) return listOf() - var createdQuests = 0 - // only buildings with no housenumber-nodes inside them - for (building in buildings.values) { - // even though we could continue here, limit the max amount of quests created to the - // default maximum to avoid performance problems - if (createdQuests++ >= DEFAULT_MAX_QUESTS) break - - val addrContainedInBuilding = getPositionContainedInBuilding(building.geometry, addrPositions) - if (addrContainedInBuilding != null) { - addrPositions.remove(addrContainedInBuilding) - continue - } + /** exclude buildings that intersect with the bounding box because it is not possible to + ascertain for these if there is an address node within the building - it could be outside + the bounding box */ - handler(building.element, building.geometry) + val buildingGeometriesById = buildings.associate { + it.id to mapData.getGeometry(it.type, it.id) as? ElementPolygonsGeometry } - Log.d("AddHousenumber", "Processing data took ${System.currentTimeMillis() - ms}ms") + buildings.removeAll { building -> + val buildingBounds = buildingGeometriesById[building.id]?.getBounds() + (buildingBounds == null || !buildingBounds.isCompletelyInside(bbox)) + } - return true - } + if (buildings.isEmpty()) return listOf() - private fun downloadBuildingsWithoutAddresses(bbox: BoundingBox): MutableMap? { - val buildingsByCenterPoint = mutableMapOf() - val query = getBuildingsWithoutAddressesOverpassQuery(bbox) - val success = overpass.query(query) { element, geometry -> - if (geometry is ElementPolygonsGeometry) { - buildingsByCenterPoint[geometry.center] = ElementWithArea(element, geometry) - } - } - return if (success) buildingsByCenterPoint else null - } + /** exclude buildings that contain an address node somewhere within their area */ - private fun downloadFreeFloatingPositionsWithAddresses(bbox: BoundingBox): LatLonRaster? { - val grid = LatLonRaster(bbox, 0.0005) - val query = getFreeFloatingAddressesOverpassQuery(bbox) - val success = overpass.query(query) { _, geometry -> - if (geometry != null) grid.insert(geometry.center) + val addressPositions = LatLonRaster(bbox, 0.0005) + for (node in addressNodesById.values) { + addressPositions.insert(node.position) } - return if (success) grid else null - } - private fun downloadAreasWithAddresses(bbox: BoundingBox): List? { - val areas = mutableListOf() - val query = getNonBuildingAreasWithAddressesOverpassQuery(bbox) - val success = overpass.query(query) { _, geometry -> - if (geometry is ElementPolygonsGeometry) areas.add(geometry) + buildings.removeAll { building -> + val buildingGeometry = buildingGeometriesById[building.id] + if (buildingGeometry != null) { + val nearbyAddresses = addressPositions.getAll(buildingGeometry.getBounds()) + nearbyAddresses.any { it.isInMultipolygon(buildingGeometry.polygons) } + } else true } - return if (success) areas else null - } - private fun getPositionContainedInBuilding(building: ElementPolygonsGeometry, positions: LatLonRaster): LatLon? { - for (pos in positions.getAll(building.getBounds())) { - if (pos.isInMultipolygon(building.polygons)) return pos + if (buildings.isEmpty()) return listOf() + + /** exclude buildings that are contained in an area that has an address */ + + val areasWithAddresses = mapData + .filter { nonBuildingAreasWithAddressFilter.matches(it) } + .mapNotNull { mapData.getGeometry(it.type, it.id) as? ElementPolygonsGeometry } + + val buildingsByCenterPosition: Map = buildings.associateBy { buildingGeometriesById[it.id]?.center } + + val buildingPositions = LatLonRaster(bbox, 0.0005) + for (buildingCenterPosition in buildingsByCenterPosition.keys) { + if (buildingCenterPosition != null) buildingPositions.insert(buildingCenterPosition) } - return null - } - private fun getBoundingBoxThatIncludes(buildings: Iterable): BoundingBox { - // see #885: The area in which the app should search for address nodes (and areas) must be - // adjusted to the bounding box of all the buildings found. The found buildings may in parts - // not be within the specified bounding box. But in exactly that part, there may be an - // address + for (areaWithAddress in areasWithAddresses) { + val nearbyBuildings = buildingPositions.getAll(areaWithAddress.getBounds()) + val buildingPositionsInArea = nearbyBuildings.filter { it.isInMultipolygon(areaWithAddress.polygons) } + val buildingsInArea = buildingPositionsInArea.mapNotNull { buildingsByCenterPosition[it] } - val allThePoints = FlattenIterable(LatLon::class.java) - for (building in buildings) { - allThePoints.add(building.geometry.polygons) + buildings.removeAll(buildingsInArea) } - return allThePoints.enclosingBoundingBox() + + return buildings } + override fun isApplicableTo(element: Element): Boolean? = null + override fun createForm() = AddHousenumberForm() override fun applyAnswerTo(answer: HousenumberAnswer, changes: StringMapChangesBuilder) { @@ -163,66 +132,70 @@ class AddHousenumber(private val overpass: OverpassMapDataAndGeometryApi) : } is HouseAndBlockNumber -> { changes.add("addr:housenumber", answer.houseNumber) - changes.add("addr:block_number", answer.blockNumber) + changes.addOrModify("addr:block_number", answer.blockNumber) } } } - -} - -/** Query that returns all areas that are not buildings but have addresses */ -private fun getNonBuildingAreasWithAddressesOverpassQuery(bbox: BoundingBox): String { - val globalBbox = bbox.toGlobalOverpassBBox() - return """ - $globalBbox - wr[!building] $ANY_ADDRESS_FILTER; - out geom; - """.trimIndent() } -/** Query that returns all buildings that don't have an address node on their outline, nor are - * part of a relation that has a housenumber */ -private fun getBuildingsWithoutAddressesOverpassQuery(bbox: BoundingBox): String { - val bboxFilter = bbox.toOverpassBboxFilter() - return """ - ( - way$BUILDINGS_WITHOUT_ADDRESS_FILTER$bboxFilter; - rel$BUILDINGS_WITHOUT_ADDRESS_FILTER$bboxFilter; - ) -> .buildings; - - .buildings << -> .relations_containing_buildings; - rel.relations_containing_buildings$ANY_ADDRESS_FILTER; >> -> .elements_contained_in_relations_with_addr; - - .buildings > -> .building_nodes; - node.building_nodes$ANY_ADDRESS_FILTER; < -> .buildings_with_addr_nodes; - - ((.buildings; - .buildings_with_addr_nodes;); - .elements_contained_in_relations_with_addr; ); - out meta geom; - """.trimIndent() -} - -/** Query that returns all address nodes that are not part of any building outline */ -private fun getFreeFloatingAddressesOverpassQuery(bbox: BoundingBox): String { - val globalBbox = bbox.toGlobalOverpassBBox() - return """ - $globalBbox - ( - node$ANY_ADDRESS_FILTER; - - (wr[building];>;); - ); - out skel; - """.trimIndent() -} - -private data class ElementWithArea(val element: Element, val geometry: ElementPolygonsGeometry) - -private const val ANY_ADDRESS_FILTER = - "[~'^addr:(housenumber|housename|conscriptionnumber|streetnumber)$'~'.']" +private val nonBuildingAreasWithAddressFilter by lazy { ElementFiltersParser().parse(""" + ways, relations with + !building and ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" +""")} + +private val nonMultipolygonRelationsWithAddressFilter by lazy { ElementFiltersParser().parse(""" + relations with + type != multipolygon + and ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" +""")} + +private val nodesWithAddressFilter by lazy { ElementFiltersParser().parse(""" + nodes with ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" +""")} + +private val buildingsWithMissingAddressFilter by lazy { ElementFiltersParser().parse(""" + ways, relations with + building ~ ${buildingTypesThatShouldHaveAddresses.joinToString("|")} + and location != underground + and ruins != yes + and abandoned != yes + and !addr:housenumber + and !addr:housename + and !addr:conscriptionnumber + and !addr:streetnumber + and !noaddress + and !nohousenumber +""")} + +private val buildingTypesThatShouldHaveAddresses = listOf( + "house", "residential", "apartments", "detached", "terrace", "dormitory", "semi", + "semidetached_house", "farm", "school", "civic", "college", "university", "public", "hospital", + "kindergarten", "train_station", "hotel", "retail", "commercial" +) + +/** returns whether this bounding box is completely inside the other */ +private fun BoundingBox.isCompletelyInside(other: BoundingBox): Boolean = + minLongitude > other.minLongitude && + minLatitude > other.minLatitude && + maxLongitude < other.maxLongitude && + maxLatitude < other.maxLatitude + +private fun Element.containsAnyNode(nodeIds: Set, mapData: MapDataWithGeometry): Boolean = + when (this) { + is Way -> this.nodeIds.any { it in nodeIds } + is Relation -> containsAnyNode(nodeIds, mapData) + else -> false + } -private const val NO_ADDRESS_FILTER = - "[!'addr:housenumber'][!'addr:housename'][!'addr:conscriptionnumber'][!'addr:streetnumber'][!noaddress][!nohousenumber]" +/** return whether any way contained in this relation contains any of the nodes with the given ids */ +private fun Relation.containsAnyNode(nodeIds: Set, mapData: MapDataWithGeometry): Boolean = + members + .filter { it.type == Element.Type.WAY } + .any { member -> + val way = mapData.getWay(member.ref)!! + way.nodeIds.any { it in nodeIds } + } -private const val BUILDINGS_WITHOUT_ADDRESS_FILTER = - "['building'~'^(house|residential|apartments|detached|terrace|dormitory|semi|semidetached_house|farm|" + - "school|civic|college|university|public|hospital|kindergarten|train_station|hotel|" + - "retail|commercial)$'][location!=underground][ruins!=yes]" + NO_ADDRESS_FILTER +/** return whether any of the ways with the given ids are contained in this relation */ +private fun Relation.containsWay(wayId: Long): Boolean = + members.any { it.type == Element.Type.WAY && wayId == it.ref } \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/LatLonRaster.kt b/app/src/main/java/de/westnordost/streetcomplete/util/LatLonRaster.kt index bccb048637..2621b2a190 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/LatLonRaster.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/LatLonRaster.kt @@ -31,7 +31,7 @@ class LatLonRaster(bounds: BoundingBox, private val cellSize: Double) { fun insert(p: LatLon) { val x = longitudeToCellX(p.longitude) val y = latitudeToCellY(p.latitude) - checkBounds(x, y) + if(!isInsideBounds(x, y)) return var list = raster[y * rasterWidth + x] if (list == null) { list = ArrayList() @@ -66,10 +66,8 @@ class LatLonRaster(bounds: BoundingBox, private val cellSize: Double) { return result } - private fun checkBounds(x: Int, y: Int) { - require(x in 0 until rasterWidth) { "Longitude is out of bounds" } - require(y in 0 until rasterHeight) { "Latitude is out of bounds" } - } + private fun isInsideBounds(x: Int, y: Int): Boolean = + x in 0 until rasterWidth && y in 0 until rasterHeight private fun longitudeToCellX(longitude: Double) = floor(normalizeLongitude(longitude - bbox.minLongitude) / cellSize).toInt() diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt index 40e47cb85e..baf0151d73 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt @@ -1,7 +1,6 @@ package de.westnordost.streetcomplete.quests.housenumber import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.verifyAnswer import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -9,7 +8,7 @@ import org.junit.Test class AddHousenumberTest { - private val questType = AddHousenumber(mock()) + private val questType = AddHousenumber() @Test fun `housenumber regex`() { val r = VALID_HOUSENUMBER_REGEX From 934fd91d7a4cd66a04ce2d04b25f3482e0cc4587 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 22 Oct 2020 00:54:25 +0200 Subject: [PATCH 23/63] add tests for housenumber quest creation --- .../quests/housenumber/AddHousenumber.kt | 8 +- .../quests/housenumber/AddHousenumberTest.kt | 129 +++++++++++++++++- 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index 3a46093de9..e7ada5f303 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -175,10 +175,10 @@ private val buildingTypesThatShouldHaveAddresses = listOf( /** returns whether this bounding box is completely inside the other */ private fun BoundingBox.isCompletelyInside(other: BoundingBox): Boolean = - minLongitude > other.minLongitude && - minLatitude > other.minLatitude && - maxLongitude < other.maxLongitude && - maxLatitude < other.maxLatitude + minLongitude >= other.minLongitude && + minLatitude >= other.minLatitude && + maxLongitude <= other.maxLongitude && + maxLatitude <= other.maxLatitude private fun Element.containsAnyNode(nodeIds: Set, mapData: MapDataWithGeometry): Boolean = when (this) { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt index baf0151d73..d82050e7a3 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt @@ -1,15 +1,105 @@ package de.westnordost.streetcomplete.quests.housenumber +import de.westnordost.osmapi.map.data.* import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry import de.westnordost.streetcomplete.quests.verifyAnswer -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue +import org.junit.Assert.* import org.junit.Test class AddHousenumberTest { private val questType = AddHousenumber() + @Test fun `does not create quest for generic building`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf("building" to "yes")) to POSITIONS1 + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building with address`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached", + "addr:housenumber" to "123" + )) to POSITIONS1 + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does create quest for building without address`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to POSITIONS1 + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building with address node on outline`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to POSITIONS1, + OsmNode(2L, 1, P2, mapOf( + "addr:housenumber" to "123" + )) to ElementPointGeometry(P2) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building that is part of a relation with an address`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to POSITIONS1, + OsmRelation(2L, 1, listOf( + OsmRelationMember(1L, "something", Element.Type.WAY) + ), mapOf( + "addr:housenumber" to "123" + )) to ElementPointGeometry(P2) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building that is inside an area with an address`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to POSITIONS1, + OsmWay(1L, 1, NODES2, mapOf( + "addr:housenumber" to "123", + "amenity" to "school", + )) to POSITIONS2, + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building that contains an address node`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to POSITIONS1, + OsmNode(1L, 1, PC, mapOf( + "addr:housenumber" to "123" + )) to ElementPointGeometry(PC), + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `does not create quest for building that intersects bounding box`() { + val mapData = createMapData(mapOf( + OsmWay(1L, 1, NODES1, mapOf( + "building" to "detached" + )) to ElementPolygonsGeometry(listOf(listOf(P1, P2, PO, P4, P1)), PC) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + @Test fun `housenumber regex`() { val r = VALID_HOUSENUMBER_REGEX assertTrue("1".matches(r)) @@ -87,3 +177,38 @@ class AddHousenumberTest { ) } } + +private val P1 = OsmLatLon(0.25,0.25) +private val P2 = OsmLatLon(0.25,0.75) +private val P3 = OsmLatLon(0.75,0.75) +private val P4 = OsmLatLon(0.75,0.25) + +private val P5 = OsmLatLon(0.1,0.1) +private val P6 = OsmLatLon(0.1,0.9) +private val P7 = OsmLatLon(0.9,0.9) +private val P8 = OsmLatLon(0.9,0.1) + +private val PO = OsmLatLon(1.5, 1.5) +private val PC = OsmLatLon(0.5,0.5) + +private val NODES1 = listOf(1,2,3,4,1) +private val NODES2 = listOf(5,6,7,8,5) + +private val POSITIONS1 = ElementPolygonsGeometry(listOf(listOf(P1, P2, P3, P4, P1)), PC) +private val POSITIONS2 = ElementPolygonsGeometry(listOf(listOf(P5, P6, P7, P8, P5)), PC) + +private fun createMapData(elements: Map): TestMapDataWithGeometry { + val result = TestMapDataWithGeometry(elements.keys) + for((element, geometry) in elements) { + when(element) { + is Node -> + result.nodeGeometriesById[element.id] = geometry as ElementPointGeometry + is Way -> + result.wayGeometriesById[element.id] = geometry + is Relation -> + result.relationGeometriesById[element.id] = geometry + } + } + result.handle(BoundingBox(0.0, 0.0, 1.0, 1.0)) + return result +} \ No newline at end of file From cdb81b8c321780020d9966121c63d34272c513e4 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 22 Oct 2020 13:57:30 +0200 Subject: [PATCH 24/63] allow creation of incomplete element geometry (for relations) --- .../elementgeometry/ElementGeometryCreator.kt | 40 ++++++++++++------- .../ElementGeometryCreatorTest.kt | 40 ++++++++++++++++++- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt index d486ec784e..256b5aa02e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreator.kt @@ -17,10 +17,12 @@ class ElementGeometryCreator @Inject constructor() { * * @param element the element to create the geometry for * @param mapData the MapData that contains the elements with the necessary + * @param allowIncomplete whether incomplete relations should return an incomplete + * ElementGeometry (otherwise: null) * * @return an ElementGeometry or null if any necessary element to create the geometry is not * in the given MapData */ - fun create(element: Element, mapData: MapData): ElementGeometry? { + fun create(element: Element, mapData: MapData, allowIncomplete: Boolean = false): ElementGeometry? { when(element) { is Node -> { return create(element) @@ -30,7 +32,7 @@ class ElementGeometryCreator @Inject constructor() { return create(element, positions) } is Relation -> { - val positionsByWayId = mapData.getWaysNodePositions(element) ?: return null + val positionsByWayId = mapData.getWaysNodePositions(element, allowIncomplete) ?: return null return create(element, positionsByWayId) } else -> return null @@ -73,6 +75,7 @@ class ElementGeometryCreator @Inject constructor() { * @return an ElementPolygonsGeometry if the relation describes an area or an * ElementPolylinesGeometry if it describes is a linear feature */ fun create(relation: Relation, wayGeometries: Map>): ElementGeometry? { + return if (relation.isArea()) { createMultipolygonGeometry(relation, wayGeometries) } else { @@ -133,17 +136,17 @@ class ElementGeometryCreator @Inject constructor() { private fun getRelationMemberWaysNodePositions( relation: Relation, wayGeometries: Map> ): List> { - return relation.members.filter { it.type == Element.Type.WAY }.mapNotNull { - getValidNodePositions(wayGeometries[it.ref]) - } + return relation.members + .filter { it.type == Element.Type.WAY } + .mapNotNull { getValidNodePositions(wayGeometries[it.ref]) } } private fun getRelationMemberWaysNodePositions( relation: Relation, withRole: String, wayGeometries: Map> ): List> { - return relation.members.filter { it.type == Element.Type.WAY && it.role == withRole }.mapNotNull { - getValidNodePositions(wayGeometries[it.ref]) - } + return relation.members + .filter { it.type == Element.Type.WAY && it.role == withRole } + .mapNotNull { getValidNodePositions(wayGeometries[it.ref]) } } private fun getValidNodePositions(wayGeometry: List?): List? { @@ -238,14 +241,21 @@ private fun MapData.getNodePositions(way: Way): List? { } } -private fun MapData.getWaysNodePositions(relation: Relation): Map>? { +private fun MapData.getWaysNodePositions(relation: Relation, allowIncomplete: Boolean = false): Map>? { val wayMembers = relation.members.filter { it.type == Element.Type.WAY } - return wayMembers.associate { wayMember -> - val way = getWay(wayMember.ref) ?: return null - val wayPositions = way.nodeIds.map { nodeId -> - val node = getNode(nodeId) ?: return null - node.position + val result = mutableMapOf>() + for (wayMember in wayMembers) { + val way = getWay(wayMember.ref) + if (way != null) { + val wayPositions = getNodePositions(way) + if (wayPositions != null) { + result[way.id] = wayPositions + } else { + if (!allowIncomplete) return null + } + } else { + if (!allowIncomplete) return null } - way.id to wayPositions } + return result } \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt index 0ec9ecec28..800c548b0c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/elementgeometry/ElementGeometryCreatorTest.kt @@ -169,6 +169,42 @@ class ElementGeometryCreatorTest { ), null) assertNull(create(relation, MutableMapData())) } + + @Test fun `returns null for relation with a way that's missing from map data`() { + val relation = OsmRelation(1L, 1, listOf( + OsmRelationMember(0L, "", Element.Type.WAY), + OsmRelationMember(1L, "", Element.Type.WAY) + ), null) + val mapData = MutableMapData() + mapData.addAll(listOf( + relation, + OsmWay(0, 0, listOf(0,1), null), + OsmNode(0, 0, P0, null), + OsmNode(1, 0, P1, null) + )) + + assertNull(create(relation, mapData)) + } + + @Test fun `does not return null for relation with a way that's missing from map data if returning incomplete geometries is ok`() { + val relation = OsmRelation(1L, 1, listOf( + OsmRelationMember(0L, "", Element.Type.WAY), + OsmRelationMember(1L, "", Element.Type.WAY) + ), null) + val way = OsmWay(0, 0, listOf(0,1), null) + val mapData = MutableMapData() + mapData.addAll(listOf( + relation, + way, + OsmNode(0, 0, P0, null), + OsmNode(1, 0, P1, null) + )) + + assertEquals( + create(way), + create(relation, mapData, true) + ) + } } private fun create(node: Node) = @@ -180,8 +216,8 @@ private fun create(way: Way) = private fun create(relation: Relation) = ElementGeometryCreator().create(relation, WAY_GEOMETRIES) -private fun create(element: Element, mapData: MapData) = - ElementGeometryCreator().create(element, mapData) +private fun create(element: Element, mapData: MapData, allowIncomplete: Boolean = false) = + ElementGeometryCreator().create(element, mapData, allowIncomplete) private val WAY_AREA = mapOf("area" to "yes") From 218fbd719485a60a3cef290f39cd9a21e5ee3cb8 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 22 Oct 2020 18:33:06 +0200 Subject: [PATCH 25/63] don't require full relation geometry for quest analysis; multithreading --- .../java/de/westnordost/osmapi/map/MapData.kt | 15 ++ .../osmquest/CachingMapDataWithGeometry.kt | 55 +++++++ .../data/osm/osmquest/OsmApiMapData.kt | 119 --------------- .../osm/osmquest/OsmApiQuestDownloader.kt | 140 +++++++++++++++--- .../streetcomplete/quests/QuestModule.kt | 2 +- .../quests/bikeway/AddCycleway.kt | 1 - .../summit_register/AddSummitRegister.kt | 72 +++++---- .../osm/osmquest/OsmApiQuestDownloaderTest.kt | 18 ++- 8 files changed, 233 insertions(+), 189 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/CachingMapDataWithGeometry.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt diff --git a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt index 8c5917051f..5e8718a822 100644 --- a/app/src/main/java/de/westnordost/osmapi/map/MapData.kt +++ b/app/src/main/java/de/westnordost/osmapi/map/MapData.kt @@ -68,3 +68,18 @@ open class MutableMapData : MapData, MapDataHandler { return elements.iterator() } } + +fun MapData.isRelationComplete(id: Long): Boolean = + getRelation(id)?.members?.all { member -> + when (member.type!!) { + Element.Type.NODE -> getNode(member.ref) != null + Element.Type.WAY -> getWay(member.ref) != null && isWayComplete(member.ref) + /* not being recursive here is deliberate. sub-relations are considered not relevant + for the element geometry in StreetComplete (and OSM API call to get a "complete" + relation also does not include sub-relations) */ + Element.Type.RELATION -> getRelation(member.ref) != null + } + } ?: false + +fun MapData.isWayComplete(id: Long): Boolean = + getWay(id)?.nodeIds?.all { getNode(it) != null } ?: false \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/CachingMapDataWithGeometry.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/CachingMapDataWithGeometry.kt new file mode 100644 index 0000000000..fcdb464c06 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/CachingMapDataWithGeometry.kt @@ -0,0 +1,55 @@ +package de.westnordost.streetcomplete.data.osm.osmquest + +import de.westnordost.osmapi.map.MapDataWithGeometry +import de.westnordost.osmapi.map.MutableMapData +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry +import javax.inject.Inject + +/** MapDataWithGeometry that lazily creates the element geometry. Will create incomplete (relation) + * geometry */ +class CachingMapDataWithGeometry @Inject constructor( + private val elementGeometryCreator: ElementGeometryCreator +) : MutableMapData(), MapDataWithGeometry { + + private val nodeGeometriesById: MutableMap = mutableMapOf() + private val wayGeometriesById: MutableMap = mutableMapOf() + private val relationGeometriesById: MutableMap = mutableMapOf() + + override fun getNodeGeometry(id: Long): ElementPointGeometry? { + val node = nodesById[id] ?: return null + if (!nodeGeometriesById.containsKey(id)) { + synchronized(nodeGeometriesById) { + if (!nodeGeometriesById.containsKey(id)) { + nodeGeometriesById[id] = elementGeometryCreator.create(node) + } + } + } + return nodeGeometriesById[id] + } + + override fun getWayGeometry(id: Long): ElementGeometry? { + val way = waysById[id] ?: return null + if (!wayGeometriesById.containsKey(id)) { + synchronized(wayGeometriesById) { + if (!wayGeometriesById.containsKey(id)) { + wayGeometriesById[id] = elementGeometryCreator.create(way, this, true) + } + } + } + return wayGeometriesById[id] + } + + override fun getRelationGeometry(id: Long): ElementGeometry? { + val relation = relationsById[id] ?: return null + if (!relationGeometriesById.containsKey(id)) { + synchronized(relationGeometriesById) { + if (!relationGeometriesById.containsKey(id)) { + relationGeometriesById[id] = elementGeometryCreator.create(relation, this, true) + } + } + } + return relationGeometriesById[id] + } +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt deleted file mode 100644 index a12e179816..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiMapData.kt +++ /dev/null @@ -1,119 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.osmapi.common.errors.OsmQueryTooBigException -import de.westnordost.osmapi.map.* -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element.Type.* -import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.streetcomplete.data.MapDataApi -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry -import javax.inject.Inject - -// TODO TEST - -// TODO thread safety! - should be safe if it is going to be accessed by a thread pool (which makes sense) -/** MapDataWithGeometry that fetches the necessary data from the OSM API itself */ -class OsmApiMapData @Inject constructor( - private val mapDataApi: MapDataApi, - private val elementGeometryCreator: ElementGeometryCreator -) : MutableMapData(), MapDataWithGeometry { - - private val nodeGeometriesById: MutableMap = mutableMapOf() - private val wayGeometriesById: MutableMap = mutableMapOf() - private val relationGeometriesById: MutableMap = mutableMapOf() - - /* caching which ways and relations are complete so they do not need to be checked every time - they are accessed */ - private val completeWays: MutableSet = mutableSetOf() - private val completeRelations: MutableSet = mutableSetOf() - - /* overridden because the publicly accessible bounding box should be the bbox of the initial - download, not of any consecutive ones */ - override var boundingBox: BoundingBox? = null - - fun initWith(boundingBox: BoundingBox) { - check(this.boundingBox == null) - this.boundingBox = boundingBox - - getMapAndHandleTooBigQuery(boundingBox) - // we know that all ways included in the initial download are complete - completeWays.addAll(waysById.keys) - } - - private fun getMapAndHandleTooBigQuery(bounds: BoundingBox) { - try { - mapDataApi.getMap(bounds, this) - } catch (e : OsmQueryTooBigException) { - for (subBounds in bounds.splitIntoFour()) { - getMapAndHandleTooBigQuery(subBounds) - } - } - } - - override fun getNodeGeometry(id: Long): ElementPointGeometry? { - if (!nodesById.containsKey(id)) return null - return nodeGeometriesById.getOrPut(id) { - elementGeometryCreator.create(nodesById.getValue(id)) - } - } - - override fun getWayGeometry(id: Long): ElementGeometry? { - if (!waysById.containsKey(id)) return null - return wayGeometriesById.getOrPut(id) { - ensureWayIsComplete(id) - elementGeometryCreator.create(waysById.getValue(id), this) - } - } - - override fun getRelationGeometry(id: Long): ElementGeometry? { - if (!relationsById.containsKey(id)) return null - return relationGeometriesById.getOrPut(id) { - ensureRelationIsComplete(id) - elementGeometryCreator.create(relationsById.getValue(id), this) - } - } - - private fun ensureRelationIsComplete(id: Long) { - /* conditionally need to fetch from OSM API here because the nodes and ways of relations - are not included in the normal map download call if not all are in the bbox */ - if (!completeRelations.contains(id)) { - if (!isRelationComplete(id)) mapDataApi.getRelationComplete(id, this) - completeRelations.add(id) - } - } - - private fun ensureWayIsComplete(id: Long) { - /* conditionally need to fetch additional data from OSM API here */ - if (!completeWays.contains(id)) { - if (!isWayComplete(id)) mapDataApi.getWayComplete(id, this) - completeWays.add(id) - } - } - - private fun isRelationComplete(id: Long): Boolean = - relationsById.getValue(id).members.all { - when (it.type!!) { - NODE -> nodesById.containsKey(it.ref) - WAY -> waysById.containsKey(it.ref) && isWayComplete(it.ref) - /* not being recursive here is deliberate. sub-relations are considered not relevant - for the element geometry in StreetComplete */ - RELATION -> relationsById.containsKey(it.ref) - } - } - - private fun isWayComplete(id: Long): Boolean = - waysById.getValue(id).nodeIds.all { nodesById.containsKey(it) } - -} - -private fun BoundingBox.splitIntoFour(): List { - val center = OsmLatLon((maxLatitude + minLatitude) / 2, (maxLongitude + minLongitude) / 2) - return listOf( - BoundingBox(minLatitude, minLongitude, center.latitude, center.longitude), - BoundingBox(minLatitude, center.longitude, center.latitude, maxLongitude), - BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), - BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) - ) -} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index a57c4fe045..84b4228949 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -3,13 +3,26 @@ package de.westnordost.streetcomplete.data.osm.osmquest import android.util.Log import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.countryboundaries.intersects +import de.westnordost.osmapi.common.errors.OsmQueryTooBigException +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Element.Type.* import de.westnordost.osmapi.map.data.LatLon import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.getRelationComplete +import de.westnordost.osmapi.map.handler.MapDataHandler +import de.westnordost.osmapi.map.isRelationComplete +import de.westnordost.streetcomplete.data.MapDataApi +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource import de.westnordost.streetcomplete.data.quest.QuestType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.util.* import java.util.concurrent.FutureTask import javax.inject.Inject @@ -23,16 +36,23 @@ class OsmApiQuestDownloader @Inject constructor( private val osmQuestController: OsmQuestController, private val countryBoundariesFuture: FutureTask, private val notePositionsSource: NotePositionsSource, - private val osmApiMapDataProvider: Provider, + private val mapDataApi: MapDataApi, + private val mapDataWithGeometry: Provider, + private val elementGeometryCreator: ElementGeometryCreator, private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker -) { +) : CoroutineScope by CoroutineScope(Dispatchers.Default) { + fun download(questTypes: List>, bbox: BoundingBox) { if (questTypes.isEmpty()) return var time = System.currentTimeMillis() - val mapData = osmApiMapDataProvider.get() - mapData.initWith(bbox) + val completeRelationGeometries = mutableMapOf() + + val mapData = mapDataWithGeometry.get() + getMapAndHandleTooBigQuery(bbox, mapData) + // bbox should be the bbox of the complete download + mapData.handle(bbox) val quests = ArrayList() val questElements = HashSet() @@ -43,28 +63,38 @@ class OsmApiQuestDownloader @Inject constructor( val truncatedBlacklistedPositions = notePositionsSource.getAllPositions(bbox).map { it.truncateTo5Decimals() }.toSet() - for (questType in questTypes) { - // TODO multithreading! - val questTypeName = questType.getName() - - val countries = questType.enabledInCountries - if (!countryBoundariesFuture.get().intersects(bbox, countries)) { - Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") - continue - } - - for (element in questType.getApplicableElements(mapData)) { - val geometry = mapData.getGeometry(element.type, element.id) - if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) continue - - val quest = OsmQuest(questType, element.type, element.id, geometry!!) - - quests.add(quest) - questElements.add(element) + val countryBoundaries = countryBoundariesFuture.get() + + runBlocking { + for (questType in questTypes) { + launch(Dispatchers.Default) { + val questTypeName = questType.getName() + if (!countryBoundaries.intersects(bbox, questType.enabledInCountries)) { + Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") + } else { + var i = 0 + val questTime = System.currentTimeMillis() + for (element in questType.getApplicableElements(mapData)) { + val geometry = getCompleteGeometry(element.type!!, element.id, mapData, completeRelationGeometries) + if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) continue + + val quest = OsmQuest(questType, element.type, element.id, geometry!!) + + quests.add(quest) + questElements.add(element) + ++i + } + Log.i(TAG, "${Thread.currentThread().id} $questTypeName: Found $i quests in ${System.currentTimeMillis() - questTime}ms") + } + } } } val secondsSpentAnalyzing = (System.currentTimeMillis() - time) / 1000 + Log.i(TAG,"Created ${quests.size} quests in ${secondsSpentAnalyzing}s") + + time = System.currentTimeMillis() + // elements must be put into DB first because quests have foreign keys on it elementDB.putAll(questElements) @@ -77,7 +107,59 @@ class OsmApiQuestDownloader @Inject constructor( questType.cleanMetadata() } - Log.i(TAG,"${questTypeNames.joinToString()}: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpentAnalyzing}s") + val secondsSpentPersisting = (System.currentTimeMillis() - time) / 1000 + + Log.i(TAG,"Persisting ${quests.size} quests in ${secondsSpentPersisting}s") + + Log.i(TAG,"Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests") + } + + private fun getCompleteGeometry( + elementType: Element.Type, + elementId: Long, + mapData: MapDataWithGeometry, + cache: MutableMap + ): ElementGeometry? { + return when(elementType) { + NODE -> mapData.getNodeGeometry(elementId) + WAY -> mapData.getWayGeometry(elementId) + // relations are downloaded incomplete from the OSM API, we want the complete geometry here + RELATION -> getCompleteRelationGeometry(elementId, mapData, cache) + } + } + + private fun getCompleteRelationGeometry(id: Long, mapData: MapDataWithGeometry, cache: MutableMap): ElementGeometry? { + if (!cache.containsKey(id)) { + synchronized(cache) { + if (!cache.containsKey(id)) { + cache[id] = createCompleteRelationGeometry(id, mapData) + } + } + } + return cache[id] + } + + private fun createCompleteRelationGeometry(id: Long, mapData: MapDataWithGeometry): ElementGeometry? { + val isComplete = mapData.isRelationComplete(id) + if (isComplete) { + // if the relation is already complete within the given mapData, we can just take it from there + return mapData.getRelationGeometry(id) + } else { + // otherwise we need to query the API first and create it from that data instead + val completeRelationData = mapDataApi.getRelationComplete(id) + val relation = mapData.getRelation(id) ?: return null + return elementGeometryCreator.create(relation, completeRelationData, false) + } + } + + private fun getMapAndHandleTooBigQuery(bounds: BoundingBox, mapDataHandler: MapDataHandler) { + try { + mapDataApi.getMap(bounds, mapDataHandler) + } catch (e : OsmQueryTooBigException) { + for (subBounds in bounds.splitIntoFour()) { + getMapAndHandleTooBigQuery(subBounds, mapDataHandler) + } + } } companion object { @@ -90,4 +172,14 @@ private fun QuestType<*>.getName() = javaClass.simpleName // the resulting precision is about ~1 meter (see #1089) private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) -private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 \ No newline at end of file +private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 + +private fun BoundingBox.splitIntoFour(): List { + val center = OsmLatLon((maxLatitude + minLatitude) / 2, (maxLongitude + minLongitude) / 2) + return listOf( + BoundingBox(minLatitude, minLongitude, center.latitude, center.longitude), + BoundingBox(minLatitude, center.longitude, center.latitude, maxLongitude), + BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), + BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) + ) +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 0b5a452817..1a1c46878b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -201,7 +201,7 @@ object QuestModule // ↓ 8. defined in the wiki, but not really used by anyone yet. Just collected for // the sake of mapping it in case it makes sense later AddIsDefibrillatorIndoor(), - AddSummitRegister(o, r), + AddSummitRegister(r), AddCyclewayPartSurface(r), AddFootwayPartSurface(r), AddMotorcycleParkingCover(), diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index a52e987904..a4c8066ad4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -7,7 +7,6 @@ import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt index b89d9b6890..571d6cfe2c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt @@ -1,26 +1,26 @@ package de.westnordost.streetcomplete.quests.summit_register -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate -import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.data.quest.NoCountriesExcept +import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +import de.westnordost.streetcomplete.util.distanceToArcs -class AddSummitRegister( - private val overpassMapDataApi: OverpassMapDataAndGeometryApi, - private val r: ResurveyIntervalsStore -) : OsmDownloaderQuestType { +class AddSummitRegister(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { + + private val filter by lazy { ElementFiltersParser().parse(""" + nodes with + natural = peak and name and + (!summit:register or summit:register older today -${r * 4} years) + """) } override val commitMessage = "Add whether summit register is present" override val wikiLink = "Key:summit:register" @@ -39,36 +39,30 @@ class AddSummitRegister( override fun getTitle(tags: Map) = R.string.quest_summit_register_title - override fun createForm() = YesNoQuestAnswerFragment() - - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassMapDataApi.query(getOverpassQuery(bbox), handler) + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val peaks = mapData.nodes.filter { filter.matches(it) } + if (peaks.isEmpty()) return emptyList() + + val hikingRoutes = mapData.relations + .filter { it.tags?.get("route") == "hiking" } + .mapNotNull { mapData.getRelationGeometry(it.id) as? ElementPolylinesGeometry } + if (hikingRoutes.isEmpty()) return emptyList() + + // yes, this is very inefficient, however, peaks are very rare + return peaks.filter { peak -> + hikingRoutes.any { hikingRoute -> + hikingRoute.polylines.any { ways -> + peak.position.distanceToArcs(ways) <= 10 + } + } + } } override fun isApplicableTo(element: Element): Boolean? = null + override fun createForm() = YesNoQuestAnswerFragment() + override fun applyAnswerTo(answer: Boolean, changes: StringMapChangesBuilder) { - if (answer) { - changes.updateWithCheckDate("summit:register", "yes") - } else { - changes.updateWithCheckDate("summit:register", "no") - } + changes.updateWithCheckDate("summit:register", answer.toYesNo()) } - - private fun getOverpassQuery(bbox: BoundingBox) = """ - ${bbox.toGlobalOverpassBBox()} - ( - relation["route"="hiking"]; - )->.hiking; - node(around.hiking:10)[natural=peak][!"summit:register"][name] -> .summits_with_unknown_status; - node(around.hiking:10)["summit:register"][name]${olderThan(4).toOverpassQLString()} -> .summits_with_old_status; - - (.summits_with_unknown_status; .summits_with_old_status;); - - ${getQuestPrintStatement()} - """.trimIndent() - - private fun olderThan(years: Int) = - TagOlderThan("summit:register", RelativeDate(-(r * 365 * years).toFloat())) - } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt index f7527ea4c2..569e6cb543 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt @@ -7,7 +7,9 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.OsmLatLon import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.any +import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource @@ -28,7 +30,9 @@ class OsmApiQuestDownloaderTest { private lateinit var osmQuestController: OsmQuestController private lateinit var countryBoundaries: CountryBoundaries private lateinit var notePositionsSource: NotePositionsSource - private lateinit var osmApiMapData: OsmApiMapData + private lateinit var mapDataApi: MapDataApi + private lateinit var mapDataWithGeometry: CachingMapDataWithGeometry + private lateinit var elementGeometryCreator: ElementGeometryCreator private lateinit var elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker private lateinit var downloader: OsmApiQuestDownloader @@ -39,13 +43,17 @@ class OsmApiQuestDownloaderTest { osmQuestController = mock() on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) countryBoundaries = mock() - osmApiMapData = mock() + mapDataApi = mock() + mapDataWithGeometry = mock() + elementGeometryCreator = mock() notePositionsSource = mock() elementEligibleForOsmQuestChecker = mock() val countryBoundariesFuture = FutureTask { countryBoundaries } countryBoundariesFuture.run() - val osmApiMapDataProvider = Provider { osmApiMapData } - downloader = OsmApiQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, osmApiMapDataProvider, elementEligibleForOsmQuestChecker) + val mapDataProvider = Provider { mapDataWithGeometry } + downloader = OsmApiQuestDownloader( + elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, mapDataApi, + mapDataProvider, elementGeometryCreator, elementEligibleForOsmQuestChecker) } @Test fun `creates quest for element`() { @@ -54,7 +62,7 @@ class OsmApiQuestDownloaderTest { val geom = ElementPointGeometry(pos) val questType = TestMapDataQuestType(listOf(node)) - on(osmApiMapData.getGeometry(Element.Type.NODE, 5)).thenReturn(geom) + on(mapDataWithGeometry.getNodeGeometry(5)).thenReturn(geom) on(elementEligibleForOsmQuestChecker.mayCreateQuestFrom(any(), any(), any())).thenReturn(true) on(osmQuestController.replaceInBBox(any(), any(), any())).thenAnswer { val createdQuests = it.arguments[0] as List From c37e6e6e326f41f678e7c3b3a85a68e3e9bcc78d Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 22 Oct 2020 20:36:48 +0200 Subject: [PATCH 26/63] delete unsolved quests after 14 days, mark downloaded data as old after 3 days --- .../westnordost/streetcomplete/ApplicationConstants.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java index e61d48213f..615368e6c9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java +++ b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java @@ -24,9 +24,10 @@ public class ApplicationConstants /** a "best before" duration for quests. Quests will not be downloaded again for any tile * before the time expired */ - public static final long REFRESH_QUESTS_AFTER = 7L*24*60*60*1000; // 1 week in ms - /** the duration after which quests will be deleted from the database if unsolved */ - public static final long DELETE_UNSOLVED_QUESTS_AFTER = 1L*30*24*60*60*1000; // 1 months in ms + public static final long REFRESH_QUESTS_AFTER = 3L*24*60*60*1000; // 3 days in ms + /** the duration after which quests (and quest meta data) will be deleted from the database if + * unsolved and not refreshed in the meantime */ + public static final long DELETE_UNSOLVED_QUESTS_AFTER = 14*24*60*60*1000; // 14 days in ms /** the max age of the undo history - one cannot undo changes older than X */ public static final long MAX_QUEST_UNDO_HISTORY_AGE = 24*60*60*1000; // 1 day in ms From 6a6cd3102173cb6f7b8b7f63f7b141838f4207df Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 22 Oct 2020 20:37:45 +0200 Subject: [PATCH 27/63] make sure road suggestion data is deleted (together with the quests) after DELETE_UNSOLVED_QUESTS_AFTER if not refreshed --- .../data/StreetCompleteSQLiteOpenHelper.kt | 11 ++++++++++- .../quests/address/AddAddressStreet.kt | 4 ++++ .../streetcomplete/quests/road_name/AddRoadName.kt | 5 +++++ .../quests/road_name/data/RoadNameSuggestionsDao.kt | 13 ++++++++++++- .../quests/road_name/data/RoadNamesTable.kt | 4 +++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt b/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt index 72deeb973e..90fc9c6c0c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt @@ -29,6 +29,7 @@ import de.westnordost.streetcomplete.ktx.hasColumn import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable import de.westnordost.streetcomplete.quests.oneway_suspects.AddSuspectedOneway import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowTable +import java.util.* @Singleton class StreetCompleteSQLiteOpenHelper(context: Context, dbName: String) : SQLiteOpenHelper(context, dbName, null, DB_VERSION) { @@ -201,9 +202,17 @@ import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowT DELETE FROM ${RelationTable.NAME} """.trimIndent()) } + + if (oldVersion < 17 && newVersion >= 17) { + db.execSQL(""" + ALTER TABLE ${RoadNamesTable.NAME} + ADD COLUMN ${RoadNamesTable.Columns.LAST_UPDATE} int NOT NULL default ${Date().time}; + """.trimIndent()) + } + // for later changes to the DB // ... } } -private const val DB_VERSION = 16 +private const val DB_VERSION = 17 diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index eb7b3e8788..849b935950 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -68,6 +68,10 @@ class AddAddressStreet( changes.add(key, answer.name) } + override fun cleanMetadata() { + roadNameSuggestionsDao.cleanUp() + } + companion object { const val MAX_DIST_FOR_ROAD_NAME_SUGGESTION = 100.0 diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index b214afd84e..0bb6ed8b8d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -2,6 +2,7 @@ package de.westnordost.streetcomplete.quests.road_name import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element +import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder @@ -103,6 +104,10 @@ class AddRoadName( roadNameSuggestionsDao.putRoad( answer.wayId, roadNameByLanguage, answer.wayGeometry) } + override fun cleanMetadata() { + roadNameSuggestionsDao.cleanUp() + } + companion object { const val MAX_DIST_FOR_ROAD_NAME_SUGGESTION = 30.0 //m diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt index cae311fb79..86721b5648 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt @@ -7,6 +7,7 @@ import de.westnordost.osmapi.map.data.Element import javax.inject.Inject import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.ktx.getBlob @@ -21,9 +22,13 @@ import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.Column import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.NAME import de.westnordost.streetcomplete.util.Serializer import de.westnordost.streetcomplete.ktx.toObject +import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.Columns.LAST_UPDATE import de.westnordost.streetcomplete.util.distanceToArcs import de.westnordost.streetcomplete.util.enclosingBoundingBox +import java.util.* import java.util.regex.Pattern +import kotlin.collections.ArrayList +import kotlin.collections.HashMap class RoadNameSuggestionsDao @Inject constructor( private val dbHelper: SQLiteOpenHelper, @@ -40,7 +45,8 @@ class RoadNameSuggestionsDao @Inject constructor( MIN_LATITUDE to bbox.minLatitude, MIN_LONGITUDE to bbox.minLongitude, MAX_LATITUDE to bbox.maxLatitude, - MAX_LONGITUDE to bbox.maxLongitude + MAX_LONGITUDE to bbox.maxLongitude, + LAST_UPDATE to Date().time ) db.replaceOrThrow(NAME, null, v) } @@ -93,6 +99,11 @@ class RoadNameSuggestionsDao @Inject constructor( // return only the road names, sorted by distance ascending return distancesByRoad.entries.sortedBy { it.value }.map { it.key } } + + fun cleanUp() { + val oldNameSuggestionsTimestamp = System.currentTimeMillis() - ApplicationConstants.DELETE_UNSOLVED_QUESTS_AFTER + db.delete(NAME, "$LAST_UPDATE < ?", arrayOf(oldNameSuggestionsTimestamp.toString())) + } } fun RoadNameSuggestionsDao.putRoadNameSuggestion(element: Element, geometry: ElementGeometry?) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNamesTable.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNamesTable.kt index 27d9a2194d..88f14c5a8e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNamesTable.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNamesTable.kt @@ -11,6 +11,7 @@ object RoadNamesTable { const val MIN_LONGITUDE = "min_longitude" const val MAX_LATITUDE = "max_latitude" const val MAX_LONGITUDE = "max_longitude" + const val LAST_UPDATE = "last_update" } const val CREATE = """ @@ -21,6 +22,7 @@ object RoadNamesTable { ${Columns.MIN_LATITUDE} double NOT NULL, ${Columns.MIN_LONGITUDE} double NOT NULL, ${Columns.MAX_LATITUDE} double NOT NULL, - ${Columns.MAX_LONGITUDE} double NOT NULL + ${Columns.MAX_LONGITUDE} double NOT NULL, + ${Columns.LAST_UPDATE} int NOT NULL );""" } From 2b7afd2c9ca5630757de5e193ee327999916ffa9 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 23 Oct 2020 00:34:27 +0200 Subject: [PATCH 28/63] convert AddAddressStreet and AddRoadName --- .../osm/osmquest/OsmApiQuestDownloader.kt | 2 +- .../streetcomplete/quests/QuestModule.kt | 4 +- .../quests/address/AddAddressStreet.kt | 86 +++++++++--------- .../quests/road_name/AddRoadName.kt | 89 ++++++++----------- .../quests/road_name/AddRoadNameForm.kt | 6 +- .../quests/address/AddAddressStreetTest.kt | 48 ++++++++++ .../quests/road_name/AddRoadNameTest.kt | 2 +- 7 files changed, 136 insertions(+), 101 deletions(-) create mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 84b4228949..626aaa95a0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -84,7 +84,7 @@ class OsmApiQuestDownloader @Inject constructor( questElements.add(element) ++i } - Log.i(TAG, "${Thread.currentThread().id} $questTypeName: Found $i quests in ${System.currentTimeMillis() - questTime}ms") + Log.i(TAG, "$questTypeName: Found $i quests in ${System.currentTimeMillis() - questTime}ms") } } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 1a1c46878b..207beaa03c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -112,7 +112,7 @@ object QuestModule osmNoteQuestType, // ↓ 2. important data that is used by many data consumers - AddRoadName(o, roadNameSuggestionsDao), + AddRoadName(roadNameSuggestionsDao), AddPlaceName(featureDictionaryFuture), AddOneway(), AddSuspectedOneway(o, trafficFlowSegmentsApi, trafficFlowDao), @@ -120,7 +120,7 @@ object QuestModule AddBusStopRef(), AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings AddHousenumber(), - AddAddressStreet(o, roadNameSuggestionsDao), + AddAddressStreet(roadNameSuggestionsDao), MarkCompletedHighwayConstruction(r), AddReligionToPlaceOfWorship(), // icons on maps are different - OSM Carto, mapy.cz, OsmAnd, Sputnik etc AddParkingAccess(), //OSM Carto, mapy.cz, OSMand, Sputnik etc diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index 849b935950..aa7be3f62c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -1,23 +1,32 @@ package de.westnordost.streetcomplete.quests.address -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.Relation import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion class AddAddressStreet( - private val overpassApi: OverpassMapDataAndGeometryApi, private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmDownloaderQuestType { +) : OsmMapDataQuestType { + + private val filter by lazy { ElementFiltersParser().parse(""" + nodes, ways, relations with + addr:housenumber and !addr:street and !addr:place and !addr:block_number + or addr:streetnumber and !addr:street + """) } + + private val roadsWithNamesFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ ${ALL_ROADS.joinToString("|")} + and name + """)} override val commitMessage = "Add street/place names to address" override val icon = R.drawable.ic_quest_housenumber_street @@ -31,34 +40,33 @@ class AddAddressStreet( return if (housenumber != null) arrayOf(housenumber) else arrayOf() } - override fun createForm() = AddAddressStreetForm() + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val associatedStreetRelations = mapData.relations.filter { + val type = it.tags?.get("type") + type == "associatedStreet" || type == "street" + } - /* cannot be determined offline because the quest kinda needs the street name suggestions - to work conveniently (see #1856) */ - override fun isApplicableTo(element: Element): Boolean? = null + val addressesWithoutStreet = mapData.filter { address -> + filter.matches(address) && + associatedStreetRelations.none { it.contains(address.type, address.id) } + } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - if (!overpassApi.query(getStreetNameSuggestionsOverpassQuery(bbox), roadNameSuggestionsDao::putRoadNameSuggestion)) return false - if (!overpassApi.query(getOverpassQuery(bbox), handler)) return false - return true + if (addressesWithoutStreet.isNotEmpty()) { + val roadsWithNames = mapData.ways.filter { roadsWithNamesFilter.matches(it) } + for (roadWithName in roadsWithNames) { + val roadGeometry = mapData.getWayGeometry(roadWithName.id) + if (roadGeometry != null) { + roadNameSuggestionsDao.putRoadNameSuggestion(roadWithName, roadGeometry) + } + } + } + return addressesWithoutStreet } - private fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + """ - relation["type"="associatedStreet"]; > -> .inStreetRelation; - $ADDRESSES_WITHOUT_STREETS -> .missing_data; - (.missing_data; - .inStreetRelation;); - """.trimIndent() + getQuestPrintStatement() + override fun createForm() = AddAddressStreetForm() - /** return overpass query string to get roads with names around addresses without streets - * */ - private fun getStreetNameSuggestionsOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + """ - $ADDRESSES_WITHOUT_STREETS -> .address_missing_street; - $ROADS_WITH_NAMES -> .named_roads; - way.named_roads( - around.address_missing_street: $MAX_DIST_FOR_ROAD_NAME_SUGGESTION); - out body geom;""".trimIndent() + /* cannot be determined because of the associated street relations */ + override fun isApplicableTo(element: Element): Boolean? = null override fun applyAnswerTo(answer: AddressStreetAnswer, changes: StringMapChangesBuilder) { val key = when(answer) { @@ -71,16 +79,8 @@ class AddAddressStreet( override fun cleanMetadata() { roadNameSuggestionsDao.cleanUp() } - - companion object { - const val MAX_DIST_FOR_ROAD_NAME_SUGGESTION = 100.0 - - private val ADDRESSES_WITHOUT_STREETS = """ - ( - nwr["addr:housenumber"][!"addr:street"][!"addr:place"][!"addr:block_number"]; - nwr["addr:streetnumber"][!"addr:street"]; - )""".trimIndent() - private val ROADS_WITH_NAMES = - "way[highway ~ \"^(${ALL_ROADS.joinToString("|")})$\"][name]" - } } + +private fun Relation.contains(elementType: Element.Type, elementId: Long) : Boolean { + return members.any { it.type == elementType && it.ref == elementId } +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index 0bb6ed8b8d..4d895ec44f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -1,26 +1,40 @@ package de.westnordost.streetcomplete.quests.road_name -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.LocalizedName import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion class AddRoadName( - private val overpassApi: OverpassMapDataAndGeometryApi, private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmDownloaderQuestType { +) : OsmMapDataQuestType { + + private val filter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ primary|secondary|tertiary|unclassified|residential|living_street|pedestrian + and !name + and !ref + and noname != yes + and !junction + and area != yes + and ( + access !~ private|no + or foot and foot !~ private|no + ) + """)} + + private val roadsWithNamesFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ ${ALL_ROADS.joinToString("|")} + and name + """)} override val enabledInCountries = AllCountriesExcept("JP") override val commitMessage = "Determine road names and types" @@ -35,35 +49,22 @@ class AddRoadName( else R.string.quest_streetName_title - override fun isApplicableTo(element: Element) = ROADS_WITHOUT_NAMES_TFE.matches(element) + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val roadsWithoutNames = mapData.ways.filter { filter.matches(it) } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - if (!overpassApi.query(getOverpassQuery(bbox), handler)) return false - if (!overpassApi.query(getStreetNameSuggestionsOverpassQuery(bbox), roadNameSuggestionsDao::putRoadNameSuggestion)) return false - return true + if (roadsWithoutNames.isNotEmpty()) { + val roadsWithNames = mapData.ways.filter { roadsWithNamesFilter.matches(it) } + for (roadWithName in roadsWithNames) { + val roadGeometry = mapData.getWayGeometry(roadWithName.id) + if (roadGeometry != null) { + roadNameSuggestionsDao.putRoadNameSuggestion(roadWithName, roadGeometry) + } + } + } + return roadsWithoutNames } - /** returns overpass query string for creating the quests */ - private fun getOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + - ROADS_WITHOUT_NAMES + "->.unnamed;\n" + - "(\n" + - " way.unnamed['access' !~ '^(private|no)$'];\n" + - " way.unnamed['foot']['foot' !~ '^(private|no)$'];\n" + - "); " + - getQuestPrintStatement() - - /** return overpass query string to get roads with names near roads that don't have names - * private roads are not filtered out here, partially to reduce complexity but also - * because the road may have a private segment that is named already or is close to a road - * with a useful name - * */ - private fun getStreetNameSuggestionsOverpassQuery(bbox: BoundingBox) = - bbox.toGlobalOverpassBBox() + "\n" + """ - $ROADS_WITHOUT_NAMES -> .without_names; - $ROADS_WITH_NAMES -> .with_names; - way.with_names(around.without_names: $MAX_DIST_FOR_ROAD_NAME_SUGGESTION ); - out body geom;""".trimIndent() + override fun isApplicableTo(element: Element) = filter.matches(element) override fun createForm() = AddRoadNameForm() @@ -107,24 +108,6 @@ class AddRoadName( override fun cleanMetadata() { roadNameSuggestionsDao.cleanUp() } - - companion object { - const val MAX_DIST_FOR_ROAD_NAME_SUGGESTION = 30.0 //m - - // to avoid spam, only ask for names on a limited set of roads - private const val NAMEABLE_ROADS = - "primary|secondary|tertiary|unclassified|residential|living_street|pedestrian" - private const val ROADS_WITHOUT_NAMES = - "way[highway ~ \"^($NAMEABLE_ROADS)$\"][!name][!ref][noname != yes][!junction][area != yes]" - // this must be the same as above but in tag filter expression syntax - private val ROADS_WITHOUT_NAMES_TFE by lazy { ElementFiltersParser().parse( - "ways with highway ~ $NAMEABLE_ROADS and !name and !ref and noname != yes and !junction and area != yes" - )} - - // but we can find name suggestions on any type of already-named road - private val ROADS_WITH_NAMES = - "way[highway ~ \"^(${ALL_ROADS.joinToString("|")})$\"][name]" - } } private fun LocalizedName.isRef() = diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt index 6dd5226837..c817201709 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameForm.kt @@ -52,7 +52,7 @@ class AddRoadNameForm : AAddLocalizedNameForm() { } return roadNameSuggestionsDao.getNames( listOf(polyline.first(), polyline.last()), - AddRoadName.MAX_DIST_FOR_ROAD_NAME_SUGGESTION + MAX_DIST_FOR_ROAD_NAME_SUGGESTION ) } @@ -145,4 +145,8 @@ class AddRoadNameForm : AAddLocalizedNameForm() { .setNegativeButton(R.string.quest_generic_confirmation_no, null) .show() } + + companion object { + const val MAX_DIST_FOR_ROAD_NAME_SUGGESTION = 30.0 //m + } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt new file mode 100644 index 0000000000..44ca221814 --- /dev/null +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt @@ -0,0 +1,48 @@ +package de.westnordost.streetcomplete.quests.address + +import de.westnordost.osmapi.map.data.Element +import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.osmapi.map.data.OsmRelation +import de.westnordost.osmapi.map.data.OsmRelationMember +import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry +import org.junit.Assert.* +import org.junit.Test + +class AddAddressStreetTest { + + private val questType = AddAddressStreet(mock()) + + @Test fun `applicable to place without street name`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "addr:housenumber" to "123" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to place with street name`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "addr:housenumber" to "123", + "addr:street" to "onetwothree", + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to place without street name but in a associatedStreet relation`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "addr:housenumber" to "123" + )), + OsmRelation(1L, 1, listOf( + OsmRelationMember(1L, "doesntmatter", Element.Type.NODE) + ), mapOf( + "type" to "associatedStreet" + )), + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameTest.kt index 0862566afb..1663c4cd7b 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/road_name/AddRoadNameTest.kt @@ -10,7 +10,7 @@ import org.junit.Test class AddRoadNameTest { - private val questType = AddRoadName(mock(), mock()) + private val questType = AddRoadName(mock()) private val tags = mapOf("highway" to "residential") From b8ab2145df5f3ec309e7a2b9ec7ec03cecea92b8 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 23 Oct 2020 01:58:19 +0200 Subject: [PATCH 29/63] convert suspected oneway quest --- .../streetcomplete/quests/QuestModule.kt | 2 +- .../oneway_suspects/AddSuspectedOneway.kt | 97 +++++++++---------- .../AddRecyclingContainerMaterials.kt | 1 - 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 207beaa03c..6767df9dc8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -115,7 +115,7 @@ object QuestModule AddRoadName(roadNameSuggestionsDao), AddPlaceName(featureDictionaryFuture), AddOneway(), - AddSuspectedOneway(o, trafficFlowSegmentsApi, trafficFlowDao), + AddSuspectedOneway(trafficFlowSegmentsApi, trafficFlowDao), AddBusStopName(), AddBusStopRef(), AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index 9a244fb3a2..fc54805e77 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -1,33 +1,30 @@ package de.westnordost.streetcomplete.quests.oneway_suspects import android.util.Log +import de.westnordost.osmapi.map.MapDataWithGeometry -import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.LatLon -import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegment import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegmentsApi import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowDao +import kotlin.math.hypot class AddSuspectedOneway( - private val overpassMapDataApi: OverpassMapDataAndGeometryApi, private val trafficFlowSegmentsApi: TrafficFlowSegmentsApi, private val db: WayTrafficFlowDao -) : OsmDownloaderQuestType { +) : OsmMapDataQuestType { - private val tagFilters = """ + private val filter by lazy { ElementFiltersParser().parse(""" ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road and !oneway and junction != roundabout and area != yes and (access !~ private|no or (foot and foot !~ private|no)) - """ + """) } override val commitMessage = "Add whether roads are one-way roads as they were marked as likely oneway by improveosm.org" @@ -36,55 +33,59 @@ class AddSuspectedOneway( override val hasMarkersAtEnds = true override val isSplitWayEnabled = true - private val filter by lazy { ElementFiltersParser().parse(tagFilters) } - override fun getTitle(tags: Map) = R.string.quest_oneway_title - override fun isApplicableTo(element: Element) = - filter.matches(element) && db.isForward(element.id) != null + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val bbox = mapData.boundingBox ?: return emptyList() - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { val trafficDirectionMap: Map> try { trafficDirectionMap = trafficFlowSegmentsApi.get(bbox) } catch (e: Exception) { - Log.e("AddOneway", "Unable to download traffic metadata", e) - return false + Log.e("AddSuspectedOneway", "Unable to download traffic metadata", e) + return emptyList() } - if (trafficDirectionMap.isEmpty()) return true - - val query = "way(id:${trafficDirectionMap.keys.joinToString(",")}); out meta geom;" - overpassMapDataApi.query(query) { element, geometry -> - fun checkValidAndHandle(element: Element, geometry: ElementGeometry?) { - if (geometry == null) return - if (geometry !is ElementPolylinesGeometry) return - // filter the data as ImproveOSM data may be outdated or catching too much - if (!filter.matches(element)) return - - val way = element as? Way ?: return - val segments = trafficDirectionMap[way.id] ?: return - /* exclude rings because the driving direction can then not be determined reliably - from the improveosm data and the quest should stay simple, i.e not require the - user to input it in those cases. Additionally, whether a ring-road is a oneway or - not is less valuable information (for routing) and many times such a ring will - actually be a roundabout. Oneway information on roundabouts is superfluous. - See #1320 */ - if (way.nodeIds.last() == way.nodeIds.first()) return - /* only create quest if direction can be clearly determined and is the same - direction for all segments belonging to one OSM way (because StreetComplete - cannot split ways up) */ - val isForward = isForward(geometry.polylines.first(), segments) ?: return - - db.put(way.id, isForward) - handler(element, geometry) - } - checkValidAndHandle(element, geometry) + val suspectedOnewayWayIds = trafficDirectionMap.keys + + val onewayCandidates = mapData.ways.filter { + // so, only the ways suspected by improveOSM to be oneways + it.id in suspectedOnewayWayIds && + // but also filter the data as ImproveOSM data may be outdated or catching too much + filter.matches(it) && + /* also exclude rings because the driving direction can then not be determined reliably + from the improveosm data and the quest should stay simple, i.e not require the + user to input it in those cases. Additionally, whether a ring-road is a oneway or + not is less valuable information (for routing) and many times such a ring will + actually be a roundabout. Oneway information on roundabouts is superfluous. + See #1320 */ + it.nodeIds.first() != it.nodeIds.last() + } + + // rehash traffic direction data into simple "way id -> forward/backward" data and persist + val onewayDirectionMap = onewayCandidates.associate { way -> + val segments = trafficDirectionMap[way.id] + val geometry = mapData.getWayGeometry(way.id) as? ElementPolylinesGeometry + val isForward = + if (segments != null && geometry != null) + isForward(geometry.polylines.first(), segments) + else null + + way.id to isForward + } + + for ((wayId, isForward) in onewayDirectionMap) { + if (isForward != null) db.put(wayId, isForward) } - return true + /* only create quest if direction could be clearly determined (isForward != null) and is the + same direction for all segments belonging to one OSM way */ + return onewayCandidates.filter { onewayDirectionMap[it.id] != null } } + override fun isApplicableTo(element: Element) = + filter.matches(element) && db.isForward(element.id) != null + /** returns true if all given [trafficFlowSegments] point forward in relation to the * direction of the OSM [way] and false if they all point backward. * @@ -113,9 +114,8 @@ class AddSuspectedOneway( private fun findClosestPositionIndexOf(positions: List, latlon: LatLon): Int { var shortestDistance = 1.0 var result = -1 - var index = 0 - for (pos in positions) { - val distance = Math.hypot( + for ((index, pos) in positions.withIndex()) { + val distance = hypot( pos.longitude - latlon.longitude, pos.latitude - latlon.latitude ) @@ -123,7 +123,6 @@ class AddSuspectedOneway( shortestDistance = distance result = index } - index++ } return result diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 083c468ae0..5639fc2c9b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -6,7 +6,6 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox From 6eeea69ad10a789fa3008078ccd0aa6951557b55 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 23 Oct 2020 02:32:51 +0200 Subject: [PATCH 30/63] fix nullpointer --- .../streetcomplete/quests/housenumber/AddHousenumber.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index e7ada5f303..b3d58371da 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -192,8 +192,8 @@ private fun Relation.containsAnyNode(nodeIds: Set, mapData: MapDataWithGeo members .filter { it.type == Element.Type.WAY } .any { member -> - val way = mapData.getWay(member.ref)!! - way.nodeIds.any { it in nodeIds } + val way = mapData.getWay(member.ref) + way?.nodeIds?.any { it in nodeIds } ?: false } /** return whether any of the ways with the given ids are contained in this relation */ From e15d64d5a075d577911f161454ea214f52d9bb15 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 23 Oct 2020 13:10:54 +0200 Subject: [PATCH 31/63] remove unused code --- .../data/osm/osmquest/OsmQuestDownloader.kt | 5 ----- .../quests/OsmDownloaderQuestType.kt | 19 ------------------- 2 files changed, 24 deletions(-) delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt index d89a15c7c8..88f166d0da 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt @@ -15,7 +15,6 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.LatLon import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource -import java.util.* import kotlin.collections.ArrayList /** Takes care of downloading one quest type in a bounding box and persisting the downloaded quests. @@ -69,8 +68,6 @@ class OsmQuestDownloader @Inject constructor( return true } - - companion object { private const val TAG = "QuestDownload" } @@ -82,5 +79,3 @@ private fun QuestType<*>.getName() = javaClass.simpleName private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 - -private fun Element.toLogString() = "${type.name.toLowerCase(Locale.US)} #$id" diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt deleted file mode 100644 index 2e94056d49..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/OsmDownloaderQuestType.kt +++ /dev/null @@ -1,19 +0,0 @@ -package de.westnordost.streetcomplete.quests - -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType -import org.junit.Assert.fail - -fun OsmDownloaderQuestType<*>.verifyDownloadYieldsNoQuest(bbox: BoundingBox) { - download(bbox) { element, _ -> - fail("Expected zero elements. Element returned: ${element.type.name}#${element.id}") - } -} - -fun OsmDownloaderQuestType<*>.verifyDownloadYieldsQuest(bbox: BoundingBox) { - var hasQuest = false - download(bbox) { _, _ -> hasQuest = true } - if (!hasQuest) { - fail("Expected nonzero elements. Elements not returned") - } -} From 8f3e2cb42a5f82a3b05635cd6bc67769978df96c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sat, 24 Oct 2020 20:23:49 +0200 Subject: [PATCH 32/63] remove unused imports --- .../de/westnordost/streetcomplete/quests/oneway/AddOneway.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index a20eda5592..ec7eb313cd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -7,9 +7,6 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.quests.bikeway.createCyclewaySides import de.westnordost.streetcomplete.quests.bikeway.estimatedWidth import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType From ffbdbddb2f3ddb87c74959844af461c4b713ad12 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Oct 2020 01:17:36 +0200 Subject: [PATCH 33/63] convert AddRecyclingcontainerMaterials quest --- .../streetcomplete/quests/QuestModule.kt | 2 +- .../AddRecyclingContainerMaterials.kt | 92 +++++++++++-------- .../AddRecyclingContainerMaterialsTest.kt | 87 +++++++++++++++++- .../quests/TestMapDataWithGeometry.kt | 2 + .../quests/oneway/AddOnewayTest.kt | 4 +- 5 files changed, 143 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 6767df9dc8..0574f144cd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -127,7 +127,7 @@ object QuestModule // ↓ 3. useful data that is used by some data consumers AddRecyclingType(), - AddRecyclingContainerMaterials(o, r), + AddRecyclingContainerMaterials(r), AddSport(), AddRoadSurface(r), // used by BRouter, OsmAnd, OSRM, graphhopper, HOT map style AddMaxSpeed(), // should best be after road surface because it excludes unpaved roads diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 5639fc2c9b..5ac6855dd8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -1,67 +1,67 @@ package de.westnordost.streetcomplete.quests.recycling_material -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +import de.westnordost.streetcomplete.util.LatLonRaster +import de.westnordost.streetcomplete.util.distanceTo +import de.westnordost.streetcomplete.util.enclosingBoundingBox class AddRecyclingContainerMaterials( - private val overpassApi: OverpassMapDataAndGeometryApi, private val r: ResurveyIntervalsStore -) : OsmDownloaderQuestType { +) : OsmMapDataQuestType { + + private val filter by lazy { ElementFiltersParser().parse(""" + nodes with + amenity = recycling and recycling_type = container + """) } override val commitMessage = "Add recycled materials to container" override val wikiLink = "Key:recycling" override val icon = R.drawable.ic_quest_recycling_container - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox), handler) - } + override fun getApplicableElements(mapData: MapDataWithGeometry): List { + val bbox = mapData.boundingBox ?: return emptyList() - private val allKnownMaterials = RecyclingMaterial.values().map { "recycling:" + it.value } - - /* Return recycling containers that do either not have any recycling:* tag yet, or if they do, - * haven't been touched for 2 years and are exclusively recycling types selectable in - * StreetComplete. - * Also, exclude recycling containers right next to another because the user can't know if - * certain materials are already recycled in that other container */ - private fun getOverpassQuery(bbox: BoundingBox) = """ - ${bbox.toGlobalOverpassBBox()} - node[amenity = recycling][recycling_type = container] -> .all; - - node.all[~"^recycling:.*$" ~ "yes"] -> .with_recycling; - (.all; - .with_recycling;) -> .without_recycling; - - node.with_recycling[~"^(${allKnownMaterials.joinToString("|")})$" ~ "yes" ] -> .with_known_recycling; - (.with_recycling; - .with_known_recycling;) -> .with_unknown_recycling; - - node.all${olderThan(2).toOverpassQLString()} -> .old; - - (.without_recycling; (.old; - .with_unknown_recycling;);) -> .recyclings; - - foreach .recyclings ( - node[amenity = recycling][recycling_type = container](around: 20); - node._(if:count(nodes) == 1); - out meta geom; - ); - """.trimIndent() + val olderThan2Years = TagOlderThan("recycling", RelativeDate(-(r * 365 * 2).toFloat())) + + val containers = mapData.nodes.filter { filter.matches(it) } + + /* Only recycling containers that do either not have any recycling:* tag yet or + * haven't been touched for 2 years and are exclusively recycling types selectable in + * StreetComplete. */ + val eligibleContainers = containers.filter { + !it.hasAnyRecyclingMaterials() || + (olderThan2Years.matches(it) && !it.hasUnknownRecyclingMaterials()) + } + /* Also, exclude recycling containers right next to another because the user can't know if + * certain materials are already recycled in that other container */ + val containerPositions = LatLonRaster(bbox, 0.0005) + for (container in containers) { + containerPositions.insert(container.position) + } + + val minDistance = 20.0 + return eligibleContainers.filter { container -> + val nearbyBounds = container.position.enclosingBoundingBox(minDistance) + val nearbyContainerPositions = containerPositions.getAll(nearbyBounds) + // only finds one position = only found self -> no other container is near + nearbyContainerPositions.count { container.position.distanceTo(it) <= minDistance } == 1 + } + } // can't determine by tags alone because we need info about geometry surroundings override fun isApplicableTo(element: Element): Boolean? = null - private fun olderThan(years: Int) = - TagOlderThan("recycling", RelativeDate(-(r * 365 * years).toFloat())) - override fun getTitle(tags: Map) = R.string.quest_recycling_materials_title override fun createForm() = AddRecyclingContainerMaterialsForm() @@ -137,3 +137,15 @@ class AddRecyclingContainerMaterials( changes.deleteCheckDatesForKey("recycling") } } + +private val allKnownMaterials = RecyclingMaterial.values().map { "recycling:" + it.value } + +private fun Element.hasAnyRecyclingMaterials(): Boolean = + tags?.any { it.key.startsWith("recycling:") && it.value == "yes" } ?: false + +private fun Element.hasUnknownRecyclingMaterials(): Boolean = + tags?.any { + it.key.startsWith("recycling:") && + it.key !in allKnownMaterials && + it.value == "yes" + } ?: true \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt index 4204376dc0..67015c58a1 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt @@ -1,5 +1,7 @@ package de.westnordost.streetcomplete.quests +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete @@ -11,6 +13,8 @@ import de.westnordost.streetcomplete.quests.recycling_material.RecyclingMaterial import de.westnordost.streetcomplete.quests.recycling_material.IsWasteContainer import de.westnordost.streetcomplete.quests.recycling_material.RecyclingMaterial.* import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +import de.westnordost.streetcomplete.util.translate +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyDouble @@ -23,11 +27,92 @@ class AddRecyclingContainerMaterialsTest { val r: ResurveyIntervalsStore = mock() on(r.times(anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } on(r.times(anyDouble())).thenAnswer { (it.arguments[0] as Double) } - questType = AddRecyclingContainerMaterials(mock(), r) + questType = AddRecyclingContainerMaterials(r) } private lateinit var questType: AddRecyclingContainerMaterials + @Test fun `applicable to container without recycling materials`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "amenity" to "recycling", + "recycling_type" to "container" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to container with recycling materials`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "amenity" to "recycling", + "recycling_type" to "container", + "recycling:something" to "yes" + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `applicable to container with old recycling materials`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "amenity" to "recycling", + "recycling_type" to "container", + "check_date:recycling" to "2001-01-01", + "recycling:plastic_packaging" to "yes", + "recycling:something_else" to "no" + ), null, Date()) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to container with old but unknown recycling materials`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, 0.0,0.0, mapOf( + "amenity" to "recycling", + "recycling_type" to "container", + "check_date:recycling" to "2001-01-01", + "recycling:something_else" to "yes" + ), null, Date()) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `not applicable to container without recycling materials close to another container`() { + val pos1 = OsmLatLon(0.0,0.0) + val pos2 = pos1.translate(19.0, 45.0) + + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, pos1, mapOf( + "amenity" to "recycling", + "recycling_type" to "container" + )), + OsmNode(2L, 1, pos2, mapOf( + "amenity" to "recycling", + "recycling_type" to "container" + )) + )) + assertEquals(0, questType.getApplicableElements(mapData).size) + } + + @Test fun `applicable to container without recycling materials not too close to another container`() { + val pos1 = OsmLatLon(0.0,0.0) + val pos2 = pos1.translate(21.0, 45.0) + + val mapData = TestMapDataWithGeometry(listOf( + OsmNode(1L, 1, pos1, mapOf( + "amenity" to "recycling", + "recycling_type" to "container" + )), + OsmNode(2L, 1, pos2, mapOf( + "amenity" to "recycling", + "recycling_type" to "container", + "recycling:paper" to "yes" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).size) + } + @Test fun `apply normal answer`() { questType.verifyAnswer( RecyclingMaterials(listOf(SHOES, PAPER)), diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt index 23375abb4a..0aa04f1be8 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/TestMapDataWithGeometry.kt @@ -2,6 +2,7 @@ package de.westnordost.streetcomplete.quests import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.MutableMapData +import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry @@ -10,6 +11,7 @@ class TestMapDataWithGeometry(elements: Iterable) : MutableMapData(), M init { addAll(elements) + handle(BoundingBox(0.0,0.0,1.0,1.0)) } val nodeGeometriesById: MutableMap = mutableMapOf() diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt index 8bdb3146d0..75757946f4 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt @@ -55,13 +55,13 @@ class AddOnewayTest { } @Test fun `applies to wider road that has cycle lanes`() { - setUpElements(noDeadEndWays(mapOf( + val mapData = TestMapDataWithGeometry(noDeadEndWays(mapOf( "highway" to "residential", "width" to "6", "lanes" to "1", "cycleway" to "lane" ))) - questType.verifyDownloadYieldsQuest(mock()) + assertEquals(1, questType.getApplicableElements(mapData).size) } @Test fun `does not apply to slim road with more than one lane`() { From 767348bb444f3fa0dcbc7eb575b96832b7d3c2f3 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Oct 2020 01:19:25 +0200 Subject: [PATCH 34/63] getApplicableElements may be iterable --- .../streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt | 2 +- .../streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt | 2 +- .../streetcomplete/quests/address/AddAddressStreet.kt | 2 +- .../quests/clothing_bin_operator/AddClothingBinOperator.kt | 2 +- .../streetcomplete/quests/crossing_island/AddCrossingIsland.kt | 2 +- .../streetcomplete/quests/housenumber/AddHousenumber.kt | 2 +- .../streetcomplete/quests/max_height/AddMaxHeight.kt | 2 +- .../de/westnordost/streetcomplete/quests/oneway/AddOneway.kt | 2 +- .../streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt | 2 +- .../streetcomplete/quests/opening_hours/AddOpeningHours.kt | 2 +- .../streetcomplete/quests/place_name/AddPlaceName.kt | 2 +- .../quests/railway_crossing/AddRailwayCrossingBarrier.kt | 2 +- .../quests/recycling_material/AddRecyclingContainerMaterials.kt | 2 +- .../westnordost/streetcomplete/quests/road_name/AddRoadName.kt | 2 +- .../streetcomplete/quests/summit_register/AddSummitRegister.kt | 2 +- .../quests/tactile_paving/AddTactilePavingCrosswalk.kt | 2 +- .../data/osm/osmquest/OsmApiQuestDownloaderTest.kt | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt index eab4772243..b207ba375e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt @@ -12,7 +12,7 @@ abstract class OsmFilterQuestType : OsmMapDataQuestType { protected abstract val elementFilter: String - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { /* this is a considerate performance improvement over just iterating over the whole MapData * because for quests that only filter for one (or two) element types, any filter checks * are completely avoided */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt index 149319f685..edf5d008ff 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt @@ -6,5 +6,5 @@ import de.westnordost.osmapi.map.data.Element /** Quest type based on OSM data whose quests can be created by looking at a MapData */ interface OsmMapDataQuestType : OsmElementQuestType { /** return all elements within the given map data that are applicable to this quest type. */ - fun getApplicableElements(mapData: MapDataWithGeometry): List + fun getApplicableElements(mapData: MapDataWithGeometry): Iterable } \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index aa7be3f62c..3de23e5a02 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -40,7 +40,7 @@ class AddAddressStreet( return if (housenumber != null) arrayOf(housenumber) else arrayOf() } - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val associatedStreetRelations = mapData.relations.filter { val type = it.tags?.get("type") type == "associatedStreet" || type == "street" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index d8f8624179..33c05964d1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -22,7 +22,7 @@ class AddClothingBinOperator : OsmMapDataQuestType { override val wikiLink = "Tag:amenity=recycling" override val icon = R.drawable.ic_quest_recycling_clothes - override fun getApplicableElements(mapData: MapDataWithGeometry): List = + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.nodes.filter { filter.matches(it) && it.tags.hasNoOtherRecyclingTags() } override fun isApplicableTo(element: Element): Boolean = diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt index 10bfce71d9..09a580008d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt @@ -32,7 +32,7 @@ class AddCrossingIsland : OsmMapDataQuestType { override fun getTitle(tags: Map) = R.string.quest_pedestrian_crossing_island - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val excludedWayNodeIds = mutableSetOf() mapData.ways .filter { excludedWaysFilter.matches(it) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index b3d58371da..b3da34a26b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -30,7 +30,7 @@ class AddHousenumber : OsmMapDataQuestType { override fun getTitle(tags: Map) = R.string.quest_address_title - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val bbox = mapData.boundingBox ?: return listOf() val addressNodesById = mapData.nodes.filter { nodesWithAddressFilter.matches(it) }.associateBy { it.id } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt index ad7beb8f34..d909de6ce2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt @@ -45,7 +45,7 @@ class AddMaxHeight : OsmMapDataQuestType { } } - override fun getApplicableElements(mapData: MapDataWithGeometry): List = + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.nodes.filter { nodeFilter.matches(it) } + mapData.ways.filter { wayFilter.matches(it) } override fun isApplicableTo(element: Element) = diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index ec7eb313cd..4d85fe62fa 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -36,7 +36,7 @@ class AddOneway : OsmMapDataQuestType { override fun getTitle(tags: Map) = R.string.quest_oneway2_title - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val allRoads = mapData.ways.filter { allRoadsFilter.matches(it) && it.nodeIds.size >= 2 } val connectionCountByNodeIds = mutableMapOf() val onewayCandidates = mutableListOf() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index fc54805e77..098890e94d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -35,7 +35,7 @@ class AddSuspectedOneway( override fun getTitle(tags: Map) = R.string.quest_oneway_title - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val bbox = mapData.boundingBox ?: return emptyList() val trafficDirectionMap: Map> diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 5bf1096fc7..312d4397a0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -127,7 +127,7 @@ class AddOpeningHours ( } } - override fun getApplicableElements(mapData: MapDataWithGeometry): List = + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) : Boolean { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt index 1cd452d55b..c3107789b0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt @@ -103,7 +103,7 @@ class AddPlaceName( override fun getTitleArgs(tags: Map, featureName: Lazy) = featureName.value?.let { arrayOf(it) } ?: arrayOf() - override fun getApplicableElements(mapData: MapDataWithGeometry): List = + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = mapData.filter { isApplicableTo(it) } override fun isApplicableTo(element: Element) = diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt index 9fc5948263..15e18c9298 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt @@ -29,7 +29,7 @@ class AddRailwayCrossingBarrier(private val r: ResurveyIntervalsStore) : OsmMapD override fun getTitle(tags: Map) = R.string.quest_railway_crossing_barrier_title2 - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val excludedWayNodeIds = mutableSetOf() mapData.ways .filter { excludedWaysFilter.matches(it) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 5ac6855dd8..4311fa5aaf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -29,7 +29,7 @@ class AddRecyclingContainerMaterials( override val wikiLink = "Key:recycling" override val icon = R.drawable.ic_quest_recycling_container - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val bbox = mapData.boundingBox ?: return emptyList() val olderThan2Years = TagOlderThan("recycling", RelativeDate(-(r * 365 * 2).toFloat())) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index 4d895ec44f..3ba6dc17fc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -49,7 +49,7 @@ class AddRoadName( else R.string.quest_streetName_title - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val roadsWithoutNames = mapData.ways.filter { filter.matches(it) } if (roadsWithoutNames.isNotEmpty()) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt index 571d6cfe2c..eb4a50d290 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt @@ -39,7 +39,7 @@ class AddSummitRegister(private val r: ResurveyIntervalsStore) : OsmMapDataQuest override fun getTitle(tags: Map) = R.string.quest_summit_register_title - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val peaks = mapData.nodes.filter { filter.matches(it) } if (peaks.isEmpty()) return emptyList() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt index 78459ba1b1..5c0a07ecaf 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt @@ -54,7 +54,7 @@ class AddTactilePavingCrosswalk(private val r: ResurveyIntervalsStore) : OsmMapD override fun getTitle(tags: Map) = R.string.quest_tactilePaving_title_crosswalk - override fun getApplicableElements(mapData: MapDataWithGeometry): List { + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val excludedWayNodeIds = mutableSetOf() mapData.ways .filter { excludedWaysFilter.matches(it) } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt index 569e6cb543..9596abbd92 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt @@ -93,5 +93,5 @@ private class TestMapDataQuestType(private val list: List) : OsmMapData override fun createForm() = object : AbstractQuestAnswerFragment() {} override fun isApplicableTo(element: Element) = false override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} - override fun getApplicableElements(mapData: MapDataWithGeometry): List = list + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable = list } From 86eb0959d1a800758e58ff6f4912ad5d3977d4ea Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Oct 2020 01:23:08 +0200 Subject: [PATCH 35/63] fix tests --- .../AddRecyclingContainerMaterialsTest.kt | 12 +++++------ .../quests/address/AddAddressStreetTest.kt | 6 +++--- .../crossing_island/AddCrossingIslandTest.kt | 4 ++-- .../quests/housenumber/AddHousenumberTest.kt | 16 +++++++-------- .../quests/oneway/AddOnewayTest.kt | 20 +++++++++---------- .../AddRailwayCrossingBarrierTest.kt | 4 ++-- .../AddTactilePavingCrosswalkTest.kt | 4 ++-- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt index 67015c58a1..78ae855ff8 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt @@ -39,7 +39,7 @@ class AddRecyclingContainerMaterialsTest { "recycling_type" to "container" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to container with recycling materials`() { @@ -50,7 +50,7 @@ class AddRecyclingContainerMaterialsTest { "recycling:something" to "yes" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applicable to container with old recycling materials`() { @@ -63,7 +63,7 @@ class AddRecyclingContainerMaterialsTest { "recycling:something_else" to "no" ), null, Date()) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to container with old but unknown recycling materials`() { @@ -75,7 +75,7 @@ class AddRecyclingContainerMaterialsTest { "recycling:something_else" to "yes" ), null, Date()) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to container without recycling materials close to another container`() { @@ -92,7 +92,7 @@ class AddRecyclingContainerMaterialsTest { "recycling_type" to "container" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applicable to container without recycling materials not too close to another container`() { @@ -110,7 +110,7 @@ class AddRecyclingContainerMaterialsTest { "recycling:paper" to "yes" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `apply normal answer`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt index 44ca221814..09427a314a 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/address/AddAddressStreetTest.kt @@ -19,7 +19,7 @@ class AddAddressStreetTest { "addr:housenumber" to "123" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to place with street name`() { @@ -29,7 +29,7 @@ class AddAddressStreetTest { "addr:street" to "onetwothree", )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to place without street name but in a associatedStreet relation`() { @@ -43,6 +43,6 @@ class AddAddressStreetTest { "type" to "associatedStreet" )), )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } } \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt index 22615a49b5..65efb1781a 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIslandTest.kt @@ -16,7 +16,7 @@ class AddCrossingIslandTest { "crossing" to "something" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to crossing with private road`() { @@ -30,6 +30,6 @@ class AddCrossingIslandTest { "access" to "private" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } } \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt index d82050e7a3..16e8a4f7d4 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumberTest.kt @@ -18,7 +18,7 @@ class AddHousenumberTest { val mapData = createMapData(mapOf( OsmWay(1L, 1, NODES1, mapOf("building" to "yes")) to POSITIONS1 )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building with address`() { @@ -28,7 +28,7 @@ class AddHousenumberTest { "addr:housenumber" to "123" )) to POSITIONS1 )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does create quest for building without address`() { @@ -37,7 +37,7 @@ class AddHousenumberTest { "building" to "detached" )) to POSITIONS1 )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building with address node on outline`() { @@ -49,7 +49,7 @@ class AddHousenumberTest { "addr:housenumber" to "123" )) to ElementPointGeometry(P2) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building that is part of a relation with an address`() { @@ -63,7 +63,7 @@ class AddHousenumberTest { "addr:housenumber" to "123" )) to ElementPointGeometry(P2) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building that is inside an area with an address`() { @@ -76,7 +76,7 @@ class AddHousenumberTest { "amenity" to "school", )) to POSITIONS2, )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building that contains an address node`() { @@ -88,7 +88,7 @@ class AddHousenumberTest { "addr:housenumber" to "123" )) to ElementPointGeometry(PC), )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not create quest for building that intersects bounding box`() { @@ -97,7 +97,7 @@ class AddHousenumberTest { "building" to "detached" )) to ElementPolygonsGeometry(listOf(listOf(P1, P2, PO, P4, P1)), PC) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `housenumber regex`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt index 75757946f4..a1f6b60d87 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/oneway/AddOnewayTest.kt @@ -11,7 +11,7 @@ class AddOnewayTest { @Test fun `does not apply to element without tags`() { val mapData = TestMapDataWithGeometry(noDeadEndWays(null)) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applies to slim road`() { @@ -20,7 +20,7 @@ class AddOnewayTest { "width" to "4", "lanes" to "1" ))) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not apply to wide road`() { @@ -29,7 +29,7 @@ class AddOnewayTest { "width" to "5", "lanes" to "1" ))) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applies to wider road that has parking lanes`() { @@ -40,7 +40,7 @@ class AddOnewayTest { "parking:lane:both" to "perpendicular", "parking:lane:both:perpendicular" to "on_street" ))) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not apply to wider road that has parking lanes but not enough`() { @@ -51,7 +51,7 @@ class AddOnewayTest { "parking:lane:both" to "perpendicular", "parking:lane:both:perpendicular" to "on_street" ))) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applies to wider road that has cycle lanes`() { @@ -61,7 +61,7 @@ class AddOnewayTest { "lanes" to "1", "cycleway" to "lane" ))) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not apply to slim road with more than one lane`() { @@ -70,7 +70,7 @@ class AddOnewayTest { "width" to "4", "lanes" to "2" ))) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not apply to dead end road #1`() { @@ -82,7 +82,7 @@ class AddOnewayTest { "lanes" to "1" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `does not apply to dead end road #2`() { @@ -94,7 +94,7 @@ class AddOnewayTest { )), way(2,listOf(3,4), mapOf("highway" to "residential")) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `applies to road that ends as an intersection in another`() { @@ -107,7 +107,7 @@ class AddOnewayTest { )), way(3,listOf(5,3,4), mapOf("highway" to "residential")) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } private fun way(id: Long, nodeIds: List, tags: Map?) = OsmWay(id,1, nodeIds, tags) diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt index 77dbea81ae..d5a6df8607 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt @@ -16,7 +16,7 @@ class AddRailwayCrossingBarrierTest { "railway" to "level_crossing" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to crossing with private road`() { @@ -29,6 +29,6 @@ class AddRailwayCrossingBarrierTest { "access" to "private" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } } \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt index 596e3786f8..74bd5c1d27 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt @@ -16,7 +16,7 @@ class AddTactilePavingCrosswalkTest { "highway" to "crossing" )) )) - assertEquals(1, questType.getApplicableElements(mapData).size) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) } @Test fun `not applicable to crossing with private road`() { @@ -29,6 +29,6 @@ class AddTactilePavingCrosswalkTest { "access" to "private" )) )) - assertEquals(0, questType.getApplicableElements(mapData).size) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } } \ No newline at end of file From e72e167966756e5822e3dbb454c198e4f0d68b36 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Oct 2020 17:56:40 +0100 Subject: [PATCH 36/63] add polygon area function --- .../streetcomplete/ktx/Collections.kt | 6 +- .../streetcomplete/quests/SplitWayFragment.kt | 4 +- .../streetcomplete/util/SphericalEarthMath.kt | 73 +++++++++++++++---- .../streetcomplete/ktx/CollectionsTest.kt | 21 ++++-- .../util/SphericalEarthMathTest.kt | 36 +++++++-- 5 files changed, 106 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ktx/Collections.kt b/app/src/main/java/de/westnordost/streetcomplete/ktx/Collections.kt index 054f1c10a3..5992d36232 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ktx/Collections.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/ktx/Collections.kt @@ -1,5 +1,7 @@ package de.westnordost.streetcomplete.ktx +import de.westnordost.osmapi.map.data.LatLon + /** Return the first and last element of this list. If it contains only one element, just that one */ fun List.firstAndLast() = if (size == 1) listOf(first()) else listOf(first(), last()) @@ -32,8 +34,8 @@ inline fun List.findNext(index: Int, predicate: (T) -> Boolean): T? { return null } -/** Iterate through the given list in pairs (advancing each one item, not two) */ -inline fun Iterable.forEachPair(predicate: (first: T, second: T) -> Unit) { +/** Iterate through the given list of points in pairs, so [predicate] is called for every line */ +inline fun Iterable.forEachLine(predicate: (first: LatLon, second: LatLon) -> Unit) { val it = iterator() if (!it.hasNext()) return var item1 = it.next() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/SplitWayFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/SplitWayFragment.kt index bedfd5ee43..c8e0ee24ea 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/SplitWayFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/SplitWayFragment.kt @@ -246,14 +246,14 @@ class SplitWayFragment : Fragment(R.layout.fragment_split_way), private fun createSplitsForLines(clickPosition: LatLon, clickAreaSizeInMeters: Double): Set { val result = mutableSetOf() - positions.forEachPair { first, second -> + positions.forEachLine { first, second -> val crossTrackDistance = abs(clickPosition.crossTrackDistanceTo(first, second)) if (clickAreaSizeInMeters > crossTrackDistance) { val alongTrackDistance = clickPosition.alongTrackDistanceTo(first, second) val distance = first.distanceTo(second) if (distance > alongTrackDistance && alongTrackDistance > 0) { val delta = alongTrackDistance / distance - result.add(SplitAtLinePosition(first, second, delta)) + result.add(SplitAtLinePosition(OsmLatLon(first), OsmLatLon(second), delta)) } } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt index 1a809f74e1..fe3ca2200a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt @@ -5,7 +5,7 @@ package de.westnordost.streetcomplete.util import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.LatLon import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.streetcomplete.ktx.forEachPair +import de.westnordost.streetcomplete.ktx.forEachLine import kotlin.math.* /** Calculate stuff assuming a spherical Earth. The Earth is not spherical, but it is a good @@ -118,14 +118,16 @@ fun LatLon.alongTrackDistanceTo(start: LatLon, end: LatLon, globeRadius: Double /** Returns the shortest distance between this point and the arc between the given points */ fun LatLon.distanceToArc(start: LatLon, end: LatLon, globeRadius: Double = EARTH_RADIUS): Double = - abs(angularDistanceToArc( - start.latitude.toRadians(), - start.longitude.toRadians(), - end.latitude.toRadians(), - end.longitude.toRadians(), - latitude.toRadians(), - longitude.toRadians() - )) * globeRadius + abs( + angularDistanceToArc( + start.latitude.toRadians(), + start.longitude.toRadians(), + end.latitude.toRadians(), + end.longitude.toRadians(), + latitude.toRadians(), + longitude.toRadians() + ) + ) * globeRadius /** Returns the shortest distance between this point and the arcs between the given points */ fun LatLon.distanceToArcs(polyLine: List, globeRadius: Double = EARTH_RADIUS): Double { @@ -133,7 +135,7 @@ fun LatLon.distanceToArcs(polyLine: List, globeRadius: Double = EARTH_RA if (polyLine.size == 1) return distanceTo(polyLine[0]) var shortestDistance = Double.MAX_VALUE - polyLine.forEachPair { first, second -> + polyLine.forEachLine { first, second -> val distance = distanceToArc(first, second, globeRadius) if (distance < shortestDistance) shortestDistance = distance } @@ -173,7 +175,7 @@ fun Iterable.enclosingBoundingBox(): BoundingBox { fun List.measuredLength(globeRadius: Double = EARTH_RADIUS): Double { if (isEmpty()) return 0.0 var length = 0.0 - forEachPair { first, second -> + forEachLine { first, second -> length += first.distanceTo(second, globeRadius) } return length @@ -185,7 +187,7 @@ fun List.centerLineOfPolyline(globeRadius: Double = EARTH_RADIUS): Pair< require(size >= 2) { "positions list must contain at least 2 elements" } var halfDistance = measuredLength() / 2 - forEachPair { first, second -> + forEachLine { first, second -> halfDistance -= first.distanceTo(second, globeRadius) if (halfDistance <= 0) { return Pair(first, second) @@ -222,7 +224,7 @@ fun List.pointOnPolylineFromEnd(distance: Double): LatLon? { private fun List.pointOnPolyline(distance: Double, fromEnd: Boolean): LatLon? { val list = if (fromEnd) this.asReversed() else this var d = 0.0 - list.forEachPair { first, second -> + list.forEachLine { first, second -> val segmentDistance = first.distanceTo(second) if (segmentDistance > 0) { d += segmentDistance @@ -251,7 +253,7 @@ fun List.centerPointOfPolygon(): LatLon { var lat = 0.0 var area = 0.0 val origin = first() - forEachPair { first, second -> + forEachLine { first, second -> // calculating with offsets to avoid rounding imprecision and 180th meridian problem val dx1 = normalizeLongitude(first.longitude - origin.longitude) val dy1 = first.latitude - origin.latitude @@ -280,7 +282,7 @@ fun LatLon.isInPolygon(polygon: List): Boolean { var lastWasIntersectionAtVertex = false val lon = longitude val lat = latitude - polygon.forEachPair { first, second -> + polygon.forEachLine { first, second -> val lat0 = first.latitude val lat1 = second.latitude // scanline check, disregard line segments parallel to the cast ray @@ -307,6 +309,35 @@ fun LatLon.isInPolygon(polygon: List): Boolean { private fun inside(v: Double, bound0: Double, bound1: Double): Boolean = if (bound0 < bound1) v in bound0..bound1 else v in bound1..bound0 +/** + * Returns the area of a this polygon + */ +fun List.measuredArea(globeRadius: Double = EARTH_RADIUS): Double { + return abs(measuredAreaSigned(globeRadius)) +} + +/** + * Returns the signed area of a this polygon. If it is defined counterclockwise, it'll return + * something positive, clockwise something negative + */ +fun List.measuredAreaSigned(globeRadius: Double = EARTH_RADIUS): Double { + // not closed: area 0 + if (size < 4) return 0.0 + if (first().latitude != last().latitude || first().longitude != last().longitude) return 0.0 + var area = 0.0 + /* The algorithm is basically the same as for the planar case, only the calculation of the area + * for each polygon edge is the polar triangle area */ + forEachLine { first, second -> + area += polarTriangleArea( + second.latitude.toRadians(), + second.longitude.toRadians(), + first.latitude.toRadians(), + first.longitude.toRadians() + ) + } + return area * (globeRadius * globeRadius) +} + /** * Returns whether the given position is within the given multipolygon. Polygons defined * counterclockwise count as outer shells, polygons defined clockwise count as holes. @@ -333,7 +364,7 @@ fun List.isRingDefinedClockwise(): Boolean { var sum = 0.0 val origin = first() - forEachPair { first, second -> + forEachLine { first, second -> // calculating with offsets to handle 180th meridian val lon0 = normalizeLongitude(first.longitude - origin.longitude) val lat0 = first.latitude - origin.latitude @@ -468,3 +499,13 @@ private fun angularDistanceToArc(φ1: Double, λ1: Double, φ2: Double, λ2: Dou if (δat > δ12) return angularDistance(φ2, λ2, φ3, λ3) return δxt } + +/** Returns the signed area of a triangle spanning between the north pole and the two given points + * */ +private fun polarTriangleArea(φ1: Double, λ1: Double, φ2: Double, λ2: Double): Double { + val tanφ1 = tan((PI / 2 - φ1) / 2) + val tanφ2 = tan((PI / 2 - φ2) / 2) + val Δλ = λ1 - λ2 + val tan = tanφ1 * tanφ2 + return 2 * atan2(tan * sin(Δλ), 1 + tan * cos(Δλ)) +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/ktx/CollectionsTest.kt b/app/src/test/java/de/westnordost/streetcomplete/ktx/CollectionsTest.kt index 2c7944935b..60c9a002c0 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/ktx/CollectionsTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/ktx/CollectionsTest.kt @@ -1,5 +1,7 @@ package de.westnordost.streetcomplete.ktx +import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.osmapi.map.data.OsmLatLon import org.junit.Assert.* import org.junit.Test @@ -49,18 +51,23 @@ class CollectionsTest { assertNull(listOf(1, 2, 3).findPrevious(-1) { true }) } - @Test fun `forEachPair with empty list`() { - listOf().forEachPair { _, _ -> fail() } + @Test fun `forEachLine with empty list`() { + listOf().forEachLine { _, _ -> fail() } } - @Test fun `forEachPair with list with only one element`() { - listOf(1).forEachPair { _, _ -> fail() } + @Test fun `forEachLine with list with only one element`() { + listOf(OsmLatLon(0.0,0.0)).forEachLine { _, _ -> fail() } } - @Test fun `forEachPair with several elements`() { + @Test fun `forEachLine with several elements`() { var counter = 0 - listOf(1,2,3,4).forEachPair { first, second -> - assertEquals(first+1, second) + listOf( + OsmLatLon(0.0,0.0), + OsmLatLon(1.0,0.0), + OsmLatLon(2.0,0.0), + OsmLatLon(3.0,0.0), + ).forEachLine { first, second -> + assertEquals(first.latitude + 1, second.latitude, 0.0) counter++ } assertEquals(3, counter) diff --git a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt index cce7c2b861..5396f84889 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt @@ -392,7 +392,7 @@ class SphericalEarthMathTest { } @Test fun `point at polygon edge is in polygon`() { - val square = p(0.0, 0.0).createSquare(10.0) + val square = p(0.0, 0.0).createClockwiseSquare(10.0) assertTrue(p(0.0, 10.0).isInPolygon(square)) assertTrue(p(10.0, 0.0).isInPolygon(square)) assertTrue(p(-10.0, 0.0).isInPolygon(square)) @@ -400,7 +400,7 @@ class SphericalEarthMathTest { } @Test fun `point at polygon edge at 180th meridian is in polygon`() { - val square = p(180.0, 0.0).createSquare(10.0) + val square = p(180.0, 0.0).createClockwiseSquare(10.0) assertTrue(p(180.0, 10.0).isInPolygon(square)) assertTrue(p(-170.0, 0.0).isInPolygon(square)) assertTrue(p(170.0, 0.0).isInPolygon(square)) @@ -477,20 +477,20 @@ class SphericalEarthMathTest { } @Test fun `point outside polygon is outside polygon`() { - assertFalse(p(0.0, 11.0).isInPolygon(p(0.0, 0.0).createSquare(10.0))) + assertFalse(p(0.0, 11.0).isInPolygon(p(0.0, 0.0).createClockwiseSquare(10.0))) } @Test fun `point outside polygon is outside polygon at 180th meridian`() { - assertFalse(p(-169.0, 0.0).isInPolygon(p(180.0, 0.0).createSquare(10.0))) + assertFalse(p(-169.0, 0.0).isInPolygon(p(180.0, 0.0).createClockwiseSquare(10.0))) } @Test fun `polygon direction does not matter for point-in-polygon check`() { - val square = p(0.0, 0.0).createSquare(10.0).reversed() + val square = p(0.0, 0.0).createClockwiseSquare(10.0).reversed() assertTrue(p(5.0, 5.0).isInPolygon(square)) } @Test fun `polygon direction does not matter for point-in-polygon check at 180th meridian`() { - val square = p(180.0, 0.0).createSquare(10.0).reversed() + val square = p(180.0, 0.0).createClockwiseSquare(10.0).reversed() assertTrue(p(-175.0, 5.0).isInPolygon(square)) } @@ -558,6 +558,28 @@ class SphericalEarthMathTest { assertFalse(p.isInMultipolygon(listOf(way))) } + @Test fun `polygon area is 0 for a polygon with less than 3 edges`() { + val twoEdges = listOf(p(0.0,0.0), p(1.0,0.0), p(1.0,1.0)) + assertEquals(0.0, twoEdges.measuredArea(), 0.0) + } + + @Test fun `polygon area is 0 for a polygon that is not closed`() { + val notClosed = listOf(p(0.0,0.0), p(1.0,0.0), p(1.0,1.0), p(0.0,1.0)) + assertEquals(0.0, notClosed.measuredArea(), 0.0) + } + + @Test fun `polygon area is positive for a counterclockwise polygon`() { + val square = p(0.0,0.0).createClockwiseSquare(1.0).reversed() + assertTrue(square.measuredAreaSigned() > 0) + assertTrue(square.measuredArea() > 0) + } + + @Test fun `polygon area is negative for a clockwise polygon`() { + val square = p(0.0,0.0).createClockwiseSquare(1.0) + assertTrue(square.measuredAreaSigned() < 0) + assertTrue(square.measuredArea() > 0) + } + companion object { private val HH = p(10.0, 53.5) } @@ -572,7 +594,7 @@ private val LatLon.y get() = latitude | + | o---o */ -private fun LatLon.createSquare(l: Double) = listOf( +private fun LatLon.createClockwiseSquare(l: Double) = listOf( p(x + l, y + l), p(x + l, y - l), p(x - l, y - l), From 01de38554cb4b0a65e18431c9ad5d845932956c0 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Sun, 25 Oct 2020 18:54:29 +0100 Subject: [PATCH 37/63] convert forest leaf type quest --- .../streetcomplete/quests/QuestModule.kt | 2 +- .../quests/leaf_detail/AddForestLeafType.kt | 41 +++++++++++-------- .../streetcomplete/util/SphericalEarthMath.kt | 15 +++++-- .../util/SphericalEarthMathTest.kt | 20 +++++---- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 0574f144cd..671d8d2238 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -154,7 +154,7 @@ object QuestModule AddPathSurface(r), // used by OSM Carto, OsmAnd AddTracktype(r), // widely used in map rendering - OSM Carto, OsmAnd... AddMaxWeight(), // used by OSRM and other routing engines - AddForestLeafType(o), // used by OSM Carto + AddForestLeafType(), // used by OSM Carto AddBikeParkingType(), // used by OsmAnd AddStepsRamp(r), AddWheelchairAccessToilets(r), // used by wheelmap, OsmAnd, MAPS.ME diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt index 58510e53c5..55ecf5b3b1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt @@ -1,34 +1,39 @@ package de.westnordost.streetcomplete.quests.leaf_detail -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.util.measuredMultiPolygonArea + +class AddForestLeafType : OsmMapDataQuestType { + private val areaFilter by lazy { ElementFiltersParser().parse(""" + ways, relations with landuse = forest or natural = wood and !leaf_type + """)} + private val wayFilter by lazy { ElementFiltersParser().parse(""" + ways with natural = tree_row and !leaf_type + """)} -class AddForestLeafType(private val overpassApi: OverpassMapDataAndGeometryApi) : OsmDownloaderQuestType { override val commitMessage = "Add leaf type" override val wikiLink = "Key:leaf_type" override val icon = R.drawable.ic_quest_leaf override val isSplitWayEnabled = true - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox), handler) + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { + val forests = mapData + .filter { areaFilter.matches(it) } + .filter { + val geometry = mapData.getGeometry(it.type, it.id) as? ElementPolygonsGeometry + val area = geometry?.polygons?.measuredMultiPolygonArea() ?: 0.0 + area > 0.0 && area < 10000 + } + val treeRows = mapData.filter { wayFilter.matches(it) } + return forests + treeRows } - private fun getOverpassQuery(bbox: BoundingBox) = """ - ${bbox.toGlobalOverpassBBox()} - ( - wr[landuse = forest][!leaf_type](if: length()<700.0); - wr[natural = wood][!leaf_type](if: length()<700.0); - way[natural = tree_row][!leaf_type](if: length()<700.0); - ); - ${getQuestPrintStatement()}""".trimIndent() - override fun isApplicableTo(element: Element):Boolean? = null override fun getTitle(tags: Map) = R.string.quest_leafType_title diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt index fe3ca2200a..2658be87b6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt @@ -309,6 +309,15 @@ fun LatLon.isInPolygon(polygon: List): Boolean { private fun inside(v: Double, bound0: Double, bound1: Double): Boolean = if (bound0 < bound1) v in bound0..bound1 else v in bound1..bound0 + +/** + * Returns the area of a this multipolygon, assuming the outer shell is defined counterclockwise and + * any holes are defined clockwise + */ +fun List>.measuredMultiPolygonArea(globeRadius: Double = EARTH_RADIUS): Double { + return sumOf { it.measuredAreaSigned(globeRadius) } +} + /** * Returns the area of a this polygon */ @@ -329,10 +338,10 @@ fun List.measuredAreaSigned(globeRadius: Double = EARTH_RADIUS): Double * for each polygon edge is the polar triangle area */ forEachLine { first, second -> area += polarTriangleArea( + first.latitude.toRadians(), + first.longitude.toRadians(), second.latitude.toRadians(), second.longitude.toRadians(), - first.latitude.toRadians(), - first.longitude.toRadians() ) } return area * (globeRadius * globeRadius) @@ -500,7 +509,7 @@ private fun angularDistanceToArc(φ1: Double, λ1: Double, φ2: Double, λ2: Dou return δxt } -/** Returns the signed area of a triangle spanning between the north pole and the two given points +/** Returns the signed area of a triangle spanning between the north pole and the two given points. * */ private fun polarTriangleArea(φ1: Double, λ1: Double, φ2: Double, λ2: Double): Double { val tanφ1 = tan((PI / 2 - φ1) / 2) diff --git a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt index 5396f84889..7e909cd7de 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt @@ -392,7 +392,7 @@ class SphericalEarthMathTest { } @Test fun `point at polygon edge is in polygon`() { - val square = p(0.0, 0.0).createClockwiseSquare(10.0) + val square = p(0.0, 0.0).createCounterClockwiseSquare(10.0) assertTrue(p(0.0, 10.0).isInPolygon(square)) assertTrue(p(10.0, 0.0).isInPolygon(square)) assertTrue(p(-10.0, 0.0).isInPolygon(square)) @@ -400,7 +400,7 @@ class SphericalEarthMathTest { } @Test fun `point at polygon edge at 180th meridian is in polygon`() { - val square = p(180.0, 0.0).createClockwiseSquare(10.0) + val square = p(180.0, 0.0).createCounterClockwiseSquare(10.0) assertTrue(p(180.0, 10.0).isInPolygon(square)) assertTrue(p(-170.0, 0.0).isInPolygon(square)) assertTrue(p(170.0, 0.0).isInPolygon(square)) @@ -477,20 +477,20 @@ class SphericalEarthMathTest { } @Test fun `point outside polygon is outside polygon`() { - assertFalse(p(0.0, 11.0).isInPolygon(p(0.0, 0.0).createClockwiseSquare(10.0))) + assertFalse(p(0.0, 11.0).isInPolygon(p(0.0, 0.0).createCounterClockwiseSquare(10.0))) } @Test fun `point outside polygon is outside polygon at 180th meridian`() { - assertFalse(p(-169.0, 0.0).isInPolygon(p(180.0, 0.0).createClockwiseSquare(10.0))) + assertFalse(p(-169.0, 0.0).isInPolygon(p(180.0, 0.0).createCounterClockwiseSquare(10.0))) } @Test fun `polygon direction does not matter for point-in-polygon check`() { - val square = p(0.0, 0.0).createClockwiseSquare(10.0).reversed() + val square = p(0.0, 0.0).createCounterClockwiseSquare(10.0).reversed() assertTrue(p(5.0, 5.0).isInPolygon(square)) } @Test fun `polygon direction does not matter for point-in-polygon check at 180th meridian`() { - val square = p(180.0, 0.0).createClockwiseSquare(10.0).reversed() + val square = p(180.0, 0.0).createCounterClockwiseSquare(10.0).reversed() assertTrue(p(-175.0, 5.0).isInPolygon(square)) } @@ -569,13 +569,15 @@ class SphericalEarthMathTest { } @Test fun `polygon area is positive for a counterclockwise polygon`() { - val square = p(0.0,0.0).createClockwiseSquare(1.0).reversed() + val square = p(0.0,0.0).createCounterClockwiseSquare(1.0) + assertFalse(square.isRingDefinedClockwise()) assertTrue(square.measuredAreaSigned() > 0) assertTrue(square.measuredArea() > 0) } @Test fun `polygon area is negative for a clockwise polygon`() { - val square = p(0.0,0.0).createClockwiseSquare(1.0) + val square = p(0.0,0.0).createCounterClockwiseSquare(1.0).reversed() + assertTrue(square.isRingDefinedClockwise()) assertTrue(square.measuredAreaSigned() < 0) assertTrue(square.measuredArea() > 0) } @@ -594,7 +596,7 @@ private val LatLon.y get() = latitude | + | o---o */ -private fun LatLon.createClockwiseSquare(l: Double) = listOf( +private fun LatLon.createCounterClockwiseSquare(l: Double) = listOf( p(x + l, y + l), p(x + l, y - l), p(x - l, y - l), From 2bf1c786c075dd62e9e4c075e050371e2a5556ca Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 01:00:00 +0100 Subject: [PATCH 38/63] add functions to SphericalEarthMath --- .../quests/housenumber/AddHousenumber.kt | 8 +- .../streetcomplete/util/SphericalEarthMath.kt | 61 ++++++++++ .../util/SphericalEarthMathTest.kt | 108 ++++++++++++++++++ 3 files changed, 170 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index b3da34a26b..ec53e193c6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -10,6 +10,7 @@ import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.util.LatLonRaster +import de.westnordost.streetcomplete.util.isCompletelyInside import de.westnordost.streetcomplete.util.isInMultipolygon class AddHousenumber : OsmMapDataQuestType { @@ -173,13 +174,6 @@ private val buildingTypesThatShouldHaveAddresses = listOf( "kindergarten", "train_station", "hotel", "retail", "commercial" ) -/** returns whether this bounding box is completely inside the other */ -private fun BoundingBox.isCompletelyInside(other: BoundingBox): Boolean = - minLongitude >= other.minLongitude && - minLatitude >= other.minLatitude && - maxLongitude <= other.maxLongitude && - maxLatitude <= other.maxLatitude - private fun Element.containsAnyNode(nodeIds: Set, mapData: MapDataWithGeometry): Boolean = when (this) { is Way -> this.nodeIds.any { it in nodeIds } diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt index 2658be87b6..2697095aa4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/SphericalEarthMath.kt @@ -144,6 +144,12 @@ fun LatLon.distanceToArcs(polyLine: List, globeRadius: Double = EARTH_RA /* -------------------------------- Polyline extension functions -------------------------------- */ +/** Returns the shortest distance between this polyline and given polyline */ +fun List.distanceTo(polyline: List, globeRadius: Double = EARTH_RADIUS): Double { + require(isNotEmpty()) { "Polyline must not be empty" } + return minOf { it.distanceToArcs(polyline, globeRadius) } +} + /** Returns a bounding box that contains all points */ fun Iterable.enclosingBoundingBox(): BoundingBox { val it = iterator() @@ -395,7 +401,62 @@ fun BoundingBox.area(globeRadius: Double = EARTH_RADIUS): Double { return min.distanceTo(minLatMaxLon, globeRadius) * min.distanceTo(maxLatMinLon, globeRadius) } +/** Returns a new bounding box that is [radius] larger than this bounding box */ +fun BoundingBox.enlargedBy(radius: Double, globeRadius: Double = EARTH_RADIUS): BoundingBox { + return BoundingBox( + min.translate(radius, 225.0, globeRadius), + max.translate(radius, 45.0, globeRadius) + ) +} +/** returns whether this bounding box intersects with the other. Works if any of the bounding boxes + * cross the 180th meridian */ +fun BoundingBox.intersect(other: BoundingBox): Boolean = + checkAlignment(other) { bbox1, bbox2 -> bbox1.intersectCanonical(bbox2) } + +/** returns whether this bounding box is completely inside the other, assuming both bounding boxes + * do not cross the 180th meridian */ +fun BoundingBox.isCompletelyInside(other: BoundingBox): Boolean = + checkAlignment(other) { bbox1, bbox2 -> bbox1.isCompletelyInsideCanonical(bbox2) } + +/** returns whether this bounding box intersects with the other, assuming both bounding boxes do + * not cross the 180th meridian */ +private fun BoundingBox.intersectCanonical(other: BoundingBox): Boolean = + maxLongitude >= other.minLongitude && + minLongitude <= other.maxLongitude && + maxLatitude >= other.minLatitude && + minLatitude <= other.maxLatitude + +/** returns whether this bounding box is completely inside the other, assuming both bounding boxes + * do not cross the 180th meridian */ +private fun BoundingBox.isCompletelyInsideCanonical(other: BoundingBox): Boolean = + minLongitude >= other.minLongitude && + minLatitude >= other.minLatitude && + maxLongitude <= other.maxLongitude && + maxLatitude <= other.maxLatitude + + +private inline fun BoundingBox.checkAlignment( + other: BoundingBox, + canonicalCheck: (bbox1: BoundingBox, bbox2: BoundingBox) -> Boolean +): Boolean { + return if(crosses180thMeridian()) { + val these = splitAt180thMeridian() + if (other.crosses180thMeridian()) { + val others = other.splitAt180thMeridian() + these.any { a -> others.any { b -> canonicalCheck(a, b) } } + } else { + these.any { canonicalCheck(it, other) } + } + } else { + if (other.crosses180thMeridian()) { + val others = other.splitAt180thMeridian() + others.any { canonicalCheck(this, it) } + } else { + canonicalCheck(this, other) + } + } +} fun createTranslated(latitude: Double, longitude: Double): LatLon { var lat = latitude diff --git a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt index 7e909cd7de..72dee43b53 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/util/SphericalEarthMathTest.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.util +import de.westnordost.osmapi.map.data.BoundingBox import org.junit.Test import de.westnordost.osmapi.map.data.LatLon @@ -243,6 +244,113 @@ class SphericalEarthMathTest { assertEquals(160.0, bbox.minLongitude, 0.0) } + @Test fun `isCompletelyInside works`() { + val bbox1 = BoundingBox(-1.0, -1.0, 1.0, 1.0) + val bbox2 = BoundingBox(-0.5, -0.5, 0.5, 1.0) + assertTrue(bbox2.isCompletelyInside(bbox1)) + assertFalse(bbox1.isCompletelyInside(bbox2)) + } + + @Test fun `isCompletelyInside works at 180th meridian`() { + val bbox1 = BoundingBox(0.0, 179.0, 1.0, -179.0) + val bbox2 = BoundingBox(0.0, 179.5, 0.5, -179.5) + assertTrue(bbox2.isCompletelyInside(bbox1)) + assertFalse(bbox1.isCompletelyInside(bbox2)) + } + + @Test fun `enlargedBy really enlarges bounding box`() { + val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) + assertTrue(bbox.isCompletelyInside(bbox.enlargedBy(1.0))) + } + + @Test fun `enlargedBy really enlarges bounding box, even at 180th meridian`() { + val bbox1 = BoundingBox(0.0, 179.0, 1.0, 180.0) + // enlarged bounding box should go over the 180th meridian + assertTrue(bbox1.isCompletelyInside(bbox1.enlargedBy(100.0))) + // here already bbox2 crosses the 180th meridian, maybe this makes a difference + val bbox2 = BoundingBox(0.0, 179.0, 1.0, -179.0) + assertTrue(bbox2.isCompletelyInside(bbox2.enlargedBy(100.0))) + } + + @Test fun `bounding box not on same latitude do not intersect`() { + val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) + val above = BoundingBox(1.1, 0.0, 1.2, 1.0) + val below = BoundingBox(-0.2, 0.0, -0.1, 1.0) + assertFalse(bbox.intersect(above)) + assertFalse(bbox.intersect(below)) + assertFalse(above.intersect(bbox)) + assertFalse(below.intersect(bbox)) + } + + @Test fun `bounding box not on same longitude do not intersect`() { + val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) + val left = BoundingBox(0.0, -0.2, 1.0, -0.1) + val right = BoundingBox(0.0, 1.1, 1.0, 1.2) + assertFalse(bbox.intersect(left)) + assertFalse(bbox.intersect(right)) + assertFalse(left.intersect(bbox)) + assertFalse(right.intersect(bbox)) + } + + @Test fun `intersecting bounding boxes`() { + val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) + val touchLeft = BoundingBox(0.0, -0.1, 1.0, 0.0) + val touchUpperRightCorner = BoundingBox(1.0, 1.0, 1.1, 1.1) + val completelyInside = BoundingBox(0.4, 0.4, 0.8, 0.8) + val intersectLeft = BoundingBox(0.4, -0.5, 0.5, 0.5) + val intersectRight = BoundingBox(0.4, 0.8, 0.5, 1.2) + val intersectTop = BoundingBox(0.9, 0.4, 1.1, 0.8) + val intersectBottom = BoundingBox(-0.2, 0.4, 0.2, 0.6) + assertTrue(bbox.intersect(touchLeft)) + assertTrue(bbox.intersect(touchUpperRightCorner)) + assertTrue(bbox.intersect(completelyInside)) + assertTrue(bbox.intersect(intersectLeft)) + assertTrue(bbox.intersect(intersectRight)) + assertTrue(bbox.intersect(intersectTop)) + assertTrue(bbox.intersect(intersectBottom)) + // and the other way around + assertTrue(touchLeft.intersect(bbox)) + assertTrue(touchUpperRightCorner.intersect(bbox)) + assertTrue(completelyInside.intersect(bbox)) + assertTrue(intersectLeft.intersect(bbox)) + assertTrue(intersectRight.intersect(bbox)) + assertTrue(intersectTop.intersect(bbox)) + assertTrue(intersectBottom.intersect(bbox)) + } + + @Test fun `bounding box not on same longitude do not intersect, even on 180th meridian`() { + val bbox = BoundingBox(0.0, 179.0, 1.0, -179.0) + val other = BoundingBox(0.0, -178.0, 1.0, 178.0) + assertFalse(bbox.intersect(other)) + assertFalse(other.intersect(bbox)) + } + + @Test fun `intersecting bounding boxes at 180th meridian`() { + val bbox = BoundingBox(0.0, 179.5, 1.0, -170.0) + val touchLeft = BoundingBox(0.0, 179.0, 1.0, 180.0) + val touchUpperRightCorner = BoundingBox(1.0, -170.0, 1.1, -169.0) + val completelyInside = BoundingBox(0.4, 179.9, 0.8, -179.9) + val intersectLeft = BoundingBox(0.4, 179.0, 0.5, 179.9) + val intersectRight = BoundingBox(0.4, -179.0, 0.5, -150.0) + val intersectTop = BoundingBox(0.9, 179.9, 1.1, -179.8) + val intersectBottom = BoundingBox(-0.2, 179.9, 0.2, -179.8) + assertTrue(bbox.intersect(touchLeft)) + assertTrue(bbox.intersect(touchUpperRightCorner)) + assertTrue(bbox.intersect(completelyInside)) + assertTrue(bbox.intersect(intersectLeft)) + assertTrue(bbox.intersect(intersectRight)) + assertTrue(bbox.intersect(intersectTop)) + assertTrue(bbox.intersect(intersectBottom)) + // and the other way around + assertTrue(touchLeft.intersect(bbox)) + assertTrue(touchUpperRightCorner.intersect(bbox)) + assertTrue(completelyInside.intersect(bbox)) + assertTrue(intersectLeft.intersect(bbox)) + assertTrue(intersectRight.intersect(bbox)) + assertTrue(intersectTop.intersect(bbox)) + assertTrue(intersectBottom.intersect(bbox)) + } + /* ++++++++++++++++++++++++++++++ test translating of positions +++++++++++++++++++++++++++++ */ @Test fun `translate latitude north`() { checkTranslate(1000, 0) } From 28346a954d92f094710f40caa49aca144ed1200c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 01:51:36 +0100 Subject: [PATCH 39/63] convert sidewalk quest --- .../streetcomplete/quests/QuestModule.kt | 4 +- .../quests/sidewalk/AddSidewalk.kt | 106 ++++++++++-------- .../streetcomplete/quests/AddSidewalkTest.kt | 61 +++++++++- 3 files changed, 122 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 671d8d2238..248386d8e7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -116,6 +116,8 @@ object QuestModule AddPlaceName(featureDictionaryFuture), AddOneway(), AddSuspectedOneway(trafficFlowSegmentsApi, trafficFlowDao), + AddCycleway(o,r), // for any cyclist routers (and cyclist maps) + AddSidewalk(), // for any pedestrian routers AddBusStopName(), AddBusStopRef(), AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings @@ -139,8 +141,6 @@ object QuestModule AddBikeParkingCapacity(r), // used by cycle map layer on osm.org, OsmAnd AddOrchardProduce(), AddBuildingType(), // because housenumber, building levels etc. depend on it - AddCycleway(o,r), // SLOW QUERY - AddSidewalk(o), // SLOW QUERY AddProhibitedForPedestrians(), // uses info from AddSidewalk quest, should be after it AddCrossingType(r), AddCrossingIsland(), diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt index 7ff48cab8e..44761c9c6a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt @@ -1,19 +1,41 @@ package de.westnordost.streetcomplete.quests.sidewalk -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.util.distanceTo +import de.westnordost.streetcomplete.util.enlargedBy +import de.westnordost.streetcomplete.util.intersect -class AddSidewalk(private val overpassApi: OverpassMapDataAndGeometryApi) : - OsmDownloaderQuestType { +class AddSidewalk : OsmMapDataQuestType { + + /* the filter additionally filters out ways that are unlikely to have sidewalks: + * unpaved roads, roads with very low speed limits and roads that are probably not developed + * enough to have pavement (that are not lit). + * Also, anything explicitly tagged as no pedestrians or explicitly tagged that the sidewalk + * is mapped as a separate way + * */ + private val filter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential + and area != yes + and motorroad != yes + and !sidewalk and !sidewalk:left and !sidewalk:right and !sidewalk:both + and (maxspeed < 8 or maxspeed !~ "5 mph|walk") + and surface !~ ${ANYTHING_UNPAVED.joinToString("|")} + and lit = yes + and foot != no and access !~ private|no + and foot != use_sidepath + """) } + + private val maybeSeparatelyMappedSidewalksFilter by lazy { ElementFiltersParser().parse(""" + ways with highway ~ path|footway|cycleway + """) } override val commitMessage = "Add whether there are sidewalks" override val wikiLink = "Key:sidewalk" @@ -22,42 +44,38 @@ class AddSidewalk(private val overpassApi: OverpassMapDataAndGeometryApi) : override fun getTitle(tags: Map) = R.string.quest_sidewalk_title - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox), handler) - } + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { + val roadsWithMissingSidewalks = mapData.ways.filter { filter.matches(it) } + if (roadsWithMissingSidewalks.isEmpty()) return emptyList() + + /* Unfortunately, the filter above is not enough. In OSM, sidewalks may be mapped as + * separate ways as well and it is not guaranteed that in this case, sidewalk = separate + * (or foot = use_sidepath) is always tagged on the main road then. So, all roads should + * be excluded whose center is within of ~15 meters of a footway, to be on the safe side. */ - /** returns overpass query string to get streets without sidewalk info not near separately mapped - * sidewalks (and other paths) - */ - private fun getOverpassQuery(bbox: BoundingBox): String { - val minDistToWays = 15 //m + val maybeSeparatelyMappedSidewalkGeometries = mapData.ways + .filter { maybeSeparatelyMappedSidewalksFilter.matches(it) } + .mapNotNull { mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry } + if (maybeSeparatelyMappedSidewalkGeometries.isEmpty()) return roadsWithMissingSidewalks - // note: this query is very similar to the query in AddCycleway - return bbox.toGlobalOverpassBBox() + "\n" + - "way[highway ~ '^(primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential)$']" + - "[area != yes]" + - // not any motorroads - "[motorroad != yes]" + - // only without sidewalk tags - "[!sidewalk][!'sidewalk:left'][!'sidewalk:right'][!'sidewalk:both']" + - // not any with very low speed limit because they not very likely to have sidewalks - "[maxspeed !~ '^(8|7|6|5|5 mph|walk)$']" + - // not any unpaved because of the same reason - "[surface !~ '^(" + ANYTHING_UNPAVED.joinToString("|") + ")$']" + - "[lit = yes]" + - // not any explicitly tagged as no pedestrians - "[foot != no]" + - "[access !~ '^(private|no)$']" + - // some roads may be farther than minDistToWays from ways, not tagged with - // footway=separate/sidepath but may have a hint that there is a separately tagged - // sidewalk - "[foot != use_sidepath]" + - " -> .streets;\n" + - "way[highway ~ '^(path|footway|cycleway)$'](around.streets: " + minDistToWays + ")" + - " -> .ways;\n" + - "way.streets(around.ways: " + minDistToWays + ") -> .streets_near_ways;\n" + - "(.streets; - .streets_near_ways;);\n" + - getQuestPrintStatement() + val minDistToWays = 15.0 //m + + // filter out roads with missing sidewalks that... + return roadsWithMissingSidewalks.filter { road -> + val roadGeometry = mapData.getWayGeometry(road.id) as? ElementPolylinesGeometry + if (roadGeometry != null) { + val roadGeometryBounds = roadGeometry.getBounds().enlargedBy(minDistToWays) + // are too close to any footway: + maybeSeparatelyMappedSidewalkGeometries.none { sidewalkGeometry -> + // so, the bounding box of the road and footway intersect and... + roadGeometryBounds.intersect(sidewalkGeometry.getBounds()) && + // ...the minimum distance between these lines is too small + roadGeometry.polylines.single().distanceTo(sidewalkGeometry.polylines.single()) < minDistToWays + } + } else { + false + } + } } override fun isApplicableTo(element: Element): Boolean? = null @@ -78,4 +96,4 @@ class AddSidewalk(private val overpassApi: OverpassMapDataAndGeometryApi) : else -> "none" } } - } +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddSidewalkTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddSidewalkTest.kt index c2ec31dd6b..cc987dcb68 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddSidewalkTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddSidewalkTest.kt @@ -1,16 +1,71 @@ package de.westnordost.streetcomplete.quests +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.quests.sidewalk.AddSidewalk import de.westnordost.streetcomplete.quests.sidewalk.SeparatelyMapped -import de.westnordost.streetcomplete.quests.sidewalk.SidewalkAnswer import de.westnordost.streetcomplete.quests.sidewalk.SidewalkSides +import de.westnordost.streetcomplete.util.translate +import org.junit.Assert.assertEquals import org.junit.Test class AddSidewalkTest { - private val questType = AddSidewalk(mock()) + private val questType = AddSidewalk() + + @Test fun `applicable to road with missing sidewalk`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "primary", + "lit" to "yes" + )) + )) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `not applicable to road with nearby footway`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2), mapOf( + "highway" to "primary", + "lit" to "yes" + )), + OsmWay(2L, 1, listOf(3,4), mapOf( + "highway" to "footway" + )) + )) + val p1 = OsmLatLon(0.0,0.0) + val p2 = p1.translate(50.0, 45.0) + val p3 = p1.translate(14.0, 135.0) + val p4 = p3.translate(50.0, 45.0) + + mapData.wayGeometriesById[1L] = ElementPolylinesGeometry(listOf(listOf(p1, p2)), p1) + mapData.wayGeometriesById[2L] = ElementPolylinesGeometry(listOf(listOf(p3, p4)), p3) + + assertEquals(0, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `applicable to road with footway that is far away enough`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2), mapOf( + "highway" to "primary", + "lit" to "yes" + )), + OsmWay(2L, 1, listOf(3,4), mapOf( + "highway" to "footway" + )) + )) + val p1 = OsmLatLon(0.0,0.0) + val p2 = p1.translate(50.0, 45.0) + val p3 = p1.translate(16.0, 135.0) + val p4 = p3.translate(50.0, 45.0) + + mapData.wayGeometriesById[1L] = ElementPolylinesGeometry(listOf(listOf(p1, p2)), p1) + mapData.wayGeometriesById[2L] = ElementPolylinesGeometry(listOf(listOf(p3, p4)), p3) + + assertEquals(1, questType.getApplicableElements(mapData).toList().size) + } @Test fun `apply no sidewalk answer`() { questType.verifyAnswer( From b0193271c384f1a3cf9a6586823af31ca685c50d Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 20:00:01 +0100 Subject: [PATCH 40/63] convert cycleway quest --- .../streetcomplete/MainActivity.java | 8 +- .../streetcomplete/quests/QuestModule.kt | 2 +- .../quests/bikeway/AddCycleway.kt | 189 +++++++++++------- .../AddClothingBinOperator.kt | 4 +- .../AddRecyclingContainerMaterials.kt | 4 +- .../quests/sidewalk/AddSidewalk.kt | 17 +- .../settings/SettingsFragment.kt | 14 +- .../util/ElementGeometryUtils.kt | 20 +- .../streetcomplete/quests/AddCyclewayTest.kt | 95 ++++++++- 9 files changed, 240 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java index fb7b680212..124cf88438 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java +++ b/app/src/main/java/de/westnordost/streetcomplete/MainActivity.java @@ -14,6 +14,7 @@ import android.os.Build; import android.os.Bundle; import androidx.annotation.AnyThread; +import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -28,9 +29,6 @@ import android.widget.TextView; import android.widget.Toast; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - import javax.inject.Inject; import de.westnordost.osmapi.common.errors.OsmApiException; @@ -387,7 +385,7 @@ else if(e instanceof OsmAuthorizationException) /* --------------------------------- NotificationButtonFragment.Listener ---------------------------------- */ - @Override public void onClickShowNotification(@NotNull Notification notification) + @Override public void onClickShowNotification(@NonNull Notification notification) { Fragment f = getSupportFragmentManager().findFragmentById(R.id.notifications_container_fragment); ((NotificationsContainerFragment) f).showNotification(notification); @@ -400,7 +398,7 @@ else if(e instanceof OsmAuthorizationException) ensureLoggedIn(); } - @Override public void onCreatedNote(@NotNull Point screenPosition) + @Override public void onCreatedNote(@NonNull Point screenPosition) { ensureLoggedIn(); } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 248386d8e7..282eab47db 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -116,7 +116,7 @@ object QuestModule AddPlaceName(featureDictionaryFuture), AddOneway(), AddSuspectedOneway(trafficFlowSegmentsApi, trafficFlowDao), - AddCycleway(o,r), // for any cyclist routers (and cyclist maps) + AddCycleway(r), // for any cyclist routers (and cyclist maps) AddSidewalk(), // for any pedestrian routers AddBusStopName(), AddBusStopRef(), diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index a4c8066ad4..bb60f22d19 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -1,30 +1,26 @@ package de.westnordost.streetcomplete.quests.bikeway -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi -import de.westnordost.streetcomplete.data.elementfilter.getQuestPrintStatement -import de.westnordost.streetcomplete.data.elementfilter.toGlobalOverpassBBox import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.data.osm.osmquest.OsmDownloaderQuestType +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry +import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +import de.westnordost.streetcomplete.util.isNear -class AddCycleway( - private val overpassApi: OverpassMapDataAndGeometryApi, - private val r: ResurveyIntervalsStore -) : OsmDownloaderQuestType { +class AddCycleway(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { override val commitMessage = "Add whether there are cycleways" override val wikiLink = "Key:cycleway" @@ -68,81 +64,57 @@ class AddCycleway( R.string.quest_cycleway_title2 } - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - return overpassApi.query(getOverpassQuery(bbox), handler) - } + override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { + val eligibleRoads = mapData.ways.filter { roadsFilter.matches(it) } - private fun getOverpassQuery(bbox: BoundingBox): String { - val minDistToCycleways = 15 //m + /* we want to return two sets of roads: Those that do not have any cycleway tags, and those + * that do but have been checked more than 4 years ago. */ - val anythingUnpaved = ANYTHING_UNPAVED.joinToString("|") - val olderThan4Years = olderThan(4).toOverpassQLString() - val handledCycleways = KNOWN_CYCLEWAY_VALUES.joinToString("|") - val handledCyclewayLanes = KNOWN_CYCLEWAY_LANE_VALUES.joinToString("|") + /* For the first, the roadsWithMissingCycleway filter is not enough. In OSM, cycleways may be + * mapped as separate ways as well and it is not guaranteed that in this case, + * cycleway = separate or something is always tagged on the main road then. So, all roads + * should be excluded whose center is within of ~15 meters of a cycleway, to be on the safe + * side. */ - /* Excluded is - - anything explicitly tagged as no bicycles or having to use separately mapped sidepath - - if not already tagged with a cycleway: streets with low speed or that are not paved, as - they are very unlikely to have cycleway infrastructure - - if not already tagged, roads that are close (15m) to foot or cycleways (see #718) - - if already tagged, if not older than 8 years or if the cycleway tag uses some unknown value - */ - return bbox.toGlobalOverpassBBox() + """ - way - [highway ~ '^(primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|service)$'] - [motorroad != yes] - [bicycle_road != yes][cyclestreet != yes] - [area != yes] - [bicycle != no][bicycle != designated] - [access !~ '^(private|no)$'] - [bicycle != use_sidepath] - ['bicycle:backward' != use_sidepath]['bicycle:forward' != use_sidepath] - -> .streets; - - way.streets - [highway ~ '^(primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified)$'] - [!cycleway] - [!'cycleway:left'][!'cycleway:right'][!'cycleway:both'] - [!'sidewalk:bicycle'] - [!'sidewalk:both:bicycle'][!'sidewalk:left:bicycle'][!'sidewalk:right:bicycle'] - [maxspeed !~ '^(20|15|10|8|7|6|5|10 mph|5 mph|walk)$'] - [surface !~ '^($anythingUnpaved)$'] - -> .untagged; - - way[highway ~ '^(path|footway|cycleway)$'](around.streets: $minDistToCycleways) -> .cycleways; - way.untagged(around.cycleways: $minDistToCycleways) -> .untagged_near_cycleways; - - way.streets - [~'^(cycleway(:(left|right|both))?)$' ~ '.*'] - $olderThan4Years - -> .old; - - (""" + - KNOWN_CYCLEWAY_KEYS.joinToString("") { "way.old['$it']['$it' !~ '^($handledCycleways)$'];\n" } + - KNOWN_CYCLEWAY_LANES_KEYS.joinToString("") { "way.old['$it']['$it' !~ '^($handledCyclewayLanes)$'];\n" } + - """) -> .old_with_unknown_tags; - - ( - (.untagged; - .untagged_near_cycleways;); - (.old; - .old_with_unknown_tags;); - ); - - ${getQuestPrintStatement()} - """.trimIndent() + val roadsWithMissingCycleway = eligibleRoads.filter { untaggedRoadsFilter.matches(it) } + + val maybeSeparatelyMappedCyclewayGeometries = mapData.ways + .filter { maybeSeparatelyMappedCyclewaysFilter.matches(it) } + .mapNotNull { mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry } + + val minDistToWays = 15.0 //m + + // filter out roads with missing sidewalks that are near footways + val roadsWithMissingCyclewayNotNearSeparateCycleways = roadsWithMissingCycleway.filter { road -> + val roadGeometry = mapData.getWayGeometry(road.id) as? ElementPolylinesGeometry + if (roadGeometry != null) { + !roadGeometry.isNear(minDistToWays, maybeSeparatelyMappedCyclewayGeometries) + } else { + false + } + } + + /* For the second, nothing special. Filter out ways that have been checked less then 4 + * years ago or have no known cycleway tags */ + + val oldRoadsWithKnownCycleways = eligibleRoads.filter { + OLDER_THAN_4_YEARS.matches(it) && it.hasOnlyKnownCyclewayTags() + } + + return roadsWithMissingCyclewayNotNearSeparateCycleways + oldRoadsWithKnownCycleways } + override fun isApplicableTo(element: Element): Boolean? { val tags = element.tags ?: return false // can't determine for yet untagged roads by the tags alone because we need info about // surrounding geometry, but for already tagged ones, we can! if (!tags.keys.containsAny(KNOWN_CYCLEWAY_KEYS)) return null - return olderThan(4).matches(element) - && tags.filterKeys { it in KNOWN_CYCLEWAY_KEYS }.values.all { it in KNOWN_CYCLEWAY_VALUES } - && tags.filterKeys { it in KNOWN_CYCLEWAY_LANES_KEYS }.values.all { it in KNOWN_CYCLEWAY_LANE_VALUES } - } - private fun olderThan(years: Int) = - TagOlderThan("cycleway", RelativeDate(-(r * 365 * years).toFloat())) + return roadsFilter.matches(element) && + OLDER_THAN_4_YEARS.matches(element) && + element.hasOnlyKnownCyclewayTags() + } override fun createForm() = AddCyclewayForm() @@ -302,14 +274,61 @@ class AddCycleway( } companion object { - private val KNOWN_CYCLEWAY_KEYS = listOf( + + /* Excluded is + - anything explicitly tagged as no bicycles or having to use separately mapped sidepath + - if not already tagged with a cycleway: streets with low speed or that are not paved, as + they are very unlikely to have cycleway infrastructure + - if not already tagged, roads that are close (15m) to foot or cycleways (see #718) + - if already tagged, if not older than 8 years or if the cycleway tag uses some unknown value + */ + + // streets what may have cycleway tagging + private val roadsFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|service + and area != yes + and motorroad != yes + and bicycle_road != yes + and cyclestreet != yes + and bicycle != no + and bicycle != designated + and access !~ private|no + and bicycle != use_sidepath + and bicycle:backward != use_sidepath + and bicycle:forward != use_sidepath + """) } + + // streets that do not have cycleway tagging yet + private val untaggedRoadsFilter by lazy { ElementFiltersParser().parse(""" + ways with + highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential + and !cycleway + and !cycleway:left + and !cycleway:right + and !cycleway:both + and !sidewalk:bicycle + and !sidewalk:left:bicycle + and !sidewalk:right:bicycle + and !sidewalk:both:bicycle + and (!maxspeed or maxspeed > 20 or maxspeed !~ "10 mph|5 mph|walk") + and surface !~ ${ANYTHING_UNPAVED.joinToString("|")} + """) } + + private val maybeSeparatelyMappedCyclewaysFilter by lazy { ElementFiltersParser().parse(""" + ways with highway ~ path|footway|cycleway + """) } + + private val OLDER_THAN_4_YEARS = TagOlderThan("cycleway", RelativeDate(-(365 * 4).toFloat())) + + private val KNOWN_CYCLEWAY_KEYS = setOf( "cycleway", "cycleway:left", "cycleway:right", "cycleway:both" ) - private val KNOWN_CYCLEWAY_LANES_KEYS = listOf( + private val KNOWN_CYCLEWAY_LANES_KEYS = setOf( "cycleway:lane", "cycleway:left:lane", "cycleway:right:lane", "cycleway:both:lane" ) - private val KNOWN_CYCLEWAY_VALUES = listOf( + private val KNOWN_CYCLEWAY_VALUES = setOf( "lane", "track", "shared_lane", @@ -346,6 +365,22 @@ class AddCycleway( "mandatory", "exclusive_lane", // same as exclusive. Exclusive lanes are mandatory for bicyclists "soft_lane", "advisory_lane", "dashed" // synonym for advisory lane ) + + private fun Element.hasOnlyKnownCyclewayTags(): Boolean { + val tags = tags ?: return false + + val cyclewayTags = tags.filterKeys { it in KNOWN_CYCLEWAY_KEYS } + // has no cycleway tagging + if (cyclewayTags.isEmpty()) return false + // any cycleway tagging is not known + if (cyclewayTags.values.any { it !in KNOWN_CYCLEWAY_VALUES }) return false + + // any cycleway lane tagging is not known + val cycleLaneTags = tags.filterKeys { it in KNOWN_CYCLEWAY_LANES_KEYS } + if (cycleLaneTags.values.any { it !in KNOWN_CYCLEWAY_LANE_VALUES }) return false + + return true + } } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index 33c05964d1..1218ef3f59 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -32,8 +32,8 @@ class AddClothingBinOperator : OsmMapDataQuestType { return entries.find { it.key.startsWith("recycling:") it.key != "recycling:shoes" && - it.key != "recycling:clothes" && - it.value == "yes" + it.key != "recycling:clothes" && + it.value == "yes" } == null } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 4311fa5aaf..7d8f850d75 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -146,6 +146,6 @@ private fun Element.hasAnyRecyclingMaterials(): Boolean = private fun Element.hasUnknownRecyclingMaterials(): Boolean = tags?.any { it.key.startsWith("recycling:") && - it.key !in allKnownMaterials && - it.value == "yes" + it.key !in allKnownMaterials && + it.value == "yes" } ?: true \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt index 44761c9c6a..f964423565 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt @@ -8,9 +8,7 @@ import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType -import de.westnordost.streetcomplete.util.distanceTo -import de.westnordost.streetcomplete.util.enlargedBy -import de.westnordost.streetcomplete.util.intersect +import de.westnordost.streetcomplete.util.isNear class AddSidewalk : OsmMapDataQuestType { @@ -26,7 +24,7 @@ class AddSidewalk : OsmMapDataQuestType { and area != yes and motorroad != yes and !sidewalk and !sidewalk:left and !sidewalk:right and !sidewalk:both - and (maxspeed < 8 or maxspeed !~ "5 mph|walk") + and (!maxspeed or maxspeed > 8 or maxspeed !~ "5 mph|walk") and surface !~ ${ANYTHING_UNPAVED.joinToString("|")} and lit = yes and foot != no and access !~ private|no @@ -60,18 +58,11 @@ class AddSidewalk : OsmMapDataQuestType { val minDistToWays = 15.0 //m - // filter out roads with missing sidewalks that... + // filter out roads with missing sidewalks that are near footways return roadsWithMissingSidewalks.filter { road -> val roadGeometry = mapData.getWayGeometry(road.id) as? ElementPolylinesGeometry if (roadGeometry != null) { - val roadGeometryBounds = roadGeometry.getBounds().enlargedBy(minDistToWays) - // are too close to any footway: - maybeSeparatelyMappedSidewalkGeometries.none { sidewalkGeometry -> - // so, the bounding box of the road and footway intersect and... - roadGeometryBounds.intersect(sidewalkGeometry.getBounds()) && - // ...the minimum distance between these lines is too small - roadGeometry.polylines.single().distanceTo(sidewalkGeometry.polylines.single()) < minDistToWays - } + !roadGeometry.isNear(minDistToWays, maybeSeparatelyMappedSidewalkGeometries) } else { false } diff --git a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt index e8120575e6..1cc8dd4c42 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt @@ -31,7 +31,6 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope by CoroutineScope(Dispatchers.Main) { @Inject internal lateinit var prefs: SharedPreferences - @Inject internal lateinit var userController: UserController @Inject internal lateinit var downloadedTilesDao: DownloadedTilesDao @Inject internal lateinit var osmQuestController: OsmQuestController @Inject internal lateinit var osmNoteQuestController: OsmNoteQuestController @@ -116,14 +115,11 @@ class SettingsFragment : PreferenceFragmentCompat(), val view = LayoutInflater.from(activity).inflate(R.layout.dialog_tutorial_upload, null) val filled = requireContext().getString(R.string.action_download) val uploadExplanation = view.findViewById(R.id.tutorialDownloadPanel) - uploadExplanation.text = context!!.getString(R.string.dialog_tutorial_download, filled) - context?.let { - AlertDialog.Builder(it) - .setView(view) - .setPositiveButton(android.R.string.ok, null) - .show() - } - + uploadExplanation.text = requireContext().getString(R.string.dialog_tutorial_download, filled) + AlertDialog.Builder(requireContext()) + .setView(view) + .setPositiveButton(android.R.string.ok, null) + .show() } } Prefs.THEME_SELECT -> { diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/ElementGeometryUtils.kt b/app/src/main/java/de/westnordost/streetcomplete/util/ElementGeometryUtils.kt index 38a8119420..ce114edf27 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/ElementGeometryUtils.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/ElementGeometryUtils.kt @@ -5,4 +5,22 @@ import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGe fun ElementPolylinesGeometry.getOrientationAtCenterLineInDegrees(): Float { val centerLine = polylines.first().centerLineOfPolyline() return centerLine.first.initialBearingTo(centerLine.second).toFloat() -} \ No newline at end of file +} + +/** Returns whether this ElementPolylinesGeometry is near a set of other ElementPolylinesGeometries + * + * Warning: This is computationally very expensive ( for normal ways, O(n³) ), avoid if possible */ +fun ElementPolylinesGeometry.isNear( + maxDistance: Double, + others: Iterable +): Boolean { + val bounds = getBounds().enlargedBy(maxDistance) + return others.any { other -> + bounds.intersect(other.getBounds()) && + polylines.any { polyline -> + other.polylines.any { otherPolyline -> + polyline.distanceTo(otherPolyline) <= maxDistance + } + } + } +} diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt index 0924f6836d..9085fff5fe 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt @@ -1,17 +1,20 @@ package de.westnordost.streetcomplete.quests import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.OsmNode +import de.westnordost.osmapi.map.data.OsmLatLon +import de.westnordost.osmapi.map.data.OsmWay import de.westnordost.streetcomplete.data.meta.toCheckDate import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.on import de.westnordost.streetcomplete.quests.bikeway.* import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore +import de.westnordost.streetcomplete.util.translate import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -26,7 +29,93 @@ class AddCyclewayTest { val r: ResurveyIntervalsStore = mock() on(r.times(ArgumentMatchers.anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } on(r.times(ArgumentMatchers.anyDouble())).thenAnswer { (it.arguments[0] as Double) } - questType = AddCycleway(mock(), r) + questType = AddCycleway(r) + } + + + @Test fun `applicable to road with missing cycleway`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "primary" + )) + )) + val p1 = OsmLatLon(0.0,0.0) + val p2 = p1.translate(50.0, 45.0) + mapData.wayGeometriesById[1L] = ElementPolylinesGeometry(listOf(listOf(p1, p2)), p1) + + assertEquals(1, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `not applicable to road with nearby cycleway`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2), mapOf( + "highway" to "primary" + )), + OsmWay(2L, 1, listOf(3,4), mapOf( + "highway" to "cycleway" + )) + )) + val p1 = OsmLatLon(0.0,0.0) + val p2 = p1.translate(50.0, 45.0) + val p3 = p1.translate(14.0, 135.0) + val p4 = p3.translate(50.0, 45.0) + + mapData.wayGeometriesById[1L] = ElementPolylinesGeometry(listOf(listOf(p1, p2)), p1) + mapData.wayGeometriesById[2L] = ElementPolylinesGeometry(listOf(listOf(p3, p4)), p3) + + assertEquals(0, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `applicable to road with cycleway that is far away enough`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2), mapOf( + "highway" to "primary" + )), + OsmWay(2L, 1, listOf(3,4), mapOf( + "highway" to "cycleway" + )) + )) + val p1 = OsmLatLon(0.0,0.0) + val p2 = p1.translate(50.0, 45.0) + val p3 = p1.translate(16.0, 135.0) + val p4 = p3.translate(50.0, 45.0) + + mapData.wayGeometriesById[1L] = ElementPolylinesGeometry(listOf(listOf(p1, p2)), p1) + mapData.wayGeometriesById[2L] = ElementPolylinesGeometry(listOf(listOf(p3, p4)), p3) + + assertEquals(1, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `not applicable to road with cycleway that is not old enough`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "primary", + "cycleway" to "track" + ), null, Date()) + )) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `applicable to road with cycleway that is old enough`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "primary", + "cycleway" to "track", + "check_date:cycleway" to "2001-01-01" + ), null, Date()) + )) + assertEquals(1, questType.getApplicableElements(mapData).toList().size) + } + + @Test fun `not applicable to road with cycleway that is old enough but has unknown cycleway tagging`() { + val mapData = TestMapDataWithGeometry(listOf( + OsmWay(1L, 1, listOf(1,2,3), mapOf( + "highway" to "primary", + "cycleway" to "whatsthis", + "check_date:cycleway" to "2001-01-01" + ), null, Date()) + )) + assertEquals(0, questType.getApplicableElements(mapData).toList().size) } @Test fun `apply cycleway lane answer`() { @@ -489,5 +578,5 @@ class AddCyclewayTest { } private fun createElement(tags: Map, date: Date? = null): Element = - OsmNode(0L, 1, 0.0, 0.0, tags, null, date) + OsmWay(0L, 1, listOf(1,2,3), tags, null, date) } From 35a1f6e98265006cfb5bd4819aa8e9c29e3a63cc Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 20:43:25 +0100 Subject: [PATCH 41/63] remove Overpass-stuff that is unnecessary now --- .github/ISSUE_TEMPLATE/quest-suggestion.md | 2 +- ARCHITECTURE.md | 17 ---- app/build.gradle.kts | 7 -- .../de/westnordost/streetcomplete/Prefs.java | 1 - .../streetcomplete/data/OsmApiModule.kt | 24 +----- .../streetcomplete/data/OsmapiTypealiases.kt | 2 - .../data/elementfilter/OverpassQLUtils.kt | 22 ------ .../mapdata/OverpassMapDataAndGeometryApi.kt | 78 ------------------- .../data/osm/osmquest/OsmElementQuestType.kt | 4 +- .../streetcomplete/data/quest/QuestStatus.kt | 4 +- .../streetcomplete/quests/QuestModule.kt | 2 - .../AddClothingBinOperator.kt | 4 +- app/src/main/res/values/arrays.xml | 4 - app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/preferences.xml | 10 --- .../streetcomplete/OpeningHoursParsingTest.kt | 2 +- .../streetcomplete/QuestsOverpassPrinter.kt | 20 +---- .../elementfilter/OverpassQLUtilsKtTest.kt | 20 ----- .../OverpassMapDataAndGeometryApiTest.kt | 53 ------------- 19 files changed, 10 insertions(+), 270 deletions(-) delete mode 100644 ARCHITECTURE.md delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApi.kt delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtilsKtTest.kt delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApiTest.kt diff --git a/.github/ISSUE_TEMPLATE/quest-suggestion.md b/.github/ISSUE_TEMPLATE/quest-suggestion.md index 0aad34abc6..47cc6d9e87 100644 --- a/.github/ISSUE_TEMPLATE/quest-suggestion.md +++ b/.github/ISSUE_TEMPLATE/quest-suggestion.md @@ -31,7 +31,7 @@ If you are not sure about how one condition applies to your suggestion or you ha ### Ideas for implementation - + **Element selection:** diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 42bf671179..0000000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,17 +0,0 @@ -# Quest data flow - -StreetComplete queries external services to find quest candidates. - -Every quest type has defined some properties, including [Overpass](https://wiki.openstreetmap.org/wiki/Overpass_API) queries. The Overpass instance is queried and responds with OpenStreetMap data matching rules for quest candidates. - -Query responses are used to build a local quest database, displayed to the user as markers on the map. Once a user solves a quest, the solution is stored in the local database as a diff. Changes are [uploaded to OSM](https://wiki.openstreetmap.org/wiki/API_v0.6) as soon as possible. Changes are made in the OSM database using credentials provided by the user. Edits are grouped into changesets by quest types. - -The definition of quests in the program may be simple, with just few parameters and made with reusable blocks, like [quest asking whatever toilet is paid](https://github.com/westnordost/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/quests/toilets_fee/AddToiletsFee.kt). Some definitions are highly complicated, defining special interfaces, using [country specific data](https://github.com/westnordost/StreetComplete/tree/master/res/country_metadata) or involve special processing of data. [The quest asking about house number](https://github.com/westnordost/StreetComplete/tree/master/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber) is a good example of a quest handling quite complex situations. For starters, not the entire world has the same numbering system – some countries have block-based addressing or addresses with [more than one](https://wiki.openstreetmap.org/wiki/Key:addr:conscriptionnumber) assigned house number. - -Some quests may be based on other data sources. The [note quest](https://github.com/westnordost/StreetComplete/tree/master/app/src/main/java/de/westnordost/streetcomplete/quests/note_discussion) is based on data directly downloaded from the [OSM API](https://wiki.openstreetmap.org/wiki/API_v0.6#Map_Notes_API). The [oneway quest](https://github.com/westnordost/StreetComplete/tree/master/app/src/main/java/de/westnordost/streetcomplete/quests/oneway) is using [an external list of roads likely to be a oneway](https://github.com/ENT8R/oneway-data-api). - -The note quest is also special because part of the answer – photos made by users – is uploaded to a [special photo service](https://github.com/exploide/sc-photo-service), as OSM notes do not allow hosting of images directly on OSM servers. - -# Map - -SC downloads the [vector tiles](https://github.com/tilezen/vector-datasource) used for displaying its map from an external source and renders them using the library [tangram-es](https://github.com/tangrams/tangram-es). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d70bc6265f..f4e72c9720 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -86,12 +86,6 @@ configurations { all { exclude(group = "net.sf.kxml", module = "kxml2") } - - compile.configure { - exclude(group = "org.jetbrains", module = "annotations") - exclude(group = "com.intellij", module = "annotations") - exclude(group = "org.intellij", module = "annotations") - } } dependencies { @@ -146,7 +140,6 @@ dependencies { // finding a name for a feature without a name tag implementation("de.westnordost:osmfeatures-android:1.1") // talking with the OSM API - implementation("de.westnordost:osmapi-overpass:1.1") implementation("de.westnordost:osmapi-map:1.2") implementation("de.westnordost:osmapi-changesets:1.2") implementation("de.westnordost:osmapi-notes:1.1") diff --git a/app/src/main/java/de/westnordost/streetcomplete/Prefs.java b/app/src/main/java/de/westnordost/streetcomplete/Prefs.java index 0fdd8b7884..b5948dc310 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/Prefs.java +++ b/app/src/main/java/de/westnordost/streetcomplete/Prefs.java @@ -15,7 +15,6 @@ public class Prefs KEEP_SCREEN_ON = "display.keepScreenOn", UNGLUE_HINT_TIMES_SHOWN = "unglueHint.shown", THEME_SELECT = "theme.select", - OVERPASS_URL = "overpass_url", RESURVEY_INTERVALS = "quests.resurveyIntervals"; diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt b/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt index 561b3ff027..bb6741b935 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt @@ -1,12 +1,9 @@ package de.westnordost.streetcomplete.data -import android.content.SharedPreferences import dagger.Module import dagger.Provides import de.westnordost.osmapi.OsmConnection -import de.westnordost.osmapi.overpass.OverpassMapDataDao import de.westnordost.streetcomplete.ApplicationConstants -import de.westnordost.streetcomplete.Prefs import de.westnordost.streetcomplete.data.user.OAuthStore import oauth.signpost.OAuthConsumer import javax.inject.Singleton @@ -14,14 +11,7 @@ import javax.inject.Singleton @Module object OsmApiModule { - const val OSM_API_URL = "https://api.openstreetmap.org/api/0.6/" - const val OVERPASS_API_URL = "https://lz4.overpass-api.de/api/" - - // see https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#timeout: - // default value is 180 seconds - // give additional 4 seconds to get and process refusal from Overpass - // or maybe a bit late response rather than trigger timeout exception - private const val OVERPASS_QUERY_TIMEOUT_IN_MILISECONDS = (180 + 4) * 1000 + private const val OSM_API_URL = "https://api.openstreetmap.org/api/0.6/" /** Returns the osm connection singleton used for all daos with the saved oauth consumer */ @Provides @Singleton fun osmConnection(oAuthStore: OAuthStore): OsmConnection { @@ -33,18 +23,6 @@ object OsmApiModule { return OsmConnection(OSM_API_URL, ApplicationConstants.USER_AGENT, consumer) } - @Provides @Singleton - fun overpassMapDataDao(prefs: SharedPreferences): OverpassMapDataDao { - val timeout = OVERPASS_QUERY_TIMEOUT_IN_MILISECONDS - val overpassConnection = OsmConnection( - prefs.getString(Prefs.OVERPASS_URL, OVERPASS_API_URL), - ApplicationConstants.USER_AGENT, - null, - timeout - ) - return OverpassMapDataDao(overpassConnection) - } - @Provides fun userDao(osm: OsmConnection): UserApi = UserApi(osm) @Provides fun notesDao(osm: OsmConnection): NotesApi = NotesApi(osm) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/OsmapiTypealiases.kt b/app/src/main/java/de/westnordost/streetcomplete/data/OsmapiTypealiases.kt index 6b301b29d9..6fee6dd706 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/OsmapiTypealiases.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/OsmapiTypealiases.kt @@ -2,7 +2,6 @@ package de.westnordost.streetcomplete.data import de.westnordost.osmapi.map.MapDataDao import de.westnordost.osmapi.notes.NotesDao -import de.westnordost.osmapi.overpass.OverpassMapDataDao import de.westnordost.osmapi.user.PermissionsDao import de.westnordost.osmapi.user.UserDao @@ -10,4 +9,3 @@ typealias NotesApi = NotesDao typealias PermissionsApi = PermissionsDao typealias MapDataApi = MapDataDao typealias UserApi = UserDao -typealias OverpassMapDataApi = OverpassMapDataDao \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtils.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtils.kt index 2223357597..545488b747 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtils.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtils.kt @@ -1,27 +1,5 @@ package de.westnordost.streetcomplete.data.elementfilter -import de.westnordost.osmapi.map.data.BoundingBox -import java.text.NumberFormat -import java.util.* - -const val DEFAULT_MAX_QUESTS = 2000 - -// by default we limit the number of quests created to something that does not cause -// performance problems -fun getQuestPrintStatement() = "out meta geom $DEFAULT_MAX_QUESTS;" - -fun BoundingBox.toGlobalOverpassBBox() = "[bbox:${toOverpassBbox()}];" - -fun BoundingBox.toOverpassBboxFilter() = "(${toOverpassBbox()})" - -private fun BoundingBox.toOverpassBbox(): String { - val df = NumberFormat.getNumberInstance(Locale.US) - df.maximumFractionDigits = 7 - - return df.format(minLatitude) + "," + df.format(minLongitude) + "," + - df.format(maxLatitude) + "," + df.format(maxLongitude) -} - private val QUOTES_NOT_REQUIRED = Regex("[a-zA-Z_][a-zA-Z0-9_]*|-?[0-9]+") fun String.quoteIfNecessary() = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApi.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApi.kt deleted file mode 100644 index 62470840bf..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApi.kt +++ /dev/null @@ -1,78 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.mapdata - -import android.util.Log -import de.westnordost.streetcomplete.data.OverpassMapDataApi -import de.westnordost.osmapi.map.data.* -import de.westnordost.osmapi.overpass.MapDataWithGeometryHandler -import de.westnordost.osmapi.overpass.OsmTooManyRequestsException -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import javax.inject.Inject - -/** Queries data from the Overpass API and handles quota by suspending the thread until it has - * replenished.*/ -class OverpassMapDataAndGeometryApi @Inject constructor( - private val api: OverpassMapDataApi, - private val elementGeometryCreator: ElementGeometryCreator -) { - - /** Get data from Overpass assuming a "out meta geom;" query and handles automatically waiting - * for the request quota to replenish. - * - * @param query Query string. Either Overpass QL or Overpass XML query string - * @param callback map data callback that is fed the map data and geometry - * @return false if it was interrupted while waiting for the quota to be replenished - * - * @throws OsmBadUserInputException if there is an error if the query - */ - fun query(query: String, callback: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - - val handler = object : MapDataWithGeometryHandler { - override fun handle(bounds: BoundingBox) {} - - override fun handle(node: Node) { - callback(node, elementGeometryCreator.create(node)) - } - - override fun handle(way: Way, bounds: BoundingBox, geometry: MutableList) { - callback(way, elementGeometryCreator.create(way, geometry)) - } - - override fun handle( - relation: Relation, - bounds: BoundingBox, - nodeGeometries: MutableMap, - wayGeometries: MutableMap> - ) { - callback(relation, elementGeometryCreator.create(relation, wayGeometries)) - } - } - - try { - api.queryElementsWithGeometry(query, handler) - } catch (e: OsmTooManyRequestsException) { - val status = api.getStatus() - if (status.availableSlots == 0) { - // apparently sometimes Overpass does not tell the client when the next slot is - // available when there is currently no slot available. So let's just wait 60s - // before trying again - // also, rather wait 1s longer than required cause we only get the time in seconds - val nextAvailableSlotIn = status.nextAvailableSlotIn - val waitInSeconds = if (nextAvailableSlotIn != null) nextAvailableSlotIn + 1 else 60 - Log.i(TAG, "Hit Overpass quota. Waiting ${waitInSeconds}s before continuing") - try { - Thread.sleep(waitInSeconds * 1000L) - } catch (ie: InterruptedException) { - Log.d(TAG, "Thread interrupted while waiting for Overpass quota to be replenished") - return false - } - } - return query(query, callback) - } - return true - } - - companion object { - private const val TAG = "OverpassMapDataGeomDao" - } -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt index ecf7e2fd14..34d03eb891 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt @@ -37,8 +37,8 @@ interface OsmElementQuestType : QuestType { override val title: Int get() = getTitle(emptyMap()) /** returns whether a quest of this quest type could be created out of the given [element]. If the - * element alone does not suffice to find this out (but e.g. an Overpass query would need to be - * made to find this out), this should return null. + * element alone does not suffice to find this out (but f.e. is determined by the data around + * it), this should return null. * * The implications of returning null here is that this quest will never be created directly * as consequence of solving another quest and also after reverting an input, the quest will diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestStatus.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestStatus.kt index a2cfcccea9..2bcf8d1552 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestStatus.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestStatus.kt @@ -10,9 +10,7 @@ enum class QuestStatus { /** the system (decided that it) doesn't show the quest. They may become visible again (-> NEW) */ INVISIBLE, /** the quest has been uploaded (either solved or dropped through conflict). The app needs to - * remember its solved quests for some time before deleting them because the source the app - * is pulling it's data for creating quests from (usually Overpass) lags behind the database - * where the app is uploading its changes to. + * remember its solved quests for some time before deleting them so that they can be reverted * Note quests are generally closed after upload, they are never deleted */ CLOSED, /** the quest has been closed and after that the user chose to revert (aka undo) it. This state diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 282eab47db..32fcc96f75 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -3,7 +3,6 @@ package de.westnordost.streetcomplete.quests import dagger.Module import dagger.Provides import de.westnordost.osmfeatures.FeatureDictionary -import de.westnordost.streetcomplete.data.osm.mapdata.OverpassMapDataAndGeometryApi import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry import de.westnordost.streetcomplete.quests.accepts_cash.AddAcceptsCash @@ -101,7 +100,6 @@ object QuestModule { @Provides @Singleton fun questTypeRegistry( osmNoteQuestType: OsmNoteQuestType, - o: OverpassMapDataAndGeometryApi, r: ResurveyIntervalsStore, roadNameSuggestionsDao: RoadNameSuggestionsDao, trafficFlowSegmentsApi: TrafficFlowSegmentsApi, trafficFlowDao: WayTrafficFlowDao, diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index 1218ef3f59..0e3ee1dbb2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -10,8 +10,8 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType class AddClothingBinOperator : OsmMapDataQuestType { /* not the complete filter, see below: we want to filter out additionally all elements that - contain any recycling:* = yes that is not shoes or clothes but this can neither be expressed - in the elements filter syntax nor overpass QL */ + contain any recycling:* = yes that is not shoes or clothes but this can not be expressed + in the elements filter syntax */ private val filter by lazy { ElementFiltersParser().parse(""" nodes with amenity = recycling and recycling_type = container and recycling:clothes = yes diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 3a001e1014..fe35e34f1a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -36,8 +36,4 @@ DARK - - "https://lz4.overpass-api.de/api/" - "https://overpass.kumi.systems/api/" - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a68597bc34..63e18fcc09 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -183,7 +183,7 @@ The app does not share your GPS location with anyone. It is used to automaticall <p><b>Data Usage</b></p> <p> -As mentioned, the app directly communicates with OSM infrastructure. That is, with the <a href=\"https://wiki.openstreetmap.org/wiki/Overpass_API\">Overpass API</a> for downloading quests (which is anonymous) and with the <a href=\"https://wiki.openstreetmap.org/wiki/API_v0.6\">OSM API</a> for uploading changes.<br/> +As mentioned, the app directly communicates with OSM infrastructure.<br/> However, before uploading your changes, the app checks with a <a href=\"https://www.westnordost.de/streetcomplete/banned_versions.txt\">simple text file</a> on my server whether it has been banned from uploading any changes. This is a precautionary measure to be able to keep versions of the app that turn out to have critical bugs from possibly corrupting OSM data.</p>" "<p>To display the map, vector tiles are retrieved from %1$s. See their <a href=\"%2$s\">privacy statement</a> for more information.</p>" <p>Photos you attach to a note are uploaded to my server and deleted some days after that note has been resolved. Their meta-data is stripped before upload.</p> @@ -708,8 +708,6 @@ Otherwise, you can download another keyboard in the app store. Popular keyboards Light Dark System default - Change Overpass server - Requires application restart to apply. May bypass censorship, for example in Russia. This street was tagged as having no sidewalk on either side. In the case that there is a sidewalk after all but it is displayed as a separate way, please answer \"sidewalk\". Open location in another app No other map application installed diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c4f2cd3dca..abc7999f5d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -94,16 +94,6 @@ android:negativeButtonText="@android:string/cancel" /> - - - var query = invocation.getArgument(0) as String - // make query overpass-turbo friendly - query = query - .replace("0,0,1,1", "{{bbox}}") - .replace("out meta geom 2000;", "out meta geom;") - print("```\n$query\n```\n") - true - } - val resurveyIntervalsStoreMock: ResurveyIntervalsStore = mock() on(resurveyIntervalsStoreMock.times(anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } on(resurveyIntervalsStoreMock.times(ArgumentMatchers.anyDouble())).thenAnswer { (it.arguments[0] as Double) } val registry = QuestModule.questTypeRegistry( - mock(), overpassMock, resurveyIntervalsStoreMock, mock(), mock(), mock(), mock() + mock(), resurveyIntervalsStoreMock, mock(), mock(), mock(), mock() ) - val bbox = BoundingBox(0.0,0.0,1.0,1.0) - for (questType in registry.all) { if (questType is OsmElementQuestType) { println("### " + questType.javaClass.simpleName) if (questType is OsmFilterQuestType) { val query = "[bbox:{{bbox}}];\n" + questType.filter.toOverpassQLString() + "\n out meta geom;" println("```\n$query\n```") - } else if (questType is OsmDownloaderQuestType) { - questType.download(bbox, mock()) } else { println("Not available, see source code") } diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtilsKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtilsKtTest.kt deleted file mode 100644 index c35b900d3f..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/OverpassQLUtilsKtTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.westnordost.streetcomplete.data.elementfilter - -import de.westnordost.osmapi.map.data.BoundingBox -import org.junit.Assert.* -import org.junit.Test - -class OverpassQLUtilsKtTest { - - @Test fun `truncates overpass bbox`() { - assertEquals( - "[bbox:0.0001235,1.0001235,2.0001235,3.0001235];", - BoundingBox( - 0.0001234567890123456789, - 1.0001234567890123456789, - 2.0001234567890123456789, - 3.0001234567890123456789 - ).toGlobalOverpassBBox()) - } - -} diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApiTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApiTest.kt deleted file mode 100644 index 9b027b5cdf..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/mapdata/OverpassMapDataAndGeometryApiTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.mapdata - -import de.westnordost.streetcomplete.data.OverpassMapDataApi -import de.westnordost.osmapi.overpass.MapDataWithGeometryParser -import de.westnordost.osmapi.overpass.OsmTooManyRequestsException -import de.westnordost.osmapi.overpass.OverpassStatus -import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on -import org.junit.Assert.assertFalse -import org.junit.Test -import org.mockito.Mockito.* -import javax.inject.Provider -import kotlin.concurrent.thread - -class OverpassMapDataAndGeometryApiTest { - - @Test fun handleOverpassQuota() { - val provider = mock>() - on(provider.get()).thenReturn(mock()) - - val status = OverpassStatus() - status.availableSlots = 0 - status.nextAvailableSlotIn = 1 - status.maxAvailableSlots = 2 - - val overpass = mock() - on(overpass.getStatus()).thenReturn(status) - doThrow(OsmTooManyRequestsException::class.java).on(overpass).queryElementsWithGeometry(any(), any()) - val dao = OverpassMapDataAndGeometryApi(overpass, mock()) - // the dao will call get(), get an exception in return, ask its status - // then and at least wait for the specified amount of time before calling again - var result = false - val dlThread = thread { - result = dao.query("") { _, _ -> Unit } - } - // sleep the wait time: Downloader should not try to call - // overpass again in this time - Thread.sleep(status.nextAvailableSlotIn!! * 1000L) - verify(overpass, times(1)).getStatus() - verify(overpass, times(1)).queryElementsWithGeometry(any(), any()) - // now we test if dao will call overpass again after that time. It is not really - // defined when the downloader must call overpass again, lets assume 1.5 secs here and - // change it when it fails - Thread.sleep(1500) - verify(overpass, times(2)).getStatus() - verify(overpass, times(2)).queryElementsWithGeometry(any(), any()) - // we are done here, interrupt thread (still part of the test though...) - dlThread.interrupt() - dlThread.join() - assertFalse(result) - } -} From 820c52fd4e0c4ddf403eebde4d63ae95b9c331a8 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 21:30:38 +0100 Subject: [PATCH 42/63] use different solution than ResurveyIntervalsStore --- .../StreetCompleteApplication.java | 4 + .../elementfilter/filters/RelativeDate.kt | 6 +- .../streetcomplete/quests/QuestModule.kt | 73 +++++++++---------- .../AddBikeParkingCapacity.kt | 5 +- .../quests/bikeway/AddCycleway.kt | 3 +- .../bus_stop_bench/AddBenchStatusOnBusStop.kt | 5 +- .../bus_stop_shelter/AddBusStopShelter.kt | 5 +- .../MarkCompletedBuildingConstruction.kt | 5 +- .../MarkCompletedHighwayConstruction.kt | 5 +- .../quests/crossing_type/AddCrossingType.kt | 5 +- .../quests/diet_type/AddVegan.kt | 5 +- .../quests/diet_type/AddVegetarian.kt | 5 +- .../ferry/AddFerryAccessMotorVehicle.kt | 2 +- .../quests/handrail/AddHandrail.kt | 9 +-- .../internet_access/AddInternetAccess.kt | 5 +- .../AddMotorcycleParkingCapacity.kt | 5 +- .../quests/opening_hours/AddOpeningHours.kt | 6 +- .../quests/parking_fee/AddParkingFee.kt | 5 +- .../AddPostboxCollectionTimes.kt | 5 +- .../AddRailwayCrossingBarrier.kt | 5 +- .../AddRecyclingContainerMaterials.kt | 7 +- .../segregated/AddCyclewaySegregation.kt | 5 +- .../quests/steps_ramp/AddStepsRamp.kt | 7 +- .../summit_register/AddSummitRegister.kt | 5 +- .../quests/surface/AddCyclewayPartSurface.kt | 5 +- .../quests/surface/AddFootwayPartSurface.kt | 5 +- .../quests/surface/AddPathSurface.kt | 7 +- .../quests/surface/AddRoadSurface.kt | 7 +- .../tactile_paving/AddTactilePavingBusStop.kt | 7 +- .../AddTactilePavingCrosswalk.kt | 7 +- .../quests/tracktype/AddTracktype.kt | 9 +-- .../AddTrafficSignalsSound.kt | 7 +- .../AddTrafficSignalsVibration.kt | 7 +- .../quests/way_lit/AddWayLit.kt | 7 +- .../AddWheelchairAccessOutside.kt | 5 +- .../AddWheelchairAccessPublicTransport.kt | 7 +- .../AddWheelchairAccessToilets.kt | 7 +- .../AddWheelchairAccessToiletsPart.kt | 7 +- ...lsStore.kt => ResurveyIntervalsUpdater.kt} | 16 ++-- .../settings/SettingsFragment.kt | 5 +- .../streetcomplete/QuestsOverpassPrinter.kt | 11 +-- .../quests/AddBusStopShelterTest.kt | 3 +- .../quests/AddCrossingTypeTest.kt | 3 +- .../streetcomplete/quests/AddCyclewayTest.kt | 15 +--- .../quests/AddParkingFeeTest.kt | 3 +- .../quests/AddPostboxCollectionTimesTest.kt | 3 +- .../AddRecyclingContainerMaterialsTest.kt | 15 +--- .../opening_hours/AddOpeningHoursTest.kt | 2 +- .../AddRailwayCrossingBarrierTest.kt | 3 +- .../quests/steps_ramp/AddStepsRampTest.kt | 3 +- .../AddTactilePavingCrosswalkTest.kt | 3 +- 51 files changed, 151 insertions(+), 220 deletions(-) rename app/src/main/java/de/westnordost/streetcomplete/settings/{ResurveyIntervalsStore.kt => ResurveyIntervalsUpdater.kt} (56%) diff --git a/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java b/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java index 61e4e9de98..74a41d23a5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java +++ b/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate; import de.westnordost.countryboundaries.CountryBoundaries; import de.westnordost.osmfeatures.FeatureDictionary; +import de.westnordost.streetcomplete.settings.ResurveyIntervalsUpdater; import de.westnordost.streetcomplete.util.CrashReportExceptionHandler; public class StreetCompleteApplication extends Application @@ -18,6 +19,7 @@ public class StreetCompleteApplication extends Application @Inject FutureTask countryBoundariesFuture; @Inject FutureTask featuresDictionaryFuture; @Inject CrashReportExceptionHandler crashReportExceptionHandler; + @Inject ResurveyIntervalsUpdater resurveyIntervalsUpdater; @Inject SharedPreferences prefs; private static final String PRELOAD_TAG = "Preload"; @@ -36,6 +38,8 @@ public void onCreate() Prefs.Theme theme = Prefs.Theme.valueOf(prefs.getString(Prefs.THEME_SELECT, "AUTO")); AppCompatDelegate.setDefaultNightMode(theme.appCompatNightMode); + + resurveyIntervalsUpdater.update(); } /** Load some things in the background that are needed later */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/RelativeDate.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/RelativeDate.kt index 1260165f71..427ede6c3c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/RelativeDate.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/filters/RelativeDate.kt @@ -10,9 +10,13 @@ interface DateFilter { class RelativeDate(val deltaDays: Float): DateFilter { override val date: Date get() { val cal: Calendar = Calendar.getInstance() - cal.add(Calendar.SECOND, (deltaDays * 24 * 60 * 60).toInt()) + cal.add(Calendar.SECOND, (deltaDays * 24 * 60 * 60 * MULTIPLIER).toInt()) return cal.time } + + companion object { + var MULTIPLIER: Float = 1f + } } class FixedDate(override val date: Date): DateFilter \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt index 32fcc96f75..0bee98029b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/QuestModule.kt @@ -90,17 +90,14 @@ import de.westnordost.streetcomplete.quests.traffic_signals_vibrate.AddTrafficSi import de.westnordost.streetcomplete.quests.traffic_signals_sound.AddTrafficSignalsSound import de.westnordost.streetcomplete.quests.way_lit.AddWayLit import de.westnordost.streetcomplete.quests.wheelchair_access.* -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.concurrent.FutureTask import javax.inject.Singleton -@Module -object QuestModule +@Module object QuestModule { @Provides @Singleton fun questTypeRegistry( osmNoteQuestType: OsmNoteQuestType, - r: ResurveyIntervalsStore, roadNameSuggestionsDao: RoadNameSuggestionsDao, trafficFlowSegmentsApi: TrafficFlowSegmentsApi, trafficFlowDao: WayTrafficFlowDao, featureDictionaryFuture: FutureTask @@ -114,48 +111,48 @@ object QuestModule AddPlaceName(featureDictionaryFuture), AddOneway(), AddSuspectedOneway(trafficFlowSegmentsApi, trafficFlowDao), - AddCycleway(r), // for any cyclist routers (and cyclist maps) + AddCycleway(), // for any cyclist routers (and cyclist maps) AddSidewalk(), // for any pedestrian routers AddBusStopName(), AddBusStopRef(), AddIsBuildingUnderground(), //to avoid asking AddHousenumber and other for underground buildings AddHousenumber(), AddAddressStreet(roadNameSuggestionsDao), - MarkCompletedHighwayConstruction(r), + MarkCompletedHighwayConstruction(), AddReligionToPlaceOfWorship(), // icons on maps are different - OSM Carto, mapy.cz, OsmAnd, Sputnik etc AddParkingAccess(), //OSM Carto, mapy.cz, OSMand, Sputnik etc // ↓ 3. useful data that is used by some data consumers AddRecyclingType(), - AddRecyclingContainerMaterials(r), + AddRecyclingContainerMaterials(), AddSport(), - AddRoadSurface(r), // used by BRouter, OsmAnd, OSRM, graphhopper, HOT map style + AddRoadSurface(), // used by BRouter, OsmAnd, OSRM, graphhopper, HOT map style AddMaxSpeed(), // should best be after road surface because it excludes unpaved roads AddMaxHeight(), // OSRM and other routing engines - AddRailwayCrossingBarrier(r), // useful for routing - AddPostboxCollectionTimes(r), - AddOpeningHours(featureDictionaryFuture, r), + AddRailwayCrossingBarrier(), // useful for routing + AddPostboxCollectionTimes(), + AddOpeningHours(featureDictionaryFuture), DetailRoadSurface(), // used by BRouter, OsmAnd, OSRM, graphhopper - AddBikeParkingCapacity(r), // used by cycle map layer on osm.org, OsmAnd + AddBikeParkingCapacity(), // used by cycle map layer on osm.org, OsmAnd AddOrchardProduce(), AddBuildingType(), // because housenumber, building levels etc. depend on it AddProhibitedForPedestrians(), // uses info from AddSidewalk quest, should be after it - AddCrossingType(r), + AddCrossingType(), AddCrossingIsland(), AddBuildingLevels(), - AddBusStopShelter(r), // at least OsmAnd - AddVegetarian(r), - AddVegan(r), - AddInternetAccess(r), // used by OsmAnd - AddParkingFee(r), // used by OsmAnd - AddMotorcycleParkingCapacity(r), - AddPathSurface(r), // used by OSM Carto, OsmAnd - AddTracktype(r), // widely used in map rendering - OSM Carto, OsmAnd... + AddBusStopShelter(), // at least OsmAnd + AddVegetarian(), + AddVegan(), + AddInternetAccess(), // used by OsmAnd + AddParkingFee(), // used by OsmAnd + AddMotorcycleParkingCapacity(), + AddPathSurface(), // used by OSM Carto, OsmAnd + AddTracktype(), // widely used in map rendering - OSM Carto, OsmAnd... AddMaxWeight(), // used by OSRM and other routing engines AddForestLeafType(), // used by OSM Carto AddBikeParkingType(), // used by OsmAnd - AddStepsRamp(r), - AddWheelchairAccessToilets(r), // used by wheelmap, OsmAnd, MAPS.ME + AddStepsRamp(), + AddWheelchairAccessToilets(), // used by wheelmap, OsmAnd, MAPS.ME AddPlaygroundAccess(), //late as in many areas all needed access=private is already mapped AddWheelchairAccessBusiness(featureDictionaryFuture), // used by wheelmap, OsmAnd, MAPS.ME AddToiletAvailability(), //OSM Carto, shown in OsmAnd descriptions @@ -171,25 +168,25 @@ object QuestModule // ↓ 6. may be shown as possibly missing in QA tools // ↓ 7. data useful for only a specific use case - AddWayLit(r), // used by OsmAnd if "Street lighting" is enabled. (Configure map, Map rendering, Details) + AddWayLit(), // used by OsmAnd if "Street lighting" is enabled. (Configure map, Map rendering, Details) AddToiletsFee(), // used by OsmAnd in the object description AddBabyChangingTable(), // used by OsmAnd in the object description AddBikeParkingCover(), // used by OsmAnd in the object description - AddTactilePavingCrosswalk(r), // Paving can be completed while waiting to cross - AddTrafficSignalsSound(r), // Sound needs to be done as or after you're crossing - AddTrafficSignalsVibration(r), + AddTactilePavingCrosswalk(), // Paving can be completed while waiting to cross + AddTrafficSignalsSound(), // Sound needs to be done as or after you're crossing + AddTrafficSignalsVibration(), AddRoofShape(), - AddWheelchairAccessPublicTransport(r), - AddWheelchairAccessOutside(r), - AddTactilePavingBusStop(r), + AddWheelchairAccessPublicTransport(), + AddWheelchairAccessOutside(), + AddTactilePavingBusStop(), AddBridgeStructure(), AddReligionToWaysideShrine(), - AddCyclewaySegregation(r), - MarkCompletedBuildingConstruction(r), + AddCyclewaySegregation(), + MarkCompletedBuildingConstruction(), AddGeneralFee(), AddSelfServiceLaundry(), AddStepsIncline(), // can be gathered while walking perpendicular to the way e.g. the other side of the road or when running/cycling past - AddHandrail(r), // for accessibility of pedestrian routing, can be gathered when walking past + AddHandrail(), // for accessibility of pedestrian routing, can be gathered when walking past AddStepCount(), // can only be gathered when walking along this way, also needs the most effort and least useful AddInformationToTourism(), AddAtmOperator(), @@ -199,18 +196,18 @@ object QuestModule // ↓ 8. defined in the wiki, but not really used by anyone yet. Just collected for // the sake of mapping it in case it makes sense later AddIsDefibrillatorIndoor(), - AddSummitRegister(r), - AddCyclewayPartSurface(r), - AddFootwayPartSurface(r), + AddSummitRegister(), + AddCyclewayPartSurface(), + AddFootwayPartSurface(), AddMotorcycleParkingCover(), AddFireHydrantType(), AddParkingType(), AddPostboxRef(), - AddWheelchairAccessToiletsPart(r), + AddWheelchairAccessToiletsPart(), AddBoardType(), AddPowerPolesMaterial(), AddCarWashType(), - AddBenchStatusOnBusStop(r), + AddBenchStatusOnBusStop(), AddBenchBackrest(), AddTrafficSignalsButton() )) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt index 77bb1b5f8f..8eed319adb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bike_parking_capacity/AddBikeParkingCapacity.kt @@ -4,16 +4,15 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddBikeParkingCapacity(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddBikeParkingCapacity : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with amenity = bicycle_parking and access !~ private|no and ( !capacity - or bicycle_parking ~ stands|wall_loops and capacity older today -${r * 4} years + or bicycle_parking ~ stands|wall_loops and capacity older today -4 years ) """ /* Bike capacity may change more often for stands and wheelbenders as adding or diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index bb60f22d19..d2e0a3fed6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -17,10 +17,9 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import de.westnordost.streetcomplete.util.isNear -class AddCycleway(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { +class AddCycleway : OsmMapDataQuestType { override val commitMessage = "Add whether there are cycleways" override val wikiLink = "Key:cycleway" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt index b9effbbaa4..5b8c99380d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bus_stop_bench/AddBenchStatusOnBusStop.kt @@ -5,9 +5,8 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddBenchStatusOnBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddBenchStatusOnBusStop : OsmFilterQuestType() { override val elementFilter = """ nodes with @@ -17,7 +16,7 @@ class AddBenchStatusOnBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddBusStopShelter : OsmFilterQuestType() { override val elementFilter = """ nodes with @@ -17,7 +16,7 @@ class AddBusStopShelter(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class MarkCompletedBuildingConstruction : OsmFilterQuestType() { override val elementFilter = """ ways with building = construction and (!opening_date or opening_date < today) - and older today -${r * 6} months + and older today -6 months """ override val commitMessage = "Determine whether construction is now completed" override val wikiLink = "Tag:building=construction" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt index a1003a7fff..fcac0ee177 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/construction/MarkCompletedHighwayConstruction.kt @@ -7,15 +7,14 @@ import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.* -class MarkCompletedHighwayConstruction(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class MarkCompletedHighwayConstruction : OsmFilterQuestType() { override val elementFilter = """ ways with highway = construction and (!opening_date or opening_date < today) - and older today -${r * 2} weeks + and older today -2 weeks """ override val commitMessage = "Determine whether construction is now completed" override val wikiLink = "Tag:highway=construction" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt index 521a83ae53..aaf6c1a6ab 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_type/AddCrossingType.kt @@ -5,9 +5,8 @@ import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCrossingType(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddCrossingType : OsmFilterQuestType() { override val elementFilter = """ nodes with highway = crossing @@ -17,7 +16,7 @@ class AddCrossingType(r: ResurveyIntervalsStore) : OsmFilterQuestType() or crossing ~ island|unknown|yes or ( crossing ~ traffic_signals|uncontrolled|zebra|marked|unmarked - and crossing older today -${r * 8} years + and crossing older today -8 years ) ) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt index e3cb8680d0..853a5defa8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegan.kt @@ -4,9 +4,8 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddVegan(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddVegan : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with @@ -16,7 +15,7 @@ class AddVegan(r: ResurveyIntervalsStore) : OsmFilterQuestType() { ) and name and ( !diet:vegan - or diet:vegan != only and diet:vegan older today -${r * 2} years + or diet:vegan != only and diet:vegan older today -2 years ) """ override val commitMessage = "Add vegan diet type" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt index 5255bacea0..3c6a164868 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/diet_type/AddVegetarian.kt @@ -4,15 +4,14 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddVegetarian(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddVegetarian : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with amenity ~ restaurant|cafe|fast_food and name and ( !diet:vegetarian - or diet:vegetarian != only and diet:vegetarian older today -${r * 2} years + or diet:vegetarian != only and diet:vegetarian older today -2 years ) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt index e0dbd0f51d..6e27e9a78f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/ferry/AddFerryAccessMotorVehicle.kt @@ -6,7 +6,7 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddFerryAccessMotorVehicle() : OsmFilterQuestType() { +class AddFerryAccessMotorVehicle : OsmFilterQuestType() { override val elementFilter = "ways, relations with route = ferry and !motor_vehicle" override val commitMessage = "Specify ferry access for motor vehicles" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt index 9c37cb453a..78c61f2efa 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/handrail/AddHandrail.kt @@ -6,9 +6,8 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddHandrail(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddHandrail : OsmFilterQuestType() { override val elementFilter = """ ways with highway = steps @@ -17,9 +16,9 @@ class AddHandrail(r: ResurveyIntervalsStore) : OsmFilterQuestType() { and (!conveying or conveying = no) and ( !handrail and !handrail:center and !handrail:left and !handrail:right - or handrail = no and handrail older today -${r * 4} years - or handrail older today -${r * 8} years - or older today -${r * 8} years + or handrail = no and handrail older today -4 years + or handrail older today -8 years + or older today -8 years ) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt index 00558229d9..bb7b63e1c3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/internet_access/AddInternetAccess.kt @@ -4,9 +4,8 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddInternetAccess(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddInternetAccess : OsmFilterQuestType() { override val elementFilter = """ nodes, ways, relations with @@ -18,7 +17,7 @@ class AddInternetAccess(r: ResurveyIntervalsStore) : OsmFilterQuestType( and ( !internet_access or internet_access = yes - or internet_access older today -${r * 2} years + or internet_access older today -2 years ) """ /* Asked less often than for example opening hours because this quest is only asked for diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt index 2a1550c4e4..c91c832d4f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/motorcycle_parking_capacity/AddMotorcycleParkingCapacity.kt @@ -4,14 +4,13 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddMotorcycleParkingCapacity(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddMotorcycleParkingCapacity : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with amenity = motorcycle_parking and access !~ private|no - and (!capacity or capacity older today -${r * 4} years) + and (!capacity or capacity older today -4 years) """ override val commitMessage = "Add motorcycle parking capacities" override val wikiLink = "Tag:amenity=motorcycle_parking" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 312d4397a0..564ce8fa51 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -11,12 +11,10 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRows import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRules -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import java.util.concurrent.FutureTask class AddOpeningHours ( - private val featureDictionaryFuture: FutureTask, - private val r: ResurveyIntervalsStore + private val featureDictionaryFuture: FutureTask ) : OsmMapDataQuestType { /* See also AddWheelchairAccessBusiness and AddPlaceName, which has a similar list and is/should @@ -84,7 +82,7 @@ class AddOpeningHours ( ) and !opening_hours ) - or opening_hours older today -${r * 1} years + or opening_hours older today -1 years ) and (access !~ private|no) and (name or brand or noname = yes) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt index b0d4f25552..06e35af67e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFee.kt @@ -4,16 +4,15 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddParkingFee(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddParkingFee : OsmFilterQuestType() { override val elementFilter = """ nodes, ways, relations with amenity = parking and access ~ yes|customers|public and ( !fee and !fee:conditional - or fee older today -${r * 8} years + or fee older today -8 years ) """ override val commitMessage = "Add whether there is a parking fee" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt index 98b8346da4..9da87ace9e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/postbox_collection_times/AddPostboxCollectionTimes.kt @@ -6,15 +6,14 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddPostboxCollectionTimes(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddPostboxCollectionTimes : OsmFilterQuestType() { override val elementFilter = """ nodes with amenity = post_box and access !~ private|no and collection_times:signed != no - and (!collection_times or collection_times older today -${r * 2} years) + and (!collection_times or collection_times older today -2 years) """ /* Don't ask again for postboxes without signed collection times. This is very unlikely to diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt index 15e18c9298..7c1e1d1dc7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt @@ -7,14 +7,13 @@ import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddRailwayCrossingBarrier(private val r: ResurveyIntervalsStore) : OsmMapDataQuestType { +class AddRailwayCrossingBarrier : OsmMapDataQuestType { private val crossingFilter by lazy { ElementFiltersParser().parse(""" nodes with railway = level_crossing - and (!crossing:barrier or crossing:barrier older today -${r * 8} years) + and (!crossing:barrier or crossing:barrier older today -8 years) """)} private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 7d8f850d75..0cd04b319a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -11,14 +11,11 @@ import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import de.westnordost.streetcomplete.util.LatLonRaster import de.westnordost.streetcomplete.util.distanceTo import de.westnordost.streetcomplete.util.enclosingBoundingBox -class AddRecyclingContainerMaterials( - private val r: ResurveyIntervalsStore -) : OsmMapDataQuestType { +class AddRecyclingContainerMaterials : OsmMapDataQuestType { private val filter by lazy { ElementFiltersParser().parse(""" nodes with @@ -32,7 +29,7 @@ class AddRecyclingContainerMaterials( override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable { val bbox = mapData.boundingBox ?: return emptyList() - val olderThan2Years = TagOlderThan("recycling", RelativeDate(-(r * 365 * 2).toFloat())) + val olderThan2Years = TagOlderThan("recycling", RelativeDate(-(365 * 2).toFloat())) val containers = mapData.nodes.filter { filter.matches(it) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt index 3fc606adf6..9fbb92527b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/segregated/AddCyclewaySegregation.kt @@ -6,9 +6,8 @@ import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCyclewaySegregation(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddCyclewaySegregation : OsmFilterQuestType() { override val elementFilter = """ ways with @@ -19,7 +18,7 @@ class AddCyclewaySegregation(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddStepsRamp : OsmFilterQuestType() { override val elementFilter = """ ways with highway = steps @@ -18,8 +17,8 @@ class AddStepsRamp(r: ResurveyIntervalsStore) : OsmFilterQuestType { +class AddSummitRegister : OsmMapDataQuestType { private val filter by lazy { ElementFiltersParser().parse(""" nodes with natural = peak and name and - (!summit:register or summit:register older today -${r * 4} years) + (!summit:register or summit:register older today -4 years) """) } override val commitMessage = "Add whether summit register is present" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt index 48cb5cf8b8..278cb96ace 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddCyclewayPartSurface.kt @@ -4,9 +4,8 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddCyclewayPartSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddCyclewayPartSurface : OsmFilterQuestType() { override val elementFilter = """ ways with @@ -16,7 +15,7 @@ class AddCyclewayPartSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddFootwayPartSurface : OsmFilterQuestType() { override val elementFilter = """ ways with @@ -15,7 +14,7 @@ class AddFootwayPartSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddPathSurface : OsmFilterQuestType() { override val elementFilter = """ ways with highway ~ path|footway|cycleway|bridleway|steps @@ -16,8 +15,8 @@ class AddPathSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { and (!conveying or conveying = no) and (!indoor or indoor = no) and ( !surface - or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and surface older today -${r * 4} years - or surface older today -${r * 8} years + or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and surface older today -4 years + or surface older today -8 years ) """ /* ~paved ways are less likely to change the surface type */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt index 017626724c..90dca1e759 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/surface/AddRoadSurface.kt @@ -5,16 +5,15 @@ import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddRoadSurface(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddRoadSurface : OsmFilterQuestType() { override val elementFilter = """ ways with highway ~ ${ROADS_WITH_SURFACES.joinToString("|")} and ( !surface - or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and surface older today -${r * 4} years - or surface older today -${r * 12} years + or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and surface older today -4 years + or surface older today -12 years ) and (access !~ private|no or (foot and foot !~ private|no)) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt index 9f08cc7468..1d7c986fcc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingBusStop.kt @@ -6,9 +6,8 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.ktx.toYesNo -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTactilePavingBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddTactilePavingBusStop : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with @@ -20,8 +19,8 @@ class AddTactilePavingBusStop(r: ResurveyIntervalsStore) : OsmFilterQuestType { +class AddTactilePavingCrosswalk : OsmMapDataQuestType { private val crossingFilter by lazy { ElementFiltersParser().parse(""" nodes with @@ -21,8 +20,8 @@ class AddTactilePavingCrosswalk(private val r: ResurveyIntervalsStore) : OsmMapD ) and ( !tactile_paving - or tactile_paving = no and tactile_paving older today -${r * 4} years - or older today -${r * 8} years + or tactile_paving = no and tactile_paving older today -4 years + or older today -8 years ) """)} diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt index 9c61005652..9ec332c069 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tracktype/AddTracktype.kt @@ -5,17 +5,16 @@ import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTracktype(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddTracktype : OsmFilterQuestType() { override val elementFilter = """ ways with highway = track and ( !tracktype - or tracktype != grade1 and tracktype older today -${r * 4} years - or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and tracktype older today -${r * 4} years - or tracktype older today -${r * 8} years + or tracktype != grade1 and tracktype older today -4 years + or surface ~ ${ANYTHING_UNPAVED.joinToString("|")} and tracktype older today -4 years + or tracktype older today -8 years ) and (access !~ private|no or (foot and foot !~ private|no)) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt index 9725a89e48..8338e04233 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_sound/AddTrafficSignalsSound.kt @@ -6,16 +6,15 @@ import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTrafficSignalsSound(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddTrafficSignalsSound : OsmFilterQuestType() { override val elementFilter = """ nodes with crossing = traffic_signals and highway ~ crossing|traffic_signals and ( !$SOUND_SIGNALS - or $SOUND_SIGNALS = no and $SOUND_SIGNALS older today -${r * 4} years - or $SOUND_SIGNALS older today -${r * 8} years + or $SOUND_SIGNALS = no and $SOUND_SIGNALS older today -4 years + or $SOUND_SIGNALS older today -8 years ) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt index 4393dd1f7b..ac73b2db44 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/traffic_signals_vibrate/AddTrafficSignalsVibration.kt @@ -5,16 +5,15 @@ import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.ktx.toYesNo -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddTrafficSignalsVibration(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddTrafficSignalsVibration : OsmFilterQuestType() { override val elementFilter = """ nodes with crossing = traffic_signals and highway ~ crossing|traffic_signals and ( !$VIBRATING_BUTTON - or $VIBRATING_BUTTON = no and $VIBRATING_BUTTON older today -${r * 4} years - or $VIBRATING_BUTTON older today -${r * 8} years + or $VIBRATING_BUTTON = no and $VIBRATING_BUTTON older today -4 years + or $VIBRATING_BUTTON older today -8 years ) """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt index b50a22519d..f36bd32f76 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/way_lit/AddWayLit.kt @@ -4,9 +4,8 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWayLit(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddWayLit : OsmFilterQuestType() { /* Using sidewalk as a tell-tale tag for (urban) streets which reached a certain level of development. I.e. non-urban streets will usually not even be lit in industrialized @@ -31,8 +30,8 @@ class AddWayLit(r: ResurveyIntervalsStore) : OsmFilterQuestType() { ) and !lit ) - or highway and lit = no and lit older today -${r * 8} years - or highway and lit older today -${r * 16} years + or highway and lit = no and lit older today -8 years + or highway and lit older today -16 years ) and (access !~ private|no or (foot and foot !~ private|no)) and indoor != yes diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt index b2f25f9f60..ce3d28673e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessOutside.kt @@ -4,13 +4,12 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessOutside(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddWheelchairAccessOutside : OsmFilterQuestType() { override val elementFilter = """ nodes, ways, relations with leisure = dog_park - and (!wheelchair or wheelchair older today -${r * 8} years) + and (!wheelchair or wheelchair older today -8 years) """ override val commitMessage = "Add wheelchair access to outside places" override val wikiLink = "Key:wheelchair" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt index 7d1b6e7ffb..292626ed06 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessPublicTransport.kt @@ -4,16 +4,15 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessPublicTransport(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddWheelchairAccessPublicTransport : OsmFilterQuestType() { override val elementFilter = """ nodes, ways, relations with (amenity = bus_station or railway ~ station|subway_entrance) and ( !wheelchair - or wheelchair != yes and wheelchair older today -${r * 4} years - or wheelchair older today -${r * 8} years + or wheelchair != yes and wheelchair older today -4 years + or wheelchair older today -8 years ) """ override val commitMessage = "Add wheelchair access to public transport platforms" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt index af15a3086b..72c792a1e5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToilets.kt @@ -4,17 +4,16 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessToilets(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddWheelchairAccessToilets : OsmFilterQuestType() { override val elementFilter = """ nodes, ways with amenity = toilets and access !~ private|customers and ( !wheelchair - or wheelchair != yes and wheelchair older today -${r * 4} years - or wheelchair older today -${r * 8} years + or wheelchair != yes and wheelchair older today -4 years + or wheelchair older today -8 years ) """ override val commitMessage = "Add wheelchair access to toilets" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt index 192e21a141..effc0105dd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/wheelchair_access/AddWheelchairAccessToiletsPart.kt @@ -4,16 +4,15 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -class AddWheelchairAccessToiletsPart(r: ResurveyIntervalsStore) : OsmFilterQuestType() { +class AddWheelchairAccessToiletsPart : OsmFilterQuestType() { override val elementFilter = """ nodes, ways, relations with name and toilets = yes and ( !toilets:wheelchair - or toilets:wheelchair != yes and toilets:wheelchair older today -${r * 4} years - or toilets:wheelchair older today -${r * 8} years + or toilets:wheelchair != yes and toilets:wheelchair older today -4 years + or toilets:wheelchair older today -8 years ) """ override val commitMessage = "Add wheelchair access to toilets" diff --git a/app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsStore.kt b/app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsUpdater.kt similarity index 56% rename from app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsStore.kt rename to app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsUpdater.kt index 5aaaaa442a..85b48e0b80 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsStore.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/settings/ResurveyIntervalsUpdater.kt @@ -3,19 +3,21 @@ package de.westnordost.streetcomplete.settings import android.content.SharedPreferences import de.westnordost.streetcomplete.Prefs import de.westnordost.streetcomplete.Prefs.ResurveyIntervals.* +import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import javax.inject.Inject import javax.inject.Singleton /** This class is just to access the user's preference about which multiplier for the resurvey * intervals to use */ -@Singleton class ResurveyIntervalsStore @Inject constructor(private val prefs: SharedPreferences) { - operator fun times(num: Double) = num * multiplier - operator fun times(num: Int) = num * multiplier +@Singleton class ResurveyIntervalsUpdater @Inject constructor(private val prefs: SharedPreferences) { + fun update() { + RelativeDate.MULTIPLIER = multiplier + } - private val multiplier: Double get() = when(intervalsPreference) { - LESS_OFTEN -> 2.0 - DEFAULT -> 1.0 - MORE_OFTEN -> 0.5 + private val multiplier: Float get() = when(intervalsPreference) { + LESS_OFTEN -> 2.0f + DEFAULT -> 1.0f + MORE_OFTEN -> 0.5f } private val intervalsPreference: Prefs.ResurveyIntervals get() = diff --git a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt index 1cc8dd4c42..e3584ef7b4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt @@ -20,7 +20,6 @@ import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestController import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuest import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestController -import de.westnordost.streetcomplete.data.user.UserController import de.westnordost.streetcomplete.ktx.toast import kotlinx.coroutines.* import javax.inject.Inject @@ -34,6 +33,7 @@ class SettingsFragment : PreferenceFragmentCompat(), @Inject internal lateinit var downloadedTilesDao: DownloadedTilesDao @Inject internal lateinit var osmQuestController: OsmQuestController @Inject internal lateinit var osmNoteQuestController: OsmNoteQuestController + @Inject internal lateinit var resurveyIntervalsUpdater: ResurveyIntervalsUpdater interface Listener { fun onClickedQuestSelection() @@ -127,6 +127,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AppCompatDelegate.setDefaultNightMode(theme.appCompatNightMode) activity?.recreate() } + Prefs.RESURVEY_INTERVALS -> { + resurveyIntervalsUpdater.update() + } } } diff --git a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt index c633539c69..668d74f1ce 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/QuestsOverpassPrinter.kt @@ -3,19 +3,10 @@ package de.westnordost.streetcomplete import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.osm.osmquest.OsmFilterQuestType import de.westnordost.streetcomplete.quests.QuestModule -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.anyInt fun main() { - val resurveyIntervalsStoreMock: ResurveyIntervalsStore = mock() - on(resurveyIntervalsStoreMock.times(anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } - on(resurveyIntervalsStoreMock.times(ArgumentMatchers.anyDouble())).thenAnswer { (it.arguments[0] as Double) } - - val registry = QuestModule.questTypeRegistry( - mock(), resurveyIntervalsStoreMock, mock(), mock(), mock(), mock() - ) + val registry = QuestModule.questTypeRegistry(mock(), mock(), mock(), mock(), mock()) for (questType in registry.all) { if (questType is OsmElementQuestType) { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt index d89bfea858..b3907fe2ba 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddBusStopShelterTest.kt @@ -3,7 +3,6 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.bus_stop_shelter.AddBusStopShelter import de.westnordost.streetcomplete.quests.bus_stop_shelter.BusStopShelterAnswer import org.junit.Test @@ -11,7 +10,7 @@ import java.util.* class AddBusStopShelterTest { - private val questType = AddBusStopShelter(mock()) + private val questType = AddBusStopShelter() @Test fun `apply shelter yes answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt index 8a5b6b9637..d686ee6a5f 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCrossingTypeTest.kt @@ -3,14 +3,13 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.crossing_type.AddCrossingType import org.junit.Test import java.util.* class AddCrossingTypeTest { - private val questType = AddCrossingType(mock()) + private val questType = AddCrossingType() @Test fun `apply normal answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt index 9085fff5fe..182ca21932 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddCyclewayTest.kt @@ -9,29 +9,16 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on import de.westnordost.streetcomplete.quests.bikeway.* import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import de.westnordost.streetcomplete.util.translate import org.junit.Assert.* -import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers import java.util.* class AddCyclewayTest { - private lateinit var questType: AddCycleway - - @Before fun setUp() { - val r: ResurveyIntervalsStore = mock() - on(r.times(ArgumentMatchers.anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } - on(r.times(ArgumentMatchers.anyDouble())).thenAnswer { (it.arguments[0] as Double) } - questType = AddCycleway(r) - } - + private val questType = AddCycleway() @Test fun `applicable to road with missing cycleway`() { val mapData = TestMapDataWithGeometry(listOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt index de6de7d9ce..d38dfd28d1 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddParkingFeeTest.kt @@ -8,7 +8,6 @@ import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.opening_hours.model.* import de.westnordost.streetcomplete.quests.parking_fee.* import org.junit.Test @@ -16,7 +15,7 @@ import java.util.* class AddParkingFeeTest { - private val questType = AddParkingFee(mock()) + private val questType = AddParkingFee() private val openingHours = OpeningHoursRuleList(listOf( Rule().apply { diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt index eea963acf6..085fc26677 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddPostboxCollectionTimesTest.kt @@ -1,14 +1,13 @@ package de.westnordost.streetcomplete.quests import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.opening_hours.model.Weekdays import de.westnordost.streetcomplete.quests.postbox_collection_times.* import org.junit.Test class AddPostboxCollectionTimesTest { - private val questType = AddPostboxCollectionTimes(mock()) + private val questType = AddPostboxCollectionTimes() @Test fun `apply no signed times answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt index 78ae855ff8..4c4d4f7090 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/AddRecyclingContainerMaterialsTest.kt @@ -6,31 +6,18 @@ import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on import de.westnordost.streetcomplete.quests.recycling_material.AddRecyclingContainerMaterials import de.westnordost.streetcomplete.quests.recycling_material.RecyclingMaterials import de.westnordost.streetcomplete.quests.recycling_material.IsWasteContainer import de.westnordost.streetcomplete.quests.recycling_material.RecyclingMaterial.* -import de.westnordost.streetcomplete.settings.ResurveyIntervalsStore import de.westnordost.streetcomplete.util.translate import org.junit.Assert.assertEquals -import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers.anyDouble -import org.mockito.ArgumentMatchers.anyInt import java.util.* class AddRecyclingContainerMaterialsTest { - @Before fun setUp() { - val r: ResurveyIntervalsStore = mock() - on(r.times(anyInt())).thenAnswer { (it.arguments[0] as Int).toDouble() } - on(r.times(anyDouble())).thenAnswer { (it.arguments[0] as Double) } - questType = AddRecyclingContainerMaterials(r) - } - - private lateinit var questType: AddRecyclingContainerMaterials + private val questType = AddRecyclingContainerMaterials() @Test fun `applicable to container without recycling materials`() { val mapData = TestMapDataWithGeometry(listOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt index f3ed250654..aa496bebab 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursTest.kt @@ -22,7 +22,7 @@ import java.util.* class AddOpeningHoursTest { - private val questType = AddOpeningHours(mock(), mock()) + private val questType = AddOpeningHours(mock()) @Test fun `apply description answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt index d5a6df8607..85a44a7841 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrierTest.kt @@ -2,13 +2,12 @@ package de.westnordost.streetcomplete.quests.railway_crossing import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.osmapi.map.data.OsmWay -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry import org.junit.Assert.* import org.junit.Test class AddRailwayCrossingBarrierTest { - private val questType = AddRailwayCrossingBarrier(mock()) + private val questType = AddRailwayCrossingBarrier() @Test fun `applicable to crossing`() { val mapData = TestMapDataWithGeometry(listOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt index 8e6afc5d00..76cced39e4 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/steps_ramp/AddStepsRampTest.kt @@ -4,14 +4,13 @@ import de.westnordost.streetcomplete.data.meta.toCheckDateString import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryAdd import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryDelete import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.verifyAnswer import org.junit.Test import java.util.* class AddStepsRampTest { - private val questType = AddStepsRamp(mock()) + private val questType = AddStepsRamp() @Test fun `apply bicycle ramp answer`() { questType.verifyAnswer( diff --git a/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt index 74bd5c1d27..4a2a878086 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalkTest.kt @@ -2,13 +2,12 @@ package de.westnordost.streetcomplete.quests.tactile_paving import de.westnordost.osmapi.map.data.OsmNode import de.westnordost.osmapi.map.data.OsmWay -import de.westnordost.streetcomplete.mock import de.westnordost.streetcomplete.quests.TestMapDataWithGeometry import org.junit.Assert.* import org.junit.Test class AddTactilePavingCrosswalkTest { - private val questType = AddTactilePavingCrosswalk(mock()) + private val questType = AddTactilePavingCrosswalk() @Test fun `applicable to crossing`() { val mapData = TestMapDataWithGeometry(listOf( From 612a78dcd9ca798b6a8eca137de27666977e76e0 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Mon, 26 Oct 2020 21:46:21 +0100 Subject: [PATCH 43/63] element filter expression parser can be a static function --- .../data/elementfilter/ElementFiltersParser.kt | 11 +++++------ .../data/osm/osmquest/OsmFilterQuestType.kt | 4 ++-- .../westnordost/streetcomplete/ktx/Element.kt | 6 +++--- .../quests/address/AddAddressStreet.kt | 10 +++++----- .../quests/bikeway/AddCycleway.kt | 14 +++++++------- .../quests/bikeway/AddCyclewayForm.kt | 6 +++--- .../AddClothingBinOperator.kt | 6 +++--- .../crossing_island/AddCrossingIsland.kt | 10 +++++----- .../quests/housenumber/AddHousenumber.kt | 18 +++++++++--------- .../quests/leaf_detail/AddForestLeafType.kt | 11 ++++++----- .../quests/max_height/AddMaxHeight.kt | 10 +++++----- .../streetcomplete/quests/oneway/AddOneway.kt | 10 +++++----- .../oneway_suspects/AddSuspectedOneway.kt | 6 +++--- .../quests/opening_hours/AddOpeningHours.kt | 7 +++---- .../quests/place_name/AddPlaceName.kt | 7 +++---- .../AddRailwayCrossingBarrier.kt | 10 +++++----- .../AddRecyclingContainerMaterials.kt | 6 +++--- .../quests/road_name/AddRoadName.kt | 10 +++++----- .../quests/sidewalk/AddSidewalk.kt | 10 +++++----- .../summit_register/AddSummitRegister.kt | 6 +++--- .../AddTactilePavingCrosswalk.kt | 10 +++++----- ...FiltersParserAndOverpassQueryCreatorTest.kt | 4 ++-- 22 files changed, 95 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt index d13ee8852b..4457a48d8c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParser.kt @@ -16,13 +16,12 @@ import kotlin.math.min * "ways with (highway = residential or highway = tertiary) and !name" (finds all * residential and tertiary roads that have no name) */ -class ElementFiltersParser { - fun parse(input: String): ElementFilterExpression { - // convert all white-spacey things to whitespaces so we do not have to deal with them later - val cursor = StringWithCursor(input.replace("\\s".toRegex(), " ")) - return ElementFilterExpression(cursor.parseElementsDeclaration(), cursor.parseTags()) - } +fun String.toElementFilterExpression(): ElementFilterExpression { + // convert all white-spacey things to whitespaces so we do not have to deal with them later + val cursor = StringWithCursor(replace("\\s".toRegex(), " ")) + + return ElementFilterExpression(cursor.parseElementsDeclaration(), cursor.parseTags()) } private const val WITH = "with" diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt index b207ba375e..81b54cf2c1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt @@ -2,13 +2,13 @@ package de.westnordost.streetcomplete.data.osm.osmquest import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.util.MultiIterable /** Quest type that's based on a simple element filter expression */ abstract class OsmFilterQuestType : OsmMapDataQuestType { - val filter by lazy { ElementFiltersParser().parse(elementFilter) } + val filter by lazy { elementFilter.toElementFilterExpression() } protected abstract val elementFilter: String diff --git a/app/src/main/java/de/westnordost/streetcomplete/ktx/Element.kt b/app/src/main/java/de/westnordost/streetcomplete/ktx/Element.kt index 6f28b47d5f..f0fc70d796 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ktx/Element.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/ktx/Element.kt @@ -1,7 +1,7 @@ package de.westnordost.streetcomplete.ktx import de.westnordost.osmapi.map.data.* -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import java.util.ArrayList fun Element.copy(newId: Long = id, newVersion: Int = version): Element { @@ -24,7 +24,7 @@ fun Element.isArea(): Boolean { } } -private val IS_AREA_EXPR = ElementFiltersParser().parse(""" +private val IS_AREA_EXPR = """ ways with area = yes or area != no and ( aeroway or amenity @@ -49,4 +49,4 @@ private val IS_AREA_EXPR = ElementFiltersParser().parse(""" or cemetery ~ sector|grave or natural ~ wood|scrub|heath|moor|grassland|fell|bare_rock|scree|shingle|sand|mud|water|wetland|glacier|beach|rock|sinkhole or man_made ~ beacon|bridge|campanile|dolphin|lighthouse|obelisk|observatory|tower|bunker_silo|chimney|gasometer|kiln|mineshaft|petroleum_well|silo|storage_tank|watermill|windmill|works|communications_tower|monitoring_station|street_cabinet|pumping_station|reservoir_covered|wastewater_plant|water_tank|water_tower|water_well|water_works - )""") + )""".toElementFilterExpression() diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index 3de23e5a02..15df4c7d30 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -4,7 +4,7 @@ import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Relation import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.AllCountriesExcept @@ -16,17 +16,17 @@ class AddAddressStreet( private val roadNameSuggestionsDao: RoadNameSuggestionsDao ) : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ nodes, ways, relations with addr:housenumber and !addr:street and !addr:place and !addr:block_number or addr:streetnumber and !addr:street - """) } + """.toElementFilterExpression() } - private val roadsWithNamesFilter by lazy { ElementFiltersParser().parse(""" + private val roadsWithNamesFilter by lazy { """ ways with highway ~ ${ALL_ROADS.joinToString("|")} and name - """)} + """.toElementFilterExpression()} override val commitMessage = "Add street/place names to address" override val icon = R.drawable.ic_quest_housenumber_street diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index d2e0a3fed6..4e7cbad535 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -3,9 +3,9 @@ package de.westnordost.streetcomplete.quests.bikeway import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept @@ -283,7 +283,7 @@ class AddCycleway : OsmMapDataQuestType { */ // streets what may have cycleway tagging - private val roadsFilter by lazy { ElementFiltersParser().parse(""" + private val roadsFilter by lazy { """ ways with highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|service and area != yes @@ -296,10 +296,10 @@ class AddCycleway : OsmMapDataQuestType { and bicycle != use_sidepath and bicycle:backward != use_sidepath and bicycle:forward != use_sidepath - """) } + """.toElementFilterExpression() } // streets that do not have cycleway tagging yet - private val untaggedRoadsFilter by lazy { ElementFiltersParser().parse(""" + private val untaggedRoadsFilter by lazy { """ ways with highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential and !cycleway @@ -312,11 +312,11 @@ class AddCycleway : OsmMapDataQuestType { and !sidewalk:both:bicycle and (!maxspeed or maxspeed > 20 or maxspeed !~ "10 mph|5 mph|walk") and surface !~ ${ANYTHING_UNPAVED.joinToString("|")} - """) } + """.toElementFilterExpression() } - private val maybeSeparatelyMappedCyclewaysFilter by lazy { ElementFiltersParser().parse(""" + private val maybeSeparatelyMappedCyclewaysFilter by lazy { """ ways with highway ~ path|footway|cycleway - """) } + """.toElementFilterExpression() } private val OLDER_THAN_4_YEARS = TagOlderThan("cycleway", RelativeDate(-(365 * 4).toFloat())) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCyclewayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCyclewayForm.kt index cc5c7a354d..aac643727d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCyclewayForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCyclewayForm.kt @@ -8,7 +8,7 @@ import java.util.Collections import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.quests.AbstractQuestFormAnswerFragment import de.westnordost.streetcomplete.quests.OtherAnswer import de.westnordost.streetcomplete.quests.StreetSideRotater @@ -32,12 +32,12 @@ class AddCyclewayForm : AbstractQuestFormAnswerFragment() { return result } - private val likelyNoBicycleContraflow = ElementFiltersParser().parse(""" + private val likelyNoBicycleContraflow = """ ways with oneway:bicycle != no and ( oneway ~ yes|-1 and highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified or junction = roundabout ) - """) + """.toElementFilterExpression() private var streetSideRotater: StreetSideRotater? = null diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index 0e3ee1dbb2..306d6eea7d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -3,7 +3,7 @@ package de.westnordost.streetcomplete.quests.clothing_bin_operator import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType @@ -12,11 +12,11 @@ class AddClothingBinOperator : OsmMapDataQuestType { /* not the complete filter, see below: we want to filter out additionally all elements that contain any recycling:* = yes that is not shoes or clothes but this can not be expressed in the elements filter syntax */ - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ nodes with amenity = recycling and recycling_type = container and recycling:clothes = yes and !operator - """)} + """.toElementFilterExpression() } override val commitMessage = "Add clothing bin operator" override val wikiLink = "Tag:amenity=recycling" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt index 09a580008d..a845c2eae2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt @@ -3,7 +3,7 @@ package de.westnordost.streetcomplete.quests.crossing_island import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.toYesNo @@ -11,20 +11,20 @@ import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment class AddCrossingIsland : OsmMapDataQuestType { - private val crossingFilter by lazy { ElementFiltersParser().parse(""" + private val crossingFilter by lazy { """ nodes with highway = crossing and crossing and crossing != island and !crossing:island - """)} + """.toElementFilterExpression()} - private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + private val excludedWaysFilter by lazy { """ ways with highway and access ~ private|no or highway and oneway and oneway != no or highway ~ path|footway|cycleway|pedestrian - """)} + """.toElementFilterExpression()} override val commitMessage = "Add whether pedestrian crossing has an island" override val wikiLink = "Key:crossing:island" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index ec53e193c6..5e34480a53 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -7,7 +7,7 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.util.LatLonRaster import de.westnordost.streetcomplete.util.isCompletelyInside @@ -139,22 +139,22 @@ class AddHousenumber : OsmMapDataQuestType { } } -private val nonBuildingAreasWithAddressFilter by lazy { ElementFiltersParser().parse(""" +private val nonBuildingAreasWithAddressFilter by lazy { """ ways, relations with !building and ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" -""")} + """.toElementFilterExpression()} -private val nonMultipolygonRelationsWithAddressFilter by lazy { ElementFiltersParser().parse(""" +private val nonMultipolygonRelationsWithAddressFilter by lazy { """ relations with type != multipolygon and ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" -""")} + """.toElementFilterExpression()} -private val nodesWithAddressFilter by lazy { ElementFiltersParser().parse(""" +private val nodesWithAddressFilter by lazy { """ nodes with ~"addr:(housenumber|housename|conscriptionnumber|streetnumber)" -""")} + """.toElementFilterExpression()} -private val buildingsWithMissingAddressFilter by lazy { ElementFiltersParser().parse(""" +private val buildingsWithMissingAddressFilter by lazy { """ ways, relations with building ~ ${buildingTypesThatShouldHaveAddresses.joinToString("|")} and location != underground @@ -166,7 +166,7 @@ private val buildingsWithMissingAddressFilter by lazy { ElementFiltersParser().p and !addr:streetnumber and !noaddress and !nohousenumber -""")} + """.toElementFilterExpression()} private val buildingTypesThatShouldHaveAddresses = listOf( "house", "residential", "apartments", "detached", "terrace", "dormitory", "semi", diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt index 55ecf5b3b1..9ce8eef078 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt @@ -3,19 +3,20 @@ package de.westnordost.streetcomplete.quests.leaf_detail import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.util.measuredMultiPolygonArea class AddForestLeafType : OsmMapDataQuestType { - private val areaFilter by lazy { ElementFiltersParser().parse(""" + private val areaFilter by lazy { """ ways, relations with landuse = forest or natural = wood and !leaf_type - """)} - private val wayFilter by lazy { ElementFiltersParser().parse(""" + """.toElementFilterExpression()} + + private val wayFilter by lazy { """ ways with natural = tree_row and !leaf_type - """)} + """.toElementFilterExpression()} override val commitMessage = "Add leaf type" override val wikiLink = "Key:leaf_type" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt index d909de6ce2..7ae3a310f9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt @@ -4,21 +4,21 @@ import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType class AddMaxHeight : OsmMapDataQuestType { - private val nodeFilter by lazy { ElementFiltersParser().parse(""" + private val nodeFilter by lazy { """ nodes with ( barrier = height_restrictor or amenity = parking_entrance and parking ~ underground|multi-storey ) and !maxheight and !maxheight:physical - """)} + """.toElementFilterExpression()} - private val wayFilter by lazy { ElementFiltersParser().parse(""" + private val wayFilter by lazy { """ ways with ( highway ~ motorway|motorway_link|trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|track|road @@ -26,7 +26,7 @@ class AddMaxHeight : OsmMapDataQuestType { ) and (covered = yes or tunnel ~ yes|building_passage|avalanche_protector) and !maxheight and !maxheight:physical - """)} + """.toElementFilterExpression()} override val commitMessage = "Add maximum heights" override val wikiLink = "Key:maxheight" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index 4d85fe62fa..538616fac1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -4,7 +4,7 @@ import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.quests.bikeway.createCyclewaySides @@ -16,17 +16,17 @@ import de.westnordost.streetcomplete.quests.parking_lanes.* class AddOneway : OsmMapDataQuestType { /** find all roads */ - private val allRoadsFilter by lazy { ElementFiltersParser().parse(""" + private val allRoadsFilter by lazy { """ ways with highway ~ ${ALL_ROADS.joinToString("|")} and area != yes - """) } + """.toElementFilterExpression() } /** find only those roads eligible for asking for oneway */ - private val elementFilter by lazy { ElementFiltersParser().parse(""" + private val elementFilter by lazy { """ ways with highway ~ living_street|residential|service|tertiary|unclassified and !oneway and area != yes and junction != roundabout and (access !~ private|no or (foot and foot !~ private|no)) and lanes <= 1 and width - """) } + """.toElementFilterExpression() } override val commitMessage = "Add whether this road is a one-way road because it is quite slim" override val wikiLink = "Key:oneway" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index 098890e94d..ae511d7a9d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -8,7 +8,7 @@ import de.westnordost.osmapi.map.data.LatLon import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegment import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegmentsApi @@ -20,11 +20,11 @@ class AddSuspectedOneway( private val db: WayTrafficFlowDao ) : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road and !oneway and junction != roundabout and area != yes and (access !~ private|no or (foot and foot !~ private|no)) - """) } + """.toElementFilterExpression() } override val commitMessage = "Add whether roads are one-way roads as they were marked as likely oneway by improveosm.org" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 564ce8fa51..353fc14356 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -5,7 +5,7 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.ktx.containsAny @@ -19,7 +19,7 @@ class AddOpeningHours ( /* See also AddWheelchairAccessBusiness and AddPlaceName, which has a similar list and is/should be ordered in the same way for better overview */ - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { (""" nodes, ways, relations with ( ( @@ -87,8 +87,7 @@ class AddOpeningHours ( and (access !~ private|no) and (name or brand or noname = yes) and opening_hours:signed != no - """.trimIndent() - )} + """.trimIndent()).toElementFilterExpression() } override val commitMessage = "Add opening hours" override val wikiLink = "Key:opening_hours" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt index c3107789b0..96c386cd4f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt @@ -5,7 +5,7 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import java.util.concurrent.FutureTask @@ -13,7 +13,7 @@ class AddPlaceName( private val featureDictionaryFuture: FutureTask ) : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { (""" nodes, ways, relations with ( shop and shop !~ no|vacant @@ -91,8 +91,7 @@ class AddPlaceName( ).map { it.key + " ~ " + it.value.joinToString("|") }.joinToString("\n or ") + "\n" + """ ) and !name and !brand and noname != yes and name:signed != no - """.trimIndent() - )} + """.trimIndent()).toElementFilterExpression() } override val commitMessage = "Determine place names" override val wikiLink = "Key:name" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt index 7c1e1d1dc7..c8c984e1c9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt @@ -3,24 +3,24 @@ package de.westnordost.streetcomplete.quests.railway_crossing import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType class AddRailwayCrossingBarrier : OsmMapDataQuestType { - private val crossingFilter by lazy { ElementFiltersParser().parse(""" + private val crossingFilter by lazy { """ nodes with railway = level_crossing and (!crossing:barrier or crossing:barrier older today -8 years) - """)} + """.toElementFilterExpression() } - private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + private val excludedWaysFilter by lazy { """ ways with highway and access ~ private|no or railway ~ tram|abandoned - """)} + """.toElementFilterExpression() } override val commitMessage = "Add type of barrier for railway crossing" override val wikiLink = "Key:crossing:barrier" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index 0cd04b319a..e8ac320a50 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -3,9 +3,9 @@ package de.westnordost.streetcomplete.quests.recycling_material import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser import de.westnordost.streetcomplete.data.elementfilter.filters.RelativeDate import de.westnordost.streetcomplete.data.elementfilter.filters.TagOlderThan +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey @@ -17,10 +17,10 @@ import de.westnordost.streetcomplete.util.enclosingBoundingBox class AddRecyclingContainerMaterials : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ nodes with amenity = recycling and recycling_type = container - """) } + """.toElementFilterExpression() } override val commitMessage = "Add recycled materials to container" override val wikiLink = "Key:recycling" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index 3ba6dc17fc..38f3d43481 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -6,7 +6,7 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.LocalizedName import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao @@ -16,7 +16,7 @@ class AddRoadName( private val roadNameSuggestionsDao: RoadNameSuggestionsDao ) : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ ways with highway ~ primary|secondary|tertiary|unclassified|residential|living_street|pedestrian and !name @@ -28,13 +28,13 @@ class AddRoadName( access !~ private|no or foot and foot !~ private|no ) - """)} + """.toElementFilterExpression() } - private val roadsWithNamesFilter by lazy { ElementFiltersParser().parse(""" + private val roadsWithNamesFilter by lazy { """ ways with highway ~ ${ALL_ROADS.joinToString("|")} and name - """)} + """.toElementFilterExpression() } override val enabledInCountries = AllCountriesExcept("JP") override val commitMessage = "Determine road names and types" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt index f964423565..f5297e5e06 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt @@ -3,7 +3,7 @@ package de.westnordost.streetcomplete.quests.sidewalk import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry @@ -18,7 +18,7 @@ class AddSidewalk : OsmMapDataQuestType { * Also, anything explicitly tagged as no pedestrians or explicitly tagged that the sidewalk * is mapped as a separate way * */ - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ ways with highway ~ primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential and area != yes @@ -29,11 +29,11 @@ class AddSidewalk : OsmMapDataQuestType { and lit = yes and foot != no and access !~ private|no and foot != use_sidepath - """) } + """.toElementFilterExpression() } - private val maybeSeparatelyMappedSidewalksFilter by lazy { ElementFiltersParser().parse(""" + private val maybeSeparatelyMappedSidewalksFilter by lazy { """ ways with highway ~ path|footway|cycleway - """) } + """.toElementFilterExpression() } override val commitMessage = "Add whether there are sidewalks" override val wikiLink = "Key:sidewalk" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt index 7b262092ff..597be3d3b0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt @@ -3,7 +3,7 @@ package de.westnordost.streetcomplete.quests.summit_register import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry @@ -15,11 +15,11 @@ import de.westnordost.streetcomplete.util.distanceToArcs class AddSummitRegister : OsmMapDataQuestType { - private val filter by lazy { ElementFiltersParser().parse(""" + private val filter by lazy { """ nodes with natural = peak and name and (!summit:register or summit:register older today -4 years) - """) } + """.toElementFilterExpression() } override val commitMessage = "Add whether summit register is present" override val wikiLink = "Key:summit:register" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt index ef11dfeba5..43f3b712eb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt @@ -4,7 +4,7 @@ import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.elementfilter.ElementFiltersParser +import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType @@ -12,7 +12,7 @@ import de.westnordost.streetcomplete.ktx.toYesNo class AddTactilePavingCrosswalk : OsmMapDataQuestType { - private val crossingFilter by lazy { ElementFiltersParser().parse(""" + private val crossingFilter by lazy { """ nodes with ( highway = traffic_signals and crossing = traffic_signals and foot != no @@ -23,13 +23,13 @@ class AddTactilePavingCrosswalk : OsmMapDataQuestType { or tactile_paving = no and tactile_paving older today -4 years or older today -8 years ) - """)} + """.toElementFilterExpression() } - private val excludedWaysFilter by lazy { ElementFiltersParser().parse(""" + private val excludedWaysFilter by lazy { """ ways with highway = cycleway and foot !~ yes|designated or highway and access ~ private|no - """)} + """.toElementFilterExpression() } override val commitMessage = "Add tactile pavings on crosswalks" override val wikiLink = "Key:tactile_paving" diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt index 028b46f7e8..22e867d844 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/elementfilter/ElementFiltersParserAndOverpassQueryCreatorTest.kt @@ -383,14 +383,14 @@ class ElementFiltersParserAndOverpassQueryCreatorTest { private fun shouldFail(input: String) { try { - ElementFiltersParser().parse(input) + input.toElementFilterExpression() fail() } catch (ignore: ParseException) { } } private fun check(input: String, output: String) { - val expr = ElementFiltersParser().parse(input) + val expr = input.toElementFilterExpression() assertEquals( output.replace("\n","").replace(" ",""), expr.toOverpassQLString().replace("\n","").replace(" ","")) From 6413787c5d3e8a0662091834d4f118b0e6be1b5c Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Oct 2020 17:29:55 +0100 Subject: [PATCH 44/63] update osmapi dependency There was a performance problem on parsing map data --- app/build.gradle.kts | 8 ++++---- .../streetcomplete/data/user/StatisticsDownloader.kt | 5 +++-- .../de/westnordost/streetcomplete/data/user/UserStore.kt | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f4e72c9720..c4e5268eea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -140,10 +140,10 @@ dependencies { // finding a name for a feature without a name tag implementation("de.westnordost:osmfeatures-android:1.1") // talking with the OSM API - implementation("de.westnordost:osmapi-map:1.2") - implementation("de.westnordost:osmapi-changesets:1.2") - implementation("de.westnordost:osmapi-notes:1.1") - implementation("de.westnordost:osmapi-user:1.1") + implementation("de.westnordost:osmapi-map:1.3") + implementation("de.westnordost:osmapi-changesets:1.3") + implementation("de.westnordost:osmapi-notes:1.2") + implementation("de.westnordost:osmapi-user:1.2") // widgets implementation("androidx.viewpager2:viewpager2:1.0.0") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/StatisticsDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/StatisticsDownloader.kt index 1f508e05bb..b094ff8114 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/StatisticsDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/StatisticsDownloader.kt @@ -1,16 +1,17 @@ package de.westnordost.streetcomplete.data.user -import de.westnordost.osmapi.common.Iso8601CompatibleDateFormat import de.westnordost.streetcomplete.ApplicationConstants import org.json.JSONObject import java.io.IOException import java.net.HttpURLConnection import java.net.URL +import java.text.SimpleDateFormat +import java.util.* /** Downloads statistics from the backend */ class StatisticsDownloader(private val baseUrl: String) { - private val lastActivityDateFormat = Iso8601CompatibleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + private val lastActivityDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US) fun download(osmUserId: Long): Statistics { (URL("$baseUrl?user_id=$osmUserId").openConnection() as HttpURLConnection).run { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/user/UserStore.kt b/app/src/main/java/de/westnordost/streetcomplete/data/user/UserStore.kt index 822649b9a5..0f6bfce70f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/user/UserStore.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/user/UserStore.kt @@ -2,9 +2,9 @@ package de.westnordost.streetcomplete.data.user import android.content.SharedPreferences import androidx.core.content.edit -import de.westnordost.osmapi.common.Iso8601CompatibleDateFormat import de.westnordost.osmapi.user.UserDetails import de.westnordost.streetcomplete.Prefs +import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -21,7 +21,7 @@ import javax.inject.Singleton } private val listeners: MutableList = CopyOnWriteArrayList() - private val dateFormat = Iso8601CompatibleDateFormat("yyyy-MM-dd HH:mm:ss z") + private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US) val userId: Long get() = prefs.getLong(Prefs.OSM_USER_ID, -1) val userName: String? get() = prefs.getString(Prefs.OSM_USER_NAME, null) From 9dcba0461e78600740e6f8878d3f188d3c5d1d25 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Oct 2020 18:03:01 +0100 Subject: [PATCH 45/63] discard Changeset information on parsed elements right away --- .../map/LightweightOsmMapDataFactory.kt | 28 +++++++++++++++++++ .../streetcomplete/data/OsmApiModule.kt | 7 ++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/de/westnordost/osmapi/map/LightweightOsmMapDataFactory.kt diff --git a/app/src/main/java/de/westnordost/osmapi/map/LightweightOsmMapDataFactory.kt b/app/src/main/java/de/westnordost/osmapi/map/LightweightOsmMapDataFactory.kt new file mode 100644 index 0000000000..7ee72cb8ae --- /dev/null +++ b/app/src/main/java/de/westnordost/osmapi/map/LightweightOsmMapDataFactory.kt @@ -0,0 +1,28 @@ +package de.westnordost.osmapi.map + +import de.westnordost.osmapi.changesets.Changeset +import de.westnordost.osmapi.map.data.* +import java.util.* + +/** Same as OsmMapDataFactory only that it throws away the Changeset data included in the OSM + * response */ +class LightweightOsmMapDataFactory : MapDataFactory { + override fun createNode( + id: Long, version: Int, lat: Double, lon: Double, tags: MutableMap?, + changeset: Changeset?, dateEdited: Date? + ): Node = OsmNode(id, version, lat, lon, tags, null, dateEdited) + + override fun createWay( + id: Long, version: Int, nodes: MutableList, tags: MutableMap?, + changeset: Changeset?, dateEdited: Date? + ): Way = OsmWay(id, version, nodes, tags, null, dateEdited) + + override fun createRelation( + id: Long, version: Int, members: MutableList, + tags: MutableMap?, changeset: Changeset?, dateEdited: Date? + ): Relation = OsmRelation(id, version, members, tags, null, dateEdited) + + override fun createRelationMember( + ref: Long, role: String?, type: Element.Type + ): RelationMember = OsmRelationMember(ref, role, type) +} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt b/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt index bb6741b935..aed029757c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/OsmApiModule.kt @@ -3,6 +3,7 @@ package de.westnordost.streetcomplete.data import dagger.Module import dagger.Provides import de.westnordost.osmapi.OsmConnection +import de.westnordost.osmapi.map.LightweightOsmMapDataFactory import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.user.OAuthStore import oauth.signpost.OAuthConsumer @@ -27,5 +28,9 @@ object OsmApiModule { @Provides fun notesDao(osm: OsmConnection): NotesApi = NotesApi(osm) - @Provides fun mapDataDao(osm: OsmConnection): MapDataApi = MapDataApi(osm) + @Provides fun mapDataDao(osm: OsmConnection): MapDataApi { + // generally we are not interested in certain data returned by the OSM API, so we use a + // map data factory that does not include that data + return MapDataApi(osm, LightweightOsmMapDataFactory()) + } } \ No newline at end of file From d9bbeff7b9a585e8395717858860a09753f4765e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Oct 2020 19:34:02 +0100 Subject: [PATCH 46/63] remove QuestDownloader stuff --- .../data/download/QuestDownloader.kt | 21 ---- .../osm/osmquest/OsmDownloaderQuestType.kt | 13 -- .../data/osm/osmquest/OsmQuestDownloader.kt | 81 ------------ .../osm/osmquest/OsmQuestDownloaderTest.kt | 119 ------------------ 4 files changed, 234 deletions(-) delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt delete mode 100644 app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index dadd015329..cfe7081480 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -11,7 +11,6 @@ import de.westnordost.streetcomplete.data.osmnotes.OsmNotesDownloader import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao import de.westnordost.streetcomplete.data.osm.osmquest.* import de.westnordost.streetcomplete.data.osm.osmquest.OsmApiQuestDownloader -import de.westnordost.streetcomplete.data.osm.osmquest.OsmQuestDownloader import de.westnordost.streetcomplete.data.user.UserStore import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider import de.westnordost.streetcomplete.util.TilesRect @@ -23,7 +22,6 @@ import kotlin.math.max /** Takes care of downloading all note and osm quests */ class QuestDownloader @Inject constructor( private val osmNotesDownloaderProvider: Provider, - private val osmQuestDownloaderProvider: Provider, private val osmApiQuestDownloaderProvider: Provider, private val downloadedTilesDao: DownloadedTilesDao, private val questTypeRegistry: QuestTypeRegistry, @@ -76,17 +74,6 @@ class QuestDownloader @Inject constructor( // download multiple quests at once val downloadedQuestTypes = downloadOsmMapDataQuestTypes(bbox, tiles) questTypes.removeAll(downloadedQuestTypes) - - if (questTypes.isEmpty()) return - if (cancelState.get()) return - - // download remaining quests that haven't been downloaded in the previous step - val remainingOsmElementQuestTypes = questTypes.filterIsInstance>() - for (questType in remainingOsmElementQuestTypes) { - if (cancelState.get()) break - downloadOsmQuestType(bbox, tiles, questType) - questTypes.remove(questType) - } } private fun getOsmNoteQuestType() = @@ -121,14 +108,6 @@ class QuestDownloader @Inject constructor( progressListener?.onFinished(noteQuestType.toDownloadItem()) } - private fun downloadOsmQuestType(bbox: BoundingBox, tiles: TilesRect, questType: OsmDownloaderQuestType<*>) { - progressListener?.onStarted(questType.toDownloadItem()) - val questDownload = osmQuestDownloaderProvider.get() - if (questDownload.download(questType, bbox)) { - downloadedTilesDao.put(tiles, questType.javaClass.simpleName) - } - progressListener?.onFinished(questType.toDownloadItem()) - } private fun downloadOsmMapDataQuestTypes(bbox: BoundingBox, tiles: TilesRect): List> { val downloadItem = DownloadItem(R.drawable.ic_search_black_128dp, "Multi download") diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt deleted file mode 100644 index 5665c49460..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmDownloaderQuestType.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry - -/** Quest type based on OSM data which downloads the necessary data to create quests itself */ -interface OsmDownloaderQuestType : OsmElementQuestType { - - /** Downloads map data for this quest type for the given [bbox] and puts the received data into - * the [handler]. Returns whether the download was successful */ - fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt deleted file mode 100644 index 88f166d0da..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloader.kt +++ /dev/null @@ -1,81 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import android.util.Log - -import java.util.concurrent.FutureTask - -import javax.inject.Inject - -import de.westnordost.countryboundaries.CountryBoundaries -import de.westnordost.countryboundaries.intersects -import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.streetcomplete.data.quest.QuestType -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.LatLon -import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource -import kotlin.collections.ArrayList - -/** Takes care of downloading one quest type in a bounding box and persisting the downloaded quests. - * Calls download(bbox) on the quest type */ -class OsmQuestDownloader @Inject constructor( - private val elementDB: MergedElementDao, - private val osmQuestController: OsmQuestController, - private val countryBoundariesFuture: FutureTask, - private val notePositionsSource: NotePositionsSource, - private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker -) { - private val countryBoundaries: CountryBoundaries get() = countryBoundariesFuture.get() - - fun download(questType: OsmDownloaderQuestType<*>, bbox: BoundingBox): Boolean { - val questTypeName = questType.getName() - - val countries = questType.enabledInCountries - if (!countryBoundaries.intersects(bbox, countries)) { - Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") - return true - } - - val elements = mutableListOf() - val quests = ArrayList() - val truncatedBlacklistedPositions = notePositionsSource.getAllPositions(bbox).map { it.truncateTo5Decimals() }.toSet() - - val time = System.currentTimeMillis() - val success = questType.download(bbox) { element, geometry -> - if (elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) { - val quest = OsmQuest(questType, element.type, element.id, geometry!!) - - quests.add(quest) - elements.add(element) - } - } - if (!success) return false - - // elements must be put into DB first because quests have foreign keys on it - elementDB.putAll(elements) - - val replaceResult = osmQuestController.replaceInBBox(quests, bbox, listOf(questTypeName)) - - // note: this could be done after ALL osm quest types have been downloaded if this - // turns out to be slow if done for every quest type - elementDB.deleteUnreferenced() - questType.cleanMetadata() - - val secondsSpent = (System.currentTimeMillis() - time) / 1000 - Log.i(TAG,"$questTypeName: Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests (total: ${quests.size}) in ${secondsSpent}s") - - return true - } - - companion object { - private const val TAG = "QuestDownload" - } -} - -private fun QuestType<*>.getName() = javaClass.simpleName - -// the resulting precision is about ~1 meter (see #1089) -private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) - -private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt deleted file mode 100644 index ca82b0160b..0000000000 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDownloaderTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.countryboundaries.CountryBoundaries -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.Element -import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.osmapi.map.data.OsmNode -import de.westnordost.streetcomplete.any -import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPointGeometry -import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao -import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource -import de.westnordost.streetcomplete.data.quest.AllCountries -import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.quest.Countries -import de.westnordost.streetcomplete.mock -import de.westnordost.streetcomplete.on -import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.* -import java.util.concurrent.FutureTask - - -class OsmQuestDownloaderTest { - private lateinit var elementDb: MergedElementDao - private lateinit var osmQuestController: OsmQuestController - private lateinit var countryBoundaries: CountryBoundaries - private lateinit var notePositionsSource: NotePositionsSource - private lateinit var elementGeometryCreator: ElementGeometryCreator - private lateinit var elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker - private lateinit var downloader: OsmQuestDownloader - - @Before fun setUp() { - elementDb = mock() - osmQuestController = mock() - on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) - countryBoundaries = mock() - elementGeometryCreator = mock() - notePositionsSource = mock() - elementEligibleForOsmQuestChecker = mock() - val countryBoundariesFuture = FutureTask { countryBoundaries } - countryBoundariesFuture.run() - downloader = OsmQuestDownloader(elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, elementEligibleForOsmQuestChecker) - } - - @Test fun `ignore element with invalid geometry`() { - val questType = TestDownloaderQuestType(mapOf( - OsmNode(0, 0, OsmLatLon(1.0, 1.0), null) to - null - )) - - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) - } - - @Test fun `ignore at blacklisted position`() { - val blacklistPos = OsmLatLon(0.3, 0.4) - on(notePositionsSource.getAllPositions(any())).thenReturn(listOf(blacklistPos)) - - val questType = TestDownloaderQuestType(mapOf( - OsmNode(0, 0, blacklistPos, null) to - ElementPointGeometry(blacklistPos) - )) - - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) - } - - @Test fun `ignore element in country for which this quest is disabled`() { - val pos = OsmLatLon(1.0, 1.0) - - val questType = TestDownloaderQuestType(mapOf( - OsmNode(0, 0, pos, null) to - ElementPointGeometry(pos) - )) - questType.enabledInCountries = AllCountriesExcept("AA") - // country boundaries say that position is in AA - on(countryBoundaries.isInAny(anyDouble(),anyDouble(),any())).thenReturn(true) - on(countryBoundaries.getContainingIds(anyDouble(),anyDouble(),anyDouble(),anyDouble())).thenReturn(setOf()) - - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) - } - - @Test fun `creates quest for element`() { - val pos = OsmLatLon(1.0, 1.0) - - val questType = TestDownloaderQuestType(mapOf( - OsmNode(0, 0, pos, null) to - ElementPointGeometry(pos) - )) - - on(osmQuestController.replaceInBBox(any(), any(), any())).thenReturn(OsmQuestController.UpdateResult(0,0)) - - downloader.download(questType, BoundingBox(0.0, 0.0, 1.0, 1.0)) - - verify(elementDb).putAll(any()) - verify(osmQuestController).replaceInBBox(any(), any(), any()) - } -} - -private class TestDownloaderQuestType(private val map: Map) : OsmDownloaderQuestType { - - override var enabledInCountries: Countries = AllCountries - - override fun download(bbox: BoundingBox, handler: (element: Element, geometry: ElementGeometry?) -> Unit): Boolean { - for ((element, geometry) in map) { - handler(element, geometry) - } - return true - } - - override val icon = 0 - override val commitMessage = "" - override fun getTitle(tags: Map) = 0 - override fun createForm() = object : AbstractQuestAnswerFragment() {} - override fun isApplicableTo(element: Element) = false - override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} -} From 5d5451fe1a6972aeaa667407f1b7ba3b011e34db Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Oct 2020 19:36:05 +0100 Subject: [PATCH 47/63] remove limiting number of quest types for download --- .../streetcomplete/ApplicationConstants.java | 3 --- .../data/download/MobileDataAutoDownloadStrategy.kt | 1 - .../data/download/QuestAutoDownloadStrategy.kt | 3 --- .../data/download/QuestDownloadController.kt | 6 ++---- .../data/download/QuestDownloadService.kt | 9 ++------- .../streetcomplete/data/download/QuestDownloader.kt | 11 ++++------- .../data/download/WifiAutoDownloadStrategy.kt | 6 +----- .../streetcomplete/data/quest/QuestAutoSyncer.kt | 5 +---- .../de/westnordost/streetcomplete/map/MainFragment.kt | 2 +- 9 files changed, 11 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java index 615368e6c9..c8d74082f6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java +++ b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java @@ -19,9 +19,6 @@ public class ApplicationConstants public final static int NOTE_MIN_ZOOM = 15; - /** How many quests to download when pressing manually on "download quests" */ - public final static int MANUAL_DOWNLOAD_QUEST_TYPE_COUNT = 10; - /** a "best before" duration for quests. Quests will not be downloaded again for any tile * before the time expired */ public static final long REFRESH_QUESTS_AFTER = 3L*24*60*60*1000; // 3 days in ms diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt index 6785aa1e7a..1ad7edd504 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt @@ -12,7 +12,6 @@ class MobileDataAutoDownloadStrategy @Inject constructor( questTypesProvider: OrderedVisibleQuestTypesProvider ) : AActiveRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypesProvider) { - override val questTypeDownloadCount = 5 override val minQuestsInActiveRadiusPerKm2 = 12 override val activeRadii = intArrayOf(200) override val downloadRadius = 600 diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt index f17996b2df..04d4db2a60 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt @@ -4,9 +4,6 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.LatLon interface QuestAutoDownloadStrategy { - /** returns the number of quest types to retrieve in one run */ - val questTypeDownloadCount: Int - /** returns true if quests should be downloaded automatically at this position now */ fun mayDownloadHere(pos: LatLon): Boolean diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt index 4fda5b48d1..48ad4c5d56 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt @@ -54,14 +54,12 @@ import javax.inject.Singleton * in a (z14) tiles grid that encloses the given bounding box will be downloaded. * * @param bbox the minimum area to download - * @param maxQuestTypesToDownload download at most the given number of quest types. null for - * unlimited * @param isPriority whether this shall be a priority download (cancels previous downloads and * puts itself in the front) */ - fun download(bbox: BoundingBox, maxQuestTypesToDownload: Int? = null, isPriority: Boolean = false) { + fun download(bbox: BoundingBox, isPriority: Boolean = false) { val tilesRect = bbox.enclosingTilesRect(ApplicationConstants.QUEST_TILE_ZOOM) - context.startService(QuestDownloadService.createIntent(context, tilesRect, maxQuestTypesToDownload, isPriority)) + context.startService(QuestDownloadService.createIntent(context, tilesRect, isPriority)) } private fun bindServices() { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt index 416eb3c906..456d4f869f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt @@ -94,16 +94,13 @@ class QuestDownloadService : SingleIntentService(TAG) { } val tiles = intent.getSerializableExtra(ARG_TILES_RECT) as TilesRect - val maxQuestTypes = - if (intent.hasExtra(ARG_MAX_QUEST_TYPES)) intent.getIntExtra(ARG_MAX_QUEST_TYPES, 0) - else null val dl = questDownloaderProvider.get() dl.progressListener = progressListenerRelay try { isPriorityDownload = intent.hasExtra(ARG_IS_PRIORITY) isDownloading = true - dl.download(tiles, maxQuestTypes, cancelState) + dl.download(tiles, cancelState) } catch (e: Exception) { Log.e(TAG, "Unable to download quests", e) progressListenerRelay.onError(e) @@ -132,15 +129,13 @@ class QuestDownloadService : SingleIntentService(TAG) { companion object { private const val TAG = "QuestDownload" const val ARG_TILES_RECT = "tilesRect" - const val ARG_MAX_QUEST_TYPES = "maxQuestTypes" const val ARG_IS_PRIORITY = "isPriority" const val ARG_CANCEL = "cancel" - fun createIntent(context: Context, tilesRect: TilesRect?, maxQuestTypesToDownload: Int?, isPriority: Boolean): Intent { + fun createIntent(context: Context, tilesRect: TilesRect?,isPriority: Boolean): Intent { val intent = Intent(context, QuestDownloadService::class.java) intent.putExtra(ARG_TILES_RECT, tilesRect) intent.putExtra(ARG_IS_PRIORITY, isPriority) - maxQuestTypesToDownload?.let { intent.putExtra(ARG_MAX_QUEST_TYPES, it) } return intent } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index cfe7081480..b02d066948 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -30,11 +30,11 @@ class QuestDownloader @Inject constructor( ) { var progressListener: DownloadProgressListener? = null - @Synchronized fun download(tiles: TilesRect, maxQuestTypes: Int?, cancelState: AtomicBoolean) { + @Synchronized fun download(tiles: TilesRect, cancelState: AtomicBoolean) { if (cancelState.get()) return progressListener?.onStarted() - val questTypes = getQuestTypesToDownload(tiles, maxQuestTypes).toMutableList() + val questTypes = getQuestTypesToDownload(tiles).toMutableList() if (questTypes.isEmpty()) { progressListener?.onSuccess() progressListener?.onFinished() @@ -80,7 +80,7 @@ class QuestDownloader @Inject constructor( questTypeRegistry.getByName(OsmNoteQuestType::class.java.simpleName)!! - private fun getQuestTypesToDownload(tiles: TilesRect, maxQuestTypes: Int?): List> { + private fun getQuestTypesToDownload(tiles: TilesRect): List> { val result = questTypesProvider.get().toMutableList() val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER val ignoreOlderThan = max(0, System.currentTimeMillis() - questExpirationTime) @@ -90,10 +90,7 @@ class QuestDownloader @Inject constructor( result.removeAll(alreadyDownloaded) Log.i(TAG, "Quest types already in local store: ${alreadyDownloadedNames.joinToString()}") } - return if (maxQuestTypes != null && maxQuestTypes < result.size) - result.subList(0, maxQuestTypes) - else - result + return result } private fun downloadNotes(bbox: BoundingBox, tiles: TilesRect) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt index 909f00644a..c5010ecb97 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt @@ -17,12 +17,8 @@ class WifiAutoDownloadStrategy @Inject constructor( /** Let's assume that if the user is on wifi, he is either at home, at work, in the hotel, at a * café,... in any case, somewhere that would act as a "base" from which he can go on an * excursion. Let's make sure he can, even if there is no or bad internet. - * - * Since download size is almost unlimited, we can be very generous here. - * However, Overpass is as limited as always, so the number of quest types we download is - * limited as before */ + */ - override val questTypeDownloadCount = 5 override val minQuestsInActiveRadiusPerKm2 = 12 // checks if either in 600 or 200m radius, there are enough quests. diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt index 108df6d080..269bda11c5 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt @@ -142,10 +142,7 @@ import javax.inject.Singleton val downloadStrategy = if (isWifi) wifiDownloadStrategy else mobileDataDownloadStrategy if (downloadStrategy.mayDownloadHere(pos)) { try { - questDownloadController.download( - downloadStrategy.getDownloadBoundingBox(pos), - downloadStrategy.questTypeDownloadCount - ) + questDownloadController.download(downloadStrategy.getDownloadBoundingBox(pos)) } catch (e: IllegalStateException) { // The Android 9 bug described here should not result in a hard crash of the app // https://stackoverflow.com/questions/52013545/android-9-0-not-allowed-to-start-service-app-is-in-background-after-onresume diff --git a/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt index e5e46f79f0..d6cbeb6849 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt @@ -646,7 +646,7 @@ class MainFragment : Fragment(R.layout.fragment_main), ) } } - questDownloadController.download(bbox, ApplicationConstants.MANUAL_DOWNLOAD_QUEST_TYPE_COUNT, true) + questDownloadController.download(bbox, true) } // ---------------------------------- Location Pointer Pin --------------------------------- */ From 3ea7104baae2b22868583f91e59c712e61634a31 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Tue, 27 Oct 2020 21:33:17 +0100 Subject: [PATCH 48/63] put road name suggestions into DB in one transaction --- .../quests/address/AddAddressStreet.kt | 19 ++++++++----- .../quests/road_name/AddRoadName.kt | 19 ++++++++----- .../road_name/data/RoadNameSuggestionsDao.kt | 28 ++++++++++++------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index 15df4c7d30..eaacf0a1eb 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -7,10 +7,12 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionEntry import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao -import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion +import de.westnordost.streetcomplete.quests.road_name.data.toRoadNameByLanguage class AddAddressStreet( private val roadNameSuggestionsDao: RoadNameSuggestionsDao @@ -52,13 +54,16 @@ class AddAddressStreet( } if (addressesWithoutStreet.isNotEmpty()) { - val roadsWithNames = mapData.ways.filter { roadsWithNamesFilter.matches(it) } - for (roadWithName in roadsWithNames) { - val roadGeometry = mapData.getWayGeometry(roadWithName.id) - if (roadGeometry != null) { - roadNameSuggestionsDao.putRoadNameSuggestion(roadWithName, roadGeometry) + val roadsWithNames = mapData.ways + .filter { roadsWithNamesFilter.matches(it) } + .mapNotNull { + val geometry = mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry + val roadNamesByLanguage = it.tags?.toRoadNameByLanguage() + if (geometry != null && roadNamesByLanguage != null) { + RoadNameSuggestionEntry(it.id, roadNamesByLanguage, geometry.polylines.first()) + } else null } - } + roadNameSuggestionsDao.putRoads(roadsWithNames) } return addressesWithoutStreet } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index 38f3d43481..32683317de 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -7,10 +7,12 @@ import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType import de.westnordost.streetcomplete.quests.LocalizedName +import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionEntry import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao -import de.westnordost.streetcomplete.quests.road_name.data.putRoadNameSuggestion +import de.westnordost.streetcomplete.quests.road_name.data.toRoadNameByLanguage class AddRoadName( private val roadNameSuggestionsDao: RoadNameSuggestionsDao @@ -53,13 +55,16 @@ class AddRoadName( val roadsWithoutNames = mapData.ways.filter { filter.matches(it) } if (roadsWithoutNames.isNotEmpty()) { - val roadsWithNames = mapData.ways.filter { roadsWithNamesFilter.matches(it) } - for (roadWithName in roadsWithNames) { - val roadGeometry = mapData.getWayGeometry(roadWithName.id) - if (roadGeometry != null) { - roadNameSuggestionsDao.putRoadNameSuggestion(roadWithName, roadGeometry) + val roadsWithNames = mapData.ways + .filter { roadsWithNamesFilter.matches(it) } + .mapNotNull { + val geometry = mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry + val roadNamesByLanguage = it.tags?.toRoadNameByLanguage() + if (geometry != null && roadNamesByLanguage != null) { + RoadNameSuggestionEntry(it.id, roadNamesByLanguage, geometry.polylines.first()) + } else null } - } + roadNameSuggestionsDao.putRoads(roadsWithNames) } return roadsWithoutNames } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt index 86721b5648..3402198d57 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/data/RoadNameSuggestionsDao.kt @@ -7,6 +7,7 @@ import de.westnordost.osmapi.map.data.Element import javax.inject.Inject import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.osmapi.map.data.Way import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry @@ -22,6 +23,7 @@ import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.Column import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.NAME import de.westnordost.streetcomplete.util.Serializer import de.westnordost.streetcomplete.ktx.toObject +import de.westnordost.streetcomplete.ktx.transaction import de.westnordost.streetcomplete.quests.road_name.data.RoadNamesTable.Columns.LAST_UPDATE import de.westnordost.streetcomplete.util.distanceToArcs import de.westnordost.streetcomplete.util.enclosingBoundingBox @@ -51,6 +53,14 @@ class RoadNameSuggestionsDao @Inject constructor( db.replaceOrThrow(NAME, null, v) } + fun putRoads(roads: Iterable) { + db.transaction { + for (road in roads) { + putRoad(road.wayId, road.namesByLanguage, road.geometry) + } + } + } + /** returns something like [{"": "17th Street", "de": "17. Straße", "en": "17th Street", "international": "17 🛣 ️" }, ...] */ fun getNames(points: List, maxDistance: Double): List> { if (points.isEmpty()) return emptyList() @@ -106,17 +116,9 @@ class RoadNameSuggestionsDao @Inject constructor( } } -fun RoadNameSuggestionsDao.putRoadNameSuggestion(element: Element, geometry: ElementGeometry?) { - if (element.type != Element.Type.WAY) return - if (geometry !is ElementPolylinesGeometry) return - val namesByLanguage = element.tags?.toRoadNameByLanguage() ?: return - - putRoad(element.id, namesByLanguage, geometry.polylines.first()) -} - /** OSM tags (i.e. name:de=Bäckergang) to map of language code -> name (i.e. de=Bäckergang) * int_name becomes "international" */ -private fun Map.toRoadNameByLanguage(): Map? { +fun Map.toRoadNameByLanguage(): Map? { val result = mutableMapOf() val namePattern = Pattern.compile("name(:(.*))?") for ((key, value) in this) { @@ -129,4 +131,10 @@ private fun Map.toRoadNameByLanguage(): Map? { } } return if (result.isEmpty()) null else result -} \ No newline at end of file +} + +data class RoadNameSuggestionEntry( + val wayId: Long, + val namesByLanguage: Map, + val geometry: List +) \ No newline at end of file From 48ff1d2343b1b136647d64d12cf0a90da76cff50 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 00:53:05 +0100 Subject: [PATCH 49/63] decrease download areas --- .../westnordost/streetcomplete/ApplicationConstants.java | 6 +++--- .../streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt | 7 ++++++- .../data/download/MobileDataAutoDownloadStrategy.kt | 2 +- .../data/download/QuestDownloadController.kt | 2 +- .../data/download/WifiAutoDownloadStrategy.kt | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java index c8d74082f6..982f8b3322 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java +++ b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java @@ -8,14 +8,14 @@ public class ApplicationConstants QUESTTYPE_TAG_KEY = NAME + ":quest_type"; public final static double - MAX_DOWNLOADABLE_AREA_IN_SQKM = 20, - MIN_DOWNLOADABLE_AREA_IN_SQKM = 1; + MAX_DOWNLOADABLE_AREA_IN_SQKM = 10, + MIN_DOWNLOADABLE_AREA_IN_SQKM = 0.1; public final static double MIN_DOWNLOADABLE_RADIUS_IN_METERS = 600; public final static String DATABASE_NAME = "streetcomplete.db"; - public final static int QUEST_TILE_ZOOM = 14; + public final static int QUEST_TILE_ZOOM = 16; public final static int NOTE_MIN_ZOOM = 15; diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt b/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt index 90fc9c6c0c..f405448b5c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/StreetCompleteSQLiteOpenHelper.kt @@ -210,9 +210,14 @@ import java.util.* """.trimIndent()) } + if (oldVersion < 18 && newVersion >= 18) { + // QUEST_TILE_ZOOM changed + db.execSQL("DELETE FROM ${DownloadedTilesTable.NAME}") + } + // for later changes to the DB // ... } } -private const val DB_VERSION = 17 +private const val DB_VERSION = 18 diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt index 1ad7edd504..da708b1274 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt @@ -14,5 +14,5 @@ class MobileDataAutoDownloadStrategy @Inject constructor( override val minQuestsInActiveRadiusPerKm2 = 12 override val activeRadii = intArrayOf(200) - override val downloadRadius = 600 + override val downloadRadius = 200 } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt index 48ad4c5d56..637a231760 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadController.kt @@ -51,7 +51,7 @@ import javax.inject.Singleton } /** Download quests in at least the given bounding box asynchronously. The next-bigger rectangle - * in a (z14) tiles grid that encloses the given bounding box will be downloaded. + * in a (z16) tiles grid that encloses the given bounding box will be downloaded. * * @param bbox the minimum area to download * @param isPriority whether this shall be a priority download (cancels previous downloads and diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt index c5010ecb97..17aaae5c8b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt @@ -23,5 +23,5 @@ class WifiAutoDownloadStrategy @Inject constructor( // checks if either in 600 or 200m radius, there are enough quests. override val activeRadii = intArrayOf(600, 200) - override val downloadRadius = 1200 + override val downloadRadius = 600 } From 54b1c9d9b72511c3a1a49c46cf6460896fadaecf Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 15:42:03 +0100 Subject: [PATCH 50/63] auto sync option now only controls the upload --- .../streetcomplete/data/quest/QuestAutoSyncer.kt | 1 - .../streetcomplete/settings/SettingsFragment.kt | 4 ---- app/src/main/res/layout/dialog_tutorial_upload.xml | 11 ----------- app/src/main/res/values/strings.xml | 1 - 4 files changed, 17 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt index 269bda11c5..f2c8868b2b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt @@ -131,7 +131,6 @@ import javax.inject.Singleton /* ------------------------------------------------------------------------------------------ */ fun triggerAutoDownload() { - if (!isAllowedByPreference) return val pos = pos ?: return if (!isConnected) return if (questDownloadController.isDownloadInProgress) return diff --git a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt index e3584ef7b4..15ab688ec8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/settings/SettingsFragment.kt @@ -4,7 +4,6 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Bundle import android.view.LayoutInflater -import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate @@ -113,9 +112,6 @@ class SettingsFragment : PreferenceFragmentCompat(), Prefs.AUTOSYNC -> { if (Prefs.Autosync.valueOf(prefs.getString(Prefs.AUTOSYNC, "ON")!!) != Prefs.Autosync.ON) { val view = LayoutInflater.from(activity).inflate(R.layout.dialog_tutorial_upload, null) - val filled = requireContext().getString(R.string.action_download) - val uploadExplanation = view.findViewById(R.id.tutorialDownloadPanel) - uploadExplanation.text = requireContext().getString(R.string.dialog_tutorial_download, filled) AlertDialog.Builder(requireContext()) .setView(view) .setPositiveButton(android.R.string.ok, null) diff --git a/app/src/main/res/layout/dialog_tutorial_upload.xml b/app/src/main/res/layout/dialog_tutorial_upload.xml index 73377538ac..b95447aee8 100644 --- a/app/src/main/res/layout/dialog_tutorial_upload.xml +++ b/app/src/main/res/layout/dialog_tutorial_upload.xml @@ -17,15 +17,4 @@ app:drawableTint="@color/text" android:text="@string/dialog_tutorial_upload" android:textAppearance="@android:style/TextAppearance.Theme.Dialog"/> - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63e18fcc09..7e87e52b65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -927,7 +927,6 @@ Otherwise, you can download another keyboard in the app store. Popular keyboards house number %s: Is there a summit register at %s? Is this defibrillator (AED) inside a building? - With auto-sync off, quests are not downloaded automatically. To do this manually, use the \"%s\" button in the menu at the location you want to download. "Does this pedestrian crossing have an island?" From 1b14f9a90604699fc8add0419b826f9fce3002ef Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 18:24:52 +0100 Subject: [PATCH 51/63] rework auto sync download strategies --- .../data/download/AActiveRadiusStrategy.kt | 73 ----------------- .../data/download/AVariableRadiusStrategy.kt | 79 +++++++++++++++++++ .../MobileDataAutoDownloadStrategy.kt | 7 +- .../download/QuestAutoDownloadStrategy.kt | 8 +- .../data/download/WifiAutoDownloadStrategy.kt | 9 +-- .../data/osm/osmquest/OsmQuestController.kt | 8 +- .../data/quest/QuestAutoSyncer.kt | 15 +--- .../data/quest/VisibleQuestsSource.kt | 7 +- .../streetcomplete/util/SlippyMapMath.kt | 2 + 9 files changed, 99 insertions(+), 109 deletions(-) delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/AActiveRadiusStrategy.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/AActiveRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/AActiveRadiusStrategy.kt deleted file mode 100644 index 298e77cc30..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/AActiveRadiusStrategy.kt +++ /dev/null @@ -1,73 +0,0 @@ -package de.westnordost.streetcomplete.data.download - -import android.util.Log - -import de.westnordost.streetcomplete.ApplicationConstants -import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao -import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider -import de.westnordost.osmapi.map.data.BoundingBox -import de.westnordost.osmapi.map.data.LatLon -import de.westnordost.streetcomplete.data.quest.VisibleQuestsSource -import de.westnordost.streetcomplete.util.area -import de.westnordost.streetcomplete.util.enclosingBoundingBox -import de.westnordost.streetcomplete.util.enclosingTilesRect -import kotlin.math.max - -/** Quest auto download strategy that observes that a minimum amount of quests in a predefined - * radius around the user is not undercut */ -abstract class AActiveRadiusStrategy( - private val visibleQuestsSource: VisibleQuestsSource, - private val downloadedTilesDao: DownloadedTilesDao, - private val questTypesProvider: OrderedVisibleQuestTypesProvider -) : QuestAutoDownloadStrategy { - - protected abstract val minQuestsInActiveRadiusPerKm2: Int - protected abstract val activeRadii: IntArray - protected abstract val downloadRadius: Int - - private fun mayDownloadHere(pos: LatLon, radius: Int, questTypeNames: List): Boolean { - val bbox = pos.enclosingBoundingBox(radius.toDouble()) - - // nothing more to download - val tiles = bbox.enclosingTilesRect(ApplicationConstants.QUEST_TILE_ZOOM) - val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER - val ignoreOlderThan = max(0, System.currentTimeMillis() - questExpirationTime) - val alreadyDownloaded = downloadedTilesDao.get(tiles, ignoreOlderThan).toSet() - val notAlreadyDownloaded = mutableListOf() - for (questTypeName in questTypeNames) { - if (!alreadyDownloaded.contains(questTypeName)) notAlreadyDownloaded.add(questTypeName) - } - - if (notAlreadyDownloaded.isEmpty()) { - Log.i(TAG, "Not downloading quests because everything has been downloaded already in ${radius}m radius") - return false - } - - if (alreadyDownloaded.isNotEmpty()) { - val areaInKm2 = bbox.area() / 1000.0 / 1000.0 - // got enough quests in vicinity - val visibleQuests = visibleQuestsSource.getAllVisibleCount(bbox, alreadyDownloaded) - if (visibleQuests / areaInKm2 > minQuestsInActiveRadiusPerKm2) { - Log.i(TAG, "Not downloading quests because there are enough quests in ${radius}m radius") - return false - } - } - - return true - } - - override fun mayDownloadHere(pos: LatLon): Boolean { - val questTypeNames = questTypesProvider.get().map { it.javaClass.simpleName } - return activeRadii.any { radius -> - mayDownloadHere(pos, radius, questTypeNames) - } - } - - override fun getDownloadBoundingBox(pos: LatLon): BoundingBox { - return pos.enclosingBoundingBox(downloadRadius.toDouble()) - } - - companion object { - private const val TAG = "AutoQuestDownload" - } -} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt new file mode 100644 index 0000000000..31902ace50 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt @@ -0,0 +1,79 @@ +package de.westnordost.streetcomplete.data.download + +import android.util.Log + +import de.westnordost.streetcomplete.ApplicationConstants +import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao +import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider +import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.streetcomplete.data.quest.VisibleQuestsSource +import de.westnordost.streetcomplete.util.* +import kotlin.math.PI +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +/** Quest auto download strategy decides how big of an area to download based on the quest density */ +abstract class AVariableRadiusStrategy( + private val visibleQuestsSource: VisibleQuestsSource, + private val downloadedTilesDao: DownloadedTilesDao, + private val questTypesProvider: OrderedVisibleQuestTypesProvider +) : QuestAutoDownloadStrategy { + + protected abstract val maxDownloadAreaInKm2: Double + protected abstract val desiredQuestCountInVicinity: Int + + override fun getDownloadBoundingBox(pos: LatLon): BoundingBox? { + val tileZoom = ApplicationConstants.QUEST_TILE_ZOOM + + val thisTile = pos.enclosingTile(tileZoom) + val hasMissingQuestsForThisTile = hasMissingQuestsFor(thisTile.toTilesRect()) + + // if at the location where we are, there is nothing yet, first download the tiniest + // possible bbox (~ 360x360m) so that we can estimate the quest density + if (hasMissingQuestsForThisTile) { + Log.i(TAG, "Downloading tiny area around user") + return thisTile.asBoundingBox(tileZoom) + } + + // otherwise, see if anything is missing in a variable radius, based on quest density + val density = getQuestDensityFor(thisTile.asBoundingBox(tileZoom)) + val radius = min( + sqrt( desiredQuestCountInVicinity / ( PI * density )), + sqrt( maxDownloadAreaInKm2 * 1000 * 1000 / PI ) + ) + + val activeBoundingBox = pos.enclosingBoundingBox(radius) + if (hasMissingQuestsFor(activeBoundingBox.enclosingTilesRect(tileZoom))) { + Log.i(TAG, "Downloading in ${radius}m radius of user") + return activeBoundingBox + } + Log.i(TAG, "All downloaded in ${radius}m of user") + return null + } + + /** return the quest density in quests per m² for this given [boundingBox]*/ + private fun getQuestDensityFor(boundingBox: BoundingBox): Double { + val areaInKm = boundingBox.area() + val visibleQuestCount = visibleQuestsSource.getAllVisibleCount(boundingBox) + return visibleQuestCount / areaInKm + } + + /** return if there are any quests in the given tiles rect that haven't been downloaded yet */ + private fun hasMissingQuestsFor(tilesRect: TilesRect): Boolean { + val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER + val ignoreOlderThan = max(0, System.currentTimeMillis() - questExpirationTime) + val questTypeNames = questTypesProvider.get().map { it.javaClass.simpleName } + val alreadyDownloaded = downloadedTilesDao.get(tilesRect, ignoreOlderThan).toSet() + val notAlreadyDownloaded = mutableListOf() + for (questTypeName in questTypeNames) { + if (!alreadyDownloaded.contains(questTypeName)) notAlreadyDownloaded.add(questTypeName) + } + return notAlreadyDownloaded.isNotEmpty() + } + + companion object { + private const val TAG = "AutoQuestDownload" + } +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt index da708b1274..e6b15bd79d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt @@ -10,9 +10,8 @@ class MobileDataAutoDownloadStrategy @Inject constructor( visibleQuestsSource: VisibleQuestsSource, downloadedTilesDao: DownloadedTilesDao, questTypesProvider: OrderedVisibleQuestTypesProvider -) : AActiveRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypesProvider) { +) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypesProvider) { - override val minQuestsInActiveRadiusPerKm2 = 12 - override val activeRadii = intArrayOf(200) - override val downloadRadius = 200 + override val maxDownloadAreaInKm2 = 6.0 // that's a radius of about 1.4 km + override val desiredQuestCountInVicinity = 500 } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt index 04d4db2a60..32bdba8f03 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestAutoDownloadStrategy.kt @@ -4,9 +4,7 @@ import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.LatLon interface QuestAutoDownloadStrategy { - /** returns true if quests should be downloaded automatically at this position now */ - fun mayDownloadHere(pos: LatLon): Boolean - - /** returns the bbox that should be downloaded at this position (if mayDownloadHere returned true) */ - fun getDownloadBoundingBox(pos: LatLon): BoundingBox + /** returns the bbox that should be downloaded at this position or null if nothing should be + * downloaded now */ + fun getDownloadBoundingBox(pos: LatLon): BoundingBox? } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt index 17aaae5c8b..c04243c1a2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt @@ -12,16 +12,13 @@ class WifiAutoDownloadStrategy @Inject constructor( visibleQuestsSource: VisibleQuestsSource, downloadedTilesDao: DownloadedTilesDao, questTypes: OrderedVisibleQuestTypesProvider -) : AActiveRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypes) { +) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypes) { /** Let's assume that if the user is on wifi, he is either at home, at work, in the hotel, at a * café,... in any case, somewhere that would act as a "base" from which he can go on an * excursion. Let's make sure he can, even if there is no or bad internet. */ - override val minQuestsInActiveRadiusPerKm2 = 12 - - // checks if either in 600 or 200m radius, there are enough quests. - override val activeRadii = intArrayOf(600, 200) - override val downloadRadius = 600 + override val maxDownloadAreaInKm2 = 12.0 // that's a radius of about 2 km + override val desiredQuestCountInVicinity = 1000 } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt index 49135a5caa..9f087767f4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestController.kt @@ -237,13 +237,11 @@ import javax.inject.Singleton } - /** Get count of all unanswered quests in given bounding box of given types */ - fun getAllVisibleInBBoxCount(bbox: BoundingBox, questTypes: Collection) : Int { - if (questTypes.isEmpty()) return 0 + /** Get count of all unanswered quests in given bounding box */ + fun getAllVisibleInBBoxCount(bbox: BoundingBox) : Int { return dao.getCount( statusIn = listOf(QuestStatus.NEW), - bounds = bbox, - questTypes = questTypes + bounds = bbox ) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt index f2c8868b2b..e549b1e873 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/QuestAutoSyncer.kt @@ -33,7 +33,6 @@ import javax.inject.Singleton private val mobileDataDownloadStrategy: MobileDataAutoDownloadStrategy, private val wifiDownloadStrategy: WifiAutoDownloadStrategy, private val context: Context, - private val visibleQuestsSource: VisibleQuestsSource, private val unsyncedChangesCountSource: UnsyncedChangesCountSource, private val downloadProgressSource: DownloadProgressSource, private val prefs: SharedPreferences, @@ -45,13 +44,6 @@ import javax.inject.Singleton private var isConnected: Boolean = false private var isWifi: Boolean = false - // amount of visible quests is reduced -> check if re-downloading makes sense now - private val visibleQuestListener = object : VisibleQuestListener { - override fun onUpdatedVisibleQuests(added: Collection, removed: Collection, group: QuestGroup) { - if (removed.isNotEmpty()) { triggerAutoDownload() } - } - } - // new location is known -> check if downloading makes sense now private val locationManager = FineLocationManager(context.getSystemService()!!) { location -> pos = OsmLatLon(location.latitude, location.longitude) @@ -93,7 +85,6 @@ import javax.inject.Singleton /* ---------------------------------------- Lifecycle --------------------------------------- */ init { - visibleQuestsSource.addListener(visibleQuestListener) unsyncedChangesCountSource.addListener(unsyncedChangesListener) downloadProgressSource.addDownloadProgressListener(downloadProgressListener) } @@ -113,7 +104,6 @@ import javax.inject.Singleton } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { - visibleQuestsSource.removeListener(visibleQuestListener) unsyncedChangesCountSource.removeListener(unsyncedChangesListener) downloadProgressSource.removeDownloadProgressListener(downloadProgressListener) coroutineContext.cancel() @@ -139,9 +129,10 @@ import javax.inject.Singleton launch { val downloadStrategy = if (isWifi) wifiDownloadStrategy else mobileDataDownloadStrategy - if (downloadStrategy.mayDownloadHere(pos)) { + val downloadBoundingBox = downloadStrategy.getDownloadBoundingBox(pos) + if (downloadBoundingBox != null) { try { - questDownloadController.download(downloadStrategy.getDownloadBoundingBox(pos)) + questDownloadController.download(downloadBoundingBox) } catch (e: IllegalStateException) { // The Android 9 bug described here should not result in a hard crash of the app // https://stackoverflow.com/questions/52013545/android-9-0-not-allowed-to-start-service-app-is-in-background-after-onresume diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt b/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt index 585eb5247f..aac7b5ffe6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSource.kt @@ -68,10 +68,9 @@ import javax.inject.Singleton } - /** Get count of all unanswered quests in given bounding box of given types */ - fun getAllVisibleCount(bbox: BoundingBox, questTypes: Collection): Int { - if (questTypes.isEmpty()) return 0 - return osmQuestController.getAllVisibleInBBoxCount(bbox, questTypes) + + /** Get count of all unanswered quests in given bounding box */ + fun getAllVisibleCount(bbox: BoundingBox): Int { + return osmQuestController.getAllVisibleInBBoxCount(bbox) + osmNoteQuestController.getAllVisibleInBBoxCount(bbox) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/util/SlippyMapMath.kt b/app/src/main/java/de/westnordost/streetcomplete/util/SlippyMapMath.kt index 9129654887..bb4b8d6e8f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/util/SlippyMapMath.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/util/SlippyMapMath.kt @@ -17,6 +17,8 @@ data class Tile(val x: Int, val y:Int) { tile2lon(x + 1, zoom) ) } + + fun toTilesRect() = TilesRect(x,y,x,y) } /** Returns the minimum rectangle of tiles that encloses all the tiles */ From 1e4c769c4c2ab61e854f6af42823c5cec3900616 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 18:26:24 +0100 Subject: [PATCH 52/63] rename auto-sync --- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/preferences.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e87e52b65..5302ede5ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -162,7 +162,7 @@ "Ice hockey" "Netball" "Rugby" - "Auto-sync" + "Upload answers automatically" "Zoom out" "Zoom in" "Menu" diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index abc7999f5d..16143d2f02 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -8,7 +8,7 @@ Date: Wed, 28 Oct 2020 19:43:52 +0100 Subject: [PATCH 53/63] reduce debug level --- .../streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 626aaa95a0..689d27f302 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -70,7 +70,7 @@ class OsmApiQuestDownloader @Inject constructor( launch(Dispatchers.Default) { val questTypeName = questType.getName() if (!countryBoundaries.intersects(bbox, questType.enabledInCountries)) { - Log.i(TAG, "$questTypeName: Skipped because it is disabled for this country") + Log.d(TAG, "$questTypeName: Skipped because it is disabled for this country") } else { var i = 0 val questTime = System.currentTimeMillis() @@ -84,7 +84,7 @@ class OsmApiQuestDownloader @Inject constructor( questElements.add(element) ++i } - Log.i(TAG, "$questTypeName: Found $i quests in ${System.currentTimeMillis() - questTime}ms") + Log.d(TAG, "$questTypeName: Found $i quests in ${System.currentTimeMillis() - questTime}ms") } } } From d9bf5980bcc3eb589b755438aabb3ec3df298c38 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 20:40:39 +0100 Subject: [PATCH 54/63] reduce DownloadTilesDao functionality/complexity now does not store per quest type, but per download type. There is currently only one download type, "quests" --- .../download/tiles/DownloadedTilesDaoTest.kt | 6 -- .../de/westnordost/streetcomplete/Prefs.java | 1 + .../StreetCompleteApplication.java | 17 +++++ .../data/download/AVariableRadiusStrategy.kt | 13 +--- .../MobileDataAutoDownloadStrategy.kt | 5 +- .../data/download/QuestDownloadService.kt | 14 ++-- .../data/download/QuestDownloader.kt | 70 +++++-------------- .../data/download/WifiAutoDownloadStrategy.kt | 5 +- .../data/download/tiles/DownloadedTilesDao.kt | 35 +++------- .../download/tiles/DownloadedTilesTable.kt | 6 +- .../download/tiles/DownloadedTilesType.kt | 5 ++ .../data/quest/VisibleQuestsSourceTest.kt | 4 +- 12 files changed, 70 insertions(+), 111 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesType.kt diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt index 07e78b4c31..3b62199961 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDaoTest.kt @@ -78,11 +78,5 @@ class DownloadedTilesDaoTest : ApplicationDbTestCase() { assertTrue(check.isEmpty()) } - @Test fun putMultipleQuestTypes() { - val rect = r(0, 0, 5, 5) - dao.putAll(rect, listOf("Huhu", "Haha")) - assertTrue(dao.get(rect, 0).containsExactlyInAnyOrder(listOf("Huhu", "Haha"))) - } - private fun r(left: Int, top: Int, right: Int, bottom: Int) = TilesRect(left, top, right, bottom) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/Prefs.java b/app/src/main/java/de/westnordost/streetcomplete/Prefs.java index b5948dc310..2ba0189185 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/Prefs.java +++ b/app/src/main/java/de/westnordost/streetcomplete/Prefs.java @@ -37,6 +37,7 @@ public class Prefs LAST_PICKED_PREFIX = "imageListLastPicked.", LAST_LOCATION_REQUEST_DENIED = "location.denied", LAST_VERSION = "lastVersion", + LAST_VERSION_DATA = "lastVersion_data", HAS_SHOWN_TUTORIAL = "hasShownTutorial"; public static final String HAS_SHOWN_UNDO_FUCKUP_WARNING = "alert.undo_fuckup_warning"; diff --git a/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java b/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java index 74a41d23a5..3b3549443f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java +++ b/app/src/main/java/de/westnordost/streetcomplete/StreetCompleteApplication.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate; import de.westnordost.countryboundaries.CountryBoundaries; import de.westnordost.osmfeatures.FeatureDictionary; +import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao; import de.westnordost.streetcomplete.settings.ResurveyIntervalsUpdater; import de.westnordost.streetcomplete.util.CrashReportExceptionHandler; @@ -20,6 +21,7 @@ public class StreetCompleteApplication extends Application @Inject FutureTask featuresDictionaryFuture; @Inject CrashReportExceptionHandler crashReportExceptionHandler; @Inject ResurveyIntervalsUpdater resurveyIntervalsUpdater; + @Inject DownloadedTilesDao downloadedTilesDao; @Inject SharedPreferences prefs; private static final String PRELOAD_TAG = "Preload"; @@ -40,6 +42,16 @@ public void onCreate() AppCompatDelegate.setDefaultNightMode(theme.appCompatNightMode); resurveyIntervalsUpdater.update(); + + String lastVersion = prefs.getString(Prefs.LAST_VERSION_DATA, null); + if (!BuildConfig.VERSION_NAME.equals(lastVersion)) + { + prefs.edit().putString(Prefs.LAST_VERSION_DATA, BuildConfig.VERSION_NAME).apply(); + if (lastVersion != null) + { + onNewVersion(); + } + } } /** Load some things in the background that are needed later */ @@ -60,4 +72,9 @@ private void preload() Log.i(PRELOAD_TAG, "Loaded features dictionary"); }).start(); } + + private void onNewVersion() { + // on each new version, invalidate quest cache + downloadedTilesDao.removeAll(); + } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt index 31902ace50..2c0962628d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt @@ -4,9 +4,9 @@ import android.util.Log import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao -import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypesProvider import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.osmapi.map.data.LatLon +import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesType import de.westnordost.streetcomplete.data.quest.VisibleQuestsSource import de.westnordost.streetcomplete.util.* import kotlin.math.PI @@ -17,8 +17,7 @@ import kotlin.math.sqrt /** Quest auto download strategy decides how big of an area to download based on the quest density */ abstract class AVariableRadiusStrategy( private val visibleQuestsSource: VisibleQuestsSource, - private val downloadedTilesDao: DownloadedTilesDao, - private val questTypesProvider: OrderedVisibleQuestTypesProvider + private val downloadedTilesDao: DownloadedTilesDao ) : QuestAutoDownloadStrategy { protected abstract val maxDownloadAreaInKm2: Double @@ -64,13 +63,7 @@ abstract class AVariableRadiusStrategy( private fun hasMissingQuestsFor(tilesRect: TilesRect): Boolean { val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER val ignoreOlderThan = max(0, System.currentTimeMillis() - questExpirationTime) - val questTypeNames = questTypesProvider.get().map { it.javaClass.simpleName } - val alreadyDownloaded = downloadedTilesDao.get(tilesRect, ignoreOlderThan).toSet() - val notAlreadyDownloaded = mutableListOf() - for (questTypeName in questTypeNames) { - if (!alreadyDownloaded.contains(questTypeName)) notAlreadyDownloaded.add(questTypeName) - } - return notAlreadyDownloaded.isNotEmpty() + return !downloadedTilesDao.get(tilesRect, ignoreOlderThan).contains(DownloadedTilesType.QUESTS) } companion object { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt index e6b15bd79d..74ad616c4d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/MobileDataAutoDownloadStrategy.kt @@ -8,9 +8,8 @@ import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypes /** Download strategy if user is on mobile data */ class MobileDataAutoDownloadStrategy @Inject constructor( visibleQuestsSource: VisibleQuestsSource, - downloadedTilesDao: DownloadedTilesDao, - questTypesProvider: OrderedVisibleQuestTypesProvider -) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypesProvider) { + downloadedTilesDao: DownloadedTilesDao +) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao) { override val maxDownloadAreaInKm2 = 6.0 // that's a radius of about 1.4 km override val desiredQuestCountInVicinity = 500 diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt index 456d4f869f..42f837cccc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloadService.kt @@ -17,9 +17,7 @@ import javax.inject.Provider * * Generally, starting a new download cancels the old one. This is a feature; Consideration: * If the user requests a new area to be downloaded, he'll generally be more interested in his last - * request than any request he made earlier and he wants that as fast as possible. (Downloading - * in-parallel is not possible with Overpass, only one request a time is allowed on the public - * instance) + * request than any request he made earlier and he wants that as fast as possible. * * The service can be bound to snoop into the state of the downloading process: * * To receive progress callbacks @@ -39,8 +37,14 @@ class QuestDownloadService : SingleIntentService(TAG) { private var progressListenerRelay = object : DownloadProgressListener { override fun onStarted() { progressListener?.onStarted() } override fun onError(e: Exception) { progressListener?.onError(e) } - override fun onSuccess() { progressListener?.onSuccess() } - override fun onFinished() { progressListener?.onFinished() } + override fun onSuccess() { + isDownloading = false + progressListener?.onSuccess() + } + override fun onFinished() { + isDownloading = false + progressListener?.onFinished() + } override fun onStarted(item: DownloadItem) { currentDownloadItem = item progressListener?.onStarted(item) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index b02d066948..cfbbb5789d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -4,11 +4,9 @@ import android.util.Log import de.westnordost.osmapi.map.data.BoundingBox import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.quest.QuestType -import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry -import de.westnordost.streetcomplete.data.osmnotes.notequests.OsmNoteQuestType import de.westnordost.streetcomplete.data.osmnotes.OsmNotesDownloader import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesDao +import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesType import de.westnordost.streetcomplete.data.osm.osmquest.* import de.westnordost.streetcomplete.data.osm.osmquest.OsmApiQuestDownloader import de.westnordost.streetcomplete.data.user.UserStore @@ -24,7 +22,6 @@ class QuestDownloader @Inject constructor( private val osmNotesDownloaderProvider: Provider, private val osmApiQuestDownloaderProvider: Provider, private val downloadedTilesDao: DownloadedTilesDao, - private val questTypeRegistry: QuestTypeRegistry, private val questTypesProvider: OrderedVisibleQuestTypesProvider, private val userStore: UserStore ) { @@ -34,8 +31,7 @@ class QuestDownloader @Inject constructor( if (cancelState.get()) return progressListener?.onStarted() - val questTypes = getQuestTypesToDownload(tiles).toMutableList() - if (questTypes.isEmpty()) { + if (hasQuestsAlready(tiles)) { progressListener?.onSuccess() progressListener?.onFinished() return @@ -44,10 +40,9 @@ class QuestDownloader @Inject constructor( val bbox = tiles.asBoundingBox(ApplicationConstants.QUEST_TILE_ZOOM) Log.i(TAG, "(${bbox.asLeftBottomRightTopString}) Starting") - Log.i(TAG, "Quest types to download: ${questTypes.joinToString { it.javaClass.simpleName }}") try { - downloadQuestTypes(tiles, bbox, questTypes, cancelState) + downloadQuestTypes(tiles, bbox, cancelState) progressListener?.onSuccess() } finally { progressListener?.onFinished() @@ -55,72 +50,41 @@ class QuestDownloader @Inject constructor( } } - private fun downloadQuestTypes( - tiles: TilesRect, - bbox: BoundingBox, - questTypes: MutableList>, - cancelState: AtomicBoolean) - { - // always first download notes, because note positions are blockers for creating other - // osm quests - val noteQuestType = getOsmNoteQuestType() - if (questTypes.remove(noteQuestType)) { - downloadNotes(bbox, tiles) - } + private fun downloadQuestTypes(tiles: TilesRect, bbox: BoundingBox, cancelState: AtomicBoolean) { + val downloadItem = DownloadItem(R.drawable.ic_search_black_128dp, "Multi download") + progressListener?.onStarted(downloadItem) - if (questTypes.isEmpty()) return + // always first download notes, note positions are blockers for creating other quests + downloadNotes(bbox) if (cancelState.get()) return + downloadOsmMapDataQuestTypes(bbox) + downloadedTilesDao.put(tiles, DownloadedTilesType.QUESTS) - // download multiple quests at once - val downloadedQuestTypes = downloadOsmMapDataQuestTypes(bbox, tiles) - questTypes.removeAll(downloadedQuestTypes) + progressListener?.onFinished(downloadItem) } - private fun getOsmNoteQuestType() = - questTypeRegistry.getByName(OsmNoteQuestType::class.java.simpleName)!! - - - private fun getQuestTypesToDownload(tiles: TilesRect): List> { - val result = questTypesProvider.get().toMutableList() + private fun hasQuestsAlready(tiles: TilesRect): Boolean { val questExpirationTime = ApplicationConstants.REFRESH_QUESTS_AFTER val ignoreOlderThan = max(0, System.currentTimeMillis() - questExpirationTime) - val alreadyDownloadedNames = downloadedTilesDao.get(tiles, ignoreOlderThan) - if (alreadyDownloadedNames.isNotEmpty()) { - val alreadyDownloaded = alreadyDownloadedNames.map { questTypeRegistry.getByName(it) } - result.removeAll(alreadyDownloaded) - Log.i(TAG, "Quest types already in local store: ${alreadyDownloadedNames.joinToString()}") - } - return result + return downloadedTilesDao.get(tiles, ignoreOlderThan).contains(DownloadedTilesType.QUESTS) } - private fun downloadNotes(bbox: BoundingBox, tiles: TilesRect) { + private fun downloadNotes(bbox: BoundingBox) { val notesDownload = osmNotesDownloaderProvider.get() val userId: Long = userStore.userId.takeIf { it != -1L } ?: return // do not download notes if not logged in because notes shall only be downloaded if logged in - val noteQuestType = getOsmNoteQuestType() - progressListener?.onStarted(noteQuestType.toDownloadItem()) + val maxNotes = 10000 notesDownload.download(bbox, userId, maxNotes) - downloadedTilesDao.put(tiles, OsmNoteQuestType::class.java.simpleName) - progressListener?.onFinished(noteQuestType.toDownloadItem()) } - private fun downloadOsmMapDataQuestTypes(bbox: BoundingBox, tiles: TilesRect): List> { - val downloadItem = DownloadItem(R.drawable.ic_search_black_128dp, "Multi download") - progressListener?.onStarted(downloadItem) - // Since we query all the data at once, we can also do the downloading for quests not on our list. + private fun downloadOsmMapDataQuestTypes(bbox: BoundingBox) { val questTypes = questTypesProvider.get().filterIsInstance>() - val questDownload = osmApiQuestDownloaderProvider.get() - questDownload.download(questTypes, bbox) - downloadedTilesDao.putAll(tiles, questTypes.map { it.javaClass.simpleName }) - progressListener?.onFinished(downloadItem) - return questTypes + osmApiQuestDownloaderProvider.get().download(questTypes, bbox) } companion object { private const val TAG = "QuestDownload" } } - -private fun QuestType<*>.toDownloadItem(): DownloadItem = DownloadItem(icon, javaClass.simpleName) \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt index c04243c1a2..345b5f43e8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/WifiAutoDownloadStrategy.kt @@ -10,9 +10,8 @@ import de.westnordost.streetcomplete.data.visiblequests.OrderedVisibleQuestTypes /** Download strategy if user is on wifi */ class WifiAutoDownloadStrategy @Inject constructor( visibleQuestsSource: VisibleQuestsSource, - downloadedTilesDao: DownloadedTilesDao, - questTypes: OrderedVisibleQuestTypesProvider -) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao, questTypes) { + downloadedTilesDao: DownloadedTilesDao +) : AVariableRadiusStrategy(visibleQuestsSource, downloadedTilesDao) { /** Let's assume that if the user is on wifi, he is either at home, at work, in the hotel, at a * café,... in any case, somewhere that would act as a "base" from which he can go on an diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt index d7e35533c7..c4ca47a07b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt @@ -4,52 +4,35 @@ import android.database.sqlite.SQLiteOpenHelper import androidx.core.content.contentValuesOf import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.X import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.Y -import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.QUEST_TYPE +import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.TYPE import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.Columns.DATE import de.westnordost.streetcomplete.data.download.tiles.DownloadedTilesTable.NAME import de.westnordost.streetcomplete.ktx.query -import de.westnordost.streetcomplete.ktx.transaction import de.westnordost.streetcomplete.util.Tile import de.westnordost.streetcomplete.util.TilesRect import javax.inject.Inject -/** Keeps info in which areas quests have been downloaded already in a tile grid of zoom level 14 - * (~0.022° per tile -> a few kilometers sidelength) */ +/** Keeps info in which areas things have been downloaded already in a tile grid of zoom level 14 */ class DownloadedTilesDao @Inject constructor(private val dbHelper: SQLiteOpenHelper) { private val db get() = dbHelper.writableDatabase - /** Persist that the given quest types have been downloaded in every tile in the given tile range */ - fun putAll(tilesRect: TilesRect, questTypeNames: List) { - db.transaction { - for (questTypeName in questTypeNames) { - putQuestType(tilesRect, questTypeName) - } - } - } - - /** Persist that the given quest type has been downloaded in every tile in the given tile range */ - fun put(tilesRect: TilesRect, questTypeName: String) { - db.transaction { - putQuestType(tilesRect, questTypeName) - } - } - - private fun putQuestType(tilesRect: TilesRect, questTypeName: String) { + /** Persist that the given type has been downloaded in every tile in the given tile range */ + fun put(tilesRect: TilesRect, typeName: String) { val time = System.currentTimeMillis() for (tile in tilesRect.asTileSequence()) { val values = contentValuesOf( X to tile.x, Y to tile.y, - QUEST_TYPE to questTypeName, + TYPE to typeName, DATE to time ) db.replaceOrThrow(NAME, null, values) } } - /** Invalidate all quest types within the given tile. (consider them as not-downloaded) */ + /** Invalidate all types within the given tile. (consider them as not-downloaded) */ fun remove(tile: Tile): Int { return db.delete(NAME, "$X = ? AND $Y = ?", arrayOf(tile.x.toString(), tile.y.toString())) } @@ -58,13 +41,13 @@ class DownloadedTilesDao @Inject constructor(private val dbHelper: SQLiteOpenHel db.execSQL("DELETE FROM $NAME") } - /** @return a list of quest type names which have already been downloaded in every tile in the + /** @return a list of type names which have already been downloaded in every tile in the * given tile range */ fun get(tilesRect: TilesRect, ignoreOlderThan: Long): List { val tileCount = tilesRect.size return db.query(NAME, - columns = arrayOf(QUEST_TYPE), + columns = arrayOf(TYPE), selection = "$X BETWEEN ? AND ? AND $Y BETWEEN ? AND ? AND $DATE > ?", selectionArgs = arrayOf( tilesRect.left.toString(), @@ -73,7 +56,7 @@ class DownloadedTilesDao @Inject constructor(private val dbHelper: SQLiteOpenHel tilesRect.bottom.toString(), ignoreOlderThan.toString() ), - groupBy = QUEST_TYPE, + groupBy = TYPE, having = "COUNT(*) >= $tileCount") { it.getString(0) } } } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesTable.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesTable.kt index ea53594b7e..ee215fb1e1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesTable.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesTable.kt @@ -6,7 +6,7 @@ object DownloadedTilesTable { object Columns { const val X = "x" const val Y = "y" - const val QUEST_TYPE = "quest_type" + const val TYPE = "quest_type" const val DATE = "date" } @@ -14,8 +14,8 @@ object DownloadedTilesTable { CREATE TABLE $NAME ( ${Columns.X} int NOT NULL, ${Columns.Y} int NOT NULL, - ${Columns.QUEST_TYPE} varchar(255) NOT NULL, + ${Columns.TYPE} varchar(255) NOT NULL, ${Columns.DATE} int NOT NULL, - CONSTRAINT primary_key PRIMARY KEY (${Columns.X}, ${Columns.Y}, ${Columns.QUEST_TYPE}) + CONSTRAINT primary_key PRIMARY KEY (${Columns.X}, ${Columns.Y}, ${Columns.TYPE}) );""" } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesType.kt new file mode 100644 index 0000000000..48ca14d91d --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesType.kt @@ -0,0 +1,5 @@ +package de.westnordost.streetcomplete.data.download.tiles + +object DownloadedTilesType { + const val QUESTS = "quests" +} \ No newline at end of file diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt index 4ef5b97650..eab0deae92 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/quest/VisibleQuestsSourceTest.kt @@ -54,10 +54,10 @@ class VisibleQuestsSourceTest { } @Test fun getAllVisibleCount() { - on(osmQuestController.getAllVisibleInBBoxCount(bbox, questTypes)).thenReturn(3) + on(osmQuestController.getAllVisibleInBBoxCount(bbox)).thenReturn(3) on(osmNoteQuestController.getAllVisibleInBBoxCount(bbox)).thenReturn(4) - assertEquals(7, source.getAllVisibleCount(bbox, questTypes)) + assertEquals(7, source.getAllVisibleCount(bbox)) } @Test fun getAllVisible() { From d0a93ed9d485ae268ee1a1d0d9137db00c804a2f Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 21:01:39 +0100 Subject: [PATCH 55/63] remove OsmMapDataQuestType again --- .../data/osm/osmquest/OsmQuestDaoTest.kt | 14 +++++++------- .../data/osm/osmquest/TestQuestType.kt | 4 ++-- .../data/download/QuestDownloader.kt | 10 ++++++---- .../data/osm/osmquest/OsmApiQuestDownloader.kt | 2 +- .../data/osm/osmquest/OsmElementQuestType.kt | 4 ++++ .../data/osm/osmquest/OsmFilterQuestType.kt | 3 +-- .../data/osm/osmquest/OsmMapDataQuestType.kt | 10 ---------- .../data/osm/osmquest/OsmQuest.kt | 18 +++++++++--------- .../quests/address/AddAddressStreet.kt | 4 ++-- .../quests/bikeway/AddCycleway.kt | 4 ++-- .../AddClothingBinOperator.kt | 4 ++-- .../crossing_island/AddCrossingIsland.kt | 4 ++-- .../quests/housenumber/AddHousenumber.kt | 4 ++-- .../quests/leaf_detail/AddForestLeafType.kt | 4 ++-- .../quests/max_height/AddMaxHeight.kt | 4 ++-- .../streetcomplete/quests/oneway/AddOneway.kt | 4 ++-- .../oneway_suspects/AddSuspectedOneway.kt | 4 ++-- .../quests/opening_hours/AddOpeningHours.kt | 4 ++-- .../quests/place_name/AddPlaceName.kt | 4 ++-- .../AddRailwayCrossingBarrier.kt | 4 ++-- .../AddRecyclingContainerMaterials.kt | 4 ++-- .../quests/road_name/AddRoadName.kt | 4 ++-- .../quests/sidewalk/AddSidewalk.kt | 4 ++-- .../summit_register/AddSummitRegister.kt | 4 ++-- .../AddTactilePavingCrosswalk.kt | 4 ++-- .../osm/osmquest/OsmApiQuestDownloaderTest.kt | 2 +- .../OpenQuestChangesetsManagerTest.kt | 2 ++ 27 files changed, 67 insertions(+), 70 deletions(-) delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDaoTest.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDaoTest.kt index 50539b26a2..88f92c73c2 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDaoTest.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuestDaoTest.kt @@ -204,13 +204,13 @@ private val TEST_QUEST_TYPE = TestQuestType() private val TEST_QUEST_TYPE2 = TestQuestType2() private fun create( - questType: OsmElementQuestType<*> = TEST_QUEST_TYPE, - elementType: Element.Type = Element.Type.NODE, - elementId: Long = 1, - status: QuestStatus = QuestStatus.NEW, - geometry: ElementGeometry = ElementPointGeometry(OsmLatLon(5.0, 5.0)), - changes: StringMapChanges? = null, - changesSource: String? = null + questType: OsmElementQuestType<*> = TEST_QUEST_TYPE, + elementType: Element.Type = Element.Type.NODE, + elementId: Long = 1, + status: QuestStatus = QuestStatus.NEW, + geometry: ElementGeometry = ElementPointGeometry(OsmLatLon(5.0, 5.0)), + changes: StringMapChanges? = null, + changesSource: String? = null ) = OsmQuest( null, questType, elementType, elementId, status, changes, changesSource, Date(), geometry ) diff --git a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt index e5cc07c500..b58a88b79d 100644 --- a/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt +++ b/app/src/androidTest/java/de/westnordost/streetcomplete/data/osm/osmquest/TestQuestType.kt @@ -1,9 +1,8 @@ package de.westnordost.streetcomplete.data.osm.osmquest -import de.westnordost.osmapi.map.data.BoundingBox +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.quests.AbstractQuestAnswerFragment open class TestQuestType : OsmElementQuestType { @@ -14,4 +13,5 @@ open class TestQuestType : OsmElementQuestType { override val icon = 0 override fun createForm(): AbstractQuestAnswerFragment = object : AbstractQuestAnswerFragment() {} override val commitMessage = "" + override fun getApplicableElements(mapData: MapDataWithGeometry) = emptyList() } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt index cfbbb5789d..d64d83dca3 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/QuestDownloader.kt @@ -56,8 +56,11 @@ class QuestDownloader @Inject constructor( // always first download notes, note positions are blockers for creating other quests downloadNotes(bbox) + if (cancelState.get()) return - downloadOsmMapDataQuestTypes(bbox) + + downloadOsmElementQuestTypes(bbox) + downloadedTilesDao.put(tiles, DownloadedTilesType.QUESTS) progressListener?.onFinished(downloadItem) @@ -78,9 +81,8 @@ class QuestDownloader @Inject constructor( notesDownload.download(bbox, userId, maxNotes) } - - private fun downloadOsmMapDataQuestTypes(bbox: BoundingBox) { - val questTypes = questTypesProvider.get().filterIsInstance>() + private fun downloadOsmElementQuestTypes(bbox: BoundingBox) { + val questTypes = questTypesProvider.get().filterIsInstance>() osmApiQuestDownloaderProvider.get().download(questTypes, bbox) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 689d27f302..4179057373 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -42,7 +42,7 @@ class OsmApiQuestDownloader @Inject constructor( private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker ) : CoroutineScope by CoroutineScope(Dispatchers.Default) { - fun download(questTypes: List>, bbox: BoundingBox) { + fun download(questTypes: List>, bbox: BoundingBox) { if (questTypes.isEmpty()) return var time = System.currentTimeMillis() diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt index 34d03eb891..58e5218c19 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmElementQuestType.kt @@ -1,5 +1,6 @@ package de.westnordost.streetcomplete.data.osm.osmquest +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.data.quest.QuestType import de.westnordost.streetcomplete.data.quest.AllCountries @@ -36,6 +37,9 @@ interface OsmElementQuestType : QuestType { override val title: Int get() = getTitle(emptyMap()) + /** return all elements within the given map data that are applicable to this quest type. */ + fun getApplicableElements(mapData: MapDataWithGeometry): Iterable + /** returns whether a quest of this quest type could be created out of the given [element]. If the * element alone does not suffice to find this out (but f.e. is determined by the data around * it), this should return null. diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt index 81b54cf2c1..cc577d637c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmFilterQuestType.kt @@ -6,7 +6,7 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.util.MultiIterable /** Quest type that's based on a simple element filter expression */ -abstract class OsmFilterQuestType : OsmMapDataQuestType { +abstract class OsmFilterQuestType : OsmElementQuestType { val filter by lazy { elementFilter.toElementFilterExpression() } @@ -21,7 +21,6 @@ abstract class OsmFilterQuestType : OsmMapDataQuestType { if (filter.includesElementType(Element.Type.WAY)) iterable.add(mapData.ways) if (filter.includesElementType(Element.Type.RELATION)) iterable.add(mapData.relations) return iterable.filter { element -> filter.matches(element) } - } override fun isApplicableTo(element: Element) = filter.matches(element) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt deleted file mode 100644 index edf5d008ff..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmMapDataQuestType.kt +++ /dev/null @@ -1,10 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.osmapi.map.MapDataWithGeometry -import de.westnordost.osmapi.map.data.Element - -/** Quest type based on OSM data whose quests can be created by looking at a MapData */ -interface OsmMapDataQuestType : OsmElementQuestType { - /** return all elements within the given map data that are applicable to this quest type. */ - fun getApplicableElements(mapData: MapDataWithGeometry): Iterable -} \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuest.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuest.kt index 5f4f68a190..2225efd40f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuest.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmQuest.kt @@ -18,15 +18,15 @@ import de.westnordost.streetcomplete.util.pointOnPolylineFromStart /** Represents one task for the user to complete/correct the data based on one OSM element */ data class OsmQuest( - override var id: Long?, - override val osmElementQuestType: OsmElementQuestType<*>, // underlying OSM data - override val elementType: Element.Type, - override val elementId: Long, - override var status: QuestStatus, - override var changes: StringMapChanges?, - var changesSource: String?, - override var lastUpdate: Date, - override val geometry: ElementGeometry + override var id: Long?, + override val osmElementQuestType: OsmElementQuestType<*>, // underlying OSM data + override val elementType: Element.Type, + override val elementId: Long, + override var status: QuestStatus, + override var changes: StringMapChanges?, + var changesSource: String?, + override var lastUpdate: Date, + override val geometry: ElementGeometry ) : Quest, UploadableInChangeset, HasElementTagChanges { constructor(type: OsmElementQuestType<*>, elementType: Element.Type, elementId: Long, geometry: ElementGeometry) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt index eaacf0a1eb..9f1bdd3844 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/address/AddAddressStreet.kt @@ -9,14 +9,14 @@ import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.quest.AllCountriesExcept -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionEntry import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao import de.westnordost.streetcomplete.quests.road_name.data.toRoadNameByLanguage class AddAddressStreet( private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmMapDataQuestType { +) : OsmElementQuestType { private val filter by lazy { """ nodes, ways, relations with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index 4e7cbad535..27f44a5a61 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -13,13 +13,13 @@ import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.bikeway.Cycleway.* import de.westnordost.streetcomplete.util.isNear -class AddCycleway : OsmMapDataQuestType { +class AddCycleway : OsmElementQuestType { override val commitMessage = "Add whether there are cycleways" override val wikiLink = "Key:cycleway" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index 306d6eea7d..93f1a2e931 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -5,9 +5,9 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -class AddClothingBinOperator : OsmMapDataQuestType { +class AddClothingBinOperator : OsmElementQuestType { /* not the complete filter, see below: we want to filter out additionally all elements that contain any recycling:* = yes that is not shoes or clothes but this can not be expressed diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt index a845c2eae2..2772886e3f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/crossing_island/AddCrossingIsland.kt @@ -5,11 +5,11 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment -class AddCrossingIsland : OsmMapDataQuestType { +class AddCrossingIsland : OsmElementQuestType { private val crossingFilter by lazy { """ nodes with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt index 5e34480a53..94bab0d8d8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/housenumber/AddHousenumber.kt @@ -8,12 +8,12 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.util.LatLonRaster import de.westnordost.streetcomplete.util.isCompletelyInside import de.westnordost.streetcomplete.util.isInMultipolygon -class AddHousenumber : OsmMapDataQuestType { +class AddHousenumber : OsmElementQuestType { override val commitMessage = "Add housenumbers" override val wikiLink = "Key:addr" diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt index 9ce8eef078..4e9a2bf258 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/leaf_detail/AddForestLeafType.kt @@ -6,10 +6,10 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolygonsGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.util.measuredMultiPolygonArea -class AddForestLeafType : OsmMapDataQuestType { +class AddForestLeafType : OsmElementQuestType { private val areaFilter by lazy { """ ways, relations with landuse = forest or natural = wood and !leaf_type """.toElementFilterExpression()} diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt index 7ae3a310f9..728062b744 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeight.kt @@ -5,9 +5,9 @@ import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -class AddMaxHeight : OsmMapDataQuestType { +class AddMaxHeight : OsmElementQuestType { private val nodeFilter by lazy { """ nodes with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index 538616fac1..5afb6c4a08 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -9,11 +9,11 @@ import de.westnordost.streetcomplete.data.meta.ALL_ROADS import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.quests.bikeway.createCyclewaySides import de.westnordost.streetcomplete.quests.bikeway.estimatedWidth -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.quests.oneway.OnewayAnswer.* import de.westnordost.streetcomplete.quests.parking_lanes.* -class AddOneway : OsmMapDataQuestType { +class AddOneway : OsmElementQuestType { /** find all roads */ private val allRoadsFilter by lazy { """ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index ae511d7a9d..bfff7d583b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -9,7 +9,7 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegment import de.westnordost.streetcomplete.quests.oneway_suspects.data.TrafficFlowSegmentsApi import de.westnordost.streetcomplete.quests.oneway_suspects.data.WayTrafficFlowDao @@ -18,7 +18,7 @@ import kotlin.math.hypot class AddSuspectedOneway( private val trafficFlowSegmentsApi: TrafficFlowSegmentsApi, private val db: WayTrafficFlowDao -) : OsmMapDataQuestType { +) : OsmElementQuestType { private val filter by lazy { """ ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt index 353fc14356..7a03198b0e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHours.kt @@ -7,7 +7,7 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.ktx.containsAny import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRows import de.westnordost.streetcomplete.quests.opening_hours.parser.toOpeningHoursRules @@ -15,7 +15,7 @@ import java.util.concurrent.FutureTask class AddOpeningHours ( private val featureDictionaryFuture: FutureTask -) : OsmMapDataQuestType { +) : OsmElementQuestType { /* See also AddWheelchairAccessBusiness and AddPlaceName, which has a similar list and is/should be ordered in the same way for better overview */ diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt index 96c386cd4f..5c2af71dbe 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/place_name/AddPlaceName.kt @@ -6,12 +6,12 @@ import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import java.util.concurrent.FutureTask class AddPlaceName( private val featureDictionaryFuture: FutureTask -) : OsmMapDataQuestType { +) : OsmElementQuestType { private val filter by lazy { (""" nodes, ways, relations with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt index c8c984e1c9..a1bfd84f9e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/railway_crossing/AddRailwayCrossingBarrier.kt @@ -6,9 +6,9 @@ import de.westnordost.streetcomplete.R import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.updateWithCheckDate -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType -class AddRailwayCrossingBarrier : OsmMapDataQuestType { +class AddRailwayCrossingBarrier : OsmElementQuestType { private val crossingFilter by lazy { """ nodes with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt index e8ac320a50..e5f64387bd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/recycling_material/AddRecyclingContainerMaterials.kt @@ -10,12 +10,12 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.deleteCheckDatesForKey import de.westnordost.streetcomplete.data.meta.updateCheckDateForKey import de.westnordost.streetcomplete.data.osm.changes.StringMapEntryModify -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.util.LatLonRaster import de.westnordost.streetcomplete.util.distanceTo import de.westnordost.streetcomplete.util.enclosingBoundingBox -class AddRecyclingContainerMaterials : OsmMapDataQuestType { +class AddRecyclingContainerMaterials : OsmElementQuestType { private val filter by lazy { """ nodes with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt index 32683317de..5415496039 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/road_name/AddRoadName.kt @@ -8,7 +8,7 @@ import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.AllCountriesExcept import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.quests.LocalizedName import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionEntry import de.westnordost.streetcomplete.quests.road_name.data.RoadNameSuggestionsDao @@ -16,7 +16,7 @@ import de.westnordost.streetcomplete.quests.road_name.data.toRoadNameByLanguage class AddRoadName( private val roadNameSuggestionsDao: RoadNameSuggestionsDao -) : OsmMapDataQuestType { +) : OsmElementQuestType { private val filter by lazy { """ ways with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt index f5297e5e06..76fa802239 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/sidewalk/AddSidewalk.kt @@ -7,10 +7,10 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.meta.ANYTHING_UNPAVED import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.util.isNear -class AddSidewalk : OsmMapDataQuestType { +class AddSidewalk : OsmElementQuestType { /* the filter additionally filters out ways that are unlikely to have sidewalks: * unpaved roads, roads with very low speed limits and roads that are probably not developed diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt index 597be3d3b0..6cb5001025 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/summit_register/AddSummitRegister.kt @@ -7,13 +7,13 @@ import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpressio import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.data.quest.NoCountriesExcept import de.westnordost.streetcomplete.ktx.toYesNo import de.westnordost.streetcomplete.quests.YesNoQuestAnswerFragment import de.westnordost.streetcomplete.util.distanceToArcs -class AddSummitRegister : OsmMapDataQuestType { +class AddSummitRegister : OsmElementQuestType { private val filter by lazy { """ nodes with diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt index 43f3b712eb..1ec00e55e9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/tactile_paving/AddTactilePavingCrosswalk.kt @@ -7,10 +7,10 @@ import de.westnordost.streetcomplete.data.meta.updateWithCheckDate import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression import de.westnordost.streetcomplete.data.osm.changes.StringMapChangesBuilder import de.westnordost.streetcomplete.data.quest.NoCountriesExcept -import de.westnordost.streetcomplete.data.osm.osmquest.OsmMapDataQuestType +import de.westnordost.streetcomplete.data.osm.osmquest.OsmElementQuestType import de.westnordost.streetcomplete.ktx.toYesNo -class AddTactilePavingCrosswalk : OsmMapDataQuestType { +class AddTactilePavingCrosswalk : OsmElementQuestType { private val crossingFilter by lazy { """ nodes with diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt index 9596abbd92..4f2e11537c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt @@ -83,7 +83,7 @@ class OsmApiQuestDownloaderTest { } } -private class TestMapDataQuestType(private val list: List) : OsmMapDataQuestType { +private class TestMapDataQuestType(private val list: List) : OsmElementQuestType { override var enabledInCountries: Countries = AllCountries diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt index 2a60eaa9cc..daffa7ccbb 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/upload/changesets/OpenQuestChangesetsManagerTest.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.osm.upload.changesets import android.content.SharedPreferences +import de.westnordost.osmapi.map.MapDataWithGeometry import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.osmapi.map.data.Element import de.westnordost.streetcomplete.ApplicationConstants @@ -75,6 +76,7 @@ class OpenQuestChangesetsManagerTest { private class TestQuestType : OsmElementQuestType { + override fun getApplicableElements(mapData: MapDataWithGeometry) = emptyList() override fun getTitle(tags: Map) = 0 override fun isApplicableTo(element: Element):Boolean? = null override fun applyAnswerTo(answer: String, changes: StringMapChangesBuilder) {} From 6f8cf82e0671a96ddc4e1623f0cf80dec27dea0e Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 28 Oct 2020 23:13:21 +0100 Subject: [PATCH 56/63] nicer logging --- .../streetcomplete/data/download/AVariableRadiusStrategy.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt index 2c0962628d..c9292da31a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt @@ -45,10 +45,10 @@ abstract class AVariableRadiusStrategy( val activeBoundingBox = pos.enclosingBoundingBox(radius) if (hasMissingQuestsFor(activeBoundingBox.enclosingTilesRect(tileZoom))) { - Log.i(TAG, "Downloading in ${radius}m radius of user") + Log.i(TAG, "Downloading in radius of ${radius.toInt()} meters around user") return activeBoundingBox } - Log.i(TAG, "All downloaded in ${radius}m of user") + Log.i(TAG, "All downloaded in radius of ${radius.toInt()} meters around user") return null } From e458b3a695d859f26d41b571688cfab5fe6d5a5b Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 01:30:26 +0100 Subject: [PATCH 57/63] correct some comments --- .../streetcomplete/data/download/tiles/DownloadedTilesDao.kt | 2 +- .../de/westnordost/streetcomplete/quests/oneway/AddOneway.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt index c4ca47a07b..b02f007d07 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/tiles/DownloadedTilesDao.kt @@ -13,7 +13,7 @@ import de.westnordost.streetcomplete.util.TilesRect import javax.inject.Inject -/** Keeps info in which areas things have been downloaded already in a tile grid of zoom level 14 */ +/** Keeps info in which areas things have been downloaded already in a tile grid */ class DownloadedTilesDao @Inject constructor(private val dbHelper: SQLiteOpenHelper) { private val db get() = dbHelper.writableDatabase diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt index 5afb6c4a08..af603c2123 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway/AddOneway.kt @@ -47,7 +47,7 @@ class AddOneway : OsmElementQuestType { connectionCountByNodeIds[nodeId] = prevCount + 1 } if (elementFilter.matches(road)) { - // check if the width of the road minus the space consumed by parking lanes is quite narrow + // check if the width of the road minus the space consumed by other stuff is quite narrow val width = road.tags["width"]?.toFloatOrNull() val isNarrow = width != null && width <= estimatedWidthConsumedByOtherThings(road.tags) + 4f if (isNarrow) { From 09a919db422c3e4ceccd36e87b7fadf4679fd9d2 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 15:35:50 +0100 Subject: [PATCH 58/63] fix possible division by zero --- .../data/download/AVariableRadiusStrategy.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt index c9292da31a..6b4c13ac95 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/AVariableRadiusStrategy.kt @@ -38,10 +38,11 @@ abstract class AVariableRadiusStrategy( // otherwise, see if anything is missing in a variable radius, based on quest density val density = getQuestDensityFor(thisTile.asBoundingBox(tileZoom)) - val radius = min( - sqrt( desiredQuestCountInVicinity / ( PI * density )), - sqrt( maxDownloadAreaInKm2 * 1000 * 1000 / PI ) - ) + val maxRadius = sqrt( maxDownloadAreaInKm2 * 1000 * 1000 / PI ) + + var radius = if (density > 0) sqrt( desiredQuestCountInVicinity / ( PI * density )) else maxRadius + + radius = min( radius, maxRadius) val activeBoundingBox = pos.enclosingBoundingBox(radius) if (hasMissingQuestsFor(activeBoundingBox.enclosingTilesRect(tileZoom))) { From bc8ba7e0afa81f39319f5edaf0c0113307a8cf82 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 15:36:08 +0100 Subject: [PATCH 59/63] formatting --- .../data/osm/osmquest/OsmApiQuestDownloader.kt | 4 ++-- .../quests/oneway_suspects/AddSuspectedOneway.kt | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 4179057373..5606fdcdb9 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -29,8 +29,8 @@ import javax.inject.Inject import javax.inject.Provider import kotlin.collections.ArrayList -/** Does one API call to get all the map data and generates quests from that. Calls isApplicable - * on all the quest types on all elements in the downloaded data. */ +/** Does one API call to get all the map data and generates quests from that. Calls getApplicableElements + * on all the quest types */ class OsmApiQuestDownloader @Inject constructor( private val elementDB: MergedElementDao, private val osmQuestController: OsmQuestController, diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt index bfff7d583b..ae7e43b3f1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/oneway_suspects/AddSuspectedOneway.kt @@ -21,9 +21,15 @@ class AddSuspectedOneway( ) : OsmElementQuestType { private val filter by lazy { """ - ways with highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road - and !oneway and junction != roundabout and area != yes - and (access !~ private|no or (foot and foot !~ private|no)) + ways with + highway ~ trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|track|road + and !oneway + and junction != roundabout + and area != yes + and ( + access !~ private|no + or (foot and foot !~ private|no) + ) """.toElementFilterExpression() } override val commitMessage = From 16c336e82c083879f4f2ada4d2c78744f1c1c2ce Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 15:50:34 +0100 Subject: [PATCH 60/63] eliminate ElementEligibleForOsmQuestChecker --- .../ElementEligibleForOsmQuestChecker.kt | 51 ------------------- .../osm/osmquest/OsmApiQuestDownloader.kt | 38 +++++++++++--- 2 files changed, 31 insertions(+), 58 deletions(-) delete mode 100644 app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt deleted file mode 100644 index 84b862ca72..0000000000 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/ElementEligibleForOsmQuestChecker.kt +++ /dev/null @@ -1,51 +0,0 @@ -package de.westnordost.streetcomplete.data.osm.osmquest - -import de.westnordost.countryboundaries.CountryBoundaries -import de.westnordost.countryboundaries.isInAny -import de.westnordost.osmapi.map.data.LatLon -import de.westnordost.osmapi.map.data.OsmLatLon -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry -import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry -import de.westnordost.streetcomplete.util.measuredLength -import java.util.concurrent.FutureTask -import javax.inject.Inject - -/** Checks if the given quest type may be created based on its geometry and location */ -class ElementEligibleForOsmQuestChecker @Inject constructor( - private val countryBoundariesFuture: FutureTask, -) { - fun mayCreateQuestFrom( - questType: OsmElementQuestType<*>, geometry: ElementGeometry?, blacklistedPositions: Set - ): Boolean { - // invalid geometry -> can't show this quest, so skip it - val pos = geometry?.center ?: return false - - // do not create quests whose marker is at/near a blacklisted position - if (blacklistedPositions.contains(pos.truncateTo5Decimals())) { - return false - } - - // do not create quests in countries where the quest is not activated - val countries = questType.enabledInCountries - if (!countryBoundariesFuture.get().isInAny(pos, countries)) { - return false - } - - // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey - if (geometry is ElementPolylinesGeometry) { - val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } - if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { - return false - } - } - - return true - } -} - -const val MAX_GEOMETRY_LENGTH_IN_METERS = 500 - -// the resulting precision is about ~1 meter (see #1089) -private fun LatLon.truncateTo5Decimals() = OsmLatLon(latitude.truncateTo5Decimals(), longitude.truncateTo5Decimals()) - -private fun Double.truncateTo5Decimals() = (this * 1e5).toInt().toDouble() / 1e5 \ No newline at end of file diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt index 5606fdcdb9..6334bf993a 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloader.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.data.osm.osmquest import android.util.Log +import de.westnordost.countryboundaries.isInAny import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.countryboundaries.intersects import de.westnordost.osmapi.common.errors.OsmQueryTooBigException @@ -16,9 +17,11 @@ import de.westnordost.osmapi.map.isRelationComplete import de.westnordost.streetcomplete.data.MapDataApi import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometry import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementGeometryCreator +import de.westnordost.streetcomplete.data.osm.elementgeometry.ElementPolylinesGeometry import de.westnordost.streetcomplete.data.osm.mapdata.MergedElementDao import de.westnordost.streetcomplete.data.osmnotes.NotePositionsSource import de.westnordost.streetcomplete.data.quest.QuestType +import de.westnordost.streetcomplete.util.measuredLength import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -38,8 +41,7 @@ class OsmApiQuestDownloader @Inject constructor( private val notePositionsSource: NotePositionsSource, private val mapDataApi: MapDataApi, private val mapDataWithGeometry: Provider, - private val elementGeometryCreator: ElementGeometryCreator, - private val elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker + private val elementGeometryCreator: ElementGeometryCreator ) : CoroutineScope by CoroutineScope(Dispatchers.Default) { fun download(questTypes: List>, bbox: BoundingBox) { @@ -75,10 +77,8 @@ class OsmApiQuestDownloader @Inject constructor( var i = 0 val questTime = System.currentTimeMillis() for (element in questType.getApplicableElements(mapData)) { - val geometry = getCompleteGeometry(element.type!!, element.id, mapData, completeRelationGeometries) - if (!elementEligibleForOsmQuestChecker.mayCreateQuestFrom(questType, geometry, truncatedBlacklistedPositions)) continue - - val quest = OsmQuest(questType, element.type, element.id, geometry!!) + val geometry = getCompleteGeometry(element.type, element.id, mapData, completeRelationGeometries) + val quest = createQuest(questType, element, geometry, truncatedBlacklistedPositions) ?: continue quests.add(quest) questElements.add(element) @@ -114,6 +114,28 @@ class OsmApiQuestDownloader @Inject constructor( Log.i(TAG,"Added ${replaceResult.added} new and removed ${replaceResult.deleted} already resolved quests") } + private fun createQuest(questType: OsmElementQuestType<*>, element: Element, geometry: ElementGeometry?, blacklistedPositions: Set): OsmQuest? { + // invalid geometry -> can't show this quest, so skip it + val pos = geometry?.center ?: return null + + // do not create quests whose marker is at/near a blacklisted position + if (blacklistedPositions.contains(pos.truncateTo5Decimals())) return null + + // do not create quests in countries where the quest is not activated + val countries = questType.enabledInCountries + if (!countryBoundariesFuture.get().isInAny(pos, countries)) return null + + // do not create quests that refer to geometry that is too long for a surveyor to be expected to survey + if (geometry is ElementPolylinesGeometry) { + val totalLength = geometry.polylines.sumByDouble { it.measuredLength() } + if (totalLength > MAX_GEOMETRY_LENGTH_IN_METERS) { + return null + } + } + + return OsmQuest(questType, element.type, element.id, geometry) + } + private fun getCompleteGeometry( elementType: Element.Type, elementId: Long, @@ -182,4 +204,6 @@ private fun BoundingBox.splitIntoFour(): List { BoundingBox(center.latitude, minLongitude, maxLatitude, center.longitude), BoundingBox(center.latitude, center.longitude, maxLatitude, maxLongitude) ) -} \ No newline at end of file +} + +const val MAX_GEOMETRY_LENGTH_IN_METERS = 600 From 43837d1e272550a9137eb2b752037cab081bde1a Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 15:50:54 +0100 Subject: [PATCH 61/63] remove MIN_DOWNLOADABLE_RADIUS_IN_METERS --- .../streetcomplete/ApplicationConstants.java | 4 +--- .../de/westnordost/streetcomplete/map/MainFragment.kt | 11 +++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java index 982f8b3322..e79b5dcef2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java +++ b/app/src/main/java/de/westnordost/streetcomplete/ApplicationConstants.java @@ -8,11 +8,9 @@ public class ApplicationConstants QUESTTYPE_TAG_KEY = NAME + ":quest_type"; public final static double - MAX_DOWNLOADABLE_AREA_IN_SQKM = 10, + MAX_DOWNLOADABLE_AREA_IN_SQKM = 12.0, MIN_DOWNLOADABLE_AREA_IN_SQKM = 0.1; - public final static double MIN_DOWNLOADABLE_RADIUS_IN_METERS = 600; - public final static String DATABASE_NAME = "streetcomplete.db"; public final static int QUEST_TILE_ZOOM = 16; diff --git a/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt b/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt index d6cbeb6849..3269254f8f 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/map/MainFragment.kt @@ -71,6 +71,7 @@ import javax.inject.Inject import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin +import kotlin.math.sqrt /** Contains the quests map and the controls for it. */ class MainFragment : Fragment(R.layout.fragment_main), @@ -612,7 +613,7 @@ class MainFragment : Fragment(R.layout.fragment_main), context?.toast(R.string.cannot_find_bbox_or_reduce_tilt, Toast.LENGTH_LONG) } else { val enclosingBBox = displayArea.asBoundingBoxOfEnclosingTiles(ApplicationConstants.QUEST_TILE_ZOOM) - val areaInSqKm = enclosingBBox.area(EARTH_RADIUS) / 1000000 + val areaInSqKm = enclosingBBox.area() / 1000000 if (areaInSqKm > ApplicationConstants.MAX_DOWNLOADABLE_AREA_IN_SQKM) { context?.toast(R.string.download_area_too_big, Toast.LENGTH_LONG) } else { @@ -635,15 +636,13 @@ class MainFragment : Fragment(R.layout.fragment_main), private fun downloadAreaConfirmed(bbox: BoundingBox) { var bbox = bbox - val areaInSqKm = bbox.area(EARTH_RADIUS) / 1000000 + val areaInSqKm = bbox.area() / 1000000 // below a certain threshold, it does not make sense to download, so let's enlarge it if (areaInSqKm < ApplicationConstants.MIN_DOWNLOADABLE_AREA_IN_SQKM) { val cameraPosition = mapFragment?.cameraPosition if (cameraPosition != null) { - bbox = cameraPosition.position.enclosingBoundingBox( - ApplicationConstants.MIN_DOWNLOADABLE_RADIUS_IN_METERS, - EARTH_RADIUS - ) + val radius = sqrt( 1000000 * ApplicationConstants.MIN_DOWNLOADABLE_AREA_IN_SQKM / PI) + bbox = cameraPosition.position.enclosingBoundingBox(radius) } } questDownloadController.download(bbox, true) From 0d327689fc21f5248a1d71737a39ce4d426aac27 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 17:40:46 +0100 Subject: [PATCH 62/63] shortcut: don't create geometry when not needed --- .../quests/bikeway/AddCycleway.kt | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt index 27f44a5a61..46f5321e94 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/bikeway/AddCycleway.kt @@ -75,21 +75,20 @@ class AddCycleway : OsmElementQuestType { * should be excluded whose center is within of ~15 meters of a cycleway, to be on the safe * side. */ - val roadsWithMissingCycleway = eligibleRoads.filter { untaggedRoadsFilter.matches(it) } + val roadsWithMissingCycleway = eligibleRoads.filter { untaggedRoadsFilter.matches(it) }.toMutableList() - val maybeSeparatelyMappedCyclewayGeometries = mapData.ways - .filter { maybeSeparatelyMappedCyclewaysFilter.matches(it) } - .mapNotNull { mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry } + if (roadsWithMissingCycleway.isNotEmpty()) { - val minDistToWays = 15.0 //m + val maybeSeparatelyMappedCyclewayGeometries = mapData.ways + .filter { maybeSeparatelyMappedCyclewaysFilter.matches(it) } + .mapNotNull { mapData.getWayGeometry(it.id) as? ElementPolylinesGeometry } - // filter out roads with missing sidewalks that are near footways - val roadsWithMissingCyclewayNotNearSeparateCycleways = roadsWithMissingCycleway.filter { road -> - val roadGeometry = mapData.getWayGeometry(road.id) as? ElementPolylinesGeometry - if (roadGeometry != null) { - !roadGeometry.isNear(minDistToWays, maybeSeparatelyMappedCyclewayGeometries) - } else { - false + val minDistToWays = 15.0 //m + + // filter out roads with missing sidewalks that are near footways + roadsWithMissingCycleway.removeAll { road -> + val roadGeometry = mapData.getWayGeometry(road.id) as? ElementPolylinesGeometry + roadGeometry?.isNear(minDistToWays, maybeSeparatelyMappedCyclewayGeometries) ?: true } } @@ -100,7 +99,7 @@ class AddCycleway : OsmElementQuestType { OLDER_THAN_4_YEARS.matches(it) && it.hasOnlyKnownCyclewayTags() } - return roadsWithMissingCyclewayNotNearSeparateCycleways + oldRoadsWithKnownCycleways + return roadsWithMissingCycleway + oldRoadsWithKnownCycleways } From 0134cb25fbf9f91518ac37f7ee154358378bb702 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Thu, 29 Oct 2020 17:40:57 +0100 Subject: [PATCH 63/63] fix test --- .../data/osm/osmquest/OsmApiQuestDownloaderTest.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt index 4f2e11537c..1b8a12d914 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/data/osm/osmquest/OsmApiQuestDownloaderTest.kt @@ -33,7 +33,6 @@ class OsmApiQuestDownloaderTest { private lateinit var mapDataApi: MapDataApi private lateinit var mapDataWithGeometry: CachingMapDataWithGeometry private lateinit var elementGeometryCreator: ElementGeometryCreator - private lateinit var elementEligibleForOsmQuestChecker: ElementEligibleForOsmQuestChecker private lateinit var downloader: OsmApiQuestDownloader private val bbox = BoundingBox(0.0, 0.0, 1.0, 1.0) @@ -47,13 +46,12 @@ class OsmApiQuestDownloaderTest { mapDataWithGeometry = mock() elementGeometryCreator = mock() notePositionsSource = mock() - elementEligibleForOsmQuestChecker = mock() val countryBoundariesFuture = FutureTask { countryBoundaries } countryBoundariesFuture.run() val mapDataProvider = Provider { mapDataWithGeometry } downloader = OsmApiQuestDownloader( elementDb, osmQuestController, countryBoundariesFuture, notePositionsSource, mapDataApi, - mapDataProvider, elementGeometryCreator, elementEligibleForOsmQuestChecker) + mapDataProvider, elementGeometryCreator) } @Test fun `creates quest for element`() { @@ -63,7 +61,6 @@ class OsmApiQuestDownloaderTest { val questType = TestMapDataQuestType(listOf(node)) on(mapDataWithGeometry.getNodeGeometry(5)).thenReturn(geom) - on(elementEligibleForOsmQuestChecker.mayCreateQuestFrom(any(), any(), any())).thenReturn(true) on(osmQuestController.replaceInBBox(any(), any(), any())).thenAnswer { val createdQuests = it.arguments[0] as List assertEquals(1, createdQuests.size)