1 /**********************************************************************************************
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16 **********************************************************************************************/
17
18 #include "CMainWindow.h"
19 #include "gis/trk/CEnergyCycling.h"
20 #include "gis/trk/CGisItemTrk.h"
21 #include "helpers/CSettings.h"
22
23 /** @brief Constructor - Load parameter from SETTINGS to the track's parameter set
24
25 @param trk The track for which the "Energy Use Cycling" value should computed
26 */
CEnergyCycling(CGisItemTrk & trk)27 CEnergyCycling::CEnergyCycling(CGisItemTrk& trk) :
28 trk(trk)
29 {
30 loadSettings(energyTrkSet);
31 }
32
33 /** @brief Loads parameters from SETTINGS
34
35 On initial start (no parameters are saved in SETTINGS in file QMapShack.conf) the default parameters from header file be used.
36
37 When modifying the parameters and clicking on button "Ok" parameters SETTINGS be saved TO SETTINGS. When loading a track
38 (ex. from GPS device) with Energy Use = NOFLOAT the parameters from SETTING will be used and shown in the dialog.
39
40 When modifying and clicking on "Ok" the parameters will be saved back to SETTINGS file QMapShack.conf.
41
42 SETTINGS will be load either in the energyTrkSet belonging to the track at at initiation
43 or in a energyTmpSet belonging to the Dialog. By clicking "Ok" in the dialog energyTmpSet will be saved back to track’s energyTrkSet.
44
45 By clicking on buttons "Cancel" or "Remove" no changes will be made in track’s energyTrkSet and no SETTINGS will be saved.
46
47 The button “Load previous Set” can be used for the following uses cases:
48 - You load an old track with a computed Energy Use value and you will replace
49 all the parameter with your current active parameter set
50 - You have to “play” to much with all these parameters in the dialog and you
51 are a bit confused, so you can load your last save parameter set
52 - When clicking in history and you will use an older track to start again with modifying
53 you can you also use your latest save parameter set
54 - When you have some tracks in edit mode and you would like to update them all
55 with the same parameter set, edit the first one and use “Load previous Set” for all the others
56
57 @param energySet The parameter set in which the SETTINGS parameter will be load
58 */
loadSettings(CEnergyCycling::energy_set_t & energySet)59 void CEnergyCycling::loadSettings(CEnergyCycling::energy_set_t& energySet)
60 {
61 SETTINGS;
62 cfg.beginGroup("EnergyCycling");
63
64 energy_set_t energyDefaultSet; // to get the default values defined in header
65
66 energySet.driverWeight = cfg.value("personalWeight", energyDefaultSet.driverWeight).toDouble();
67 energySet.bikeWeight = cfg.value("bikeWeight", energyDefaultSet.bikeWeight).toDouble();
68 energySet.airDensity = cfg.value("airDensity", energyDefaultSet.airDensity).toDouble();
69 energySet.windSpeedIndex = cfg.value("windSpeedIndex", energyDefaultSet.windSpeedIndex).toInt();
70 energySet.windSpeed = cfg.value("windSpeed", energyDefaultSet.windSpeed).toDouble();
71 energySet.windPositionIndex = cfg.value("windPositionIndex", energyDefaultSet.windPositionIndex).toInt();
72 energySet.frontalArea = cfg.value("frontalArea", energyDefaultSet.frontalArea).toDouble();
73 energySet.windDragCoeff = cfg.value("windDragCoeff", energyDefaultSet.windDragCoeff).toDouble();
74 energySet.groundIndex = cfg.value("groundIndex", energyDefaultSet.groundIndex).toInt();
75 energySet.rollingCoeff = cfg.value("rollingCoeff", energyDefaultSet.rollingCoeff).toDouble();
76 energySet.pedalCadence = cfg.value("pedalCadence", energyDefaultSet.pedalCadence).toDouble();
77
78 cfg.endGroup();
79 }
80
81 /** @brief Saves parameters to SETTINGS
82
83 @note Will be triggered by clicking on "ok" button in the dialog
84 */
saveSettings()85 void CEnergyCycling::saveSettings()
86 {
87 SETTINGS;
88 cfg.beginGroup("EnergyCycling");
89
90 cfg.setValue("personalWeight", energyTrkSet.driverWeight);
91 cfg.setValue("bikeWeight", energyTrkSet.bikeWeight);
92 cfg.setValue("airDensity", energyTrkSet.airDensity);
93 cfg.setValue("windSpeedIndex", energyTrkSet.windSpeedIndex);
94 cfg.setValue("windSpeed", energyTrkSet.windSpeed);
95 cfg.setValue("windPositionIndex", energyTrkSet.windPositionIndex);
96 cfg.setValue("frontalArea", energyTrkSet.frontalArea);
97 cfg.setValue("windDragCoeff", energyTrkSet.windDragCoeff);
98 cfg.setValue("groundIndex", energyTrkSet.groundIndex);
99 cfg.setValue("rollingCoeff", energyTrkSet.rollingCoeff);
100 cfg.setValue("pedalCadence", energyTrkSet.pedalCadence);
101
102 cfg.endGroup();
103 }
104
105 /** @brief Computes the "Energy Use Cycling" value
106
107 @note
108 Computes only when "Energy Use Cycling" != NOFLOAT, which indicates a no-show in the info panel
109 Computation based directly on the track parameter set
110 */
compute()111 void CEnergyCycling::compute()
112 {
113 if (energyTrkSet.energyKcal != NOFLOAT)
114 {
115 compute(energyTrkSet);
116 }
117 }
118
119 /** @brief Computes the "Energy Use Cycling" value
120 @note
121 Used by dialog to compute temporarily parameter set in all cases
122
123 Related information for the computation process and used formulas:
124 - http://www.blog.ultracycle.net/2010/05/cycling-power-calculations (English)
125 - http://www.cptips.com/energy.htm (English)
126 - http://www.tribology-abc.com/calculators/cycling.htm (English)
127 - http://www.kreuzotter.de/deutsch/speed.htm (German)
128 - http://horst-online.com/physik-des-fahrrads/index.html (German)
129 - http://www.helpster.de/wirkungsgrad-vom-mensch-erklaerung_198168 (German)
130 - http://www.wolfgang-menn.de/motion_d.htm (German)
131 - http://www.msporting.com/planung/5_3_6%20Aerodynamik.htm (German)
132
133 @param energySet The parameter set used for the computation algorithm (input and output values)
134 */
compute(CEnergyCycling::energy_set_t & energySet)135 void CEnergyCycling::compute(CEnergyCycling::energy_set_t& energySet)
136 {
137 if(!isValid())
138 {
139 return;
140 }
141
142 // Input values
143 constexpr qreal joule2Calor = 4.1868;
144 constexpr qreal gravityAccel = 9.81; // kg * m / s2
145 constexpr qreal muscleCoeff = 23; // %
146 constexpr qreal pedalRange = 70; // Degree °
147 constexpr qreal crankLength = 175; // mm
148
149 qreal totalWeight = energySet.driverWeight + energySet.bikeWeight;
150 qreal airDensity = energySet.airDensity;
151 qreal windSpeed = energySet.windSpeed;
152 qreal pedalCadence = energySet.pedalCadence;
153 qreal frontalArea = energySet.frontalArea;
154 qreal windDragCoeff = energySet.windDragCoeff;
155 qreal rollingCoeff = energySet.rollingCoeff;
156
157 // Output values
158 energySet.airResistForce = 0;
159 energySet.rollResistForce = totalWeight * gravityAccel * rollingCoeff;
160 energySet.gravitySlopeForce = 0;
161 energySet.sumForce = 0;
162 energySet.positivePedalForce = 0;
163 energySet.power = 0;
164 energySet.positivePower = 0;
165 energySet.powerMovingTime = 0;
166 energySet.powerMovingTimeRatio = 0;
167 energySet.energyKJoule = 0;
168
169 qint32 cntPowerPoints = 0; // Count the moving track points
170 qint32 cntPositivePowerPoints = 0; // Count the moving track point and positive force to the pedal
171
172 qreal pedalSpeed = crankLength * pedalCadence * 2 * M_PI / 60 / 1000;
173
174 const CTrackData::trkpt_t* lastTrkpt = nullptr;
175
176 for(const CTrackData::trkpt_t& pt : trk.getTrackData())
177 {
178 if(pt.isHidden())
179 {
180 continue;
181 }
182
183 if(lastTrkpt != nullptr) // First track point will not considered
184 {
185 qreal deltaTime = (pt.time.toMSecsSinceEpoch() - lastTrkpt->time.toMSecsSinceEpoch()) / 1000.0;
186 if(deltaTime > 0 && ((pt.deltaDistance / deltaTime) <= 0.2)) // 0.2 ==> to be synchron with deriveSecondaryData()
187 {
188 lastTrkpt = &pt;
189 continue; // Standstill - no moving, track point will not considered
190 }
191
192 qreal slope = pt.slope2;
193 qreal speed = pt.speed;
194
195 qreal airResistForce = 0.5 * windDragCoeff * frontalArea * airDensity * qPow(speed + windSpeed, 2);
196
197 if ((speed + windSpeed) < 0)
198 {
199 airResistForce *= -1;
200 }
201 qreal gravitySlopeForce = totalWeight * gravityAccel * slope / 100;
202 energySet.airResistForce += airResistForce;
203 energySet.gravitySlopeForce += gravitySlopeForce;
204 energySet.sumForce += airResistForce + gravitySlopeForce + energySet.rollResistForce;
205
206 qreal power = (qAbs(airResistForce) * (speed + windSpeed)) + ((energySet.rollResistForce + gravitySlopeForce) * speed);
207 energySet.power += power; // Positive and negative power
208
209 cntPowerPoints++;
210 if (power > 0)
211 {
212 energySet.powerMovingTime += deltaTime;
213 energySet.positivePower += power; // Positive power only
214 energySet.energyKJoule += power * deltaTime / muscleCoeff / 1000 * 100;
215 energySet.positivePedalForce += power / pedalSpeed * 180 / pedalRange;
216 cntPositivePowerPoints++;
217 }
218 }
219 lastTrkpt = &pt;
220 }
221
222 if (cntPowerPoints) // For all moving points
223 {
224 energySet.airResistForce /= cntPowerPoints;
225 energySet.gravitySlopeForce /= cntPowerPoints;
226 energySet.sumForce /= cntPowerPoints;
227 energySet.power /= cntPowerPoints;
228 }
229
230 qreal totalElapsedSecondsMoving = trk.getTotalElapsedSecondsMoving(); // The track moving time
231 if(totalElapsedSecondsMoving)
232 {
233 energySet.powerMovingTimeRatio = (quint32)energySet.powerMovingTime / totalElapsedSecondsMoving;
234 }
235
236 if(cntPositivePowerPoints) // For the moving points with positive force to the pedal
237 {
238 energySet.positivePedalForce /= cntPositivePowerPoints;
239 energySet.positivePower /= cntPositivePowerPoints;
240 }
241 energySet.energyKcal = energySet.energyKJoule / joule2Calor; // The final energy use cycling value to show in the info panel
242 }
243
244 /** @brief Set the "Energy Use Cycling" value to NOFLOAT which indicates a remove
245
246 Updates the info panel to noshow the "Energy Use Cycling" value
247 */
remove()248 void CEnergyCycling::remove()
249 {
250 energyTrkSet.energyKcal = NOFLOAT;
251 trk.updateHistory(CGisItemTrk::eVisualDetails);
252 }
253
254 /** @brief Verifies whether the track is valid to compute the "Energy Use Cycling" value
255
256 @note
257 Time, elevation and slope needs to be valid for computation algorithm. On invaild toolButton in status panel will be disabled.
258 */
isValid() const259 bool CEnergyCycling::isValid() const
260 {
261 if(!trk.isTrkTimeValid() || trk.isTrkElevationInvalid() || trk.isTrkSlopeInvalid())
262 {
263 return false;
264 }
265 return true;
266 }
267
268 /** @brief Sets a parameter set to the track
269
270 @note
271 Will be used:
272 - by serialization to load the parameter from the stream to the track
273 - by the dialog when clicking Ok, saving temporarily parameters back to the track
274
275 @param energyTrkSet The parameter set which will be set to the track
276 @param updateHistory Set to true to trigger an update of history and info panel
277 */
setEnergyTrkSet(const energy_set_t & energyTrkSet,bool updateHistory)278 void CEnergyCycling::setEnergyTrkSet(const energy_set_t& energyTrkSet, bool updateHistory)
279 {
280 this->energyTrkSet = energyTrkSet;
281 if (updateHistory)
282 {
283 trk.updateHistory(CGisItemTrk::eVisualDetails);
284 }
285 }
286