diff --git a/CHANGELOG.md b/CHANGELOG.md index 106852f..1651b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.0 : 2022-07-30 + +- **Feature**: [Real distance calculation #37](https://github.com/Sibyx/phpGPX/issues/37) (DistanceCalculator refactor) + ## 1.1.3 : 2021-07-29 - **Fix**: [Fix negative duration #58](https://github.com/Sibyx/phpGPX/pull/58) by [@neronmoon](https://github.com/neronmoon) diff --git a/LICENSE b/LICENSE index ec36573..a881351 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Jakub Dubec +Copyright (c) 2017-2022 Jakub Dubec Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 54ae56b..39887fd 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ You can easily install phpGPX library with [composer](https://getcomposer.org/). please use release candidates. ``` -composer require sibyx/phpgpx:1.1.3 +composer require sibyx/phpgpx:1.2.0 ``` ## Examples diff --git a/composer.json b/composer.json index 2a07c70..c4fa14b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "sibyx/phpgpx", "type": "library", - "version": "1.1.3", + "version": "1.2.0", "description": "A simple PHP library for GPX import/export", "minimum-stability": "stable", "license": "MIT", diff --git a/src/phpGPX/Helpers/DistanceCalculator.php b/src/phpGPX/Helpers/DistanceCalculator.php index 7c03a4c..5a2a51b 100644 --- a/src/phpGPX/Helpers/DistanceCalculator.php +++ b/src/phpGPX/Helpers/DistanceCalculator.php @@ -3,31 +3,56 @@ * DistanceCalculator.php * * @author Jens Hassler + * @author Jakub Dubec * @since 07/2018 + * @version 2.0 */ namespace phpGPX\Helpers; -use phpGPX\Helpers\GeoHelper; use phpGPX\Models\Point; use phpGPX\phpGPX; class DistanceCalculator { + /** + * @var Point[] + */ + private $points; + + /** + * DistanceCalculator constructor. + * @param Point[] $points + */ + public function __construct(array $points) + { + $this->points = $points; + } + + public function getRawDistance() + { + return $this->calculate([GeoHelper::class, 'getRawDistance']); + } + + public function getRealDistance() + { + return $this->calculate([GeoHelper::class, 'getRealDistance']); + } + /** * @param Point[]|array $points * @return float */ - public static function calculate(array $points) + private function calculate($strategy) { $distance = 0; - $pointCount = count($points); + $pointCount = count($this->points); $lastConsideredPoint = null; for ($p = 0; $p < $pointCount; $p++) { - $curPoint = $points[$p]; + $curPoint = $this->points[$p]; // skip the first point if ($p === 0) { @@ -36,11 +61,11 @@ public static function calculate(array $points) } // calculate the delta from current point to last considered point - $curPoint->difference = GeoHelper::getDistance($lastConsideredPoint, $curPoint); + $curPoint->difference = call_user_func($strategy, $lastConsideredPoint, $curPoint); // if smoothing is applied we only consider points with a delta above the threshold (e.g. 2 meters) if (phpGPX::$APPLY_DISTANCE_SMOOTHING) { - $differenceFromLastConsideredPoint = GeoHelper::getDistance($curPoint, $lastConsideredPoint); + $differenceFromLastConsideredPoint = call_user_func($strategy, $curPoint, $lastConsideredPoint); if ($differenceFromLastConsideredPoint > phpGPX::$DISTANCE_SMOOTHING_THRESHOLD) { $distance += $differenceFromLastConsideredPoint; diff --git a/src/phpGPX/Helpers/GeoHelper.php b/src/phpGPX/Helpers/GeoHelper.php index 4fec6c1..adaabe1 100644 --- a/src/phpGPX/Helpers/GeoHelper.php +++ b/src/phpGPX/Helpers/GeoHelper.php @@ -24,7 +24,7 @@ abstract class GeoHelper * @param Point $point2 * @return float */ - public static function getDistance(Point $point1, Point $point2) + public static function getRawDistance(Point $point1, Point $point2) { $latFrom = deg2rad($point1->latitude); $lonFrom = deg2rad($point1->longitude); @@ -38,4 +38,21 @@ public static function getDistance(Point $point1, Point $point2) return $angle * self::EARTH_RADIUS; } + + /** + * Returns distance between two points including elevation gain/loss + * @param Point $point1 + * @param Point $point2 + * @return float + */ + public static function getRealDistance(Point $point1, Point $point2) + { + $distance = self::getRawDistance($point1, $point2); + + $elevation1 = $point1->elevation != null ? $point1->elevation : 0; + $elevation2 = $point2->elevation != null ? $point2->elevation : 0; + $elevDiff = abs($elevation1 - $elevation2); + + return sqrt(pow($distance, 2) + pow($elevDiff, 2)); + } } diff --git a/src/phpGPX/Models/Route.php b/src/phpGPX/Models/Route.php index c9677c4..5a708f8 100644 --- a/src/phpGPX/Models/Route.php +++ b/src/phpGPX/Models/Route.php @@ -102,7 +102,9 @@ public function recalculateStats() list($this->stats->cumulativeElevationGain, $this->stats->cumulativeElevationLoss) = ElevationGainLossCalculator::calculate($this->getPoints()); - $this->stats->distance = DistanceCalculator::calculate($this->getPoints()); + $calculator = new DistanceCalculator($this->getPoints()); + $this->stats->distance = $calculator->getRawDistance(); + $this->stats->realDistance = $calculator->getRealDistance(); for ($p = 0; $p < $pointCount; $p++) { if ((phpGPX::$IGNORE_ELEVATION_0 === false || $this->points[$p]->elevation > 0) && $this->stats->minAltitude > $this->points[$p]->elevation) { diff --git a/src/phpGPX/Models/Segment.php b/src/phpGPX/Models/Segment.php index f388306..9e1b7df 100644 --- a/src/phpGPX/Models/Segment.php +++ b/src/phpGPX/Models/Segment.php @@ -97,7 +97,9 @@ public function recalculateStats() list($this->stats->cumulativeElevationGain, $this->stats->cumulativeElevationLoss) = ElevationGainLossCalculator::calculate($this->getPoints()); - $this->stats->distance = DistanceCalculator::calculate($this->getPoints()); + $calculator = new DistanceCalculator($this->getPoints()); + $this->stats->distance = $calculator->getRawDistance(); + $this->stats->realDistance = $calculator->getRealDistance(); for ($i = 0; $i < $count; $i++) { if ($this->stats->maxAltitude < $this->points[$i]->elevation) { diff --git a/src/phpGPX/Models/Stats.php b/src/phpGPX/Models/Stats.php index 5cfa249..04ec695 100644 --- a/src/phpGPX/Models/Stats.php +++ b/src/phpGPX/Models/Stats.php @@ -22,6 +22,12 @@ class Stats implements Summarizable */ public $distance = 0; + /** + * Distance in meters (m) including elevation loss/gain + * @var float + */ + public $realDistance = 0; + /** * Average speed in meters per second (m/s) * @var float @@ -82,6 +88,7 @@ class Stats implements Summarizable public function reset() { $this->distance = null; + $this->realDistance = null; $this->averageSpeed = null; $this->averagePace = null; $this->minAltitude = null; @@ -100,6 +107,7 @@ public function toArray() { return [ 'distance' => (float)$this->distance, + 'realDistance' => (float)$this->realDistance, 'avgSpeed' => (float)$this->averageSpeed, 'avgPace' => (float)$this->averagePace, 'minAltitude' => (float)$this->minAltitude, diff --git a/src/phpGPX/Models/Track.php b/src/phpGPX/Models/Track.php index b33f945..a9c4d93 100644 --- a/src/phpGPX/Models/Track.php +++ b/src/phpGPX/Models/Track.php @@ -124,6 +124,7 @@ public function recalculateStats() $this->stats->cumulativeElevationLoss += $this->segments[$s]->stats->cumulativeElevationLoss; $this->stats->distance += $this->segments[$s]->stats->distance; + $this->stats->realDistance += $this->segments[$s]->stats->realDistance; if ($this->stats->minAltitude === null) { $this->stats->minAltitude = $this->segments[$s]->stats->minAltitude; diff --git a/tests/LoadFileTest.php b/tests/LoadFileTest.php index 3e398a9..6ff9993 100644 --- a/tests/LoadFileTest.php +++ b/tests/LoadFileTest.php @@ -84,6 +84,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 2.314424561683643, + 'realDistance' => 2.314424561683643, 'avgSpeed' => 0.2571582846315159, 'avgPace' => 3888.6555859279733, 'minAltitude' => 0.0, @@ -98,6 +99,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 2.314424561683643, + 'realDistance' => 2.314424561683643, 'avgSpeed' => 0.2571582846315159, 'avgPace' => 3888.6555859279733, 'minAltitude' => 0.0, @@ -141,6 +143,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 7.062730302497254, + 'realDistance' => 7.062730302497254, 'avgSpeed' => 2.354243434165751, 'avgPace' => 424.7649098167112, 'minAltitude' => 0.0, @@ -155,6 +158,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 7.062730302497254, + 'realDistance' => 7.062730302497254, 'avgSpeed' => 2.354243434165751, 'avgPace' => 424.7649098167112, 'minAltitude' => 0.0, diff --git a/tests/LoadRouteFileTest.php b/tests/LoadRouteFileTest.php index d7e00aa..ec859aa 100644 --- a/tests/LoadRouteFileTest.php +++ b/tests/LoadRouteFileTest.php @@ -88,6 +88,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 132.0785853, + 'realDistance' => 132.0785853, 'avgSpeed' => 0.0, 'avgPace' => 0.0, 'minAltitude' => 0.0, @@ -133,6 +134,7 @@ private function createExpectedArray() ], 'stats' => [ 'distance' => 132.0785853, + 'realDistance' => 132.0785853, 'avgSpeed' => 0.0, 'avgPace' => 0.0, 'minAltitude' => 0.0, diff --git a/tests/UnitTests/phpGPX/Helpers/GeoHelperTest.php b/tests/UnitTests/phpGPX/Helpers/GeoHelperTest.php index d7adb5f..c99ea1f 100644 --- a/tests/UnitTests/phpGPX/Helpers/GeoHelperTest.php +++ b/tests/UnitTests/phpGPX/Helpers/GeoHelperTest.php @@ -31,9 +31,39 @@ public function testGetDistance() $this->assertEquals( 856.97, - GeoHelper::getDistance($point1, $point2), + GeoHelper::getRawDistance($point1, $point2), "Invalid distance between two points!", 1 ); } + + /** + * @link http://cosinekitty.com/compass.html + */ + public function testRealDistance() + { + $point1 = new Point(Point::WAYPOINT); + $point1->latitude = 48.1573923225717; + $point1->longitude = 17.0547121910204; + $point1->elevation = 100; + + $point2 = new Point(Point::WAYPOINT); + $point2->latitude = 48.1644916381763; + $point2->longitude = 17.0591753907502; + $point2->elevation = 200; + + $this->assertEquals( + 856.97, + GeoHelper::getRawDistance($point1, $point2), + "Invalid distance between two points!", + 1 + ); + + $this->assertEquals( + 862, + GeoHelper::getRealDistance($point1, $point2), + "Invalid real distance between two points!", + 1 + ); + } }