Completed
Push — master ( 93f655...59e77d )
by Michal
03:15
created

ShapeFile::_loadRecords()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 6
b 0
f 0
nc 4
nop 0
dl 0
loc 17
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
    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
            if (!isset($this->boundingBox['mmin']) || $this->boundingBox['mmin'] == 0.0 || ($this->boundingBox['mmin'] > $record->SHPData['mmin'])) {
173
                $this->boundingBox['mmin'] = $record->SHPData['mmin'];
174
            }
175
            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
            if (!isset($this->boundingBox['zmin']) || $this->boundingBox['zmin'] == 0.0 || ($this->boundingBox['zmin'] > $record->SHPData['zmin'])) {
182
                $this->boundingBox['zmin'] = $record->SHPData['zmin'];
183
            }
184
            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);
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()) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
340
                break;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_BREAK
Loading history...
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);
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);
373
    }
374
375
    private function _openSHPFile($toWrite = false) {
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) {
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