/******************************************************************************* * Copyright (c) 2011 epyx SA. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package ch.windmobile.server.mongo; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import org.joda.time.DateTime; import org.joda.time.MutableDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.windmobile.server.datasourcemodel.DataSourceException; import ch.windmobile.server.datasourcemodel.DataSourceException.Error; import ch.windmobile.server.datasourcemodel.LinearRegression; import ch.windmobile.server.datasourcemodel.WindMobileDataSource; import ch.windmobile.server.datasourcemodel.xml.Chart; import ch.windmobile.server.datasourcemodel.xml.Point; import ch.windmobile.server.datasourcemodel.xml.Serie; import ch.windmobile.server.datasourcemodel.xml.StationData; import ch.windmobile.server.datasourcemodel.xml.StationInfo; import ch.windmobile.server.datasourcemodel.xml.StationLocationType; import ch.windmobile.server.datasourcemodel.xml.StationUpdateTime; import ch.windmobile.server.datasourcemodel.xml.Status; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.Mongo; import com.mongodb.MongoException; public abstract class MongoDataSource implements WindMobileDataSource { protected final Logger log = LoggerFactory.getLogger(getClass()); // 1 hour by default private int historicDuration = 60 * 60; private int windTrendScale = 500000; static enum DataTypeConstant { windDirection("w-dir", "windDirection"), windAverage("w-avg", "windAverage"), windMax("w-max", "windMax"), airTemperature("temp", "airTemperature"), airHumidity("hum", "airHumidity"), airPressure("pres", "airPressure"), rain("rain", "rain"); private final String jsonKey; private final String name; private DataTypeConstant(String jsonKey, String name) { this.jsonKey = jsonKey; this.name = name; } public String getJsonKey() { return jsonKey; } public String getName() { return name; } } protected static DataSourceException exceptionHandler(Throwable e) { if (e instanceof DataSourceException) { return (DataSourceException) e; } else if (e instanceof MongoException) { return new DataSourceException(Error.DATABASE_ERROR, e); } return new DataSourceException(Error.SERVER_ERROR, e); } private Mongo mongoService; private DB database; public MongoDataSource() { try { mongoService = new Mongo(); } catch (UnknownHostException e) { } database = mongoService.getDB("windmobile"); } abstract protected String getProvider(); protected List<String> getStationsFilter() { return null; } private String getStationsCollectionName() { return "stations"; } private BasicDBObject findStationJson(String stationId) throws DataSourceException { DBCollection stationsCollection = database.getCollection(getStationsCollectionName()); BasicDBObject stationJson = (BasicDBObject) stationsCollection.findOne(BasicDBObjectBuilder.start("_id", stationId).get()); if (stationJson != null) { return stationJson; } else { throw new DataSourceException(DataSourceException.Error.INVALID_DATA, "Unable to find station with id '" + stationId + "'"); } } private String getDataCollectionName(String stationId) { return stationId; } private DateTime getLastUpdateDateTime(BasicDBObject lastDataJson) { return new DateTime(lastDataJson.getLong("_id") * 1000); } private List<BasicDBObject> getHistoricData(String stationId, DateTime lastUpdate, int duration) { DBCollection dataCollection = database.getCollection(getDataCollectionName(stationId)); long startTime = lastUpdate.getMillis() - duration * 1000; DBObject query = BasicDBObjectBuilder.start("_id", BasicDBObjectBuilder.start("$gte", startTime / 1000).get()).get(); List<BasicDBObject> datas = new ArrayList<BasicDBObject>(); DBCursor cursor = dataCollection.find(query); while (cursor.hasNext()) { datas.add((BasicDBObject) cursor.next()); } return datas; } @Override public StationUpdateTime getLastUpdate(String stationId) throws DataSourceException { try { BasicDBObject lastDataJson = (BasicDBObject) findStationJson(stationId).get("last"); if (lastDataJson == null) { throw new DataSourceException(DataSourceException.Error.INVALID_DATA, "No last data for station '" + stationId + "'"); } StationUpdateTime returnObject = new StationUpdateTime(); returnObject.setLastUpdate(getLastUpdateDateTime(lastDataJson)); return returnObject; } catch (Exception e) { throw exceptionHandler(e); } } // Replaced by getExpirationDate() @Deprecated private int getDataValidity(DateTime now) { if (isSummerFrequency(now)) { // Summer frequency return 20 * 60; } else { // Winter frequency return 60 * 60; } } static protected boolean isSummerFrequency(DateTime date) { return (date.getMonthOfYear() >= 4) && (date.getMonthOfYear() <= 9); } static protected DateTime getExpirationDate(DateTime now, DateTime lastUpdate) { MutableDateTime expirationDate = new MutableDateTime(lastUpdate.getZone()); if (isSummerFrequency(now)) { expirationDate.setMillis(lastUpdate.getMillis() + 20 * 60 * 1000); if (expirationDate.getHourOfDay() >= 20) { expirationDate.addDays(1); expirationDate.setHourOfDay(8); expirationDate.setMinuteOfHour(0); expirationDate.setSecondOfMinute(0); } } else { expirationDate.setMillis(lastUpdate.getMillis() + 60 * 60 * 1000); if (expirationDate.getHourOfDay() >= 17) { expirationDate.addDays(1); expirationDate.setHourOfDay(9); expirationDate.setMinuteOfHour(0); expirationDate.setSecondOfMinute(0); } } return expirationDate.toDateTime(); } static protected Status getDataStatus(String status, DateTime now, DateTime expirationDate) { // Orange > 10 minutes late DateTime orangeStatusLimit = expirationDate.plus(10 * 60 * 1000); // Red > 2h10 late DateTime redStatusLimit = expirationDate.plus(2 * 3600 * 1000 + 10 * 60 * 1000); if (Status.RED.value().equalsIgnoreCase(status) || now.isAfter(redStatusLimit)) { return Status.RED; } else if (Status.ORANGE.value().equalsIgnoreCase(status) || now.isAfter(orangeStatusLimit)) { return Status.ORANGE; } else { // Handle case when data received are in the future return Status.GREEN; } } private StationInfo createStationInfo(BasicDBObject stationJson) { StationInfo stationInfo = new StationInfo(); stationInfo.setId(stationJson.getString("_id")); stationInfo.setShortName(stationJson.getString("short")); stationInfo.setName(stationJson.getString("name")); stationInfo.setDataValidity(getDataValidity(new DateTime())); stationInfo.setStationLocationType(StationLocationType.TAKEOFF); BasicDBObject locationJson = (BasicDBObject) stationJson.get("loc"); BasicDBList coordinatesJson = (BasicDBList) locationJson.get("coordinates"); stationInfo.setWgs84Longitude((Double)coordinatesJson.get(0)); stationInfo.setWgs84Latitude((Double)coordinatesJson.get(1));; stationInfo.setAltitude(stationJson.getInt("alt")); stationInfo.setMaintenanceStatus(Status.fromValue(stationJson.getString("status"))); return stationInfo; } @Override public List<StationInfo> getStationInfoList(boolean allStation) throws DataSourceException { try { DBCollection stations = database.getCollection(getStationsCollectionName()); List<String> list = new ArrayList<String>(); if (allStation == true) { list.add(Status.RED.value()); list.add(Status.ORANGE.value()); list.add(Status.GREEN.value()); } else { list.add(Status.GREEN.value()); } DBObject query = BasicDBObjectBuilder.start("pv-code", getProvider()).add("status", new BasicDBObject("$in", list)).get(); DBCursor cursor = stations.find(query); List<StationInfo> stationInfoList = new ArrayList<StationInfo>(); while (cursor.hasNext()) { try { BasicDBObject stationJson = (BasicDBObject) cursor.next(); if (getStationsFilter() != null) { String stationId = stationJson.getString("_id"); if (getStationsFilter().contains(stationId)) { stationInfoList.add(createStationInfo(stationJson)); } } else { stationInfoList.add(createStationInfo(stationJson)); } } catch (Exception e) { log.warn("Station was ignored because:", e); } } return stationInfoList; } catch (Exception e) { throw exceptionHandler(e); } } @Override public StationInfo getStationInfo(String stationId) throws DataSourceException { try { return createStationInfo(findStationJson(stationId)); } catch (Exception e) { throw exceptionHandler(e); } } private StationData createStationData(String stationId) throws DataSourceException { BasicDBObject stationJson = findStationJson(stationId); BasicDBObject lastDataJson = (BasicDBObject) stationJson.get("last"); if (lastDataJson == null) { throw new DataSourceException(DataSourceException.Error.INVALID_DATA, "No last data for station '" + stationId + "'"); } StationData stationData = new StationData(); stationData.setStationId(stationId); DateTime lastUpdate = getLastUpdateDateTime(lastDataJson); stationData.setLastUpdate(lastUpdate); DateTime now = new DateTime(); DateTime expirationDate = getExpirationDate(now, lastUpdate); stationData.setExpirationDate(expirationDate); // Status stationData.setStatus(getDataStatus(stationJson.getString("status"), now, expirationDate)); // Wind average stationData.setWindAverage((float) lastDataJson.getDouble(DataTypeConstant.windAverage.getJsonKey())); // Wind max stationData.setWindMax((float) lastDataJson.getDouble(DataTypeConstant.windMax.getJsonKey())); List<BasicDBObject> datas = getHistoricData(stationId, lastUpdate, getHistoricDuration()); if (datas.size() > 0) { // Wind direction chart Serie windDirectionSerie = createSerie(datas, DataTypeConstant.windDirection.getJsonKey()); windDirectionSerie.setName(DataTypeConstant.windDirection.getName()); Chart windDirectionChart = new Chart(); windDirectionChart.setDuration(getHistoricDuration()); windDirectionChart.getSeries().add(windDirectionSerie); stationData.setWindDirectionChart(windDirectionChart); // Wind history min/average double minValue = Double.MAX_VALUE; double maxValue = Double.MIN_VALUE; double sum = 0; double[][] windTrendMaxDatas = new double[datas.size()][2]; // double[][] windTrendAverageDatas = new double[windAverageDatas.size()][2]; for (int i = 0; i < datas.size(); i++) { BasicDBObject data = datas.get(i); // JDC unix-time is in seconds, windmobile java-time in millis long millis = data.getLong("_id") * 1000; double windAverage = data.getDouble(DataTypeConstant.windAverage.getJsonKey()); double windMax = data.getDouble(DataTypeConstant.windMax.getJsonKey()); minValue = Math.min(minValue, windAverage); maxValue = Math.max(maxValue, windMax); sum += windAverage; windTrendMaxDatas[i][0] = millis; windTrendMaxDatas[i][1] = windMax; } stationData.setWindHistoryMin((float) minValue); stationData.setWindHistoryAverage((float) (sum / datas.size())); stationData.setWindHistoryMax((float) maxValue); // Wind trend LinearRegression linearRegression = new LinearRegression(windTrendMaxDatas); linearRegression.compute(); double slope = linearRegression.getBeta1(); double angle = Math.toDegrees(Math.atan(slope * getWindTrendScale())); stationData.setWindTrend((int) Math.round(angle)); } // Air temperature stationData.setAirTemperature((float) lastDataJson.getDouble(DataTypeConstant.airTemperature.getJsonKey(), -1)); // Air humidity stationData.setAirHumidity((float) lastDataJson.getDouble(DataTypeConstant.airHumidity.getJsonKey(), -1)); // Air pressure String key = DataTypeConstant.airPressure.getJsonKey(); if (lastDataJson.containsField(key)) { stationData.setAirPressure((float) lastDataJson.getDouble(key)); } // Rain key = DataTypeConstant.rain.getJsonKey(); if (lastDataJson.containsField(key)) { stationData.setRain((float) lastDataJson.getDouble(key)); } return stationData; } @Override public StationData getStationData(String stationId) throws DataSourceException { try { return createStationData(stationId); } catch (Exception e) { throw exceptionHandler(e); } } private Serie createSerie(List<BasicDBObject> datas, String key) { Serie serie = new Serie(); for (BasicDBObject data : datas) { Point newPoint = new Point(); // JDC unix-time is in seconds, windmobile java-time in millis newPoint.setDate(data.getLong("_id") * 1000); newPoint.setValue(((Number) data.get(key)).floatValue()); serie.getPoints().add(newPoint); } return serie; } @Override public Chart getWindChart(String stationId, int duration) throws DataSourceException { try { Chart windChart = new Chart(); windChart.setStationId(stationId); BasicDBObject lastDataJson = (BasicDBObject) findStationJson(stationId).get("last"); if (lastDataJson == null) { throw new DataSourceException(DataSourceException.Error.INVALID_DATA, "No last data for station '" + stationId + "'"); } DateTime lastUpdate = getLastUpdateDateTime(lastDataJson); windChart.setLastUpdate(lastUpdate); DateTime now = new DateTime(); DateTime expirationDate = getExpirationDate(now, lastUpdate); windChart.setExpirationDate(expirationDate); List<BasicDBObject> datas = getHistoricData(stationId, lastUpdate, duration); // Wind historic chart Serie windAverageSerie = createSerie(datas, DataTypeConstant.windAverage.getJsonKey()); windAverageSerie.setName(DataTypeConstant.windAverage.getName()); Serie windMaxSerie = createSerie(datas, DataTypeConstant.windMax.getJsonKey()); windMaxSerie.setName(DataTypeConstant.windMax.getName()); Serie windDirectionSerie = createSerie(datas, DataTypeConstant.windDirection.getJsonKey()); windDirectionSerie.setName(DataTypeConstant.windDirection.getName()); windChart.setDuration(duration); windChart.getSeries().add(windAverageSerie); windChart.getSeries().add(windMaxSerie); windChart.getSeries().add(windDirectionSerie); return windChart; } catch (Exception e) { throw exceptionHandler(e); } } public int getHistoricDuration() { return historicDuration; } public void setHistoricDuration(int historicDuration) { this.historicDuration = historicDuration; } public int getWindTrendScale() { return windTrendScale; } public void setWindTrendScale(int windTrendScale) { this.windTrendScale = windTrendScale; } }