Completed
Push — master ( 59e77d...c915c1 )
by Michal
03:36
created

ShapeFile::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 5
nc 1
nop 3
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
    static $shape_names = array(
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $shape_names.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
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
     * @param ShapeRecord $record
147
     */
148
    public function addRecord($record) {
149
        if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) {
150
            $record->updateDBFInfo($this->DBFHeader);
151
        }
152
153
        $this->fileLength += ($record->getContentLength() + 4);
154
        $this->records[] = $record;
155
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
156
157
        if ($this->boundingBox['xmin'] == 0.0 || ($this->boundingBox['xmin'] > $record->SHPData['xmin'])) {
158
            $this->boundingBox['xmin'] = $record->SHPData['xmin'];
159
        }
160
        if ($this->boundingBox['xmax'] == 0.0 || ($this->boundingBox['xmax'] < $record->SHPData['xmax'])) {
161
            $this->boundingBox['xmax'] = $record->SHPData['xmax'];
162
        }
163
164
        if ($this->boundingBox['ymin'] == 0.0 || ($this->boundingBox['ymin'] > $record->SHPData['ymin'])) {
165
            $this->boundingBox['ymin'] = $record->SHPData['ymin'];
166
        }
167
        if ($this->boundingBox['ymax'] == 0.0 || ($this->boundingBox['ymax'] < $record->SHPData['ymax'])) {
168
            $this->boundingBox['ymax'] = $record->SHPData['ymax'];
169
        }
170
171
        if (in_array($this->shapeType, array(11, 13, 15, 18, 21, 23, 25, 28))) {
172 View Code Duplication
            if (!isset($this->boundingBox['mmin']) || $this->boundingBox['mmin'] == 0.0 || ($this->boundingBox['mmin'] > $record->SHPData['mmin'])) {
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...
173
                $this->boundingBox['mmin'] = $record->SHPData['mmin'];
174
            }
175 View Code Duplication
            if (!isset($this->boundingBox['mmax']) || $this->boundingBox['mmax'] == 0.0 || ($this->boundingBox['mmax'] < $record->SHPData['mmax'])) {
176
                $this->boundingBox['mmax'] = $record->SHPData['mmax'];
177
            }
178
        }
179
180
        if (in_array($this->shapeType, array(11, 13, 15, 18))) {
181 View Code Duplication
            if (!isset($this->boundingBox['zmin']) || $this->boundingBox['zmin'] == 0.0 || ($this->boundingBox['zmin'] > $record->SHPData['zmin'])) {
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...
182
                $this->boundingBox['zmin'] = $record->SHPData['zmin'];
183
            }
184 View Code Duplication
            if (!isset($this->boundingBox['zmax']) || $this->boundingBox['zmax'] == 0.0 || ($this->boundingBox['zmax'] < $record->SHPData['zmax'])) {
185
                $this->boundingBox['zmax'] = $record->SHPData['zmax'];
186
            }
187
        }
188
189
        return (count($this->records) - 1);
190
    }
191
192
    public function deleteRecord($index) {
193
        if (isset($this->records[$index])) {
194
            $this->fileLength -= ($this->records[$index]->getContentLength() + 4);
195
            $count = count($this->records) - 1;
196
            for ($i = $index; $i < $count; $i++) {
197
                $this->records[$i] = $this->records[$i + 1];
198
            }
199
            unset($this->records[count($this->records) - 1]);
200
            $this->_deleteRecordFromDBF($index);
201
        }
202
    }
203
204
    public function getDBFHeader() {
205
        return $this->DBFHeader;
206
    }
207
208
    public function setDBFHeader($header) {
209
        $this->DBFHeader = $header;
210
211
        $count = count($this->records);
212
        for ($i = 0; $i < $count; $i++) {
213
            $this->records[$i]->updateDBFInfo($header);
214
        }
215
    }
216
217
    public function getIndexFromDBFData($field, $value) {
218
        $result = -1;
219
        $count = count($this->records) - 1;
220
        for ($i = 0; $i < $count; $i++) {
221
            if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) {
222
                $result = $i;
223
            }
224
        }
225
226
        return $result;
227
    }
228
229
    private function _loadDBFHeader() {
230
        $DBFFile = fopen($this->_getFilename('.dbf'), 'r');
231
232
        $result = array();
233
        $i = 1;
234
        $inHeader = true;
235
236
        while ($inHeader) {
237
            if (!feof($DBFFile)) {
238
                $buff32 = fread($DBFFile, 32);
239
                if ($i > 1) {
240
                    if (substr($buff32, 0, 1) == chr(13)) {
241
                        $inHeader = false;
242
                    } else {
243
                        $pos = strpos(substr($buff32, 0, 10), chr(0));
244
                        $pos = ($pos == 0 ? 10 : $pos);
245
246
                        $fieldName = substr($buff32, 0, $pos);
247
                        $fieldType = substr($buff32, 11, 1);
248
                        $fieldLen = ord(substr($buff32, 16, 1));
249
                        $fieldDec = ord(substr($buff32, 17, 1));
250
251
                        array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec));
252
                    }
253
                }
254
                $i++;
255
            } else {
256
                $inHeader = false;
257
            }
258
        }
259
260
        fclose($DBFFile);
261
        return($result);
262
    }
263
264
    private function _deleteRecordFromDBF($index) {
265
        if (@dbase_delete_record($this->DBFFile, $index)) {
266
            @dbase_pack($this->DBFFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
267
        }
268
    }
269
270
    private function _loadHeaders() {
271
        if (Util::loadData('N', $this->readSHP(4)) != 9994) {
272
            $this->setError('Not a SHP file (file code mismatch)');
273
            return false;
274
        }
275
276
        /* Skip 20 unused bytes */
277
        $this->readSHP(20);
278
279
        $this->fileLength = Util::loadData('N', $this->readSHP(4));
280
281
        /* We currently ignore version */
282
        $this->readSHP(4);
283
284
        $this->shapeType = Util::loadData('V', $this->readSHP(4));
285
286
        $this->boundingBox = array();
287
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
288
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
289
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
290
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
291
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
292
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
293
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
294
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
295
296
        if (ShapeFile::supports_dbase()) {
297
            $this->DBFHeader = $this->_loadDBFHeader();
298
        }
299
        return true;
300
    }
301
302
    private function _saveHeaders() {
303
        fwrite($this->SHPFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
304
        fwrite($this->SHPFile, pack('N', $this->fileLength));
305
        fwrite($this->SHPFile, pack('V', 1000));
306
        fwrite($this->SHPFile, pack('V', $this->shapeType));
307
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmin']));
308
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymin']));
309
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmax']));
310
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymax']));
311
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
312
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
313
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
314
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
315
316
        fwrite($this->SHXFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
317
        fwrite($this->SHXFile, pack('N', 50 + 4 * count($this->records)));
318
        fwrite($this->SHXFile, pack('V', 1000));
319
        fwrite($this->SHXFile, pack('V', $this->shapeType));
320
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmin']));
321
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymin']));
322
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmax']));
323
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymax']));
324
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
325
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
326
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
327
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
328
    }
329
330
    private function _loadRecords() {
331
        /* Need to start at offset 100 */
332
        while (!$this->eofSHP()) {
333
            $record = new ShapeRecord(-1);
334
            $record->loadFromFile($this, $this->SHPFile, $this->DBFFile);
335
            if ($record->lastError != '') {
336
                $this->setError($record->lastError);
337
                return false;
338
            }
339
            if (($record->shapeType === false || $record->shapeType === '') && $this->eofSHP()) {
340
                break;
341
            }
342
343
            $this->records[] = $record;
344
        }
345
        return true;
346
    }
347
348
    private function _saveRecords() {
349
        if (!ShapeFile::supports_dbase()) {
350
            return;
351
        }
352
        $dbf_name = $this->_getFilename('.dbf');
353
        if (file_exists($dbf_name)) {
354
            @unlink($dbf_name);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
355
        }
356
        if (!($this->DBFFile = @dbase_create($dbf_name, $this->DBFHeader))) {
357
            return $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
358
        }
359
360
        $offset = 50;
361
        if (is_array($this->records) && (count($this->records) > 0)) {
362
            foreach ($this->records as $index => $record) {
363
                //Save the record to the .shp file
364
                $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1);
365
366
                //Save the record to the .shx file
367
                fwrite($this->SHXFile, pack('N', $offset));
368
                fwrite($this->SHXFile, pack('N', $record->getContentLength()));
369
                $offset += (4 + $record->getContentLength());
370
            }
371
        }
372
        @dbase_pack($this->DBFFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
373
    }
374
375
    private function _openSHPFile($toWrite = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
376
        $shp_name = $this->_getFilename('.shp');
377
        $this->SHPFile = @fopen($shp_name, ($toWrite ? 'wb+' : 'rb'));
378
        if (!$this->SHPFile) {
379
            return $this->setError(sprintf('It wasn\'t possible to open the Shape file "%s"', $shp_name));
380
        }
381
382
        return true;
383
    }
384
385
    private function _closeSHPFile() {
386
        if ($this->SHPFile) {
387
            fclose($this->SHPFile);
388
            $this->SHPFile = null;
389
        }
390
    }
391
392
    private function _openSHXFile($toWrite = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
393
        $shx_name = $this->_getFilename('.shx');
394
        $this->SHXFile = @fopen($shx_name, ($toWrite ? 'wb+' : 'rb'));
395
        if (!$this->SHXFile) {
396
            return $this->setError(sprintf('It wasn\'t possible to open the Index file "%s"', $shx_name));
397
        }
398
399
        return true;
400
    }
401
402
    private function _closeSHXFile() {
403
        if ($this->SHXFile) {
404
            fclose($this->SHXFile);
405
            $this->SHXFile = null;
406
        }
407
    }
408
409
    /**
410
     * Loads DBF file if supported
411
     *
412
     * @return bool
413
     */
414
    private function _openDBFFile($toWrite = false) {
415
        if (!ShapeFile::supports_dbase()) {
416
            return true;
417
        }
418
        $dbf_name = $this->_getFilename('.dbf');
419
        $checkFunction = $toWrite ? 'is_writable' : 'is_readable';
420
        if (($toWrite) && (!file_exists($dbf_name))) {
421
            if (!@dbase_create($dbf_name, $this->DBFHeader)) {
422
                return $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
423
            }
424
        }
425
        if ($checkFunction($dbf_name)) {
426
            $this->DBFFile = @dbase_open($dbf_name, ($toWrite ? 2 : 0));
427
            if (!$this->DBFFile) {
428
                return $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbf_name));
429
            }
430
        } else {
431
            return $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbf_name));
432
        }
433
        return true;
434
    }
435
436
    private function _closeDBFFile() {
437
        if ($this->DBFFile) {
438
            dbase_close($this->DBFFile);
439
            $this->DBFFile = null;
440
        }
441
    }
442
443
    /**
444
     * Sets error message
445
     *
446
     * @param string $error
447
     *
448
     * @return bool
449
     */
450
    public function setError($error) {
451
        $this->lastError = $error;
452
        return false;
453
    }
454
455
    /**
456
     * Reads given number of bytes from SHP file
457
     *
458
     * @return string|false
459
     */
460
    public function readSHP($bytes)
461
    {
462
        return fread($this->SHPFile, $bytes);
463
    }
464
465
    /**
466
     * Checks whether file is at EOF
467
     *
468
     * @return bool
469
     */
470
    public function eofSHP()
471
    {
472
        return feof($this->SHPFile);
473
    }
474
475
    /**
476
     * Returns shape name
477
     *
478
     * @return string
479
     */
480
    public function getShapeName()
481
    {
482
        return ShapeFile::nameShape($this->shapeType);
483
    }
484
485
    public static function nameShape($type)
486
    {
487
        if (isset(self::$shape_names[$type])) {
488
            return self::$shape_names[$type];
489
        }
490
        return sprintf('Shape %d', $type);
491
    }
492
}
493
494