ShapeRecord::addPoint()   C
last analyzed

Complexity

Conditions 14
Paths 14

Size

Total Lines 39
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 15.2919

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 14
eloc 29
c 3
b 0
f 0
nc 14
nop 2
dl 0
loc 39
ccs 13
cts 16
cp 0.8125
crap 15.2919
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * phpMyAdmin ShapeFile library
7
 * <https://github.com/phpmyadmin/shapefile/>.
8
 *
9
 * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net>
10
 * Copyright 2016 - 2017 Michal Čihař <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU General Public License
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, you can download one from
23
 * https://www.gnu.org/copyleft/gpl.html.
24
 */
25
26
namespace PhpMyAdmin\ShapeFile;
27
28
use function array_values;
29
use function count;
30
use function fwrite;
31
use function in_array;
32
use function is_array;
33
use function pack;
34
use function sprintf;
35
use function strlen;
36
37
/**
38
 * ShapeFile record class.
39
 */
40
class ShapeRecord
41
{
42
    /** @var resource */
43
    private $shpFile;
44
45
    private ShapeFile|null $shapeFile = null;
46
47
    private int $size = 0;
48
49
    private int $read = 0;
50
51
    public int $recordNumber = 0;
52
53
    public string $lastError = '';
54
55
    /** @var mixed[] */
56
    public array $shpData = [];
57
58
    /** @var mixed[] */
59
    public array $dbfData = [];
60
61 86
    public function __construct(public int $shapeType)
62
    {
63 86
    }
64
65
    /**
66
     * Loads record from files.
67
     *
68
     * @param ShapeFile      $shapeFile The ShapeFile object
69
     * @param resource|false $dbfFile   Opened DBF file
70
     */
71 82
    public function loadFromFile(ShapeFile $shapeFile, $dbfFile): void
72
    {
73 82
        $this->shapeFile = $shapeFile;
74 82
        $this->loadHeaders();
75
76
        /* No header read */
77 82
        if ($this->read === 0) {
78 82
            return;
79
        }
80
81 82
        match ($this->shapeType) {
82 82
            ShapeType::NULL => $this->loadNullRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadNullRecord() targeting PhpMyAdmin\ShapeFile\ShapeRecord::loadNullRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
83 82
            ShapeType::POINT => $this->loadPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...cord::loadPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
84 82
            ShapeType::POINT_M => $this->loadPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::loadPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
85 82
            ShapeType::POINT_Z => $this->loadPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::loadPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
86 82
            ShapeType::POLY_LINE => $this->loadPolyLineRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolyLineRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
87 82
            ShapeType::POLY_LINE_M => $this->loadPolyLineMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineMRecord() targeting PhpMyAdmin\ShapeFile\Sha...::loadPolyLineMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
88 82
            ShapeType::POLY_LINE_Z => $this->loadPolyLineZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineZRecord() targeting PhpMyAdmin\ShapeFile\Sha...::loadPolyLineZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
89 82
            ShapeType::POLYGON => $this->loadPolygonRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonRecord() targeting PhpMyAdmin\ShapeFile\Sha...rd::loadPolygonRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
90 82
            ShapeType::POLYGON_M => $this->loadPolygonMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonMRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolygonMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
91 82
            ShapeType::POLYGON_Z => $this->loadPolygonZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonZRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolygonZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
92 82
            ShapeType::MULTI_POINT => $this->loadMultiPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...:loadMultiPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93 82
            ShapeType::MULTI_POINT_M => $this->loadMultiPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...loadMultiPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
94 82
            ShapeType::MULTI_POINT_Z => $this->loadMultiPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...loadMultiPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
95 82
            default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setError(sprintf(...d.', $this->shapeType)) targeting PhpMyAdmin\ShapeFile\ShapeRecord::setError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
96 82
        };
97
98
        /* We need to skip rest of the record */
99 82
        while ($this->read < $this->size) {
100 24
            $this->loadData('V', 4);
101
        }
102
103
        /* Check if we didn't read too much */
104 82
        if ($this->read !== $this->size) {
105
            $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size));
106
        }
107
108 82
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
109 38
            return;
110
        }
111
112 44
        $this->loadDBFData($dbfFile);
113
    }
114
115
    /**
116
     * Saves record to files.
117
     *
118
     * @param resource       $shpFile      Opened SHP file
119
     * @param resource|false $dbfFile      Opened DBF file
120
     * @param int            $recordNumber Record number
121
     */
122 54
    public function saveToFile($shpFile, $dbfFile, int $recordNumber): void
123
    {
124 54
        $this->shpFile = $shpFile;
125 54
        $this->recordNumber = $recordNumber;
126 54
        $this->saveHeaders();
127
128 54
        match ($this->shapeType) {
129 54
            ShapeType::NULL => null, // Nothing to save
130 54
            ShapeType::POINT => $this->savePointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointRecord() targeting PhpMyAdmin\ShapeFile\Sha...cord::savePointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
131 54
            ShapeType::POINT_M => $this->savePointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::savePointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
132 54
            ShapeType::POINT_Z => $this->savePointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::savePointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
133 54
            ShapeType::POLY_LINE => $this->savePolyLineRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolyLineRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
134 54
            ShapeType::POLY_LINE_M => $this->savePolyLineMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineMRecord() targeting PhpMyAdmin\ShapeFile\Sha...::savePolyLineMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
135 54
            ShapeType::POLY_LINE_Z => $this->savePolyLineZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineZRecord() targeting PhpMyAdmin\ShapeFile\Sha...::savePolyLineZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
136 54
            ShapeType::POLYGON => $this->savePolygonRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonRecord() targeting PhpMyAdmin\ShapeFile\Sha...rd::savePolygonRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
137 54
            ShapeType::POLYGON_M => $this->savePolygonMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonMRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolygonMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
138 54
            ShapeType::POLYGON_Z => $this->savePolygonZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonZRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolygonZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
139 54
            ShapeType::MULTI_POINT => $this->saveMultiPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...:saveMultiPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
140 54
            ShapeType::MULTI_POINT_M => $this->saveMultiPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...saveMultiPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
141 54
            ShapeType::MULTI_POINT_Z => $this->saveMultiPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...saveMultiPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
142 54
            default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setError(sprintf(...d.', $this->shapeType)) targeting PhpMyAdmin\ShapeFile\ShapeRecord::setError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
143 54
        };
144
145 54
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
146 24
            return;
147
        }
148
149 30
        $this->saveDBFData($dbfFile);
150
    }
151
152
    /**
153
     * Updates DBF data to match header.
154
     *
155
     * @param mixed[] $header DBF structure header
156
     */
157 54
    public function updateDBFInfo(array $header): void
158
    {
159 54
        $tmp = $this->dbfData;
160 54
        $this->dbfData = [];
161 54
        foreach ($header as [$value]) {
162 54
            $this->dbfData[$value] = $tmp[$value] ?? '';
163
        }
164
    }
165
166
    /**
167
     * Reads data.
168
     *
169
     * @param string      $type  type for unpack()
170
     * @param int<0, max> $count number of bytes
171
     */
172 82
    private function loadData(string $type, int $count): mixed
173
    {
174 82
        $data = $this->shapeFile->readSHP($count);
0 ignored issues
show
Bug introduced by
The method readSHP() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
        /** @scrutinizer ignore-call */ 
175
        $data = $this->shapeFile->readSHP($count);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
175 82
        if ($data === false) {
176
            return false;
177
        }
178
179 82
        $this->read += strlen($data);
180
181 82
        return Util::loadData($type, $data);
182
    }
183
184
    /**
185
     * Loads metadata header from a file.
186
     */
187 82
    private function loadHeaders(): void
188
    {
189 82
        $this->shapeType = -1;
190 82
        $recordNumber = $this->loadData('N', 4);
191 82
        if ($recordNumber === false) {
192 82
            return;
193
        }
194
195 82
        $this->recordNumber = (int) $recordNumber;
196
197
        // We read the length of the record
198 82
        $size = $this->loadData('N', 4);
199 82
        if ($size === false) {
200
            return;
201
        }
202
203 82
        $this->size = ($size * 2) + 8;
204
205 82
        $shapeType = $this->loadData('V', 4);
206 82
        if ($shapeType === false) {
207
            return;
208
        }
209
210 82
        $this->shapeType = (int) $shapeType;
211
    }
212
213
    /**
214
     * Saves metadata header to a file.
215
     */
216 54
    private function saveHeaders(): void
217
    {
218 54
        fwrite($this->shpFile, pack('N', $this->recordNumber));
219 54
        fwrite($this->shpFile, pack('N', $this->getContentLength()));
220 54
        fwrite($this->shpFile, pack('V', $this->shapeType));
221
    }
222
223
    /** @return mixed[] */
224 82
    private function loadPoint(): array
225
    {
226 82
        return [
227 82
            'x' => $this->loadData('d', 8),
228 82
            'y' => $this->loadData('d', 8),
229 82
        ];
230
    }
231
232
    /** @return mixed[] */
233 10
    private function loadPointM(): array
234
    {
235 10
        $data = $this->loadPoint();
236
237 10
        $data['m'] = $this->loadData('d', 8);
238
239 10
        return $data;
240
    }
241
242
    /** @return mixed[] */
243 10
    private function loadPointZ(): array
244
    {
245 10
        $data = $this->loadPoint();
246
247 10
        $data['z'] = $this->loadData('d', 8);
248 10
        $data['m'] = $this->loadData('d', 8);
249
250 10
        return $data;
251
    }
252
253
    /** @param mixed[] $data */
254 46
    private function savePoint(array $data): void
255
    {
256 46
        fwrite($this->shpFile, Util::packDouble($data['x']));
257 46
        fwrite($this->shpFile, Util::packDouble($data['y']));
258
    }
259
260
    /** @param mixed[] $data */
261 10
    private function savePointM(array $data): void
262
    {
263 10
        fwrite($this->shpFile, Util::packDouble($data['x']));
264 10
        fwrite($this->shpFile, Util::packDouble($data['y']));
265 10
        fwrite($this->shpFile, Util::packDouble($data['m']));
266
    }
267
268
    /** @param mixed[] $data */
269 10
    private function savePointZ(array $data): void
270
    {
271 10
        fwrite($this->shpFile, Util::packDouble($data['x']));
272 10
        fwrite($this->shpFile, Util::packDouble($data['y']));
273 10
        fwrite($this->shpFile, Util::packDouble($data['z']));
274 10
        fwrite($this->shpFile, Util::packDouble($data['m']));
275
    }
276
277
    private function loadNullRecord(): void
278
    {
279
        $this->shpData = [];
280
    }
281
282 22
    private function loadPointRecord(): void
283
    {
284 22
        $this->shpData = $this->loadPoint();
285
    }
286
287 10
    private function loadPointMRecord(): void
288
    {
289 10
        $this->shpData = $this->loadPointM();
290
    }
291
292 10
    private function loadPointZRecord(): void
293
    {
294 10
        $this->shpData = $this->loadPointZ();
295
    }
296
297 10
    private function savePointRecord(): void
298
    {
299 10
        $this->savePoint($this->shpData);
300
    }
301
302 10
    private function savePointMRecord(): void
303
    {
304 10
        $this->savePointM($this->shpData);
305
    }
306
307 10
    private function savePointZRecord(): void
308
    {
309 10
        $this->savePointZ($this->shpData);
310
    }
311
312 58
    private function loadBBox(): void
313
    {
314 58
        $this->shpData['xmin'] = $this->loadData('d', 8);
315 58
        $this->shpData['ymin'] = $this->loadData('d', 8);
316 58
        $this->shpData['xmax'] = $this->loadData('d', 8);
317 58
        $this->shpData['ymax'] = $this->loadData('d', 8);
318
    }
319
320 16
    private function loadMultiPointRecord(): void
321
    {
322 16
        $this->shpData = [];
323 16
        $this->loadBBox();
324
325 16
        $this->shpData['numpoints'] = $this->loadData('V', 4);
326
327 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
328 16
            $this->shpData['points'][] = $this->loadPoint();
329
        }
330
    }
331
332 12
    private function loadMultiPointMZRecord(string $type): void
333
    {
334
        /* The m dimension is optional, depends on bounding box data */
335 12
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
336 12
            return;
337
        }
338
339 8
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
340 8
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
341
342 8
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
343 8
            $this->shpData['points'][$i][$type] = $this->loadData('d', 8);
344
        }
345
    }
346
347 4
    private function loadMultiPointMRecord(): void
348
    {
349 4
        $this->loadMultiPointRecord();
350
351 4
        $this->loadMultiPointMZRecord('m');
352
    }
353
354 8
    private function loadMultiPointZRecord(): void
355
    {
356 8
        $this->loadMultiPointRecord();
357
358 8
        $this->loadMultiPointMZRecord('z');
359 8
        $this->loadMultiPointMZRecord('m');
360
    }
361
362 12
    private function saveMultiPointRecord(): void
363
    {
364 12
        fwrite($this->shpFile, pack(
365 12
            'dddd',
366 12
            $this->shpData['xmin'],
367 12
            $this->shpData['ymin'],
368 12
            $this->shpData['xmax'],
369 12
            $this->shpData['ymax'],
370 12
        ));
371
372 12
        fwrite($this->shpFile, pack('V', $this->shpData['numpoints']));
373
374 12
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
375 12
            $this->savePoint($this->shpData['points'][$i]);
376
        }
377
    }
378
379 8
    private function saveMultiPointMZRecord(string $type): void
380
    {
381 8
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
382
383 8
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
384 8
            fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type]));
385
        }
386
    }
387
388 4
    private function saveMultiPointMRecord(): void
389
    {
390 4
        $this->saveMultiPointRecord();
391
392 4
        $this->saveMultiPointMZRecord('m');
393
    }
394
395 4
    private function saveMultiPointZRecord(): void
396
    {
397 4
        $this->saveMultiPointRecord();
398
399 4
        $this->saveMultiPointMZRecord('z');
400 4
        $this->saveMultiPointMZRecord('m');
401
    }
402
403 42
    private function loadPolyLineRecord(): void
404
    {
405 42
        $this->shpData = [];
406 42
        $this->loadBBox();
407
408 42
        $this->shpData['numparts'] = $this->loadData('V', 4);
409 42
        $this->shpData['numpoints'] = $this->loadData('V', 4);
410
411 42
        $numparts = $this->shpData['numparts'];
412 42
        $numpoints = $this->shpData['numpoints'];
413
414 42
        for ($i = 0; $i < $numparts; ++$i) {
415 42
            $this->shpData['parts'][$i] = $this->loadData('V', 4);
416
        }
417
418 42
        $part = 0;
419 42
        for ($i = 0; $i < $numpoints; ++$i) {
420 42
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
421 38
                ++$part;
422
            }
423
424
            if (
425 42
                ! isset($this->shpData['parts'][$part]['points'])
426 42
                || ! is_array($this->shpData['parts'][$part]['points'])
427
            ) {
428 42
                $this->shpData['parts'][$part] = ['points' => []];
429
            }
430
431 42
            $this->shpData['parts'][$part]['points'][] = $this->loadPoint();
432
        }
433
    }
434
435 20
    private function loadPolyLineMZRecord(string $type): void
436
    {
437
        /* The m dimension is optional, depends on bounding box data */
438 20
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
439 20
            return;
440
        }
441
442 12
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
443 12
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
444
445 12
        $numparts = $this->shpData['numparts'];
446 12
        $numpoints = $this->shpData['numpoints'];
447
448 12
        $part = 0;
449 12
        for ($i = 0; $i < $numpoints; ++$i) {
450 12
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
451
                ++$part;
452
            }
453
454 12
            $this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8);
455
        }
456
    }
457
458 8
    private function loadPolyLineMRecord(): void
459
    {
460 8
        $this->loadPolyLineRecord();
461
462 8
        $this->loadPolyLineMZRecord('m');
463
    }
464
465 12
    private function loadPolyLineZRecord(): void
466
    {
467 12
        $this->loadPolyLineRecord();
468
469 12
        $this->loadPolyLineMZRecord('z');
470 12
        $this->loadPolyLineMZRecord('m');
471
    }
472
473 30
    private function savePolyLineRecord(): void
474
    {
475 30
        fwrite($this->shpFile, pack(
476 30
            'dddd',
477 30
            $this->shpData['xmin'],
478 30
            $this->shpData['ymin'],
479 30
            $this->shpData['xmax'],
480 30
            $this->shpData['ymax'],
481 30
        ));
482
483 30
        fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
484
485 30
        $partIndex = 0;
486 30
        for ($i = 0; $i < $this->shpData['numparts']; ++$i) {
487 30
            fwrite($this->shpFile, pack('V', $partIndex));
488 30
            $partIndex += count($this->shpData['parts'][$i]['points']);
489
        }
490
491 30
        foreach ($this->shpData['parts'] as $partData) {
492 30
            foreach ($partData['points'] as $pointData) {
493 30
                $this->savePoint($pointData);
494
            }
495
        }
496
    }
497
498 16
    private function savePolyLineMZRecord(string $type): void
499
    {
500 16
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
501
502 16
        foreach ($this->shpData['parts'] as $partData) {
503 16
            foreach ($partData['points'] as $pointData) {
504 16
                fwrite($this->shpFile, Util::packDouble($pointData[$type]));
505
            }
506
        }
507
    }
508
509 8
    private function savePolyLineMRecord(): void
510
    {
511 8
        $this->savePolyLineRecord();
512
513 8
        $this->savePolyLineMZRecord('m');
514
    }
515
516 8
    private function savePolyLineZRecord(): void
517
    {
518 8
        $this->savePolyLineRecord();
519
520 8
        $this->savePolyLineMZRecord('z');
521 8
        $this->savePolyLineMZRecord('m');
522
    }
523
524 12
    private function loadPolygonRecord(): void
525
    {
526 12
        $this->loadPolyLineRecord();
527
    }
528
529 4
    private function loadPolygonMRecord(): void
530
    {
531 4
        $this->loadPolyLineMRecord();
532
    }
533
534 8
    private function loadPolygonZRecord(): void
535
    {
536 8
        $this->loadPolyLineZRecord();
537
    }
538
539 4
    private function savePolygonRecord(): void
540
    {
541 4
        $this->savePolyLineRecord();
542
    }
543
544 4
    private function savePolygonMRecord(): void
545
    {
546 4
        $this->savePolyLineMRecord();
547
    }
548
549 4
    private function savePolygonZRecord(): void
550
    {
551 4
        $this->savePolyLineZRecord();
552
    }
553
554
    /** @param mixed[] $point */
555 54
    private function adjustBBox(array $point): void
556
    {
557
        // Adjusts bounding box based on point
558 54
        foreach (['x', 'y', 'z', 'm'] as $direction) {
559 54
            if (! isset($point[$direction])) {
560 38
                continue;
561
            }
562
563 54
            $min = $direction . 'min';
564 54
            $max = $direction . 'max';
565 54
            if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) {
566 54
                $this->shpData[$min] = $point[$direction];
567
            }
568
569 54
            if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) {
570 42
                continue;
571
            }
572
573 54
            $this->shpData[$max] = $point[$direction];
574
        }
575
    }
576
577
    /**
578
     * Adjust point and bounding box when adding point.
579
     * Sets dimension to 0 if not set.
580
     *
581
     * @param mixed[] $point Point data
582
     *
583
     * @return mixed[] Fixed point data
584
     */
585 54
    private function adjustPoint(array $point): array
586
    {
587 54
        if (in_array($this->shapeType, ShapeType::MEASURED_TYPES, true)) {
588 38
            $point['m'] ??= 0.0;
589
        }
590
591 54
        if (in_array($this->shapeType, ShapeType::TYPES_WITH_Z, true)) {
592 22
            $point['z'] ??= 0.0;
593
        }
594
595 54
        return $point;
596
    }
597
598
    /**
599
     * Adds point to a record.
600
     *
601
     * @param mixed[] $point     Point data
602
     * @param int     $partIndex Part index
603
     */
604 54
    public function addPoint(array $point, int $partIndex = 0): void
605
    {
606 54
        $point = $this->adjustPoint($point);
607 54
        switch ($this->shapeType) {
608
            case ShapeType::NULL:
609
                //Don't add anything
610
                return;
611
612
            case ShapeType::POINT:
613
            case ShapeType::POINT_Z:
614
            case ShapeType::POINT_M:
615
                //Substitutes the value of the current point
616 18
                $this->shpData = $point;
617 18
                break;
618
            case ShapeType::POLY_LINE:
619
            case ShapeType::POLYGON:
620
            case ShapeType::POLY_LINE_Z:
621
            case ShapeType::POLYGON_Z:
622
            case ShapeType::POLY_LINE_M:
623
            case ShapeType::POLYGON_M:
624
                //Adds a new point to the selected part
625 30
                $this->shpData['parts'][$partIndex]['points'][] = $point;
626 30
                $this->shpData['numparts'] = count($this->shpData['parts']);
627 30
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
628 30
                break;
629
            case ShapeType::MULTI_POINT:
630
            case ShapeType::MULTI_POINT_Z:
631
            case ShapeType::MULTI_POINT_M:
632
                //Adds a new point
633 12
                $this->shpData['points'][] = $point;
634 12
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
635 12
                break;
636
            default:
637
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
638
639
                return;
640
        }
641
642 54
        $this->adjustBBox($point);
643
    }
644
645
    /**
646
     * Deletes point from a record.
647
     *
648
     * @param int $pointIndex Point index
649
     * @param int $partIndex  Part index
650
     */
651 48
    public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void
652
    {
653 48
        switch ($this->shapeType) {
654
            case ShapeType::NULL:
655
                //Don't delete anything
656
                break;
657
            case ShapeType::POINT:
658
            case ShapeType::POINT_Z:
659
            case ShapeType::POINT_M:
660
                //Sets the value of the point to zero
661 12
                $this->shpData['x'] = 0.0;
662 12
                $this->shpData['y'] = 0.0;
663 12
                if (in_array($this->shapeType, [ShapeType::POINT_Z, ShapeType::POINT_M], true)) {
664 8
                    $this->shpData['m'] = 0.0;
665
                }
666
667 12
                if ($this->shapeType === ShapeType::POINT_Z) {
668 4
                    $this->shpData['z'] = 0.0;
669
                }
670
671 12
                break;
672
            case ShapeType::POLY_LINE:
673
            case ShapeType::POLYGON:
674
            case ShapeType::POLY_LINE_Z:
675
            case ShapeType::POLYGON_Z:
676
            case ShapeType::POLY_LINE_M:
677
            case ShapeType::POLYGON_M:
678
                //Deletes the point from the selected part, if exists
679
                if (
680 24
                    isset($this->shpData['parts'][$partIndex])
681 24
                    && isset($this->shpData['parts'][$partIndex]['points'][$pointIndex])
682
                ) {
683 24
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
684 24
                    for ($i = $pointIndex; $i < $count; ++$i) {
685 24
                        $point = $this->shpData['parts'][$partIndex]['points'][$i + 1];
686 24
                        $this->shpData['parts'][$partIndex]['points'][$i] = $point;
687
                    }
688
689 24
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
690 24
                    unset($this->shpData['parts'][$partIndex]['points'][$count]);
691
692 24
                    $this->shpData['numparts'] = count($this->shpData['parts']);
693 24
                    --$this->shpData['numpoints'];
694
                }
695
696 24
                break;
697
            case ShapeType::MULTI_POINT:
698
            case ShapeType::MULTI_POINT_Z:
699
            case ShapeType::MULTI_POINT_M:
700
                //Deletes the point, if exists
701 12
                if (isset($this->shpData['points'][$pointIndex])) {
702 12
                    $count = count($this->shpData['points']) - 1;
703 12
                    for ($i = $pointIndex; $i < $count; ++$i) {
704 12
                        $this->shpData['points'][$i] = $this->shpData['points'][$i + 1];
705
                    }
706
707 12
                    unset($this->shpData['points'][count($this->shpData['points']) - 1]);
708
709 12
                    --$this->shpData['numpoints'];
710
                }
711
712 12
                break;
713
            default:
714
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
715
                break;
716
        }
717
    }
718
719
    /**
720
     * Returns length of content.
721
     */
722 54
    public function getContentLength(): int|null
723
    {
724
        // The content length for a record is the length of the record contents section measured in 16-bit words.
725
        // one coordinate makes 4 16-bit words (64 bit double)
726 54
        switch ($this->shapeType) {
727
            case ShapeType::NULL:
728
                $result = 0;
729
                break;
730
            case ShapeType::POINT:
731 10
                $result = 10;
732 10
                break;
733
            case ShapeType::POINT_M:
734 10
                $result = 10 + 4;
735 10
                break;
736
            case ShapeType::POINT_Z:
737 10
                $result = 10 + 8;
738 10
                break;
739
            case ShapeType::POLY_LINE:
740
            case ShapeType::POLYGON:
741 14
                $count = count($this->shpData['parts']);
742 14
                $result = 22 + 2 * $count;
743 14
                for ($i = 0; $i < $count; ++$i) {
744 14
                    $result += 8 * count($this->shpData['parts'][$i]['points']);
745
                }
746
747 14
                break;
748
            case ShapeType::POLY_LINE_M:
749
            case ShapeType::POLYGON_M:
750 8
                $count = count($this->shpData['parts']);
751 8
                $result = 22 + (2 * 4) + 2 * $count;
752 8
                for ($i = 0; $i < $count; ++$i) {
753 8
                    $result += (8 + 4) * count($this->shpData['parts'][$i]['points']);
754
                }
755
756 8
                break;
757
            case ShapeType::POLY_LINE_Z:
758
            case ShapeType::POLYGON_Z:
759 8
                $count = count($this->shpData['parts']);
760 8
                $result = 22 + (4 * 4) + 2 * $count;
761 8
                for ($i = 0; $i < $count; ++$i) {
762 8
                    $result += (8 + 8) * count($this->shpData['parts'][$i]['points']);
763
                }
764
765 8
                break;
766
            case ShapeType::MULTI_POINT:
767 4
                $result = 20 + 8 * count($this->shpData['points']);
768 4
                break;
769
            case ShapeType::MULTI_POINT_M:
770 4
                $result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']);
771 4
                break;
772
            case ShapeType::MULTI_POINT_Z:
773 4
                $result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']);
774 4
                break;
775
            default:
776
                $result = null;
777
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
778
                break;
779
        }
780
781 54
        return $result;
782
    }
783
784
    /** @param resource $dbfFile Opened DBF file */
785 44
    private function loadDBFData($dbfFile): void
786
    {
787 44
        $this->dbfData = @dbase_get_record_with_names($dbfFile, $this->recordNumber);
0 ignored issues
show
Bug introduced by
The function dbase_get_record_with_names was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

787
        $this->dbfData = @/** @scrutinizer ignore-call */ dbase_get_record_with_names($dbfFile, $this->recordNumber);
Loading history...
introduced by
Function dbase_get_record_with_names() should not be referenced via a fallback global name, but via a use statement.
Loading history...
788 44
        unset($this->dbfData['deleted']);
789
    }
790
791
    /** @param resource $dbfFile */
792 30
    private function saveDBFData($dbfFile): void
793
    {
794 30
        if ($this->dbfData === []) {
795
            return;
796
        }
797
798 30
        unset($this->dbfData['deleted']);
799 30
        if ($this->recordNumber <= dbase_numrecords($dbfFile)) {
0 ignored issues
show
Bug introduced by
The function dbase_numrecords was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

799
        if ($this->recordNumber <= /** @scrutinizer ignore-call */ dbase_numrecords($dbfFile)) {
Loading history...
introduced by
Function dbase_numrecords() should not be referenced via a fallback global name, but via a use statement.
Loading history...
800
            if (! dbase_replace_record($dbfFile, array_values($this->dbfData), $this->recordNumber)) {
0 ignored issues
show
Bug introduced by
The function dbase_replace_record was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

800
            if (! /** @scrutinizer ignore-call */ dbase_replace_record($dbfFile, array_values($this->dbfData), $this->recordNumber)) {
Loading history...
introduced by
Function dbase_replace_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
801
                $this->setError("I wasn't possible to update the information in the DBF file.");
802
            }
803 30
        } elseif (! dbase_add_record($dbfFile, array_values($this->dbfData))) {
0 ignored issues
show
Bug introduced by
The function dbase_add_record was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

803
        } elseif (! /** @scrutinizer ignore-call */ dbase_add_record($dbfFile, array_values($this->dbfData))) {
Loading history...
introduced by
Function dbase_add_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
804
            $this->setError("I wasn't possible to add the information to the DBF file.");
805
        }
806
    }
807
808
    /**
809
     * Sets error message.
810
     */
811
    public function setError(string $error): void
812
    {
813
        $this->lastError = $error;
814
    }
815
816
    /**
817
     * Returns shape name.
818
     *
819
     * @psalm-return non-empty-string
820
     */
821 4
    public function getShapeName(): string
822
    {
823 4
        return ShapeType::name($this->shapeType);
824
    }
825
}
826