Viewing file: ShapeRecord.php (25.06 KB) -rwxr-x--- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
declare(strict_types=1);
/** * phpMyAdmin ShapeFile library * <https://github.com/phpmyadmin/shapefile/>. * * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net> * Copyright 2016 - 2017 Michal Čihař <michal@cihar.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you can download one from * https://www.gnu.org/copyleft/gpl.html. */
namespace PhpMyAdmin\ShapeFile;
use function array_values; use function count; use function fwrite; use function in_array; use function is_array; use function pack; use function sprintf; use function strlen;
/** * ShapeFile record class. */ class ShapeRecord { /** @var resource */ private $shpFile = null; /** @var resource */ private $dbfFile = null; /** @var ShapeFile */ private $shapeFile = null;
/** @var int */ private $size = 0; /** @var int */ private $read = 0;
/** @var int|null */ public $recordNumber = null;
/** @var int */ public $shapeType = null;
/** @var string */ public $lastError = '';
/** @var array */ public $shpData = []; /** @var array */ public $dbfData = [];
public function __construct(int $shapeType) { $this->shapeType = $shapeType; }
/** * Loads record from files. * * @param ShapeFile $shapeFile The ShapeFile object * @param resource $shpFile Opened SHP file (by reference) * @param resource $dbfFile Opened DBF file (by reference) */ public function loadFromFile(ShapeFile &$shapeFile, &$shpFile, &$dbfFile): void { $this->shapeFile = $shapeFile; $this->shpFile = $shpFile; $this->dbfFile = $dbfFile; $this->loadHeaders();
/* No header read */ if ($this->read === 0) { return; }
switch ($this->shapeType) { case 0: $this->loadNullRecord(); break; case 1: $this->loadPointRecord(); break; case 21: $this->loadPointMRecord(); break; case 11: $this->loadPointZRecord(); break; case 3: $this->loadPolyLineRecord(); break; case 23: $this->loadPolyLineMRecord(); break; case 13: $this->loadPolyLineZRecord(); break; case 5: $this->loadPolygonRecord(); break; case 25: $this->loadPolygonMRecord(); break; case 15: $this->loadPolygonZRecord(); break; case 8: $this->loadMultiPointRecord(); break; case 28: $this->loadMultiPointMRecord(); break; case 18: $this->loadMultiPointZRecord(); break; default: $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); break; }
/* We need to skip rest of the record */ while ($this->read < $this->size) { $this->loadData('V', 4); }
/* Check if we didn't read too much */ if ($this->read !== $this->size) { $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size)); }
if (! ShapeFile::supportsDbase() || ! isset($this->dbfFile)) { return; }
$this->loadDBFData(); }
/** * Saves record to files. * * @param resource $shpFile Opened SHP file * @param resource $dbfFile Opened DBF file * @param int $recordNumber Record number */ public function saveToFile(&$shpFile, &$dbfFile, int $recordNumber): void { $this->shpFile = $shpFile; $this->dbfFile = $dbfFile; $this->recordNumber = $recordNumber; $this->saveHeaders();
switch ($this->shapeType) { case 0: // Nothing to save break; case 1: $this->savePointRecord(); break; case 21: $this->savePointMRecord(); break; case 11: $this->savePointZRecord(); break; case 3: $this->savePolyLineRecord(); break; case 23: $this->savePolyLineMRecord(); break; case 13: $this->savePolyLineZRecord(); break; case 5: $this->savePolygonRecord(); break; case 25: $this->savePolygonMRecord(); break; case 15: $this->savePolygonZRecord(); break; case 8: $this->saveMultiPointRecord(); break; case 28: $this->saveMultiPointMRecord(); break; case 18: $this->saveMultiPointZRecord(); break; default: $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); break; }
if (! ShapeFile::supportsDbase() || $this->dbfFile === null) { return; }
$this->saveDBFData(); }
/** * Updates DBF data to match header. * * @param array $header DBF structure header */ public function updateDBFInfo(array $header): void { $tmp = $this->dbfData; unset($this->dbfData); $this->dbfData = []; foreach ($header as $value) { $this->dbfData[$value[0]] = $tmp[$value[0]] ?? ''; } }
/** * Reads data. * * @param string $type type for unpack() * @param int $count number of bytes * * @return mixed */ private function loadData(string $type, int $count) { $data = $this->shapeFile->readSHP($count); if ($data === false) { return false; }
$this->read += strlen($data);
return Util::loadData($type, $data); }
/** * Loads metadata header from a file. */ private function loadHeaders(): void { $this->shapeType = false; $this->recordNumber = $this->loadData('N', 4); if ($this->recordNumber === false) { return; }
// We read the length of the record $this->size = $this->loadData('N', 4); if ($this->size === false) { return; }
$this->size = ($this->size * 2) + 8; $this->shapeType = $this->loadData('V', 4); }
/** * Saves metadata header to a file. */ private function saveHeaders(): void { fwrite($this->shpFile, pack('N', $this->recordNumber)); fwrite($this->shpFile, pack('N', $this->getContentLength())); fwrite($this->shpFile, pack('V', $this->shapeType)); }
private function loadPoint(): array { $data = [];
$data['x'] = $this->loadData('d', 8); $data['y'] = $this->loadData('d', 8);
return $data; }
private function loadPointM(): array { $data = $this->loadPoint();
$data['m'] = $this->loadData('d', 8);
return $data; }
private function loadPointZ(): array { $data = $this->loadPoint();
$data['z'] = $this->loadData('d', 8); $data['m'] = $this->loadData('d', 8);
return $data; }
private function savePoint(array $data): void { fwrite($this->shpFile, Util::packDouble($data['x'])); fwrite($this->shpFile, Util::packDouble($data['y'])); }
private function savePointM(array $data): void { fwrite($this->shpFile, Util::packDouble($data['x'])); fwrite($this->shpFile, Util::packDouble($data['y'])); fwrite($this->shpFile, Util::packDouble($data['m'])); }
private function savePointZ(array $data): void { fwrite($this->shpFile, Util::packDouble($data['x'])); fwrite($this->shpFile, Util::packDouble($data['y'])); fwrite($this->shpFile, Util::packDouble($data['z'])); fwrite($this->shpFile, Util::packDouble($data['m'])); }
private function loadNullRecord(): void { $this->shpData = []; }
private function loadPointRecord(): void { $this->shpData = $this->loadPoint(); }
private function loadPointMRecord(): void { $this->shpData = $this->loadPointM(); }
private function loadPointZRecord(): void { $this->shpData = $this->loadPointZ(); }
private function savePointRecord(): void { $this->savePoint($this->shpData); }
private function savePointMRecord(): void { $this->savePointM($this->shpData); }
private function savePointZRecord(): void { $this->savePointZ($this->shpData); }
private function loadBBox(): void { $this->shpData['xmin'] = $this->loadData('d', 8); $this->shpData['ymin'] = $this->loadData('d', 8); $this->shpData['xmax'] = $this->loadData('d', 8); $this->shpData['ymax'] = $this->loadData('d', 8); }
private function loadMultiPointRecord(): void { $this->shpData = []; $this->loadBBox();
$this->shpData['numpoints'] = $this->loadData('V', 4);
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { $this->shpData['points'][] = $this->loadPoint(); } }
private function loadMultiPointMZRecord(string $type): void { /* The m dimension is optional, depends on bounding box data */ if ($type === 'm' && ! $this->shapeFile->hasMeasure()) { return; }
$this->shpData[$type . 'min'] = $this->loadData('d', 8); $this->shpData[$type . 'max'] = $this->loadData('d', 8);
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { $this->shpData['points'][$i][$type] = $this->loadData('d', 8); } }
private function loadMultiPointMRecord(): void { $this->loadMultiPointRecord();
$this->loadMultiPointMZRecord('m'); }
private function loadMultiPointZRecord(): void { $this->loadMultiPointRecord();
$this->loadMultiPointMZRecord('z'); $this->loadMultiPointMZRecord('m'); }
private function saveMultiPointRecord(): void { fwrite($this->shpFile, pack( 'dddd', $this->shpData['xmin'], $this->shpData['ymin'], $this->shpData['xmax'], $this->shpData['ymax'] ));
fwrite($this->shpFile, pack('V', $this->shpData['numpoints']));
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { $this->savePoint($this->shpData['points'][$i]); } }
private function saveMultiPointMZRecord(string $type): void { fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
for ($i = 0; $i < $this->shpData['numpoints']; ++$i) { fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type])); } }
private function saveMultiPointMRecord(): void { $this->saveMultiPointRecord();
$this->saveMultiPointMZRecord('m'); }
private function saveMultiPointZRecord(): void { $this->saveMultiPointRecord();
$this->saveMultiPointMZRecord('z'); $this->saveMultiPointMZRecord('m'); }
private function loadPolyLineRecord(): void { $this->shpData = []; $this->loadBBox();
$this->shpData['numparts'] = $this->loadData('V', 4); $this->shpData['numpoints'] = $this->loadData('V', 4);
$numparts = $this->shpData['numparts']; $numpoints = $this->shpData['numpoints'];
for ($i = 0; $i < $numparts; ++$i) { $this->shpData['parts'][$i] = $this->loadData('V', 4); }
$part = 0; for ($i = 0; $i < $numpoints; ++$i) { if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) { ++$part; }
if (! isset($this->shpData['parts'][$part]['points']) || ! is_array($this->shpData['parts'][$part]['points']) ) { $this->shpData['parts'][$part] = ['points' => []]; }
$this->shpData['parts'][$part]['points'][] = $this->loadPoint(); } }
private function loadPolyLineMZRecord(string $type): void { /* The m dimension is optional, depends on bounding box data */ if ($type === 'm' && ! $this->shapeFile->hasMeasure()) { return; }
$this->shpData[$type . 'min'] = $this->loadData('d', 8); $this->shpData[$type . 'max'] = $this->loadData('d', 8);
$numparts = $this->shpData['numparts']; $numpoints = $this->shpData['numpoints'];
$part = 0; for ($i = 0; $i < $numpoints; ++$i) { if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) { ++$part; }
$this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8); } }
private function loadPolyLineMRecord(): void { $this->loadPolyLineRecord();
$this->loadPolyLineMZRecord('m'); }
private function loadPolyLineZRecord(): void { $this->loadPolyLineRecord();
$this->loadPolyLineMZRecord('z'); $this->loadPolyLineMZRecord('m'); }
private function savePolyLineRecord(): void { fwrite($this->shpFile, pack( 'dddd', $this->shpData['xmin'], $this->shpData['ymin'], $this->shpData['xmax'], $this->shpData['ymax'] ));
fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
$partIndex = 0; for ($i = 0; $i < $this->shpData['numparts']; ++$i) { fwrite($this->shpFile, pack('V', $partIndex)); $partIndex += count($this->shpData['parts'][$i]['points']); }
foreach ($this->shpData['parts'] as $partData) { foreach ($partData['points'] as $pointData) { $this->savePoint($pointData); } } }
private function savePolyLineMZRecord(string $type): void { fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
foreach ($this->shpData['parts'] as $partData) { foreach ($partData['points'] as $pointData) { fwrite($this->shpFile, Util::packDouble($pointData[$type])); } } }
private function savePolyLineMRecord(): void { $this->savePolyLineRecord();
$this->savePolyLineMZRecord('m'); }
private function savePolyLineZRecord(): void { $this->savePolyLineRecord();
$this->savePolyLineMZRecord('z'); $this->savePolyLineMZRecord('m'); }
private function loadPolygonRecord(): void { $this->loadPolyLineRecord(); }
private function loadPolygonMRecord(): void { $this->loadPolyLineMRecord(); }
private function loadPolygonZRecord(): void { $this->loadPolyLineZRecord(); }
private function savePolygonRecord(): void { $this->savePolyLineRecord(); }
private function savePolygonMRecord(): void { $this->savePolyLineMRecord(); }
private function savePolygonZRecord(): void { $this->savePolyLineZRecord(); }
private function adjustBBox(array $point): void { // Adjusts bounding box based on point $directions = [ 'x', 'y', 'z', 'm', ]; foreach ($directions as $direction) { if (! isset($point[$direction])) { continue; }
$min = $direction . 'min'; $max = $direction . 'max'; if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) { $this->shpData[$min] = $point[$direction]; }
if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) { continue; }
$this->shpData[$max] = $point[$direction]; } }
/** * Sets dimension to 0 if not set. * * @param array $point Point to check * @param string $dimension Dimension to check * * @return array */ private function fixPoint(array $point, string $dimension): array { if (! isset($point[$dimension])) { $point[$dimension] = 0.0; // no_value }
return $point; }
/** * Adjust point and bounding box when adding point. * * @param array $point Point data * * @return array Fixed point data */ private function adjustPoint(array $point): array { $type = $this->shapeType / 10; if ($type >= 2) { $point = $this->fixPoint($point, 'm'); } elseif ($type >= 1) { $point = $this->fixPoint($point, 'z'); $point = $this->fixPoint($point, 'm'); }
return $point; }
/** * Adds point to a record. * * @param array $point Point data * @param int $partIndex Part index */ public function addPoint(array $point, int $partIndex = 0): void { $point = $this->adjustPoint($point); switch ($this->shapeType) { case 0: //Don't add anything return; case 1: case 11: case 21: //Substitutes the value of the current point $this->shpData = $point; break; case 3: case 5: case 13: case 15: case 23: case 25: //Adds a new point to the selected part $this->shpData['parts'][$partIndex]['points'][] = $point; $this->shpData['numparts'] = count($this->shpData['parts']); $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0); break; case 8: case 18: case 28: //Adds a new point $this->shpData['points'][] = $point; $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0); break; default: $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
return; }
$this->adjustBBox($point); }
/** * Deletes point from a record. * * @param int $pointIndex Point index * @param int $partIndex Part index */ public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void { switch ($this->shapeType) { case 0: //Don't delete anything break; case 1: case 11: case 21: //Sets the value of the point to zero $this->shpData['x'] = 0.0; $this->shpData['y'] = 0.0; if (in_array($this->shapeType, [11, 21])) { $this->shpData['m'] = 0.0; }
if (in_array($this->shapeType, [11])) { $this->shpData['z'] = 0.0; }
break; case 3: case 5: case 13: case 15: case 23: case 25: //Deletes the point from the selected part, if exists if (isset($this->shpData['parts'][$partIndex]) && isset($this->shpData['parts'][$partIndex]['points'][$pointIndex]) ) { $count = count($this->shpData['parts'][$partIndex]['points']) - 1; for ($i = $pointIndex; $i < $count; ++$i) { $point = $this->shpData['parts'][$partIndex]['points'][$i + 1]; $this->shpData['parts'][$partIndex]['points'][$i] = $point; }
$count = count($this->shpData['parts'][$partIndex]['points']) - 1; unset($this->shpData['parts'][$partIndex]['points'][$count]);
$this->shpData['numparts'] = count($this->shpData['parts']); --$this->shpData['numpoints']; }
break; case 8: case 18: case 28: //Deletes the point, if exists if (isset($this->shpData['points'][$pointIndex])) { $count = count($this->shpData['points']) - 1; for ($i = $pointIndex; $i < $count; ++$i) { $this->shpData['points'][$i] = $this->shpData['points'][$i + 1]; }
unset($this->shpData['points'][count($this->shpData['points']) - 1]);
--$this->shpData['numpoints']; }
break; default: $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); break; } }
/** * Returns length of content. */ public function getContentLength(): ?int { // The content length for a record is the length of the record contents section measured in 16-bit words. // one coordinate makes 4 16-bit words (64 bit double) switch ($this->shapeType) { case 0: $result = 0; break; case 1: $result = 10; break; case 21: $result = 10 + 4; break; case 11: $result = 10 + 8; break; case 3: case 5: $count = count($this->shpData['parts']); $result = 22 + 2 * $count; for ($i = 0; $i < $count; ++$i) { $result += 8 * count($this->shpData['parts'][$i]['points']); }
break; case 23: case 25: $count = count($this->shpData['parts']); $result = 22 + (2 * 4) + 2 * $count; for ($i = 0; $i < $count; ++$i) { $result += (8 + 4) * count($this->shpData['parts'][$i]['points']); }
break; case 13: case 15: $count = count($this->shpData['parts']); $result = 22 + (4 * 4) + 2 * $count; for ($i = 0; $i < $count; ++$i) { $result += (8 + 8) * count($this->shpData['parts'][$i]['points']); }
break; case 8: $result = 20 + 8 * count($this->shpData['points']); break; case 28: $result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']); break; case 18: $result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']); break; default: $result = null; $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)); break; }
return $result; }
private function loadDBFData(): void { $this->dbfData = @dbase_get_record_with_names($this->dbfFile, $this->recordNumber); unset($this->dbfData['deleted']); }
private function saveDBFData(): void { if (count($this->dbfData) === 0) { return; }
unset($this->dbfData['deleted']); if ($this->recordNumber <= dbase_numrecords($this->dbfFile)) { if (! dbase_replace_record($this->dbfFile, array_values($this->dbfData), $this->recordNumber)) { $this->setError('I wasn\'t possible to update the information in the DBF file.'); } } else { if (! dbase_add_record($this->dbfFile, array_values($this->dbfData))) { $this->setError('I wasn\'t possible to add the information to the DBF file.'); } } }
/** * Sets error message. */ public function setError(string $error): void { $this->lastError = $error; }
/** * Returns shape name. */ public function getShapeName(): string { return Util::nameShape($this->shapeType); } }
|