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