Skip to content

Commit

Permalink
basic implementation of OpenStreetMapApi
Browse files Browse the repository at this point in the history
  • Loading branch information
johan12345 committed Jul 2, 2023
1 parent ae33fce commit a916c32
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 55 deletions.
35 changes: 35 additions & 0 deletions app/src/main/java/net/vonforst/evmap/api/ChargepointApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.car2go.maps.model.LatLngBounds
import net.vonforst.evmap.R
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.api.openstreetmap.OpenStreetMapApiWrapper
import net.vonforst.evmap.model.*
import net.vonforst.evmap.viewmodel.Resource
import java.time.Duration
Expand Down Expand Up @@ -58,6 +59,34 @@ interface ChargepointApi<out T : ReferenceData> {
* Duration we are limited to if there is a required API local cache time limit.
*/
val cacheLimit: Duration

/**
* Whether this API supports querying for chargers at the backend
*
* This determines whether the getChargepoints, getChargepointsRadius and getChargepointDetail functions are supported.
*/
val supportsOnlineQueries: Boolean

/**
* Whether this API supports downloading the whole dataset into local storage
*
* This determines whether the getAllChargepoints function is supported.
*/
val supportsFullDownload: Boolean

/**
* Fetches all available chargers from this API.
*
* This may take a long time and should only be used when the user explicitly wants to download all chargers.
*
* TODO: add an optional callback parameter to this function to be able to receive updates on the download progress?
* TODO: Should this also include getting the ReferenceData, instead of taking it as an argument?
* ReferenceData typically includes information that is needed to create the filter options, e.g.
* mappings between IDs and readable names (for operators etc.). So probably for OSM it makes sense
* to generate that within this function (e.g. build the list of available operators using all the
* operators found in the dataset).
*/
suspend fun fullDownload(referenceData: ReferenceData): List<ChargeLocation>
}

interface StringProvider {
Expand All @@ -79,13 +108,19 @@ fun createApi(type: String, ctx: Context): ChargepointApi<ReferenceData> {
)
)
}

"goingelectric" -> {
GoingElectricApiWrapper(
ctx.getString(
R.string.goingelectric_key
)
)
}

"openstreetmap" -> {
OpenStreetMapApiWrapper()
}

else -> throw IllegalArgumentException()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ class GoingElectricApiWrapper(
override val name = "GoingElectric.de"
override val id = "goingelectric"
override val cacheLimit = Duration.ofDays(1)
override val supportsOnlineQueries = true
override val supportsFullDownload = false

override suspend fun fullDownload(referenceData: ReferenceData): List<ChargeLocation> {
throw NotImplementedError()
}

override suspend fun getChargepoints(
referenceData: ReferenceData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ class OpenChargeMapApiWrapper(

override val name = "OpenChargeMap.org"
override val id = "openchargemap"
override val supportsOnlineQueries = true
override val supportsFullDownload = false

override suspend fun fullDownload(referenceData: ReferenceData): List<ChargeLocation> {
throw NotImplementedError()
}

private fun formatMultipleChoice(value: MultipleChoiceFilterValue?) =
if (value == null || value.all) null else value.values.joinToString(",")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.vonforst.evmap.api.openstreetmap

import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import java.time.Instant
import kotlin.math.floor

internal class InstantAdapter {
@FromJson
fun fromJson(value: Double?): Instant? = value?.let {
val seconds = floor(it).toLong()
val nanos = ((value - seconds) * 1e9).toLong()
Instant.ofEpochSecond(seconds, nanos)
}

@ToJson
fun toJson(value: Instant?): Double? = value?.let {
it.epochSecond.toDouble() + it.nano / 1e9
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package net.vonforst.evmap.api.openstreetmap

import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import com.squareup.moshi.Moshi
import net.vonforst.evmap.BuildConfig
import net.vonforst.evmap.addDebugInterceptors
import net.vonforst.evmap.api.ChargepointApi
import net.vonforst.evmap.api.ChargepointList
import net.vonforst.evmap.api.FiltersSQLQuery
import net.vonforst.evmap.api.StringProvider
import net.vonforst.evmap.api.openchargemap.ZonedDateTimeAdapter
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Filter
import net.vonforst.evmap.model.FilterValue
import net.vonforst.evmap.model.FilterValues
import net.vonforst.evmap.model.ReferenceData
import net.vonforst.evmap.viewmodel.Resource
import okhttp3.OkHttpClient
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import java.io.IOException
import java.time.Duration
import java.time.Instant

interface OpenStreetMapApi {
@GET("charging-stations-osm.json")
suspend fun getAllChargingStations(): Response<OSMDocument>

companion object {
private val moshi = Moshi.Builder()
.add(ZonedDateTimeAdapter())
.add(InstantAdapter())
.build()

fun create(
baseurl: String = "https://evmap-dev.vonforst.net"
): OpenStreetMapApi {
val client = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) addDebugInterceptors()
}.build()

val retrofit = Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(client)
.build()
return retrofit.create(OpenStreetMapApi::class.java)
}
}

}

class OpenStreetMapApiWrapper(baseurl: String = "https://evmap-dev.vonforst.net") :
ChargepointApi<OSMReferenceData> {
override val name = "OpenStreetMap"
override val id = "openstreetmap"
override val cacheLimit = Duration.ofDays(300L)
override val supportsOnlineQueries = false
override val supportsFullDownload = true

val api = OpenStreetMapApi.create(baseurl)

override suspend fun getChargepoints(
referenceData: ReferenceData,
bounds: LatLngBounds,
zoom: Float,
useClustering: Boolean,
filters: FilterValues?
): Resource<ChargepointList> {
throw NotImplementedError()
}

override suspend fun getChargepointsRadius(
referenceData: ReferenceData,
location: LatLng,
radius: Int,
zoom: Float,
useClustering: Boolean,
filters: FilterValues?
): Resource<ChargepointList> {
throw NotImplementedError()
}

override suspend fun getChargepointDetail(
referenceData: ReferenceData,
id: Long
): Resource<ChargeLocation> {
throw NotImplementedError()
}

override suspend fun getReferenceData(): Resource<OSMReferenceData> {
TODO("Not yet implemented")
}

override fun getFilters(
referenceData: ReferenceData,
sp: StringProvider
): List<Filter<FilterValue>> {
return emptyList()
}

override fun convertFiltersToSQL(
filters: FilterValues,
referenceData: ReferenceData
): FiltersSQLQuery {
TODO("Not yet implemented")
}

override fun filteringInSQLRequiresDetails(filters: FilterValues): Boolean {
return true
}

override suspend fun fullDownload(referenceData: ReferenceData): List<ChargeLocation> {
val response = api.getAllChargingStations()
if (!response.isSuccessful) {
throw IOException(response.message())
} else {
val body = response.body()!!
val time = body.timestamp
return body.elements.map { it.convert(time) }
}
}
}

data class OSMReferenceData(val test: String) : ReferenceData()
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ private val SOCKET_TYPES = immutableListOf(
OsmSocket("sev1011_t25", null),
)

@JsonClass(generateAdapter = true)
data class OSMDocument(
val timestamp: Instant,
val elements: List<OSMChargingStation>
)

@JsonClass(generateAdapter = true)
data class OSMChargingStation(
// Unique numeric ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class DataSourceSelectDialog : MaterialDialogFragment() {
when (prefs.dataSource) {
"goingelectric" -> binding.rgDataSource.rbGoingElectric.isChecked = true
"openchargemap" -> binding.rgDataSource.rbOpenChargeMap.isChecked = true
"openstreetmap" -> binding.rgDataSource.rbOpenStreetMap.isChecked = true
}
}

Expand All @@ -65,6 +66,8 @@ class DataSourceSelectDialog : MaterialDialogFragment() {
"goingelectric"
} else if (binding.rgDataSource.rbOpenChargeMap.isChecked) {
"openchargemap"
} else if (binding.rgDataSource.rbOpenStreetMap.isChecked) {
"openstreetmap"
} else {
return@setOnClickListener
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class DataSourceSelectFragment : OnboardingPageFragment() {
when (prefs.dataSource) {
"goingelectric" -> binding.rgDataSource.rbGoingElectric.isChecked = true
"openchargemap" -> binding.rgDataSource.rbOpenChargeMap.isChecked = true
"openstreetmap" -> binding.rgDataSource.rbOpenStreetMap.isChecked = true
}

binding.btnGetStarted.setOnClickListener {
Expand All @@ -263,6 +264,8 @@ class DataSourceSelectFragment : OnboardingPageFragment() {
"goingelectric"
} else if (binding.rgDataSource.rbOpenChargeMap.isChecked) {
"openchargemap"
} else if (binding.rgDataSource.rbOpenStreetMap.isChecked) {
"openstreetmap"
} else {
return@setOnClickListener
}
Expand Down
Loading

0 comments on commit a916c32

Please sign in to comment.