Completed
Push — master ( d75f37...f2cc6f )
by Hannes
04:19 queued 01:37
created

GeoTIFFReader::checkByteOrderAndTiffIdentifier()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.1755

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 15
ccs 7
cts 9
cp 0.7778
rs 9.2
cc 4
eloc 8
nc 3
nop 0
crap 4.1755
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\GeoTIFF;
13
14
use Runalyze\DEM\Exception\RuntimeException;
15
use Runalyze\DEM\Provider\AbstractResourceReader;
16
17
class GeoTIFFReader extends AbstractResourceReader
18
{
19
    /**
20
     * The number of bytes required to hold a TIFF offset address.
21
     * @var int
22
     */
23
    const LEN_OFFSET = 4;
24
25
    /**
26
     * The number of bytes containing each item of elevation data
27
     * (= BitsPerSample tag value / 8).
28
     * @var int
29
     */
30
    const BYTES_PER_SAMPLE = 2;
31
32
    /**
33
     * Magic number located at bytes 2-3 which identifies a TIFF file.
34
     * @var int
35
     */
36
    const MAGIC_TIFF_ID = 42;
37
38
    /** @var int */
39
    const TIFF_CONST_STRIPOFSETS = 273;
40
41
    /** @var int */
42
    const TIFF_CONST_IMAGE_WIDTH = 256;
43
44
    /** @var int */
45
    const TIFF_CONST_IMAGE_LENGTH = 257;
46
47
    /** @var int */
48
    const TIFF_CONST_STRIPBYTECOUNTS = 279;
49
50
    /** @var int */
51
    const TIFF_CONST_IFD_ENTRY_BYTES = 12;
52
53
    /** @var string */
54
    const BIG_ENDIAN = 'MM';
55
56
    /** @var string */
57
    const LITTLE_ENDIAN = 'II';
58
59
    /** @var int */
60
    const UNKNOWN = -32768;
61
62
    /** @var int */
63
    protected $NumDataRows;
64
65
    /** @var int */
66
    protected $NumDataCols;
67
68
    /** @var int */
69
    protected $StripOffsets;
70
71
    /** @var string */
72
    protected $ByteOrder;
73
74 7
    public function readHeader()
75
    {
76 7
        $this->checkByteOrderAndTiffIdentifier();
77 7
        $this->goToIFDEntries();
78 7
        $this->readIFDEntries();
79 7
    }
80
81
    /**
82
     * Go to the file header and work out the byte order (bytes 0-1) and TIFF identifier (bytes 2-3).
83
     * @throws RuntimeException
84
     */
85 7
    protected function checkByteOrderAndTiffIdentifier()
86
    {
87 7
        fseek($this->FileResource, 0);
88 7
        $data = unpack('c2chars/vTIFF_ID', fread($this->FileResource, 4));
89
90 7
        if (static::MAGIC_TIFF_ID !== $data['TIFF_ID']) {
91
            throw new RuntimeException('Provided GeoTIFF file is not a valid tiff file.');
92
        }
93
94 7
        $this->ByteOrder = sprintf('%c%c', $data['chars1'], $data['chars2']);
95
96 7
        if ($this->ByteOrder !== static::LITTLE_ENDIAN && $this->ByteOrder !== static::BIG_ENDIAN) {
97
            throw new RuntimeException('Provided GeoTIFF file has an unknown byte order.');
98
        }
99 7
    }
100
101
    /**
102
     * The remaining 4 bytes in the header are the offset to the IFD.
103
     */
104 7
    protected function goToIFDEntries()
105
    {
106 7
        fseek($this->FileResource, 4);
107
108 7
        $ifdOffsetFormat = $this->isLittleEndian() ? 'VIFDoffset' : 'NIFDoffset';
109 7
        $data = unpack($ifdOffsetFormat, fread($this->FileResource, 4));
110
111 7
        fseek($this->FileResource, $data['IFDoffset']);
112 7
    }
113
114
    /**
115
     * Read IFD entries which (the number of entries in each is in the first two bytes).
116
     */
117 7
    protected function readIFDEntries()
118
    {
119 7
        $countFormat = $this->isLittleEndian() ? 'vcount' : 'ncount';
120 7
        $countData = unpack($countFormat, fread($this->FileResource, 2));
121 7
        $ifdFormat = $this->isLittleEndian() ? 'vtag/vtype/Vcount/Voffset' : 'ntag/ntype/Ncount/Noffset';
122
123 7
        for ($i = 0; $i < $countData['count']; ++$i) {
124 7
            $constData = unpack($ifdFormat, fread($this->FileResource, static::TIFF_CONST_IFD_ENTRY_BYTES));
125
126 7
            switch ($constData['tag']) {
127 7
                case static::TIFF_CONST_IMAGE_WIDTH:
128 7
                    $this->NumDataCols = $constData['offset'];
129 7
                    break;
130 7
                case static::TIFF_CONST_IMAGE_LENGTH:
131 7
                    $this->NumDataRows = $constData['offset'];
132 7
                    break;
133 7
                case static::TIFF_CONST_STRIPOFSETS:
134 7
                    $this->StripOffsets = $constData['offset'];
135 7
                    break;
136 7
            }
137 7
        }
138 7
    }
139
140
    /**
141
     * @return bool
142
     */
143 7
    protected function isLittleEndian()
144
    {
145 7
        return static::LITTLE_ENDIAN === $this->ByteOrder;
146
    }
147
148
    /**
149
     * @param  float   $relativeLatitude
150
     * @param  float   $relativeLongitude
151
     * @return float[] array(row, col)
152
     */
153 7
    public function getExactRowAndColFor($relativeLatitude, $relativeLongitude)
154
    {
155
        return [
156 7
            $relativeLatitude * ($this->NumDataRows - 1),
157 7
            $relativeLongitude * ($this->NumDataCols - 1),
158 7
        ];
159
    }
160
161
    /**
162
     * @param  int      $row
163
     * @param  int      $col
164
     * @return int|bool
165
     */
166 7
    public function getElevationFor($row, $col)
167
    {
168 7
        fseek($this->FileResource, $this->StripOffsets + ($row * static::LEN_OFFSET));
169
170 7
        $firstColumnData = unpack('Voffset', fread($this->FileResource, static::LEN_OFFSET));
171
172 7
        fseek($this->FileResource, $firstColumnData['offset'] + $col * static::BYTES_PER_SAMPLE);
173
174 7
        $elevation = unpack('velevation', fread($this->FileResource, static::BYTES_PER_SAMPLE))['elevation'];
175
176 7
        return ($elevation <= self::UNKNOWN || $elevation === -self::UNKNOWN) ? false : $elevation;
177
    }
178
}
179