GpxService::getGpxStringFromFilename()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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