1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* phpMyAdmin ShapeFile library |
7
|
|
|
* <https://github.com/phpmyadmin/shapefile/>. |
8
|
|
|
* |
9
|
|
|
* Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net> |
10
|
|
|
* Copyright 2016 - 2017 Michal Čihař <[email protected]> |
11
|
|
|
* |
12
|
|
|
* This program is free software; you can redistribute it and/or |
13
|
|
|
* modify it under the terms of the GNU General Public License |
14
|
|
|
* as published by the Free Software Foundation. |
15
|
|
|
* |
16
|
|
|
* This program is distributed in the hope that it will be useful, |
17
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
18
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19
|
|
|
* GNU General Public License for more details. |
20
|
|
|
* |
21
|
|
|
* You should have received a copy of the GNU General Public License |
22
|
|
|
* along with this program; if not, you can download one from |
23
|
|
|
* https://www.gnu.org/copyleft/gpl.html. |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
namespace PhpMyAdmin\ShapeFile; |
27
|
|
|
|
28
|
|
|
use function array_values; |
29
|
|
|
use function count; |
30
|
|
|
use function fwrite; |
31
|
|
|
use function in_array; |
32
|
|
|
use function is_array; |
33
|
|
|
use function pack; |
34
|
|
|
use function sprintf; |
35
|
|
|
use function strlen; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* ShapeFile record class. |
39
|
|
|
*/ |
40
|
|
|
class ShapeRecord |
41
|
|
|
{ |
42
|
|
|
/** @var resource */ |
43
|
|
|
private $shpFile; |
44
|
|
|
|
45
|
|
|
private ShapeFile|null $shapeFile = null; |
46
|
|
|
|
47
|
|
|
private int $size = 0; |
48
|
|
|
|
49
|
|
|
private int $read = 0; |
50
|
|
|
|
51
|
|
|
public int $recordNumber = 0; |
52
|
|
|
|
53
|
|
|
public string $lastError = ''; |
54
|
|
|
|
55
|
|
|
/** @var mixed[] */ |
56
|
|
|
public array $shpData = []; |
57
|
|
|
|
58
|
|
|
/** @var mixed[] */ |
59
|
|
|
public array $dbfData = []; |
60
|
|
|
|
61
|
86 |
|
public function __construct(public int $shapeType) |
62
|
|
|
{ |
63
|
86 |
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Loads record from files. |
67
|
|
|
* |
68
|
|
|
* @param ShapeFile $shapeFile The ShapeFile object |
69
|
|
|
* @param resource|false $dbfFile Opened DBF file |
70
|
|
|
*/ |
71
|
82 |
|
public function loadFromFile(ShapeFile $shapeFile, $dbfFile): void |
72
|
|
|
{ |
73
|
82 |
|
$this->shapeFile = $shapeFile; |
74
|
82 |
|
$this->loadHeaders(); |
75
|
|
|
|
76
|
|
|
/* No header read */ |
77
|
82 |
|
if ($this->read === 0) { |
78
|
82 |
|
return; |
79
|
|
|
} |
80
|
|
|
|
81
|
82 |
|
match ($this->shapeType) { |
82
|
82 |
|
ShapeType::NULL => $this->loadNullRecord(), |
|
|
|
|
83
|
82 |
|
ShapeType::POINT => $this->loadPointRecord(), |
|
|
|
|
84
|
82 |
|
ShapeType::POINT_M => $this->loadPointMRecord(), |
|
|
|
|
85
|
82 |
|
ShapeType::POINT_Z => $this->loadPointZRecord(), |
|
|
|
|
86
|
82 |
|
ShapeType::POLY_LINE => $this->loadPolyLineRecord(), |
|
|
|
|
87
|
82 |
|
ShapeType::POLY_LINE_M => $this->loadPolyLineMRecord(), |
|
|
|
|
88
|
82 |
|
ShapeType::POLY_LINE_Z => $this->loadPolyLineZRecord(), |
|
|
|
|
89
|
82 |
|
ShapeType::POLYGON => $this->loadPolygonRecord(), |
|
|
|
|
90
|
82 |
|
ShapeType::POLYGON_M => $this->loadPolygonMRecord(), |
|
|
|
|
91
|
82 |
|
ShapeType::POLYGON_Z => $this->loadPolygonZRecord(), |
|
|
|
|
92
|
82 |
|
ShapeType::MULTI_POINT => $this->loadMultiPointRecord(), |
|
|
|
|
93
|
82 |
|
ShapeType::MULTI_POINT_M => $this->loadMultiPointMRecord(), |
|
|
|
|
94
|
82 |
|
ShapeType::MULTI_POINT_Z => $this->loadMultiPointZRecord(), |
|
|
|
|
95
|
82 |
|
default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)), |
|
|
|
|
96
|
82 |
|
}; |
97
|
|
|
|
98
|
|
|
/* We need to skip rest of the record */ |
99
|
82 |
|
while ($this->read < $this->size) { |
100
|
24 |
|
$this->loadData('V', 4); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/* Check if we didn't read too much */ |
104
|
82 |
|
if ($this->read !== $this->size) { |
105
|
|
|
$this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size)); |
106
|
|
|
} |
107
|
|
|
|
108
|
82 |
|
if (! ShapeFile::supportsDbase() || $dbfFile === false) { |
109
|
38 |
|
return; |
110
|
|
|
} |
111
|
|
|
|
112
|
44 |
|
$this->loadDBFData($dbfFile); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Saves record to files. |
117
|
|
|
* |
118
|
|
|
* @param resource $shpFile Opened SHP file |
119
|
|
|
* @param resource|false $dbfFile Opened DBF file |
120
|
|
|
* @param int $recordNumber Record number |
121
|
|
|
*/ |
122
|
54 |
|
public function saveToFile($shpFile, $dbfFile, int $recordNumber): void |
123
|
|
|
{ |
124
|
54 |
|
$this->shpFile = $shpFile; |
125
|
54 |
|
$this->recordNumber = $recordNumber; |
126
|
54 |
|
$this->saveHeaders(); |
127
|
|
|
|
128
|
54 |
|
match ($this->shapeType) { |
129
|
54 |
|
ShapeType::NULL => null, // Nothing to save |
130
|
54 |
|
ShapeType::POINT => $this->savePointRecord(), |
|
|
|
|
131
|
54 |
|
ShapeType::POINT_M => $this->savePointMRecord(), |
|
|
|
|
132
|
54 |
|
ShapeType::POINT_Z => $this->savePointZRecord(), |
|
|
|
|
133
|
54 |
|
ShapeType::POLY_LINE => $this->savePolyLineRecord(), |
|
|
|
|
134
|
54 |
|
ShapeType::POLY_LINE_M => $this->savePolyLineMRecord(), |
|
|
|
|
135
|
54 |
|
ShapeType::POLY_LINE_Z => $this->savePolyLineZRecord(), |
|
|
|
|
136
|
54 |
|
ShapeType::POLYGON => $this->savePolygonRecord(), |
|
|
|
|
137
|
54 |
|
ShapeType::POLYGON_M => $this->savePolygonMRecord(), |
|
|
|
|
138
|
54 |
|
ShapeType::POLYGON_Z => $this->savePolygonZRecord(), |
|
|
|
|
139
|
54 |
|
ShapeType::MULTI_POINT => $this->saveMultiPointRecord(), |
|
|
|
|
140
|
54 |
|
ShapeType::MULTI_POINT_M => $this->saveMultiPointMRecord(), |
|
|
|
|
141
|
54 |
|
ShapeType::MULTI_POINT_Z => $this->saveMultiPointZRecord(), |
|
|
|
|
142
|
54 |
|
default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)), |
|
|
|
|
143
|
54 |
|
}; |
144
|
|
|
|
145
|
54 |
|
if (! ShapeFile::supportsDbase() || $dbfFile === false) { |
146
|
24 |
|
return; |
147
|
|
|
} |
148
|
|
|
|
149
|
30 |
|
$this->saveDBFData($dbfFile); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Updates DBF data to match header. |
154
|
|
|
* |
155
|
|
|
* @param mixed[] $header DBF structure header |
156
|
|
|
*/ |
157
|
54 |
|
public function updateDBFInfo(array $header): void |
158
|
|
|
{ |
159
|
54 |
|
$tmp = $this->dbfData; |
160
|
54 |
|
$this->dbfData = []; |
161
|
54 |
|
foreach ($header as [$value]) { |
162
|
54 |
|
$this->dbfData[$value] = $tmp[$value] ?? ''; |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Reads data. |
168
|
|
|
* |
169
|
|
|
* @param string $type type for unpack() |
170
|
|
|
* @param int<0, max> $count number of bytes |
171
|
|
|
*/ |
172
|
82 |
|
private function loadData(string $type, int $count): mixed |
173
|
|
|
{ |
174
|
82 |
|
$data = $this->shapeFile->readSHP($count); |
|
|
|
|
175
|
82 |
|
if ($data === false) { |
176
|
|
|
return false; |
177
|
|
|
} |
178
|
|
|
|
179
|
82 |
|
$this->read += strlen($data); |
180
|
|
|
|
181
|
82 |
|
return Util::loadData($type, $data); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Loads metadata header from a file. |
186
|
|
|
*/ |
187
|
82 |
|
private function loadHeaders(): void |
188
|
|
|
{ |
189
|
82 |
|
$this->shapeType = -1; |
190
|
82 |
|
$recordNumber = $this->loadData('N', 4); |
191
|
82 |
|
if ($recordNumber === false) { |
192
|
82 |
|
return; |
193
|
|
|
} |
194
|
|
|
|
195
|
82 |
|
$this->recordNumber = (int) $recordNumber; |
196
|
|
|
|
197
|
|
|
// We read the length of the record |
198
|
82 |
|
$size = $this->loadData('N', 4); |
199
|
82 |
|
if ($size === false) { |
200
|
|
|
return; |
201
|
|
|
} |
202
|
|
|
|
203
|
82 |
|
$this->size = ($size * 2) + 8; |
204
|
|
|
|
205
|
82 |
|
$shapeType = $this->loadData('V', 4); |
206
|
82 |
|
if ($shapeType === false) { |
207
|
|
|
return; |
208
|
|
|
} |
209
|
|
|
|
210
|
82 |
|
$this->shapeType = (int) $shapeType; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Saves metadata header to a file. |
215
|
|
|
*/ |
216
|
54 |
|
private function saveHeaders(): void |
217
|
|
|
{ |
218
|
54 |
|
fwrite($this->shpFile, pack('N', $this->recordNumber)); |
219
|
54 |
|
fwrite($this->shpFile, pack('N', $this->getContentLength())); |
220
|
54 |
|
fwrite($this->shpFile, pack('V', $this->shapeType)); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** @return mixed[] */ |
224
|
82 |
|
private function loadPoint(): array |
225
|
|
|
{ |
226
|
82 |
|
return [ |
227
|
82 |
|
'x' => $this->loadData('d', 8), |
228
|
82 |
|
'y' => $this->loadData('d', 8), |
229
|
82 |
|
]; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** @return mixed[] */ |
233
|
10 |
|
private function loadPointM(): array |
234
|
|
|
{ |
235
|
10 |
|
$data = $this->loadPoint(); |
236
|
|
|
|
237
|
10 |
|
$data['m'] = $this->loadData('d', 8); |
238
|
|
|
|
239
|
10 |
|
return $data; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** @return mixed[] */ |
243
|
10 |
|
private function loadPointZ(): array |
244
|
|
|
{ |
245
|
10 |
|
$data = $this->loadPoint(); |
246
|
|
|
|
247
|
10 |
|
$data['z'] = $this->loadData('d', 8); |
248
|
10 |
|
$data['m'] = $this->loadData('d', 8); |
249
|
|
|
|
250
|
10 |
|
return $data; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** @param mixed[] $data */ |
254
|
46 |
|
private function savePoint(array $data): void |
255
|
|
|
{ |
256
|
46 |
|
fwrite($this->shpFile, Util::packDouble($data['x'])); |
257
|
46 |
|
fwrite($this->shpFile, Util::packDouble($data['y'])); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** @param mixed[] $data */ |
261
|
10 |
|
private function savePointM(array $data): void |
262
|
|
|
{ |
263
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['x'])); |
264
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['y'])); |
265
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['m'])); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** @param mixed[] $data */ |
269
|
10 |
|
private function savePointZ(array $data): void |
270
|
|
|
{ |
271
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['x'])); |
272
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['y'])); |
273
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['z'])); |
274
|
10 |
|
fwrite($this->shpFile, Util::packDouble($data['m'])); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
private function loadNullRecord(): void |
278
|
|
|
{ |
279
|
|
|
$this->shpData = []; |
280
|
|
|
} |
281
|
|
|
|
282
|
22 |
|
private function loadPointRecord(): void |
283
|
|
|
{ |
284
|
22 |
|
$this->shpData = $this->loadPoint(); |
285
|
|
|
} |
286
|
|
|
|
287
|
10 |
|
private function loadPointMRecord(): void |
288
|
|
|
{ |
289
|
10 |
|
$this->shpData = $this->loadPointM(); |
290
|
|
|
} |
291
|
|
|
|
292
|
10 |
|
private function loadPointZRecord(): void |
293
|
|
|
{ |
294
|
10 |
|
$this->shpData = $this->loadPointZ(); |
295
|
|
|
} |
296
|
|
|
|
297
|
10 |
|
private function savePointRecord(): void |
298
|
|
|
{ |
299
|
10 |
|
$this->savePoint($this->shpData); |
300
|
|
|
} |
301
|
|
|
|
302
|
10 |
|
private function savePointMRecord(): void |
303
|
|
|
{ |
304
|
10 |
|
$this->savePointM($this->shpData); |
305
|
|
|
} |
306
|
|
|
|
307
|
10 |
|
private function savePointZRecord(): void |
308
|
|
|
{ |
309
|
10 |
|
$this->savePointZ($this->shpData); |
310
|
|
|
} |
311
|
|
|
|
312
|
58 |
|
private function loadBBox(): void |
313
|
|
|
{ |
314
|
58 |
|
$this->shpData['xmin'] = $this->loadData('d', 8); |
315
|
58 |
|
$this->shpData['ymin'] = $this->loadData('d', 8); |
316
|
58 |
|
$this->shpData['xmax'] = $this->loadData('d', 8); |
317
|
58 |
|
$this->shpData['ymax'] = $this->loadData('d', 8); |
318
|
|
|
} |
319
|
|
|
|
320
|
16 |
|
private function loadMultiPointRecord(): void |
321
|
|
|
{ |
322
|
16 |
|
$this->shpData = []; |
323
|
16 |
|
$this->loadBBox(); |
324
|
|
|
|
325
|
16 |
|
$this->shpData['numpoints'] = $this->loadData('V', 4); |
326
|
|
|
|
327
|
16 |
|
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { |
328
|
16 |
|
$this->shpData['points'][] = $this->loadPoint(); |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
|
332
|
12 |
|
private function loadMultiPointMZRecord(string $type): void |
333
|
|
|
{ |
334
|
|
|
/* The m dimension is optional, depends on bounding box data */ |
335
|
12 |
|
if ($type === 'm' && ! $this->shapeFile->hasMeasure()) { |
336
|
12 |
|
return; |
337
|
|
|
} |
338
|
|
|
|
339
|
8 |
|
$this->shpData[$type . 'min'] = $this->loadData('d', 8); |
340
|
8 |
|
$this->shpData[$type . 'max'] = $this->loadData('d', 8); |
341
|
|
|
|
342
|
8 |
|
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { |
343
|
8 |
|
$this->shpData['points'][$i][$type] = $this->loadData('d', 8); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
|
347
|
4 |
|
private function loadMultiPointMRecord(): void |
348
|
|
|
{ |
349
|
4 |
|
$this->loadMultiPointRecord(); |
350
|
|
|
|
351
|
4 |
|
$this->loadMultiPointMZRecord('m'); |
352
|
|
|
} |
353
|
|
|
|
354
|
8 |
|
private function loadMultiPointZRecord(): void |
355
|
|
|
{ |
356
|
8 |
|
$this->loadMultiPointRecord(); |
357
|
|
|
|
358
|
8 |
|
$this->loadMultiPointMZRecord('z'); |
359
|
8 |
|
$this->loadMultiPointMZRecord('m'); |
360
|
|
|
} |
361
|
|
|
|
362
|
12 |
|
private function saveMultiPointRecord(): void |
363
|
|
|
{ |
364
|
12 |
|
fwrite($this->shpFile, pack( |
365
|
12 |
|
'dddd', |
366
|
12 |
|
$this->shpData['xmin'], |
367
|
12 |
|
$this->shpData['ymin'], |
368
|
12 |
|
$this->shpData['xmax'], |
369
|
12 |
|
$this->shpData['ymax'], |
370
|
12 |
|
)); |
371
|
|
|
|
372
|
12 |
|
fwrite($this->shpFile, pack('V', $this->shpData['numpoints'])); |
373
|
|
|
|
374
|
12 |
|
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { |
375
|
12 |
|
$this->savePoint($this->shpData['points'][$i]); |
376
|
|
|
} |
377
|
|
|
} |
378
|
|
|
|
379
|
8 |
|
private function saveMultiPointMZRecord(string $type): void |
380
|
|
|
{ |
381
|
8 |
|
fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max'])); |
382
|
|
|
|
383
|
8 |
|
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { |
384
|
8 |
|
fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type])); |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
4 |
|
private function saveMultiPointMRecord(): void |
389
|
|
|
{ |
390
|
4 |
|
$this->saveMultiPointRecord(); |
391
|
|
|
|
392
|
4 |
|
$this->saveMultiPointMZRecord('m'); |
393
|
|
|
} |
394
|
|
|
|
395
|
4 |
|
private function saveMultiPointZRecord(): void |
396
|
|
|
{ |
397
|
4 |
|
$this->saveMultiPointRecord(); |
398
|
|
|
|
399
|
4 |
|
$this->saveMultiPointMZRecord('z'); |
400
|
4 |
|
$this->saveMultiPointMZRecord('m'); |
401
|
|
|
} |
402
|
|
|
|
403
|
42 |
|
private function loadPolyLineRecord(): void |
404
|
|
|
{ |
405
|
42 |
|
$this->shpData = []; |
406
|
42 |
|
$this->loadBBox(); |
407
|
|
|
|
408
|
42 |
|
$this->shpData['numparts'] = $this->loadData('V', 4); |
409
|
42 |
|
$this->shpData['numpoints'] = $this->loadData('V', 4); |
410
|
|
|
|
411
|
42 |
|
$numparts = $this->shpData['numparts']; |
412
|
42 |
|
$numpoints = $this->shpData['numpoints']; |
413
|
|
|
|
414
|
42 |
|
for ($i = 0; $i < $numparts; ++$i) { |
415
|
42 |
|
$this->shpData['parts'][$i] = $this->loadData('V', 4); |
416
|
|
|
} |
417
|
|
|
|
418
|
42 |
|
$part = 0; |
419
|
42 |
|
for ($i = 0; $i < $numpoints; ++$i) { |
420
|
42 |
|
if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) { |
421
|
38 |
|
++$part; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
if ( |
425
|
42 |
|
! isset($this->shpData['parts'][$part]['points']) |
426
|
42 |
|
|| ! is_array($this->shpData['parts'][$part]['points']) |
427
|
|
|
) { |
428
|
42 |
|
$this->shpData['parts'][$part] = ['points' => []]; |
429
|
|
|
} |
430
|
|
|
|
431
|
42 |
|
$this->shpData['parts'][$part]['points'][] = $this->loadPoint(); |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
|
435
|
20 |
|
private function loadPolyLineMZRecord(string $type): void |
436
|
|
|
{ |
437
|
|
|
/* The m dimension is optional, depends on bounding box data */ |
438
|
20 |
|
if ($type === 'm' && ! $this->shapeFile->hasMeasure()) { |
439
|
20 |
|
return; |
440
|
|
|
} |
441
|
|
|
|
442
|
12 |
|
$this->shpData[$type . 'min'] = $this->loadData('d', 8); |
443
|
12 |
|
$this->shpData[$type . 'max'] = $this->loadData('d', 8); |
444
|
|
|
|
445
|
12 |
|
$numparts = $this->shpData['numparts']; |
446
|
12 |
|
$numpoints = $this->shpData['numpoints']; |
447
|
|
|
|
448
|
12 |
|
$part = 0; |
449
|
12 |
|
for ($i = 0; $i < $numpoints; ++$i) { |
450
|
12 |
|
if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) { |
451
|
|
|
++$part; |
452
|
|
|
} |
453
|
|
|
|
454
|
12 |
|
$this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8); |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
|
458
|
8 |
|
private function loadPolyLineMRecord(): void |
459
|
|
|
{ |
460
|
8 |
|
$this->loadPolyLineRecord(); |
461
|
|
|
|
462
|
8 |
|
$this->loadPolyLineMZRecord('m'); |
463
|
|
|
} |
464
|
|
|
|
465
|
12 |
|
private function loadPolyLineZRecord(): void |
466
|
|
|
{ |
467
|
12 |
|
$this->loadPolyLineRecord(); |
468
|
|
|
|
469
|
12 |
|
$this->loadPolyLineMZRecord('z'); |
470
|
12 |
|
$this->loadPolyLineMZRecord('m'); |
471
|
|
|
} |
472
|
|
|
|
473
|
30 |
|
private function savePolyLineRecord(): void |
474
|
|
|
{ |
475
|
30 |
|
fwrite($this->shpFile, pack( |
476
|
30 |
|
'dddd', |
477
|
30 |
|
$this->shpData['xmin'], |
478
|
30 |
|
$this->shpData['ymin'], |
479
|
30 |
|
$this->shpData['xmax'], |
480
|
30 |
|
$this->shpData['ymax'], |
481
|
30 |
|
)); |
482
|
|
|
|
483
|
30 |
|
fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints'])); |
484
|
|
|
|
485
|
30 |
|
$partIndex = 0; |
486
|
30 |
|
for ($i = 0; $i < $this->shpData['numparts']; ++$i) { |
487
|
30 |
|
fwrite($this->shpFile, pack('V', $partIndex)); |
488
|
30 |
|
$partIndex += count($this->shpData['parts'][$i]['points']); |
489
|
|
|
} |
490
|
|
|
|
491
|
30 |
|
foreach ($this->shpData['parts'] as $partData) { |
492
|
30 |
|
foreach ($partData['points'] as $pointData) { |
493
|
30 |
|
$this->savePoint($pointData); |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
} |
497
|
|
|
|
498
|
16 |
|
private function savePolyLineMZRecord(string $type): void |
499
|
|
|
{ |
500
|
16 |
|
fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max'])); |
501
|
|
|
|
502
|
16 |
|
foreach ($this->shpData['parts'] as $partData) { |
503
|
16 |
|
foreach ($partData['points'] as $pointData) { |
504
|
16 |
|
fwrite($this->shpFile, Util::packDouble($pointData[$type])); |
505
|
|
|
} |
506
|
|
|
} |
507
|
|
|
} |
508
|
|
|
|
509
|
8 |
|
private function savePolyLineMRecord(): void |
510
|
|
|
{ |
511
|
8 |
|
$this->savePolyLineRecord(); |
512
|
|
|
|
513
|
8 |
|
$this->savePolyLineMZRecord('m'); |
514
|
|
|
} |
515
|
|
|
|
516
|
8 |
|
private function savePolyLineZRecord(): void |
517
|
|
|
{ |
518
|
8 |
|
$this->savePolyLineRecord(); |
519
|
|
|
|
520
|
8 |
|
$this->savePolyLineMZRecord('z'); |
521
|
8 |
|
$this->savePolyLineMZRecord('m'); |
522
|
|
|
} |
523
|
|
|
|
524
|
12 |
|
private function loadPolygonRecord(): void |
525
|
|
|
{ |
526
|
12 |
|
$this->loadPolyLineRecord(); |
527
|
|
|
} |
528
|
|
|
|
529
|
4 |
|
private function loadPolygonMRecord(): void |
530
|
|
|
{ |
531
|
4 |
|
$this->loadPolyLineMRecord(); |
532
|
|
|
} |
533
|
|
|
|
534
|
8 |
|
private function loadPolygonZRecord(): void |
535
|
|
|
{ |
536
|
8 |
|
$this->loadPolyLineZRecord(); |
537
|
|
|
} |
538
|
|
|
|
539
|
4 |
|
private function savePolygonRecord(): void |
540
|
|
|
{ |
541
|
4 |
|
$this->savePolyLineRecord(); |
542
|
|
|
} |
543
|
|
|
|
544
|
4 |
|
private function savePolygonMRecord(): void |
545
|
|
|
{ |
546
|
4 |
|
$this->savePolyLineMRecord(); |
547
|
|
|
} |
548
|
|
|
|
549
|
4 |
|
private function savePolygonZRecord(): void |
550
|
|
|
{ |
551
|
4 |
|
$this->savePolyLineZRecord(); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** @param mixed[] $point */ |
555
|
54 |
|
private function adjustBBox(array $point): void |
556
|
|
|
{ |
557
|
|
|
// Adjusts bounding box based on point |
558
|
54 |
|
foreach (['x', 'y', 'z', 'm'] as $direction) { |
559
|
54 |
|
if (! isset($point[$direction])) { |
560
|
38 |
|
continue; |
561
|
|
|
} |
562
|
|
|
|
563
|
54 |
|
$min = $direction . 'min'; |
564
|
54 |
|
$max = $direction . 'max'; |
565
|
54 |
|
if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) { |
566
|
54 |
|
$this->shpData[$min] = $point[$direction]; |
567
|
|
|
} |
568
|
|
|
|
569
|
54 |
|
if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) { |
570
|
42 |
|
continue; |
571
|
|
|
} |
572
|
|
|
|
573
|
54 |
|
$this->shpData[$max] = $point[$direction]; |
574
|
|
|
} |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* Adjust point and bounding box when adding point. |
579
|
|
|
* Sets dimension to 0 if not set. |
580
|
|
|
* |
581
|
|
|
* @param mixed[] $point Point data |
582
|
|
|
* |
583
|
|
|
* @return mixed[] Fixed point data |
584
|
|
|
*/ |
585
|
54 |
|
private function adjustPoint(array $point): array |
586
|
|
|
{ |
587
|
54 |
|
if (in_array($this->shapeType, ShapeType::MEASURED_TYPES, true)) { |
588
|
38 |
|
$point['m'] ??= 0.0; |
589
|
|
|
} |
590
|
|
|
|
591
|
54 |
|
if (in_array($this->shapeType, ShapeType::TYPES_WITH_Z, true)) { |
592
|
22 |
|
$point['z'] ??= 0.0; |
593
|
|
|
} |
594
|
|
|
|
595
|
54 |
|
return $point; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
/** |
599
|
|
|
* Adds point to a record. |
600
|
|
|
* |
601
|
|
|
* @param mixed[] $point Point data |
602
|
|
|
* @param int $partIndex Part index |
603
|
|
|
*/ |
604
|
54 |
|
public function addPoint(array $point, int $partIndex = 0): void |
605
|
|
|
{ |
606
|
54 |
|
$point = $this->adjustPoint($point); |
607
|
54 |
|
switch ($this->shapeType) { |
608
|
|
|
case ShapeType::NULL: |
609
|
|
|
//Don't add anything |
610
|
|
|
return; |
611
|
|
|
|
612
|
|
|
case ShapeType::POINT: |
613
|
|
|
case ShapeType::POINT_Z: |
614
|
|
|
case ShapeType::POINT_M: |
615
|
|
|
//Substitutes the value of the current point |
616
|
18 |
|
$this->shpData = $point; |
617
|
18 |
|
break; |
618
|
|
|
case ShapeType::POLY_LINE: |
619
|
|
|
case ShapeType::POLYGON: |
620
|
|
|
case ShapeType::POLY_LINE_Z: |
621
|
|
|
case ShapeType::POLYGON_Z: |
622
|
|
|
case ShapeType::POLY_LINE_M: |
623
|
|
|
case ShapeType::POLYGON_M: |
624
|
|
|
//Adds a new point to the selected part |
625
|
30 |
|
$this->shpData['parts'][$partIndex]['points'][] = $point; |
626
|
30 |
|
$this->shpData['numparts'] = count($this->shpData['parts']); |
627
|
30 |
|
$this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0); |
628
|
30 |
|
break; |
629
|
|
|
case ShapeType::MULTI_POINT: |
630
|
|
|
case ShapeType::MULTI_POINT_Z: |
631
|
|
|
case ShapeType::MULTI_POINT_M: |
632
|
|
|
//Adds a new point |
633
|
12 |
|
$this->shpData['points'][] = $point; |
634
|
12 |
|
$this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0); |
635
|
12 |
|
break; |
636
|
|
|
default: |
637
|
|
|
$this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); |
638
|
|
|
|
639
|
|
|
return; |
640
|
|
|
} |
641
|
|
|
|
642
|
54 |
|
$this->adjustBBox($point); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
/** |
646
|
|
|
* Deletes point from a record. |
647
|
|
|
* |
648
|
|
|
* @param int $pointIndex Point index |
649
|
|
|
* @param int $partIndex Part index |
650
|
|
|
*/ |
651
|
48 |
|
public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void |
652
|
|
|
{ |
653
|
48 |
|
switch ($this->shapeType) { |
654
|
|
|
case ShapeType::NULL: |
655
|
|
|
//Don't delete anything |
656
|
|
|
break; |
657
|
|
|
case ShapeType::POINT: |
658
|
|
|
case ShapeType::POINT_Z: |
659
|
|
|
case ShapeType::POINT_M: |
660
|
|
|
//Sets the value of the point to zero |
661
|
12 |
|
$this->shpData['x'] = 0.0; |
662
|
12 |
|
$this->shpData['y'] = 0.0; |
663
|
12 |
|
if (in_array($this->shapeType, [ShapeType::POINT_Z, ShapeType::POINT_M], true)) { |
664
|
8 |
|
$this->shpData['m'] = 0.0; |
665
|
|
|
} |
666
|
|
|
|
667
|
12 |
|
if ($this->shapeType === ShapeType::POINT_Z) { |
668
|
4 |
|
$this->shpData['z'] = 0.0; |
669
|
|
|
} |
670
|
|
|
|
671
|
12 |
|
break; |
672
|
|
|
case ShapeType::POLY_LINE: |
673
|
|
|
case ShapeType::POLYGON: |
674
|
|
|
case ShapeType::POLY_LINE_Z: |
675
|
|
|
case ShapeType::POLYGON_Z: |
676
|
|
|
case ShapeType::POLY_LINE_M: |
677
|
|
|
case ShapeType::POLYGON_M: |
678
|
|
|
//Deletes the point from the selected part, if exists |
679
|
|
|
if ( |
680
|
24 |
|
isset($this->shpData['parts'][$partIndex]) |
681
|
24 |
|
&& isset($this->shpData['parts'][$partIndex]['points'][$pointIndex]) |
682
|
|
|
) { |
683
|
24 |
|
$count = count($this->shpData['parts'][$partIndex]['points']) - 1; |
684
|
24 |
|
for ($i = $pointIndex; $i < $count; ++$i) { |
685
|
24 |
|
$point = $this->shpData['parts'][$partIndex]['points'][$i + 1]; |
686
|
24 |
|
$this->shpData['parts'][$partIndex]['points'][$i] = $point; |
687
|
|
|
} |
688
|
|
|
|
689
|
24 |
|
$count = count($this->shpData['parts'][$partIndex]['points']) - 1; |
690
|
24 |
|
unset($this->shpData['parts'][$partIndex]['points'][$count]); |
691
|
|
|
|
692
|
24 |
|
$this->shpData['numparts'] = count($this->shpData['parts']); |
693
|
24 |
|
--$this->shpData['numpoints']; |
694
|
|
|
} |
695
|
|
|
|
696
|
24 |
|
break; |
697
|
|
|
case ShapeType::MULTI_POINT: |
698
|
|
|
case ShapeType::MULTI_POINT_Z: |
699
|
|
|
case ShapeType::MULTI_POINT_M: |
700
|
|
|
//Deletes the point, if exists |
701
|
12 |
|
if (isset($this->shpData['points'][$pointIndex])) { |
702
|
12 |
|
$count = count($this->shpData['points']) - 1; |
703
|
12 |
|
for ($i = $pointIndex; $i < $count; ++$i) { |
704
|
12 |
|
$this->shpData['points'][$i] = $this->shpData['points'][$i + 1]; |
705
|
|
|
} |
706
|
|
|
|
707
|
12 |
|
unset($this->shpData['points'][count($this->shpData['points']) - 1]); |
708
|
|
|
|
709
|
12 |
|
--$this->shpData['numpoints']; |
710
|
|
|
} |
711
|
|
|
|
712
|
12 |
|
break; |
713
|
|
|
default: |
714
|
|
|
$this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); |
715
|
|
|
break; |
716
|
|
|
} |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
/** |
720
|
|
|
* Returns length of content. |
721
|
|
|
*/ |
722
|
54 |
|
public function getContentLength(): int|null |
723
|
|
|
{ |
724
|
|
|
// The content length for a record is the length of the record contents section measured in 16-bit words. |
725
|
|
|
// one coordinate makes 4 16-bit words (64 bit double) |
726
|
54 |
|
switch ($this->shapeType) { |
727
|
|
|
case ShapeType::NULL: |
728
|
|
|
$result = 0; |
729
|
|
|
break; |
730
|
|
|
case ShapeType::POINT: |
731
|
10 |
|
$result = 10; |
732
|
10 |
|
break; |
733
|
|
|
case ShapeType::POINT_M: |
734
|
10 |
|
$result = 10 + 4; |
735
|
10 |
|
break; |
736
|
|
|
case ShapeType::POINT_Z: |
737
|
10 |
|
$result = 10 + 8; |
738
|
10 |
|
break; |
739
|
|
|
case ShapeType::POLY_LINE: |
740
|
|
|
case ShapeType::POLYGON: |
741
|
14 |
|
$count = count($this->shpData['parts']); |
742
|
14 |
|
$result = 22 + 2 * $count; |
743
|
14 |
|
for ($i = 0; $i < $count; ++$i) { |
744
|
14 |
|
$result += 8 * count($this->shpData['parts'][$i]['points']); |
745
|
|
|
} |
746
|
|
|
|
747
|
14 |
|
break; |
748
|
|
|
case ShapeType::POLY_LINE_M: |
749
|
|
|
case ShapeType::POLYGON_M: |
750
|
8 |
|
$count = count($this->shpData['parts']); |
751
|
8 |
|
$result = 22 + (2 * 4) + 2 * $count; |
752
|
8 |
|
for ($i = 0; $i < $count; ++$i) { |
753
|
8 |
|
$result += (8 + 4) * count($this->shpData['parts'][$i]['points']); |
754
|
|
|
} |
755
|
|
|
|
756
|
8 |
|
break; |
757
|
|
|
case ShapeType::POLY_LINE_Z: |
758
|
|
|
case ShapeType::POLYGON_Z: |
759
|
8 |
|
$count = count($this->shpData['parts']); |
760
|
8 |
|
$result = 22 + (4 * 4) + 2 * $count; |
761
|
8 |
|
for ($i = 0; $i < $count; ++$i) { |
762
|
8 |
|
$result += (8 + 8) * count($this->shpData['parts'][$i]['points']); |
763
|
|
|
} |
764
|
|
|
|
765
|
8 |
|
break; |
766
|
|
|
case ShapeType::MULTI_POINT: |
767
|
4 |
|
$result = 20 + 8 * count($this->shpData['points']); |
768
|
4 |
|
break; |
769
|
|
|
case ShapeType::MULTI_POINT_M: |
770
|
4 |
|
$result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']); |
771
|
4 |
|
break; |
772
|
|
|
case ShapeType::MULTI_POINT_Z: |
773
|
4 |
|
$result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']); |
774
|
4 |
|
break; |
775
|
|
|
default: |
776
|
|
|
$result = null; |
777
|
|
|
$this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); |
778
|
|
|
break; |
779
|
|
|
} |
780
|
|
|
|
781
|
54 |
|
return $result; |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
/** @param resource $dbfFile Opened DBF file */ |
785
|
44 |
|
private function loadDBFData($dbfFile): void |
786
|
|
|
{ |
787
|
44 |
|
$this->dbfData = @dbase_get_record_with_names($dbfFile, $this->recordNumber); |
|
|
|
|
788
|
44 |
|
unset($this->dbfData['deleted']); |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
/** @param resource $dbfFile */ |
792
|
30 |
|
private function saveDBFData($dbfFile): void |
793
|
|
|
{ |
794
|
30 |
|
if ($this->dbfData === []) { |
795
|
|
|
return; |
796
|
|
|
} |
797
|
|
|
|
798
|
30 |
|
unset($this->dbfData['deleted']); |
799
|
30 |
|
if ($this->recordNumber <= dbase_numrecords($dbfFile)) { |
|
|
|
|
800
|
|
|
if (! dbase_replace_record($dbfFile, array_values($this->dbfData), $this->recordNumber)) { |
|
|
|
|
801
|
|
|
$this->setError("I wasn't possible to update the information in the DBF file."); |
802
|
|
|
} |
803
|
30 |
|
} elseif (! dbase_add_record($dbfFile, array_values($this->dbfData))) { |
|
|
|
|
804
|
|
|
$this->setError("I wasn't possible to add the information to the DBF file."); |
805
|
|
|
} |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
/** |
809
|
|
|
* Sets error message. |
810
|
|
|
*/ |
811
|
|
|
public function setError(string $error): void |
812
|
|
|
{ |
813
|
|
|
$this->lastError = $error; |
814
|
|
|
} |
815
|
|
|
|
816
|
|
|
/** |
817
|
|
|
* Returns shape name. |
818
|
|
|
* |
819
|
|
|
* @psalm-return non-empty-string |
820
|
|
|
*/ |
821
|
4 |
|
public function getShapeName(): string |
822
|
|
|
{ |
823
|
4 |
|
return ShapeType::name($this->shapeType); |
824
|
|
|
} |
825
|
|
|
} |
826
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.