mirror of
https://github.com/ev-map/EVMap.git
synced 2026-04-23 23:57:08 -04:00
implement availability detector with NewMotion API
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.johan.evmap.api
|
||||
|
||||
import android.location.Location
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
@@ -37,11 +36,22 @@ suspend fun Call.await(): Response {
|
||||
}
|
||||
}
|
||||
|
||||
const val earthRadiusKm: Double = 6372.8
|
||||
|
||||
fun distanceBetween(
|
||||
startLatitude: Double, startLongitude: Double,
|
||||
endLatitude: Double, endLongitude: Double
|
||||
): Float {
|
||||
val distance = floatArrayOf(0f)
|
||||
Location.distanceBetween(startLatitude, startLongitude, endLatitude, endLongitude, distance)
|
||||
return distance[0]
|
||||
): Double {
|
||||
// see https://rosettacode.org/wiki/Haversine_formula#Java
|
||||
val dLat = Math.toRadians(endLatitude - startLatitude);
|
||||
val dLon = Math.toRadians(endLongitude - endLongitude);
|
||||
val originLat = Math.toRadians(startLatitude);
|
||||
val destinationLat = Math.toRadians(endLatitude);
|
||||
|
||||
val a = Math.pow(Math.sin(dLat / 2), 2.toDouble()) + Math.pow(
|
||||
Math.sin(dLon / 2),
|
||||
2.toDouble()
|
||||
) * Math.cos(originLat) * Math.cos(destinationLat);
|
||||
val c = 2 * Math.asin(Math.sqrt(a));
|
||||
return earthRadiusKm * c * 1000;
|
||||
}
|
||||
@@ -45,6 +45,37 @@ abstract class BaseAvailabilityDetector(private val client: OkHttpClient) : Avai
|
||||
}
|
||||
return filter.getOrNull(0)
|
||||
}
|
||||
|
||||
|
||||
protected fun matchChargepoints(
|
||||
connectors: Map<Long, Pair<Double, String>>,
|
||||
chargepoints: List<Chargepoint>
|
||||
): Map<Chargepoint, Set<Long>> {
|
||||
// iterate over each connector type
|
||||
val types = connectors.map { it.value.second }.distinct().toSet()
|
||||
val geTypes = chargepoints.map { it.type }.distinct().toSet()
|
||||
if (types != geTypes) throw AvailabilityDetectorException("chargepoints do not match")
|
||||
return types.flatMap { type ->
|
||||
// find connectors of this type
|
||||
val connsOfType = connectors.filter { it.value.second == type }
|
||||
// find powers this connector is available as
|
||||
val powers = connsOfType.map { it.value.first }.distinct().sorted()
|
||||
// find corresponding powers in GE data
|
||||
val gePowers =
|
||||
chargepoints.filter { it.type == type }.map { it.power }.distinct().sorted()
|
||||
|
||||
// if the distinct number of powers is the same, try to match.
|
||||
if (powers.size == gePowers.size) {
|
||||
gePowers.zip(powers).map { (gePower, power) ->
|
||||
val chargepoint = chargepoints.find { it.type == type && it.power == gePower }!!
|
||||
val ids = connsOfType.filter { it.value.first == power }.keys
|
||||
chargepoint to ids
|
||||
}
|
||||
} else {
|
||||
throw AvailabilityDetectorException("chargepoints do not match")
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
data class ChargeLocationStatus(
|
||||
|
||||
@@ -44,6 +44,7 @@ interface NewMotionApi {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMConnector(
|
||||
val uid: Long,
|
||||
val connectorType: String,
|
||||
val electricalProperties: NMElectricalProperties
|
||||
)
|
||||
@@ -72,9 +73,9 @@ interface NewMotionApi {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(client: OkHttpClient): NewMotionApi {
|
||||
fun create(client: OkHttpClient, baseUrl: String? = null): NewMotionApi {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://my.newmotion.com/api/map/v2/")
|
||||
.baseUrl(baseUrl ?: "https://my.newmotion.com/api/map/v2/")
|
||||
.addConverterFactory(MoshiConverterFactory.create())
|
||||
.client(client)
|
||||
.build()
|
||||
@@ -83,8 +84,9 @@ interface NewMotionApi {
|
||||
}
|
||||
}
|
||||
|
||||
class NewMotionAvailabilityDetector(client: OkHttpClient) : BaseAvailabilityDetector(client) {
|
||||
val api = NewMotionApi.create(client)
|
||||
class NewMotionAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
|
||||
BaseAvailabilityDetector(client) {
|
||||
val api = NewMotionApi.create(client, baseUrl)
|
||||
|
||||
override suspend fun getAvailability(location: ChargeLocation): ChargeLocationStatus {
|
||||
val lat = location.coordinates.lat
|
||||
@@ -121,8 +123,10 @@ class NewMotionAvailabilityDetector(client: OkHttpClient) : BaseAvailabilityDete
|
||||
}
|
||||
}
|
||||
|
||||
val chargepointStatus = mutableMapOf<Chargepoint, List<ChargepointStatus>>()
|
||||
val nmConnectors = mutableMapOf<Long, Pair<Double, String>>()
|
||||
val nmStatus = mutableMapOf<Long, ChargepointStatus>()
|
||||
connectorStatus.forEach { (connector, statusStr) ->
|
||||
val id = connector.uid
|
||||
val power = connector.electricalProperties.getPower()
|
||||
val type = when (connector.connectorType) {
|
||||
"Type2" -> Chargepoint.TYPE_2
|
||||
@@ -138,47 +142,18 @@ class NewMotionAvailabilityDetector(client: OkHttpClient) : BaseAvailabilityDete
|
||||
"Unspecified" -> ChargepointStatus.UNKNOWN
|
||||
else -> ChargepointStatus.UNKNOWN
|
||||
}
|
||||
|
||||
var chargepoint = getCorrespondingChargepoint(chargepointStatus.keys, type, power)
|
||||
val statusList: List<ChargepointStatus>
|
||||
if (chargepoint == null) {
|
||||
// find corresponding chargepoint from goingelectric to get correct power
|
||||
val geChargepoint =
|
||||
getCorrespondingChargepoint(location.chargepoints, type, power)
|
||||
?: throw AvailabilityDetectorException(
|
||||
"Chargepoints from NewMotion API and goingelectric do not match."
|
||||
)
|
||||
chargepoint = Chargepoint(
|
||||
type,
|
||||
geChargepoint.power,
|
||||
1
|
||||
)
|
||||
statusList = listOf(status)
|
||||
} else {
|
||||
val previousStatus = chargepointStatus[chargepoint]!!
|
||||
statusList = previousStatus + listOf(status)
|
||||
chargepointStatus.remove(chargepoint)
|
||||
chargepoint =
|
||||
Chargepoint(
|
||||
chargepoint.type,
|
||||
chargepoint.power,
|
||||
chargepoint.count + 1
|
||||
)
|
||||
}
|
||||
|
||||
chargepointStatus[chargepoint] = statusList
|
||||
nmConnectors.put(id, power to type)
|
||||
nmStatus.put(id, status)
|
||||
}
|
||||
|
||||
if (chargepointStatus.keys == location.chargepoints.toSet()) {
|
||||
return ChargeLocationStatus(
|
||||
chargepointStatus,
|
||||
"NewMotion"
|
||||
)
|
||||
} else {
|
||||
throw AvailabilityDetectorException(
|
||||
"Chargepoints from NewMotion API and goingelectric do not match."
|
||||
)
|
||||
val match = matchChargepoints(nmConnectors, location.chargepoints)
|
||||
val chargepointStatus = match.mapValues { entry ->
|
||||
entry.value.map { nmStatus[it]!! }
|
||||
}
|
||||
return ChargeLocationStatus(
|
||||
chargepointStatus,
|
||||
"NewMotion"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface GoingElectricApi {
|
||||
@GET("/chargepoints/")
|
||||
@GET("chargepoints/")
|
||||
fun getChargepoints(
|
||||
@Query("sw_lat") swlat: Double, @Query("sw_lng") sw_lng: Double,
|
||||
@Query("ne_lat") ne_lat: Double, @Query("ne_lng") ne_lng: Double,
|
||||
@@ -19,11 +19,14 @@ interface GoingElectricApi {
|
||||
@Query("cluster_distance") clusterDistance: Int
|
||||
): Call<ChargepointList>
|
||||
|
||||
@GET("/chargepoints/")
|
||||
@GET("chargepoints/")
|
||||
fun getChargepointDetail(@Query("ge_id") id: Long): Call<ChargepointList>
|
||||
|
||||
companion object {
|
||||
fun create(apikey: String): GoingElectricApi {
|
||||
fun create(
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.goingelectric.de"
|
||||
): GoingElectricApi {
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor { chain ->
|
||||
// add API key to every request
|
||||
@@ -42,7 +45,7 @@ interface GoingElectricApi {
|
||||
.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.goingelectric.de")
|
||||
.baseUrl(baseurl)
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.client(client)
|
||||
.build()
|
||||
|
||||
Reference in New Issue
Block a user