Completed
Push — master ( c9cf55...ebb0ff )
by Michal
03:18
created

ShapeFile::readSHP()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
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
    private $FileName;
31
32
    private $SHPFile = null;
33
    private $SHXFile = null;
34
    private $DBFFile = null;
35
36
    private $DBFHeader;
37
38
    public $lastError = '';
39
40
    private $boundingBox = array('xmin' => 0.0, 'ymin' => 0.0, 'xmax' => 0.0, 'ymax' => 0.0);
41
    private $fileLength = 0;
42
    private $shapeType = 0;
43
44
    public $records;
45
46
    /**
47
     * Checks whether dbase manipuations are supported.
48
     *
49
     * @return bool
50
     */
51
    public static function supports_dbase()
52
    {
53
        return extension_loaded('dbase');
54
    }
55
56
    /**
57
     * @param integer $shapeType
58
     */
59
    public function __construct($shapeType, $boundingBox = array('xmin' => 0.0, 'ymin' => 0.0, 'xmax' => 0.0, 'ymax' => 0.0), $FileName = null) {
60
        $this->shapeType = $shapeType;
61
        $this->boundingBox = $boundingBox;
62
        $this->FileName = $FileName;
63
        $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).
64
    }
65
66
    /**
67
     * @param string $FileName
68
     */
69
    public function loadFromFile($FileName) {
70
        $this->FileName = $FileName;
71
72
        if (($this->_openSHPFile()) && ($this->_openDBFFile())) {
73
            $this->_loadHeaders();
74
            $this->_loadRecords();
75
            $this->_closeSHPFile();
76
            $this->_closeDBFFile();
77
        } else {
78
            return false;
79
        }
80
    }
81
82
    /**
83
     * @param string|null $FileName Name of file to open
84
     */
85
    public function saveToFile($FileName = null) {
86
        if (! is_null($FileName)) {
87
            $this->FileName = $FileName;
88
        }
89
90
        if (($this->_openSHPFile(true)) && ($this->_openSHXFile(true)) && ($this->_openDBFFile(true))) {
91
            $this->_saveHeaders();
92
            $this->_saveRecords();
93
            $this->_closeSHPFile();
94
            $this->_closeSHXFile();
95
            $this->_closeDBFFile();
96
        } else {
97
            return false;
98
        }
99
    }
100
101
    /**
102
     * Generates filename with given extension
103
     *
104
     * @param string $extension Extension to use (including dot)
105
     *
106
     * @return string
107
     */
108
    private function _getFilename($extension)
109
    {
110
        return str_replace('.*', $extension, $this->FileName);
111
    }
112
113
    /**
114
     * @param ShapeRecord $record
115
     */
116
    public function addRecord($record) {
117
        if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) {
118
            $record->updateDBFInfo($this->DBFHeader);
119
        }
120
121
        $this->fileLength += ($record->getContentLength() + 4);
122
        $this->records[] = $record;
123
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
124
125
        if ($this->boundingBox['xmin'] == 0.0 || ($this->boundingBox['xmin'] > $record->SHPData['xmin'])) {
126
            $this->boundingBox['xmin'] = $record->SHPData['xmin'];
127
        }
128
        if ($this->boundingBox['xmax'] == 0.0 || ($this->boundingBox['xmax'] < $record->SHPData['xmax'])) {
129
            $this->boundingBox['xmax'] = $record->SHPData['xmax'];
130
        }
131
132
        if ($this->boundingBox['ymin'] == 0.0 || ($this->boundingBox['ymin'] > $record->SHPData['ymin'])) {
133
            $this->boundingBox['ymin'] = $record->SHPData['ymin'];
134
        }
135
        if ($this->boundingBox['ymax'] == 0.0 || ($this->boundingBox['ymax'] < $record->SHPData['ymax'])) {
136
            $this->boundingBox['ymax'] = $record->SHPData['ymax'];
137
        }
138
139
        if (in_array($this->shapeType, array(11, 13, 15, 18, 21, 23, 25, 28))) {
140 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...
141
                $this->boundingBox['mmin'] = $record->SHPData['mmin'];
142
            }
143 View Code Duplication
            if (!isset($this->boundingBox['mmax']) || $this->boundingBox['mmax'] == 0.0 || ($this->boundingBox['mmax'] < $record->SHPData['mmax'])) {
144
                $this->boundingBox['mmax'] = $record->SHPData['mmax'];
145
            }
146
        }
147
148
        if (in_array($this->shapeType, array(11, 13, 15, 18))) {
149 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...
150
                $this->boundingBox['zmin'] = $record->SHPData['zmin'];
151
            }
152 View Code Duplication
            if (!isset($this->boundingBox['zmax']) || $this->boundingBox['zmax'] == 0.0 || ($this->boundingBox['zmax'] < $record->SHPData['zmax'])) {
153
                $this->boundingBox['zmax'] = $record->SHPData['zmax'];
154
            }
155
        }
156
157
        return (count($this->records) - 1);
158
    }
159
160
    public function deleteRecord($index) {
161
        if (isset($this->records[$index])) {
162
            $this->fileLength -= ($this->records[$index]->getContentLength() + 4);
163
            $count = count($this->records) - 1;
164
            for ($i = $index; $i < $count; $i++) {
165
                $this->records[$i] = $this->records[$i + 1];
166
            }
167
            unset($this->records[count($this->records) - 1]);
168
            $this->_deleteRecordFromDBF($index);
169
        }
170
    }
171
172
    public function getDBFHeader() {
173
        return $this->DBFHeader;
174
    }
175
176
    public function setDBFHeader($header) {
177
        $this->DBFHeader = $header;
178
179
        $count = count($this->records);
180
        for ($i = 0; $i < $count; $i++) {
181
            $this->records[$i]->updateDBFInfo($header);
182
        }
183
    }
184
185
    public function getIndexFromDBFData($field, $value) {
186
        $result = -1;
187
        $count = count($this->records) - 1;
188
        for ($i = 0; $i < $count; $i++) {
189
            if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) {
190
                $result = $i;
191
            }
192
        }
193
194
        return $result;
195
    }
196
197
    private function _loadDBFHeader() {
198
        $DBFFile = fopen($this->_getFilename('.dbf'), 'r');
199
200
        $result = array();
201
        $i = 1;
202
        $inHeader = true;
203
204
        while ($inHeader) {
205
            if (!feof($DBFFile)) {
206
                $buff32 = fread($DBFFile, 32);
207
                if ($i > 1) {
208
                    if (substr($buff32, 0, 1) == chr(13)) {
209
                        $inHeader = false;
210
                    } else {
211
                        $pos = strpos(substr($buff32, 0, 10), chr(0));
212
                        $pos = ($pos == 0 ? 10 : $pos);
213
214
                        $fieldName = substr($buff32, 0, $pos);
215
                        $fieldType = substr($buff32, 11, 1);
216
                        $fieldLen = ord(substr($buff32, 16, 1));
217
                        $fieldDec = ord(substr($buff32, 17, 1));
218
219
                        array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec));
220
                    }
221
                }
222
                $i++;
223
            } else {
224
                $inHeader = false;
225
            }
226
        }
227
228
        fclose($DBFFile);
229
        return($result);
230
    }
231
232
    private function _deleteRecordFromDBF($index) {
233
        if (@dbase_delete_record($this->DBFFile, $index)) {
234
            @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...
235
        }
236
    }
237
238
    private function _loadHeaders() {
239
        fseek($this->SHPFile, 24, SEEK_SET);
240
        $this->fileLength = Util::loadData('N', $this->readSHP(4));
241
242
        fseek($this->SHPFile, 32, SEEK_SET);
243
        $this->shapeType = Util::loadData('V', $this->readSHP(4));
244
245
        $this->boundingBox = array();
246
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
247
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
248
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
249
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
250
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
251
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
252
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
253
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
254
255
        if (ShapeFile::supports_dbase()) {
256
            $this->DBFHeader = $this->_loadDBFHeader();
257
        }
258
    }
259
260
    private function _saveHeaders() {
261
        fwrite($this->SHPFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
262
        fwrite($this->SHPFile, pack('N', $this->fileLength));
263
        fwrite($this->SHPFile, pack('V', 1000));
264
        fwrite($this->SHPFile, pack('V', $this->shapeType));
265
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmin']));
266
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymin']));
267
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmax']));
268
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymax']));
269
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
270
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
271
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
272
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
273
274
        fwrite($this->SHXFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
275
        fwrite($this->SHXFile, pack('N', 50 + 4 * count($this->records)));
276
        fwrite($this->SHXFile, pack('V', 1000));
277
        fwrite($this->SHXFile, pack('V', $this->shapeType));
278
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmin']));
279
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymin']));
280
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmax']));
281
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymax']));
282
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
283
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
284
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
285
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
286
    }
287
288
    private function _loadRecords() {
289
        fseek($this->SHPFile, 100);
290
        while (!feof($this->SHPFile)) {
291
            $bByte = ftell($this->SHPFile);
292
            $record = new ShapeRecord(-1);
293
            $record->loadFromFile($this, $this->SHPFile, $this->DBFFile);
294
            $eByte = ftell($this->SHPFile);
295
            if (($eByte <= $bByte) || ($record->lastError != '')) {
296
                return false;
297
            }
298
299
            $this->records[] = $record;
300
        }
301
    }
302
303
    private function _saveRecords() {
304
        if (! ShapeFile::supports_dbase()) {
305
            return;
306
        }
307
        $dbf_name = $this->_getFilename('.dbf');
308
        if (file_exists($dbf_name)) {
309
            @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...
310
        }
311
        if (!($this->DBFFile = @dbase_create($dbf_name, $this->DBFHeader))) {
312
            return $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
313
        }
314
315
        $offset = 50;
316
        if (is_array($this->records) && (count($this->records) > 0)) {
317
            foreach ($this->records as $index => $record) {
318
                //Save the record to the .shp file
319
                $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1);
320
321
                //Save the record to the .shx file
322
                fwrite($this->SHXFile, pack('N', $offset));
323
                fwrite($this->SHXFile, pack('N', $record->getContentLength()));
324
                $offset += (4 + $record->getContentLength());
325
            }
326
        }
327
        @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...
328
    }
329
330
    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...
331
        $shp_name = $this->_getFilename('.shp');
332
        $this->SHPFile = @fopen($shp_name, ($toWrite ? 'wb+' : 'rb'));
333
        if (!$this->SHPFile) {
334
            return $this->setError(sprintf('It wasn\'t possible to open the Shape file "%s"', $shp_name));
335
        }
336
337
        return true;
338
    }
339
340
    private function _closeSHPFile() {
341
        if ($this->SHPFile) {
342
            fclose($this->SHPFile);
343
            $this->SHPFile = null;
344
        }
345
    }
346
347
    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...
348
        $shx_name = $this->_getFilename('.shx');
349
        $this->SHXFile = @fopen($shx_name, ($toWrite ? 'wb+' : 'rb'));
350
        if (!$this->SHXFile) {
351
            return $this->setError(sprintf('It wasn\'t possible to open the Index file "%s"', $shx_name));
352
        }
353
354
        return true;
355
    }
356
357
    private function _closeSHXFile() {
358
        if ($this->SHXFile) {
359
            fclose($this->SHXFile);
360
            $this->SHXFile = null;
361
        }
362
    }
363
364
    /**
365
     * Loads DBF file if supported
366
     *
367
     * @return bool
368
     */
369
    private function _openDBFFile($toWrite = false) {
370
        if (! ShapeFile::supports_dbase()) {
371
            return true;
372
        }
373
        $dbf_name = $this->_getFilename('.dbf');
374
        $checkFunction = $toWrite ? 'is_writable' : 'is_readable';
375
        if (($toWrite) && (!file_exists($dbf_name))) {
376
            if (!@dbase_create($dbf_name, $this->DBFHeader)) {
377
                return $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
378
            }
379
        }
380
        if ($checkFunction($dbf_name)) {
381
            $this->DBFFile = @dbase_open($dbf_name, ($toWrite ? 2 : 0));
382
            if (!$this->DBFFile) {
383
                return $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbf_name));
384
            }
385
        } else {
386
            return $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbf_name));
387
        }
388
        return true;
389
    }
390
391
    private function _closeDBFFile() {
392
        if ($this->DBFFile) {
393
            dbase_close($this->DBFFile);
394
            $this->DBFFile = null;
395
        }
396
    }
397
398
    /**
399
     * Sets error message
400
     *
401
     * @param string $error
402
     *
403
     * @return bool
404
     */
405
    public function setError($error) {
406
        $this->lastError = $error;
407
        return false;
408
    }
409
410
    /**
411
     * Reads given number of bytes from SHP file
412
     *
413
     * @return string|false
414
     */
415
    public function readSHP($bytes)
416
    {
417
        return fread($this->SHPFile, $bytes);
418
    }
419
}
420
421