comparison artifacts/src/main/java/org/dive4elements/river/artifacts/services/DynamicMainValuesService.java @ 9392:e4a6679b868f

Implemented first approach of bundu dynamic main value calculation
author mschaefer
date Thu, 09 Aug 2018 18:20:04 +0200
parents 82c67b859aa7
children 0255c51283a4
comparison
equal deleted inserted replaced
9391:2da486c7c05f 9392:e4a6679b868f
7 */ 7 */
8 8
9 package org.dive4elements.river.artifacts.services; 9 package org.dive4elements.river.artifacts.services;
10 10
11 import java.math.BigDecimal; 11 import java.math.BigDecimal;
12 import java.math.RoundingMode;
12 import java.util.ArrayList; 13 import java.util.ArrayList;
13 import java.util.Calendar; 14 import java.util.Calendar;
14 import java.util.Date; 15 import java.util.Date;
15 import java.util.List; 16 import java.util.List;
16 17
17 import org.dive4elements.artifacts.CallMeta; 18 import org.dive4elements.artifacts.CallMeta;
18 import org.dive4elements.artifacts.GlobalContext; 19 import org.dive4elements.artifacts.GlobalContext;
19 import org.dive4elements.artifacts.common.ArtifactNamespaceContext; 20 import org.dive4elements.artifacts.common.ArtifactNamespaceContext;
21 import org.dive4elements.artifacts.common.utils.DateUtils;
20 import org.dive4elements.artifacts.common.utils.XMLUtils; 22 import org.dive4elements.artifacts.common.utils.XMLUtils;
23 import org.dive4elements.river.backend.SessionHolder;
21 import org.dive4elements.river.model.Gauge; 24 import org.dive4elements.river.model.Gauge;
22 import org.dive4elements.river.model.MainValue; 25 import org.dive4elements.river.model.MainValue;
23 import org.dive4elements.river.model.MainValueType; 26 import org.dive4elements.river.model.MainValueType;
24 import org.dive4elements.river.model.MainValueType.MainValueTypeKey; 27 import org.dive4elements.river.model.MainValueType.MainValueTypeKey;
25 import org.dive4elements.river.model.NamedMainValue; 28 import org.dive4elements.river.model.NamedMainValue;
26 import org.dive4elements.river.model.OfficialLine; 29 import org.dive4elements.river.model.OfficialLine;
27 import org.dive4elements.river.model.River; 30 import org.dive4elements.river.model.River;
28 import org.dive4elements.river.model.TimeInterval; 31 import org.dive4elements.river.model.TimeInterval;
32 import org.dive4elements.river.model.sinfo.DailyDischargeValue;
33 import org.dive4elements.river.model.sinfo.DailyDischargeValue.OrderByField;
34 import org.dive4elements.river.utils.DoubleUtil;
35 import org.hibernate.Session;
29 import org.w3c.dom.Document; 36 import org.w3c.dom.Document;
37
38 import gnu.trove.TDoubleArrayList;
30 39
31 /** 40 /**
32 * This service returns the main values of a river's gauge based on the start 41 * This service returns the main values of a river's gauge based on the start
33 * and end point of the river. 42 * and end point of the river.
34 * 43 *
54 final List<MainValue> mainValues = getMainValues(river, gauge, startTime, endTime); 63 final List<MainValue> mainValues = getMainValues(river, gauge, startTime, endTime);
55 64
56 return buildDocument(river, gauge, mainValues, context); 65 return buildDocument(river, gauge, mainValues, context);
57 } 66 }
58 catch (final MainValuesServiceException e) { 67 catch (final MainValuesServiceException e) {
68 // e.printStackTrace();
69 return error(e.getMessage());
70 }
71 catch (final Exception e) {
59 e.printStackTrace(); 72 e.printStackTrace();
60 return error(e.getMessage()); 73 return error(e.getMessage());
61 } 74 }
62 } 75 }
63 76
104 throw new MainValuesServiceException("invalid end year"); 117 throw new MainValuesServiceException("invalid end year");
105 } 118 }
106 } 119 }
107 120
108 /** 121 /**
109 * This method creates the result document that includes the main values of 122 * Computes a gauge's main values for a period of time based on its daily discharges stored in the database
110 * the specified <i>gauge</i>. 123 */
111 * 124 protected List<MainValue> getMainValues(final River river, final Gauge gauge, final Date startTime, final Date endTime) throws MainValuesServiceException {
112 * @param river 125
113 * The river.
114 * @param gauge
115 * The gauge.
116 * @param endYear
117 * @param startYear
118 *
119 * @return a document that includes the main values of the specified river
120 * at the specified gauge.
121 */
122 protected List<MainValue> getMainValues(final River river, final Gauge gauge, final Date startTime, final Date endTime) {
123
124 // TODO: compute our own main values from the discharge timeseries.
125
126 // final List<MainValue> mainValues = gauge.getMainValues();
127 final List<MainValue> mainValues = new ArrayList<>(); 126 final List<MainValue> mainValues = new ArrayList<>();
128 127 computeMainDischargeValues(gauge, startTime, endTime, mainValues);
129 final MainValue myMain = new MainValue();
130
131 // TODO: fetch real NamedMainValue from database: GLQ20, MNQ, MQ, MHQ, HQ5 + Dauerzahlen
132 final NamedMainValue mainValue = new NamedMainValue("Testname", new MainValueType(MainValueTypeKey.Q.getName()));
133 mainValue.setOfficialLines(new ArrayList<OfficialLine>());
134
135 myMain.setMainValue(mainValue);
136 // FIXME: compute value
137 myMain.setValue(new BigDecimal("1234.567"));
138
139 final TimeInterval timeInterval = new TimeInterval(startTime, endTime);
140 myMain.setTimeInterval(timeInterval);
141
142 mainValues.add(myMain);
143
144 return mainValues; 128 return mainValues;
145 } 129 }
130
131 /**
132 * Computes mnq, mq, mhq, hq5 and q(d=0..364) and adds them to a MainValue list
133 */
134 private void computeMainDischargeValues(final Gauge gauge, final Date startTime, final Date endTime, final List<MainValue> mainValues)
135 throws MainValuesServiceException {
136
137 // Query the gauge's daily Q values
138 final List<DailyDischargeValue> qdvs = DailyDischargeValue.getValues(gauge, startTime, endTime, OrderByField.DAY);
139 if (qdvs.isEmpty())
140 throw new MainValuesServiceException("no daily discharge values for gauge " + gauge.getName() + " in the requested time period");
141 // return; // TODO Fehlermeldung
142
143 // Build yearly aggregates
144 final TDoubleArrayList mnqs = new TDoubleArrayList();
145 final TDoubleArrayList mqs = new TDoubleArrayList();
146 int mqcnt = 0;
147 final TDoubleArrayList mhqs = new TDoubleArrayList();
148 for (int i = 0, j = 0; i <= qdvs.size() - 1; i++) {
149 final DailyDischargeValue qdv = qdvs.get(i);
150 if ((i >= 1) && isSameQYear(qdv.getDay(), qdvs.get(i - 1).getDay())) {
151 // Continue aggregating the values of the current year
152 mnqs.set(j, Math.min(mnqs.get(j), qdv.getDischarge().doubleValue()));
153 mhqs.set(j, Math.max(mhqs.get(j), qdv.getDischarge().doubleValue()));
154 mqs.set(j, mqs.get(j) + qdv.getDischarge().doubleValue());
155 mqcnt++;
156 if (i == qdvs.size() - 1)
157 mqs.set(j, mqs.get(j) / mqcnt);
158 }
159 else {
160 // Complete mq aggregation
161 if (mqcnt >= 1) {
162 mqs.set(j, mqs.get(j) / mqcnt);
163 j++;
164 }
165 // Start next year
166 mnqs.add(qdv.getDischarge().doubleValue());
167 mhqs.add(qdv.getDischarge().doubleValue());
168 mqs.add(qdv.getDischarge().doubleValue());
169 mqcnt = 1;
170 }
171 }
172
173 // Compute arithmetic means of the yearly values
174 final Session session = SessionHolder.HOLDER.get();
175 final TimeInterval timeperiod = new TimeInterval(startTime, endTime);
176 final double mnq = DoubleUtil.sum(mnqs.toNativeArray()) / mnqs.size();
177 mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MNQ", session), mnq, timeperiod));
178 final double mq = DoubleUtil.sum(mqs.toNativeArray()) / mqs.size();
179 mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MQ", session), mq, timeperiod));
180 final double mhq = DoubleUtil.sum(mhqs.toNativeArray()) / mhqs.size();
181 mainValues.add(createMainValue(gauge, fetchNamedQMainValue("MHQ", session), mhq, timeperiod));
182
183 // Compute hq5
184 mhqs.sort();
185 final double hq5 = mhqs.get((int) Math.ceil(4 * mhqs.size() / 5));
186 mainValues.add(createMainValue(gauge, fetchNamedQMainValue("HQ5", session), hq5, timeperiod));
187
188 // Query the gauge's daily Q values and build a list sorted by ascending Q
189 final TDoubleArrayList qs = new TDoubleArrayList();
190 for (final DailyDischargeValue qdv : qdvs)
191 qs.add(qdv.getDischarge().doubleValue());
192 qs.sort();
193
194 // Step through the sorted Q list and get the duration discharges
195 final int yearCnt = DateUtils.getYearFromDate(endTime) - DateUtils.getYearFromDate(startTime) + 1;
196 double glq20 = Double.NaN;
197 for (int i = 0, k = 0; (i <= 364) && (k <= qs.size() - 1); i++, k += yearCnt) {
198 final NamedMainValue nmv = fetchNamedQMainValue(i, session, mainValues.get(0).getMainValue().getType());
199 if (nmv != null)
200 mainValues.add(createMainValue(gauge, nmv, getDurationQ(qs, k), timeperiod));
201 if (i == 20)
202 glq20 = qs.get(k);
203 }
204 mainValues.add(createMainValue(gauge, fetchNamedQMainValue("GlQ", session), glq20, timeperiod));
205 }
206
207 /**
208 * Checks year equality of two dates (calendar year)
209 */
210 private boolean isSameQYear(final Date a, final Date b) {
211 final Calendar ca = Calendar.getInstance();
212 ca.setTime(a);
213 final Calendar cb = Calendar.getInstance();
214 cb.setTime(b);
215 return (ca.get(Calendar.YEAR) == cb.get(Calendar.YEAR));
216 }
217
218 /**
219 * Fetches a named main Q value from the database, if existing
220 */
221 private NamedMainValue fetchNamedQMainValue(final String name, final Session session) {
222 final NamedMainValue nmv = NamedMainValue.fetchByNameAndType(name, MainValueTypeKey.Q.getName(), session);
223 if (nmv != null)
224 nmv.setOfficialLines(new ArrayList<OfficialLine>());
225 return nmv;
226 }
227
228 /**
229 * Fetches a named main Q(duration) value from the database, if existing
230 */
231 private NamedMainValue fetchNamedQMainValue(final int days, final Session session, final MainValueType qType) {
232 final NamedMainValue nmv = NamedMainValue.fetchByNameAndType(Integer.toString(days), MainValueTypeKey.DURATION.getName(), session);
233 // final NamedMainValue nmv = new NamedMainValue(Integer.toString(days), qType);
234 if (nmv != null)
235 nmv.setOfficialLines(new ArrayList<OfficialLine>());
236 return nmv;
237 }
238
239 /**
240 * Creates a main value for a main value name
241 */
242 private MainValue createMainValue(final Gauge gauge, final NamedMainValue nmv, final double value, final TimeInterval timeperiod) {
243 return new MainValue(gauge, nmv, BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_EVEN), timeperiod); // TODO Scale per Formatter-Konstante o.รค.
244 }
245
246 /**
247 * Gets the q from a list at a list position, or the next larger q if the immediate successors are equal
248 */
249 private double getDurationQ(final TDoubleArrayList qs, final int i) {
250 for (int j = i; j + 1 <= qs.size() - 1; j++)
251 if (qs.get(j + 1) > qs.get(j) + 0.001)
252 return qs.get(j);
253 // TODO Increment q on third significant digit
254 return qs.get(qs.size() - 1);
255 }
146 } 256 }

http://dive4elements.wald.intevation.org