Completed
Push — master ( 44fa0b...b8e178 )
by Michal
03:22
created

ShapeFile::updateBBox()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 6
Ratio 50 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 7
c 1
b 0
f 0
nc 4
nop 2
dl 6
loc 12
rs 8.2222
1
<?php
2
/**
3
 * phpMyAdmin ShapeFile library
4
 * <https://github.com/phpmyadmin/shapefile/>
5
 *
6
 * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net>
7
 * Copyright 2016 Michal Čihař <[email protected]>
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, you can download one from
20
 * http://www.gnu.org/copyleft/gpl.html.
21
 */
22
namespace ShapeFile;
23
24
/**
25
 * ShapeFile class
26
 *
27
 * @package ShapeFile
28
 */
29
class ShapeFile {
30
    public $FileName;
31
32
    private $SHPFile = null;
33
    private $SHXFile = null;
34
    private $DBFFile = null;
35
36
    private $DBFHeader;
37
38
    public $lastError = '';
39
40
    public $boundingBox = array('xmin' => 0.0, 'ymin' => 0.0, 'xmax' => 0.0, 'ymax' => 0.0);
41
    private $fileLength = 0;
42
    public $shapeType = 0;
43
44
    public $records;
45
46
    private static $shape_names = array(
47
        0 => 'Null Shape',
48
        1 => 'Point',
49
        3 => 'PolyLine',
50
        5 => 'Polygon',
51
        8 => 'MultiPoint',
52
        11 => 'PointZ',
53
        13 => 'PolyLineZ',
54
        15 => 'PolygonZ',
55
        18 => 'MultiPointZ',
56
        21 => 'PointM',
57
        23 => 'PolyLineM',
58
        25 => 'PolygonM',
59
        28 => 'MultiPointM',
60
        31 => 'MultiPatch',
61
    );
62
63
    /**
64
     * Checks whether dbase manipuations are supported.
65
     *
66
     * @return bool
67
     */
68
    public static function supports_dbase()
69
    {
70
        return extension_loaded('dbase');
71
    }
72
73
    /**
74
     * @param integer $shapeType
75
     */
76
    public function __construct($shapeType, $boundingBox = array('xmin' => 0.0, 'ymin' => 0.0, 'xmax' => 0.0, 'ymax' => 0.0), $FileName = null) {
77
        $this->shapeType = $shapeType;
78
        $this->boundingBox = $boundingBox;
79
        $this->FileName = $FileName;
80
        $this->fileLength = 50; // The value for file length is the total length of the file in 16-bit words (including the fifty 16-bit words that make up the header).
81
    }
82
83
    /**
84
     * @param string $FileName
85
     */
86
    public function loadFromFile($FileName) {
87
        if (!empty($FileName)) {
88
            $this->FileName = $FileName;
89
            $result = $this->_openSHPFile();
90
        } else {
91
            /* We operate on buffer emulated by readSHP / eofSHP */
92
            $result = true;
93
        }
94
95
        if ($result && ($this->_openDBFFile())) {
96
            if (!$this->_loadHeaders()) {
97
                $this->_closeSHPFile();
98
                $this->_closeDBFFile();
99
                return false;
100
            }
101
            if (!$this->_loadRecords()) {
102
                $this->_closeSHPFile();
103
                $this->_closeDBFFile();
104
                return false;
105
            }
106
            $this->_closeSHPFile();
107
            $this->_closeDBFFile();
108
            return true;
109
        } else {
110
            return false;
111
        }
112
    }
113
114
    /**
115
     * @param string|null $FileName Name of file to open
116
     */
117
    public function saveToFile($FileName = null) {
118
        if (!is_null($FileName)) {
119
            $this->FileName = $FileName;
120
        }
121
122
        if (($this->_openSHPFile(true)) && ($this->_openSHXFile(true)) && ($this->_openDBFFile(true))) {
123
            $this->_saveHeaders();
124
            $this->_saveRecords();
125
            $this->_closeSHPFile();
126
            $this->_closeSHXFile();
127
            $this->_closeDBFFile();
128
        } else {
129
            return false;
130
        }
131
    }
132
133
    /**
134
     * Generates filename with given extension
135
     *
136
     * @param string $extension Extension to use (including dot)
137
     *
138
     * @return string
139
     */
140
    private function _getFilename($extension)
141
    {
142
        return str_replace('.*', $extension, $this->FileName);
143
    }
144
145
    /**
146
     * Updates bounding box based on SHPData
147
     *
148
     * @param string $type Type of box
149
     * @param array  $data ShapeRecord SHPData
150
     *
151
     * @return void
152
     */
153
    private function updateBBox($type, $data)
154
    {
155
        $min = $type.'min';
156
        $max = $type.'max';
157
158 View Code Duplication
        if (!isset($this->boundingBox[$min]) || $this->boundingBox[$min] == 0.0 || ($this->boundingBox[$min] > $data[$min])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
            $this->boundingBox[$min] = $data[$min];
160
        }
161 View Code Duplication
        if (!isset($this->boundingBox[$max]) || $this->boundingBox[$max] == 0.0 || ($this->boundingBox[$max] < $data[$max])) {
162
            $this->boundingBox[$max] = $data[$max];
163
        }
164
    }
165
166
    /**
167
     * @param ShapeRecord $record
168
     */
169
    public function addRecord($record) {
170
        if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) {
171
            $record->updateDBFInfo($this->DBFHeader);
172
        }
173
174
        $this->fileLength += ($record->getContentLength() + 4);
175
        $this->records[] = $record;
176
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
177
178
        $this->updateBBox('x', $record->SHPData);
179
        $this->updateBBox('y', $record->SHPData);
180
181
        if (in_array($this->shapeType, array(11, 13, 15, 18, 21, 23, 25, 28))) {
182
            $this->updateBBox('m', $record->SHPData);
183
        }
184
185
        if (in_array($this->shapeType, array(11, 13, 15, 18))) {
186
            $this->updateBBox('z', $record->SHPData);
187
        }
188
189
        return (count($this->records) - 1);
190
    }
191
192
    /**
193
     * @param integer $index
194
     */
195
    public function deleteRecord($index) {
196
        if (isset($this->records[$index])) {
197
            $this->fileLength -= ($this->records[$index]->getContentLength() + 4);
198
            $count = count($this->records) - 1;
199
            for ($i = $index; $i < $count; $i++) {
200
                $this->records[$i] = $this->records[$i + 1];
201
            }
202
            unset($this->records[count($this->records) - 1]);
203
            $this->_deleteRecordFromDBF($index);
204
        }
205
    }
206
207
    public function getDBFHeader() {
208
        return $this->DBFHeader;
209
    }
210
211
    public function setDBFHeader($header) {
212
        $this->DBFHeader = $header;
213
214
        $count = count($this->records);
215
        for ($i = 0; $i < $count; $i++) {
216
            $this->records[$i]->updateDBFInfo($header);
217
        }
218
    }
219
220
    public function getIndexFromDBFData($field, $value) {
221
        $result = -1;
222
        $count = count($this->records) - 1;
223
        for ($i = 0; $i < $count; $i++) {
224
            if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) {
225
                $result = $i;
226
            }
227
        }
228
229
        return $result;
230
    }
231
232
    private function _loadDBFHeader() {
233
        $DBFFile = fopen($this->_getFilename('.dbf'), 'r');
234
235
        $result = array();
236
        $i = 1;
237
        $inHeader = true;
238
239
        while ($inHeader) {
240
            if (!feof($DBFFile)) {
241
                $buff32 = fread($DBFFile, 32);
242
                if ($i > 1) {
243
                    if (substr($buff32, 0, 1) == chr(13)) {
244
                        $inHeader = false;
245
                    } else {
246
                        $pos = strpos(substr($buff32, 0, 10), chr(0));
247
                        $pos = ($pos == 0 ? 10 : $pos);
248
249
                        $fieldName = substr($buff32, 0, $pos);
250
                        $fieldType = substr($buff32, 11, 1);
251
                        $fieldLen = ord(substr($buff32, 16, 1));
252
                        $fieldDec = ord(substr($buff32, 17, 1));
253
254
                        array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec));
255
                    }
256
                }
257
                $i++;
258
            } else {
259
                $inHeader = false;
260
            }
261
        }
262
263
        fclose($DBFFile);
264
        return($result);
265
    }
266
267
    private function _deleteRecordFromDBF($index) {
268
        if (@dbase_delete_record($this->DBFFile, $index)) {
269
            dbase_pack($this->DBFFile);
270
        }
271
    }
272
273
    private function _loadHeaders() {
274
        if (Util::loadData('N', $this->readSHP(4)) != 9994) {
275
            $this->setError('Not a SHP file (file code mismatch)');
276
            return false;
277
        }
278
279
        /* Skip 20 unused bytes */
280
        $this->readSHP(20);
281
282
        $this->fileLength = Util::loadData('N', $this->readSHP(4));
283
284
        /* We currently ignore version */
285
        $this->readSHP(4);
286
287
        $this->shapeType = Util::loadData('V', $this->readSHP(4));
288
289
        $this->boundingBox = array();
290
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
291
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
292
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
293
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
294
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
295
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
296
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
297
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
298
299
        if (ShapeFile::supports_dbase()) {
300
            $this->DBFHeader = $this->_loadDBFHeader();
301
        }
302
        return true;
303
    }
304
305
    private function _saveHeaders() {
306
        fwrite($this->SHPFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
307
        fwrite($this->SHPFile, pack('N', $this->fileLength));
308
        fwrite($this->SHPFile, pack('V', 1000));
309
        fwrite($this->SHPFile, pack('V', $this->shapeType));
310
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmin']));
311
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymin']));
312
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmax']));
313
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymax']));
314
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
315
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
316
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
317
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
318
319
        fwrite($this->SHXFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
320
        fwrite($this->SHXFile, pack('N', 50 + 4 * count($this->records)));
321
        fwrite($this->SHXFile, pack('V', 1000));
322
        fwrite($this->SHXFile, pack('V', $this->shapeType));
323
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmin']));
324
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymin']));
325
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmax']));
326
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymax']));
327
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
328
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
329
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
330
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
331
    }
332
333
    private function _loadRecords() {
334
        /* Need to start at offset 100 */
335
        while (!$this->eofSHP()) {
336
            $record = new ShapeRecord(-1);
337
            $record->loadFromFile($this, $this->SHPFile, $this->DBFFile);
338
            if ($record->lastError != '') {
339
                $this->setError($record->lastError);
340
                return false;
341
            }
342
            if (($record->shapeType === false || $record->shapeType === '') && $this->eofSHP()) {
343
                break;
344
            }
345
346
            $this->records[] = $record;
347
        }
348
        return true;
349
    }
350
351
    private function _saveRecords() {
352
        if (!ShapeFile::supports_dbase()) {
353
            return;
354
        }
355
        $dbf_name = $this->_getFilename('.dbf');
356
        if (file_exists($dbf_name)) {
357
            unlink($dbf_name);
358
        }
359 View Code Duplication
        if (!($this->DBFFile = @dbase_create($dbf_name, $this->DBFHeader))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360
            $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
361
            return false;
362
        }
363
364
        $offset = 50;
365
        if (is_array($this->records) && (count($this->records) > 0)) {
366
            foreach ($this->records as $index => $record) {
367
                //Save the record to the .shp file
368
                $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1);
369
370
                //Save the record to the .shx file
371
                fwrite($this->SHXFile, pack('N', $offset));
372
                fwrite($this->SHXFile, pack('N', $record->getContentLength()));
373
                $offset += (4 + $record->getContentLength());
374
            }
375
        }
376
        dbase_pack($this->DBFFile);
377
    }
378
379
    private function _openFile($toWrite, $extension, $name) {
380
        $shp_name = $this->_getFilename($extension);
381
        $result = @fopen($shp_name, ($toWrite ? 'wb+' : 'rb'));
382
        if (!$result) {
383
            $this->setError(sprintf('It wasn\'t possible to open the %s file "%s"', $name, $shp_name));
384
            return false;
385
        }
386
387
        return $result;
388
    }
389
390
    private function _openSHPFile($toWrite = false) {
391
        $this->SHPFile = $this->_openFile($toWrite, '.shp', 'Shape');
392
        if (!$this->SHPFile) {
393
            return false;
394
        }
395
        return true;
396
    }
397
398
    private function _closeSHPFile() {
399
        if ($this->SHPFile) {
400
            fclose($this->SHPFile);
401
            $this->SHPFile = null;
402
        }
403
    }
404
405
    private function _openSHXFile($toWrite = false) {
406
        $this->SHXFile = $this->_openFile($toWrite, '.shx', 'Index');
407
        if (!$this->SHXFile) {
408
            return false;
409
        }
410
        return true;
411
    }
412
413
    private function _closeSHXFile() {
414
        if ($this->SHXFile) {
415
            fclose($this->SHXFile);
416
            $this->SHXFile = null;
417
        }
418
    }
419
420
    /**
421
     * Loads DBF file if supported
422
     *
423
     * @return bool
424
     */
425
    private function _openDBFFile($toWrite = false) {
426
        if (!ShapeFile::supports_dbase()) {
427
            return true;
428
        }
429
        $dbf_name = $this->_getFilename('.dbf');
430
        $checkFunction = $toWrite ? 'is_writable' : 'is_readable';
431
        if (($toWrite) && (!file_exists($dbf_name))) {
432 View Code Duplication
            if (!@dbase_create($dbf_name, $this->DBFHeader)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
433
                $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
434
                return false;
435
            }
436
        }
437
        if ($checkFunction($dbf_name)) {
438
            $this->DBFFile = @dbase_open($dbf_name, ($toWrite ? 2 : 0));
439
            if (!$this->DBFFile) {
440
                $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbf_name));
441
                return false;
442
            }
443
        } else {
444
            $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbf_name));
445
            return false;
446
        }
447
        return true;
448
    }
449
450
    private function _closeDBFFile() {
451
        if ($this->DBFFile) {
452
            dbase_close($this->DBFFile);
453
            $this->DBFFile = null;
454
        }
455
    }
456
457
    /**
458
     * Sets error message
459
     *
460
     * @param string $error
461
     *
462
     * @return void
463
     */
464
    public function setError($error) {
465
        $this->lastError = $error;
466
    }
467
468
    /**
469
     * Reads given number of bytes from SHP file
470
     *
471
     * @param integer $bytes
472
     * @return string
473
     */
474
    public function readSHP($bytes)
475
    {
476
        return fread($this->SHPFile, $bytes);
477
    }
478
479
    /**
480
     * Checks whether file is at EOF
481
     *
482
     * @return bool
483
     */
484
    public function eofSHP()
485
    {
486
        return feof($this->SHPFile);
487
    }
488
489
    /**
490
     * Returns shape name
491
     *
492
     * @return string
493
     */
494
    public function getShapeName()
495
    {
496
        return ShapeFile::nameShape($this->shapeType);
497
    }
498
499
    /**
500
     * @param integer $type
501
     */
502
    public static function nameShape($type)
503
    {
504
        if (isset(self::$shape_names[$type])) {
505
            return self::$shape_names[$type];
506
        }
507
        return sprintf('Shape %d', $type);
508
    }
509
}
510
511