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