Completed
Push — master ( f12f85...ac41f4 )
by Hannes
02:45
created

GeoTIFFReader::readIFDEntries()   C

Complexity

Conditions 7
Paths 20

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 22
ccs 19
cts 19
cp 1
rs 6.9811
cc 7
eloc 16
nc 20
nop 0
crap 7
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\Provider\AbstractResourceReader;
15
16
class GeoTIFFReader extends AbstractResourceReader
17
{
18
    /**
19
     * The number of bytes required to hold a TIFF offset address.
20
     * @var int
21
     */
22
    const LEN_OFFSET = 4;
23
24
    /**
25
     * The number of bytes containing each item of elevation data
26
     * (= BitsPerSample tag value / 8).
27
     * @var int
28
     */
29
    const BYTES_PER_SAMPLE = 2;
30
31
    /**
32
     * Magic number located at bytes 2-3 which identifies a TIFF file.
33
     * @var int
34
     */
35
    const MAGIC_TIFF_ID = 42;
36
37
    /** @var int */
38
    const TIFF_CONST_STRIPOFSETS = 273;
39
40
    /** @var int */
41
    const TIFF_CONST_IMAGE_WIDTH = 256;
42
43
    /** @var int */
44
    const TIFF_CONST_IMAGE_LENGTH = 257;
45
46
    /** @var int */
47
    const TIFF_CONST_STRIPBYTECOUNTS = 279;
48
49
    /** @var int */
50
    const TIFF_CONST_IFD_ENTRY_BYTES = 12;
51
52
    /** @var string */
53
    const BIG_ENDIAN = 'MM';
54
55
    /** @var string */
56
    const LITTLE_ENDIAN = 'II';
57
58
    /** @var int */
59
    const UNKNOWN = -32768;
60
61
    /** @var int */
62
    protected $NumDataRows;
63
64
    /** @var int */
65
    protected $NumDataCols;
66
67
    /** @var int */
68
    protected $StripOffsets;
69
70
    /** @var string */
71
    protected $ByteOrder;
72
73
    /**
74
     * @param string $filename
0 ignored issues
show
Bug introduced by
There is no parameter named $filename. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
75
     */
76 6
    public function readHeader()
77
    {
78 6
        $this->checkByteOrderAndTiffIdentifier();
79 6
        $this->goToIFDEntries();
80 6
        $this->readIFDEntries();
81 6
    }
82
83
    /**
84
     * Go to the file header and work out the byte order (bytes 0-1) and TIFF identifier (bytes 2-3).
85
     * @throws \RuntimeException
86
     */
87 6
    protected function checkByteOrderAndTiffIdentifier()
88
    {
89 6
        fseek($this->FileResource, 0);
90 6
        $data = unpack('c2chars/vTIFF_ID', fread($this->FileResource, 4));
91
92 6
        if (static::MAGIC_TIFF_ID !== $data['TIFF_ID']) {
93
            throw new \RuntimeException('Provider file "'.$this->CurrentFilename.'"" is not a valid tiff file.');
0 ignored issues
show
Bug introduced by
The property CurrentFilename does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
94
        }
95
96 6
        $this->ByteOrder = sprintf('%c%c', $data['chars1'], $data['chars2']);
97
98 6
        if ($this->ByteOrder !== static::LITTLE_ENDIAN && $this->ByteOrder !== static::BIG_ENDIAN) {
99
            throw new \RuntimeException('Provider file "'.$this->CurrentFilename.'"" has an unknown byte order.');
100
        }
101 6
    }
102
103
    /**
104
     * The remaining 4 bytes in the header are the offset to the IFD.
105
     */
106 6
    protected function goToIFDEntries()
107
    {
108 6
        fseek($this->FileResource, 4);
109
110 6
        $ifdOffsetFormat = $this->isLittleEndian() ? 'VIFDoffset' : 'NIFDoffset';
111 6
        $data = unpack($ifdOffsetFormat, fread($this->FileResource, 4));
112
113 6
        fseek($this->FileResource, $data['IFDoffset']);
114 6
    }
115
116
    /**
117
     * Read IFD entries which (the number of entries in each is in the first two bytes).
118
     */
119 6
    protected function readIFDEntries()
120
    {
121 6
        $countFormat = $this->isLittleEndian() ? 'vcount' : 'ncount';
122 6
        $countData = unpack($countFormat, fread($this->FileResource, 2));
123 6
        $ifdFormat = $this->isLittleEndian() ? 'vtag/vtype/Vcount/Voffset' : 'ntag/ntype/Ncount/Noffset';
124
125 6
        for ($i = 0; $i < $countData['count']; ++$i) {
126 6
            $constData = unpack($ifdFormat, fread($this->FileResource, static::TIFF_CONST_IFD_ENTRY_BYTES));
127
128 6
            switch ($constData['tag']) {
129 6
                case static::TIFF_CONST_IMAGE_WIDTH:
130 6
                    $this->NumDataCols = $constData['offset'];
131 6
                    break;
132 6
                case static::TIFF_CONST_IMAGE_LENGTH:
133 6
                    $this->NumDataRows = $constData['offset'];
134 6
                    break;
135 6
                case static::TIFF_CONST_STRIPOFSETS:
136 6
                    $this->StripOffsets = $constData['offset'];
137 6
                    break;
138 6
            }
139 6
        }
140 6
    }
141
142
    /**
143
     * @return bool
144
     */
145 6
    protected function isLittleEndian()
146
    {
147 6
        return static::LITTLE_ENDIAN === $this->ByteOrder;
148
    }
149
150
    /**
151
     * @param  float   $relativeLatitude
152
     * @param  float   $relativeLongitude
153
     * @return float[] array(row, col)
154
     */
155 6
    public function getExactRowAndColFor($relativeLatitude, $relativeLongitude)
156
    {
157
        return [
158 6
            $relativeLatitude * ($this->NumDataRows - 1),
159 6
            $relativeLongitude * ($this->NumDataCols - 1),
160 6
        ];
161
    }
162
163
    /**
164
     * @param  int      $row
165
     * @param  int      $col
166
     * @return int|bool
167
     */
168 6
    public function getElevationFor($row, $col)
169
    {
170 6
        fseek($this->FileResource, $this->StripOffsets + ($row * static::LEN_OFFSET));
171
172 6
        $firstColumnData = unpack('Voffset', fread($this->FileResource, static::LEN_OFFSET));
173
174 6
        fseek($this->FileResource, $firstColumnData['offset'] + $col * static::BYTES_PER_SAMPLE);
175
176 6
        $elevation = unpack('velevation', fread($this->FileResource, static::BYTES_PER_SAMPLE))['elevation'];
177
178 6
        return ($elevation <= self::UNKNOWN || $elevation === -self::UNKNOWN) ? false : $elevation;
179
    }
180
}
181