diff --git a/.travis.yml b/.travis.yml index 17d2589..faf09ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ php: - 5.5 - 5.6 - 7.0 - - hhvm sudo: false diff --git a/bin/download-testfiles.sh b/bin/download-testfiles.sh index e26d98a..1e0824e 100755 --- a/bin/download-testfiles.sh +++ b/bin/download-testfiles.sh @@ -11,4 +11,12 @@ do unzip -o tests/testfiles/${file}.zip ${file}.tif -d tests/testfiles rm -rf tests/testfiles/${file}.zip fi +done + +for file in n40_w075 n49_e007 n51_w001 s23_e017 s34_e151 +do + if [ ! -f tests/testfiles/${file}_1arc_v3.tif ] + then + curl https://cdn.runalyze.com/static/srtm1/${file}_1arc_v3.tif > tests/testfiles/${file}_1arc_v3.tif + fi done \ No newline at end of file diff --git a/src/Provider/GeoTIFF/GeoTIFFReader.php b/src/Provider/GeoTIFF/GeoTIFFReader.php index 3839b3e..872a1c3 100644 --- a/src/Provider/GeoTIFF/GeoTIFFReader.php +++ b/src/Provider/GeoTIFF/GeoTIFFReader.php @@ -14,6 +14,9 @@ use Runalyze\DEM\Exception\RuntimeException; use Runalyze\DEM\Provider\AbstractResourceReader; +/** + * @see http://www.awaresystems.be/imaging/tiff/tifftags.html + */ class GeoTIFFReader extends AbstractResourceReader { /** @@ -22,13 +25,6 @@ class GeoTIFFReader extends AbstractResourceReader */ const LEN_OFFSET = 4; - /** - * The number of bytes containing each item of elevation data - * (= BitsPerSample tag value / 8). - * @var int - */ - const BYTES_PER_SAMPLE = 2; - /** * Magic number located at bytes 2-3 which identifies a TIFF file. * @var int @@ -44,6 +40,15 @@ class GeoTIFFReader extends AbstractResourceReader /** @var int */ const TIFF_CONST_IMAGE_LENGTH = 257; + /** @var int */ + const TIFF_CONST_BITS_PER_SAMPLE = 258; + + /** @var int */ + const TIFF_CONST_SAMPLES_PER_PIXEL = 277; + + /** @var int */ + const TIFF_CONST_ROWS_PER_STRIP = 278; + /** @var int */ const TIFF_CONST_STRIPBYTECOUNTS = 279; @@ -59,6 +64,15 @@ class GeoTIFFReader extends AbstractResourceReader /** @var int */ const UNKNOWN = -32768; + /** @var int The number of bytes containing each item of elevation data */ + protected $BytesPerSample = 2; + + /** @var int The number of components per pixel */ + protected $SamplesPerPixel = 1; + + /** @var int The number of rows per strip */ + protected $RowsPerStrip = 1; + /** @var int */ protected $NumDataRows; @@ -130,6 +144,15 @@ protected function readIFDEntries() case static::TIFF_CONST_IMAGE_LENGTH: $this->NumDataRows = $constData['offset']; break; + case static::TIFF_CONST_BITS_PER_SAMPLE: + $this->BytesPerSample = (int) ($constData['offset'] / 8); + break; + case static::TIFF_CONST_SAMPLES_PER_PIXEL: + $this->SamplesPerPixel = $constData['offset']; + break; + case static::TIFF_CONST_ROWS_PER_STRIP: + $this->RowsPerStrip = $constData['offset']; + break; case static::TIFF_CONST_STRIPOFSETS: $this->StripOffsets = $constData['offset']; break; @@ -165,13 +188,13 @@ public function getExactRowAndColFor($relativeLatitude, $relativeLongitude) */ public function getElevationFor($row, $col) { - fseek($this->FileResource, $this->StripOffsets + ($row * static::LEN_OFFSET)); + fseek($this->FileResource, $this->StripOffsets + ceil($row / $this->RowsPerStrip) * static::LEN_OFFSET); $firstColumnData = unpack('Voffset', fread($this->FileResource, static::LEN_OFFSET)); - fseek($this->FileResource, $firstColumnData['offset'] + $col * static::BYTES_PER_SAMPLE); + fseek($this->FileResource, $firstColumnData['offset'] + ($this->NumDataCols * ($row % $this->RowsPerStrip) + $col) * $this->BytesPerSample); - $elevation = unpack('velevation', fread($this->FileResource, static::BYTES_PER_SAMPLE))['elevation']; + $elevation = unpack('velevation', fread($this->FileResource, $this->BytesPerSample))['elevation']; return ($elevation <= self::UNKNOWN || $elevation >= -self::UNKNOWN) ? false : $elevation; } diff --git a/src/Provider/GeoTIFF/SRTM1ArcSecondProvider.php b/src/Provider/GeoTIFF/SRTM1ArcSecondProvider.php new file mode 100644 index 0000000..e22b6ce --- /dev/null +++ b/src/Provider/GeoTIFF/SRTM1ArcSecondProvider.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Runalyze\DEM\Provider\GeoTIFF; + +use Runalyze\DEM\Exception\InvalidArgumentException; + +class SRTM1ArcSecondProvider extends SRTM4Provider +{ + /** @var float */ + const DEGREES_PER_TILE = 1.0; + + /** @var string */ + protected $FilenameFormat = '%s%02d_%s%03d.tif'; + + /** + * @param float $latitude + * @param float $longitude + * @return string + * @throws InvalidArgumentException + */ + protected function getFilenameFor($latitude, $longitude) + { + $this->loadTileReferencesFor($latitude, $longitude); + + return sprintf( + $this->FilenameFormat, + $this->CurrentTileVerticalReference < 0 ? 's' : 'n', + abs($this->CurrentTileVerticalReference), + $this->CurrentTileHorizontalReference < 0 ? 'w' : 'e', + abs($this->CurrentTileHorizontalReference) + ); + } + + /** + * @param float $latitude + * @param float $longitude + */ + protected function loadTileReferencesFor($latitude, $longitude) + { + $this->CurrentTileVerticalReference = floor($latitude / static::DEGREES_PER_TILE); + $this->CurrentTileHorizontalReference = floor($longitude / static::DEGREES_PER_TILE); + + $this->CurrentTileLatitude = $this->CurrentTileVerticalReference; + $this->CurrentTileLongitude = $this->CurrentTileHorizontalReference; + } + + /** + * @param float $latitude + * @param float $longitude + * @return float[] array(row, col) + */ + protected function getExactRowAndColFor($latitude, $longitude) + { + return $this->ResourceReader->getExactRowAndColFor( + abs($this->CurrentTileLatitude + 1 - $latitude) / static::DEGREES_PER_TILE, + abs($longitude - $this->CurrentTileLongitude) / static::DEGREES_PER_TILE + ); + } +} diff --git a/src/Tests/Provider/GeoTIFF/SRTM1ArcSecondProviderTest.php b/src/Tests/Provider/GeoTIFF/SRTM1ArcSecondProviderTest.php new file mode 100644 index 0000000..e015d3e --- /dev/null +++ b/src/Tests/Provider/GeoTIFF/SRTM1ArcSecondProviderTest.php @@ -0,0 +1,161 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Runalyze\DEM\Tests\Provider\GeoTIFF; + +use Runalyze\DEM\Interpolation\BilinearInterpolation; +use Runalyze\DEM\Provider\GeoTIFF\SRTM1ArcSecondProvider; + +class SRTM1ArcSecondProviderTest extends \PHPUnit_Framework_TestCase +{ + /** @var string */ + const PATH_TO_FILES = '/../../../../tests/testfiles'; + + /** + * @var SRTM1ArcSecondProvider + */ + protected $Provider; + + public function setUp() + { + $this->Provider = new SRTM1ArcSecondProvider(__DIR__.self::PATH_TO_FILES); + $this->Provider->setFilenameFormat('%s%02d_%s%03d_1arc_v3.tif'); + $this->Provider->setInterpolation(new BilinearInterpolation()); + } + + /** + * @param string $filename + */ + protected function checkFile($filename) + { + if (!$this->fileIsThere($filename)) { + $this->markTestSkipped('Required testfile "'.$filename.'" is not available.'); + } + } + + /** + * @param string $filename + * @return bool + */ + protected function fileIsThere($filename) + { + return file_exists(__DIR__.self::PATH_TO_FILES.'/'.$filename); + } + + public function testTileBoundary() + { + $this->checkFile('n49_e007_1arc_v3.tif'); + + if ( + !$this->fileIsThere('n49_e006_1arc_v3.tif') && + !$this->fileIsThere('n49_e008_1arc_v3.tif') && + !$this->fileIsThere('n48_e007_1arc_v3.tif') && + !$this->fileIsThere('n50_e007_1arc_v3.tif') + ) { + $this->assertTrue($this->Provider->hasDataFor([[49.00000, 7.00000]])); + $this->assertTrue($this->Provider->hasDataFor([[49.99999, 7.00000]])); + $this->assertTrue($this->Provider->hasDataFor([[49.99999, 7.99999]])); + $this->assertTrue($this->Provider->hasDataFor([[49.00000, 7.99999]])); + + $this->assertFalse($this->Provider->hasDataFor([[49.00000, 6.99999]])); + $this->assertFalse($this->Provider->hasDataFor([[49.00000, 8.00000]])); + $this->assertFalse($this->Provider->hasDataFor([[48.99999, 7.99999]])); + } else { + $this->markTestSkipped('Can\'t check boundaries of tile as too many files are there.'); + } + } + + /** + * @group germany + */ + public function testSingleElevationInGermany() + { + $this->checkFile('n49_e007_1arc_v3.tif'); + + // SRTM4: 237 + $this->assertEquals(239, $this->Provider->getElevation(49.444722, 7.768889)); + } + + public function testMultipleElevationsInSydney() + { + $this->checkFile('s34_e151_1arc_v3.tif'); + + // SRTM4: 4, 4, 3 + $this->assertEquals( + [3, 3, 2], + $this->Provider->getElevations( + [-33.8706555, -33.8705667, -33.8704860], + [151.1486918, 151.1486337, 151.1485585] + ) + ); + } + + /** + * @group london + */ + public function testLocationsInLondon() + { + $this->checkFile('n51_w001_1arc_v3.tif'); + + // SRTM4: 20, 19, 19 + $this->assertEquals( + [18, 19, 18], + $this->Provider->getElevations( + [51.5073509, 51.5074509, 51.5075509], + [-0.1277583, -0.1278583, -0.1279583] + ) + ); + } + + public function testLocationsInWindhoek() + { + $this->checkFile('s23_e017_1arc_v3.tif'); + + // SRTM4: 1668, 1671, 1672 + $this->assertEquals( + [1665, 1672, 1671], + $this->Provider->getElevations( + [-22.5700, -22.5705, -22.5710], + [17.0836, 17.0841, 17.0846] + ) + ); + } + + public function testNewYork() + { + $this->checkFile('n40_w075_1arc_v3.tif'); + + // SRTM4: 26, 29, 39 + $this->assertEquals( + [28, 27, 46], + $this->Provider->getElevations( + [40.7127, 40.7132, 40.7137], + [-74.0059, -74.0064, -74.0069] + ) + ); + } + + public function testNewYorkWithoutInterpolation() + { + $this->checkFile('n40_w075_1arc_v3.tif'); + + $this->Provider->removeInterpolation(); + + // SRTM4: 26, 32, 32 + $this->assertEquals( + [28, 28, 48], + $this->Provider->getElevations( + [40.7127, 40.7132, 40.7137], + [-74.0059, -74.0064, -74.0069] + ) + ); + } +}