Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-21 15:11:39 +01:00
parent d6b5d53060
commit d90a1dc8df
2145 changed files with 210227 additions and 2 deletions

1
domain/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

21
domain/build.gradle.kts Normal file
View file

@ -0,0 +1,21 @@
plugins {
id("breezy.library")
kotlin("android")
kotlin("plugin.serialization")
}
android {
namespace = "breezyweather.domain"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
}
dependencies {
implementation(projects.weatherUnit)
implementation(libs.kotlinx.serialization.json)
api(libs.sqldelight.android.paging)
}

View file

21
domain/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View file

@ -0,0 +1,385 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.location.model
import android.os.Parcel
import android.os.Parcelable
import breezyweather.domain.weather.model.Weather
import java.util.Locale
import java.util.TimeZone
data class Location(
val latitude: Double = 0.0,
val longitude: Double = 0.0,
val timeZone: TimeZone = TimeZone.getTimeZone("GMT"),
val country: String = "",
val countryCode: String? = null,
val admin1: String? = null,
val admin1Code: String? = null,
val admin2: String? = null,
val admin2Code: String? = null,
val admin3: String? = null,
val admin3Code: String? = null,
val admin4: String? = null,
val admin4Code: String? = null,
val city: String = "",
val cityId: String? = null,
val district: String? = null,
val needsGeocodeRefresh: Boolean = false,
val customName: String? = null,
val weather: Weather? = null,
val forecastSource: String = "openmeteo",
val currentSource: String? = null,
val airQualitySource: String? = null,
val pollenSource: String? = null,
val minutelySource: String? = null,
val alertSource: String? = null,
val normalsSource: String? = null,
val reverseGeocodingSource: String? = null,
val isCurrentPosition: Boolean = false,
/**
* "accu": {"cityId": "230"}
* "nws": {"gridId": "8", "gridX": "20", "gridY": "30"}
* etc
*/
val parameters: Map<String, Map<String, String>> = emptyMap(),
) : Parcelable {
val formattedId: String
get() = if (isCurrentPosition) {
CURRENT_POSITION_ID
} else {
String.format(Locale.US, "%f", latitude) +
"&" +
String.format(Locale.US, "%f", longitude) +
"&" +
forecastSource
}
val isUsable: Boolean
// Sorry people living exactly at 0,0
get() = latitude != 0.0 || longitude != 0.0
val isTimeZoneInvalid: Boolean
get() = timeZone.id == "GMT"
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(cityId)
parcel.writeDouble(latitude)
parcel.writeDouble(longitude)
parcel.writeString(timeZone.id)
parcel.writeString(customName)
parcel.writeString(country)
parcel.writeString(countryCode)
parcel.writeString(admin1)
parcel.writeString(admin1Code)
parcel.writeString(admin2)
parcel.writeString(admin2Code)
parcel.writeString(admin3)
parcel.writeString(admin3Code)
parcel.writeString(admin4)
parcel.writeString(admin4Code)
parcel.writeString(city)
parcel.writeString(district)
parcel.writeString(forecastSource)
parcel.writeString(currentSource)
parcel.writeString(airQualitySource)
parcel.writeString(pollenSource)
parcel.writeString(minutelySource)
parcel.writeString(alertSource)
parcel.writeString(normalsSource)
parcel.writeString(reverseGeocodingSource)
parcel.writeByte(if (isCurrentPosition) 1 else 0)
parcel.writeByte(if (needsGeocodeRefresh) 1 else 0)
}
override fun describeContents() = 0
constructor(parcel: Parcel) : this(
cityId = parcel.readString(),
latitude = parcel.readDouble(),
longitude = parcel.readDouble(),
timeZone = TimeZone.getTimeZone(parcel.readString()!!),
customName = parcel.readString(),
country = parcel.readString()!!,
countryCode = parcel.readString(),
admin1 = parcel.readString(),
admin1Code = parcel.readString(),
admin2 = parcel.readString(),
admin2Code = parcel.readString(),
admin3 = parcel.readString(),
admin3Code = parcel.readString(),
admin4 = parcel.readString(),
admin4Code = parcel.readString(),
city = parcel.readString()!!,
district = parcel.readString(),
forecastSource = parcel.readString()!!,
currentSource = parcel.readString(),
airQualitySource = parcel.readString(),
pollenSource = parcel.readString(),
minutelySource = parcel.readString(),
alertSource = parcel.readString(),
normalsSource = parcel.readString(),
reverseGeocodingSource = parcel.readString(),
isCurrentPosition = parcel.readByte() != 0.toByte(),
needsGeocodeRefresh = parcel.readByte() != 0.toByte()
)
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || other !is Location) {
return false
}
if (formattedId != other.formattedId) {
return false
}
if (customName != other.customName) {
return false
}
if (forecastSource != other.forecastSource) {
return false
}
if (currentSource != other.currentSource) {
return false
}
if (airQualitySource != other.airQualitySource) {
return false
}
if (pollenSource != other.pollenSource) {
return false
}
if (minutelySource != other.minutelySource) {
return false
}
if (alertSource != other.alertSource) {
return false
}
if (normalsSource != other.normalsSource) {
return false
}
if (needsGeocodeRefresh != other.needsGeocodeRefresh) {
return false
}
if (parameters != other.parameters) {
return false
}
val thisWeather = weather
val otherWeather = other.weather
if (thisWeather == null && otherWeather == null) {
return true
}
return if (thisWeather != null && otherWeather != null) {
thisWeather.base.refreshTime?.time == otherWeather.base.refreshTime?.time
} else {
false
}
}
override fun hashCode(): Int {
return formattedId.hashCode()
}
override fun toString(): String {
val builder = StringBuilder("$country $admin1 $admin2 $admin3 $admin4")
if (admin4 != city && city.isNotEmpty()) {
builder.append(" ").append(city)
}
if (city != district && !district.isNullOrEmpty()) {
builder.append(" ").append(district)
}
return builder.toString()
}
fun administrationLevels(): String {
val builder = StringBuilder()
if (country.isNotEmpty()) {
builder.append(country)
}
if (!admin1.isNullOrEmpty()) {
if (builder.toString().isNotEmpty()) {
builder.append(", ")
}
builder.append(admin1)
}
if (!admin2.isNullOrEmpty()) {
if (builder.toString().isNotEmpty()) {
builder.append(", ")
}
builder.append(admin2)
}
if (!admin3.isNullOrEmpty() && (!admin4.isNullOrEmpty() || admin3 != city)) {
if (builder.toString().isNotEmpty()) {
builder.append(", ")
}
builder.append(admin3)
}
if (!admin4.isNullOrEmpty() && admin4 != city) {
if (builder.toString().isNotEmpty()) {
builder.append(", ")
}
builder.append(admin4)
}
return builder.toString()
}
val cityAndDistrict: String
get() {
val builder = StringBuilder()
if (city.isNotEmpty()) {
builder.append(city)
}
if (!district.isNullOrEmpty() && district != city) {
if (builder.toString().isNotEmpty()) {
builder.append(", ")
}
builder.append(district)
}
return builder.toString()
}
fun toLocationWithAddressInfo(
currentLocale: Locale,
locationAddressInfo: LocationAddressInfo,
overwriteCoordinates: Boolean,
): Location {
return copy(
latitude = if (overwriteCoordinates &&
locationAddressInfo.latitude != null &&
locationAddressInfo.longitude != null
) {
locationAddressInfo.latitude
} else {
latitude
},
longitude = if (overwriteCoordinates &&
locationAddressInfo.latitude != null &&
locationAddressInfo.longitude != null
) {
locationAddressInfo.longitude
} else {
longitude
},
timeZone = TimeZone.getTimeZone(locationAddressInfo.timeZoneId ?: "GMT"),
country = if (locationAddressInfo.country.isNullOrEmpty() && !locationAddressInfo.country.isNullOrEmpty()) {
Locale.Builder()
.setLanguage(currentLocale.language)
.setRegion(countryCode)
.build()
.displayCountry
} else {
locationAddressInfo.country ?: ""
},
countryCode = locationAddressInfo.countryCode,
admin1 = locationAddressInfo.admin1 ?: "",
admin1Code = locationAddressInfo.admin1Code ?: "",
admin2 = locationAddressInfo.admin2 ?: "",
admin2Code = locationAddressInfo.admin2Code ?: "",
admin3 = locationAddressInfo.admin3 ?: "",
admin3Code = locationAddressInfo.admin3Code ?: "",
admin4 = locationAddressInfo.admin4 ?: "",
admin4Code = locationAddressInfo.admin4Code ?: "",
city = locationAddressInfo.city ?: "",
cityId = locationAddressInfo.cityCode ?: "",
district = locationAddressInfo.district ?: "",
needsGeocodeRefresh = false
)
}
/**
* It is not intended to be used by the reverse geocoding source
* You're supposed to call the LocationAddressInfo constructor
*
* Currently only used by Natural Earth Service for a very special case
* Use with precaution!
*/
// @Delicate
fun toAddressInfo(): LocationAddressInfo {
return LocationAddressInfo(
latitude = latitude,
longitude = longitude,
timeZoneId = timeZone.id,
country = country,
countryCode = countryCode ?: "",
admin1 = admin1,
admin1Code = admin1Code,
admin2 = admin2,
admin2Code = admin2Code,
admin3 = admin3,
admin3Code = admin3Code,
admin4 = admin4,
admin4Code = admin4Code,
city = city,
cityCode = cityId,
district = district
)
}
val hasValidCountryCode: Boolean
get() {
return !countryCode.isNullOrEmpty() && countryCode.matches(Regex("[A-Za-z]{2}"))
}
companion object {
const val CURRENT_POSITION_ID = "CURRENT_POSITION"
const val CLOSE_DISTANCE = 5000 // 5 km
fun isEquals(a: String?, b: String?): Boolean {
return if (a.isNullOrEmpty() && b.isNullOrEmpty()) {
true
} else if (!a.isNullOrEmpty() && !b.isNullOrEmpty()) {
a == b
} else {
false
}
}
@JvmField
val CREATOR = object : Parcelable.Creator<Location> {
override fun createFromParcel(parcel: Parcel): Location {
return Location(parcel)
}
override fun newArray(size: Int): Array<Location?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,80 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.location.model
data class LocationAddressInfo(
/**
* Mandatory when used in the location search process
* In the reverse geocoding process, if provided, will throw an error if the returned location is too far away
* from the originally provided coordinates
*/
val latitude: Double? = null,
/**
* Mandatory when used in the location search process
* In the reverse geocoding process, if provided, will throw an error if the returned location is too far away
* from the originally provided coordinates
*/
val longitude: Double? = null,
/**
* Time zone of the location in the TZ identifier format
* Examples: America/New_York, Europe/Paris
*
* The list of accepted time zones can be found here:
* https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
*/
val timeZoneId: String? = null,
/**
* Leave null or empty to let the system translates the country name from the countryCode
*/
val country: String? = null,
/**
* Contrary to its name, this code represents not just countries, but also dependent territories, and special areas
* of geographical interest
* Must be a valid ISO 3166-1 alpha-2 code
* https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
*
* We don't support locations in the middle of the ocean, so ensure this is non empty.
*
* Will throw an error if it is not a valid 2 alpha letter code
*/
val countryCode: String,
val admin1: String? = null,
/**
* Can be an ISO 3166-2 code, or the internal code used by the country
*/
val admin1Code: String? = null,
val admin2: String? = null,
/**
* Can be an ISO 3166-2 code, or the internal code used by the country
*/
val admin2Code: String? = null,
val admin3: String? = null,
/**
* Can be an ISO 3166-2 code, or the internal code used by the country
*/
val admin3Code: String? = null,
val admin4: String? = null,
/**
* Can be an ISO 3166-2 code, or the internal code used by the country
*/
val admin4Code: String? = null,
val city: String? = null,
val cityCode: String? = null,
val district: String? = null,
)

View file

@ -0,0 +1,39 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.source
enum class SourceContinent(
val id: String,
) {
WORLDWIDE("worldwide"),
AFRICA("africa"),
ASIA("asia"),
EUROPE("europe"),
NORTH_AMERICA("north_america"),
OCEANIA("oceania"),
SOUTH_AMERICA("south_america"),
;
companion object {
fun getInstance(
value: String,
) = SourceContinent.entries.firstOrNull {
it.id == value
}
}
}

View file

@ -0,0 +1,40 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.source
enum class SourceFeature(
val id: String,
) {
FORECAST("forecast"),
CURRENT("current"),
AIR_QUALITY("airQuality"),
POLLEN("pollen"),
MINUTELY("minutely"),
ALERT("alert"),
NORMALS("normals"),
REVERSE_GEOCODING("reverseGeocoding"),
;
companion object {
fun getInstance(
value: String,
) = SourceFeature.entries.firstOrNull {
it.id == value
}
}
}

View file

@ -0,0 +1,43 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.pollutant.PollutantConcentration
import java.io.Serializable
/**
* Air Quality.
* AQI uses 2023 update of Plume AQI
* https://plumelabs.files.wordpress.com/2023/06/plume_aqi_2023.pdf
* For missing information, it uses WHO recommandations
* https://www.who.int/news-room/fact-sheets/detail/ambient-(outdoor)-air-quality-and-health
*/
class AirQuality(
val pM25: PollutantConcentration? = null,
val pM10: PollutantConcentration? = null,
val sO2: PollutantConcentration? = null,
val nO2: PollutantConcentration? = null,
val o3: PollutantConcentration? = null,
val cO: PollutantConcentration? = null,
) : Serializable {
val isValid: Boolean
get() = pM25 != null || pM10 != null || sO2 != null || nO2 != null || o3 != null || cO != null
val isIndexValid: Boolean
get() = pM25 != null || pM10 != null || nO2 != null || o3 != null
}

View file

@ -0,0 +1,61 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import android.graphics.Color
import androidx.annotation.ColorInt
import breezyweather.domain.weather.reference.AlertSeverity
import java.io.Serializable
import java.util.Date
/**
* Alert.
*
* All properties are [androidx.annotation.NonNull].
*/
data class Alert(
/**
* If not provided by the source, can be created from Objects.hash().toString()
* Usually, you will use three parameters: alert type or title, alert level, alert start time
*/
val alertId: String,
val startDate: Date? = null,
val endDate: Date? = null,
val headline: String? = null,
/**
* HTML accepted. For compatibility with non-HTML fields, line breaks (\n) will be replaced with <br />.
*/
val description: String? = null,
val instruction: String? = null,
val source: String? = null,
val severity: AlertSeverity = AlertSeverity.UNKNOWN,
@ColorInt val color: Int,
) : Serializable {
companion object {
@ColorInt
fun colorFromSeverity(severity: AlertSeverity): Int {
return when (severity) {
AlertSeverity.EXTREME -> Color.rgb(212, 45, 65)
AlertSeverity.SEVERE -> Color.rgb(240, 140, 17)
AlertSeverity.MODERATE -> Color.rgb(244, 207, 0)
AlertSeverity.MINOR -> Color.rgb(57, 156, 199)
else -> Color.rgb(130, 168, 223)
}
}
}
}

View file

@ -0,0 +1,42 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
import java.util.Date
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
class Astro(
val riseDate: Date? = null,
val setDate: Date? = null,
) : Serializable {
// Not made to be used for moon astro, only sun
val duration: Duration?
get() = if (riseDate == null || setDate == null) {
// Polar night
0.milliseconds
} else if (riseDate.after(setDate)) {
null
} else {
(setDate.time - riseDate.time).milliseconds
}
val isValid: Boolean
get() = riseDate != null && setDate != null
}

View file

@ -0,0 +1,37 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
import java.util.Date
/**
* Base.
*/
data class Base(
// val publishDate: Date = Date(),
val refreshTime: Date? = null,
val forecastUpdateTime: Date? = null,
val currentUpdateTime: Date? = null,
val airQualityUpdateTime: Date? = null,
val pollenUpdateTime: Date? = null,
val minutelyUpdateTime: Date? = null,
val alertsUpdateTime: Date? = null,
val normalsUpdateTime: Date? = null,
val normalsUpdateLatitude: Double = 0.0,
val normalsUpdateLongitude: Double = 0.0,
) : Serializable

View file

@ -0,0 +1,66 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.reference.WeatherCode
import breezyweather.domain.weather.wrappers.CurrentWrapper
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
/**
* Current.
*/
data class Current(
val weatherText: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: Temperature? = null,
val wind: Wind? = null,
val uV: UV? = null,
val airQuality: AirQuality? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: org.breezyweather.unit.temperature.Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
val ceiling: Distance? = null,
val dailyForecast: String? = null,
// Is actually a description of the nowcast
val hourlyForecast: String? = null,
) : Serializable {
fun toCurrentWrapper() = CurrentWrapper(
weatherText = this.weatherText,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperatureWrapper(),
wind = this.wind,
uV = uV ?: this.uV,
relativeHumidity = this.relativeHumidity,
dewPoint = this.dewPoint,
pressure = this.pressure,
cloudCover = this.cloudCover,
visibility = this.visibility,
ceiling = this.ceiling,
dailyForecast = this.dailyForecast,
hourlyForecast = this.hourlyForecast
)
}

View file

@ -0,0 +1,58 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.wrappers.DailyWrapper
import java.io.Serializable
import java.util.Date
import kotlin.time.Duration
/**
* Daily.
*/
data class Daily(
/**
* Daily date initialized at 00:00 in the TimeZone of the location
*/
val date: Date,
val day: HalfDay? = null,
val night: HalfDay? = null,
val degreeDay: DegreeDay? = null,
val sun: Astro? = null,
val twilight: Astro? = null,
val moon: Astro? = null,
val moonPhase: MoonPhase? = null,
val airQuality: AirQuality? = null,
val pollen: Pollen? = null,
val uV: UV? = null,
val sunshineDuration: Duration? = null,
val relativeHumidity: DailyRelativeHumidity? = null,
val dewPoint: DailyDewPoint? = null,
val pressure: DailyPressure? = null,
val cloudCover: DailyCloudCover? = null,
val visibility: DailyVisibility? = null,
) : Serializable {
fun toDailyWrapper() = DailyWrapper(
date = this.date,
day = this.day?.toHalfDayWrapper(),
night = this.night?.toHalfDayWrapper(),
degreeDay = this.degreeDay,
uV = this.uV,
sunshineDuration = this.sunshineDuration
)
}

View file

@ -0,0 +1,24 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
interface DailyAvgMinMax<T> {
val average: T?
val max: T?
val min: T?
val summary: String?
}

View file

@ -0,0 +1,26 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
data class DailyCloudCover(
override val average: Ratio? = null,
override val max: Ratio? = null,
override val min: Ratio? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Ratio>

View file

@ -0,0 +1,26 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.temperature.Temperature
data class DailyDewPoint(
override val average: Temperature? = null,
override val max: Temperature? = null,
override val min: Temperature? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Temperature>

View file

@ -0,0 +1,26 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.pressure.Pressure
data class DailyPressure(
override val average: Pressure? = null,
override val max: Pressure? = null,
override val min: Pressure? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Pressure>

View file

@ -0,0 +1,26 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
data class DailyRelativeHumidity(
override val average: Ratio? = null,
override val max: Ratio? = null,
override val min: Ratio? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Ratio>

View file

@ -0,0 +1,26 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.distance.Distance
data class DailyVisibility(
override val average: Distance? = null,
override val max: Distance? = null,
override val min: Distance? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Distance>

View file

@ -0,0 +1,30 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
/**
* Degree Day
*/
class DegreeDay(
val heating: org.breezyweather.unit.temperature.Temperature? = null,
val cooling: org.breezyweather.unit.temperature.Temperature? = null,
) : Serializable {
val isValid = (heating != null && heating.value > 0L) || (cooling != null && cooling.value > 0L)
}

View file

@ -0,0 +1,54 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.reference.WeatherCode
import breezyweather.domain.weather.wrappers.HalfDayWrapper
import java.io.Serializable
/**
* Half day.
*/
data class HalfDay(
/**
* A short description of the weather condition
*/
val weatherText: String? = null,
/**
* A long description of the weather condition. Used as a half-day summary
*/
val weatherSummary: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: Temperature? = null,
val precipitation: Precipitation? = null,
val precipitationProbability: PrecipitationProbability? = null,
val precipitationDuration: PrecipitationDuration? = null,
val wind: Wind? = null,
) : Serializable {
fun toHalfDayWrapper() = HalfDayWrapper(
weatherText = this.weatherText,
weatherSummary = this.weatherSummary,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperatureWrapper(),
precipitation = this.precipitation,
precipitationProbability = this.precipitationProbability,
precipitationDuration = this.precipitationDuration,
wind = this.wind
)
}

View file

@ -0,0 +1,68 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.reference.WeatherCode
import breezyweather.domain.weather.wrappers.HourlyWrapper
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
import java.util.Date
/**
* Hourly.
*/
data class Hourly(
val date: Date,
val isDaylight: Boolean = true,
val weatherText: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: Temperature? = null,
val precipitation: Precipitation? = null,
val precipitationProbability: PrecipitationProbability? = null,
val wind: Wind? = null,
val airQuality: AirQuality? = null,
val uV: UV? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: org.breezyweather.unit.temperature.Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
) : Serializable {
fun toHourlyWrapper() = HourlyWrapper(
date = this.date,
isDaylight = this.isDaylight,
weatherText = this.weatherText,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperatureWrapper(),
precipitation = this.precipitation,
precipitationProbability = this.precipitationProbability,
wind = this.wind,
uV = this.uV,
relativeHumidity = this.relativeHumidity,
dewPoint = this.dewPoint,
pressure = this.pressure,
cloudCover = this.cloudCover,
visibility = this.visibility
)
}

View file

@ -0,0 +1,63 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.precipitation.Precipitation
import java.io.Serializable
import java.util.Date
import kotlin.math.log10
import kotlin.math.pow
import kotlin.time.Duration.Companion.minutes
/**
* Minutely.
*/
data class Minutely(
val date: Date,
val minuteInterval: Int,
val precipitationIntensity: Precipitation? = null,
) : Serializable {
val dbz: Int?
get() = precipitationIntensityToDBZ(precipitationIntensity?.inMillimeters)
val endingDate: Date
get() = Date(date.time + minuteInterval.minutes.inWholeMilliseconds)
fun toValidOrNull(): Minutely? {
return copy(
precipitationIntensity = precipitationIntensity?.toValidHourlyOrNull()
)
}
companion object {
private fun precipitationIntensityToDBZ(intensity: Double?): Int? {
return intensity?.let {
(10.0 * log10(200.0 * Math.pow(intensity, 8.0 / 5.0))).toInt()
}
}
fun dbzToPrecipitationIntensity(dbz: Double?): Double? {
return if (dbz == null) {
null
} else {
if (dbz <= 5) 0.0 else (10.0.pow(dbz / 10.0) / 200.0).pow(5.0 / 8.0)
}
}
}
}

View file

@ -0,0 +1,33 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
/**
* Moon phase.
*/
class MoonPhase(
/**
* Angle between 0 to 360 (no negative! Add 180 if you have negative numbers)
*/
val angle: Int? = null,
) : Serializable {
val isValid: Boolean
get() = angle != null
}

View file

@ -0,0 +1,32 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.temperature.Temperature
import java.io.Serializable
/**
* Normals
*/
class Normals(
val daytimeTemperature: Temperature? = null,
val nighttimeTemperature: Temperature? = null,
) : Serializable {
val isValid: Boolean
get() = daytimeTemperature != null && nighttimeTemperature != null
}

View file

@ -0,0 +1,74 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.pollen.PollenConcentration
import java.io.Serializable
/**
* Pollen (tree, grass, weed) and mold
*/
class Pollen(
val alder: PollenConcentration? = null,
val ash: PollenConcentration? = null,
val birch: PollenConcentration? = null,
val chestnut: PollenConcentration? = null,
val cypress: PollenConcentration? = null,
val grass: PollenConcentration? = null,
val hazel: PollenConcentration? = null,
val hornbeam: PollenConcentration? = null,
val linden: PollenConcentration? = null,
val mold: PollenConcentration? = null, // Not a pollen, but probably relevant to put with them
val mugwort: PollenConcentration? = null,
val oak: PollenConcentration? = null,
val olive: PollenConcentration? = null,
val plane: PollenConcentration? = null,
val plantain: PollenConcentration? = null,
val poplar: PollenConcentration? = null,
val ragweed: PollenConcentration? = null,
val sorrel: PollenConcentration? = null,
val tree: PollenConcentration? = null,
val urticaceae: PollenConcentration? = null,
val willow: PollenConcentration? = null,
) : Serializable {
val isMoldValid: Boolean
get() = mold != null
val isValid: Boolean
get() = alder != null ||
ash != null ||
birch != null ||
chestnut != null ||
cypress != null ||
grass != null ||
hazel != null ||
hornbeam != null ||
linden != null ||
mold != null ||
mugwort != null ||
oak != null ||
olive != null ||
plane != null ||
plantain != null ||
poplar != null ||
ragweed != null ||
sorrel != null ||
tree != null ||
urticaceae != null ||
willow != null
}

View file

@ -0,0 +1,47 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
/**
* Precipitation.
*/
data class Precipitation(
val total: org.breezyweather.unit.precipitation.Precipitation? = null,
val thunderstorm: org.breezyweather.unit.precipitation.Precipitation? = null,
val rain: org.breezyweather.unit.precipitation.Precipitation? = null,
val snow: org.breezyweather.unit.precipitation.Precipitation? = null,
val ice: org.breezyweather.unit.precipitation.Precipitation? = null,
) : Serializable {
companion object {
// Based on India Meteorological Department day values (divided by two for half days)
// https://mausam.imd.gov.in/imd_latest/contents/pdf/forecasting_sop.pdf
const val PRECIPITATION_HALF_DAY_VERY_LIGHT = 1.25
const val PRECIPITATION_HALF_DAY_LIGHT = 7.75
const val PRECIPITATION_HALF_DAY_MEDIUM = 32.25
const val PRECIPITATION_HALF_DAY_HEAVY = 57.75
const val PRECIPITATION_HALF_DAY_RAINSTORM = 102.25
// Chapter 9.3.1 - Nowcasting
const val PRECIPITATION_HOURLY_LIGHT = 5.0
const val PRECIPITATION_HOURLY_MEDIUM = 10.0
const val PRECIPITATION_HOURLY_HEAVY = 15.0
const val PRECIPITATION_HOURLY_RAINSTORM = 20.0
}
}

View file

@ -0,0 +1,31 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
import kotlin.time.Duration
/**
* Precipitation duration.
*/
data class PrecipitationDuration(
val total: Duration? = null,
val thunderstorm: Duration? = null,
val rain: Duration? = null,
val snow: Duration? = null,
val ice: Duration? = null,
) : Serializable

View file

@ -0,0 +1,35 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
/**
* Precipitation duration.
*/
data class PrecipitationProbability(
val total: Ratio? = null,
val thunderstorm: Ratio? = null,
val rain: Ratio? = null,
val snow: Ratio? = null,
val ice: Ratio? = null,
) : Serializable {
val isValid: Boolean
get() = total != null && total.value > 0
}

View file

@ -0,0 +1,42 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.wrappers.TemperatureWrapper
import java.io.Serializable
/**
* Temperature.
*/
data class Temperature(
val temperature: org.breezyweather.unit.temperature.Temperature? = null,
val sourceFeelsLike: org.breezyweather.unit.temperature.Temperature? = null,
val computedApparent: org.breezyweather.unit.temperature.Temperature? = null,
val computedWindChill: org.breezyweather.unit.temperature.Temperature? = null,
val computedHumidex: org.breezyweather.unit.temperature.Temperature? = null,
) : Serializable {
val feelsLikeTemperature: org.breezyweather.unit.temperature.Temperature? = sourceFeelsLike
?: computedApparent
?: computedWindChill
?: computedHumidex
fun toTemperatureWrapper() = TemperatureWrapper(
temperature = this.temperature,
feelsLike = this.sourceFeelsLike
)
}

View file

@ -0,0 +1,46 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import java.io.Serializable
import kotlin.math.roundToInt
/**
* UV.
*/
class UV(
val index: Double? = null,
) : Serializable {
val isValid: Boolean
get() = index != null
companion object {
const val UV_INDEX_LOW = 3.0
const val UV_INDEX_MIDDLE = 6.0
const val UV_INDEX_HIGH = 8.0
const val UV_INDEX_EXCESSIVE = 11.0
val uvThresholds = listOf(
0,
UV_INDEX_LOW.roundToInt(),
UV_INDEX_MIDDLE.roundToInt(),
UV_INDEX_HIGH.roundToInt(),
UV_INDEX_EXCESSIVE.roundToInt()
)
}
}

View file

@ -0,0 +1,189 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import breezyweather.domain.weather.reference.Month
import breezyweather.domain.weather.wrappers.AirQualityWrapper
import breezyweather.domain.weather.wrappers.DailyWrapper
import breezyweather.domain.weather.wrappers.HourlyWrapper
import breezyweather.domain.weather.wrappers.PollenWrapper
import breezyweather.domain.weather.wrappers.WeatherWrapper
import org.breezyweather.unit.precipitation.Precipitation.Companion.micrometers
import java.io.Serializable
import java.util.Date
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
data class Weather(
val base: Base = Base(),
val current: Current? = null,
val dailyForecast: List<Daily> = emptyList(),
val hourlyForecast: List<Hourly> = emptyList(),
val minutelyForecast: List<Minutely> = emptyList(),
val alertList: List<Alert> = emptyList(),
val normals: Map<Month, Normals> = emptyMap(),
) : Serializable {
// Only hourly in the future, starting from current hour until the next 24 hours
val nextHourlyForecast = hourlyForecast.filter {
// Example: 15:01 -> starts at 15:00, 15:59 -> starts at 15:00
it.date.time >= System.currentTimeMillis() - 1.hours.inWholeMilliseconds &&
it.date.time < System.currentTimeMillis() + 24.hours.inWholeMilliseconds
}
// Only hourly in the future, starting from current hour until the end
val fullNextHourlyForecast = hourlyForecast.filter {
// Example: 15:01 -> starts at 15:00, 15:59 -> starts at 15:00
it.date.time >= System.currentTimeMillis() - 1.hours.inWholeMilliseconds
}
val todayIndex = dailyForecast.indexOfFirst {
it.date.time > Date().time - 1.days.inWholeMilliseconds
}.let { if (it == -1) null else it }
val today
get() = todayIndex?.let { dailyForecast.getOrNull(it) }
val tomorrow
get() = dailyForecast.firstOrNull {
it.date.time > Date().time
}
val dailyForecastStartingToday = if (todayIndex != null) {
dailyForecast.subList(todayIndex, dailyForecast.size)
} else {
emptyList()
}
fun isValid(pollingIntervalHours: Duration?): Boolean {
val updateTime = base.refreshTime?.time ?: 0
val currentTime = System.currentTimeMillis()
return pollingIntervalHours == null ||
(currentTime >= updateTime && currentTime - updateTime < pollingIntervalHours.inWholeMilliseconds)
}
val currentAlertList: List<Alert> = alertList
.filter {
(it.startDate == null && it.endDate == null) ||
(it.startDate != null && it.endDate != null && Date() in it.startDate..it.endDate) ||
(it.startDate == null && it.endDate != null && Date() < it.endDate) ||
(it.startDate != null && it.endDate == null && Date() > it.startDate)
}
val minutelyForecastBy5Minutes: List<Minutely>
get() {
return if (minutelyForecast.any { it.minuteInterval != 5 }) {
val newMinutelyList = mutableListOf<Minutely>()
if (minutelyForecast.any { it.minuteInterval == 1 }) {
// Lets assume 1-minute by 1-minute forecast are always 1-minute all along
for (i in minutelyForecast.indices step 5) {
newMinutelyList.add(
minutelyForecast[i].copy(
precipitationIntensity = doubleArrayOf(
minutelyForecast[i].precipitationIntensity?.inMicrometers ?: 0.0,
minutelyForecast.getOrNull(i + 1)?.precipitationIntensity?.inMicrometers ?: 0.0,
minutelyForecast.getOrNull(i + 2)?.precipitationIntensity?.inMicrometers ?: 0.0,
minutelyForecast.getOrNull(i + 3)?.precipitationIntensity?.inMicrometers ?: 0.0,
minutelyForecast.getOrNull(i + 4)?.precipitationIntensity?.inMicrometers ?: 0.0
).average().micrometers,
minuteInterval = 5
)
)
}
} else {
// Lets assume the other cases are divisible by 5
for (i in minutelyForecast.indices) {
if (minutelyForecast[i].minuteInterval == 5) {
newMinutelyList.add(minutelyForecast[i])
} else {
for (j in 0..<minutelyForecast[i].minuteInterval.div(5)) {
newMinutelyList.add(
minutelyForecast[i].copy(
date = Date(
minutelyForecast[i].date.time + (j.times(5)).minutes.inWholeMilliseconds
),
minuteInterval = 5
)
)
}
}
}
}
return newMinutelyList
} else {
minutelyForecast
}
}
fun toWeatherWrapper() = WeatherWrapper(
current = this.current?.toCurrentWrapper(),
dailyForecast = this.dailyForecast.map { it.toDailyWrapper() },
hourlyForecast = this.hourlyForecast.map { it.toHourlyWrapper() },
minutelyForecast = this.minutelyForecast,
alertList = this.alertList,
normals = this.normals
)
fun toDailyWrapperList(startDate: Date): List<DailyWrapper> {
return this.dailyForecast
.filter { it.date >= startDate }
.map { it.toDailyWrapper() }
}
fun toHourlyWrapperList(startDate: Date): List<HourlyWrapper> {
return this.hourlyForecast
.filter { it.date >= startDate }
.map { it.toHourlyWrapper() }
}
fun toAirQualityWrapperList(startDate: Date): AirQualityWrapper? {
val hourlyAirQuality = hourlyForecast.filter { it.date >= startDate && it.airQuality?.isValid == true }
val dailyAirQuality = dailyForecast.filter { it.date >= startDate && it.airQuality?.isValid == true }
val currentAirQuality = current?.airQuality
if (hourlyAirQuality.isEmpty() && dailyAirQuality.isEmpty()) return null
return AirQualityWrapper(
current = currentAirQuality,
dailyForecast = dailyAirQuality.associate { it.date to it.airQuality!! },
hourlyForecast = hourlyAirQuality.associate { it.date to it.airQuality!! }
)
}
fun toPollenWrapperList(startDate: Date): PollenWrapper? {
val dailyPollen = dailyForecast.filter { it.date >= startDate && it.pollen?.isValid == true }
if (dailyPollen.isEmpty()) return null
return PollenWrapper(
dailyForecast = dailyPollen.associate { it.date to it.pollen!! }
)
}
fun toMinutelyWrapper(): List<Minutely>? {
val now = Date()
return minutelyForecast
.filter { it.date >= now }
.let { if (it.size > 3) it else null }
}
fun toAlertsWrapper(): List<Alert> {
val now = Date()
return alertList.filter {
it.endDate == null || it.endDate.time > now.time
}
}
}

View file

@ -0,0 +1,63 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.model
import org.breezyweather.unit.speed.Speed
import java.io.Serializable
/**
* Wind
*/
data class Wind(
/**
* Between 0 and 360, or -1 if variable
*/
val degree: Double? = null,
/**
* In m/s
*/
val speed: Speed? = null,
/**
* In m/s
*/
val gusts: Speed? = null,
) : Serializable {
val isValid: Boolean
get() = speed != null && speed.value > 0
val arrow: String?
get() = when (degree) {
null -> null
-1.0 -> ""
in 22.5..67.5 -> ""
in 67.5..112.5 -> ""
in 112.5..157.5 -> ""
in 157.5..202.5 -> ""
in 202.5..247.5 -> ""
in 247.5..292.5 -> ""
in 292.5..337.5 -> ""
else -> ""
}
companion object {
fun validateDegree(degree: Double?): Double? {
return degree?.let { if (it == -1.0 || it in 0.0..360.0) it else null }
}
}
}

View file

@ -0,0 +1,34 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.reference
enum class AlertSeverity(val id: Int) {
EXTREME(4),
SEVERE(3),
MODERATE(2),
MINOR(1),
UNKNOWN(0),
;
companion object {
fun getInstance(
value: Int?,
): AlertSeverity = AlertSeverity.entries.firstOrNull {
it.id == value
} ?: UNKNOWN
}
}

View file

@ -0,0 +1,217 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.reference
import java.text.DateFormatSymbols
import java.util.Locale
/**
* Lightweight reimplementation of java.time.Month only available in SDK >= 26
*
* A month-of-year, such as 'July'.
* <p>
* {@code Month} is an enum representing the 12 months of the year -
* January, February, March, April, May, June, July, August, September, October,
* November and December.
* <p>
* In addition to the textual enum name, each month-of-year has an {@code int} value.
* The {@code int} value follows normal usage and the ISO-8601 standard,
* from 1 (January) to 12 (December). It is recommended that applications use the enum
* rather than the {@code int} value to ensure code clarity.
* <p>
* <b>Do not use {@code ordinal()} to obtain the numeric representation of {@code Month}.
* Use {@code getValue()} instead.</b>
* <p>
* This enum represents a common concept that is found in many calendar systems.
* As such, this enum may be used by any calendar system that has the month-of-year
* concept defined exactly equivalent to the ISO-8601 calendar system.
*/
enum class Month {
/**
* The singleton instance for the month of January.
* This has the numeric value of {@code 1}.
*/
JANUARY,
/**
* The singleton instance for the month of February.
* This has the numeric value of {@code 2}.
*/
FEBRUARY,
/**
* The singleton instance for the month of March.
* This has the numeric value of {@code 3}.
*/
MARCH,
/**
* The singleton instance for the month of April.
* This has the numeric value of {@code 4}.
*/
APRIL,
/**
* The singleton instance for the month of May.
* This has the numeric value of {@code 5}.
*/
MAY,
/**
* The singleton instance for the month of June.
* This has the numeric value of {@code 6}.
*/
JUNE,
/**
* The singleton instance for the month of July.
* This has the numeric value of {@code 7}.
*/
JULY,
/**
* The singleton instance for the month of August.
* This has the numeric value of {@code 8}.
*/
AUGUST,
/**
* The singleton instance for the month of September.
* This has the numeric value of {@code 9}.
*/
SEPTEMBER,
/**
* The singleton instance for the month of October.
* This has the numeric value of {@code 10}.
*/
OCTOBER,
/**
* The singleton instance for the month of November.
* This has the numeric value of {@code 11}.
*/
NOVEMBER,
/**
* The singleton instance for the month of December.
* This has the numeric value of {@code 12}.
*/
DECEMBER,
;
val value: Int = ordinal + 1
/**
* Gets the textual representation, such as 'January' or 'December'.
*
* This returns the textual name used to identify the month-of-year,
* suitable for presentation to the user.
*
* @param locale the locale to use, not null
* @return the text value of the month-of-year, not null
*/
fun getDisplayName(locale: Locale): String {
return DateFormatSymbols.getInstance(locale).months[ordinal]
}
/**
* Returns the month-of-year that is the specified number of months after this one.
* <p>
* The calculation rolls around the end of the year from December to January.
* The specified period may be negative.
* <p>
* This instance is immutable and unaffected by this method call.
*
* @param months the months to add, positive or negative
* @return the resulting month, not null
*/
operator fun plus(months: Int): Month {
val amount = (months % 12)
return entries[(ordinal + (amount + 12)) % 12]
}
/**
* Returns the month-of-year that is the specified number of months before this one.
* <p>
* The calculation rolls around the start of the year from January to December.
* The specified period may be negative.
* <p>
* This instance is immutable and unaffected by this method call.
*
* @param months the months to subtract, positive or negative
* @return the resulting month, not null
*/
operator fun minus(months: Int): Month {
return plus(-(months % 12))
}
/**
* Gets the maximum length of this month in days.
*
* February has a maximum length of 29 days.
* April, June, September and November have 30 days.
* All other months have 31 days.
*
* @return the maximum length of this month in days, from 29 to 31
*/
fun maxLength(): Int {
return when (this) {
FEBRUARY -> 29
APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30
else -> 31
}
}
companion object {
/**
* Obtains an instance of {@code Month} from an {@code int} value.
* <p>
* {@code Month} is an enum representing the 12 months of the year.
* This factory allows the enum to be obtained from the {@code int} value.
* The {@code int} value follows the ISO-8601 standard, from 1 (January) to 12 (December).
*
* @param month the month-of-year to represent, from 1 (January) to 12 (December)
* @return the month-of-year, not null
* @throws Exception if the month-of-year is invalid
*/
fun of(month: Int): Month {
if (month !in 1..12) {
throw Exception("Invalid value for MonthOfYear: $month")
}
return entries[month - 1]
}
/**
* Obtains an instance of {@code Month} from a Calendar.MONTH {@code int} value.
* <p>
* {@code Month} is an enum representing the 12 months of the year.
* This factory allows the enum to be obtained from the Calendar.MONTH {@code int} value.
* The Calendar.MONTH {@code int} value follows the Calendar.MONTH, from 0 (January) to 11 (December).
*
* @param month the month-of-year to represent, from 0 (January) to 11 (December)
* @return the month-of-year, not null
* @throws Exception if the month-of-year is invalid
*/
fun fromCalendarMonth(month: Int): Month {
if (month !in 0..11) {
throw Exception("Invalid value for MonthOfYear: $month")
}
return entries[month]
}
}
}

View file

@ -0,0 +1,42 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.reference
enum class WeatherCode(val id: String) {
CLEAR("clear"),
PARTLY_CLOUDY("partly_cloudy"),
CLOUDY("cloudy"),
RAIN("rain"),
SNOW("snow"),
WIND("wind"),
FOG("fog"),
HAZE("haze"),
SLEET("sleet"),
HAIL("hail"),
THUNDER("thunder"),
THUNDERSTORM("thunderstorm"),
;
companion object {
fun getInstance(
value: String?,
): WeatherCode? = WeatherCode.entries.firstOrNull {
it.id.equals(value, ignoreCase = true)
}
}
}

View file

@ -0,0 +1,26 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.AirQuality
import java.util.Date
data class AirQualityWrapper(
val current: AirQuality? = null,
val dailyForecast: Map<Date, AirQuality>? = null,
val hourlyForecast: Map<Date, AirQuality>? = null,
)

View file

@ -0,0 +1,68 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.Current
import breezyweather.domain.weather.model.UV
import breezyweather.domain.weather.model.Wind
import breezyweather.domain.weather.reference.WeatherCode
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.temperature.Temperature
/**
* Current wrapper
*/
data class CurrentWrapper(
val weatherText: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: TemperatureWrapper? = null,
val wind: Wind? = null,
val uV: UV? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
val ceiling: Distance? = null,
val dailyForecast: String? = null,
// Is actually a description of the nowcast
val hourlyForecast: String? = null,
) {
fun toCurrent(
uV: UV? = null,
) = Current(
weatherText = this.weatherText,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperature(),
wind = this.wind,
uV = uV ?: this.uV,
relativeHumidity = this.relativeHumidity,
dewPoint = this.dewPoint,
pressure = this.pressure,
cloudCover = this.cloudCover,
visibility = this.visibility,
ceiling = this.ceiling,
dailyForecast = this.dailyForecast,
hourlyForecast = this.hourlyForecast
)
}

View file

@ -0,0 +1,66 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.AirQuality
import breezyweather.domain.weather.model.Daily
import breezyweather.domain.weather.model.DailyCloudCover
import breezyweather.domain.weather.model.DailyDewPoint
import breezyweather.domain.weather.model.DailyPressure
import breezyweather.domain.weather.model.DailyRelativeHumidity
import breezyweather.domain.weather.model.DailyVisibility
import breezyweather.domain.weather.model.DegreeDay
import breezyweather.domain.weather.model.Pollen
import breezyweather.domain.weather.model.UV
import java.util.Date
import kotlin.time.Duration
/**
* Daily wrapper
*/
data class DailyWrapper(
val date: Date,
val day: HalfDayWrapper? = null,
val night: HalfDayWrapper? = null,
val degreeDay: DegreeDay? = null,
val uV: UV? = null,
val sunshineDuration: Duration? = null,
val relativeHumidity: DailyRelativeHumidity? = null,
val dewPoint: DailyDewPoint? = null,
val pressure: DailyPressure? = null,
val cloudCover: DailyCloudCover? = null,
val visibility: DailyVisibility? = null,
) {
fun toDaily(
airQuality: AirQuality? = null,
pollen: Pollen? = null,
) = Daily(
date = this.date,
day = this.day?.toHalfDay(),
night = this.night?.toHalfDay(),
degreeDay = this.degreeDay,
airQuality = airQuality,
pollen = pollen,
uV = this.uV,
sunshineDuration = this.sunshineDuration,
relativeHumidity = this.relativeHumidity,
dewPoint = this.dewPoint,
pressure = this.pressure,
cloudCover = this.cloudCover,
visibility = this.visibility
)
}

View file

@ -0,0 +1,56 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.HalfDay
import breezyweather.domain.weather.model.Precipitation
import breezyweather.domain.weather.model.PrecipitationDuration
import breezyweather.domain.weather.model.PrecipitationProbability
import breezyweather.domain.weather.model.Wind
import breezyweather.domain.weather.reference.WeatherCode
/**
* Half day.
*/
data class HalfDayWrapper(
/**
* A short description of the weather condition
*/
val weatherText: String? = null,
/**
* A long description of the weather condition. Used as a half-day summary
*/
val weatherSummary: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: TemperatureWrapper? = null,
val precipitation: Precipitation? = null,
val precipitationProbability: PrecipitationProbability? = null,
val precipitationDuration: PrecipitationDuration? = null,
val wind: Wind? = null,
) {
fun toHalfDay() = HalfDay(
weatherText = this.weatherText,
weatherSummary = this.weatherSummary,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperature(),
precipitation = this.precipitation,
precipitationProbability = this.precipitationProbability,
precipitationDuration = this.precipitationDuration,
wind = this.wind
)
}

View file

@ -0,0 +1,81 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.AirQuality
import breezyweather.domain.weather.model.Hourly
import breezyweather.domain.weather.model.Precipitation
import breezyweather.domain.weather.model.PrecipitationProbability
import breezyweather.domain.weather.model.UV
import breezyweather.domain.weather.model.Wind
import breezyweather.domain.weather.reference.WeatherCode
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.temperature.Temperature
import java.util.Date
import kotlin.time.Duration
/**
* Hourly wrapper that allows isDaylight to be null and completed later
*/
data class HourlyWrapper(
val date: Date,
val isDaylight: Boolean? = null,
val weatherText: String? = null,
val weatherCode: WeatherCode? = null,
val temperature: TemperatureWrapper? = null,
val precipitation: Precipitation? = null,
val precipitationProbability: PrecipitationProbability? = null,
val wind: Wind? = null,
val uV: UV? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
/**
* Duration of sunshine, NOT duration of daylight
*/
val sunshineDuration: Duration? = null,
) {
fun toHourly(
airQuality: AirQuality? = null,
isDaylight: Boolean? = null,
uV: UV? = null,
) = Hourly(
date = this.date,
isDaylight = isDaylight ?: this.isDaylight ?: true,
weatherText = this.weatherText,
weatherCode = this.weatherCode,
temperature = this.temperature?.toTemperature(),
precipitation = this.precipitation,
precipitationProbability = this.precipitationProbability,
wind = this.wind,
airQuality = airQuality,
uV = uV ?: this.uV,
relativeHumidity = this.relativeHumidity,
dewPoint = this.dewPoint,
pressure = this.pressure,
cloudCover = this.cloudCover,
visibility = this.visibility
)
}

View file

@ -0,0 +1,29 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.weather.model.Pollen
import java.util.Date
data class PollenWrapper(
/**
* Current will be used as "Today" if dailyForecast and hourlyForecast are empty
*/
val current: Pollen? = null,
val dailyForecast: Map<Date, Pollen>? = null,
val hourlyForecast: Map<Date, Pollen>? = null,
)

View file

@ -0,0 +1,39 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import org.breezyweather.unit.temperature.Temperature
/**
* Temperature.
*/
data class TemperatureWrapper(
val temperature: Temperature? = null,
val feelsLike: Temperature? = null,
) {
fun toTemperature(
computedApparent: Temperature? = null,
computedWindChill: Temperature? = null,
computedHumidex: Temperature? = null,
) = breezyweather.domain.weather.model.Temperature(
temperature = this.temperature,
sourceFeelsLike = this.feelsLike,
computedApparent = computedApparent,
computedWindChill = computedWindChill,
computedHumidex = computedHumidex
)
}

View file

@ -0,0 +1,43 @@
/**
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package breezyweather.domain.weather.wrappers
import breezyweather.domain.source.SourceFeature
import breezyweather.domain.weather.model.Alert
import breezyweather.domain.weather.model.Minutely
import breezyweather.domain.weather.model.Normals
import breezyweather.domain.weather.reference.Month
/**
* Wrapper very similar to the object in database.
* Helps the transition process and computing of missing data.
*/
data class WeatherWrapper(
val dailyForecast: List<DailyWrapper>? = null,
val hourlyForecast: List<HourlyWrapper>? = null,
val current: CurrentWrapper? = null,
val airQuality: AirQualityWrapper? = null,
val pollen: PollenWrapper? = null,
val minutelyForecast: List<Minutely>? = null,
val alertList: List<Alert>? = null,
/**
* You can get the month with Month.of(month)
* where month is a value between 1 and 12
*/
val normals: Map<Month, Normals>? = null,
val failedFeatures: Map<SourceFeature, Throwable>? = null,
)