mirror of
https://github.com/PranshulGG/WeatherMaster.git
synced 2026-10-22 02:52:37 -05:00
WMO Severe Weather added as alert source
This commit is contained in:
1 parent
45ad8a2011
commit
abfca933cd
9 files changed
+295
-1
No files matched your search
@@ -6,6 +6,7 @@ import com.pranshulgg.weather_master_app.core.network.sources.airquality.accu.Ac
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.airquality.openmeteo.OpenMeteoAqiApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.accu.AccuSearchApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.geonames.GeoNamesSearchApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.search.geonames.timezone.GeoNamesTimezoneApi
|
||||
@@ -124,4 +125,8 @@ object ApiModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAccuSearchApi(): AccuSearchApi = AccuSearchApi.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWmoSevereWeatherAlertsApi(): WmoSevereWeatherApi = WmoSevereWeatherApi.create()
|
||||
}
|
||||
+9
@@ -8,6 +8,8 @@ import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.Alerts
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.accu.AccuApi
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.accu.AccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.weather.bmkg.BmkgApi
|
||||
@@ -189,4 +191,11 @@ object WeatherRepositoryModule {
|
||||
locationKeysDao: LocationKeysDao
|
||||
): IpmaRepository = IpmaRepository(dao, weatherDao, api, locationKeysDao)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWmoSevereWeatherAlertsRepository(
|
||||
api: WmoSevereWeatherApi,
|
||||
dao: AlertsDao
|
||||
): WmoSevereWeatherRepository = WmoSevereWeatherRepository(api, dao)
|
||||
|
||||
}
|
||||
@@ -18,5 +18,10 @@ enum class AlertSource(
|
||||
fullName = "WeatherApi.com",
|
||||
displayLink = "https://www.weatherapi.com/"
|
||||
),
|
||||
WMO_SEVERE_WEATHER(
|
||||
displayName = "WMO Severe Weather",
|
||||
fullName = "WMO Severe Weather Information Centre",
|
||||
displayLink = "https://www.wmo.int/"
|
||||
)
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.json.WmoSevereWeatherJson
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
interface WmoSevereWeatherApi {
|
||||
|
||||
@GET("f/wfs")
|
||||
suspend fun fetchAlerts(
|
||||
@Query("typeName") typeName: String = "local_postgis:postgis_geojsons",
|
||||
@Query("cql_filter") cqlFilter: String,
|
||||
@Query("version") version: String = "1.1.0",
|
||||
@Query("outputFormat") outputFormat: String = "json",
|
||||
@Query("request") request: String = "GetFeature",
|
||||
): Response<WmoSevereWeatherJson>
|
||||
|
||||
@GET("v2/cap-alerts/{url}")
|
||||
suspend fun fetchAlertsXml(
|
||||
@Path(value = "url", encoded = true) url: String
|
||||
): Response<ResponseBody>
|
||||
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://severeweather.wmo.int/"
|
||||
|
||||
fun create(): WmoSevereWeatherApi {
|
||||
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(WmoSevereWeatherApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather
|
||||
|
||||
import android.util.Xml
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.AppException
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.location.Location
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.alerts.AlertResult
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.alerts.AlertResultType
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.model.WmoCapAlert
|
||||
import com.pranshulgg.weather_master_app.core.utils.weather.cache.shouldReturnAlertsCache
|
||||
import com.pranshulgg.weather_master_app.data.local.dao.alerts.AlertsDao
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.alerts.sources.wmosevereweather.wmoSevereWeatherAlertsMapper
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.alerts.toDomain
|
||||
import com.pranshulgg.weather_master_app.data.local.mapper.alerts.toEntity
|
||||
import com.pranshulgg.weather_master_app.data.repository.AlertRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
class WmoSevereWeatherRepository @Inject constructor(
|
||||
private val api: WmoSevereWeatherApi,
|
||||
private val dao: AlertsDao
|
||||
) : AlertRepository {
|
||||
override suspend fun getAlerts(
|
||||
location: Location,
|
||||
isManualRefresh: Boolean,
|
||||
isForceRefresh: Boolean
|
||||
): AlertResult = withContext(Dispatchers.IO) {
|
||||
|
||||
val cache = dao.getAlertsForLocation(location.id)
|
||||
val shouldReturnCache = shouldReturnAlertsCache(cache, isManualRefresh, isForceRefresh)
|
||||
|
||||
when (shouldReturnCache) {
|
||||
AlertResultType.RETURN_CACHE -> return@withContext AlertResult.Success(cache.map { it!!.toDomain() })
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val cqlFilter =
|
||||
"INTERSECTS(wkb_geometry, POINT (${location.latitude} ${location.longitude})) AND row_type NEQ 'BOUNDARY'"
|
||||
|
||||
return@withContext try {
|
||||
|
||||
val response = api.fetchAlerts(cqlFilter = cqlFilter)
|
||||
|
||||
|
||||
val body = response.body()
|
||||
?: return@withContext AlertResult.Error(exception = AppException.Unknown())
|
||||
|
||||
|
||||
val alertsWithCap = body.features.filter {
|
||||
it.properties != null
|
||||
}.map { feature ->
|
||||
val url = feature.properties?.capUrl ?: feature.properties?.rLink
|
||||
|
||||
val capAlert = if (!url.isNullOrBlank()) {
|
||||
val responseAlert = api.fetchAlertsXml(url)
|
||||
|
||||
if (responseAlert.isSuccessful) {
|
||||
responseAlert.body()?.byteStream()?.use { stream ->
|
||||
parseAlertXmlBody(stream)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
feature to capAlert
|
||||
}
|
||||
|
||||
|
||||
val domain = wmoSevereWeatherAlertsMapper(alertsWithCap, location.id)
|
||||
|
||||
dao.insertAlerts(
|
||||
domain.map { it.toEntity(location.id) },
|
||||
location.id
|
||||
)
|
||||
|
||||
AlertResult.Success(domain)
|
||||
|
||||
} catch (e: Exception) {
|
||||
AlertResult.Error(exception = e, cacheAlerts = cache.map { it!!.toDomain() })
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAlertXmlBody(stream: InputStream): WmoCapAlert {
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setInput(stream, null)
|
||||
|
||||
|
||||
var type = parser.eventType
|
||||
|
||||
var language: String? = null
|
||||
var event: String? = null
|
||||
var severity: String? = null
|
||||
var effective: String? = null
|
||||
var expires: String? = null
|
||||
var senderName: String? = null
|
||||
var description: String? = null
|
||||
var headline: String? = null
|
||||
|
||||
while (type != XmlPullParser.END_DOCUMENT) {
|
||||
|
||||
when (type) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
when (parser.name) {
|
||||
"language" -> language = parser.nextText()
|
||||
"event" -> event = parser.nextText()
|
||||
"severity" -> severity = parser.nextText()
|
||||
"effective" -> effective = parser.nextText()
|
||||
"expires" -> expires = parser.nextText()
|
||||
"senderName" -> senderName = parser.nextText()
|
||||
"description" -> description = parser.nextText()
|
||||
"headline" -> headline = parser.nextText()
|
||||
}
|
||||
}
|
||||
}
|
||||
type = parser.next()
|
||||
}
|
||||
return WmoCapAlert(
|
||||
language = language,
|
||||
event = event,
|
||||
severity = severity,
|
||||
effective = effective,
|
||||
expires = expires,
|
||||
senderName = senderName,
|
||||
headline = headline,
|
||||
description = description
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.json
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class WmoSevereWeatherJson(
|
||||
val features: List<WmoSevereWeatherFeatureJson>
|
||||
)
|
||||
|
||||
data class WmoSevereWeatherFeatureJson(
|
||||
val properties: WmoSevereWeatherFeaturePropertiesJson?
|
||||
)
|
||||
|
||||
|
||||
data class WmoSevereWeatherFeaturePropertiesJson(
|
||||
@SerializedName("capurl") val capUrl: String?,
|
||||
@SerializedName("rlink") val rLink: String?,
|
||||
val description: String?,
|
||||
val event: String?,
|
||||
val effective: String?,
|
||||
val expires: String?,
|
||||
@SerializedName("s") val severity: Int?,
|
||||
val headline: String?
|
||||
)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.model
|
||||
|
||||
data class WmoCapAlert(
|
||||
val language: String?,
|
||||
val event: String?,
|
||||
val severity: String?,
|
||||
val effective: String?,
|
||||
val expires: String?,
|
||||
val senderName: String?,
|
||||
val headline: String?,
|
||||
val description: String?
|
||||
)
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.pranshulgg.weather_master_app.data.local.mapper.alerts.sources.wmosevereweather
|
||||
|
||||
import com.pranshulgg.weather_master_app.core.model.domain.alerts.Alert
|
||||
import com.pranshulgg.weather_master_app.core.model.weather.alerts.AlertSeverity
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.json.WmoSevereWeatherFeatureJson
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.model.WmoCapAlert
|
||||
import com.pranshulgg.weather_master_app.core.utils.extensions.DateTimeExtensions.iso8601TimestampToMilliseconds
|
||||
|
||||
|
||||
fun wmoSevereWeatherAlertsMapper(
|
||||
alerts: List<Pair<WmoSevereWeatherFeatureJson, WmoCapAlert?>>,
|
||||
locationId: String
|
||||
): List<Alert> {
|
||||
|
||||
|
||||
return alerts.filter {
|
||||
!it.first.properties?.event.isNullOrBlank()
|
||||
|| !it.first.properties?.headline.isNullOrBlank()
|
||||
|| !it.second?.event.isNullOrBlank()
|
||||
|| !it.second?.headline.isNullOrBlank()
|
||||
}.map { (feature, capAlert) ->
|
||||
|
||||
|
||||
Alert(
|
||||
locationId = locationId,
|
||||
event = capAlert?.event ?: capAlert?.headline ?: feature.properties?.event
|
||||
?: feature.properties?.headline ?: "",
|
||||
severity = getSeverity(feature.properties?.severity),
|
||||
effective = capAlert?.effective?.iso8601TimestampToMilliseconds()
|
||||
?: feature.properties?.effective?.iso8601TimestampToMilliseconds(),
|
||||
expires = capAlert?.expires?.iso8601TimestampToMilliseconds()
|
||||
?: feature.properties?.expires?.iso8601TimestampToMilliseconds(),
|
||||
description = capAlert?.description ?: feature.properties?.description ?: "",
|
||||
lastUpdatedInMilli = System.currentTimeMillis(),
|
||||
source = capAlert?.senderName ?: "WMO Severe Weather"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSeverity(code: Int?): AlertSeverity {
|
||||
return when (code) {
|
||||
4 -> AlertSeverity.CRITICAL
|
||||
3 -> AlertSeverity.HIGH
|
||||
2 -> AlertSeverity.MODERATE
|
||||
1 -> AlertSeverity.LOW
|
||||
else -> AlertSeverity.UNKNOWN
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -3,19 +3,22 @@ package com.pranshulgg.weather_master_app.data.provider
|
||||
import com.pranshulgg.weather_master_app.core.model.sources.AlertSource
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.accu.AlertsAccuRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.weatherapi.AlertsWeatherApiRepository
|
||||
import com.pranshulgg.weather_master_app.core.network.sources.alerts.wmosevereweather.WmoSevereWeatherRepository
|
||||
import com.pranshulgg.weather_master_app.data.repository.AlertRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class AlertsRepositoryProvider @Inject constructor(
|
||||
private val alertsAccuRepository: AlertsAccuRepository,
|
||||
private val alertsWeatherApiRepository: AlertsWeatherApiRepository
|
||||
private val alertsWeatherApiRepository: AlertsWeatherApiRepository,
|
||||
private val wmoSevereWeatherRepository: WmoSevereWeatherRepository
|
||||
) {
|
||||
|
||||
fun getRepository(source: AlertSource): AlertRepository? {
|
||||
return when (source) {
|
||||
AlertSource.ACCU_WEATHER -> alertsAccuRepository
|
||||
AlertSource.WEATHER_API -> alertsWeatherApiRepository
|
||||
AlertSource.WMO_SEVERE_WEATHER -> wmoSevereWeatherRepository
|
||||
AlertSource.NONE -> null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user