Completed
Pull Request — master (#88)
by Matt
04:22
created

GpxService::getWanderGeoJson()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
1
<?php
2
3
namespace App\Service;
4
5
use App\Entity\Wander;
6
use Exception;
7
use Gothick\Geotools\Polyline;
8
use Gothick\Geotools\PolylineGeoJsonFormatter;
9
use Gothick\Geotools\PolylineRdpSimplifier;
10
use phpGPX\phpGPX;
11
use Psr\Log\LoggerInterface;
12
13
class GpxService
14
{
15
    /** @var phpGPX */
16
    private $phpGpx;
17
    /** @var string */
18
    private $gpxDirectory;
19
    /** @var LoggerInterface */
20
    private $logger;
21
    /** @var array */
22
    private $homebaseCoords;
23
    /** @var int */
24
    private $wanderSimplifierEpsilonMetres;
25
26
    public function __construct(string $gpxDirectory, LoggerInterface $logger, array $homebaseCoords, int $wanderSimplifierEpsilonMetres)
27
    {
28
        $this->phpGpx = new phpGPX();
29
        $this->gpxDirectory = $gpxDirectory;
30
        $this->logger = $logger;
31
        $this->homebaseCoords = $homebaseCoords;
32
        $this->wanderSimplifierEpsilonMetres = $wanderSimplifierEpsilonMetres;
33
    }
34
35
    /**
36
     * @throws \Exception
37
     */
38
    public function getFullGpxFilePathFromWander(Wander $wander): string
39
    {
40
        $filename = $wander->getGpxFilename();
41
        if (!$filename) {
42
            throw new \Exception("No GPX file path found in wander.");
43
        }
44
        return $this->getFullGpxFilePathFromFilename($filename);
45
    }
46
47
    public function getFullGpxFilePathFromFilename(string $filename): string
48
    {
49
        return $this->gpxDirectory . '/' . $filename;
50
    }
51
52
    public function getGpxStringFromFilename(string $filename): string
53
    {
54
        $gpx = \file_get_contents($this->getFullGpxFilePathFromFilename($filename));
55
        if ($gpx === false) {
56
            throw new Exception("Couldn't read GPX file from $filename");
57
        }
58
        return $gpx;
59
    }
60
61
    private function updateGeneralStats(string $gpxxml, Wander $wander): void
62
    {
63
        $gpx = $this->phpGpx->parse($gpxxml);
64
        // TODO: Cope with multiple tracks in a file? I don't think
65
        // we've done that often, if ever.
66
        foreach ($gpx->tracks as $track)
67
        {
68
            $stats = $track->stats;
69
            // These are the only essentials
70
            $wander->setStartTime($stats->startedAt);
71
            $wander->setEndTime($stats->finishedAt);
72
            try {
73
                $wander->setDistance($stats->distance);
74
                $wander->setAvgPace($stats->averagePace);
75
                $wander->setAvgSpeed($stats->averageSpeed);
76
                $wander->setMaxAltitude($stats->maxAltitude);
77
                $wander->setMinAltitude($stats->minAltitude);
78
                $wander->setCumulativeElevationGain($stats->cumulativeElevationGain);
79
            }
80
            catch(Exception $e) {
81
                //$this->logger->debug("Couldn't set extended GPX property on wander: " . $e->getMessage());
82
                throw new Exception("Couldn't set standard GPX stats properties on wander.", 0, $e);
83
            }
84
        }
85
    }
86
87
    /**
88
     * Update centroid and related angle from "home base" to the centroid,
89
     * though now we're using our own library the definition of "centroid"
90
     * is currently "the average of the latitude and longitude values",
91
     * which is close enough for rock & roll.
92
     */
93
    private function updateCentroid(string $gpxxml, Wander $wander): void
94
    {
95
        $polyline = Polyline::fromGpxData($gpxxml);
96
        $centroid = $polyline->getCentroid();
97
        $wander->setCentroid([$centroid->getLat(), $centroid->getLng()]);
98
        $angle = $this->compass((
99
            $centroid->getLng() - $this->homebaseCoords[1]),
100
            ($centroid->getLat() - $this->homebaseCoords[0])
101
        );
102
        $wander->setAngleFromHome($angle);
103
    }
104
105
    /**
106
     * Translate co-ordinates relative to 0, 0 into a compass direction in degrees.
107
     */
108
    public function compass(float $x, float $y): float
109
    {
110
        // https://www.php.net/manual/en/function.atan2.php#88119
111
        if($x==0 AND $y==0){ return 0; } // ...or return 360
112
        return ($x < 0)
113
            ? rad2deg(atan2($x,$y)) + 360
114
            : rad2deg(atan2($x,$y));
115
    }
116
117
    public function gpxToGeoJson(string $gpx, float $epsilon): string
118
    {
119
        $polyline = Polyline::fromGpxData($gpx);
120
        $simplifier = new PolylineRdpSimplifier($epsilon);
121
        $simplifiedPolyline = $simplifier->ramerDouglasPeucker($polyline);
122
        $formatter = new PolylineGeoJsonFormatter();
123
        return $formatter->format($simplifiedPolyline);
124
    }
125
126
    // TODO: This is updating more than the stats now. Change the name to
127
    // reflect that.
128
    public function updateWanderFromGpx(Wander $wander): void
129
    {
130
        $filename = $wander->getGpxFilename();
131
        if (isset($filename))
132
        {
133
            // Basic stats, updated using phpGpx
134
            $gpxxml = $this->getGpxStringFromFilename($filename);
135
            $wander->setGeoJson($this->gpxToGeoJson($gpxxml, $this->wanderSimplifierEpsilonMetres));
136
            $this->updateGeneralStats($gpxxml, $wander);
137
            $this->updateCentroid($gpxxml, $wander);
138
        }
139
    }
140
}