Completed
Push — master ( a6a466...3425cb )
by Michal
05:54 queued 02:49
created

ShapeFile::setDBFHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
ccs 5
cts 7
cp 0.7143
crap 2.0932
rs 9.4285
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
 * https://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
    public $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
    private static $shape_names = array(
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 18
    public static function supports_dbase()
69
    {
70 18
        return extension_loaded('dbase');
71
    }
72
73
    /**
74
     * @param integer $shapeType
75
     */
76 21
    public function __construct($shapeType, $boundingBox = array('xmin' => 0.0, 'ymin' => 0.0, 'xmax' => 0.0, 'ymax' => 0.0), $FileName = null) {
77 21
        $this->shapeType = $shapeType;
78 21
        $this->boundingBox = $boundingBox;
79 21
        $this->FileName = $FileName;
80 21
        $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 21
    }
82
83
    /**
84
     * @param string $FileName
85
     */
86 20
    public function loadFromFile($FileName) {
87 20
        if (!empty($FileName)) {
88 20
            $this->FileName = $FileName;
89 20
            $result = $this->_openSHPFile();
90 20
        } else {
91
            /* We operate on buffer emulated by readSHP / eofSHP */
92
            $result = true;
93
        }
94
95 20
        if ($result && ($this->_openDBFFile())) {
96 18
            if (!$this->_loadHeaders()) {
97
                $this->_closeSHPFile();
98
                $this->_closeDBFFile();
99
                return false;
100
            }
101 18
            if (!$this->_loadRecords()) {
102
                $this->_closeSHPFile();
103
                $this->_closeDBFFile();
104
                return false;
105
            }
106 18
            $this->_closeSHPFile();
107 18
            $this->_closeDBFFile();
108 18
            return true;
109
        } else {
110 2
            return false;
111
        }
112
    }
113
114
    /**
115
     * @param string|null $FileName Name of file to open
116
     */
117 12
    public function saveToFile($FileName = null) {
118 12
        if (!is_null($FileName)) {
119 12
            $this->FileName = $FileName;
120 12
        }
121
122 12
        if (($this->_openSHPFile(true)) && ($this->_openSHXFile(true)) && ($this->_openDBFFile(true))) {
123 12
            $this->_saveHeaders();
124 12
            $this->_saveRecords();
125 12
            $this->_closeSHPFile();
126 12
            $this->_closeSHXFile();
127 12
            $this->_closeDBFFile();
128 12
        } else {
129
            return false;
130
        }
131 12
    }
132
133
    /**
134
     * Generates filename with given extension
135
     *
136
     * @param string $extension Extension to use (including dot)
137
     *
138
     * @return string
139
     */
140 20
    private function _getFilename($extension)
141
    {
142 20
        return str_replace('.*', $extension, $this->FileName);
143
    }
144
145
    /**
146
     * Updates bounding box based on SHPData
147
     *
148
     * @param string $type Type of box
149
     * @param array  $data ShapeRecord SHPData
150
     *
151
     * @return void
152
     */
153 12
    private function updateBBox($type, $data)
154
    {
155 12
        $min = $type.'min';
156 12
        $max = $type.'max';
157
158 12
        if (!isset($this->boundingBox[$min]) || $this->boundingBox[$min] == 0.0 || ($this->boundingBox[$min] > $data[$min])) {
159 12
            $this->boundingBox[$min] = $data[$min];
160 12
        }
161 12
        if (!isset($this->boundingBox[$max]) || $this->boundingBox[$max] == 0.0 || ($this->boundingBox[$max] < $data[$max])) {
162 12
            $this->boundingBox[$max] = $data[$max];
163 12
        }
164 12
    }
165
166
    /**
167
     * @param ShapeRecord $record
168
     */
169 12
    public function addRecord($record) {
170 12
        if ((isset($this->DBFHeader)) && (is_array($this->DBFHeader))) {
171 12
            $record->updateDBFInfo($this->DBFHeader);
172 12
        }
173
174 12
        $this->fileLength += ($record->getContentLength() + 4);
175 12
        $this->records[] = $record;
176 12
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
177
178 12
        $this->updateBBox('x', $record->SHPData);
179 12
        $this->updateBBox('y', $record->SHPData);
180
181 12
        if (in_array($this->shapeType, array(11, 13, 15, 18, 21, 23, 25, 28))) {
182 8
            $this->updateBBox('m', $record->SHPData);
183 8
        }
184
185 12
        if (in_array($this->shapeType, array(11, 13, 15, 18))) {
186 4
            $this->updateBBox('z', $record->SHPData);
187 4
        }
188
189 12
        return (count($this->records) - 1);
190
    }
191
192
    /**
193
     * @param integer $index
194
     */
195
    public function deleteRecord($index) {
196
        if (isset($this->records[$index])) {
197
            $this->fileLength -= ($this->records[$index]->getContentLength() + 4);
198
            $count = count($this->records) - 1;
199
            for ($i = $index; $i < $count; $i++) {
200
                $this->records[$i] = $this->records[$i + 1];
201
            }
202
            unset($this->records[count($this->records) - 1]);
203
            $this->_deleteRecordFromDBF($index);
204
        }
205
    }
206
207
    public function getDBFHeader() {
208
        return $this->DBFHeader;
209
    }
210
211 12
    public function setDBFHeader($header) {
212 12
        $this->DBFHeader = $header;
213
214 12
        $count = count($this->records);
215 12
        for ($i = 0; $i < $count; $i++) {
216
            $this->records[$i]->updateDBFInfo($header);
217
        }
218 12
    }
219
220
    public function getIndexFromDBFData($field, $value) {
221
        $result = -1;
222
        $count = count($this->records) - 1;
223
        for ($i = 0; $i < $count; $i++) {
224
            if (isset($this->records[$i]->DBFData[$field]) && (strtoupper($this->records[$i]->DBFData[$field]) == strtoupper($value))) {
225
                $result = $i;
226
            }
227
        }
228
229
        return $result;
230
    }
231
232
    private function _loadDBFHeader() {
233
        $DBFFile = fopen($this->_getFilename('.dbf'), 'r');
234
235
        $result = array();
236
        $i = 1;
237
        $inHeader = true;
238
239
        while ($inHeader) {
240
            if (!feof($DBFFile)) {
241
                $buff32 = fread($DBFFile, 32);
242
                if ($i > 1) {
243
                    if (substr($buff32, 0, 1) == chr(13)) {
244
                        $inHeader = false;
245
                    } else {
246
                        $pos = strpos(substr($buff32, 0, 10), chr(0));
247
                        $pos = ($pos == 0 ? 10 : $pos);
248
249
                        $fieldName = substr($buff32, 0, $pos);
250
                        $fieldType = substr($buff32, 11, 1);
251
                        $fieldLen = ord(substr($buff32, 16, 1));
252
                        $fieldDec = ord(substr($buff32, 17, 1));
253
254
                        array_push($result, array($fieldName, $fieldType, $fieldLen, $fieldDec));
255
                    }
256
                }
257
                $i++;
258
            } else {
259
                $inHeader = false;
260
            }
261
        }
262
263
        fclose($DBFFile);
264
        return($result);
265
    }
266
267
    private function _deleteRecordFromDBF($index) {
268
        if (@dbase_delete_record($this->DBFFile, $index)) {
269
            dbase_pack($this->DBFFile);
270
        }
271
    }
272
273 18
    private function _loadHeaders() {
274 18
        if (Util::loadData('N', $this->readSHP(4)) != 9994) {
275
            $this->setError('Not a SHP file (file code mismatch)');
276
            return false;
277
        }
278
279
        /* Skip 20 unused bytes */
280 18
        $this->readSHP(20);
281
282 18
        $this->fileLength = Util::loadData('N', $this->readSHP(4));
283
284
        /* We currently ignore version */
285 18
        $this->readSHP(4);
286
287 18
        $this->shapeType = Util::loadData('V', $this->readSHP(4));
288
289 18
        $this->boundingBox = array();
290 18
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
291 18
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
292 18
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
293 18
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
294 18
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
295 18
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
296 18
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
297 18
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
298
299 18
        if (ShapeFile::supports_dbase()) {
300
            $this->DBFHeader = $this->_loadDBFHeader();
301
        }
302 18
        return true;
303
    }
304
305 12
    private function _saveHeaders() {
306 12
        fwrite($this->SHPFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
307 12
        fwrite($this->SHPFile, pack('N', $this->fileLength));
308 12
        fwrite($this->SHPFile, pack('V', 1000));
309 12
        fwrite($this->SHPFile, pack('V', $this->shapeType));
310 12
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmin']));
311 12
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymin']));
312 12
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['xmax']));
313 12
        fwrite($this->SHPFile, Util::packDouble($this->boundingBox['ymax']));
314 12
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
315 12
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
316 12
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
317 12
        fwrite($this->SHPFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
318
319 12
        fwrite($this->SHXFile, pack('NNNNNN', 9994, 0, 0, 0, 0, 0));
320 12
        fwrite($this->SHXFile, pack('N', 50 + 4 * count($this->records)));
321 12
        fwrite($this->SHXFile, pack('V', 1000));
322 12
        fwrite($this->SHXFile, pack('V', $this->shapeType));
323 12
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmin']));
324 12
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymin']));
325 12
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['xmax']));
326 12
        fwrite($this->SHXFile, Util::packDouble($this->boundingBox['ymax']));
327 12
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmin']) ? $this->boundingBox['zmin'] : 0));
328 12
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['zmax']) ? $this->boundingBox['zmax'] : 0));
329 12
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmin']) ? $this->boundingBox['mmin'] : 0));
330 12
        fwrite($this->SHXFile, Util::packDouble(isset($this->boundingBox['mmax']) ? $this->boundingBox['mmax'] : 0));
331 12
    }
332
333 18
    private function _loadRecords() {
334
        /* Need to start at offset 100 */
335 18
        while (!$this->eofSHP()) {
336 18
            $record = new ShapeRecord(-1);
337 18
            $record->loadFromFile($this, $this->SHPFile, $this->DBFFile);
338 18
            if ($record->lastError != '') {
339
                $this->setError($record->lastError);
340
                return false;
341
            }
342 18
            if (($record->shapeType === false || $record->shapeType === '') && $this->eofSHP()) {
343 18
                break;
344
            }
345
346 18
            $this->records[] = $record;
347 18
        }
348 18
        return true;
349
    }
350
351 12
    private function _saveRecords() {
352 12
        $do_dbase = ShapeFile::supports_dbase();
353
354 12
        if ($do_dbase) {
355
            $dbf_name = $this->_getFilename('.dbf');
356
            if (file_exists($dbf_name)) {
357
                unlink($dbf_name);
358
            }
359
            $this->DBFFile = $this->_createDBFFile();
360
            if ($this->DBFFile === false) {
361
                return false;
362
            }
363
        }
364
365 12
        $offset = 50;
366 12
        if (is_array($this->records) && (count($this->records) > 0)) {
367 12
            foreach ($this->records as $index => $record) {
368
                //Save the record to the .shp file
369 12
                $record->saveToFile($this->SHPFile, $this->DBFFile, $index + 1);
370
371
                //Save the record to the .shx file
372 12
                fwrite($this->SHXFile, pack('N', $offset));
373 12
                fwrite($this->SHXFile, pack('N', $record->getContentLength()));
374 12
                $offset += (4 + $record->getContentLength());
375 12
            }
376 12
        }
377 12
        if ($do_dbase) {
378
            dbase_pack($this->DBFFile);
379
        }
380 12
    }
381
382 20
    private function _openFile($toWrite, $extension, $name) {
383 20
        $shp_name = $this->_getFilename($extension);
384 20
        $result = @fopen($shp_name, ($toWrite ? 'wb+' : 'rb'));
385 20
        if (!$result) {
386 2
            $this->setError(sprintf('It wasn\'t possible to open the %s file "%s"', $name, $shp_name));
387 2
            return false;
388
        }
389
390 18
        return $result;
391
    }
392
393 20
    private function _openSHPFile($toWrite = false) {
394 20
        $this->SHPFile = $this->_openFile($toWrite, '.shp', 'Shape');
395 20
        if (!$this->SHPFile) {
396 2
            return false;
397
        }
398 18
        return true;
399
    }
400
401 18
    private function _closeSHPFile() {
402 18
        if ($this->SHPFile) {
403 18
            fclose($this->SHPFile);
404 18
            $this->SHPFile = null;
405 18
        }
406 18
    }
407
408 12
    private function _openSHXFile($toWrite = false) {
409 12
        $this->SHXFile = $this->_openFile($toWrite, '.shx', 'Index');
410 12
        if (!$this->SHXFile) {
411
            return false;
412
        }
413 12
        return true;
414
    }
415
416 12
    private function _closeSHXFile() {
417 12
        if ($this->SHXFile) {
418 12
            fclose($this->SHXFile);
419 12
            $this->SHXFile = null;
420 12
        }
421 12
    }
422
423
    /**
424
     * Creates DBF file
425
     *
426
     * @return int|false
427
     */
428
    private function _createDBFFile()
429
    {
430
        if (count($this->DBFHeader) == 0) {
431
            return null;
432
        }
433
        $dbf_name = $this->_getFilename('.dbf');
434
        $result = @dbase_create($dbf_name, $this->DBFHeader);
435
        if ($result === false) {
436
            $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbf_name));
437
            return false;
438
        }
439
        return $result;
440
441
    }
442
443
    /**
444
     * Loads DBF file if supported
445
     *
446
     * @return bool
447
     */
448 18
    private function _openDBFFile($toWrite = false) {
449 18
        if (!ShapeFile::supports_dbase()) {
450 18
            return true;
451
        }
452
        $dbf_name = $this->_getFilename('.dbf');
453
        $checkFunction = $toWrite ? 'is_writable' : 'is_readable';
454
        if (($toWrite) && (!file_exists($dbf_name))) {
455
            if ($this->_createDBFFile() === false) {
456
                return false;
457
            }
458
        }
459
        if ($checkFunction($dbf_name)) {
460
            $this->DBFFile = @dbase_open($dbf_name, ($toWrite ? 2 : 0));
461
            if (!$this->DBFFile) {
462
                $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbf_name));
463
                return false;
464
            }
465
        } else {
466
            $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbf_name));
467
            return false;
468
        }
469
        return true;
470
    }
471
472 18
    private function _closeDBFFile() {
473 18
        if ($this->DBFFile) {
474
            dbase_close($this->DBFFile);
475
            $this->DBFFile = null;
476
        }
477 18
    }
478
479
    /**
480
     * Sets error message
481
     *
482
     * @param string $error
483
     *
484
     * @return void
485
     */
486 2
    public function setError($error) {
487 2
        $this->lastError = $error;
488 2
    }
489
490
    /**
491
     * Reads given number of bytes from SHP file
492
     *
493
     * @param integer $bytes
494
     *
495
     * @return string
496
     */
497 18
    public function readSHP($bytes)
498
    {
499 18
        return fread($this->SHPFile, $bytes);
500
    }
501
502
    /**
503
     * Checks whether file is at EOF
504
     *
505
     * @return bool
506
     */
507 18
    public function eofSHP()
508
    {
509 18
        return feof($this->SHPFile);
510
    }
511
512
    /**
513
     * Returns shape name
514
     *
515
     * @return string
516
     */
517 1
    public function getShapeName()
518
    {
519 1
        return ShapeFile::nameShape($this->shapeType);
520
    }
521
522
    /**
523
     * Returns shape name
524
     *
525
     * @param integer $type
526
     *
527
     * @return string
528
     */
529 1
    public static function nameShape($type)
530
    {
531 1
        if (isset(self::$shape_names[$type])) {
532 1
            return self::$shape_names[$type];
533
        }
534 1
        return sprintf('Shape %d', $type);
535
    }
536
537
    /**
538
     * Check whether file contains measure data.
539
     *
540
     * For some reason this is distinguished by zero bouding box in the
541
     * specification.
542
     *
543
     * @return bool
544
     */
545 8
    public function hasMeasure()
546
    {
547 8
            return $this->boundingBox['mmin'] != 0 || $this->boundingBox['mmax'] != 0;
548
    }
549
}
550
551