1 package net.pterodactylus.rhynodge.webpages.weather
3 import com.fasterxml.jackson.annotation.JsonGetter
4 import com.fasterxml.jackson.annotation.JsonProperty
5 import kotlinx.html.body
6 import kotlinx.html.div
8 import kotlinx.html.head
9 import kotlinx.html.html
10 import kotlinx.html.img
11 import kotlinx.html.stream.createHTML
12 import kotlinx.html.style
13 import kotlinx.html.unsafe
14 import net.pterodactylus.rhynodge.states.AbstractState
15 import java.text.DateFormat
16 import java.time.Instant
17 import java.time.ZoneId
18 import java.time.ZonedDateTime
19 import java.time.temporal.ChronoUnit
20 import java.util.Locale
23 * Contains a weather state.
25 * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
27 class WeatherState(val service: String, val dateTime: ZonedDateTime) : AbstractState(true), Iterable<HourState> {
29 constructor(@JsonProperty("service") service: String, @JsonProperty("dateTime") time: Long) :
30 this(service, Instant.ofEpochMilli(time).atZone(ZoneId.of("Europe/Berlin")))
32 @JsonProperty("hours")
33 val hours: List<HourState> = mutableListOf()
35 @get:JsonGetter("dateTime")
36 val timeMillis = dateTime.toInstant().toEpochMilli()
38 operator fun plusAssign(hourState: HourState) {
39 (hours as MutableList<HourState>).add(hourState)
42 override fun iterator(): Iterator<HourState> {
43 return hours.iterator()
46 override fun equals(other: Any?): Boolean {
47 other as? WeatherState ?: return false
48 return (dateTime == other.dateTime) and (hours == other.hours)
51 override fun plainText(): String=""
53 override fun htmlText(): String =
58 +".weather-states { display: table; } "
59 +".hour-state, .header { display: table-row; } "
60 +".hour-state > div { display: table-cell; padding: 0em 0.5em; text-align: center; } "
61 +".header > div { display: table-cell; padding: 0em 0.5em; font-weight: bold; text-align: center; } "
66 val startTime = dateTime.toInstant()
67 h1 { +"The Weather (according to wetter.de) on %s".format(dateFormatter.format(startTime.toEpochMilli())) }
68 val showFeltTemperature = any { it.feltTemperature != null }
69 val showGustSpeed = any { it.gustSpeed != null }
70 val showHumidity = any { it.humidity != null }
71 div("weather-states") {
74 div { +"Temperature" }
78 div { +"Chance of Rain" }
88 div { +"Description" }
93 div("time") { +"%tH:%<tM".format(startTime.plus(it.hourIndex.toLong(), ChronoUnit.HOURS).toEpochMilli()) }
94 div("temperature") { +"${it.temperature} °C" }
95 if (showFeltTemperature) {
96 div("felt-temperature") { +if (it.feltTemperature != null) "(${it.feltTemperature} °C)" else "" }
98 div("rain-probability") { +"${it.rainProbability.times(100).toInt()}%" }
99 div("rain-amount") { +"${it.rainAmount.minDigits()} l/m²" }
100 div("wind-direction") { +it.windDirection.arrow }
101 div("wind-speed") { +"${it.windSpeed} km/h" }
103 div("gust-speed") { +if (it.gustSpeed != null) "(up to ${it.gustSpeed} km/h)" else "" }
106 div("humidity") { +if (it.humidity != null) "${it.humidity.times(100).toInt()}%" else "" }
108 div("description") { +it.description }
109 div("image") { img(src = it.image) }
118 private val dateFormatter: DateFormat =
119 DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH)
121 private fun Double.minDigits(): String =
122 toString().replace(Regex("\\.0*$"), "")