Passed
Push — master ( 5f09f0...990103 )
by William
18:42 queued 16:08
created

ShapeRecord::saveHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 1
b 0
f 0
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 = null;
44
    /** @var resource */
45
    private $dbfFile = null;
46
    /** @var ShapeFile */
47
    private $shapeFile = null;
48
49
    /** @var int */
50
    private $size = 0;
51
    /** @var int */
52
    private $read = 0;
53
54
    /** @var int|null */
55
    public $recordNumber = null;
56
57
    /** @var int */
58
    public $shapeType = null;
59
60
     /** @var string */
61
    public $lastError = '';
62
63
    /** @var array */
64
    public $shpData = [];
65
    /** @var array */
66
    public $dbfData = [];
67
68 175
    public function __construct(int $shapeType)
69
    {
70 175
        $this->shapeType = $shapeType;
71 175
    }
72
73
    /**
74
     * Loads record from files.
75
     *
76
     * @param ShapeFile $shapeFile The ShapeFile object
77
     * @param resource  $shpFile   Opened SHP file (by reference)
78
     * @param resource  $dbfFile   Opened DBF file (by reference)
79
     */
80 167
    public function loadFromFile(ShapeFile &$shapeFile, &$shpFile, &$dbfFile): void
81
    {
82 167
        $this->shapeFile = $shapeFile;
83 167
        $this->shpFile = $shpFile;
84 167
        $this->dbfFile = $dbfFile;
85 167
        $this->loadHeaders();
86
87
        /* No header read */
88 167
        if ($this->read === 0) {
89 167
            return;
90
        }
91
92 167
        switch ($this->shapeType) {
93 167
            case 0:
94
                $this->loadNullRecord();
95
                break;
96 167
            case 1:
97 47
                $this->loadPointRecord();
98 47
                break;
99 135
            case 21:
100 23
                $this->loadPointMRecord();
101 23
                break;
102 127
            case 11:
103 23
                $this->loadPointZRecord();
104 23
                break;
105 119
            case 3:
106 23
                $this->loadPolyLineRecord();
107 23
                break;
108 96
            case 23:
109 8
                $this->loadPolyLineMRecord();
110 8
                break;
111 88
            case 13:
112 8
                $this->loadPolyLineZRecord();
113 8
                break;
114 80
            case 5:
115 24
                $this->loadPolygonRecord();
116 24
                break;
117 56
            case 25:
118 8
                $this->loadPolygonMRecord();
119 8
                break;
120 48
            case 15:
121 16
                $this->loadPolygonZRecord();
122 16
                break;
123 32
            case 8:
124 8
                $this->loadMultiPointRecord();
125 8
                break;
126 24
            case 28:
127 8
                $this->loadMultiPointMRecord();
128 8
                break;
129 16
            case 18:
130 16
                $this->loadMultiPointZRecord();
131 16
                break;
132
            default:
133
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
0 ignored issues
show
Bug introduced by
It seems like $this->shapeType can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

133
                $this->setError(sprintf('The Shape Type "%s" is not supported.', /** @scrutinizer ignore-type */ $this->shapeType));
Loading history...
134
                break;
135
        }
136
137
        /* We need to skip rest of the record */
138 167
        while ($this->read < $this->size) {
139 48
            $this->loadData('V', 4);
140
        }
141
142
        /* Check if we didn't read too much */
143 167
        if ($this->read !== $this->size) {
144
            $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size));
145
        }
146
147 167
        if (! ShapeFile::supportsDbase() || ! isset($this->dbfFile)) {
148 57
            return;
149
        }
150
151 110
        $this->loadDBFData();
152 110
    }
153
154
    /**
155
     * Saves record to files.
156
     *
157
     * @param resource $shpFile      Opened SHP file
158
     * @param resource $dbfFile      Opened DBF file
159
     * @param int      $recordNumber Record number
160
     */
161 111
    public function saveToFile(&$shpFile, &$dbfFile, int $recordNumber): void
162
    {
163 111
        $this->shpFile = $shpFile;
164 111
        $this->dbfFile = $dbfFile;
165 111
        $this->recordNumber = $recordNumber;
166 111
        $this->saveHeaders();
167
168 111
        switch ($this->shapeType) {
169 111
            case 0:
170
                // Nothing to save
171
                break;
172 111
            case 1:
173 23
                $this->savePointRecord();
174 23
                break;
175 103
            case 21:
176 23
                $this->savePointMRecord();
177 23
                break;
178 95
            case 11:
179 23
                $this->savePointZRecord();
180 23
                break;
181 87
            case 3:
182 23
                $this->savePolyLineRecord();
183 23
                break;
184 64
            case 23:
185 8
                $this->savePolyLineMRecord();
186 8
                break;
187 56
            case 13:
188 8
                $this->savePolyLineZRecord();
189 8
                break;
190 48
            case 5:
191 8
                $this->savePolygonRecord();
192 8
                break;
193 40
            case 25:
194 8
                $this->savePolygonMRecord();
195 8
                break;
196 32
            case 15:
197 8
                $this->savePolygonZRecord();
198 8
                break;
199 24
            case 8:
200 8
                $this->saveMultiPointRecord();
201 8
                break;
202 16
            case 28:
203 8
                $this->saveMultiPointMRecord();
204 8
                break;
205 8
            case 18:
206 8
                $this->saveMultiPointZRecord();
207 8
                break;
208
            default:
209
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
210
                break;
211
        }
212
213 111
        if (! ShapeFile::supportsDbase() || $this->dbfFile === null) {
214 36
            return;
215
        }
216
217 75
        $this->saveDBFData();
218 75
    }
219
220
    /**
221
     * Updates DBF data to match header.
222
     *
223
     * @param array $header DBF structure header
224
     */
225 111
    public function updateDBFInfo(array $header): void
226
    {
227 111
        $tmp = $this->dbfData;
228 111
        unset($this->dbfData);
229 111
        $this->dbfData = [];
230 111
        foreach ($header as $value) {
231 111
            $this->dbfData[$value[0]] = $tmp[$value[0]] ?? '';
232
        }
233 111
    }
234
235
    /**
236
     * Reads data.
237
     *
238
     * @param string $type  type for unpack()
239
     * @param int    $count number of bytes
240
     *
241
     * @return mixed
242
     */
243 167
    private function loadData(string $type, int $count)
244
    {
245 167
        $data = $this->shapeFile->readSHP($count);
246 167
        if ($data === false) {
247
            return false;
248
        }
249
250 167
        $this->read += strlen($data);
251
252 167
        return Util::loadData($type, $data);
253
    }
254
255
    /**
256
     * Loads metadata header from a file.
257
     */
258 167
    private function loadHeaders(): void
259
    {
260 167
        $this->shapeType = false;
0 ignored issues
show
Documentation Bug introduced by
The property $shapeType was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
261 167
        $this->recordNumber = $this->loadData('N', 4);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->loadData('N', 4) can also be of type false. However, the property $recordNumber is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
262 167
        if ($this->recordNumber === false) {
263 167
            return;
264
        }
265
266
        // We read the length of the record
267 167
        $this->size = $this->loadData('N', 4);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->loadData('N', 4) can also be of type false. However, the property $size is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
268 167
        if ($this->size === false) {
269
            return;
270
        }
271
272 167
        $this->size = ($this->size * 2) + 8;
273 167
        $this->shapeType = $this->loadData('V', 4);
274 167
    }
275
276
    /**
277
     * Saves metadata header to a file.
278
     */
279 111
    private function saveHeaders(): void
280
    {
281 111
        fwrite($this->shpFile, pack('N', $this->recordNumber));
282 111
        fwrite($this->shpFile, pack('N', $this->getContentLength()));
283 111
        fwrite($this->shpFile, pack('V', $this->shapeType));
284 111
    }
285
286 167
    private function loadPoint(): array
287
    {
288 167
        $data = [];
289
290 167
        $data['x'] = $this->loadData('d', 8);
291 167
        $data['y'] = $this->loadData('d', 8);
292
293 167
        return $data;
294
    }
295
296 23
    private function loadPointM(): array
297
    {
298 23
        $data = $this->loadPoint();
299
300 23
        $data['m'] = $this->loadData('d', 8);
301
302 23
        return $data;
303
    }
304
305 23
    private function loadPointZ(): array
306
    {
307 23
        $data = $this->loadPoint();
308
309 23
        $data['z'] = $this->loadData('d', 8);
310 23
        $data['m'] = $this->loadData('d', 8);
311
312 23
        return $data;
313
    }
314
315 95
    private function savePoint(array $data): void
316
    {
317 95
        fwrite($this->shpFile, Util::packDouble($data['x']));
318 95
        fwrite($this->shpFile, Util::packDouble($data['y']));
319 95
    }
320
321 23
    private function savePointM(array $data): void
322
    {
323 23
        fwrite($this->shpFile, Util::packDouble($data['x']));
324 23
        fwrite($this->shpFile, Util::packDouble($data['y']));
325 23
        fwrite($this->shpFile, Util::packDouble($data['m']));
326 23
    }
327
328 23
    private function savePointZ(array $data): void
329
    {
330 23
        fwrite($this->shpFile, Util::packDouble($data['x']));
331 23
        fwrite($this->shpFile, Util::packDouble($data['y']));
332 23
        fwrite($this->shpFile, Util::packDouble($data['z']));
333 23
        fwrite($this->shpFile, Util::packDouble($data['m']));
334 23
    }
335
336
    private function loadNullRecord(): void
337
    {
338
        $this->shpData = [];
339
    }
340
341 47
    private function loadPointRecord(): void
342
    {
343 47
        $this->shpData = $this->loadPoint();
344 47
    }
345
346 23
    private function loadPointMRecord(): void
347
    {
348 23
        $this->shpData = $this->loadPointM();
349 23
    }
350
351 23
    private function loadPointZRecord(): void
352
    {
353 23
        $this->shpData = $this->loadPointZ();
354 23
    }
355
356 23
    private function savePointRecord(): void
357
    {
358 23
        $this->savePoint($this->shpData);
359 23
    }
360
361 23
    private function savePointMRecord(): void
362
    {
363 23
        $this->savePointM($this->shpData);
364 23
    }
365
366 23
    private function savePointZRecord(): void
367
    {
368 23
        $this->savePointZ($this->shpData);
369 23
    }
370
371 119
    private function loadBBox(): void
372
    {
373 119
        $this->shpData['xmin'] = $this->loadData('d', 8);
374 119
        $this->shpData['ymin'] = $this->loadData('d', 8);
375 119
        $this->shpData['xmax'] = $this->loadData('d', 8);
376 119
        $this->shpData['ymax'] = $this->loadData('d', 8);
377 119
    }
378
379 32
    private function loadMultiPointRecord(): void
380
    {
381 32
        $this->shpData = [];
382 32
        $this->loadBBox();
383
384 32
        $this->shpData['numpoints'] = $this->loadData('V', 4);
385
386 32
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
387 32
            $this->shpData['points'][] = $this->loadPoint();
388
        }
389 32
    }
390
391 24
    private function loadMultiPointMZRecord(string $type): void
392
    {
393
        /* The m dimension is optional, depends on bounding box data */
394 24
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
395 24
            return;
396
        }
397
398 16
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
399 16
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
400
401 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
402 16
            $this->shpData['points'][$i][$type] = $this->loadData('d', 8);
403
        }
404 16
    }
405
406 8
    private function loadMultiPointMRecord(): void
407
    {
408 8
        $this->loadMultiPointRecord();
409
410 8
        $this->loadMultiPointMZRecord('m');
411 8
    }
412
413 16
    private function loadMultiPointZRecord(): void
414
    {
415 16
        $this->loadMultiPointRecord();
416
417 16
        $this->loadMultiPointMZRecord('z');
418 16
        $this->loadMultiPointMZRecord('m');
419 16
    }
420
421 24
    private function saveMultiPointRecord(): void
422
    {
423 24
        fwrite($this->shpFile, pack(
424 24
            'dddd',
425 24
            $this->shpData['xmin'],
426 24
            $this->shpData['ymin'],
427 24
            $this->shpData['xmax'],
428 24
            $this->shpData['ymax']
429
        ));
430
431 24
        fwrite($this->shpFile, pack('V', $this->shpData['numpoints']));
432
433 24
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
434 24
            $this->savePoint($this->shpData['points'][$i]);
435
        }
436 24
    }
437
438 16
    private function saveMultiPointMZRecord(string $type): void
439
    {
440 16
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
441
442 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
443 16
            fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type]));
444
        }
445 16
    }
446
447 8
    private function saveMultiPointMRecord(): void
448
    {
449 8
        $this->saveMultiPointRecord();
450
451 8
        $this->saveMultiPointMZRecord('m');
452 8
    }
453
454 8
    private function saveMultiPointZRecord(): void
455
    {
456 8
        $this->saveMultiPointRecord();
457
458 8
        $this->saveMultiPointMZRecord('z');
459 8
        $this->saveMultiPointMZRecord('m');
460 8
    }
461
462 87
    private function loadPolyLineRecord(): void
463
    {
464 87
        $this->shpData = [];
465 87
        $this->loadBBox();
466
467 87
        $this->shpData['numparts'] = $this->loadData('V', 4);
468 87
        $this->shpData['numpoints'] = $this->loadData('V', 4);
469
470 87
        $numparts = $this->shpData['numparts'];
471 87
        $numpoints = $this->shpData['numpoints'];
472
473 87
        for ($i = 0; $i < $numparts; ++$i) {
474 87
            $this->shpData['parts'][$i] = $this->loadData('V', 4);
475
        }
476
477 87
        $part = 0;
478 87
        for ($i = 0; $i < $numpoints; ++$i) {
479 87
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
480 79
                ++$part;
481
            }
482
483 87
            if (! isset($this->shpData['parts'][$part]['points'])
484 87
                || ! is_array($this->shpData['parts'][$part]['points'])
485
            ) {
486 87
                $this->shpData['parts'][$part] = ['points' => []];
487
            }
488
489 87
            $this->shpData['parts'][$part]['points'][] = $this->loadPoint();
490
        }
491 87
    }
492
493 40
    private function loadPolyLineMZRecord(string $type): void
494
    {
495
        /* The m dimension is optional, depends on bounding box data */
496 40
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
497 40
            return;
498
        }
499
500 24
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
501 24
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
502
503 24
        $numparts = $this->shpData['numparts'];
504 24
        $numpoints = $this->shpData['numpoints'];
505
506 24
        $part = 0;
507 24
        for ($i = 0; $i < $numpoints; ++$i) {
508 24
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
509
                ++$part;
510
            }
511
512 24
            $this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8);
513
        }
514 24
    }
515
516 16
    private function loadPolyLineMRecord(): void
517
    {
518 16
        $this->loadPolyLineRecord();
519
520 16
        $this->loadPolyLineMZRecord('m');
521 16
    }
522
523 24
    private function loadPolyLineZRecord(): void
524
    {
525 24
        $this->loadPolyLineRecord();
526
527 24
        $this->loadPolyLineMZRecord('z');
528 24
        $this->loadPolyLineMZRecord('m');
529 24
    }
530
531 63
    private function savePolyLineRecord(): void
532
    {
533 63
        fwrite($this->shpFile, pack(
534 63
            'dddd',
535 63
            $this->shpData['xmin'],
536 63
            $this->shpData['ymin'],
537 63
            $this->shpData['xmax'],
538 63
            $this->shpData['ymax']
539
        ));
540
541 63
        fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
542
543 63
        $partIndex = 0;
544 63
        for ($i = 0; $i < $this->shpData['numparts']; ++$i) {
545 63
            fwrite($this->shpFile, pack('V', $partIndex));
546 63
            $partIndex += count($this->shpData['parts'][$i]['points']);
547
        }
548
549 63
        foreach ($this->shpData['parts'] as $partData) {
550 63
            foreach ($partData['points'] as $pointData) {
551 63
                $this->savePoint($pointData);
552
            }
553
        }
554 63
    }
555
556 32
    private function savePolyLineMZRecord(string $type): void
557
    {
558 32
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
559
560 32
        foreach ($this->shpData['parts'] as $partData) {
561 32
            foreach ($partData['points'] as $pointData) {
562 32
                fwrite($this->shpFile, Util::packDouble($pointData[$type]));
563
            }
564
        }
565 32
    }
566
567 16
    private function savePolyLineMRecord(): void
568
    {
569 16
        $this->savePolyLineRecord();
570
571 16
        $this->savePolyLineMZRecord('m');
572 16
    }
573
574 16
    private function savePolyLineZRecord(): void
575
    {
576 16
        $this->savePolyLineRecord();
577
578 16
        $this->savePolyLineMZRecord('z');
579 16
        $this->savePolyLineMZRecord('m');
580 16
    }
581
582 24
    private function loadPolygonRecord(): void
583
    {
584 24
        $this->loadPolyLineRecord();
585 24
    }
586
587 8
    private function loadPolygonMRecord(): void
588
    {
589 8
        $this->loadPolyLineMRecord();
590 8
    }
591
592 16
    private function loadPolygonZRecord(): void
593
    {
594 16
        $this->loadPolyLineZRecord();
595 16
    }
596
597 8
    private function savePolygonRecord(): void
598
    {
599 8
        $this->savePolyLineRecord();
600 8
    }
601
602 8
    private function savePolygonMRecord(): void
603
    {
604 8
        $this->savePolyLineMRecord();
605 8
    }
606
607 8
    private function savePolygonZRecord(): void
608
    {
609 8
        $this->savePolyLineZRecord();
610 8
    }
611
612 111
    private function adjustBBox(array $point): void
613
    {
614
        // Adjusts bounding box based on point
615 15
        $directions = [
616 96
            'x',
617
            'y',
618
            'z',
619
            'm',
620
        ];
621 111
        foreach ($directions as $direction) {
622 111
            if (! isset($point[$direction])) {
623 79
                continue;
624
            }
625
626 111
            $min = $direction . 'min';
627 111
            $max = $direction . 'max';
628 111
            if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) {
629 111
                $this->shpData[$min] = $point[$direction];
630
            }
631
632 111
            if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) {
633 87
                continue;
634
            }
635
636 111
            $this->shpData[$max] = $point[$direction];
637
        }
638 111
    }
639
640
    /**
641
     * Sets dimension to 0 if not set.
642
     *
643
     * @param array  $point     Point to check
644
     * @param string $dimension Dimension to check
645
     *
646
     * @return array
647
     */
648 79
    private function fixPoint(array $point, string $dimension): array
649
    {
650 79
        if (! isset($point[$dimension])) {
651 64
            $point[$dimension] = 0.0; // no_value
652
        }
653
654 79
        return $point;
655
    }
656
657
    /**
658
     * Adjust point and bounding box when adding point.
659
     *
660
     * @param array $point Point data
661
     *
662
     * @return array Fixed point data
663
     */
664 111
    private function adjustPoint(array $point): array
665
    {
666 111
        $type = $this->shapeType / 10;
667 111
        if ($type >= 2) {
668 47
            $point = $this->fixPoint($point, 'm');
669 79
        } elseif ($type >= 1) {
670 47
            $point = $this->fixPoint($point, 'z');
671 47
            $point = $this->fixPoint($point, 'm');
672
        }
673
674 111
        return $point;
675
    }
676
677
    /**
678
     * Adds point to a record.
679
     *
680
     * @param array $point     Point data
681
     * @param int   $partIndex Part index
682
     */
683 111
    public function addPoint(array $point, int $partIndex = 0): void
684
    {
685 111
        $point = $this->adjustPoint($point);
686 111
        switch ($this->shapeType) {
687 111
            case 0:
688
                //Don't add anything
689
                return;
690 111
            case 1:
691 103
            case 11:
692 95
            case 21:
693
                //Substitutes the value of the current point
694 39
                $this->shpData = $point;
695 39
                break;
696 87
            case 3:
697 64
            case 5:
698 56
            case 13:
699 48
            case 15:
700 40
            case 23:
701 32
            case 25:
702
                //Adds a new point to the selected part
703 63
                $this->shpData['parts'][$partIndex]['points'][] = $point;
704 63
                $this->shpData['numparts'] = count($this->shpData['parts']);
705 63
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
706 63
                break;
707 24
            case 8:
708 16
            case 18:
709 8
            case 28:
710
                //Adds a new point
711 24
                $this->shpData['points'][] = $point;
712 24
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
713 24
                break;
714
            default:
715
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
716
717
                return;
718
        }
719
720 111
        $this->adjustBBox($point);
721 111
    }
722
723
    /**
724
     * Deletes point from a record.
725
     *
726
     * @param int $pointIndex Point index
727
     * @param int $partIndex  Part index
728
     */
729 96
    public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void
730
    {
731 96
        switch ($this->shapeType) {
732 96
            case 0:
733
                //Don't delete anything
734
                break;
735 96
            case 1:
736 88
            case 11:
737 80
            case 21:
738
                //Sets the value of the point to zero
739 24
                $this->shpData['x'] = 0.0;
740 24
                $this->shpData['y'] = 0.0;
741 24
                if (in_array($this->shapeType, [11, 21])) {
742 16
                    $this->shpData['m'] = 0.0;
743
                }
744
745 24
                if (in_array($this->shapeType, [11])) {
746 8
                    $this->shpData['z'] = 0.0;
747
                }
748
749 24
                break;
750 72
            case 3:
751 64
            case 5:
752 56
            case 13:
753 48
            case 15:
754 40
            case 23:
755 32
            case 25:
756
                //Deletes the point from the selected part, if exists
757 48
                if (isset($this->shpData['parts'][$partIndex])
758 48
                    && isset($this->shpData['parts'][$partIndex]['points'][$pointIndex])
759
                ) {
760 48
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
761 48
                    for ($i = $pointIndex; $i < $count; ++$i) {
762 48
                        $point = $this->shpData['parts'][$partIndex]['points'][$i + 1];
763 48
                        $this->shpData['parts'][$partIndex]['points'][$i] = $point;
764
                    }
765
766 48
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
767 48
                    unset($this->shpData['parts'][$partIndex]['points'][$count]);
768
769 48
                    $this->shpData['numparts'] = count($this->shpData['parts']);
770 48
                    --$this->shpData['numpoints'];
771
                }
772
773 48
                break;
774 24
            case 8:
775 16
            case 18:
776 8
            case 28:
777
                //Deletes the point, if exists
778 24
                if (isset($this->shpData['points'][$pointIndex])) {
779 24
                    $count = count($this->shpData['points']) - 1;
780 24
                    for ($i = $pointIndex; $i < $count; ++$i) {
781 24
                        $this->shpData['points'][$i] = $this->shpData['points'][$i + 1];
782
                    }
783
784 24
                    unset($this->shpData['points'][count($this->shpData['points']) - 1]);
785
786 24
                    --$this->shpData['numpoints'];
787
                }
788
789 24
                break;
790
            default:
791
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
792
                break;
793
        }
794 96
    }
795
796
    /**
797
     * Returns length of content.
798
     */
799 111
    public function getContentLength(): ?int
800
    {
801
        // The content length for a record is the length of the record contents section measured in 16-bit words.
802
        // one coordinate makes 4 16-bit words (64 bit double)
803 111
        switch ($this->shapeType) {
804 111
            case 0:
805
                $result = 0;
806
                break;
807 111
            case 1:
808 23
                $result = 10;
809 23
                break;
810 103
            case 21:
811 23
                $result = 10 + 4;
812 23
                break;
813 95
            case 11:
814 23
                $result = 10 + 8;
815 23
                break;
816 87
            case 3:
817 64
            case 5:
818 31
                $count = count($this->shpData['parts']);
819 31
                $result = 22 + 2 * $count;
820 31
                for ($i = 0; $i < $count; ++$i) {
821 31
                    $result += 8 * count($this->shpData['parts'][$i]['points']);
822
                }
823
824 31
                break;
825 56
            case 23:
826 48
            case 25:
827 16
                $count = count($this->shpData['parts']);
828 16
                $result = 22 + (2 * 4) + 2 * $count;
829 16
                for ($i = 0; $i < $count; ++$i) {
830 16
                    $result += (8 + 4) * count($this->shpData['parts'][$i]['points']);
831
                }
832
833 16
                break;
834 40
            case 13:
835 32
            case 15:
836 16
                $count = count($this->shpData['parts']);
837 16
                $result = 22 + (4 * 4) + 2 * $count;
838 16
                for ($i = 0; $i < $count; ++$i) {
839 16
                    $result += (8 + 8) * count($this->shpData['parts'][$i]['points']);
840
                }
841
842 16
                break;
843 24
            case 8:
844 8
                $result = 20 + 8 * count($this->shpData['points']);
845 8
                break;
846 16
            case 28:
847 8
                $result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']);
848 8
                break;
849 8
            case 18:
850 8
                $result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']);
851 8
                break;
852
            default:
853
                $result = null;
854
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
855
                break;
856
        }
857
858 111
        return $result;
859
    }
860
861 110
    private function loadDBFData(): void
862
    {
863 110
        $this->dbfData = @dbase_get_record_with_names($this->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

863
        $this->dbfData = @/** @scrutinizer ignore-call */ dbase_get_record_with_names($this->dbfFile, $this->recordNumber);
Loading history...
864 110
        unset($this->dbfData['deleted']);
865 110
    }
866
867 75
    private function saveDBFData(): void
868
    {
869 75
        if (count($this->dbfData) === 0) {
870
            return;
871
        }
872
873 75
        unset($this->dbfData['deleted']);
874 75
        if ($this->recordNumber <= dbase_numrecords($this->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

874
        if ($this->recordNumber <= /** @scrutinizer ignore-call */ dbase_numrecords($this->dbfFile)) {
Loading history...
875
            if (! dbase_replace_record($this->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

875
            if (! /** @scrutinizer ignore-call */ dbase_replace_record($this->dbfFile, array_values($this->dbfData), $this->recordNumber)) {
Loading history...
876
                $this->setError('I wasn\'t possible to update the information in the DBF file.');
877
            }
878
        } else {
879 75
            if (! dbase_add_record($this->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

879
            if (! /** @scrutinizer ignore-call */ dbase_add_record($this->dbfFile, array_values($this->dbfData))) {
Loading history...
880
                $this->setError('I wasn\'t possible to add the information to the DBF file.');
881
            }
882
        }
883 75
    }
884
885
    /**
886
     * Sets error message.
887
     */
888
    public function setError(string $error): void
889
    {
890
        $this->lastError = $error;
891
    }
892
893
    /**
894
     * Returns shape name.
895
     */
896 8
    public function getShapeName(): string
897
    {
898 8
        return Util::nameShape($this->shapeType);
899
    }
900
}
901