diff --git a/app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt b/app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt index 09ab3b56..abfe1bfa 100644 --- a/app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt +++ b/app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt @@ -130,6 +130,16 @@ data class ChargepointList(val items: List, 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 { val chargers: Sequence val progress: Float diff --git a/app/src/main/java/net/vonforst/evmap/api/openstreetmap/OpenStreetMapApi.kt b/app/src/main/java/net/vonforst/evmap/api/openstreetmap/OpenStreetMapApi.kt index 107ca2d8..937ef182 100644 --- a/app/src/main/java/net/vonforst/evmap/api/openstreetmap/OpenStreetMapApi.kt +++ b/app/src/main/java/net/vonforst/evmap/api/openstreetmap/OpenStreetMapApi.kt @@ -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) : ReferenceData() class OSMFullDownloadResult(private val body: OSMDocument) : FullDownloadResult { private var downloadProgress = 0f + private var refData: OSMReferenceData? = null + override val chargers: Sequence get() { val time = body.timestamp + val networks = mutableListOf() + 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") } diff --git a/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt b/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt index 792a1e85..25e7c39f 100644 --- a/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt +++ b/app/src/main/java/net/vonforst/evmap/storage/ChargeLocationsDao.kt @@ -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 { - 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 } diff --git a/app/src/main/java/net/vonforst/evmap/storage/Database.kt b/app/src/main/java/net/vonforst/evmap/storage/Database.kt index 988f6faa..95fe6a01 100644 --- a/app/src/main/java/net/vonforst/evmap/storage/Database.kt +++ b/app/src/main/java/net/vonforst/evmap/storage/Database.kt @@ -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`))") } } } diff --git a/app/src/main/java/net/vonforst/evmap/storage/OSMReferenceDataDao.kt b/app/src/main/java/net/vonforst/evmap/storage/OSMReferenceDataDao.kt new file mode 100644 index 00000000..c95d5a94 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/storage/OSMReferenceDataDao.kt @@ -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) { + deleteAllNetworks() + for (network in networks) { + insert(network) + } + } + + @Query("SELECT * FROM osmnetwork") + abstract fun getAllNetworks(): LiveData> +} + +class OSMReferenceDataRepository(private val dao: OSMReferenceDataDao) { + fun getReferenceData(): LiveData { + val networks = dao.getAllNetworks() + return MediatorLiveData().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) }) + } +} \ No newline at end of file