/** * Database key * @return string */ public function column() { if (\Runalyze\Configuration::Vdot()->useElevationCorrection()) { return 'vdot_with_elevation'; } return 'vdot'; }
/** * Get VDOT with elevation * @return double vdot with elevation influence */ public function getCurrentlyUsedVdot() { return Configuration::Vdot()->useElevationCorrection() && $this->getVdotWithElevation() > 0 ? $this->getVdotWithElevationCorrected() : $this->getVdotCorrected(); }
/** * Recalculate VDOT shape * @return float new shape */ public function recalculateVDOTshape() { $Shape = new JD\Shape(DB::getInstance(), SessionAccountHandler::getId(), Configuration::General()->runningSport(), Configuration::Vdot()); $Shape->setCorrector(new JD\VDOTCorrector($this->vdotFactor())); $Shape->calculate(); $this->updateVdotShape($Shape->value()); return $Shape->value(); }
GROUP BY `index`')->fetchAll(); // Cache::set('calculationsPlotData'.$Year.$All.$lastHalf.$lastYear, $Data, '300'); //} foreach ($Data as $dat) { $index = $dat['index'] + $AddDays; $Trimps_raw[$index] = 1 * $dat['trimp']; if ($dat['vdot'] != 0) { $VDOTs_raw[$index] = $dat['vdot']; // Remember: These values are already multiplied with `s` $Durations_raw[$index] = (double) $dat['s']; } } $StartDayInYear = $All || $lastHalf || $lastYear ? Time::diffInDays($StartTime, mktime(0, 0, 0, 1, 1, $StartYear)) + 1 : 0; $LowestIndex = $AddDays + 1; $HighestIndex = $AddDays + 1 + $NumberOfDays; $VDOTdays = Configuration::Vdot()->days(); $ATLdays = Configuration::Trimp()->daysForATL(); $CTLdays = Configuration::Trimp()->daysForCTL(); $TSBModel = new Runalyze\Calculation\Performance\TSB($Trimps_raw, $CTLdays, $ATLdays); $TSBModel->calculate(); if ($All) { $maxATL = $TSBModel->maxFatigue(); $maxCTL = $TSBModel->maxFitness(); if ($maxATL != Configuration::Data()->maxATL()) { Configuration::Data()->updateMaxATL($maxATL); } if ($maxCTL != Configuration::Data()->maxCTL()) { Configuration::Data()->updateMaxCTL($maxCTL); } } else { $maxATL = Configuration::Data()->maxATL();
/** * All categories * @return ConfigurationCategory[] */ private function allCategories() { return array(Configuration::General(), Configuration::Privacy(), Configuration::ActivityView(), Configuration::ActivityForm(), Configuration::Design(), Configuration::DataBrowser(), Configuration::Vdot(), Configuration::Trimp(), Configuration::BasicEndurance(), Configuration::Misc()); }
/** * Update vdot shape and corrector */ protected function updateVDOTshapeAndCorrector() { $timestampLimit = time() - Configuration::Vdot()->days() * DAY_IN_S; if ($this->Object->usesVDOT() && $this->Object->vdotByHeartRate() > 0 && $this->Object->timestamp() > $timestampLimit) { Configuration::Data()->recalculateVDOTshape(); if ($this->Object->typeid() == Configuration::General()->competitionType()) { Configuration::Data()->recalculateVDOTcorrector(); } } }
$Data = DB::getInstance()->query(' SELECT YEAR(FROM_UNIXTIME(`time`)) as `y`, MONTH(FROM_UNIXTIME(`time`)) as `m`, DAY(FROM_UNIXTIME(`time`)) as `d`, SUM(' . JD\Shape::mysqlVDOTsum($withElevation) . ')/SUM(' . JD\Shape::mysqlVDOTsumTime($withElevation) . ') as `vdot` FROM `' . PREFIX . 'training` WHERE `vdot`>0 AND use_vdot<>0 GROUP BY `y`, `m`, `d` ORDER BY `y` ASC, `m` ASC, `d` ASC')->fetchAll(); Cache::set('prognosePlotData', $Data, '300'); } if (!empty($Data)) { $StartTime = mktime(12, 0, 0, $Data[0]['m'], $Data[0]['d'], $Data[0]['y']); $windowWidth = Configuration::Vdot()->days(); $VDOTs = [$Data[0]['vdot']]; $Indices = [0]; // A 'prefix' of 15 days is needed to use uniform kernel only as 'rear mirror' foreach ($Data as $dat) { $VDOTs[] = $dat['vdot']; $Indices[] = 15 + Time::diffInDays($StartTime, mktime(12, 0, 0, $dat['m'], $dat['d'], $dat['y'])); } $MovingAverage = new WithKernel($VDOTs, $Indices); $MovingAverage->setKernel(new Kernel\Uniform($windowWidth)); $MovingAverage->calculate(); foreach ($MovingAverage->movingAverage() as $i => $value) { if ($i > 0) { // TODO: use correct GA $Strategy->setVDOT(Configuration::Data()->vdotFactor() * $value); $index = $StartTime + DAY_IN_S * ($Indices[$i] - 15);
/** * @param string $key * @return string */ private static function queryForVdot($key) { $Sum = \Runalyze\Configuration::Vdot()->useElevationCorrection() ? 'IF(`vdot_with_elevation`>0,`vdot_with_elevation`,`vdot`)*`s`' : '`vdot`*`s`'; return 'SUM(IF(`use_vdot`=1 AND `vdot`>0,' . $Sum . ',0))/SUM(IF(`use_vdot`=1 AND `vdot`>0,`s`,0)) as `' . $key . '`'; }
/** * Display with corrector */ protected function displayWithElevation() { if ($this->Context->hasRoute() && ($this->Context->route()->elevationUp() > 0 || $this->Context->route()->elevationDown())) { $up = $this->Context->route()->elevationUp(); $down = $this->Context->route()->elevationDown(); } else { $up = $this->Context->activity()->elevation(); $down = $up; } $Modifier = new Elevation\DistanceModifier($this->Context->activity()->distance(), $up, $down, Configuration::Vdot()); $VDOT = new JD\VDOT(0, new JD\VDOTCorrector(Configuration::Data()->vdotFactor())); $VDOT->fromPaceAndHR($Modifier->correctedDistance(), $this->Context->activity()->duration(), $this->Context->activity()->hrAvg() / Configuration::Data()->HRmax()); $Fieldset = new FormularFieldset(__('Correction: considering elevation')); $Fieldset->setHtmlCode(' <p class="warning small ' . (Configuration::Vdot()->useElevationCorrection() ? 'hide' : '') . '"> ' . __('This correction method is currently unused.') . ' </p> <div class="w50"> <label>' . __('Up/Down') . '</label> <span class="as-input">+' . $up . '/-' . $down . ' m</span> </div> <div class="w50 double-height-right"> <label>⇒ ' . __('VDOT') . '</label> <span class="as-input ' . (!Configuration::Vdot()->useElevationCorrection() ? '' : 'highlight') . '">' . $VDOT->value() . '</span> </div> <div class="w50"> <label>' . __('Influence') . '</label> <span class="as-input">' . sprintf("%+d", 1000 * $Modifier->additionalDistance()) . 'm = ' . Distance::format($Modifier->correctedDistance(), false, 3) . '</span> </div> '); $Fieldset->display(); }
/** * Init complete data */ private function initCompleteData() { $withElevation = Configuration::Vdot()->useElevationCorrection(); $Query = ' SELECT SUM(`s`) as `s`, SUM(IF(`distance`>0,`s`,0)) as `s_sum_with_distance`, SUM(`distance`) as `distance`, SUM(' . JD\Shape::mysqlVDOTsum($withElevation) . ')/SUM(' . JD\Shape::mysqlVDOTsumTime($withElevation) . ') as `vdot`, SUM(`trimp`) as `trimp`, SUM(`jd_intensity`) as `jd_intensity`, ' . $this->getTimerIndexForQuery() . ' as `i` FROM `' . PREFIX . 'training` WHERE `accountid`=:sessid ' . $this->getSportAndYearDependenceForQuery(); $Query .= ' GROUP BY ' . $this->getTimerForOrderingInQuery() . ' ASC'; $Request = DB::getInstance()->prepare($Query); $Request->bindValue('sessid', SessionAccountHandler::getId(), PDO::PARAM_INT); $Request->execute(); $this->CompleteData = $Request->fetchAll(); }
/** * Get string for selecting dataset in query * @return string */ public function getQuerySelectForSet() { $String = ''; $Sum = Configuration::Vdot()->useElevationCorrection() ? 'IF(`vdot_with_elevation`>0,`vdot_with_elevation`,`vdot`)*`s`' : '`vdot`*`s`'; $showVdot = 0; foreach ($this->data as $set) { if ($set['summary'] == 1) { if ($set['name'] == 'vdot' || $set['name'] == 'vdoticon') { $showVdot = 1; } elseif ($set['name'] == 'temperature') { $String .= ', ' . $set['summary_mode'] . '(NULLIF(`' . $set['name'] . '`,0)) as `' . $set['name'] . '`'; } elseif ($set['name'] != 'pace') { if ($set['summary_mode'] != 'AVG') { $String .= ', ' . $set['summary_mode'] . '(`' . $set['name'] . '`) as `' . $set['name'] . '`'; } else { $String .= ', SUM(`s`*`' . $set['name'] . '`*(`' . $set['name'] . '` > 0))' . '/SUM(`s`*(`' . $set['name'] . '` > 0)) as `' . $set['name'] . '`'; } } } } if ($showVdot) { $String .= ', SUM(IF(`use_vdot`=1 AND `vdot`>0,' . $Sum . ',0))/SUM(IF(`use_vdot`=1 AND `vdot`>0,`s`,0)) as `vdot`'; } return $String; }
$WKplugin = $Factory->newInstance('RunalyzePluginStat_Wettkampf'); } if (!isset($distance)) { $distance = 10; } $DataFailed = false; $Prognosis = array(); $Results = array(); $Strategy = new Prognosis\Daniels(); $Strategy->adjustVDOT(false); $PrognosisObj = new Prognosis\Prognosis(); $PrognosisObj->setStrategy($Strategy); if (START_TIME != time()) { $Data = Cache::get('prognosePlotData'); if (is_null($Data)) { $withElevation = Configuration::Vdot()->useElevationCorrection(); $Data = DB::getInstance()->query(' SELECT YEAR(FROM_UNIXTIME(`time`)) as `y`, MONTH(FROM_UNIXTIME(`time`)) as `m`, SUM(' . JD\Shape::mysqlVDOTsum($withElevation) . ')/SUM(' . JD\Shape::mysqlVDOTsumTime($withElevation) . ') as `vdot` FROM `' . PREFIX . 'training` WHERE `vdot`>0 AND use_vdot<>0 GROUP BY `y`, `m` ORDER BY `y` ASC, `m` ASC')->fetchAll(); Cache::set('prognosePlotData', $Data, '300'); } foreach ($Data as $dat) { // TODO: use correct GA $Strategy->setVDOT(Configuration::Data()->vdotFactor() * $dat['vdot']);
/** * Prognosis time by heart rate after correction * @return string */ public function prognosisByHRafterCorrection() { if (Configuration::Vdot()->useCorrectionFactor()) { $Prognosis = new Daniels(); $Prognosis->adjustVDOT(false); $Prognosis->setVDOT(Configuration::Data()->vdotFactor() * $this->Activity->vdotByHeartRate()); return $this->formatTime($Prognosis->inSeconds($this->Activity->distance())); } return '-'; }
/** * Init conditions fieldset */ protected function initConditions() { $this->addConditionFieldWithChosen('typeid', 'type', 'name', __('Type'), __('Choose activity type(s)')); $this->addConditionFieldWithChosen('weatherid', 'weather', 'name', __('Weather'), __('Choose weather conditions')); $this->addConditionFieldWithChosen('equipmentid', 'equipment', 'name', __('Equipment'), __('Choose equipment')); $this->addConditionFieldWithChosen('tagid', 'tag', 'tag', __('Tag'), __('Choose tag')); $this->addFieldNotes(); $this->addNumericConditionField('distance', __('Distance'), FormularInput::$SIZE_SMALL, Configuration::General()->distanceUnitSystem()->distanceUnit()); $this->addNumericConditionField('elevation', __('Elevation'), FormularInput::$SIZE_SMALL, Configuration::General()->distanceUnitSystem()->elevationUnit()); $this->addStringConditionField('route', __('Route'), FormularInput::$SIZE_MIDDLE); $this->addDurationField('s', __('Duration')); $this->addNumericConditionField('temperature', __('Temperature'), FormularInput::$SIZE_SMALL, Configuration::General()->temperatureUnit()->unit()); $this->addNumericConditionField('humidity', __('Humidity'), FormularInput::$SIZE_SMALL, (new Humidity())->unit()); $this->addNumericConditionField('pressure', __('Pressure'), FormularInput::$SIZE_SMALL, (new Pressure())->unit()); $this->addNumericConditionField('wind_speed', __('Wind Speed'), FormularInput::$SIZE_SMALL, (new WindSpeed())->unit()); $this->addStringConditionField('comment', __('Title'), FormularInput::$SIZE_MIDDLE); $this->addNumericConditionField('pulse_avg', __('avg. HR'), FormularInput::$SIZE_SMALL, FormularUnit::$BPM); $this->addNumericConditionField('kcal', __('Calories'), FormularInput::$SIZE_SMALL, FormularUnit::$KCAL); $this->addStringConditionField('partner', __('Partner'), FormularInput::$SIZE_MIDDLE); $this->addNumericConditionField('pulse_max', __('max. HR'), FormularInput::$SIZE_SMALL, FormularUnit::$BPM); $this->addNumericConditionField('cadence', __('Cadence'), FormularInput::$SIZE_SMALL, FormularUnit::$SPM); $this->addBooleanField('is_public', __('Is public')); $this->addNumericConditionField('jd_intensity', __('JD points'), FormularInput::$SIZE_SMALL); $this->addNumericConditionField('groundcontact', __('Ground contact'), FormularInput::$SIZE_SMALL, FormularUnit::$MS); $this->addNumericConditionField('groundcontact_balance', __('Ground Contact Balance'), FormularInput::$SIZE_SMALL, 'L' . FormularUnit::$PERCENT); $this->addNumericConditionField('vertical_oscillation', __('Vertical oscillation'), FormularInput::$SIZE_SMALL, FormularUnit::$CM); $this->addNumericConditionField('vertical_ratio', __('Vertical ratio'), FormularInput::$SIZE_SMALL, FormularUnit::$PERCENT); $this->addNumericConditionField('stride_length', __('Stride length'), FormularInput::$SIZE_SMALL, Configuration::General()->distanceUnitSystem()->strideLengthUnit()); if (Configuration::Vdot()->useElevationCorrection()) { $this->addNumericConditionField('vdot_with_elevation', __('VDOT'), FormularInput::$SIZE_SMALL); } else { $this->addNumericConditionField('vdot', __('VDOT'), FormularInput::$SIZE_SMALL); } $this->addBooleanField('use_vdot', __('Uses VDOT')); $this->addNumericConditionField('trimp', __('TRIMP'), FormularInput::$SIZE_SMALL); }
/** * Get order * @return string */ protected function getOrder() { $sort = !isset($_POST['search-sort-by']) || array_key_exists($_POST['search-sort-by'], $this->AllowedKeys) ? '`time`' : $this->DB->escape($_POST['search-sort-by'], false); $order = !isset($_POST['search-sort-order']) ? 'DESC' : $this->DB->escape($_POST['search-sort-order'], false); if ($sort == 'vdot' && Configuration::Vdot()->useElevationCorrection()) { return ' ORDER BY IF(`t`.`vdot_with_elevation`>0, `t`.`vdot_with_elevation`, `t`.`vdot`) ' . $order; } if ($sort == 'pace') { return ' ORDER BY IF(`t`.`distance`>0, `t`.`s`/`t`.`distance`, 0) ' . $order; } return ' ORDER BY `t`.' . $sort . ' ' . $order; }
/** * Value of used VDOT (uncorrected) * @return float */ public function usedVdot() { if (Configuration::Vdot()->useElevationCorrection()) { if ($this->Activity->vdotWithElevation() > 0) { return $this->Activity->vdotWithElevation(); } } return $this->Activity->vdotByHeartRate(); }
/** * Calculate VDOT by heart rate with elevation influence * @param int $up * @param int $down * @return float */ public function calculateVDOTbyHeartRateWithElevationFor($up, $down) { $Modifier = new Elevation\DistanceModifier($this->Activity->distance(), $up, $down, Configuration::Vdot()); return $this->calculateVDOTbyHeartRate($Modifier->correctedDistance()); }
/** * Get fieldset for VDOT * @return \FormularFieldset */ public function getFieldsetVDOT() { $Table = '<table class="fullwidth zebra-style"> <thead> <tr> <th colspan="10">' . sprintf(__('VDOT values of the last %s days'), Configuration::Vdot()->days()) . '</th> </tr> </thead> <tbody class="top-and-bottom-border">'; $Tooltip = new Tooltip(''); $VDOT = new VDOT(0, new VDOTCorrector(Configuration::Data()->vdotFactor())); $VDOTs = DB::getInstance()->query('SELECT `id`,`time`,`distance`,IF(`vdot_with_elevation`>0,`vdot_with_elevation`,`vdot`) as `vdot` FROM `' . PREFIX . 'training` WHERE time>=' . (time() - Configuration::Vdot()->days() * DAY_IN_S) . ' AND vdot>0 AND use_vdot=1 AND accountid = ' . SessionAccountHandler::getId() . ' ORDER BY time ASC')->fetchAll(); foreach ($VDOTs as $i => $Data) { if ($i % 10 == 0) { $Table .= '<tr>'; } $Tooltip->setText(date('d.m.Y', $Data['time']) . ': ' . Distance::format($Data['distance'])); $VDOT->setValue($Data['vdot']); $Table .= '<td ' . $Tooltip->attributes() . '>' . Ajax::trainingLink($Data['id'], $VDOT->value()) . '</td>'; if ($i % 10 == 9) { $Table .= '</tr>'; } } if (count($VDOTs) % 10 != 0) { $Table .= HTML::emptyTD(10 - count($VDOTs) % 10); } $Table .= '</tbody></table>'; $Fieldset = new FormularFieldset(__('VDOT')); $Fieldset->addBlock(sprintf(__('The VDOT value is the average, weighted by the time, of the VDOT of your activities in the last %s days.'), Configuration::Vdot()->days())); $Fieldset->addBlock(sprintf(__('Your current VDOT shape: <strong>%s</strong><br> '), Configuration::Data()->vdot())); $Fieldset->addBlock($Table); $Fieldset->addInfo(__('Jack Daniels uses VDOT as a fixed value and not based on the training progress.<br>' . 'We do instead predict the VDOT from all activities based on the heart rate. ' . 'These formulas are derived from Jack Daniels\' tables as well.')); return $Fieldset; }
/** * Update vdot shape and corrector */ protected function updateVDOTshapeAndCorrector() { $timestampLimit = time() - Configuration::Vdot()->days() * DAY_IN_S; if ($this->hasChanged(Entity::USE_VDOT) && ($this->NewObject->timestamp() >= $timestampLimit || $this->knowsOldObject() && $this->OldObject->timestamp() > $timestampLimit) || $this->NewObject->usesVDOT() && ($this->hasChanged(Entity::VDOT) || $this->hasChanged(Entity::VDOT_WITH_ELEVATION) || $this->hasChanged(Entity::TIMESTAMP) && $this->knowsOldObject() && ($this->NewObject->timestamp() >= $timestampLimit && $this->OldObject->timestamp() < $timestampLimit || $this->NewObject->timestamp() < $timestampLimit && $this->OldObject->timestamp() >= $timestampLimit))) { Configuration::Data()->recalculateVDOTshape(); } if (($this->NewObject->usesVDOT() || $this->hasChanged(Entity::USE_VDOT)) && ($this->NewObject->typeid() == Configuration::General()->competitionType() || $this->knowsOldObject() && $this->OldObject->typeid() == Configuration::General()->competitionType())) { Configuration::Data()->recalculateVDOTcorrector(); } }
?> <p class="info"> <?php _e('<strong>VDOT/Time (Shape):</strong> by your shape at that time<br>'); ?> <?php _e('The time is the prognosis by Runalyze.'); ?> </p> <p class="info"> <?php _e('<strong>Corrector:</strong> Ratio between VDOT and VDOT (by HR)'); ?> </p> <?php if (Configuration::Vdot()->useElevationCorrection()) { ?> <p class="warning"> <?php _e('The distance correction for elevation is not used in this table.'); ?> </p> <?php } ?> <?php echo Ajax::wrapJSforDocumentReady('$("#ajax").addClass("big-window");');