Passed
Pull Request — master (#136)
by Matt
08:11
created

GpxService::gpxToGooglePolyline()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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