OSM: implement ReferenceData and network filter

This commit is contained in:
johan12345
2023-11-11 19:47:23 +01:00
parent 2d8327f6a6
commit 6e5f894c8a
5 changed files with 107 additions and 12 deletions

View File

@@ -130,6 +130,16 @@ data class ChargepointList(val items: List<ChargepointListItem>, val isComplete:
}
}
/**
* Result returned from fullDownload() function.
*
* Note that [chargers] is implemented as a [Sequence] so that downloaded chargers can be saved
* while they are being parsed instead of having to keep all of them in RAM at once.
*
* [progress] is updated regularly to indicate the current download progress.
* [referenceData] will typically only be available once the download is completed, i.e. you have
* iterated over the whole sequence of [chargers].
*/
interface FullDownloadResult<out T : ReferenceData> {
val chargers: Sequence<ChargeLocation>
val progress: Float

View File

@@ -12,6 +12,7 @@ import net.vonforst.evmap.api.ChargepointList
import net.vonforst.evmap.api.FiltersSQLQuery
import net.vonforst.evmap.api.FullDownloadResult
import net.vonforst.evmap.api.StringProvider
import net.vonforst.evmap.api.goingelectric.GEReferenceData
import net.vonforst.evmap.api.mapPower
import net.vonforst.evmap.api.mapPowerInverse
import net.vonforst.evmap.api.nameForPlugType
@@ -128,6 +129,9 @@ class OpenStreetMapApiWrapper(baseurl: String = "https://evmap-dev.vonforst.net"
nameForPlugType(sp, plug)
}
val refData = referenceData as OSMReferenceData
val networkMap = refData.networks.associateWith { it }
return listOf(
BooleanFilter(sp.getString(R.string.filter_free), "freecharging"),
BooleanFilter(sp.getString(R.string.filter_free_parking), "freeparking"),
@@ -152,6 +156,10 @@ class OpenStreetMapApiWrapper(baseurl: String = "https://evmap-dev.vonforst.net"
),
manyChoices = true
),
MultipleChoiceFilter(
sp.getString(R.string.filter_networks), "networks",
networkMap, manyChoices = true
),
SliderFilter(
sp.getString(R.string.filter_min_connectors),
"min_connectors",
@@ -204,6 +212,16 @@ class OpenStreetMapApiWrapper(baseurl: String = "https://evmap-dev.vonforst.net"
requiresChargepointQuery = true
}
val networks = filters.getMultipleChoiceValue("networks")
if (networks != null && !networks.all) {
val networksList = if (networks.values.size == 0) {
""
} else {
networks.values.joinToString(",") { DatabaseUtils.sqlEscapeString(it) }
}
result.append(" AND network IN (${networksList})")
}
return FiltersSQLQuery(result.toString(), requiresChargepointQuery, false)
}
@@ -222,23 +240,31 @@ class OpenStreetMapApiWrapper(baseurl: String = "https://evmap-dev.vonforst.net"
}
}
data class OSMReferenceData(val test: String) : ReferenceData()
data class OSMReferenceData(val networks: List<String>) : ReferenceData()
class OSMFullDownloadResult(private val body: OSMDocument) : FullDownloadResult<OSMReferenceData> {
private var downloadProgress = 0f
private var refData: OSMReferenceData? = null
override val chargers: Sequence<ChargeLocation>
get() {
val time = body.timestamp
val networks = mutableListOf<String>()
return sequence {
body.elements.forEachIndexed { i, it ->
yield(it.convert(time))
val charger = it.convert(time)
yield(charger)
downloadProgress = i.toFloat() / body.count
charger.network?.let { networks.add(it) }
}
refData = OSMReferenceData(networks)
}
}
override val progress: Float
get() = downloadProgress
override val referenceData: OSMReferenceData
get() = TODO("Not yet implemented")
get() = refData
?: throw UnsupportedOperationException("referenceData is only available once download is complete")
}

View File

@@ -25,6 +25,7 @@ import net.vonforst.evmap.api.goingelectric.GEReferenceData
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OCMReferenceData
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.api.openstreetmap.OSMReferenceData
import net.vonforst.evmap.api.openstreetmap.OpenStreetMapApiWrapper
import net.vonforst.evmap.model.*
import net.vonforst.evmap.ui.cluster
@@ -144,15 +145,7 @@ class ChargeLocationsRepository(
}
is OpenStreetMapApiWrapper -> {
liveData<OCMReferenceData> {
emit(
OCMReferenceData(
emptyList(),
emptyList(),
emptyList()
)
)
} // TODO: add OSM reference data
OSMReferenceDataRepository(db.osmReferenceDataDao()).getReferenceData()
}
else -> {
@@ -552,6 +545,14 @@ class ChargeLocationsRepository(
true
)
)
when (api) {
is OpenStreetMapApiWrapper -> {
val refData = result.referenceData
OSMReferenceDataRepository(db.osmReferenceDataDao()).updateReferenceData(refData as OSMReferenceData)
}
}
} finally {
fullDownloadProgress.value = null
}

View File

@@ -33,6 +33,7 @@ import net.vonforst.evmap.model.*
OCMConnectionType::class,
OCMCountry::class,
OCMOperator::class,
OSMNetwork::class,
SavedRegion::class
], version = 23
)
@@ -51,6 +52,9 @@ abstract class AppDatabase : RoomDatabase() {
// OpenChargeMap API specific
abstract fun ocmReferenceDataDao(): OCMReferenceDataDao
// OpenStreetMap API specific
abstract fun osmReferenceDataDao(): OSMReferenceDataDao
companion object {
private lateinit var context: Context
private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
@@ -465,6 +469,7 @@ abstract class AppDatabase : RoomDatabase() {
override fun migrate(db: SupportSQLiteDatabase) {
// API openstreetmap added
db.execSQL("INSERT INTO `FilterProfile` (`dataSource`, `name`, `id`, `order`) VALUES ('openstreetmap', 'FILTERS_CUSTOM', $FILTERS_CUSTOM, 0)")
db.execSQL("CREATE TABLE IF NOT EXISTS `OSMNetwork` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))")
}
}
}

View File

@@ -0,0 +1,53 @@
package net.vonforst.evmap.storage
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.room.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.vonforst.evmap.api.openchargemap.*
import net.vonforst.evmap.api.openstreetmap.OSMReferenceData
import net.vonforst.evmap.viewmodel.Status
import java.time.Duration
import java.time.Instant
@Entity
data class OSMNetwork(@PrimaryKey val name: String)
@Dao
abstract class OSMReferenceDataDao {
// NETWORKS
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(vararg networks: OSMNetwork)
@Query("DELETE FROM osmnetwork")
abstract fun deleteAllNetworks()
@Transaction
open suspend fun updateNetworks(networks: List<OSMNetwork>) {
deleteAllNetworks()
for (network in networks) {
insert(network)
}
}
@Query("SELECT * FROM osmnetwork")
abstract fun getAllNetworks(): LiveData<List<OSMNetwork>>
}
class OSMReferenceDataRepository(private val dao: OSMReferenceDataDao) {
fun getReferenceData(): LiveData<OSMReferenceData> {
val networks = dao.getAllNetworks()
return MediatorLiveData<OSMReferenceData>().apply {
value = null
addSource(networks) { _ ->
val n = networks.value ?: return@addSource
value = OSMReferenceData(n.map { it.name })
}
}
}
suspend fun updateReferenceData(refData: OSMReferenceData) {
dao.updateNetworks(refData.networks.map { OSMNetwork(it) })
}
}