Completed
Push — master ( ac41f4...7a5cb9 )
by Hannes
02:54
created

AbstractFileProvider::getElevation()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.0935

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
ccs 9
cts 16
cp 0.5625
rs 8.5125
cc 5
eloc 13
nc 4
nop 2
crap 7.0935
1
<?php
2
3
/*
4
 * This file is part of the Runalyze DEM Reader.
5
 *
6
 * (c) RUNALYZE <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Runalyze\DEM\Provider;
13
14
use Runalyze\DEM\Exception\InvalidArgumentException;
15
use Runalyze\DEM\Interpolation\GuessInvalidValuesTrait;
16
use Runalyze\DEM\Interpolation\InterpolationInterface;
17
18
abstract class AbstractFileProvider implements ProviderInterface
19
{
20
    use GuessInvalidValuesTrait;
21
22
    /** @var int */
23
    const MAX_LATITUDE = 90;
24
25
    /** @var int */
26
    const MAX_LONGITUDE = 180;
27
28
    /** @var string */
29
    protected $PathToFiles;
30
31
    /** @var resource */
32
    protected $FileResource;
33
34
    /** @var string|bool */
35
    protected $CurrentFilename = false;
36
37
    /** @var InterpolationInterface|null */
38
    protected $Interpolation;
39
40
    /** @var ResourceReaderInterface */
41
    protected $ResourceReader;
42
43
    /**
44
     * @param string                      $pathToFiles
45
     * @param InterpolationInterface|null $interpolation
46
     */
47 9
    public function __construct($pathToFiles, InterpolationInterface $interpolation = null)
48
    {
49 9
        $this->PathToFiles = $pathToFiles;
50 9
        $this->Interpolation = $interpolation;
51
52 9
        $this->initResourceReader();
53 9
    }
54
55
    /**
56
     * @param InterpolationInterface $interpolation
57
     */
58 9
    public function setInterpolation(InterpolationInterface $interpolation)
59
    {
60 9
        $this->Interpolation = $interpolation;
61 9
    }
62
63
    /**
64
     * @param  array                    $latitudeLongitudes array(array($lat, $lng), ...)
65
     * @return bool
66
     * @throws InvalidArgumentException
67
     */
68 2
    public function hasDataFor(array $latitudeLongitudes)
69
    {
70 2
        foreach ($latitudeLongitudes as $location) {
71 2
            if (!is_array($location) || count($location) !== 2) {
72
                throw new InvalidArgumentException('Locations must be arrays of two values.');
73
            }
74
75 2
            if (!$this->locationIsInBounds($location[0], $location[1])) {
76 1
                return false;
77
            }
78
79 1
            if (!file_exists($this->PathToFiles.$this->getFilenameFor($location[0], $location[1]))) {
80
                return false;
81
            }
82 1
        }
83
84 1
        return true;
85
    }
86
87
    /**
88
     * @param  float $latitude
89
     * @param  float $longitude
90
     * @return bool
91
     */
92 8
    protected function locationIsInBounds($latitude, $longitude)
93
    {
94
        return
95 8
            ($latitude > -static::MAX_LATITUDE) && ($latitude <= static::MAX_LATITUDE) &&
96 8
            ($longitude > -static::MAX_LONGITUDE) && ($longitude < static::MAX_LONGITUDE)
97 8
        ;
98
    }
99
100
    /**
101
     * @return bool
102
     */
103 6
    public function usesInterpolation()
104
    {
105 6
        return null !== $this->Interpolation;
106
    }
107
108
    /**
109
     * @param  float[]                   $latitudes
110
     * @param  float[]                   $longitudes
111
     * @return array                     elevations [m] can be false if nothing retrieved
112
     * @throws InvalidArgumentException;
113
     */
114 4
    public function getElevations(array $latitudes, array $longitudes)
115
    {
116 4
        $numberOfPoints = count($latitudes);
117 4
        $elevations = [];
118
119 4
        if (count($longitudes) !== $numberOfPoints) {
120
            throw new InvalidArgumentException('Arrays for latitude and longitude must be of same size.');
121
        }
122
123 4
        for ($i = 0; $i < $numberOfPoints; ++$i) {
124 4
            $elevations[] = $this->getElevation($latitudes[$i], $longitudes[$i]);
125 4
        }
126
127 4
        return $elevations;
128
    }
129
130
    /**
131
     * Get elevation for specified location.
132
     *
133
     * Availability of data must be checked in advance via hasDataFor($locations).
134
     * Only 'invalid' locations such as (0.0, 0.0) won't throw an exception but return false.
135
     *
136
     * @param  float                     $latitude
137
     * @param  float                     $longitude
138
     * @return int|bool                  elevation [m] can be false if nothing retrieved
139
     * @throws InvalidArgumentException;
140
     */
141 7
    public function getElevation($latitude, $longitude)
142
    {
143 7
        if ($latitude === 0.0 && $longitude === 0.0) {
144 1
            return false;
145
        }
146
147 6
        if (!$this->locationIsInBounds($latitude, $longitude)) {
148
            throw new InvalidArgumentException(
149
                sprintf('Location (%f, %f) is out of bounds ([-%f, %f], [-%f, %f]).',
150
                    $latitude, $longitude,
151
                    static::MAX_LATITUDE, static::MAX_LATITUDE,
152
                    static::MAX_LONGITUDE, static::MAX_LONGITUDE
153
                )
154
            );
155
        }
156
157 6
        $filename = $this->getFilenameFor($latitude, $longitude);
158
159 6
        if ($this->CurrentFilename !== $filename) {
160 6
            $this->openResource($filename);
161 6
        }
162
163 6
        return $this->getElevationFromResource($latitude, $longitude);
164
    }
165
166
    /**
167
     * @param  float    $latitude
168
     * @param  float    $longitude
169
     * @return int|bool can be false if nothing retrieved
170
     */
171 6
    protected function getElevationFromResource($latitude, $longitude)
172
    {
173 6
        list($exactRowValue, $exactColValue) = $this->getExactRowAndColFor($latitude, $longitude);
174
175 6
        if (!$this->usesInterpolation()) {
176
            return $this->getElevationFor(round($exactRowValue), round($exactColValue));
177
        }
178
179 6
        return $this->getInterpolatedElevationFor($exactRowValue, $exactColValue);
180
    }
181
182
    /**
183
     * @param  float    $exactRowValue
184
     * @param  float    $exactColValue
185
     * @return int|bool can be false if nothing retrieved
186
     */
187 6
    protected function getInterpolatedElevationFor($exactRowValue, $exactColValue)
188
    {
189 6
        $row = floor($exactRowValue);
190 6
        $col = floor($exactColValue);
191
192
        $elevationOnBoundingBox = [
193 6
            $this->getElevationFor($row, $col),
194 6
            $this->getElevationFor($row, $col + 1),
195 6
            $this->getElevationFor($row + 1, $col),
196 6
            $this->getElevationFor($row + 1, $col + 1),
197 6
        ];
198
199 6
        $this->guessInvalidValuesOnBox($elevationOnBoundingBox);
200
201 6
        return $this->Interpolation->interpolate(
202 6
            ($exactRowValue - $row),
203 6
            ($exactColValue - $col),
204
            $elevationOnBoundingBox
205 6
        );
206
    }
207
208
    /**
209
     * @param  string $filename
210
     * @return bool
211
     */
212
    protected function resourceIsCurrentlyOpened($filename)
213
    {
214
        return $filename !== $this->CurrentFilename;
215
    }
216
217
    /**
218
     * @param  float  $latitude
219
     * @param  float  $longitude
220
     * @return string
221
     */
222
    abstract protected function getFilenameFor($latitude, $longitude);
223
224
    abstract protected function initResourceReader();
225
226
    /**
227
     * @param string $filename
228
     */
229
    abstract protected function openResource($filename);
230
231
    /**
232
     * @param  int      $row
233
     * @param  int      $col
234
     * @return int|bool elevation [m] can be false if nothing retrieved
235
     */
236
    abstract protected function getElevationFor($row, $col);
237
238
    /**
239
     * @param  float $latitude
240
     * @param  float $longitude
241
     * @return array array(row, col)
242
     */
243
    abstract protected function getExactRowAndColFor($latitude, $longitude);
244
}
245