Completed
Push — master ( 7d457a...a96574 )
by Michal
04:00
created

ShapeFile::getDBFHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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