Mercurial > dive4elements > river
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 } |