Completed
Push — master ( e53124...f4ebe5 )
by Michal
03:46
created

ShapeFile::_loadHeaders()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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