Completed
Push — master ( e98ad1...607955 )
by Michal
03:47
created

ShapeFile::_saveRecords()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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