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