Source for file PolarAutoLap.class.php

Documentation is available at PolarAutoLap.class.php

  1. <?php
  2. /**
  3.  * Class to automatize laps generation for distance steps and interval training
  4.  *
  5.  * @author    Jean-Philippe Brunon <jp75018@free.fr>
  6.  * @copyright    2007-2009 Jean-Philippe Brunon
  7.  * @license    http://www.opensource.org/licenses/gpl-license.php GPL
  8.  * @package    php-endurance
  9.  * @version    $Id: PolarAutoLap.class.php 64 2009-03-25 13:42:20Z jp75018 $
  10.  */
  11.  
  12. /**
  13.  * General configuration file
  14.  */
  15. require_once ('conf/config.php');
  16. /**
  17.  * Output is language dependant
  18.  */
  19. require_once ('lang/strings_' $GLOBALS['lang''.inc.php');
  20. /**
  21.  * HTML output (formatting)
  22.  */
  23. require_once ('include/html.php');
  24.  
  25. /**
  26.  * Class to automatize laps generation for distance steps and interval training
  27.  */
  28. {
  29. /**
  30.  * Sport standard alias (use to compute mechanical energy)
  31.  * @var string 
  32.  */
  33.   var    $sport_alias;
  34.  
  35. /**
  36.  * Class constructor
  37.  *
  38.  * @param    string  $sport_alias Standard sport alias
  39.  * @return      void 
  40.  */
  41.   function PolarAutoLap ($sport_alias)
  42.   {
  43.     $this->sport_alias = $sport_alias;
  44.   }
  45.  
  46. /**
  47.  * Generate laps array from detailed HR data for every time or distance step, or
  48.  * for each slope change
  49.  *
  50.  * @param    string    $lap_type Lap type in ('time','distance','altitude')
  51.  * @param    array    $data Heart rate data
  52.  * @param    integer    $interval Recording interval (seconds)
  53.  * @param    integer    $distance Real distance (meters, if speed measure)
  54.  * @param    integer    $rest_hr Rest heart rate of runner
  55.  * @param    integer    $weight Weight of runner (Kg), to compute energy if > 0
  56.  * @param    integer    $step_or_delta_h Lap step (sec*10 or meters) or delta H
  57.  * @param    integer    $vma VMA in km/h to compute physio energy
  58.  * @return    array    Array of laps with same structure as 'plw_lap' SQL table
  59.  */
  60.   function generate_auto_laps($lap_type$data$interval$distance,
  61.     $rest_hr$weight$step_or_delta_h$vma)
  62.   {
  63.     $data_nb count($data);
  64.     $laps array();
  65.  
  66.     switch ($lap_type)
  67.     {
  68.     case 'time' :
  69.       $step $step_or_delta_h;
  70.     // Compute subtotal groups from time step
  71.       switch ($step)
  72.       {
  73.       case 300 :
  74.       case 18000 :
  75.     $step_nb 2;
  76.     break;
  77.       case 600 :
  78.     $step_nb 5;
  79.     break;
  80.       case 3000 :
  81.     $step_nb 6;
  82.     break;
  83.       default :    // 150, 9000, 36000
  84.     $step_nb 4;
  85.       }
  86.       break;
  87.     case 'distance' :
  88.       $step $step_or_delta_h;
  89.     // Compute subtotal groups from distance step
  90.       switch ($step)
  91.       {
  92.       case 50 :
  93.     $step_nb 20;
  94.     break;
  95.       case 100 :
  96.     $step_nb 10;
  97.     break;
  98.       case 1000 :
  99.       case 2000 :
  100.     $step_nb 5;
  101.     break;
  102.       default :    // 500, 5000
  103.     $step_nb 2;
  104.       }
  105.       break;
  106.     default :    // altitude
  107.       $delta_h $step_or_delta_h;
  108.     }
  109.  
  110.     $is_hr = isset($data[0]['hr']);
  111.     $is_speed = isset($data[0]['speed']);
  112.     $is_cad = isset($data[0]['cadence']);
  113.     $is_alti = isset($data[0]['altitude']);
  114.  
  115.     if ($is_speed)
  116.     {
  117.     // Compute ratio to adjust distance
  118.       $d 0;
  119.       for ($i 0$i $data_nb$i++)
  120.       $d += $data[$i]['speed']}
  121.       if ($d 0)
  122.       $d_ratio $distance ($d ($interval 36))}
  123.       else
  124.       $d_ratio 0}
  125.       if ($lap_type == 'altitude')
  126.       {
  127.       // Store cumulated distance to check lap distance not too short
  128.     for ($i 0$i $data_nb$i++)
  129.     {
  130.       $data_dist[$i$data_dist[$i 1+
  131.         $d_ratio $data[$i]['speed'$interval 36;
  132.     }
  133.       }
  134.     }
  135.  
  136.   // If 'altitude' laps, 1st pass to set pointers to data for slope change
  137.     if ($lap_type == 'altitude')
  138.     {
  139.     // Minimum altitude change : 2 * MIN_DELTA_ALTITUDE or 2% of exe delta H
  140.       $min_delta_h MIN_DELTA_ALTITUDE;
  141.       if (($delta_h 50$min_delta_h)
  142.       $min_delta_h $delta_h 50}
  143.  
  144.       if ($is_speed)
  145.       {
  146.       // Minimum lap distance : 100m or 0.25% of total distance
  147.     $min_lap_dist 100;
  148.     if (($distance 400$min_lap_dist)
  149.     $min_lap_dist $distance 400}
  150.       }
  151.       else
  152.       {
  153.       // Minimum lap duration : 20" or 0.25% of total duration
  154.     $exe_elapsed $interval $data_nb;
  155.     $min_lap_time 20;
  156.     if (($exe_elapsed 400$min_lap_time)
  157.     $min_lap_time $exe_elapsed 400}
  158.       }
  159.  
  160.       $slope_tab array();
  161.       $zero_slope array();
  162.     // Detect slope change
  163.       $slopes array(
  164.     -0.20-0.15-0.10-0.08-0.06-0.04-0.02-0.01-0.005,
  165.     00.0050.010.020.040.060.080.100.150.20
  166.       );
  167.       while (list($sl_ind$sleach($slopes))
  168.       {
  169.     $local_min $data[0]['altitude'];
  170.     $local_min_ind 0;
  171.     $local_max $data[0]['altitude'];
  172.     $local_max_ind 0;
  173.     $p_local null;
  174.     $d $d_ratio $interval $data[0]['speed'36;
  175.     for ($i 1$i $data_nb$i++)
  176.     {
  177.     // Pseudo-rotation => Homothety by distance from depart
  178.       if ($is_speed)
  179.       {
  180.       // If no speed : If distance => Constant speed, if not => 10.8 km/h
  181.         if ($distance)
  182.         $d $i $distance $data_nb}
  183.         else
  184.         $d $i $interval 3}    // 3 m/s = 10.8 km/h
  185.       }
  186.       $altitude $data[$i]['altitude'$sl $d;
  187.       if (($altitude >= ($local_min $min_delta_h)) && ($p_local != 'min'))
  188.       {
  189.         if ($local_min_ind 0)
  190.         {
  191.           array_push($slope_tab$local_min_ind);
  192.           if ($sl == 0)    // If slope = 0 => Store for priority
  193.           array_push($zero_slope$local_min_ind)}
  194.         }
  195.         $p_local 'min';
  196.         $local_max $local_min;
  197.         $local_max_ind $local_min_ind;
  198.       }
  199.       if ($altitude $local_max)
  200.       {
  201.         $local_max $altitude;
  202.         $local_max_ind $i;
  203.       }
  204.       if (($altitude <= ($local_max $min_delta_h)) &&
  205.         ($p_local != 'max'))
  206.       {
  207.         if ($local_max_ind 0)
  208.         {
  209.           array_push($slope_tab$local_max_ind);
  210.           if ($sl == 0)    // If slope = 0 => Store for priority
  211.           array_push($zero_slope$local_max_ind)}
  212.         }
  213.         $p_local 'max';
  214.         $local_min $local_max;
  215.         $local_min_ind $local_max_ind;
  216.       }
  217.       if ($altitude $local_min)
  218.       {
  219.         $local_min $altitude;
  220.         $local_min_ind $i;
  221.       }
  222.       if ($altitude $alti_local_min)
  223.       $alti_local_min $altitude}
  224.       if ($is_speed)
  225.       $d += $d_ratio $interval $data[$i]['speed'36}
  226.     }
  227.       }
  228.     // Add last data
  229.       array_push($slope_tab$data_nb 1);
  230.       array_push($zero_slope$data_nb 1);
  231.     // Remove some pointers if too close (loop till nb of points change)
  232.       $slope_nb count($slope_tab);
  233.       $prev_slope_nb $slope_nb 1;
  234.       while ($slope_nb $prev_slope_nb)
  235.       {
  236.     $prev_slope_nb $slope_nb;
  237.     $prev_data_ind 0;
  238.     for ($i 0$i $slope_nb$i++)
  239.     {
  240.       if ($is_speed)
  241.       {
  242.         $lap_too_short (($data_dist[$slope_tab[$i]] -
  243.           $data_dist[$prev_data_ind]$min_lap_disttrue false;
  244.       }
  245.       else
  246.       {
  247.         $lap_too_short ((($slope_tab[$i$prev_data_ind*
  248.           $interval$min_lap_timetrue false;
  249.       }
  250.       if ($lap_too_short)
  251.       {
  252.         if ($i == ($slope_nb 1))
  253.         unset($slope_tab[$i 1])}
  254.         else
  255.         {
  256.           if ((in_array($slope_tab[$i]$zero_slope)) ||
  257.               in_array($prev_data_ind$zero_slope|| ($i == 0))
  258.           unset($slope_tab[$i])}
  259.           if (in_array($slope_tab[$i]$zero_slope&&
  260.               (in_array($prev_data_ind$zero_slope)))
  261.           unset($slope_tab[$i 1])}
  262.         }
  263.       }
  264.       $prev_data_ind $slope_tab[$i];
  265.     }
  266.     sort($slope_tab);
  267.     $slope_nb count($slope_tab);
  268.       }
  269.     }
  270.  
  271.     if ($is_hr)
  272.     {
  273.       $beat_nb 0;
  274.       $hr_ini $data[0]['hr'];
  275.       $hr_min $data[0]['hr'];
  276.       $hr_max $data[0]['hr'];
  277.     }
  278.     if ($is_speed)
  279.     {
  280.       $d 0;
  281.       $d_lap 0;
  282.       $speed_ini $data[0]['speed'];
  283.       $speed_min $data[0]['speed'];
  284.       $speed_max $data[0]['speed'];
  285.       $p_speed 0;
  286.     }
  287.     if ($is_cad)
  288.     {
  289.       $stride_nb 0;
  290.       $cadence_ini $data[0]['cadence'];
  291.       $cadence_min $data[0]['cadence'];
  292.       $cadence_max $data[0]['cadence'];
  293.       $stride_max 0;
  294.     }
  295.     if ($is_alti)
  296.     {
  297.       $alti_ini $data[0]['altitude'];
  298.       $alti_min $data[0]['altitude'];
  299.       $alti_max $data[0]['altitude'];
  300.       $ascend 0;
  301.       $sum_alti 0;
  302.       $alti_local_min $data[0]['altitude'];
  303.     }
  304.     if ($weight)
  305.     {
  306.       $air_en 0;
  307.       $cin_en 0;
  308.     }
  309.  
  310.     $t_lap 0;
  311.     $lap_nb 1;
  312.     for ($i 0$i $data_nb$i++)
  313.     {
  314.       if ($is_speed)
  315.       {
  316.     $speed $data[$i]['speed'];
  317.     $dd $d_ratio $interval $speed 36;
  318.       }
  319.       switch ($lap_type)
  320.       {
  321.       case 'time' :
  322.     $end_lap (($t_lap $interval 10>= $steptrue false;
  323.     break;
  324.       case 'distance' :
  325.     $end_lap (($d_lap $dd>= $steptrue false;
  326.     break;
  327.       default :        // altitude
  328.     $end_lap in_array($i$slope_tab);
  329.       }
  330.       if ($i == ($data_nb 1))
  331.       $end_lap true}
  332.       if ($end_lap)
  333.       {
  334.     switch ($lap_type)
  335.     {
  336.     case 'distance' :
  337.       if ($i == ($data_nb 1))
  338.       {
  339.         $dt $interval 10;
  340.         $lap_d $d_lap $dd;
  341.         $d += $dd;
  342.       }
  343.       else
  344.       {
  345.         $dt (($step $d_lap$dd$interval 10;
  346.         $lap_d $step;
  347.         $d += $step $d_lap;
  348.       }
  349.       break;
  350.     default :    // time, altitude
  351.       $dt $interval 10;
  352.       if ($is_speed)
  353.       {
  354.         $lap_d $d_lap $dd;
  355.         $d += $dd;
  356.       }
  357.     }
  358.     $t_lap += $dt;
  359.     if ($is_hr)
  360.     {
  361.       if ($data[$i]['hr'$hr_min)
  362.       $hr_min $data[$i]['hr']}
  363.       if ($data[$i]['hr'$hr_max)
  364.       $hr_max $data[$i]['hr']}
  365.     }
  366.     if ($is_speed)
  367.     {
  368.       if ($speed $speed_min)
  369.       $speed_min $speed}
  370.       if ($speed $speed_max)
  371.       $speed_max $speed}
  372.     }
  373.     if ($is_cad)
  374.     {
  375.       if ($data[$i]['cadence'$cadence_min)
  376.       $cadence_min $data[$i]['cadence']}
  377.       if ($data[$i]['cadence'$cadence_max)
  378.       $cadence_max $data[$i]['cadence']}
  379.       if ($data[$i]['cadence'0)
  380.       {
  381.         $stride 250 $speed ($data[$i]['cadence']);
  382.         if (($stride $stride_max&& ($stride <= STRIDE_MAX))
  383.         $stride_max $stride}
  384.       }
  385.     }
  386.     if ($is_alti)
  387.     {
  388.       $sum_alti += $data[$i]['altitude'$dt 10;
  389.       if ($data[$i]['altitude'$alti_min)
  390.       $alti_min $data[$i]['altitude']}
  391.       if ($data[$i]['altitude'$alti_max)
  392.       $alti_max $data[$i]['altitude']}
  393.       if ($data[$i]['altitude'>= ($alti_local_min MIN_DELTA_ALTITUDE))
  394.       {
  395.         $ascend += $data[$i]['altitude'$alti_local_min;
  396.         $alti_local_min $data[$i]['altitude'];
  397.       }
  398.       if ($data[$i]['altitude'$alti_local_min)
  399.       $alti_local_min $data[$i]['altitude']}
  400.     }
  401.     if ($weight && $is_speed)
  402.     {
  403.       if ($speed $p_speed)
  404.       {
  405.         switch ($this->sport_alias)
  406.         {
  407.         case 'running' :
  408.         case 'walking' :
  409.           $cin_en += $speed $speed $p_speed $p_speed;
  410.           break;
  411.         }
  412.       }
  413.       if ($speed 0)
  414.       {
  415.         switch ($this->sport_alias)
  416.         {
  417.         case 'running' :
  418.         case 'walking' :
  419.           $air_en += ($dt 10$speed AIR_CST_ENERGY *
  420.         $speed $speed 46656;    // delta_dist * C * V * V
  421.           break;
  422.         }
  423.       }
  424.     }
  425.       // Subtotal every 'step_nb' lap if time or distance,
  426.       // local min / max if 'altitude'
  427.     switch ($lap_type)
  428.     {
  429.     case 'time' :
  430.     case 'distance' :
  431.       $laps[$lap_nb]['subtotal'($lap_nb $step_nb'no':'yes';
  432.       break;
  433.     default :    // altitude
  434.       $laps[$lap_nb]['subtotal'in_array($i$zero_slope'yes':'no';
  435.     }
  436.     $laps[$lap_nb]['elapsed'round($i $interval 10 $dt);
  437.     $laps[$lap_nb]['lap_elapsed'round($t_lap);
  438.     if ($is_speed)
  439.     $laps[$lap_nb]['distance'round($lap_d)}
  440.     if ($is_hr)
  441.     {
  442.       $beat_nb += $data[$i]['hr'$dt 600;
  443.       $laps[$lap_nb]['ini_hr'$hr_ini;
  444.       $laps[$lap_nb]['min_hr'$hr_min;
  445.       $laps[$lap_nb]['avg_hr'round($beat_nb ($t_lap 600)2);
  446.       $laps[$lap_nb]['max_hr'$hr_max;
  447.       $laps[$lap_nb]['end_hr'$data[$i]['hr'];
  448.       $laps[$lap_nb]['beat_sum'$beat_nb;
  449.     }
  450.     if ($is_speed)
  451.     {
  452.       $laps[$lap_nb]['ini_speed'$speed_ini;
  453.       $laps[$lap_nb]['min_speed'$speed_min;
  454.       $laps[$lap_nb]['avg_speed'round(360 $lap_d $t_lap2);
  455.       $laps[$lap_nb]['max_speed'$speed_max;
  456.       $laps[$lap_nb]['end_speed'$speed;
  457.     }
  458.     if ($is_cad)
  459.     {
  460.       $stride_nb += $data[$i]['cadence'$dt 300;
  461.       $laps[$lap_nb]['ini_cadence'$cadence_ini;
  462.       $laps[$lap_nb]['min_cadence'$cadence_min;
  463.       $laps[$lap_nb]['avg_cadence'round($stride_nb ($t_lap 300)2);
  464.       $laps[$lap_nb]['max_cadence'$cadence_max;
  465.       $laps[$lap_nb]['end_cadence'$data[$i]['cadence'];
  466.       if ($stride_nb 0)
  467.       $laps[$lap_nb]['avg_stride'round(($lap_d*100$stride_nb,2)}
  468.       $laps[$lap_nb]['max_stride'round($stride_max2);
  469.     }
  470.     if ($is_alti)
  471.     {
  472.       $laps[$lap_nb]['ini_altitude'$alti_ini;
  473.       $laps[$lap_nb]['min_altitude'$alti_min;
  474.       $laps[$lap_nb]['avg_altitude'round(10 $sum_alti $t_lap);
  475.       $laps[$lap_nb]['max_altitude'$alti_max;
  476.       $laps[$lap_nb]['end_altitude'$data[$i]['altitude'];
  477.       $laps[$lap_nb]['ascend'$ascend;
  478.     }
  479.     if ($weight)
  480.     {
  481.       if ($is_hr)
  482.       {
  483.         $laps[$lap_nb]['physio_energy'=
  484.           round(1000 $weight $vma PHYSIO_CST_POWER *
  485.         ($beat_nb PHYSIO_CST_REST $rest_hr ($t_lap/600)));
  486.       }
  487.       if ($is_speed)
  488.       {
  489.         switch ($this->sport_alias)
  490.         {
  491.         case 'running' :
  492.         case 'walking' :
  493.           $cin_en *= $weight 2592;    // 2592 = 2 * (10 * 3.6)^2
  494.           $cin_en round($cin_en CALORY_2_JOULE);    // In cal
  495.           $mov_en round($weight $lap_d MOVE_CST_ENERGY);
  496.           $grn_en round($weight $t_lap GROUND_CST_ENERGY 10);
  497.           $air_en round($air_en $weight);
  498.           break;
  499.         }
  500.       }
  501.       if ($is_alti)
  502.       {
  503.         switch ($this->sport_alias)
  504.         {
  505.         case 'running' :
  506.         case 'walking' :
  507.           $pot_en_upround($weight $ascend GRAVI_CST CALORY_2_JOULE);
  508.           break;
  509.         }
  510.         $descend $ascend $alti_ini $data[$i]['altitude'];
  511.         if ($descend 0)
  512.         $descend 0}
  513.         switch ($this->sport_alias)
  514.         {
  515.         case 'running' :
  516.         case 'walking' :
  517.           $pot_en_down round(POT_DOWN_USED_CST_ENERGY *
  518.         $weight $descend GRAVI_CST CALORY_2_JOULE);
  519.           break;
  520.         }
  521.       }
  522.       switch ($this->sport_alias)
  523.       {
  524.       case 'running' :
  525.       case 'walking' :
  526.         $laps[$lap_nb]['meca_energy'round($cin_en +
  527.           $pot_en_up $pot_en_down $mov_en $grn_en $air_en);
  528.         break;
  529.       }
  530.     }
  531.     $lap_nb++;
  532.     switch ($lap_type)
  533.     {
  534.     case 'distance' :
  535.       $d_lap $d_lap $dd $step;
  536.       break;
  537.     default :    // time, altitude
  538.       if ($is_speed)
  539.       $d_lap 0}
  540.     }
  541.     $t_lap 10 $interval $dt;
  542.     if ($is_hr)
  543.     {
  544.       $beat_nb $data[$i]['hr'(10 $interval $dt600;
  545.       $hr_ini $data[$i]['hr'];
  546.       $hr_min $data[$i]['hr'];
  547.       $hr_max $data[$i]['hr'];
  548.     }
  549.     if ($is_speed)
  550.     {
  551.       $speed_ini $speed;
  552.       $speed_min $speed;
  553.       $speed_max $speed;
  554.     }
  555.     if ($is_cad)
  556.     {
  557.       $stride_nb $data[$i]['cadence'(10 $interval $dt300;
  558.       $cadence_ini $data[$i]['cadence'];
  559.       $cadence_min $data[$i]['cadence'];
  560.       $cadence_max $data[$i]['cadence'];
  561.       $stride_max 0;
  562.     }
  563.     if ($is_alti)
  564.     {
  565.       $sum_alti $data[$i]['altitude'(10 $interval $dt10;
  566.       $alti_ini $data[$i]['altitude'];
  567.       $alti_min $data[$i]['altitude'];
  568.       $alti_max $data[$i]['altitude'];
  569.       $ascend 0;
  570.     }
  571.     if ($weight)
  572.     {
  573.       $air_en 0;
  574.       $cin_en 0;
  575.     }
  576.     if ($is_speed && ($lap_type == 'distance'))
  577.     {
  578.       $d += $d_lap;
  579.       if ($d >= $distance)    // End of data = end of lap
  580.       break}
  581.     }
  582.       }
  583.       else
  584.       {
  585.     $t_lap += 10 $interval;
  586.     if ($is_speed)
  587.     {
  588.       $d_lap += $dd;
  589.       $d += $dd;
  590.     }
  591.     if ($is_hr)
  592.     {
  593.       $beat_nb += $data[$i]['hr'$interval 60;
  594.       if ($data[$i]['hr'$hr_min)
  595.       $hr_min $data[$i]['hr']}
  596.       if ($data[$i]['hr'$hr_max)
  597.       $hr_max $data[$i]['hr']}
  598.     }
  599.     if ($is_speed)
  600.     {
  601.       if ($speed $speed_min)
  602.       $speed_min $speed}
  603.       if ($speed $speed_max)
  604.       $speed_max $speed}
  605.     }
  606.     if ($is_cad)
  607.     {
  608.       if ($data[$i]['cadence'$cadence_min)
  609.       $cadence_min $data[$i]['cadence']}
  610.       if ($data[$i]['cadence'$cadence_max)
  611.       $cadence_max $data[$i]['cadence']}
  612.       $stride_nb += $data[$i]['cadence'$interval 30;
  613.       if ($data[$i]['cadence'0)
  614.       {
  615.         $stride 250 $speed ($data[$i]['cadence']);
  616.         if (($stride $stride_max&& ($stride <= STRIDE_MAX))
  617.         $stride_max $stride}
  618.       }
  619.     }
  620.     if ($is_alti)
  621.     {
  622.       $sum_alti += $data[$i]['altitude'$interval;
  623.       if ($data[$i]['altitude'$alti_min)
  624.       $alti_min $data[$i]['altitude']}
  625.       if ($data[$i]['altitude'$alti_max)
  626.       $alti_max $data[$i]['altitude']}
  627.       if ($data[$i]['altitude'>= ($alti_local_min MIN_DELTA_ALTITUDE))
  628.       {
  629.         $ascend += $data[$i]['altitude'$alti_local_min;
  630.         $alti_local_min $data[$i]['altitude'];
  631.       }
  632.       if ($data[$i]['altitude'$alti_local_min)
  633.       $alti_local_min $data[$i]['altitude']}
  634.     }
  635.     if ($weight && $is_speed)
  636.     {
  637.       if ($speed $p_speed)
  638.       {
  639.         switch ($this->sport_alias)
  640.         {
  641.         case 'running' :
  642.         case 'walking' :
  643.           $cin_en += $speed $speed $p_speed $p_speed;
  644.           break;
  645.         }
  646.       }
  647.       if ($speed 0)
  648.       {
  649.         switch ($this->sport_alias)
  650.         {
  651.         case 'running' :
  652.         case 'walking' :
  653.           $air_en += $interval $speed AIR_CST_ENERGY *
  654.         $speed $speed 46656;    // delta_dist * C * V * V
  655.           break;
  656.         }
  657.       }
  658.     }
  659.       }
  660.       if ($is_speed)
  661.       $p_speed $speed}
  662.     }
  663.  
  664.   // Last lap is always a subtotal
  665.     $laps[count($laps)]['subtotal''yes';
  666.  
  667.     return $laps;
  668.   }
  669.  
  670. /**
  671.  * Generate laps array from Polar laps and interval training expression
  672.  *
  673.  * Two elements can be separated or not by an implicit recovery.
  674.  * Example : 3000m + 3000m
  675.  *
  676.  * One work or recovery element can consist in several Polar laps. There are 2
  677.  * cases :
  678.  * - Work element : Polar laps are not merged to keep intermediate times.
  679.  * Example : 5000m whith a Polar lap every 1000m
  680.  * - Recovery element : Polar laps are merged.
  681.  *
  682.  * @param    array    $polar_laps Polar laps from database
  683.  * @param    array    $intervals Array of work / recovery elements
  684.  * @param    integer    $vma VMA in km/h (from database or computed),
  685.  *             used to predict minimum speeds for work and
  686.  *             maximum speeds for recovery
  687.  * @return    array    Array of laps with same structure as 'plw_lap' SQL table
  688.  *             or null if Polar laps and interval training expression
  689.  *             do not match
  690.  */
  691.   function generate_interval_training($polar_laps$intervals$vma)
  692.   {
  693. $trace false;
  694. // TODO : Try to "guess" if no speed measure...
  695.   /*
  696.     Use 100% VMA = 6' / 85% VMA = 1H as upper limit with V = V0 - C*Log(D)
  697.     (linear speed decrease with log(distance), V (km/h), distance (km)
  698.     => C = 0.15*VMA / (Log(0.85*VMA) - Log(0.1*VMA))
  699.     => V0 = VMA + C * Log(0.1*VMA)
  700.     Min for work element / Max for recovery element = 2/3 upper limit
  701.   */
  702.     $c 0.15 $vma (log(0.85 $vmalog(0.1 $vma));
  703.     $v0 $vma $c log(0.1 $vma);
  704.  
  705.   // Compute speed and duration for standard distances for linear interpolation
  706.     $std_dist array (
  707.          50,   100,   150,   200,   250,   300,   400,   500,
  708.         600,   800,  1000,  1500,  2000,  2500,  3000,  4000,
  709.        5000,  6000,  80001000015000200002109842195
  710.     );
  711.     for ($i 0$i count($std_dist)$i++)
  712.     {
  713.       $distance $std_dist[$i];
  714.       $max_speed $v0 $c log($std_dist[$i1000);
  715.       $min_time 3.6 $std_dist[$i$max_speed;
  716.       $std_int[$i]['distance'$std_dist[$i];
  717.       $std_int[$i]['speed'$max_speed;
  718.       $std_int[$i]['duration'$min_time;
  719.     }
  720. //print_r($std_int);
  721.  
  722.   // Loop on intervals to compute min work / max recovery speeds
  723.     for ($i 0$i count($intervals)$i++)
  724.     {
  725.     // Work / recovery limit speed = 2/3 of max speed for distance / duration
  726.       if ($intervals[$i]['work_distance'])
  727.       {
  728.           $intervals[$i]['work_min_speed'0.667 $this->_get_max_speed(
  729.       $intervals[$i]['work_distance']'distance'$std_int);
  730.       }
  731.       else
  732.       {
  733.     if ($intervals[$i]['work_duration'])
  734.     {
  735.       $intervals[$i]['work_min_speed'0.667 $this->_get_max_speed(
  736.         $intervals[$i]['work_duration']'duration'$std_int);
  737.     }
  738.       }
  739.       if ($intervals[$i]['reco_distance'])
  740.       {
  741.           $intervals[$i]['reco_max_speed'0.667 $this->_get_max_speed(
  742.       $intervals[$i]['reco_distance']'distance'$std_int);
  743.       }
  744.       else
  745.       {
  746.     if ($intervals[$i]['reco_duration'])
  747.     {
  748.       $intervals[$i]['reco_max_speed'0.667 $this->_get_max_speed(
  749.         $intervals[$i]['reco_duration']'duration'$std_int);
  750.     }
  751.       }
  752.     }
  753.  
  754.   /*
  755.     Build cumulated laps (pyramid of N * (N + 1) values) with distance,
  756.     duration, average speed, and delta with limit speed from duration
  757.     (> 0 => work, < 0 => recovery)
  758.   */
  759.     $laps_copy $polar_laps;
  760.     reset($polar_laps);
  761.     $prev_rank = -1;
  762.     $lap_cum array();
  763.     while (list($rank$lapeach($polar_laps))
  764.     {
  765.       reset($laps_copy);
  766.       $prev_rank_2 = -1;
  767.       while (list($rank_2$lap_2each($laps_copy))
  768.       {
  769.     if ($rank_2 $rank)
  770.     break}
  771.     $lap_cum[$rank][$rank_2]['distance'$lap['distance'];
  772.     $lap_cum[$rank][$rank_2]['duration'$lap['lap_elapsed'];
  773.     if (($prev_rank 0&& ($prev_rank_2 0))
  774.     {
  775.       $lap_cum[$rank][$rank_2]['distance'+=
  776.         $lap_cum[$prev_rank][$prev_rank_2]['distance'];
  777.       $lap_cum[$rank][$rank_2]['duration'+=
  778.         $lap_cum[$prev_rank][$prev_rank_2]['duration'];
  779.     }
  780.     $lap_cum[$rank][$rank_2]['avg_speed'=
  781.       36 $lap_cum[$rank][$rank_2]['distance'/
  782.         $lap_cum[$rank][$rank_2]['duration'];
  783.     $lap_cum[$rank][$rank_2]['delta'=
  784.       $lap_cum[$rank][$rank_2]['avg_speed'0.667 $this->_get_max_speed(
  785.         $lap_cum[$rank][$rank_2]['duration'10'duration'$std_int);
  786.     $prev_rank_2 $rank_2;
  787.       }
  788.       $prev_rank $rank;
  789.     }
  790.     unset($laps_copy);
  791.  
  792.   // Create a list of cumulated laps sorted by 'delta' [N * (N + 1) values]
  793.     $sorted_cum array();
  794.     reset($lap_cum);
  795.     $i 0;
  796.     while (list($rank$lap_sumeach($lap_cum))
  797.     {
  798. if ($trace)
  799. {
  800. printf("%3d.|"$rank);
  801. }
  802.       reset($lap_sum);
  803.       while (list($rank_2$lapeach($lap_sum))
  804.       {
  805.         $sorted_cum[$i++array($rank$rank_2$lap['delta']);
  806. if ($trace)
  807. {
  808. printf(" %5d[%+.2f]"$lap['distance']$lap['delta']);
  809. }
  810.       }
  811. if ($trace)
  812. {
  813. echo "\n    |";
  814. reset($lap_sum);
  815. while (list($rank_2$lapeach($lap_sum))
  816. printf(" %5d       "round($lap['duration'10))}
  817. echo "\n";
  818. }
  819.     }
  820.  
  821. if ($trace)
  822. {
  823. reset($lap_cum);
  824. echo '-----';
  825. while (list($rank$lap_sumeach($lap_cum))
  826. echo '-------------'}
  827. echo "\n";
  828. reset($lap_cum);
  829. echo '     ';
  830. while (list($rank$lap_sumeach($lap_cum))
  831. printf("   %3d.      "$rank)}
  832. echo "\n";
  833.     usort(&$sorted_cumarray($this'_sort_cum_cmp'));
  834. reset($sorted_cum);
  835. for ($i 0$i count($sorted_cum)$i++)
  836. {
  837.   if ($sorted_cum[$i][11)
  838.   $str_end sprintf("-> %2d"$sorted_cum[$i][0])}
  839.   else
  840.   $str_end '     '}
  841.   printf("%4d : %2d %s / %+5.2f / "$i,
  842.     $sorted_cum[$i][0$sorted_cum[$i][11$str_end$sorted_cum[$i][2]);
  843.   $duration $lap_cum[$sorted_cum[$i][0]][$sorted_cum[$i][1]]['duration'10;
  844.   $distance $lap_cum[$sorted_cum[$i][0]][$sorted_cum[$i][1]]['distance'];
  845.   $avg_speed $lap_cum[$sorted_cum[$i][0]][$sorted_cum[$i][1]]['avg_speed'];
  846.   printf("%7.1f s / %5d m / %5.2f %s\n",
  847.     $duration$distance$avg_speed$GLOBALS['str_abbr_speed_km_h']);
  848. }
  849. echo "\n";
  850. for ($i 0$i count($intervals)$i++)
  851. {
  852.   $str_work '                           ';
  853.   if ($intervals[$i]['work_distance'])
  854.   {
  855.     $str_work sprintf("W =%5d m [min:%5.2f %s]",
  856.       $intervals[$i]['work_distance']$intervals[$i]['work_min_speed'],
  857.       $GLOBALS['str_abbr_speed_km_h']);
  858.   }
  859.   if ($intervals[$i]['work_duration'])
  860.   {
  861.     $str_work sprintf("W =%5d s [min:%5.2f %s]",
  862.       $intervals[$i]['work_duration']$intervals[$i]['work_min_speed'],
  863.       $GLOBALS['str_abbr_speed_km_h']);
  864.   }
  865.   $str_reco '                           ';
  866.   if ($intervals[$i]['reco_distance'])
  867.   {
  868.     $str_reco sprintf("R =%5d m [max:%5.2f %s]",
  869.       $intervals[$i]['reco_distance']$intervals[$i]['reco_max_speed'],
  870.       $GLOBALS['str_abbr_speed_km_h']);
  871.   }
  872.   if ($intervals[$i]['reco_duration'])
  873.   {
  874.     $str_reco sprintf("R =%5d s [max:%5.2f %s]",
  875.       $intervals[$i]['reco_duration']$intervals[$i]['reco_max_speed'],
  876.       $GLOBALS['str_abbr_speed_km_h']);
  877.   }
  878.   printf("%2d : %2d x { %s / %s }\n"$i$intervals[$i]['repeat_nb'],
  879.     $str_work$str_reco);
  880. }
  881. echo "\n";
  882. reset($polar_laps);
  883. while (list($rank$lapeach($polar_laps))
  884. {
  885.   printf("%3d : %7.1f s / %5d m / %4.1f %s\n"$rank,
  886.     $lap['lap_elapsed'10$lap['distance']$lap['avg_speed'10,
  887.     $GLOBALS['str_abbr_speed_km_h']);
  888. }
  889. //print_r($intervals);
  890. //print_r($polar_laps);
  891. //print_r($sorted_cum);
  892. }
  893.  
  894.     reset($polar_laps);
  895.   /*
  896.     Loop on intervals to find candidate lap groups in cumulated laps
  897.     Good candidates :
  898.     1. closely match interval distance or duration within a tolerance
  899.        (tolerance is larger if distance / duration is small),
  900.     2. speed >= min work speed if work, speed <= max recovery speed if recovery
  901.     3. Sort candidates having max 'delta' if work, min delta if recovery.
  902.     Notes :
  903.     - Unknown recovery possible (work with > 1 repeat, null recovery).
  904.     - Polar laps not in intervals are merged
  905.     - Keep laps if more than 1 Polar lap in interval =>
  906.       . Guess exact lap distance if interval distance with proportion / rounding
  907.       . If not possible => Merge Polar laps
  908.     Success only if all intervals are matched in right order
  909.   */
  910.     for ($i 0$i count($intervals)$i++)
  911.     {
  912. //print_r($intervals[$i]);
  913.       $sum_elapsed 0;
  914.       $sum_distance 0;
  915.       $lap_set array();
  916.       for ($rep 0$rep $intervals[$i]['repeat_nb']$rep++)
  917.       {
  918. //echo "$rep.\n";
  919.     $state 'work';
  920.     if ($intervals[$i]['work_distance'])
  921.     $value $intervals[$i]['work_distance']$unit 'distance'}
  922.     else
  923.     {
  924.       if ($intervals[$i]['work_duration'])
  925.       $value $intervals[$i]['work_duration']$unit 'duration'}
  926.       else
  927.       {
  928.         $state 'reco';
  929.         if ($intervals[$i]['reco_distance'])
  930.         $value $intervals[$i]['reco_distance']$unit 'distance'}
  931.         else
  932.         $value $intervals[$i]['reco_duration']$unit 'duration'}
  933.       }
  934.     }
  935.     if ((list($rank$lapeach($polar_laps)))
  936.     return null}
  937.     $lap_set[$rank$lap;
  938.     $sum_duration += $lap['lap_elapsed'];
  939.     $sum_distance += $lap['distance'];
  940.     if ($state == 'work')
  941.     {
  942.     }
  943.  
  944.       // Caution : Recovery of last repetition is optional !
  945.       }
  946.     }
  947. if ($trace)
  948. {
  949. reset($polar_laps);
  950. print_r($polar_laps);
  951. //print_r($this->_merge_laps($polar_laps));
  952. }
  953. return $polar_laps;    // Do nothing !
  954.   }
  955.  
  956. /**
  957.  * Merge several laps to one lap
  958.  *
  959.  * @param    array    $laps Array of laps to merge (>=1 elements)
  960.  * @return    One lap array
  961.  */
  962.   function _merge_laps($laps)
  963.   {
  964.     $merged array();
  965.  
  966.   // Initial values + init min and max
  967.     if (isset($laps[1]['ini_hr']))
  968.     $merged['ini_hr'$laps[1]['ini_hr']}
  969.     if (isset($laps[1]['min_hr']))
  970.     $merged['min_hr'$laps[1]['min_hr']}
  971.     if (isset($laps[1]['max_hr']))
  972.     $merged['max_hr'$laps[1]['max_hr']}
  973.     if (isset($laps[1]['ini_speed']))
  974.     $merged['ini_speed'$laps[1]['ini_speed']}
  975.     if (isset($laps[1]['min_speed']))
  976.     $merged['min_speed'$laps[1]['min_speed']}
  977.     if (isset($laps[1]['max_speed']))
  978.     $merged['max_speed'$laps[1]['max_speed']}
  979.     if (isset($laps[1]['ini_cadence']))
  980.     $merged['ini_cadence'$laps[1]['ini_cadence']}
  981.     if (isset($laps[1]['min_cadence']))
  982.     $merged['min_cadence'$laps[1]['min_cadence']}
  983.     if (isset($laps[1]['max_cadence']))
  984.     $merged['max_cadence'$laps[1]['max_cadence']}
  985.     if (isset($laps[1]['max_stride']))
  986.     $merged['max_stride'$laps[1]['max_stride']}
  987.     if (isset($laps[1]['ini_altitude']))
  988.     $merged['ini_altitude'$laps[1]['ini_altitude']}
  989.     if (isset($laps[1]['min_altitude']))
  990.     $merged['min_altitude'$laps[1]['min_altitude']}
  991.     if (isset($laps[1]['max_altitude']))
  992.     $merged['max_altitude'$laps[1]['max_altitude']}
  993.  
  994.     $pond_cadence 0;
  995.     $pond_altitude 0;
  996.  
  997.   // Loop on laps to cumulate values + min / max
  998.     for ($i 1$i <= count($laps)$i++)
  999.     {
  1000.       $merged['lap_elapsed'+= $laps[$i]['lap_elapsed'];
  1001.       if (isset($laps[$i]['distance']))
  1002.       $merged['distance'+= $laps[$i]['distance'];
  1003.       if (isset($merged['min_hr']))
  1004.       {
  1005.           if ($laps[$i]['min_hr'$merged['min_hr'])
  1006.     $merged['min_hr'$laps[$i]['min_hr']}
  1007.       }
  1008.       if (isset($merged['max_hr']))
  1009.       {
  1010.           if ($laps[$i]['max_hr'$merged['max_hr'])
  1011.     $merged['max_hr'$laps[$i]['max_hr']}
  1012.       }
  1013.       if (isset($merged['min_speed']))
  1014.       {
  1015.           if ($laps[$i]['min_speed'$merged['min_speed'])
  1016.     $merged['min_speed'$laps[$i]['min_speed']}
  1017.       }
  1018.       if (isset($merged['max_speed']))
  1019.       {
  1020.           if ($laps[$i]['max_speed'$merged['max_speed'])
  1021.     $merged['max_speed'$laps[$i]['max_speed']}
  1022.       }
  1023.       if (isset($merged['min_cadence']))
  1024.       {
  1025.           if ($laps[$i]['min_cadence'$merged['min_cadence'])
  1026.     $merged['min_cadence'$laps[$i]['min_cadence']}
  1027.       }
  1028.       if (isset($merged['max_cadence']))
  1029.       {
  1030.           if ($laps[$i]['max_cadence'$merged['max_cadence'])
  1031.     $merged['max_cadence'$laps[$i]['max_cadence']}
  1032.       }
  1033.       if (isset($merged['max_stride']))
  1034.       {
  1035.           if ($laps[$i]['max_stride'$merged['max_stride'])
  1036.     $merged['max_stride'$laps[$i]['max_stride']}
  1037.       }
  1038.       if (isset($merged['min_altitude']))
  1039.       {
  1040.           if ($laps[$i]['min_altitude'$merged['min_altitude'])
  1041.     $merged['min_altitude'$laps[$i]['min_altitude']}
  1042.       }
  1043.       if (isset($merged['max_altitude']))
  1044.       {
  1045.           if ($laps[$i]['max_altitude'$merged['max_altitude'])
  1046.     $merged['max_altitude'$laps[$i]['max_altitude']}
  1047.       }
  1048.       if (isset($laps[$i]['ascend']))
  1049.       $merged['ascend'+= $laps[$i]['ascend']}
  1050.       if (isset($laps[$i]['physio_energy']))
  1051.       $merged['physio_energy'+= $laps[$i]['physio_energy']}
  1052.       if (isset($laps[$i]['meca_energy']))
  1053.       $merged['meca_energy'+= $laps[$i]['meca_energy']}
  1054.       if (isset($laps[$i]['beat_sum']))
  1055.       $merged['beat_sum'+= $laps[$i]['beat_sum']}
  1056.       if (isset($laps[$i]['avg_cadence']))
  1057.       $pond_cadence += $laps[$i]['avg_cadence'$laps[$i]['lap_elapsed']}
  1058.       if (isset($laps[$i]['avg_altitude']))
  1059.       $pond_altitude += $laps[$i]['avg_altitude'$laps[$i]['lap_elapsed'];}
  1060.     }
  1061.  
  1062.   // End values + compute average values
  1063.     $merged['elapsed'$laps[$i 1]['elapsed'];
  1064.     if (isset($laps[1]['avg_hr']))
  1065.     {
  1066.       $merged['avg_hr'=
  1067.     round(600 $merged['beat_sum'$merged['lap_elapsed']2);
  1068.     }
  1069.     if (isset($laps[$i 1]['end_hr']))
  1070.     $merged['end_hr'$laps[$i 1]['end_hr']}
  1071.     if (isset($laps[1]['avg_speed']))
  1072.     {
  1073.       $merged['avg_speed'=
  1074.     round(360 $merged['distance'$merged['lap_elapsed']2);
  1075.     }
  1076.     if (isset($laps[$i 1]['end_speed']))
  1077.     $merged['end_speed'$laps[$i 1]['end_speed']}
  1078.     if ($pond_cadence)
  1079.     {
  1080.       $merged['avg_cadence'round($pond_cadence $merged['lap_elapsed']2);
  1081.       if (isset($merged['distance']))
  1082.       {
  1083.           $merged['avg_stride'=
  1084.       round(30000 $merged['distance'$pond_cadence2);
  1085.       }
  1086.     }
  1087.     if (isset($laps[$i 1]['end_cadence']))
  1088.     $merged['end_cadence'$laps[$i 1]['end_cadence']}
  1089.     if ($pond_altitude)
  1090.     {
  1091.       $merged['avg_altitude'=
  1092.     round($pond_altitude $merged['lap_elapsed']2);
  1093.     }
  1094.     if (isset($laps[$i 1]['end_altitude']))
  1095.     $merged['end_altitude'$laps[$i 1]['end_altitude']}
  1096.     if (isset($laps[$i 1]['description']))
  1097.     $merged['description'$laps[$i 1]['description']}
  1098.  
  1099.     return $merged;
  1100.   }
  1101.  
  1102. /**
  1103.  * Callback comparison function to sort cumulated laps by 'delta' value
  1104.  *
  1105.  * @param    array    $v1 1st value where [2] is delta value
  1106.  * @param    array    $v2 2nd value where [2] is delta value
  1107.  * @return    integer    -1 if v1 < v2, +1 if V2 > v1, 0 if v1 = v2
  1108.  */
  1109.   function _sort_cum_cmp($v1$v2)
  1110.   {
  1111.     if ($v1[2== $v2[2])
  1112.     return 0}
  1113.  
  1114.     return ($v1[2$v2[2]? -1;
  1115.   }
  1116.  
  1117. /**
  1118.  * Get maximum average speed for a given distance or duration
  1119.  *
  1120.  * Use dichotomy algorithm, to find closest lower and upper pre-computed speeds,
  1121.  * then linear approximation.
  1122.  *
  1123.  * @param    integer    $value 
  1124.  * @param    string    $unit Unit for value in ('distance','duration').
  1125.  *             Distance in meters, duration in seconds.
  1126.  * @param    array    $std_int Pre-computed data for standard distances, each
  1127.  *             element is array('distance' => <meters>,
  1128.  *             'duration' => seconds, 'speed' => km/h)
  1129.  * @return    float    Maximum average speed in km/h
  1130.  */
  1131.   function _get_max_speed($value$unit$std_int)
  1132.   {
  1133.     $min 0;
  1134.     $max count($std_int1;
  1135.     while ($max $min 1)
  1136.     {
  1137.       $i floor(($min $max2);
  1138.       if ($std_int[$i][$unit$value)
  1139.       $max $i}
  1140.       else
  1141.       $min $i}
  1142.     }
  1143.     $max_speed $std_int[$min]['speed'+
  1144.       ($std_int[$max]['speed'$std_int[$min]['speed']*
  1145.     ($value $std_int[$min][$unit]/
  1146.     ($std_int[$max][$unit$std_int[$min][$unit]);
  1147.  
  1148.     return $max_speed;
  1149.   }
  1150. }
  1151. ?>

Documentation generated on Sat, 28 Mar 2009 23:15:43 +0000 by phpDocumentor 1.4.1