Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spatial cache for OsmQuestController, OsmNoteQuestController and MapDataController #4125

Merged
merged 147 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
7909e6c
add cache for OsmQuestController
Helium314 Jun 16, 2022
c994377
update spatial cache and use it for quest and mapdata controllers
Helium314 Jun 20, 2022
de1ff5a
partially fix missed cleanup
Helium314 Jun 21, 2022
5a2874a
fix indentation
westnordost Jun 21, 2022
5ce584e
make intention of code clearer
westnordost Jun 21, 2022
e5dbd6b
change BoundingBox.contains to operator fun
Helium314 Jun 22, 2022
c312f5b
update caches
Helium314 Jun 22, 2022
e0949aa
Merge branch 'cache' of https://github.com/Helium314/StreetComplete i…
Helium314 Jun 22, 2022
ee42ebb
add logging if bbox does not match complete tiles
Helium314 Jun 22, 2022
23144e7
add comments about suspected problems
Helium314 Jun 22, 2022
d989d84
use mutable list instead of set
Helium314 Jun 24, 2022
a42c6f0
set low initial capacity on list of ways
Helium314 Jun 24, 2022
b33114b
clean cache properly, avoid creation of indermediate lists
Helium314 Jun 25, 2022
ab8fc64
use cache for more than just map data
Helium314 Jun 25, 2022
5331b8f
also use cache for currently unused get functions, small fixes
Helium314 Jun 25, 2022
538124f
Merge branch 'master' into cache
Helium314 Jun 25, 2022
889d15d
add separate clear cache function, update comments
Helium314 Jun 25, 2022
86bc980
use cache to complete mapdata
Helium314 Jun 25, 2022
5f1c540
synchronized access to cache, fix missed getAll
Helium314 Jun 25, 2022
b2fa89c
fix missed synchronized, add comments
Helium314 Jun 25, 2022
83492a1
take care of cache issues with large bboxes
Helium314 Jun 26, 2022
96f01da
make questController cache accesses synchronized
Helium314 Jun 26, 2022
9b82831
add clearCache to osmQuestController
Helium314 Jun 26, 2022
d9d78de
add spatial cache to OsmNoteQuestController
Helium314 Jun 26, 2022
b67d64c
add cacheTrimmer to be called on low memory
Helium314 Jun 26, 2022
97fd9bf
move SpatialCache to utils
Helium314 Jun 26, 2022
9d1f8b4
less aggressive cache trimming
Helium314 Jun 26, 2022
11dd81c
use for loop with label instead of forEach
Helium314 Jun 26, 2022
b953213
don't crash on huge bbox, just load it and trim cache later
Helium314 Jun 26, 2022
7b9774a
update documentation
Helium314 Jun 26, 2022
c048c5a
performance improvement
Helium314 Jun 26, 2022
6fad7d8
fix potentially dangerous behavior with incomplete tiles, update docu…
Helium314 Jun 27, 2022
024f51f
Merge branch 'cache' of https://github.com/Helium314/StreetComplete i…
Helium314 Jun 27, 2022
bfdc352
clear cache onInvalidated
Helium314 Jun 27, 2022
203c611
move cache from OsmNoteQuestController to NoteController
Helium314 Jun 27, 2022
9c8a46e
make spatial cache contain the actual items
westnordost Jun 27, 2022
0390395
fix obvious mistake
westnordost Jun 28, 2022
7cb8d14
remove shortcut (see https://github.com/Helium314/StreetComplete/pull…
westnordost Jun 28, 2022
bcc24cf
Merge pull request #377 from streetcomplete/cache-experiment1
Helium314 Jun 29, 2022
b1ce334
Merge branch 'master' into cache
Helium314 Jun 29, 2022
9324df3
make quest controller work again
Helium314 Jul 1, 2022
54590e6
make MapDataController work again
Helium314 Jul 1, 2022
fc1bc30
update comments, use cache for element counting
Helium314 Jul 1, 2022
5d8bf13
move spatial cache from note and quest controllers to VisibleQuestsSo…
Helium314 Jul 4, 2022
2db54f2
small optimizations, update comments and names
Helium314 Jul 4, 2022
b4cf939
cache geometry instead of geometryEntry
Helium314 Jul 4, 2022
64d11db
add data to non-spatial caches on cache miss
Helium314 Jul 4, 2022
4190583
increase initial capacity of spatial cache for MapDataController
Helium314 Jul 4, 2022
7af93a6
don't put cached geometries in cache again
Helium314 Jul 4, 2022
cc1126c
change quest key and marker position to lazy
Helium314 Jul 4, 2022
2f2c912
make MapDataControllerTest work with cache
Helium314 Jul 8, 2022
789418d
make VisibleQuestsSourceTest work with cache
Helium314 Jul 8, 2022
6d4af9a
fix missing imports
Helium314 Jul 8, 2022
1100e86
add documentation comment
westnordost Jul 8, 2022
8bfb2f6
remove incorrect documentation comment
westnordost Jul 8, 2022
b9ca919
Merge branch 'master' into cache
Helium314 Jul 8, 2022
6fb9169
change getting SpatialCache keys to function returning a list
Helium314 Jul 11, 2022
3a8e77c
merge removing and adding/updating cache items into one function
Helium314 Jul 11, 2022
89ab951
make choice of values for spatial cache clear
Helium314 Jul 11, 2022
cf757da
create fewer intermediate lists
Helium314 Jul 11, 2022
b8e75a4
move cache from MapDataController to separate class
Helium314 Jul 11, 2022
de18d12
small improvements, update comments
Helium314 Jul 11, 2022
cf8bd08
change indentation
westnordost Jul 12, 2022
bcf8e79
rename variable; trivial performance improvement
westnordost Jul 12, 2022
2b18ee5
fix indentation
westnordost Jul 12, 2022
74d5a6b
make cache imitate db a bit more
Helium314 Jul 13, 2022
63010a2
more readable getWaysForNode
Helium314 Jul 13, 2022
852f7a1
more readable getRelationsForElement
Helium314 Jul 13, 2022
b9dbbee
don't use internal knowledge about the cache in MapDataController
Helium314 Jul 13, 2022
da082a0
add tests for SpatialCache
Helium314 Jul 15, 2022
d90ab80
fix wrong check
Helium314 Jul 16, 2022
e0ea10f
fix missed deletions
Helium314 Jul 16, 2022
457f563
mark open question as todo
westnordost Jul 21, 2022
6ac0dbb
add further tests (wip)
Helium314 Jul 22, 2022
8666fcb
Merge branch 'master' into cache
Helium314 Jul 22, 2022
9295ac7
update tests for SpatialCache
Helium314 Jul 22, 2022
af2bcda
remove the correct old item when updating
Helium314 Jul 27, 2022
1d27edc
also check old tile in spatial cache update test
Helium314 Aug 8, 2022
0b5f118
use correct order for assertEquals (expected, actual)
Helium314 Aug 8, 2022
5bccf69
add tests for MapDataCache (wip)
Helium314 Aug 10, 2022
1f68d13
fix bad non-removal of old item, add test
Helium314 Aug 30, 2022
51e8134
fix issues with filling relationIdsByElementKeyCache / wayIdsByNodeId…
Helium314 Aug 30, 2022
13c200d
add some more tests
Helium314 Aug 30, 2022
c251194
Merge branch 'master' into cache
Helium314 Sep 5, 2022
c51a016
Merge branch 'master' into cache
Helium314 Sep 6, 2022
5798526
update tests and comments
Helium314 Sep 6, 2022
66f057f
decide when to trim, performance improvements, comments
Helium314 Sep 8, 2022
e1e05e7
fix wrong elementType used for getRelations
Helium314 Sep 8, 2022
fae6c5e
perfomance improvements
Helium314 Sep 8, 2022
e751d29
consider relations of relations
Helium314 Sep 8, 2022
9556a83
consistent order in CacheTrimmer
Helium314 Sep 12, 2022
86731f1
try putting all items, not only in replaced tiles
Helium314 Sep 12, 2022
84fe0eb
fully synchronize all access to MapDataCache
Helium314 Sep 12, 2022
fd4317c
keep empty tiles when trimming SpatialCache
Helium314 Sep 12, 2022
55c6671
switch tile zoom to 17, update comments
Helium314 Sep 12, 2022
947fc8d
update tests
Helium314 Sep 14, 2022
e2f9489
another test update
Helium314 Sep 14, 2022
fd182ab
switch back to loop over (all) tiles in spatialCache, fix tests
Helium314 Sep 14, 2022
e52e518
don't return relations of relations in getMapDataWithGeometry
Helium314 Sep 15, 2022
3a758d1
update comments, add documentation
Helium314 Sep 16, 2022
8f2cced
add some comments
westnordost Sep 16, 2022
bcea062
put common functionality into own method
westnordost Sep 16, 2022
2fee97d
synchronize clear
westnordost Sep 16, 2022
fe6fa50
elaborate on comment
westnordost Sep 16, 2022
e2c06c5
rename a function for clarity (IMO)
westnordost Sep 16, 2022
b6da084
any write-operations on cache should be synchronized with the rest too
westnordost Sep 16, 2022
00a3887
eliminate a !!
westnordost Sep 16, 2022
05063b2
longer doc comment
westnordost Sep 16, 2022
43cb7f3
make slightly more readable and add comment
westnordost Sep 16, 2022
a75f913
use separate caches for ways and relations
Helium314 Sep 16, 2022
64f033e
don't remove empty tiles, improve readability
Helium314 Sep 16, 2022
5ca5381
update some comments
westnordost Sep 16, 2022
ed16d33
code style
westnordost Sep 16, 2022
3456a79
getElements - more easily readable (IMO)
westnordost Sep 16, 2022
187e2b9
same as last commit for geometry entries
westnordost Sep 16, 2022
f666eff
clean doc comments
westnordost Sep 16, 2022
267b7cb
expand long line
westnordost Sep 16, 2022
9699d5e
make consistent with getWaysForNode
westnordost Sep 16, 2022
69cc2fe
eliminate usage of !!
westnordost Sep 16, 2022
7ea4741
consistent naming
westnordost Sep 16, 2022
6fe8d46
initialize directly
westnordost Sep 16, 2022
17b2868
put into own variable for easier readability
westnordost Sep 16, 2022
6cfbcde
normal for is shorter and better readable
westnordost Sep 16, 2022
f8877ea
fix duplicate deletion introduced
westnordost Sep 16, 2022
477bd94
improve readability a bit (IMO)
westnordost Sep 16, 2022
264252d
increase initial capacity of spatialCache
Helium314 Sep 17, 2022
8b91498
remove commented code
Helium314 Sep 17, 2022
41fee63
let getTiles return a set instead of list
Helium314 Sep 17, 2022
5f8e197
try to make code more readable
Helium314 Sep 17, 2022
985ef48
cache null if element is not in database
Helium314 Sep 17, 2022
e00c3ee
cache null also for getElements/Geometries
Helium314 Sep 17, 2022
63515ba
make spatialCache.size return number of non-empty tiles
Helium314 Sep 17, 2022
f83846d
increase cache size and remove more tiles when trimming automatically
Helium314 Sep 17, 2022
a574ddd
I find ?.let actually OK (better readable) if it is part of an expres…
westnordost Sep 17, 2022
8f67f8f
remove unnecessary synchronized
westnordost Sep 17, 2022
1bd6f10
don't let kotlin infer the return type of functions that should just …
westnordost Sep 17, 2022
4c7f2f8
add doc and explanatory comment to complicated function
westnordost Sep 17, 2022
044213d
put the trimming into one function only
westnordost Sep 17, 2022
22483e3
Merge branch 'cache' of https://github.com/Helium314/StreetComplete i…
westnordost Sep 17, 2022
addc0ce
update comments
Helium314 Sep 17, 2022
7249915
just refer to comment above rather than copying it
westnordost Sep 17, 2022
966e936
switch back to getTilePos to guarantee unique tile for each LatLon
Helium314 Sep 20, 2022
0bf49a4
Merge branch 'master' into cache
Helium314 Sep 25, 2022
5578f88
don't cache missing elements
Helium314 Sep 28, 2022
46d0cb5
update tests for MapDataCache
Helium314 Sep 29, 2022
fc57350
more tests
Helium314 Sep 29, 2022
b79adee
add more tests
Helium314 Sep 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package de.westnordost.streetcomplete

import android.app.Application
import android.content.ComponentCallbacks2
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import de.westnordost.streetcomplete.data.CacheTrimmer
import de.westnordost.streetcomplete.data.CleanerWorker
import de.westnordost.streetcomplete.data.Preloader
import de.westnordost.streetcomplete.data.dbModule
Expand Down Expand Up @@ -68,6 +70,7 @@ class StreetCompleteApplication : Application() {
private val prefs: SharedPreferences by inject()
private val editHistoryController: EditHistoryController by inject()
private val userLoginStatusController: UserLoginStatusController by inject()
private val cacheTrimmer: CacheTrimmer by inject()

private val applicationScope = CoroutineScope(SupervisorJob() + CoroutineName("Application"))

Expand Down Expand Up @@ -156,6 +159,20 @@ class StreetCompleteApplication : Application() {
applicationScope.cancel()
}

override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
when (level) {
ComponentCallbacks2.TRIM_MEMORY_COMPLETE, ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// very low on memory -> drop caches
cacheTrimmer.clearCaches()
}
ComponentCallbacks2.TRIM_MEMORY_MODERATE, ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> {
// memory needed, but not critical -> trim only
cacheTrimmer.trimCaches()
}
}
}

private fun setDefaultLocales() {
val locale = getSelectedLocale(this)
if (locale != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.westnordost.streetcomplete.data

import de.westnordost.streetcomplete.data.osm.mapdata.MapDataController
import de.westnordost.streetcomplete.data.quest.VisibleQuestsSource

class CacheTrimmer(
private val visibleQuestsSource: VisibleQuestsSource,
private val mapDataController: MapDataController,
) {
fun trimCaches() {
mapDataController.trimCache()
visibleQuestsSource.trimCache()
}

fun clearCaches() {
mapDataController.clearCache()
visibleQuestsSource.clearCache()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.koin.dsl.module

val osmApiModule = module {
factory { Cleaner(get(), get(), get()) }
factory { CacheTrimmer(get(), get())}
factory<MapDataApi> { MapDataApiImpl(get()) }
factory<NotesApi> { NotesApiImpl(get()) }
factory<TracksApi> { TracksApiImpl(get()) }
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryCreator
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryDao
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry
import de.westnordost.streetcomplete.data.osm.geometry.ElementPointGeometry
import de.westnordost.streetcomplete.util.ktx.format
import java.lang.System.currentTimeMillis
import java.util.concurrent.CopyOnWriteArrayList
Expand Down Expand Up @@ -39,27 +38,42 @@ class MapDataController internal constructor(
}
private val listeners: MutableList<Listener> = CopyOnWriteArrayList()

/** update element data because in the given bounding box, fresh data from the OSM API has been
* downloaded */
private val cache = MapDataCache(
SPATIAL_CACHE_TILE_ZOOM,
SPATIAL_CACHE_TILES,
SPATIAL_CACHE_INITIAL_CAPACITY
) { bbox ->
val elements = elementDB.getAll(bbox)
val elementGeometries = geometryDB.getAllEntries(
elements.mapNotNull { if (it !is Node) ElementKey(it.type, it.id) else null }
)
elements to elementGeometries
}

/** update element data with [mapData] in the given [bbox] (fresh data from the OSM API has been
* downloaded) */
fun putAllForBBox(bbox: BoundingBox, mapData: MutableMapData) {
val time = currentTimeMillis()

val oldElementKeys: Set<ElementKey>
val geometryEntries: List<ElementGeometryEntry>
val geometryEntries: Collection<ElementGeometryEntry>
synchronized(this) {
// for incompletely downloaded relations, complete the map data (as far as possible) with
// local data, i.e. with local nodes and ways (still) in local storage
completeMapData(mapData)

geometryEntries = mapData.mapNotNull { element ->
val geometry = elementGeometryCreator.create(element, mapData, true)
geometry?.let { ElementGeometryEntry(element.type, element.id, it) }
}
geometryEntries = createGeometries(mapData, mapData)

// don't use cache here, because if not everything is already cached, db call will be faster
oldElementKeys = elementDB.getAllKeys(mapData.boundingBox!!).toMutableSet()
for (element in mapData) {
oldElementKeys.remove(ElementKey(element.type, element.id))
}

// for the cache, use bbox and not mapData.boundingBox because the latter is padded,
// see comment for QUEST_FILTER_PADDING
cache.update(oldElementKeys, mapData, geometryEntries, bbox)

elementDB.deleteAll(oldElementKeys)
geometryDB.deleteAll(oldElementKeys)
geometryDB.putAll(geometryEntries)
Expand All @@ -77,25 +91,25 @@ class MapDataController internal constructor(
onReplacedForBBox(bbox, mapDataWithGeometry)
}

/** incorporate the [mapDataUpdates] (data has been updated after upload) */
fun updateAll(mapDataUpdates: MapDataUpdates) {
val elements = mapDataUpdates.updated
// need mapData in order to create (updated) geometry
val mapData = MutableMapData(elements)

val deletedKeys: List<ElementKey>
val geometryEntries: List<ElementGeometryEntry>
val geometryEntries: Collection<ElementGeometryEntry>
synchronized(this) {
completeMapData(mapData)

geometryEntries = elements.mapNotNull { element ->
val geometry = elementGeometryCreator.create(element, mapData, true)
geometry?.let { ElementGeometryEntry(element.type, element.id, geometry) }
}
geometryEntries = createGeometries(elements, mapData)

val newElementKeys = mapDataUpdates.idUpdates.map { ElementKey(it.elementType, it.newElementId) }
val oldElementKeys = mapDataUpdates.idUpdates.map { ElementKey(it.elementType, it.oldElementId) }
deletedKeys = mapDataUpdates.deleted + oldElementKeys

cache.update(deletedKeys, elements, geometryEntries)

elementDB.deleteAll(deletedKeys)
geometryDB.deleteAll(deletedKeys)
geometryDB.putAll(geometryEntries)
Expand All @@ -109,6 +123,13 @@ class MapDataController internal constructor(
onUpdated(updated = mapDataWithGeom, deleted = deletedKeys)
}

/** Create ElementGeometryEntries for [elements] using [mapData] to supply the necessary geometry */
private fun createGeometries(elements: Iterable<Element>, mapData: MapData): Collection<ElementGeometryEntry> =
elements.mapNotNull { element ->
val geometry = elementGeometryCreator.create(element, mapData, true)
geometry?.let { ElementGeometryEntry(element.type, element.id, geometry) }
}

private fun completeMapData(mapData: MutableMapData) {
val missingNodeIds = mutableListOf<Long>()
val missingWayIds = mutableListOf<Long>()
Expand All @@ -125,70 +146,60 @@ class MapDataController internal constructor(
}
}

val ways = wayDB.getAll(missingWayIds)
val ways = getWays(missingWayIds)
for (way in mapData.ways + ways) {
for (nodeId in way.nodeIds) {
if (mapData.getNode(nodeId) == null) {
missingNodeIds.add(nodeId)
}
}
}
val nodes = nodeDB.getAll(missingNodeIds)
val nodes = getNodes(missingNodeIds)

mapData.addAll(nodes)
mapData.addAll(ways)
}

fun get(type: ElementType, id: Long): Element? =
elementDB.get(type, id)
fun get(type: ElementType, id: Long): Element? = cache.getElement(type, id, elementDB::get)

fun getGeometry(type: ElementType, id: Long): ElementGeometry? =
geometryDB.get(type, id)
fun getGeometry(type: ElementType, id: Long): ElementGeometry? = cache.getGeometry(type, id, geometryDB::get)

fun getGeometries(keys: Collection<ElementKey>): List<ElementGeometryEntry> =
geometryDB.getAllEntries(keys)
fun getGeometries(keys: Collection<ElementKey>): List<ElementGeometryEntry> = cache.getGeometries(keys, geometryDB::getAllEntries)

fun getMapDataWithGeometry(bbox: BoundingBox): MutableMapDataWithGeometry {
val time = currentTimeMillis()
val elements = elementDB.getAll(bbox)
/* performance improvement over geometryDB.getAllEntries(elements): no need to query
nodeDB twice (once for the element, another time for the geometry */
val elementGeometries = geometryDB.getAllEntries(
elements.mapNotNull { if (it !is Node) ElementKey(it.type, it.id) else null }
) + elements.mapNotNull { if (it is Node) it.toElementGeometryEntry() else null }
val result = MutableMapDataWithGeometry(elements, elementGeometries)
result.boundingBox = bbox
Log.i(TAG, "Fetched ${elements.size} elements and geometries in ${currentTimeMillis() - time}ms")
val result = cache.getMapDataWithGeometry(bbox)
Log.i(TAG, "Fetched ${result.size} elements and geometries in ${currentTimeMillis() - time}ms")

return result
}

private fun Node.toElementGeometryEntry() =
ElementGeometryEntry(type, id, ElementPointGeometry(position))

data class ElementCounts(val nodes: Int, val ways: Int, val relations: Int)
// this is used after downloading one tile with auto-download, so we should always have it cached
fun getElementCounts(bbox: BoundingBox): ElementCounts {
val keys = elementDB.getAllKeys(bbox)
val data = getMapDataWithGeometry(bbox)
return ElementCounts(
keys.count { it.type == ElementType.NODE },
keys.count { it.type == ElementType.WAY },
keys.count { it.type == ElementType.RELATION }
data.count { it is Node },
data.count { it is Way },
data.count { it is Relation }
)
}

override fun getNode(id: Long): Node? = nodeDB.get(id)
override fun getWay(id: Long): Way? = wayDB.get(id)
override fun getRelation(id: Long): Relation? = relationDB.get(id)
override fun getNode(id: Long): Node? = get(ElementType.NODE, id) as? Node
override fun getWay(id: Long): Way? = get(ElementType.WAY, id) as? Way
override fun getRelation(id: Long): Relation? = get(ElementType.RELATION, id) as? Relation

fun getAll(elementKeys: Collection<ElementKey>): List<Element> = elementDB.getAll(elementKeys)
fun getAll(elementKeys: Collection<ElementKey>): List<Element> =
cache.getElements(elementKeys, elementDB::getAll)

fun getNodes(ids: Collection<Long>): List<Node> = nodeDB.getAll(ids)
fun getWays(ids: Collection<Long>): List<Way> = wayDB.getAll(ids)
fun getRelations(ids: Collection<Long>): List<Relation> = relationDB.getAll(ids)
fun getNodes(ids: Collection<Long>): List<Node> = cache.getNodes(ids, nodeDB::getAll)
fun getWays(ids: Collection<Long>): List<Way> = cache.getWays(ids, wayDB::getAll)
fun getRelations(ids: Collection<Long>): List<Relation> = cache.getRelations(ids, relationDB::getAll)

override fun getWaysForNode(id: Long): List<Way> = wayDB.getAllForNode(id)
override fun getRelationsForNode(id: Long): List<Relation> = relationDB.getAllForNode(id)
override fun getRelationsForWay(id: Long): List<Relation> = relationDB.getAllForWay(id)
override fun getRelationsForRelation(id: Long): List<Relation> = relationDB.getAllForRelation(id)
override fun getWaysForNode(id: Long): List<Way> = cache.getWaysForNode(id, wayDB::getAllForNode)
override fun getRelationsForNode(id: Long): List<Relation> = cache.getRelationsForNode(id, relationDB::getAllForNode)
override fun getRelationsForWay(id: Long): List<Relation> = cache.getRelationsForWay(id, relationDB::getAllForWay)
override fun getRelationsForRelation(id: Long): List<Relation> = cache.getRelationsForRelation(id, relationDB::getAllForRelation)

override fun getWayComplete(id: Long): MapData? {
val way = getWay(id) ?: return null
Expand All @@ -214,6 +225,7 @@ class MapDataController internal constructor(
elements = elementDB.getIdsOlderThan(timestamp, limit)
if (elements.isEmpty()) return 0

cache.update(deletedKeys = elements)
elementCount = elementDB.deleteAll(elements)
geometryCount = geometryDB.deleteAll(elements)
createdElementsController.deleteAll(elements)
Expand All @@ -226,12 +238,19 @@ class MapDataController internal constructor(
}

fun clear() {
elementDB.clear()
geometryDB.clear()
createdElementsController.clear()
synchronized(this) {
clearCache()
elementDB.clear()
geometryDB.clear()
createdElementsController.clear()
}
onCleared()
}

fun clearCache() = synchronized(this) { cache.clear() }

fun trimCache() = synchronized(this) { cache.trim(SPATIAL_CACHE_TILES / 3) }

fun addListener(listener: Listener) {
listeners.add(listener)
}
Expand Down Expand Up @@ -260,3 +279,15 @@ class MapDataController internal constructor(
private const val TAG = "MapDataController"
}
}

// StyleableOverlayManager loads z16 tiles, but we want smaller tiles. Small tiles make db fetches for
// typical getMapDataWithGeometry calls noticeably faster than z16, as they usually only require a small area.
private const val SPATIAL_CACHE_TILE_ZOOM = 17

// Three times the maximum number of tiles that can be loaded at once in StyleableOverlayManager (translated from z16 tiles).
// We don't want to drop tiles from cache already when scrolling the map just a bit, especially
// considering automatic trim may temporarily reduce cache size to 2/3 of maximum.
private const val SPATIAL_CACHE_TILES = 192

// In a city this is roughly the number of nodes in ~20-40 z16 tiles
private const val SPATIAL_CACHE_INITIAL_CAPACITY = 100000
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ data class OsmQuest(
override val geometry: ElementGeometry
) : Quest, OsmQuestDaoEntry {

override val key: OsmQuestKey get() = OsmQuestKey(elementType, elementId, questTypeName)
override val key: OsmQuestKey by lazy { OsmQuestKey(elementType, elementId, questTypeName) }

override val questTypeName: String get() = type.name

override val position: LatLon get() = geometry.center

override val markerLocations: Collection<LatLon> get() {
override val markerLocations: Collection<LatLon> by lazy {
if (geometry is ElementPolylinesGeometry) {
val polyline = geometry.polylines[0]
val length = polyline.measuredLength()
Expand All @@ -36,13 +36,13 @@ data class OsmQuest(
val between = (length - (2 * MARKER_FROM_END_DISTANCE)) / (count - 1)
// space markers `between` apart, starting with `MARKER_FROM_END_DISTANCE` (the
// final marker will end up at `MARKER_FROM_END_DISTANCE` from the other end)
return polyline.pointsOnPolylineFromStart(
return@lazy polyline.pointsOnPolylineFromStart(
(0 until count).map { MARKER_FROM_END_DISTANCE + (it * between) }
)
}
}
// fall through to a single marker in the middle
return listOf(position)
listOf(position)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class OsmNoteQuest(
override val position: LatLon
) : Quest {
override val type: QuestType get() = OsmNoteQuestType
override val key: OsmNoteQuestKey get() = OsmNoteQuestKey(id)
override val markerLocations: Collection<LatLon> get() = listOf(position)
override val key: OsmNoteQuestKey by lazy { OsmNoteQuestKey(id) }
override val markerLocations: Collection<LatLon> by lazy { listOf(position) }
override val geometry: ElementGeometry get() = ElementPointGeometry(position)
}
Loading