[smhi] Add aggregated channels for daily forecast. (#9387)
* Add aggregated channels for daily forecast. Also updates to use Optionals instead of @Nullables, and add unit tests * Revert unsing explicit unit definition Signed-off-by: Anders Alfredsson <andersb86@gmail.com>
This commit is contained in:
parent
f88225113b
commit
393ae49dc4
|
@ -27,18 +27,24 @@ You can also choose for which hours and which days you would like to get forecas
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
The channels are the same for all forecasts:
|
The channels are the same for all forecasts, but the daily forecast provides some additional aggregated values.
|
||||||
|
For the other daily forecast channels, the values are for 12:00 UTC.
|
||||||
|
|
||||||
#### Basic channels
|
#### Basic channels
|
||||||
|
|
||||||
| channel | type | description |
|
| channel | type | description |
|
||||||
|----------|--------|------------------------------|
|
|----------|--------|------------------------------|
|
||||||
| Temperature | Number:Temperature | Temperature in Celsius |
|
| Temperature | Number:Temperature | Temperature in Celsius |
|
||||||
|
| Max Temperature | Number:Temperature | Highest temperature of the day (daily forecast only) |
|
||||||
|
| Min Temperature | Number:Temperature | Lowest temperature of the day (daily forecast only) |
|
||||||
| Wind direction | Number:Angle | Wind direction in degrees |
|
| Wind direction | Number:Angle | Wind direction in degrees |
|
||||||
| Wind Speed | Number:Speed | Wind speed in m/s |
|
| Wind Speed | Number:Speed | Wind speed in m/s |
|
||||||
|
| Max Wind Speed | Number:Speed | Highest wind speed of the day (daily forecast only) |
|
||||||
|
| Min Wind Speed | Number:Speed | Lowest wind speed of the day (daily forecast only) |
|
||||||
| Wind gust speed | Number:Speed | Wind gust speed in m/s |
|
| Wind gust speed | Number:Speed | Wind gust speed in m/s |
|
||||||
| Minimum precipitation | Number:Speed | Minimum precipitation intensity in mm/h |
|
| Minimum precipitation | Number:Speed | Minimum precipitation intensity in mm/h |
|
||||||
| Maximum precipitation | Number:Speed | Maximum precipitation intensity in mm/h |
|
| Maximum precipitation | Number:Speed | Maximum precipitation intensity in mm/h |
|
||||||
|
| Total precipitation | Number:Length | Total amount of precipitation during the day, in mm (daily forecast only) |
|
||||||
| Precipitation category* | Number | Type of precipitation |
|
| Precipitation category* | Number | Type of precipitation |
|
||||||
| Air pressure | Number:Pressure | Air pressure in hPa |
|
| Air pressure | Number:Pressure | Air pressure in hPa |
|
||||||
| Relative humidity | Number:Dimensionless | Relative humidity in percent |
|
| Relative humidity | Number:Dimensionless | Relative humidity in percent |
|
||||||
|
|
|
@ -16,9 +16,9 @@ package org.openhab.binding.smhi.internal;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class containing a forecast for a specific point in time.
|
* A class containing a forecast for a specific point in time.
|
||||||
|
@ -43,8 +43,8 @@ public class Forecast implements Comparable<Forecast> {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable BigDecimal getParameter(String parameter) {
|
public Optional<BigDecimal> getParameter(String parameter) {
|
||||||
return parameters.get(parameter);
|
return Optional.ofNullable(parameters.get(parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openhab.binding.smhi.internal;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Anders Alfredsson - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ForecastAggregator {
|
||||||
|
public static Optional<BigDecimal> max(TimeSeries timeSeries, int dayOffset, String parameter) {
|
||||||
|
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
|
||||||
|
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent)
|
||||||
|
.map(Optional::get).max(BigDecimal::compareTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<BigDecimal> min(TimeSeries timeSeries, int dayOffset, String parameter) {
|
||||||
|
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
|
||||||
|
return dayForecasts.stream().map(forecast -> forecast.getParameter(parameter)).filter(Optional::isPresent)
|
||||||
|
.map(Optional::get).min(BigDecimal::compareTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<BigDecimal> total(TimeSeries timeSeries, int dayOffset, String parameter) {
|
||||||
|
List<Forecast> dayForecasts = timeSeries.getDay(dayOffset);
|
||||||
|
BigDecimal sum = dayForecasts.stream().map(forecast -> forecast.getParameter(parameter))
|
||||||
|
.filter(Optional::isPresent).map(Optional::get).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
|
||||||
|
BigDecimal mean = sum.divide(BigDecimal.valueOf(dayForecasts.size()), RoundingMode.HALF_UP);
|
||||||
|
return Optional.of(mean.multiply(BigDecimal.valueOf(24)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,7 @@
|
||||||
package org.openhab.binding.smhi.internal;
|
package org.openhab.binding.smhi.internal;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
@ -56,13 +53,22 @@ public class SmhiBindingConstants {
|
||||||
public static final String PRECIPITATION_CATEGORY = "pcat";
|
public static final String PRECIPITATION_CATEGORY = "pcat";
|
||||||
public static final String WEATHER_SYMBOL = "wsymb2";
|
public static final String WEATHER_SYMBOL = "wsymb2";
|
||||||
|
|
||||||
public static final List<String> CHANNEL_IDS = Collections
|
public static final String TEMPERATURE_MAX = "tmax";
|
||||||
.unmodifiableList(Stream
|
public static final String TEMPERATURE_MIN = "tmin";
|
||||||
.of(PRESSURE, TEMPERATURE, VISIBILITY, WIND_DIRECTION, WIND_SPEED, RELATIVE_HUMIDITY,
|
public static final String WIND_MAX = "wsmax";
|
||||||
THUNDER_PROBABILITY, TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER,
|
public static final String WIND_MIN = "wsmin";
|
||||||
HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN, PRECIPITATION_MAX, PRECIPITATION_MEAN,
|
public static final String PRECIPITATION_TOTAL = "ptotal";
|
||||||
PRECIPITATION_MEDIAN, PERCENT_FROZEN, PRECIPITATION_CATEGORY, WEATHER_SYMBOL)
|
|
||||||
.collect(Collectors.toList()));
|
public static final List<String> HOURLY_CHANNELS = List.of(PRESSURE, TEMPERATURE, VISIBILITY, WIND_DIRECTION,
|
||||||
|
WIND_SPEED, RELATIVE_HUMIDITY, THUNDER_PROBABILITY, TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER,
|
||||||
|
HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN, PRECIPITATION_MAX, PRECIPITATION_MEAN, PRECIPITATION_MEDIAN,
|
||||||
|
PERCENT_FROZEN, PRECIPITATION_CATEGORY, WEATHER_SYMBOL);
|
||||||
|
|
||||||
|
public static final List<String> DAILY_CHANNELS = List.of(PRESSURE, TEMPERATURE, TEMPERATURE_MAX, TEMPERATURE_MIN,
|
||||||
|
VISIBILITY, WIND_DIRECTION, WIND_SPEED, WIND_MAX, WIND_MIN, RELATIVE_HUMIDITY, THUNDER_PROBABILITY,
|
||||||
|
TOTAL_CLOUD_COVER, LOW_CLOUD_COVER, MEDIUM_CLOUD_COVER, HIGH_CLOUD_COVER, GUST, PRECIPITATION_MIN,
|
||||||
|
PRECIPITATION_MAX, PRECIPITATION_TOTAL, PRECIPITATION_MEAN, PRECIPITATION_MEDIAN, PERCENT_FROZEN,
|
||||||
|
PRECIPITATION_CATEGORY, WEATHER_SYMBOL);
|
||||||
|
|
||||||
public static final String BASE_URL = "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/";
|
public static final String BASE_URL = "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/";
|
||||||
public static final String APPROVED_TIME_URL = BASE_URL + "approvedtime.json";
|
public static final String APPROVED_TIME_URL = BASE_URL + "approvedtime.json";
|
||||||
|
|
|
@ -17,10 +17,7 @@ import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -143,11 +140,11 @@ public class SmhiHandler extends BaseThingHandler {
|
||||||
if (channels.isEmpty()) {
|
if (channels.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Forecast forecast = timeSeries.getForecast(i);
|
Optional<Forecast> forecast = timeSeries.getForecast(i);
|
||||||
if (forecast != null) {
|
if (forecast.isPresent()) {
|
||||||
channels.forEach(c -> {
|
channels.forEach(c -> {
|
||||||
String id = c.getUID().getIdWithoutGroup();
|
String id = c.getUID().getIdWithoutGroup();
|
||||||
BigDecimal value = forecast.getParameter(id);
|
Optional<BigDecimal> value = forecast.get().getParameter(id);
|
||||||
updateChannel(c, value);
|
updateChannel(c, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -159,74 +156,87 @@ public class SmhiHandler extends BaseThingHandler {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = 24 * i + 12;
|
int dayOffset = i;
|
||||||
Forecast forecast = timeSeries.getForecast(currentDay, offset);
|
int hourOffset = 24 * dayOffset + 12;
|
||||||
|
Optional<Forecast> forecast = timeSeries.getForecast(currentDay, hourOffset);
|
||||||
|
|
||||||
if (forecast == null) {
|
if (forecast.isEmpty()) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("No forecast yet for {}", currentDay.plusHours(offset));
|
logger.debug("No forecast yet for {}", currentDay.plusHours(hourOffset));
|
||||||
}
|
}
|
||||||
channels.forEach(c -> {
|
channels.forEach(c -> {
|
||||||
updateState(c.getUID(), UnDefType.NULL);
|
updateState(c.getUID(), UnDefType.UNDEF);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
channels.forEach(c -> {
|
channels.forEach(c -> {
|
||||||
String id = c.getUID().getIdWithoutGroup();
|
String id = c.getUID().getIdWithoutGroup();
|
||||||
BigDecimal value = forecast.getParameter(id);
|
Optional<BigDecimal> value;
|
||||||
|
if (isAggregatedChannel(id)) {
|
||||||
|
value = getAggregatedValue(id, timeSeries, dayOffset);
|
||||||
|
} else {
|
||||||
|
value = forecast.get().getParameter(id);
|
||||||
|
}
|
||||||
updateChannel(c, value);
|
updateChannel(c, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChannel(Channel channel, @Nullable BigDecimal value) {
|
private void updateChannel(Channel channel, Optional<BigDecimal> value) {
|
||||||
String id = channel.getUID().getIdWithoutGroup();
|
String id = channel.getUID().getIdWithoutGroup();
|
||||||
State newState = UnDefType.NULL;
|
State newState = UnDefType.UNDEF;
|
||||||
|
|
||||||
if (value != null) {
|
if (value.isPresent()) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case PRESSURE:
|
case PRESSURE:
|
||||||
newState = new QuantityType<>(value, MetricPrefix.HECTO(SIUnits.PASCAL));
|
newState = new QuantityType<>(value.get(), MetricPrefix.HECTO(SIUnits.PASCAL));
|
||||||
break;
|
break;
|
||||||
case TEMPERATURE:
|
case TEMPERATURE:
|
||||||
newState = new QuantityType<>(value, SIUnits.CELSIUS);
|
case TEMPERATURE_MAX:
|
||||||
|
case TEMPERATURE_MIN:
|
||||||
|
newState = new QuantityType<>(value.get(), SIUnits.CELSIUS);
|
||||||
break;
|
break;
|
||||||
case VISIBILITY:
|
case VISIBILITY:
|
||||||
newState = new QuantityType<>(value, MetricPrefix.KILO(SIUnits.METRE));
|
newState = new QuantityType<>(value.get(), MetricPrefix.KILO(SIUnits.METRE));
|
||||||
break;
|
break;
|
||||||
case WIND_DIRECTION:
|
case WIND_DIRECTION:
|
||||||
newState = new QuantityType<>(value, Units.DEGREE_ANGLE);
|
newState = new QuantityType<>(value.get(), Units.DEGREE_ANGLE);
|
||||||
break;
|
break;
|
||||||
case WIND_SPEED:
|
case WIND_SPEED:
|
||||||
|
case WIND_MAX:
|
||||||
|
case WIND_MIN:
|
||||||
case GUST:
|
case GUST:
|
||||||
newState = new QuantityType<>(value, Units.METRE_PER_SECOND);
|
newState = new QuantityType<>(value.get(), Units.METRE_PER_SECOND);
|
||||||
break;
|
break;
|
||||||
case RELATIVE_HUMIDITY:
|
case RELATIVE_HUMIDITY:
|
||||||
case THUNDER_PROBABILITY:
|
case THUNDER_PROBABILITY:
|
||||||
newState = new QuantityType<>(value, Units.PERCENT);
|
newState = new QuantityType<>(value.get(), Units.PERCENT);
|
||||||
break;
|
break;
|
||||||
case PERCENT_FROZEN:
|
case PERCENT_FROZEN:
|
||||||
// Smhi returns -9 for spp if there's no precipitation, convert to UNDEF
|
// Smhi returns -9 for spp if there's no precipitation, convert to UNDEF
|
||||||
if (value.intValue() == -9) {
|
if (value.get().intValue() == -9) {
|
||||||
newState = UnDefType.UNDEF;
|
newState = UnDefType.UNDEF;
|
||||||
} else {
|
} else {
|
||||||
newState = new QuantityType<>(value, Units.PERCENT);
|
newState = new QuantityType<>(value.get(), Units.PERCENT);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HIGH_CLOUD_COVER:
|
case HIGH_CLOUD_COVER:
|
||||||
case MEDIUM_CLOUD_COVER:
|
case MEDIUM_CLOUD_COVER:
|
||||||
case LOW_CLOUD_COVER:
|
case LOW_CLOUD_COVER:
|
||||||
case TOTAL_CLOUD_COVER:
|
case TOTAL_CLOUD_COVER:
|
||||||
newState = new QuantityType<>(value.multiply(OCTAS_TO_PERCENT), Units.PERCENT);
|
newState = new QuantityType<>(value.get().multiply(OCTAS_TO_PERCENT), Units.PERCENT);
|
||||||
break;
|
break;
|
||||||
case PRECIPITATION_MAX:
|
case PRECIPITATION_MAX:
|
||||||
case PRECIPITATION_MEAN:
|
case PRECIPITATION_MEAN:
|
||||||
case PRECIPITATION_MEDIAN:
|
case PRECIPITATION_MEDIAN:
|
||||||
case PRECIPITATION_MIN:
|
case PRECIPITATION_MIN:
|
||||||
newState = new QuantityType<>(value, Units.MILLIMETRE_PER_HOUR);
|
newState = new QuantityType<>(value.get(), Units.MILLIMETRE_PER_HOUR);
|
||||||
|
break;
|
||||||
|
case PRECIPITATION_TOTAL:
|
||||||
|
newState = new QuantityType<>(value.get(), MetricPrefix.MILLI(SIUnits.METRE));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
newState = new DecimalType(value);
|
newState = new DecimalType(value.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,32 +377,27 @@ public class SmhiHandler extends BaseThingHandler {
|
||||||
private List<Channel> createChannels() {
|
private List<Channel> createChannels() {
|
||||||
List<Channel> channels = new ArrayList<>();
|
List<Channel> channels = new ArrayList<>();
|
||||||
|
|
||||||
// There's currently a bug in PaperUI that can cause options to be added more than one time
|
@Nullable
|
||||||
// to the list. Convert to a sorted set to work around this.
|
|
||||||
// See https://github.com/openhab/openhab-webui/issues/212
|
|
||||||
Set<Integer> hours = new TreeSet<>();
|
|
||||||
Set<Integer> days = new TreeSet<>();
|
|
||||||
List<Integer> hourlyForecasts = config.hourlyForecasts;
|
List<Integer> hourlyForecasts = config.hourlyForecasts;
|
||||||
if (hourlyForecasts != null) {
|
@Nullable
|
||||||
hours.addAll(hourlyForecasts);
|
|
||||||
}
|
|
||||||
List<Integer> dailyForecasts = config.dailyForecasts;
|
List<Integer> dailyForecasts = config.dailyForecasts;
|
||||||
|
|
||||||
|
if (hourlyForecasts != null) {
|
||||||
|
for (int i : hourlyForecasts) {
|
||||||
|
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "hour_" + i);
|
||||||
|
HOURLY_CHANNELS.forEach(id -> {
|
||||||
|
channels.add(createChannel(groupUID, id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dailyForecasts != null) {
|
if (dailyForecasts != null) {
|
||||||
days.addAll(dailyForecasts);
|
for (int i : dailyForecasts) {
|
||||||
}
|
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "day_" + i);
|
||||||
|
DAILY_CHANNELS.forEach(id -> {
|
||||||
for (int i : hours) {
|
channels.add(createChannel(groupUID, id));
|
||||||
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "hour_" + i);
|
});
|
||||||
CHANNEL_IDS.forEach(id -> {
|
}
|
||||||
channels.add(createChannel(groupUID, id));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i : days) {
|
|
||||||
ChannelGroupUID groupUID = new ChannelGroupUID(thing.getUID(), "day_" + i);
|
|
||||||
CHANNEL_IDS.forEach(id -> {
|
|
||||||
channels.add(createChannel(groupUID, id));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
@ -409,17 +414,22 @@ public class SmhiHandler extends BaseThingHandler {
|
||||||
String itemType = "Number";
|
String itemType = "Number";
|
||||||
switch (channelID) {
|
switch (channelID) {
|
||||||
case TEMPERATURE:
|
case TEMPERATURE:
|
||||||
|
case TEMPERATURE_MAX:
|
||||||
|
case TEMPERATURE_MIN:
|
||||||
itemType += ":Temperature";
|
itemType += ":Temperature";
|
||||||
break;
|
break;
|
||||||
case PRESSURE:
|
case PRESSURE:
|
||||||
itemType += ":Pressure";
|
itemType += ":Pressure";
|
||||||
break;
|
break;
|
||||||
case VISIBILITY:
|
case VISIBILITY:
|
||||||
|
case PRECIPITATION_TOTAL:
|
||||||
itemType += ":Length";
|
itemType += ":Length";
|
||||||
break;
|
break;
|
||||||
case WIND_DIRECTION:
|
case WIND_DIRECTION:
|
||||||
itemType += ":Angle";
|
itemType += ":Angle";
|
||||||
case WIND_SPEED:
|
case WIND_SPEED:
|
||||||
|
case WIND_MAX:
|
||||||
|
case WIND_MIN:
|
||||||
case GUST:
|
case GUST:
|
||||||
case PRECIPITATION_MAX:
|
case PRECIPITATION_MAX:
|
||||||
case PRECIPITATION_MEAN:
|
case PRECIPITATION_MEAN:
|
||||||
|
@ -442,4 +452,34 @@ public class SmhiHandler extends BaseThingHandler {
|
||||||
.withType(new ChannelTypeUID(BINDING_ID, channelID)).build();
|
.withType(new ChannelTypeUID(BINDING_ID, channelID)).build();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAggregatedChannel(String channelId) {
|
||||||
|
switch (channelId) {
|
||||||
|
case TEMPERATURE_MAX:
|
||||||
|
case TEMPERATURE_MIN:
|
||||||
|
case WIND_MAX:
|
||||||
|
case WIND_MIN:
|
||||||
|
case PRECIPITATION_TOTAL:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<BigDecimal> getAggregatedValue(String channelId, TimeSeries timeSeries, int dayOffset) {
|
||||||
|
switch (channelId) {
|
||||||
|
case TEMPERATURE_MAX:
|
||||||
|
return ForecastAggregator.max(timeSeries, dayOffset, TEMPERATURE);
|
||||||
|
case TEMPERATURE_MIN:
|
||||||
|
return ForecastAggregator.min(timeSeries, dayOffset, TEMPERATURE);
|
||||||
|
case WIND_MAX:
|
||||||
|
return ForecastAggregator.max(timeSeries, dayOffset, WIND_SPEED);
|
||||||
|
case WIND_MIN:
|
||||||
|
return ForecastAggregator.min(timeSeries, dayOffset, WIND_SPEED);
|
||||||
|
case PRECIPITATION_TOTAL:
|
||||||
|
return ForecastAggregator.total(timeSeries, dayOffset, PRECIPITATION_MEAN);
|
||||||
|
default:
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@ package org.openhab.binding.smhi.internal;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
@ -48,8 +50,8 @@ public class TimeSeries implements Iterable<Forecast> {
|
||||||
* @param hourOffset number of hours after now.
|
* @param hourOffset number of hours after now.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public @Nullable Forecast getForecast(int hourOffset) {
|
public Optional<Forecast> getForecast(int hourOffset) {
|
||||||
return getForecast(ZonedDateTime.now(), hourOffset);
|
return getForecast(referenceTime, hourOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,17 +60,23 @@ public class TimeSeries implements Iterable<Forecast> {
|
||||||
* @param hourOffset number of hours after now.
|
* @param hourOffset number of hours after now.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public @Nullable Forecast getForecast(ZonedDateTime startTime, int hourOffset) {
|
public Optional<Forecast> getForecast(ZonedDateTime startTime, int hourOffset) {
|
||||||
if (hourOffset < 0) {
|
if (hourOffset < 0) {
|
||||||
throw new IllegalArgumentException("Offset must be at least 0");
|
throw new IllegalArgumentException("Offset must be at least 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Forecast forecast : forecasts) {
|
for (Forecast forecast : forecasts) {
|
||||||
if (forecast.getValidTime().compareTo(startTime.plusHours(hourOffset)) >= 0) {
|
if (forecast.getValidTime().compareTo(startTime.plusHours(hourOffset)) > 0) {
|
||||||
return forecast;
|
return Optional.of(forecast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Forecast> getDay(int dayOffset) {
|
||||||
|
ZonedDateTime day = referenceTime.plusDays(dayOffset);
|
||||||
|
return forecasts.stream().filter(forecast -> forecast.getValidTime().getDayOfMonth() == day.getDayOfMonth())
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||||
|
|
||||||
<config-description uri="thing-type:smhi:forecast">
|
<config-description uri="thing-type:smhi:forecast">
|
||||||
<parameter name="latitude" type="decimal" required="true">
|
<parameter name="latitude" type="decimal" step="0.000001" required="true">
|
||||||
<label>Latitude</label>
|
<label>Latitude</label>
|
||||||
<description>Latitude for the forecast</description>
|
<description>Latitude for the forecast</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="longitude" type="decimal" required="true">
|
<parameter name="longitude" type="decimal" step="0.000001" required="true">
|
||||||
<label>Longitude</label>
|
<label>Longitude</label>
|
||||||
<description>Longitude for the forecast</description>
|
<description>Longitude for the forecast</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
|
|
@ -8,97 +8,127 @@
|
||||||
<item-type>Number:Pressure</item-type>
|
<item-type>Number:Pressure</item-type>
|
||||||
<label>Air Pressure</label>
|
<label>Air Pressure</label>
|
||||||
<description>Air pressure in hPa</description>
|
<description>Air pressure in hPa</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f hPa"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="t">
|
<channel-type id="t">
|
||||||
<item-type>Number:Temperature</item-type>
|
<item-type>Number:Temperature</item-type>
|
||||||
<label>Temperature</label>
|
<label>Temperature</label>
|
||||||
<description>Temperature</description>
|
<description>Temperature</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f °C"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="tmax">
|
||||||
|
<item-type>Number:Temperature</item-type>
|
||||||
|
<label>Max Temperature</label>
|
||||||
|
<description>Highest temperature of the day</description>
|
||||||
|
<state readOnly="true" pattern="%.1f °C"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="tmin">
|
||||||
|
<item-type>Number:Temperature</item-type>
|
||||||
|
<label>Min Temperature</label>
|
||||||
|
<description>Lowest temperature of the day</description>
|
||||||
|
<state readOnly="true" pattern="%.1f °C"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="vis" advanced="true">
|
<channel-type id="vis" advanced="true">
|
||||||
<item-type>Number:Length</item-type>
|
<item-type>Number:Length</item-type>
|
||||||
<label>Visibility</label>
|
<label>Visibility</label>
|
||||||
<description>Horizontal visibility</description>
|
<description>Horizontal visibility</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f km"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="wd">
|
<channel-type id="wd">
|
||||||
<item-type>Number:Angle</item-type>
|
<item-type>Number:Angle</item-type>
|
||||||
<label>Wind Direction</label>
|
<label>Wind Direction</label>
|
||||||
<description>Wind direction</description>
|
<description>Wind direction</description>
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
<state readOnly="true" pattern="%d °"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="ws">
|
<channel-type id="ws">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Wind Speed</label>
|
<label>Wind Speed</label>
|
||||||
<description>Wind speed</description>
|
<description>Wind speed</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f m/s"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="wsmax">
|
||||||
|
<item-type>Number:Speed</item-type>
|
||||||
|
<label>Max Wind Speed</label>
|
||||||
|
<description>Highest wind speed of the day</description>
|
||||||
|
<state readOnly="true" pattern="%.1f m/s"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="wsmin">
|
||||||
|
<item-type>Number:Speed</item-type>
|
||||||
|
<label>Min Wind Speed</label>
|
||||||
|
<description>Lowest wind speed of the day</description>
|
||||||
|
<state readOnly="true" pattern="%.1f m/s"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="r">
|
<channel-type id="r">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Relative Humidity</label>
|
<label>Relative Humidity</label>
|
||||||
<description>Relative humidity in percent</description>
|
<description>Relative humidity in percent</description>
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
<state readOnly="true" pattern="%d %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="tstm" advanced="true">
|
<channel-type id="tstm" advanced="true">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Thunder Probability</label>
|
<label>Thunder Probability</label>
|
||||||
<description>Probability of thunder in percent</description>
|
<description>Probability of thunder in percent</description>
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
<state readOnly="true" pattern="%d %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="tcc_mean">
|
<channel-type id="tcc_mean">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Total Cloud Cover</label>
|
<label>Total Cloud Cover</label>
|
||||||
<description>Mean value of total cloud cover in percent</description>
|
<description>Mean value of total cloud cover in percent</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="lcc_mean" advanced="true">
|
<channel-type id="lcc_mean" advanced="true">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Low Level Cloud Cover</label>
|
<label>Low Level Cloud Cover</label>
|
||||||
<description>Mean value of low level cloud cover (0-2500 m) in percent</description>
|
<description>Mean value of low level cloud cover (0-2500 m) in percent</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="mcc_mean" advanced="true">
|
<channel-type id="mcc_mean" advanced="true">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Medium Level Cloud Cover</label>
|
<label>Medium Level Cloud Cover</label>
|
||||||
<description>Mean value of medium level cloud cover (2500-6000 m) in percent</description>
|
<description>Mean value of medium level cloud cover (2500-6000 m) in percent</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="hcc_mean" advanced="true">
|
<channel-type id="hcc_mean" advanced="true">
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>High Level Cloud Cover</label>
|
<label>High Level Cloud Cover</label>
|
||||||
<description>Mean value of high level cloud cover (> 6000 m) in percent</description>
|
<description>Mean value of high level cloud cover (> 6000 m) in percent</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="gust">
|
<channel-type id="gust">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Wind Gust Speed</label>
|
<label>Wind Gust Speed</label>
|
||||||
<description>Wind gust speed</description>
|
<description>Wind gust speed</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f m/s"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="pmin">
|
<channel-type id="pmin">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Minimum Precipitation</label>
|
<label>Minimum Precipitation</label>
|
||||||
<description>Minimum precipitation intensity</description>
|
<description>Minimum precipitation intensity</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f mm/h"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="pmax">
|
<channel-type id="pmax">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Maximum Precipitation</label>
|
<label>Maximum Precipitation</label>
|
||||||
<description>Maximum precipitation intensity</description>
|
<description>Maximum precipitation intensity</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f mm/h"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="ptotal">
|
||||||
|
<item-type>Number:Length</item-type>
|
||||||
|
<label>Total Precipitation</label>
|
||||||
|
<description>Total amount of precipitation during the day</description>
|
||||||
|
<state readOnly="true" pattern="%.1f mm"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="pmean" advanced="true">
|
<channel-type id="pmean" advanced="true">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Mean Precipitation</label>
|
<label>Mean Precipitation</label>
|
||||||
<description>Mean precipitation intensity</description>
|
<description>Mean precipitation intensity</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f mm/h"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="pmedian" advanced="true">
|
<channel-type id="pmedian" advanced="true">
|
||||||
<item-type>Number:Speed</item-type>
|
<item-type>Number:Speed</item-type>
|
||||||
<label>Median Precipitation</label>
|
<label>Median Precipitation</label>
|
||||||
<description>Median precipitation intensity</description>
|
<description>Median precipitation intensity</description>
|
||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f mm/h"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="pcat">
|
<channel-type id="pcat">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
|
@ -120,7 +150,7 @@
|
||||||
<item-type>Number:Dimensionless</item-type>
|
<item-type>Number:Dimensionless</item-type>
|
||||||
<label>Frozen Precipitation</label>
|
<label>Frozen Precipitation</label>
|
||||||
<description>Percent of precipitation in frozen form</description>
|
<description>Percent of precipitation in frozen form</description>
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
<state readOnly="true" pattern="%d %%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="wsymb2">
|
<channel-type id="wsymb2">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
|
@ -190,11 +220,16 @@
|
||||||
<description>Forecast at noon for the specified offset</description>
|
<description>Forecast at noon for the specified offset</description>
|
||||||
<channels>
|
<channels>
|
||||||
<channel id="t" typeId="t"/>
|
<channel id="t" typeId="t"/>
|
||||||
|
<channel id="tmax" typeId="tmax"/>
|
||||||
|
<channel id="tmin" typeId="tmin"/>
|
||||||
<channel id="wd" typeId="wd"/>
|
<channel id="wd" typeId="wd"/>
|
||||||
<channel id="ws" typeId="ws"/>
|
<channel id="ws" typeId="ws"/>
|
||||||
|
<channel id="wsmax" typeId="wsmax"/>
|
||||||
|
<channel id="wsmin" typeId="wsmin"/>
|
||||||
<channel id="gust" typeId="gust"/>
|
<channel id="gust" typeId="gust"/>
|
||||||
<channel id="pmin" typeId="pmin"/>
|
<channel id="pmin" typeId="pmin"/>
|
||||||
<channel id="pmax" typeId="pmax"/>
|
<channel id="pmax" typeId="pmax"/>
|
||||||
|
<channel id="ptotal" typeId="ptotal"/>
|
||||||
<channel id="pcat" typeId="pcat"/>
|
<channel id="pcat" typeId="pcat"/>
|
||||||
<channel id="msl" typeId="msl"/>
|
<channel id="msl" typeId="msl"/>
|
||||||
<channel id="r" typeId="r"/>
|
<channel id="r" typeId="r"/>
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openhab.binding.smhi.internal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.openhab.binding.smhi.internal.SmhiBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Anders Alfredsson - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SmhiTest {
|
||||||
|
private static final ZonedDateTime TIME = ZonedDateTime.parse("2020-12-13T08:15:00Z");
|
||||||
|
private @NonNullByDefault({}) TimeSeries timeSeries;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
try {
|
||||||
|
InputStream is = SmhiTest.class.getResourceAsStream("forecast.json");
|
||||||
|
if (is == null) {
|
||||||
|
throw new AssertionError("Couldn't read forecast example");
|
||||||
|
}
|
||||||
|
String jsonString = new String(is.readAllBytes());
|
||||||
|
timeSeries = Parser.parseTimeSeries(jsonString);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError("Couldn't read forecast example");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parameterTest() {
|
||||||
|
assertNotNull(timeSeries);
|
||||||
|
Forecast forecast = timeSeries.getForecast(TIME, 0).orElseThrow(AssertionError::new);
|
||||||
|
|
||||||
|
BigDecimal msl = forecast.getParameter(PRESSURE).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal t = forecast.getParameter(TEMPERATURE).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal vis = forecast.getParameter(VISIBILITY).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal wd = forecast.getParameter(WIND_DIRECTION).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal ws = forecast.getParameter(WIND_SPEED).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal r = forecast.getParameter(RELATIVE_HUMIDITY).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal tstm = forecast.getParameter(THUNDER_PROBABILITY).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal tcc = forecast.getParameter(TOTAL_CLOUD_COVER).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal lcc = forecast.getParameter(LOW_CLOUD_COVER).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal mcc = forecast.getParameter(MEDIUM_CLOUD_COVER).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal hcc = forecast.getParameter(HIGH_CLOUD_COVER).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal gust = forecast.getParameter(GUST).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal pmin = forecast.getParameter(PRECIPITATION_MIN).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal pmax = forecast.getParameter(PRECIPITATION_MAX).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal spp = forecast.getParameter(PERCENT_FROZEN).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal pcat = forecast.getParameter(PRECIPITATION_CATEGORY).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal pmean = forecast.getParameter(PRECIPITATION_MEAN).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal pmedian = forecast.getParameter(PRECIPITATION_MEDIAN).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal wsymb = forecast.getParameter(WEATHER_SYMBOL).orElseThrow(AssertionError::new);
|
||||||
|
|
||||||
|
assertEquals(0, msl.compareTo(BigDecimal.valueOf(1013.7)));
|
||||||
|
assertEquals(0, t.compareTo(BigDecimal.valueOf(3.0)));
|
||||||
|
assertEquals(0, vis.compareTo(BigDecimal.valueOf(24.3)));
|
||||||
|
assertEquals(0, wd.compareTo(BigDecimal.valueOf(110)));
|
||||||
|
assertEquals(0, ws.compareTo(BigDecimal.valueOf(1.5)));
|
||||||
|
assertEquals(0, r.compareTo(BigDecimal.valueOf(96)));
|
||||||
|
assertEquals(0, tstm.compareTo(BigDecimal.valueOf(0)));
|
||||||
|
assertEquals(0, tcc.compareTo(BigDecimal.valueOf(8)));
|
||||||
|
assertEquals(0, lcc.compareTo(BigDecimal.valueOf(8)));
|
||||||
|
assertEquals(0, mcc.compareTo(BigDecimal.valueOf(4)));
|
||||||
|
assertEquals(0, hcc.compareTo(BigDecimal.valueOf(0)));
|
||||||
|
assertEquals(0, gust.compareTo(BigDecimal.valueOf(3.0)));
|
||||||
|
assertEquals(0, pmin.compareTo(BigDecimal.valueOf(0.0)));
|
||||||
|
assertEquals(0, pmax.compareTo(BigDecimal.valueOf(0.0)));
|
||||||
|
assertEquals(0, spp.compareTo(BigDecimal.valueOf(-9)));
|
||||||
|
assertEquals(0, pcat.compareTo(BigDecimal.valueOf(0)));
|
||||||
|
assertEquals(0, pmean.compareTo(BigDecimal.valueOf(0.0)));
|
||||||
|
assertEquals(0, pmedian.compareTo(BigDecimal.valueOf(0.0)));
|
||||||
|
assertEquals(0, wsymb.compareTo(BigDecimal.valueOf(6)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aggregationsTest() {
|
||||||
|
assertNotNull(timeSeries);
|
||||||
|
BigDecimal maxTemp = ForecastAggregator.max(timeSeries, 5, TEMPERATURE).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal minTemp = ForecastAggregator.min(timeSeries, 5, TEMPERATURE).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal maxWind = ForecastAggregator.max(timeSeries, 5, WIND_SPEED).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal minWind = ForecastAggregator.min(timeSeries, 5, WIND_SPEED).orElseThrow(AssertionError::new);
|
||||||
|
BigDecimal totalPrecip = ForecastAggregator.total(timeSeries, 5, PRECIPITATION_MEAN)
|
||||||
|
.orElseThrow(AssertionError::new);
|
||||||
|
|
||||||
|
assertEquals(0, maxTemp.compareTo(BigDecimal.valueOf(7.5)));
|
||||||
|
assertEquals(0, minTemp.compareTo(BigDecimal.valueOf(4.2)));
|
||||||
|
assertEquals(0, maxWind.compareTo(BigDecimal.valueOf(4.4)));
|
||||||
|
assertEquals(0, minWind.compareTo(BigDecimal.valueOf(3.7)));
|
||||||
|
assertEquals(0, totalPrecip.compareTo(BigDecimal.valueOf(2.4)));
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue