Alter Version

This commit is contained in:
Walter Hupfeld
2024-02-16 15:35:01 +01:00
parent 6e85cec1da
commit 0df6729f8b
393 changed files with 173746 additions and 6 deletions

628
vendor/Shapefile/Geometry/Geometry.php vendored Normal file
View File

@@ -0,0 +1,628 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* Abstract base class for all geometries.
* It defines some common public methods and some helper protected functions.
*/
abstract class Geometry
{
/**
* @var array|null Custom bounding box set with setCustomBoundingBox() method.
*/
private $custom_bounding_box = null;
/**
* @var array Data of the Geometry.
*/
private $data = [];
/**
* @var bool Flag representing whether the Geometry is empty.
*/
private $flag_empty = true;
/**
* @var bool Flag representing whether the Geometry has Z dimension.
*/
private $flag_z = false;
/**
* @var bool Flag representing whether the Geometry has M dimension.
*/
private $flag_m = false;
/**
* @var bool Flag representing whether the DBF record is deleted.
*/
private $flag_deleted = false;
/////////////////////////////// ABSTRACT ///////////////////////////////
/**
* Initialize the Geometry with a structured array.
*
* @param array $array Array structured according to Geometry type.
*
* @return self Returns $this to provide a fluent interface.
*/
abstract public function initFromArray($array);
/**
* Initialize the Geometry with WKT.
*
* @param string $wkt WKT string.
*
* @return self Returns $this to provide a fluent interface.
*/
abstract public function initFromWKT($wkt);
/**
* Initialize the Geometry with GeoJSON.
*
* @param string $geojson GeoJSON string.
*
* @return self Returns $this to provide a fluent interface.
*/
abstract public function initFromGeoJSON($geojson);
/**
* Gets the Geometry as a structured array.
*
* @return array
*/
abstract public function getArray();
/**
* Gets the Geometry as WKT.
*
* @return string
*/
abstract public function getWKT();
/**
* Gets the Geometry as GeoJSON.
*
* @param bool $flag_bbox If true include the bounding box in the GeoJSON output.
* @param bool $flag_feature If true output a GeoJSON Feature with all the data.
*
* @return string
*/
abstract public function getGeoJSON($flag_bbox = true, $flag_feature = false);
/**
* Gets Geometry bounding box.
* If a custom one is defined, it will be returned instead of a computed one.
*
* @return array Associative array with the xmin, xmax, ymin, ymax and optional zmin, zmax, mmin, mmax values.
*/
abstract public function getBoundingBox();
/**
* Gets the Shape base type of the Geometry.
* This is not intended for users, but Shapefile requires it for internal mechanisms.
*
* @internal
*
* @return int
*/
abstract public function getSHPBasetype();
/**
* Gets the WKT base type of the Geometry.
*
* @return string
*/
abstract protected function getWKTBasetype();
/**
* Gets the GeoJSON base type of the Geometry.
*
* @return string
*/
abstract protected function getGeoJSONBasetype();
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Gets the state of the Empty flag.
*
* @return bool
*/
public function isEmpty()
{
return $this->flag_empty;
}
/**
* Gets the state of the Z flag.
*
* @return bool
*/
public function isZ()
{
return $this->flag_z;
}
/**
* Gets the state of the M flag.
*
* @return bool
*/
public function isM()
{
return $this->flag_m;
}
/**
* Gets the state of the Deleted flag.
*
* @return bool
*/
public function isDeleted()
{
return $this->flag_deleted;
}
/**
* Sets the state of the Deleted flag.
*
* @param bool $value
*
* @return self Returns $this to provide a fluent interface.
*/
public function setFlagDeleted($value)
{
$this->flag_deleted = $value;
return $this;
}
/**
* Sets a custom bounding box for the Geometry.
* No check is carried out except a formal compliance of dimensions.
*
* @param array $bounding_box Associative array with the xmin, xmax, ymin, ymax and optional zmin, zmax, mmin, mmax values.
*
* @return self Returns $this to provide a fluent interface.
*/
public function setCustomBoundingBox($bounding_box)
{
$bounding_box = array_intersect_key($bounding_box, array_flip(['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax', 'mmin', 'mmax']));
if (
$this->isEmpty()
|| !isset($bounding_box['xmin'], $bounding_box['xmax'], $bounding_box['ymin'], $bounding_box['ymax'])
|| (($this->isZ() && !isset($bounding_box['zmin'], $bounding_box['zmax'])) || (!$this->isZ() && (isset($bounding_box['zmin']) || isset($bounding_box['zmax']))))
|| (($this->isM() && !isset($bounding_box['mmin'], $bounding_box['mmax'])) || (!$this->isM() && (isset($bounding_box['mmin']) || isset($bounding_box['mmax']))))
) {
throw new ShapefileException(Shapefile::ERR_GEOM_MISMATCHED_BBOX);
}
$this->custom_bounding_box = $bounding_box;
return $this;
}
/**
* Resets custom bounding box for the Geometry.
* It will cause getBoundingBox() method to return a normally computed bbox instead of a custom one.
*
* @return self Returns $this to provide a fluent interface.
*/
public function resetCustomBoundingBox()
{
$this->custom_bounding_box = null;
return $this;
}
/**
* Gets data value for speficied field name.
*
* @param string $fieldname Name of the field.
*
* @return mixed
*/
public function getData($fieldname)
{
if (!isset($this->data[$fieldname])) {
throw new ShapefileException(Shapefile::ERR_INPUT_FIELD_NOT_FOUND, $fieldname);
}
return $this->data[$fieldname];
}
/**
* Sets data value for speficied field name.
*
* @param string $fieldname Name of the field.
* @param mixed $value Value to assign to the field.
*
* @return self Returns $this to provide a fluent interface.
*/
public function setData($fieldname, $value)
{
$this->data[$fieldname] = $value;
return $this;
}
/**
* Gets an array of defined data.
*
* @return array
*/
public function getDataArray()
{
return $this->data;
}
/**
* Sets an array of data.
*
* @param array $data Associative array of values.
*
* @return self Returns $this to provide a fluent interface.
*/
public function setDataArray($data)
{
foreach ($data as $fieldname => $value) {
$this->data[$fieldname] = $value;
}
return $this;
}
/////////////////////////////// PROTECTED ///////////////////////////////
/**
* Sets the state of the Empty flag.
*
* @param bool $value
*
* @return self Returns $this to provide a fluent interface.
*/
protected function setFlagEmpty($value)
{
$this->flag_empty = $value;
return $this;
}
/**
* Sets the state of the Z flag.
*
* @param bool $value
*
* @return self Returns $this to provide a fluent interface.
*/
protected function setFlagZ($value)
{
$this->flag_z = $value;
return $this;
}
/**
* Sets the state of the M flag.
*
* @param bool $value
*
* @return self Returns $this to provide a fluent interface.
*/
protected function setFlagM($value)
{
$this->flag_m = $value;
return $this;
}
/**
* Checks if the Geometry has been initialized (it is not empty) and if YES throws an exception.
*
* @return self Returns $this to provide a fluent interface.
*/
protected function checkInit()
{
if (!$this->isEmpty()) {
throw new ShapefileException(Shapefile::ERR_GEOM_NOT_EMPTY);
}
return $this;
}
/**
* Gets the custom bounding box.
*
* @return array
*
* @return self Returns $this to provide a fluent interface.
*/
protected function getCustomBoundingBox()
{
return $this->custom_bounding_box;
return $this;
}
/**
* Sanitize WKT.
* It attempts to sanitize user-provided WKT and throws an exception if it appears to be invalid.
*
* @param string $wkt The WKT to sanitize.
*
* @return string Sanitized WKT.
*/
protected function wktSanitize($wkt)
{
// Normalize whitespaces
$wkt = strtoupper(preg_replace('/\s+/', ' ', trim($wkt)));
// Normalize commas
$wkt = str_replace(array(', ', ' ,'), ',', $wkt);
// Check basetype
if (substr($wkt, 0, strlen($this->getWKTBasetype())) != strtoupper($this->getWKTBasetype())) {
throw new ShapefileException(Shapefile::ERR_INPUT_WKT_NOT_VALID);
}
return $wkt;
}
/**
* Checks if WKT represents an empty Geometry.
*
* @param string $wkt
*
* @return bool
*/
protected function wktIsEmpty($wkt)
{
return substr($wkt, -5) == 'EMPTY';
}
/**
* Checks if WKT represents a Geometry that has a Z dimension.
*
* @param string $wkt The whole sanitized WKT.
*
* @return bool
*/
protected function wktIsZ($wkt)
{
return strpos(trim(substr($wkt, strlen($this->getWKTBasetype()), 3)), 'Z') !== false;
}
/**
* Checks if WKT represents a Geometry that has a M dimension.
*
* @param string $wkt The whole sanitized WKT.
*
* @return bool
*/
protected function wktIsM($wkt)
{
return strpos(trim(substr($wkt, strlen($this->getWKTBasetype()), 3)), 'M') !== false;
}
/**
* Extracts data from WKT.
*
* @param string $wkt The whole sanitized WKT.
*
* @return string
*/
protected function wktExtractData($wkt)
{
if ($this->wktIsEmpty($wkt)) {
return null;
}
$begin = strpos($wkt, '(');
if ($begin === false) {
throw new ShapefileException(ERR_INPUT_WKT_NOT_VALID);
}
$end = strrpos($wkt, ')');
if ($end === false) {
throw new ShapefileException(ERR_INPUT_WKT_NOT_VALID);
}
return trim(substr($wkt, $begin + 1, $end - $begin - 1));
}
/**
* Parse a group of WKT coordinates into an associative array.
* Refer to parseCoordinatesArray() method for output details.
*
* @param string $coordinates_string The WKT coordinates group.
* @param bool $force_z Flag to enforce the presence of Z dimension.
* @param bool $force_m Flag to enforce the presence of M dimension.
*
* @return array
*/
protected function wktParseCoordinates($coordinates_string, $force_z, $force_m)
{
return $this->parseCoordinatesArray(explode(' ', trim($coordinates_string)), $force_z, $force_m, Shapefile::ERR_INPUT_WKT_NOT_VALID);
}
/**
* Returns an initialized WKT according to the Geometry properties.
*
* @return string
*/
protected function wktInitializeOutput()
{
$ret = $this->getWKTBasetype();
if ($this->isEmpty()) {
$ret .= ' EMPTY';
} else {
$ret .= ($this->isZ() ? 'Z' : '') . ($this->isM() ? 'M' : '');
}
return $ret;
}
/**
* Return sanitized GeoJSON, keeping just the geometry part.
* It attempts to sanitize user-provided GeoJSON and throws an exception if it appears to be invalid.
*
* @param string $geojson The GeoJSON to sanitize.
*
* @return string
*/
protected function geojsonSanitize($geojson)
{
$geojson = json_decode($geojson, true);
// If it is null it means "an empty Geometry"
if ($geojson === null) {
return null;
}
// If it is a Feature just keep the Geometry part
if (isset($geojson['geometry'])) {
$geojson = $geojson['geometry'];
}
// Check if "type" and "coordinates" are defined
if (!isset($geojson['type'], $geojson['coordinates'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_GEOJSON_NOT_VALID);
}
// Check if "type" is consistent with current Geometry
if (substr(strtoupper(trim($geojson['type'])), 0, strlen($this->getGeoJSONBasetype())) != strtoupper($this->getGeoJSONBasetype())) {
throw new ShapefileException(Shapefile::ERR_INPUT_GEOJSON_NOT_VALID);
}
return $geojson;
}
/**
* Checks if GeoJSON type represents a Geometry that has a M dimension.
*
* @param string $type The type speficied in the GeoJSON.
*
* @return bool
*/
protected function geojsonIsM($type)
{
return substr($type, -1) == 'M';
}
/**
* Parse an array of GeoJSON coordinates into an associative array.
* Refer to parseCoordinatesArray() method for output details.
*
* @param string $coordinates_array GeoJSON coordinates array.
* @param bool $force_m Flag to enforce the presence of M dimension.
*
* @return array
*/
protected function geojsonParseCoordinates($coordinates_array, $force_m)
{
return $this->parseCoordinatesArray($coordinates_array, false, $force_m, Shapefile::ERR_INPUT_GEOJSON_NOT_VALID);
}
/**
* Builds valid GeoJSON starting from raw coordinates.
*
* @param array $coordinates GeoJSON coordinates array.
* @param bool $flag_bbox If true include the bounding box in the GeoJSON output.
* @param bool $flag_feature If true output a GeoJSON Feature with all the data.
*
* @return array
*/
protected function geojsonPackOutput($coordinates, $flag_bbox, $flag_feature)
{
$ret = [];
// Type
$ret['type'] = $this->getGeoJSONBasetype() . ($this->isM() ? 'M' : '');
// Bounding box
if ($flag_bbox) {
$ret['bbox'] = [];
$bbox = $this->getBoundingBox();
$ret['bbox'][] = $bbox['xmin'];
$ret['bbox'][] = $bbox['ymin'];
if ($this->isZ()) {
$ret['bbox'][] = $bbox['zmin'];
}
if ($this->isM()) {
$ret['bbox'][] = $bbox['mmin'];
}
$ret['bbox'][] = $bbox['xmax'];
$ret['bbox'][] = $bbox['ymax'];
if ($this->isZ()) {
$ret['bbox'][] = $bbox['zmax'];
}
if ($this->isM()) {
$ret['bbox'][] = $bbox['mmax'];
}
}
// Coordinates
$ret['coordinates'] = $coordinates;
// Feature
if ($flag_feature) {
$ret = [
'type' => 'Feature',
'geometry' => $ret,
'properties' => $this->data,
];
}
return json_encode($ret);
}
/////////////////////////////// PRIVATE ///////////////////////////////
/**
* Parses an indexed array of coordinates and returns an associative one in the form of:
* [
* "x" => float
* "y" => float
* "z" => float|null
* "m" => float|null
* ]
*
* @param float[] $coordinates The indexed array of coordinates to parse.
* @param bool $force_z Flag to enforce the presence of Z dimension.
* @param bool $force_m Flag to enforce the presence of M dimension.
* @param int $err_code Error code to throw an exception in case of invalid input.
*
* @return array
*/
private function parseCoordinatesArray($coordinates, $force_z, $force_m, $err_code)
{
$count = count($coordinates);
if (
$count < 2 ||
(($force_z || $force_m) && $count < 3) ||
($force_z && $force_m && $count < 4) ||
$count > 4
) {
throw new ShapefileException($err_code);
}
$ret = [
'x' => $coordinates[0],
'y' => $coordinates[1],
'z' => null,
'm' => null,
];
if ($count == 3) {
if ($force_m) {
$ret['m'] = $coordinates[2];
} else {
$ret['z'] = $coordinates[2];
}
}
if ($count == 4) {
$ret['z'] = $coordinates[2];
$ret['m'] = $coordinates[3];
}
return $ret;
}
}

View File

@@ -0,0 +1,182 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* Abstract base class for all Geometry Collections.
* It defines some common public methods and some helper protected functions.
*/
abstract class GeometryCollection extends Geometry
{
/**
* @var \Shapefile\Geometry\Geometry[] The actual geometries in the collection.
* They are enforced to be all of the same type by addGeometry() method.
*/
protected $geometries = [];
/////////////////////////////// ABSTRACT ///////////////////////////////
/**
* Gets the class name of the base geometries in the collection.
*
* @return string
*/
abstract protected function getCollectionClass();
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Constructor.
*
* @param \Shapefile\Geometry\Geometry[] $geometries Optional array of geometries to initialize the collection.
*/
public function __construct(array $geometries = null)
{
if ($geometries !== null) {
foreach ($geometries as $Geometry) {
$this->addGeometry($Geometry);
}
}
}
public function getBoundingBox()
{
if ($this->isEmpty()) {
return null;
}
$ret = $this->getCustomBoundingBox();
if (!$ret) {
$is_z = $this->isZ();
$is_m = $this->isM();
foreach ($this->geometries as $Geometry) {
$bbox = $Geometry->getBoundingBox();
if (!$ret) {
$ret = $bbox;
} elseif ($bbox) {
if ($bbox['xmin'] < $ret['xmin']) {
$ret['xmin'] = $bbox['xmin'];
}
if ($bbox['xmax'] > $ret['xmax']) {
$ret['xmax'] = $bbox['xmax'];
}
if ($bbox['ymin'] < $ret['ymin']) {
$ret['ymin'] = $bbox['ymin'];
}
if ($bbox['ymax'] > $ret['ymax']) {
$ret['ymax'] = $bbox['ymax'];
}
if ($is_z) {
if ($bbox['zmin'] < $ret['zmin']) {
$ret['zmin'] = $bbox['zmin'];
}
if ($bbox['zmax'] > $ret['zmax']) {
$ret['zmax'] = $bbox['zmax'];
}
}
if ($is_m) {
if ($ret['mmin'] === false || $bbox['mmin'] < $ret['mmin']) {
$ret['mmin'] = $bbox['mmin'];
}
if ($ret['mmax'] === false || $bbox['mmax'] > $ret['mmax']) {
$ret['mmax'] = $bbox['mmax'];
}
}
}
}
}
return $ret;
}
/////////////////////////////// PROTECTED ///////////////////////////////
/**
* Adds a Geometry to the collection.
* It enforces all geometries to be of the same type.
*
* @param \Shapefile\Geometry\Geometry $Geometry
*
* @return self Returns $this to provide a fluent interface.
*/
protected function addGeometry(Geometry $Geometry)
{
if (!is_a($Geometry, $this->getCollectionClass())) {
throw new ShapefileException(Shapefile::ERR_INPUT_GEOMETRY_TYPE_NOT_VALID, $this->getCollectionClass());
}
if (!$Geometry->isEmpty()) {
if ($this->isEmpty()) {
$this->setFlagEmpty(false);
$this->setFlagZ($Geometry->isZ());
$this->setFlagM($Geometry->isM());
} else {
if ($this->isZ() !== $Geometry->isZ() || $this->isM() !== $Geometry->isM()) {
throw new ShapefileException(Shapefile::ERR_GEOM_MISMATCHED_DIMENSIONS);
}
}
$this->geometries[] = $Geometry;
}
return $this;
}
/**
* Gets a Geometry at specified index from the collection.
*
* @param int $index The index of the Geometry.
*
* @return \Shapefile\Geometry\Geometry
*/
protected function getGeometry($index)
{
if (!isset($this->geometries[$index])) {
throw new ShapefileException(Shapefile::ERR_INPUT_GEOMETRY_INDEX_NOT_VALID, $index);
}
return $this->geometries[$index];
}
/**
* Gets all the geometries in the collection.
*
* @return \Shapefile\Geometry\Geometry[]
*/
protected function getGeometries()
{
return $this->geometries;
}
/**
* Gets the number of geometries in the collection.
*
* @return int
*/
protected function getNumGeometries()
{
return count($this->geometries);
}
/**
* Reverses the order of geometries in the collection.
*
* @return self Returns $this to provide a fluent interface.
*/
protected function reverseGeometries()
{
$this->geometries = array_reverse($this->geometries);
return $this;
}
}

218
vendor/Shapefile/Geometry/Linestring.php vendored Normal file
View File

@@ -0,0 +1,218 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* Linestring Geometry.
*
* - Array: [
* [numpoints] => int
* [points] => [
* [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
* ]
* ]
*
* - WKT:
* LINESTRING [Z][M] (x y z m, x y z m)
*
* - GeoJSON:
* {
* "type": "LineString" / "LineStringM"
* "coordinates": [
* [x, y, z] / [x, y, m] / [x, y, z, m]
* ]
* }
*/
class Linestring extends MultiPoint
{
/**
* WKT and GeoJSON basetypes, collection class type
*/
const WKT_BASETYPE = 'LINESTRING';
const GEOJSON_BASETYPE = 'LineString';
const COLLECTION_CLASS = 'Point';
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Checks whether the linestring is a closed ring or not.
* A closed ring has at least 4 vertices and the first and last ones must be the same.
*
* @return bool
*/
public function isClosedRing()
{
return $this->getNumPoints() >= 4 && $this->getPoint(0) == $this->getPoint($this->getNumPoints() - 1);
}
/**
* Forces the linestring to be a closed ring.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClosedRing()
{
if (!$this->checkRingNumPoints()) {
throw new ShapefileException(Shapefile::ERR_GEOM_RING_NOT_ENOUGH_VERTICES);
}
if (!$this->isClosedRing()) {
$this->addPoint($this->getPoint(0));
}
return $this;
}
/**
* Checks whether a ring is clockwise or not (it works with open rings too).
*
* Throws an exception if ring area is too small and cannot determine its orientation.
* Returns Shapefile::UNDEFINED or throw an exception if there are not enough points.
*
* @param bool $flag_throw_exception Optional flag to throw an exception if there are not enough points.
*
* @return bool|Shapefile::UNDEFINED
*/
public function isClockwise($flag_throw_exception = false)
{
if ($this->isEmpty()) {
return Shapefile::UNDEFINED;
}
if (!$this->checkRingNumPoints()) {
if ($flag_throw_exception) {
throw new ShapefileException(Shapefile::ERR_GEOM_RING_NOT_ENOUGH_VERTICES);
}
return Shapefile::UNDEFINED;
}
$area = $this->computeGaussArea($this->getArray()['points']);
if (!$area) {
throw new ShapefileException(Shapefile::ERR_GEOM_RING_AREA_TOO_SMALL);
}
// Negative area means clockwise direction
return $area < 0;
}
/**
* Forces the ring to be in clockwise direction (it works with open rings too).
* Throws an exception if direction is undefined.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClockwise()
{
if ($this->isClockwise(true) === false) {
$this->reverseGeometries();
}
return $this;
}
/**
* Forces the ring to be in counterclockwise direction (it works with open rings too).
* Throws an exception if direction is undefined.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceCounterClockwise()
{
if ($this->isClockwise(true) === true) {
$this->reverseGeometries();
}
return $this;
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_POLYLINE;
}
/////////////////////////////// PROTECTED ///////////////////////////////
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
protected function getCollectionClass()
{
return __NAMESPACE__ . '\\' . static::COLLECTION_CLASS;
}
/////////////////////////////// PRIVATE ///////////////////////////////
/**
* Checks if the linestring has enough points to be a ring.
*/
private function checkRingNumPoints()
{
return $this->getNumPoints() >= 3;
}
/**
* Computes ring area using a Gauss-like formula.
* The target is to determine whether it is positive or negative, not the exact area.
*
* An optional $exp parameter is used to deal with very small areas.
*
* @param array $points Array of points. Each element must have "x" and "y" members.
* @param int $exp Optional exponent to deal with small areas (coefficient = 10^exponent).
*
* @return float
*/
private function computeGaussArea($points, $exp = 0)
{
// If a coefficient of 10^9 is not enough, give up!
if ($exp > 9) {
return 0;
}
$coef = pow(10, $exp);
// At least 3 points (in case of an open ring) are needed to compute the area
$num_points = count($points);
if ($num_points < 3) {
return 0;
}
// Use Gauss's area formula (no need to be strict here, hence no 1/2 coefficient and no check for closed rings)
$num_points--;
$tot = 0;
for ($i = 0; $i < $num_points; ++$i) {
$tot += ($coef * $points[$i]['x'] * $points[$i + 1]['y']) - ($coef * $points[$i]['y'] * $points[$i + 1]['x']);
}
$tot += ($coef * $points[$num_points]['x'] * $points[0]['y']) - ($coef * $points[$num_points]['y'] * $points[0]['x']);
// If area is too small, increase coefficient exponent and retry
if ($tot == 0) {
return $this->computeGaussArea($points, $exp + 3);
}
return $tot;
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* MultiLinestring Geometry.
*
* - Array: [
* [numparts] => int
* [parts] => [
* [
* [numpoints] => int
* [points] => [
* [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
* ]
* ]
* ]
* ]
*
* - WKT:
* MULTILINESTRING [Z][M] ((x y z m, x y z m, x y z m), (x y z m, x y z m))
*
* - GeoJSON:
* {
* "type": "MultiLineString" / "MultiLineStringM"
* "coordinates": [
* [
* [x, y, z] / [x, y, m] / [x, y, z, m]
* ]
* ]
* }
*/
class MultiLinestring extends GeometryCollection
{
/**
* WKT and GeoJSON basetypes, collection class type
*/
const WKT_BASETYPE = 'MULTILINESTRING';
const GEOJSON_BASETYPE = 'MultiLineString';
const COLLECTION_CLASS = 'Linestring';
/////////////////////////////// PUBLIC ///////////////////////////////
public function initFromArray($array)
{
$this->checkInit();
if (!isset($array['parts']) || !is_array($array['parts'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
foreach ($array['parts'] as $part) {
if (!isset($part['points']) || !is_array($part['points'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
$Linestring = new Linestring();
foreach ($part['points'] as $coordinates) {
$Point = new Point();
$Point->initFromArray($coordinates);
$Linestring->addPoint($Point);
}
$this->addLinestring($Linestring);
}
return $this;
}
public function initFromWKT($wkt)
{
$this->checkInit();
$wkt = $this->wktSanitize($wkt);
if (!$this->wktIsEmpty($wkt)) {
$force_z = $this->wktIsZ($wkt);
$force_m = $this->wktIsM($wkt);
foreach (explode('),(', substr($this->wktExtractData($wkt), 1, -1)) as $part) {
$Linestring = new Linestring();
foreach (explode(',', $part) as $wkt_coordinates) {
$coordinates = $this->wktParseCoordinates($wkt_coordinates, $force_z, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$Linestring->addPoint($Point);
}
$this->addLinestring($Linestring);
}
}
return $this;
}
public function initFromGeoJSON($geojson)
{
$this->checkInit();
$geojson = $this->geojsonSanitize($geojson);
if ($geojson !== null) {
$force_m = $this->geojsonIsM($geojson['type']);
foreach ($geojson['coordinates'] as $part) {
$Linestring = new Linestring();
foreach ($part as $geojson_coordinates) {
$coordinates = $this->geojsonParseCoordinates($geojson_coordinates, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$Linestring->addPoint($Point);
}
$this->addLinestring($Linestring);
}
}
return $this;
}
public function getArray()
{
$parts = [];
foreach ($this->getLinestrings() as $Linestring) {
$parts[] = $Linestring->getArray();
}
return [
'numparts' => $this->getNumGeometries(),
'parts' => $parts,
];
}
public function getWKT()
{
$ret = $this->wktInitializeOutput();
if (!$this->isEmpty()) {
$parts = [];
foreach ($this->getLinestrings() as $Linestring) {
$points = [];
foreach ($Linestring->getPoints() as $Point) {
$points[] = implode(' ', $Point->getRawArray());
}
$parts[] = '(' . implode(', ', $points) . ')';
}
$ret .= '(' . implode(', ', $parts) . ')';
}
return $ret;
}
public function getGeoJSON($flag_bbox = true, $flag_feature = false)
{
if ($this->isEmpty()) {
return 'null';
}
$coordinates = [];
foreach ($this->getLinestrings() as $Linestring) {
$parts = [];
foreach ($Linestring->getPoints() as $Point) {
$parts[] = $Point->getRawArray();
}
$coordinates[] = $parts;
}
return $this->geojsonPackOutput($coordinates, $flag_bbox, $flag_feature);
}
/**
* Adds a linestring to the collection.
*
* @param \Shapefile\Geometry\Linestring $Linestring
*
* @return self Returns $this to provide a fluent interface.
*/
public function addLinestring(Linestring $Linestring)
{
$this->addGeometry($Linestring);
return $this;
}
/**
* Gets a linestring at specified index from the collection.
*
* @param int $index The index of the linestring.
*
* @return \Shapefile\Geometry\Linestring
*/
public function getLinestring($index)
{
return $this->getGeometry($index);
}
/**
* Gets all the linestrings in the collection.
*
* @return \Shapefile\Geometry\Linestring[]
*/
public function getLinestrings()
{
return $this->getGeometries();
}
/**
* Gets the number of linestrings in the collection.
*
* @return int
*/
public function getNumLinestrings()
{
return $this->getNumGeometries();
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_POLYLINE;
}
/////////////////////////////// PROTECTED ///////////////////////////////
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
protected function getCollectionClass()
{
return __NAMESPACE__ . '\\' . static::COLLECTION_CLASS;
}
}

207
vendor/Shapefile/Geometry/MultiPoint.php vendored Normal file
View File

@@ -0,0 +1,207 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* MultiPoint Geometry.
*
* - Array: [
* [numpoints] => int
* [points] => [
* [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
* ]
* ]
*
* - WKT:
* MULTIPOINT [Z][M] (x y z m, x y z m)
* N.B.: Points coordinates may be enclosed in additional brackets: MULTIPOINT ((x y z m), (x y z m))
*
* - GeoJSON:
* {
* "type": "MultiPoint" / "MultiPointM"
* "coordinates": [
* [x, y, z] / [x, y, m] / [x, y, z, m]
* ]
* }
*/
class MultiPoint extends GeometryCollection
{
/**
* WKT and GeoJSON basetypes, collection class type
*/
const WKT_BASETYPE = 'MULTIPOINT';
const GEOJSON_BASETYPE = 'MultiPoint';
const COLLECTION_CLASS = 'Point';
/////////////////////////////// PUBLIC ///////////////////////////////
public function initFromArray($array)
{
$this->checkInit();
if (!isset($array['points']) || !is_array($array['points'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
foreach ($array['points'] as $coordinates) {
$Point = new Point();
$Point->initFromArray($coordinates);
$this->addPoint($Point);
}
return $this;
}
public function initFromWKT($wkt)
{
$this->checkInit();
$wkt = $this->wktSanitize($wkt);
if (!$this->wktIsEmpty($wkt)) {
$force_z = $this->wktIsZ($wkt);
$force_m = $this->wktIsM($wkt);
foreach (explode(',', str_replace(array('(', ')'), '', $this->wktExtractData($wkt))) as $wkt_coordinates) {
$coordinates = $this->wktParseCoordinates($wkt_coordinates, $force_z, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$this->addPoint($Point);
}
}
return $this;
}
public function initFromGeoJSON($geojson)
{
$this->checkInit();
$geojson = $this->geojsonSanitize($geojson);
if ($geojson !== null) {
$force_m = $this->geojsonIsM($geojson['type']);
foreach ($geojson['coordinates'] as $geojson_coordinates) {
$coordinates = $this->geojsonParseCoordinates($geojson_coordinates, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$this->addPoint($Point);
}
}
return $this;
}
public function getArray()
{
$points = [];
foreach ($this->getPoints() as $Point) {
$points[] = $Point->getArray();
}
return [
'numpoints' => $this->getNumGeometries(),
'points' => $points,
];
}
public function getWKT()
{
$ret = $this->wktInitializeOutput();
if (!$this->isEmpty()) {
$points = [];
foreach ($this->getPoints() as $Point) {
$points[] = implode(' ', $Point->getRawArray());
}
$ret .= '(' . implode(', ', $points) . ')';
}
return $ret;
}
public function getGeoJSON($flag_bbox = true, $flag_feature = false)
{
if ($this->isEmpty()) {
return 'null';
}
$coordinates = [];
foreach ($this->getPoints() as $Point) {
$coordinates[] = $Point->getRawArray();
}
return $this->geojsonPackOutput($coordinates, $flag_bbox, $flag_feature);
}
/**
* Adds a point to the collection.
*
* @param \Shapefile\Geometry\Point $Point
*
* @return self Returns $this to provide a fluent interface.
*/
public function addPoint(Point $Point)
{
$this->addGeometry($Point);
return $this;
}
/**
* Gets a point at specified index from the collection.
*
* @param int $index The index of the point.
*
* @return \Shapefile\Geometry\Point
*/
public function getPoint($index)
{
return $this->getGeometry($index);
}
/**
* Gets all the points in the collection.
*
* @return \Shapefile\Geometry\Point[]
*/
public function getPoints()
{
return $this->getGeometries();
}
/**
* Gets the number of points in the collection.
*
* @return int
*/
public function getNumPoints()
{
return $this->getNumGeometries();
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_MULTIPOINT;
}
/////////////////////////////// PROTECTED ///////////////////////////////
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
protected function getCollectionClass()
{
return __NAMESPACE__ . '\\' . static::COLLECTION_CLASS;
}
}

View File

@@ -0,0 +1,435 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* MultiPolygon Geometry.
*
* - Array: [
* [numparts] => int
* [parts] => [
* [
* [numrings] => int
* [rings] => [
* [
* [numpoints] => int
* [points] => [
* [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
* ]
* ]
* ]
* ]
* ]
* ]
*
* - WKT:
* MULTIPOLYGON [Z][M] (((x y z m, x y z m, x y z m, x y z m), (x y z m, x y z m, x y z m)), ((x y z m, x y z m, x y z m, x y z m), (x y z m, x y z m, x y z m)))
*
* - GeoJSON:
* {
* "type": "MultiPolygon" / "MultiPolygonM"
* "coordinates": [
* [
* [
* [x, y, z] / [x, y, m] / [x, y, z, m]
* ]
* ]
* ]
* }
*/
class MultiPolygon extends GeometryCollection
{
/**
* WKT and GeoJSON basetypes, collection class type
*/
const WKT_BASETYPE = 'MULTIPOLYGON';
const GEOJSON_BASETYPE = 'MultiPolygon';
const COLLECTION_CLASS = 'Polygon';
/**
* @var int Action to perform on polygons rings.
*/
private $closed_rings;
/**
* @var int Orientation to force for polygons rings.
*/
private $force_orientation;
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Constructor.
*
* @param \Shapefile\Geometry\Polygon[] $polygons Optional array of polygons to initialize the multipolygon.
* @param int $closed_rings Optional action to perform on polygons rings. Possible values:
* - Shapefile::ACTION_IGNORE
* - Shapefile::ACTION_CHECK
* - Shapefile::ACTION_FORCE
* @param int $force_orientation Optional orientation to force for polygons rings. Possible values:
* - Shapefile::ORIENTATION_CLOCKWISE
* - Shapefile::ORIENTATION_COUNTERCLOCKWISE
* - Shapefile::ORIENTATION_UNCHANGED
*/
public function __construct(array $polygons = null, $closed_rings = Shapefile::ACTION_CHECK, $force_orientation = Shapefile::ORIENTATION_COUNTERCLOCKWISE)
{
$this->closed_rings = $closed_rings;
$this->force_orientation = $force_orientation;
parent::__construct($polygons);
}
public function initFromArray($array)
{
$this->checkInit();
if (!isset($array['parts']) || !is_array($array['parts'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
foreach ($array['parts'] as $part) {
if (!isset($part['rings']) || !is_array($part['rings'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
$Polygon = new Polygon(null, $this->closed_rings, $this->force_orientation);
foreach ($part['rings'] as $part) {
if (!isset($part['points']) || !is_array($part['points'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
$Linestring = new Linestring();
foreach ($part['points'] as $coordinates) {
$Point = new Point();
$Point->initFromArray($coordinates);
$Linestring->addPoint($Point);
}
$Polygon->addLinestring($Linestring);
}
$this->addGeometry($Polygon, false);
}
return $this;
}
public function initFromWKT($wkt)
{
$this->checkInit();
$wkt = $this->wktSanitize($wkt);
if (!$this->wktIsEmpty($wkt)) {
$force_z = $this->wktIsZ($wkt);
$force_m = $this->wktIsM($wkt);
foreach (explode(')),((', substr($this->wktExtractData($wkt), 2, -2)) as $part) {
$Polygon = new Polygon(null, $this->closed_rings, $this->force_orientation);
foreach (explode('),(', $part) as $ring) {
$Linestring = new Linestring();
foreach (explode(',', $ring) as $wkt_coordinates) {
$coordinates = $this->wktParseCoordinates($wkt_coordinates, $force_z, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$Linestring->addPoint($Point);
}
$Polygon->addLinestring($Linestring);
}
$this->addGeometry($Polygon, false);
}
}
return $this;
}
public function initFromGeoJSON($geojson)
{
$this->checkInit();
$geojson = $this->geojsonSanitize($geojson);
if ($geojson !== null) {
$force_m = $this->geojsonIsM($geojson['type']);
foreach ($geojson['coordinates'] as $part) {
$Polygon = new Polygon(null, $this->closed_rings, $this->force_orientation);
foreach ($part as $ring) {
$Linestring = new Linestring();
foreach ($ring as $geojson_coordinates) {
$coordinates = $this->geojsonParseCoordinates($geojson_coordinates, $force_m);
$Point = new Point($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
$Linestring->addPoint($Point);
}
$Polygon->addLinestring($Linestring);
}
$this->addGeometry($Polygon, false);
}
}
return $this;
}
public function getArray()
{
$parts = [];
foreach ($this->getPolygons() as $Polygon) {
$parts[] = $Polygon->getArray();
}
return [
'numparts' => $this->getNumGeometries(),
'parts' => $parts,
];
}
public function getWKT()
{
$ret = $this->wktInitializeOutput();
if (!$this->isEmpty()) {
$parts = [];
foreach ($this->getPolygons() as $Polygon) {
$rings = [];
foreach ($Polygon->getLinestrings() as $Linestring) {
$points = [];
foreach ($Linestring->getPoints() as $Point) {
$points[] = implode(' ', $Point->getRawArray());
}
$rings[] = '(' . implode(', ', $points) . ')';
}
$parts[] = '(' . implode(', ', $rings) . ')';
}
$ret .= '(' . implode(', ', $parts) . ')';
}
return $ret;
}
public function getGeoJSON($flag_bbox = true, $flag_feature = false)
{
if ($this->isEmpty()) {
return 'null';
}
$coordinates = [];
foreach ($this->getPolygons() as $Polygon) {
$parts = [];
foreach ($Polygon->getLinestrings() as $Linestring) {
$rings = [];
foreach ($Linestring->getPoints() as $Point) {
$rings[] = $Point->getRawArray();
}
$parts[] = $rings;
}
$coordinates[] = $parts;
}
return $this->geojsonPackOutput($coordinates, $flag_bbox, $flag_feature);
}
/**
* Adds a polygon to the collection.
*
* @param \Shapefile\Geometry\Polygon $Polygon
*
* @return self Returns $this to provide a fluent interface.
*/
public function addPolygon(Polygon $Polygon)
{
$this->addGeometry($Polygon, true);
return $this;
}
/**
* Gets a polygon at specified index from the collection.
*
* @param int $index The index of the polygon.
*
* @return \Shapefile\Geometry\Polygon
*/
public function getPolygon($index)
{
return $this->getGeometry($index);
}
/**
* Gets all the polygons in the collection.
*
* @return \Shapefile\Geometry\Polygon[]
*/
public function getPolygons()
{
return $this->getGeometries();
}
/**
* Gets the number of polygons in the collection.
*
* @return int
*/
public function getNumPolygons()
{
return $this->getNumGeometries();
}
/**
* Forces multipolygon rings to be closed.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClosedRings()
{
foreach ($this->getPolygons() as $Polygon) {
$Polygon->forceClosedRings();
}
return $this;
}
/**
* Checks whether all multipolygon outer rings have a clockwise orientation and all the inner rings have a counterclockwise one.
* Note that a false return value does not guarantee multipolygon is strictly counterclockwise. Use MultiPolygon::forceCounterClockwise() to enforce that!
*
* Returns Shapefile::UNDEFINED if geometry is empty.
*
* @return bool|Shapefile::UNDEFINED
*/
public function isClockwise()
{
if ($this->isEmpty()) {
return Shapefile::UNDEFINED;
}
foreach ($this->getPolygons() as $Polygon) {
if ($Polygon->getOuterRing()->isClockwise(true) === false) {
return false;
}
foreach ($Polygon->getInnerRings() as $Linestring) {
if ($Linestring->isClockwise(true) === true) {
return false;
}
}
}
return true;
}
/**
* Checks whether all multipolygon outer rings have a counterclockwise orientation and all the inner rings have a clockwise one.
* Note that a false return value does not guarantee multipolygon is strictly clockwise. Use MultiPolygon::forceClockwise() to enforce that!
*
* Returns Shapefile::UNDEFINED if geometry is empty.
*
* @return bool|Shapefile::UNDEFINED
*/
public function isCounterClockwise()
{
if ($this->isEmpty()) {
return Shapefile::UNDEFINED;
}
foreach ($this->getPolygons() as $Polygon) {
if ($Polygon->getOuterRing()->isClockwise(true) === true) {
return false;
}
foreach ($Polygon->getInnerRings() as $Linestring) {
if ($Linestring->isClockwise(true) === false) {
return false;
}
}
}
return true;
}
/**
* Forces all multipolygon outer rings to have a clockwise orientation and all the inner rings to have a counterclockwise one.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClockwise()
{
foreach ($this->getPolygons() as $Polygon) {
$Polygon->getOuterRing()->forceClockwise();
foreach ($Polygon->getInnerRings() as $Linestring) {
$Linestring->forceCounterClockwise();
}
}
return $this;
}
/**
* Forces all multipolygon outer rings to have a counterclockwise orientation and all the inner rings to have a clockwise one.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceCounterClockwise()
{
foreach ($this->getPolygons() as $Polygon) {
$Polygon->getOuterRing()->forceCounterClockwise();
foreach ($Polygon->getInnerRings() as $Linestring) {
$Linestring->forceClockwise();
}
}
return $this;
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_POLYGON;
}
/////////////////////////////// PROTECTED ///////////////////////////////
/**
* Enforces class-wide action and orientation for polygons rings.
*
* @param \Shapefile\Geometry\Geometry $Polygon
* @param bool $flag_rings_and_orientation Optionally enforce class action and orientation for rings.
*
* @return self Returns $this to provide a fluent interface.
*/
protected function addGeometry(Geometry $Polygon, $flag_rings_and_orientation = true)
{
parent::addGeometry($Polygon);
if ($flag_rings_and_orientation && ($this->closed_rings != Shapefile::ACTION_IGNORE || $this->force_orientation != Shapefile::ORIENTATION_UNCHANGED)) {
foreach ($Polygon->getRings() as $i => $Linestring) {
// Closed rings
if ($this->closed_rings == Shapefile::ACTION_FORCE) {
$Linestring->forceClosedRing();
} elseif ($this->closed_rings == Shapefile::ACTION_CHECK && !$Linestring->isClosedRing()) {
throw new ShapefileException(Shapefile::ERR_GEOM_POLYGON_OPEN_RING);
}
// Orientation
if ($this->force_orientation == Shapefile::ORIENTATION_CLOCKWISE) {
$Linestring->{($i == 0) ? 'forceClockwise' : 'forceCounterClockwise'}();
} elseif ($this->force_orientation == Shapefile::ORIENTATION_COUNTERCLOCKWISE) {
$Linestring->{($i == 0) ? 'forceCounterClockwise' : 'forceClockwise'}();
}
}
}
return $this;
}
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
protected function getCollectionClass()
{
return __NAMESPACE__ . '\\' . static::COLLECTION_CLASS;
}
}

312
vendor/Shapefile/Geometry/Point.php vendored Normal file
View File

@@ -0,0 +1,312 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* Point Geometry.
*
* - Array: [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
*
* - WKT:
* POINT [Z][M] (x y z m)
*
*
* - GeoJSON:
* {
* "type": "Point" / "PointM"
* "coordinates": [x, y, z] / [x, y, m] / [x, y, z, m]
* }
*/
class Point extends Geometry
{
/**
* WKT and GeoJSON basetypes
*/
const WKT_BASETYPE = 'POINT';
const GEOJSON_BASETYPE = 'Point';
/**
* @var float|null X coordinate
*/
private $x = null;
/**
* @var float|null Y coordinate
*/
private $y = null;
/**
* @var floa|null Z coordinate
*/
private $z = null;
/**
* @var float|bool|null M coordinate
*/
private $m = null;
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Constructor.
*
* @param float $x X coordinate
* @param float $y Y coordinate
* @param float $z Z coordinate
* @param float|bool $m M coordinate
*/
public function __construct($x = null, $y = null, $z = null, $m = null)
{
$this->init($x, $y, $z, $m);
}
public function initFromArray($array)
{
$this->checkInit();
$this->init(
isset($array['x']) ? $array['x'] : null,
isset($array['y']) ? $array['y'] : null,
isset($array['z']) ? $array['z'] : null,
isset($array['m']) ? $array['m'] : null
);
return $this;
}
public function initFromWKT($wkt)
{
$this->checkInit();
$wkt = $this->wktSanitize($wkt);
if (!$this->wktIsEmpty($wkt)) {
$coordinates = $this->wktParseCoordinates(
$this->wktExtractData($wkt),
$this->wktIsZ($wkt),
$this->wktIsM($wkt)
);
$this->init($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
}
return $this;
}
public function initFromGeoJSON($geojson)
{
$this->checkInit();
$geojson = $this->geojsonSanitize($geojson);
if ($geojson !== null) {
$coordinates = $this->geojsonParseCoordinates($geojson['coordinates'], $this->geojsonIsM($geojson['type']));
$this->init($coordinates['x'], $coordinates['y'], $coordinates['z'], $coordinates['m']);
}
return $this;
}
public function getArray()
{
if ($this->isEmpty()) {
return null;
}
$ret = [
'x' => $this->x,
'y' => $this->y,
];
if ($this->isZ()) {
$ret['z'] = $this->z;
}
if ($this->isM()) {
$ret['m'] = $this->m;
}
return $ret;
}
public function getWKT()
{
$ret = $this->wktInitializeOutput();
if (!$this->isEmpty()) {
$ret .= '(' . implode(' ', $this->getRawArray()) . ')';
}
return $ret;
}
public function getGeoJSON($flag_bbox = false, $flag_feature = false)
{
if ($this->isEmpty()) {
return 'null';
}
return $this->geojsonPackOutput($this->getRawArray(), $flag_bbox, $flag_feature);
}
public function getBoundingBox()
{
if ($this->isEmpty()) {
return null;
}
$ret = $this->getCustomBoundingBox();
if (!$ret) {
$ret = [
'xmin' => $this->x,
'xmax' => $this->x,
'ymin' => $this->y,
'ymax' => $this->y,
];
if ($this->isZ()) {
$ret['zmin'] = $this->z;
$ret['zmax'] = $this->z;
}
if ($this->isM()) {
$ret['mmin'] = $this->m;
$ret['mmax'] = $this->m;
}
}
return $ret;
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_POINT;
}
/**
* Gets X coordinate
*
* @return float
*/
public function getX()
{
return $this->x;
}
/**
* Gets Y coordinate
*
* @return float
*/
public function getY()
{
return $this->y;
}
/**
* Gets Z coordinate
*
* @return float
*/
public function getZ()
{
return $this->z;
}
/**
* Gets M coordinate
*
* @return float
*/
public function getM()
{
return $this->m;
}
/**
* @internal
*
* Gets an indexed array of coordinates.
* This is not actually for public use, rather it is used by other classes in the library.
*
* @return array
*/
public function getRawArray()
{
$ret = [];
if (!$this->isEmpty()) {
$ret[] = $this->x;
$ret[] = $this->y;
if ($this->isZ()) {
$ret[] = $this->z;
}
if ($this->isM()) {
$ret[] = $this->m === false ? 0 : $this->m ;
}
}
return $ret;
}
/****************************** PROTECTED ******************************/
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
/****************************** PRIVATE ******************************/
/**
* Initializes Geometry with coordinates.
*
* @param float $x X coordinate
* @param float $y Y coordinate
* @param float $z Z coordinate
* @param float|bool $m M coordinate
*
* @return self Returns $this to provide a fluent interface.
*/
private function init($x = null, $y = null, $z = null, $m = null)
{
if ($x === null xor $y === null) {
throw new ShapefileException(Shapefile::ERR_GEOM_POINT_NOT_VALID);
}
if ($x !== null && $y !== null) {
$this->x = $this->validateCoordValue($x);
$this->y = $this->validateCoordValue($y);
$this->setFlagEmpty(false);
if ($z !== null) {
$this->z = $this->validateCoordValue($z);
$this->setFlagZ(true);
}
if ($m !== null) {
$this->m = ($m === false) ? $m : $this->validateCoordValue($m);
$this->setFlagM(true);
}
}
return $this;
}
/**
* Validates a coordinate value.
*
* @param float $value Coordinate value
*
* @return float
*/
private function validateCoordValue($value)
{
if (!is_numeric($value)) {
throw new ShapefileException(Shapefile::ERR_GEOM_COORD_VALUE_NOT_VALID, $value);
}
return floatval($value);
}
}

341
vendor/Shapefile/Geometry/Polygon.php vendored Normal file
View File

@@ -0,0 +1,341 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile\Geometry;
use Shapefile\Shapefile;
use Shapefile\ShapefileException;
/**
* Polygon Geometry.
*
* - Array: [
* [numrings] => int
* [rings] => [
* [
* [numpoints] => int
* [points] => [
* [
* [x] => float
* [y] => float
* [z] => float
* [m] => float/bool
* ]
* ]
* ]
* ]
* ]
*
* - WKT:
* POLYGON [Z][M] ((x y z m, x y z m, x y z m, x y z m), (x y z m, x y z m, x y z m))
*
* - GeoJSON:
* {
* "type": "Polygon" / "PolygonM"
* "coordinates": [
* [
* [x, y, z] / [x, y, m] / [x, y, z, m]
* ]
* ]
* }
*/
class Polygon extends MultiLinestring
{
/**
* WKT and GeoJSON basetypes, collection class type
*/
const WKT_BASETYPE = 'POLYGON';
const GEOJSON_BASETYPE = 'Polygon';
const COLLECTION_CLASS = 'Linestring';
/**
* @var int Action to perform on polygon rings.
*/
private $closed_rings;
/**
* @var int Orientation to force for polygon rings.
*/
private $force_orientation;
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Constructor.
*
* @param \Shapefile\Geometry\Linestring[] $linestrings Optional array of linestrings to initialize the polygon.
* @param int $closed_rings Optional action to perform on polygon rings. Possible values:
* - Shapefile::ACTION_IGNORE
* - Shapefile::ACTION_CHECK
* - Shapefile::ACTION_FORCE
* @param int $force_orientation Optional orientation to force for polygon rings. Possible values:
* - Shapefile::ORIENTATION_CLOCKWISE
* - Shapefile::ORIENTATION_COUNTERCLOCKWISE
* - Shapefile::ORIENTATION_UNCHANGED
*/
public function __construct(array $linestrings = null, $closed_rings = Shapefile::ACTION_CHECK, $force_orientation = Shapefile::ORIENTATION_COUNTERCLOCKWISE)
{
$this->closed_rings = $closed_rings;
$this->force_orientation = $force_orientation;
parent::__construct($linestrings);
}
public function initFromArray($array)
{
$this->checkInit();
if (!isset($array['rings']) || !is_array($array['rings'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
foreach ($array['rings'] as $part) {
if (!isset($part['points']) || !is_array($part['points'])) {
throw new ShapefileException(Shapefile::ERR_INPUT_ARRAY_NOT_VALID);
}
$Linestring = new Linestring();
foreach ($part['points'] as $coordinates) {
$Point = new Point();
$Point->initFromArray($coordinates);
$Linestring->addPoint($Point);
}
$this->addRing($Linestring);
}
return $this;
}
public function getArray()
{
$rings = [];
foreach ($this->getLinestrings() as $Linestring) {
$rings[] = $Linestring->getArray();
}
return [
'numrings' => $this->getNumGeometries(),
'rings' => $rings,
];
}
/**
* Adds a ring to the collection.
*
* @param \Shapefile\Geometry\Linestring $Linestring
*
* @return self Returns $this to provide a fluent interface.
*/
public function addRing(Linestring $Linestring)
{
$this->addGeometry($Linestring);
return $this;
}
/**
* Gets a ring at specified index from the collection.
*
* @param int $index The index of the ring.
*
* @return \Shapefile\Geometry\Linestring
*/
public function getRing($index)
{
return $this->getGeometry($index);
}
/**
* Gets all the rings in the collection.
*
* @return \Shapefile\Geometry\Linestring[]
*/
public function getRings()
{
return $this->getGeometries();
}
/**
* Gets the number of rings in the collection.
*
* @return int
*/
public function getNumRings()
{
return $this->getNumGeometries();
}
/**
* Gets the polygon outer ring.
*
* @return \Shapefile\Geometry\Linestring
*/
public function getOuterRing()
{
return $this->isEmpty() ? null : $this->getRing(0);
}
/**
* Gets polygon inners rings.
*
* @return \Shapefile\Geometry\Linestring[]
*/
public function getInnerRings()
{
return array_slice($this->getRings(), 1);
}
/**
* Forces polygon rings to be closed.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClosedRings()
{
foreach ($this->getRings() as $Linestring) {
$Linestring->forceClosedRing();
}
return $this;
}
/**
* Checks whether polygon outer ring has a clockwise orientation and all the inner rings have a counterclockwise one.
* Note that a false return value does not guarantee polygon is strictly counterclockwise. Use Polygon::forceCounterClockwise() to enforce that!
*
* Returns Shapefile::UNDEFINED if geometry is empty.
*
* @return bool|Shapefile::UNDEFINED
*/
public function isClockwise()
{
if ($this->isEmpty()) {
return Shapefile::UNDEFINED;
}
if ($this->getOuterRing()->isClockwise(true) === false) {
return false;
}
foreach ($this->getInnerRings() as $Linestring) {
if ($Linestring->isClockwise(true) === true) {
return false;
}
}
return true;
}
/**
* Checks whether polygon outer ring has a counterclockwise orientation and all the inner rings have a clockwise one.
* Note that a false return value does not guarantee polygon is strictly clockwise. Use Polygon::forceClockwise() to enforce that!
*
* Returns Shapefile::UNDEFINED if geometry is empty.
*
* @return bool|Shapefile::UNDEFINED
*/
public function isCounterClockwise()
{
if ($this->isEmpty()) {
return Shapefile::UNDEFINED;
}
if ($this->getOuterRing()->isClockwise(true) === true) {
return false;
}
foreach ($this->getInnerRings() as $Linestring) {
if ($Linestring->isClockwise(true) === false) {
return false;
}
}
return true;
}
/**
* Forces polygon outer ring to have a clockwise orientation and all the inner rings to have a counterclockwise one.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceClockwise()
{
if (!$this->isEmpty()) {
$this->getOuterRing()->forceClockwise();
foreach ($this->getInnerRings() as $Linestring) {
$Linestring->forceCounterClockwise();
}
}
return $this;
}
/**
* Forces polygon outer ring to have a counterclockwise orientation and all the inner rings to have a clockwise one.
*
* @return self Returns $this to provide a fluent interface.
*/
public function forceCounterClockwise()
{
if (!$this->isEmpty()) {
$this->getOuterRing()->forceCounterClockwise();
foreach ($this->getInnerRings() as $Linestring) {
$Linestring->forceClockwise();
}
}
return $this;
}
public function getSHPBasetype()
{
return Shapefile::SHAPE_TYPE_POLYGON;
}
/////////////////////////////// PROTECTED ///////////////////////////////
/**
* Performs selected action and eventually forces orientation for polygon rings.
*
* @param \Shapefile\Geometry\Geometry $Linestring
*
* @return self Returns $this to provide a fluent interface.
*/
protected function addGeometry(Geometry $Linestring)
{
parent::addGeometry($Linestring);
// Closed rings
if ($this->closed_rings == Shapefile::ACTION_FORCE) {
$Linestring->forceClosedRing();
} elseif ($this->closed_rings == Shapefile::ACTION_CHECK && !$Linestring->isClosedRing()) {
throw new ShapefileException(Shapefile::ERR_GEOM_POLYGON_OPEN_RING);
}
// Orientation
if ($this->force_orientation == Shapefile::ORIENTATION_CLOCKWISE) {
$Linestring->{($this->getNumGeometries() == 1) ? 'forceClockwise' : 'forceCounterClockwise'}();
} elseif ($this->force_orientation == Shapefile::ORIENTATION_COUNTERCLOCKWISE) {
$Linestring->{($this->getNumGeometries() == 1) ? 'forceCounterClockwise' : 'forceClockwise'}();
}
return $this;
}
protected function getWKTBasetype()
{
return static::WKT_BASETYPE;
}
protected function getGeoJSONBasetype()
{
return static::GEOJSON_BASETYPE;
}
protected function getCollectionClass()
{
return __NAMESPACE__ . '\\' . static::COLLECTION_CLASS;
}
}

1419
vendor/Shapefile/Shapefile.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile;
/**
* Static autoloader class. It only exposes public static method register().
*/
class ShapefileAutoloader
{
/**
* Registers the actual autoloader.
*/
public static function register()
{
spl_autoload_register(function ($class) {
$prefix = __NAMESPACE__ . '\\';
$base_dir = __DIR__ . '/';
$prefix_len = strlen($prefix);
if (strncmp($prefix, $class, $prefix_len) !== 0) {
return;
}
$file = $base_dir . str_replace('\\', '/', substr($class, $prefix_len)) . '.php';
if (file_exists($file)) {
require($file);
}
});
}
/**
* Private constructor, no instances of this class allowed.
*/
private function __construct()
{
// NOP
}
}

65
vendor/Shapefile/ShapefileException.php vendored Normal file
View File

@@ -0,0 +1,65 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile;
/**
* Exception thrown by this library.
*/
class ShapefileException extends \Exception
{
/**
* @var string Error type that raised the exception.
*/
private $error_type;
/**
* @var string Additional information about the error.
*/
private $details;
/**
* Constructor
*
* @param string $error_type Error type.
* @param string $details Optional information about the error.
*/
public function __construct($error_type, $details = '')
{
$this->error_type = $error_type;
$this->details = $details;
$message = constant('Shapefile\Shapefile::' . $error_type . '_MESSAGE');
parent::__construct($message, 0, null);
}
/**
* Gets internal error type.
*
* @return string
*/
public function getErrorType()
{
return $this->error_type;
}
/**
* Gets error details.
*
* @return string
*/
public function getDetails()
{
return $this->details;
}
}

957
vendor/Shapefile/ShapefileReader.php vendored Normal file
View File

@@ -0,0 +1,957 @@
<?php
/**
* PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
*
* @package Shapefile
* @author Gaspare Sganga
* @version 3.3.0
* @license MIT
* @link https://gasparesganga.com/labs/php-shapefile/
*/
namespace Shapefile;
use Shapefile\Geometry\Point;
use Shapefile\Geometry\MultiPoint;
use Shapefile\Geometry\Linestring;
use Shapefile\Geometry\MultiLinestring;
use Shapefile\Geometry\Polygon;
use Shapefile\Geometry\MultiPolygon;
/**
* ShapefileReader class.
*/
class ShapefileReader extends Shapefile implements \Iterator
{
/** SHP read methods hash */
private static $shp_read_methods = [
Shapefile::SHAPE_TYPE_NULL => 'readNull',
Shapefile::SHAPE_TYPE_POINT => 'readPoint',
Shapefile::SHAPE_TYPE_POLYLINE => 'readPolyLine',
Shapefile::SHAPE_TYPE_POLYGON => 'readPolygon',
Shapefile::SHAPE_TYPE_MULTIPOINT => 'readMultiPoint',
Shapefile::SHAPE_TYPE_POINTZ => 'readPointZ',
Shapefile::SHAPE_TYPE_POLYLINEZ => 'readPolyLineZ',
Shapefile::SHAPE_TYPE_POLYGONZ => 'readPolygonZ',
Shapefile::SHAPE_TYPE_MULTIPOINTZ => 'readMultiPointZ',
Shapefile::SHAPE_TYPE_POINTM => 'readPointM',
Shapefile::SHAPE_TYPE_POLYLINEM => 'readPolyLineM',
Shapefile::SHAPE_TYPE_POLYGONM => 'readPolygonM',
Shapefile::SHAPE_TYPE_MULTIPOINTM => 'readMultiPointM',
];
/**
* @var array DBF field names map: fields are numerically indexed into DBF files.
*/
private $dbf_fields = [];
/**
* @var int DBF file size in bytes.
*/
private $dbf_file_size;
/**
* @var int DBF file header size in bytes.
*/
private $dbf_header_size;
/**
* @var int DBF file record size in bytes.
*/
private $dbf_record_size;
/**
* @var int DBT file size in bytes.
*/
private $dbt_file_size;
/**
* @var int Pointer to current SHP and DBF files record.
*/
private $current_record;
/////////////////////////////// PUBLIC ///////////////////////////////
/**
* Constructor.
*
* @param string|array $files Path to SHP file / Array of paths / Array of handles of individual files.
* @param array $options Optional associative array of options.
*/
public function __construct($files, $options = [])
{
// Deprecated options
if (isset($options[Shapefile::OPTION_ENFORCE_POLYGON_CLOSED_RINGS])) {
$options = array_merge([
Shapefile::OPTION_POLYGON_CLOSED_RINGS_ACTION => $options[Shapefile::OPTION_ENFORCE_POLYGON_CLOSED_RINGS] ? Shapefile::ACTION_CHECK : Shapefile::ACTION_IGNORE,
], $options);
}
if (isset($options[Shapefile::OPTION_INVERT_POLYGONS_ORIENTATION])) {
$options = array_merge([
Shapefile::OPTION_POLYGON_OUTPUT_ORIENTATION => $options[Shapefile::OPTION_INVERT_POLYGONS_ORIENTATION] ? Shapefile::ORIENTATION_COUNTERCLOCKWISE : Shapefile::ORIENTATION_CLOCKWISE,
], $options);
}
// Options
$this->initOptions([
Shapefile::OPTION_DBF_ALLOW_FIELD_SIZE_255,
Shapefile::OPTION_DBF_CONVERT_TO_UTF8,
Shapefile::OPTION_DBF_FORCE_ALL_CAPS,
Shapefile::OPTION_DBF_IGNORED_FIELDS,
Shapefile::OPTION_DBF_NULL_PADDING_CHAR,
Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES,
Shapefile::OPTION_DBF_RETURN_DATES_AS_OBJECTS,
Shapefile::OPTION_FORCE_MULTIPART_GEOMETRIES,
Shapefile::OPTION_POLYGON_CLOSED_RINGS_ACTION,
Shapefile::OPTION_POLYGON_ORIENTATION_READING_AUTOSENSE,
Shapefile::OPTION_POLYGON_OUTPUT_ORIENTATION,
Shapefile::OPTION_IGNORE_GEOMETRIES_BBOXES,
Shapefile::OPTION_IGNORE_SHAPEFILE_BBOX,
Shapefile::OPTION_SUPPRESS_M,
Shapefile::OPTION_SUPPRESS_Z,
], $options);
// Open files
$this->openFiles($files, false);
// Gets number of records from SHX file size.
$this->setTotRecords(($this->getFileSize(Shapefile::FILE_SHX) - Shapefile::SHX_HEADER_SIZE) / Shapefile::SHX_RECORD_SIZE);
// DBF file size
$this->dbf_file_size = $this->getFileSize(Shapefile::FILE_DBF);
// DBT file size
$this->dbt_file_size = ($this->isFileOpen(Shapefile::FILE_DBT) && $this->getFileSize(Shapefile::FILE_DBT) > 0) ? $this->getFileSize(Shapefile::FILE_DBT) : null;
// PRJ
if ($this->isFileOpen(Shapefile::FILE_PRJ) && $this->getFileSize(Shapefile::FILE_PRJ) > 0) {
$this->setPRJ($this->readString(Shapefile::FILE_PRJ, $this->getFileSize(Shapefile::FILE_PRJ)));
}
// CPG
if ($this->isFileOpen(Shapefile::FILE_CPG) && $this->getFileSize(Shapefile::FILE_CPG) > 0) {
$this->setCharset($this->readString(Shapefile::FILE_CPG, $this->getFileSize(Shapefile::FILE_CPG)));
}
// Read headers
$this->readSHPHeader();
$this->readDBFHeader();
// Init record pointer
$this->rewind();
}
/**
* Destructor.
*
* Closes all files.
*/
public function __destruct()
{
$this->closeFiles();
}
public function rewind()
{
$this->current_record = 0;
$this->next();
}
public function next()
{
++$this->current_record;
if (!$this->checkRecordIndex($this->current_record)) {
$this->current_record = Shapefile::EOF;
}
}
public function current()
{
return $this->readCurrentRecord();
}
public function key()
{
return $this->current_record;
}
public function valid()
{
return ($this->current_record !== Shapefile::EOF);
}
/**
* Gets current record index.
*
* Note that records count starts from 1 in Shapefiles.
* When the last record is reached, the special value Shapefile::EOF will be returned.
*
* @return int
*/
public function getCurrentRecord()
{
return $this->current_record;
}
/**
* Sets current record index. Throws an exception if provided index is out of range.
*
* @param int $index Index of the record to select.
*
* @return self Returns $this to provide a fluent interface.
*/
public function setCurrentRecord($index)
{
if (!$this->checkRecordIndex($index)) {
throw new ShapefileException(Shapefile::ERR_INPUT_RECORD_NOT_FOUND, $index);
}
$this->current_record = $index;
return $this;
}
/**
* Gets current record and moves the cursor to the next one.
*
* @return \Shapefile\Geometry\Geometry
*/
public function fetchRecord()
{
$ret = $this->readCurrentRecord();
if ($ret !== false) {
$this->next();
}
return $ret;
}
/////////////////////////////// PRIVATE ///////////////////////////////
/**
* Reads an unsigned char from a resource handle.
*
* @param string $file_type File type.
*
* @return int
*/
private function readChar($file_type)
{
return current(unpack('C', $this->readData($file_type, 1)));
}
/**
* Reads an unsigned short, 16 bit, little endian byte order, from a resource handle.
*
* @param string $file_type File type.
*
* @return int
*/
private function readInt16L($file_type)
{
return current(unpack('v', $this->readData($file_type, 2)));
}
/**
* Reads an unsigned long, 32 bit, big endian byte order, from a resource handle.
*
* @param string $file_type File type.
*
* @return int
*/
private function readInt32B($file_type)
{
return current(unpack('N', $this->readData($file_type, 4)));
}
/**
* Reads an unsigned long, 32 bit, little endian byte order, from a resource handle.
*
* @param string $file_type File type.
*
* @return int
*/
private function readInt32L($file_type)
{
return current(unpack('V', $this->readData($file_type, 4)));
}
/**
* Reads a double, 64 bit, little endian byte order, from a resource handle.
*
* @param string $file_type File type.
*
* @return double
*/
private function readDoubleL($file_type)
{
$ret = $this->readData($file_type, 8);
if ($this->isBigEndianMachine()) {
$ret = strrev($ret);
}
return current(unpack('d', $ret));
}
/**
* Reads a string of given length from a resource handle and optionally converts it to UTF-8.
*
* @param string $file_type File type.
* @param int $length Length of the string to read.
* @param bool $flag_utf8_encode Optional flag to convert output to UTF-8 if OPTION_DBF_CONVERT_TO_UTF8 is enabled.
*
* @return string
*/
private function readString($file_type, $length, $flag_utf8_encode = false)
{
$ret = current(unpack('A*', $this->readData($file_type, $length)));
if ($flag_utf8_encode && $this->getOption(Shapefile::OPTION_DBF_CONVERT_TO_UTF8)) {
$ret = @iconv($this->getCharset(), 'UTF-8', $ret);
if ($ret === false) {
throw new ShapefileException(Shapefile::ERR_DBF_CHARSET_CONVERSION);
}
}
return trim($ret);
}
/**
* Checks whether a record index value is valid or not.
*
* @param int $index The index value to check.
*
* @return bool
*/
private function checkRecordIndex($index)
{
return ($index > 0 && $index <= $this->getTotRecords());
}
/**
* Reads SHP file header.
*
* @return self Returns $this to provide a fluent interface.
*/
private function readSHPHeader()
{
// Shape Type
$this->setFilePointer(Shapefile::FILE_SHP, 32);
$this->setShapeType($this->readInt32L(Shapefile::FILE_SHP));
// Bounding Box (Z and M ranges are always present in the Shapefile, although with a 0 value if not used)
if (!$this->getOption(Shapefile::OPTION_IGNORE_SHAPEFILE_BBOX)) {
$bounding_box = $this->readXYBoundingBox() + $this->readZRange() + $this->readMRange();
if (!$this->isZ()) {
unset($bounding_box['zmin']);
unset($bounding_box['zmax']);
}
if (!$this->isM()) {
unset($bounding_box['mmin']);
unset($bounding_box['mmax']);
}
$this->setCustomBoundingBox($bounding_box);
}
return $this;
}
/**
* Reads DBF file header.
*
* @return self Returns $this to provide a fluent interface.
*/
private function readDBFHeader()
{
// Number of records
$this->setFilePointer(Shapefile::FILE_DBF, 4);
if ($this->readInt32L(Shapefile::FILE_DBF) !== $this->getTotRecords()) {
throw new ShapefileException(Shapefile::ERR_DBF_MISMATCHED_FILE);
}
// Header and Record size
$this->dbf_header_size = $this->readInt16L(Shapefile::FILE_DBF);
$this->dbf_record_size = $this->readInt16L(Shapefile::FILE_DBF);
// Fields
$this->dbf_fields = [];
$this->setFilePointer(Shapefile::FILE_DBF, 32);
while ($this->getFilePointer(Shapefile::FILE_DBF) < $this->dbf_header_size - 1) {
$name = $this->normalizeDBFFieldNameCase($this->readString(Shapefile::FILE_DBF, 10));
$this->setFileOffset(Shapefile::FILE_DBF, 1);
$type = $this->readString(Shapefile::FILE_DBF, 1);
$this->setFileOffset(Shapefile::FILE_DBF, 4);
$size = $this->readChar(Shapefile::FILE_DBF);
$decimals = $this->readChar(Shapefile::FILE_DBF);
$ignored = in_array($name, $this->getOption(Shapefile::OPTION_DBF_IGNORED_FIELDS));
if ($type === Shapefile::DBF_TYPE_MEMO && !$ignored && !$this->isFileOpen(Shapefile::FILE_DBT)) {
throw new ShapefileException(Shapefile::ERR_FILE_MISSING, strtoupper(Shapefile::FILE_DBT));
}
$this->dbf_fields[] = [
'name' => $ignored ? null : $this->addField($name, $type, $size, $decimals, true),
'ignored' => $ignored,
'size' => $size,
];
$this->setFileOffset(Shapefile::FILE_DBF, 14);
}
// Field terminator byte
if ($this->readChar(Shapefile::FILE_DBF) !== Shapefile::DBF_FIELD_TERMINATOR) {
throw new ShapefileException(Shapefile::ERR_DBF_FILE_NOT_VALID);
}
return $this;
}
/**
* Reads current record in both SHP and DBF files and returns a Geometry.
*
* @return \Shapefile\Geometry\Geometry
*/
private function readCurrentRecord()
{
if (!$this->valid()) {
return false;
}
// === SHX ===
$this->setFilePointer(Shapefile::FILE_SHX, Shapefile::SHX_HEADER_SIZE + (($this->current_record - 1) * Shapefile::SHX_RECORD_SIZE));
// Offset (stored as 16-bit words)
$shp_offset = $this->readInt32B(Shapefile::FILE_SHX) * 2;
// === SHP ===
// Set file pointer position skipping the 8-bytes record header
$this->setFilePointer(Shapefile::FILE_SHP, $shp_offset + 8);
// Shape type
$shape_type = $this->readInt32L(Shapefile::FILE_SHP);
if ($shape_type != Shapefile::SHAPE_TYPE_NULL && $shape_type != $this->getShapeType()) {
throw new ShapefileException(Shapefile::ERR_SHP_WRONG_RECORD_TYPE, $shape_type);
}
// Read Geometry
$Geometry = $this->{self::$shp_read_methods[$shape_type]}();
// === DBF ===
$dbf_file_position = $this->dbf_header_size + (($this->current_record - 1) * $this->dbf_record_size);
// Check if DBF is not corrupted (some "naive" users try to edit the DBF separately...)
// Some GIS do not include the last Shapefile::DBF_EOF_MARKER (0x1a) byte in the DBF file, hence the "- 1" in the following line
if ($dbf_file_position - 1 >= $this->dbf_file_size - $this->dbf_record_size) {
throw new ShapefileException(Shapefile::ERR_DBF_EOF_REACHED);
}
$this->setFilePointer(Shapefile::FILE_DBF, $dbf_file_position);
$Geometry->setFlagDeleted($this->readChar(Shapefile::FILE_DBF) === Shapefile::DBF_DELETED_MARKER);
foreach ($this->dbf_fields as $i => $f) {
if ($f['ignored']) {
$this->setFileOffset(Shapefile::FILE_DBF, $f['size']);
} else {
$type = $this->getField($f['name'])['type'];
$value = $this->decodeFieldValue($f['name'], $type, $this->readString(Shapefile::FILE_DBF, $f['size'], true));
// Memo (DBT)
if ($type === Shapefile::DBF_TYPE_MEMO && $value) {
$this->setFilePointer(Shapefile::FILE_DBT, intval($value) * Shapefile::DBT_BLOCK_SIZE);
$value = '';
do {
if ($this->getFilePointer(Shapefile::FILE_DBT) >= $this->dbt_file_size) {
throw new ShapefileException(Shapefile::ERR_DBT_EOF_REACHED);
}
$value .= $this->readString(Shapefile::FILE_DBT, Shapefile::DBT_BLOCK_SIZE, true);
// Some software only sets ONE field terminator instead of TWO, hence the weird loop condition check:
} while (ord(substr($value, -1)) != Shapefile::DBT_FIELD_TERMINATOR && ord(substr($value, -2, 1)) != Shapefile::DBT_FIELD_TERMINATOR);
$value = substr($value, 0, -2);
}
$Geometry->setData($f['name'], $value);
}
}
$this->pairGeometry($Geometry);
return $Geometry;
}
/**
* Decodes a raw value read from a DBF field.
*
* @param string $field Name of the field.
* @param string $type Type of the field.
* @param string $value Raw value to decode.
*
* @return mixed
*/
private function decodeFieldValue($field, $type, $value)
{
if ($this->getOption(Shapefile::OPTION_DBF_NULL_PADDING_CHAR) !== null && $value == str_repeat($this->getOption(Shapefile::OPTION_DBF_NULL_PADDING_CHAR), $this->getField($field)['size'])) {
$value = null;
} else {
switch ($type) {
case Shapefile::DBF_TYPE_DATE:
$DateTime = \DateTime::createFromFormat('Ymd', $value);
$errors = \DateTime::getLastErrors();
if ($errors['warning_count'] || $errors['error_count']) {
$value = $this->getOption(Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES) ? null : $value;
} elseif ($this->getOption(Shapefile::OPTION_DBF_RETURN_DATES_AS_OBJECTS)) {
$DateTime->setTime(0, 0, 0);
$value = $DateTime;
} else {
$value = $DateTime->format('Y-m-d');
}
break;
case Shapefile::DBF_TYPE_LOGICAL:
$value = ($value === Shapefile::DBF_VALUE_NULL) ? null : strpos(Shapefile::DBF_VALUE_MASK_TRUE, $value) !== false;
break;
}
}
return $value;
}
/**
* Reads an XY pair of coordinates and returns an associative array.
*
* @return array Associative array with "x" and "y" values.
*/
private function readXY()
{
return [
'x' => $this->readDoubleL(Shapefile::FILE_SHP),
'y' => $this->readDoubleL(Shapefile::FILE_SHP),
];
}
/**
* Reads a Z coordinate.
*
* @return array Associative array with "z" value or empty array.
*/
private function readZ()
{
$z = $this->readDoubleL(Shapefile::FILE_SHP);
return $this->getOption(Shapefile::OPTION_SUPPRESS_Z) ? [] : ['z' => $z];
}
/**
* Reads an M coordinate.
*
* @return array Associative array with "m" value or empty array.
*/
private function readM()
{
$m = $this->readDoubleL(Shapefile::FILE_SHP);
return $this->getOption(Shapefile::OPTION_SUPPRESS_M) ? [] : ['m' => $this->parseM($m)];
}
/**
* Parses an M coordinate according to the ESRI specs:
* «Any floating point number smaller than 10^38 is considered by a shapefile reader to represent a "no data" value»
*
* @param float $value Value to parse.
*
* @return float|bool
*/
private function parseM($value)
{
return ($value <= Shapefile::SHP_NO_DATA_THRESHOLD) ? false : $value;
}
/**
* Reads an XY bounding box and returns an associative array.
*
* @return array Associative array with the xmin, xmax, ymin and ymax values.
*/
private function readXYBoundingBox()
{
// Variables are used here because the order of the output array elements is different!
$xmin = $this->readDoubleL(Shapefile::FILE_SHP);
$ymin = $this->readDoubleL(Shapefile::FILE_SHP);
$xmax = $this->readDoubleL(Shapefile::FILE_SHP);
$ymax = $this->readDoubleL(Shapefile::FILE_SHP);
return [
'xmin' => $xmin,
'xmax' => $xmax,
'ymin' => $ymin,
'ymax' => $ymax,
];
}
/**
* Reads a Z range and returns an associative array.
* If flag OPTION_SUPPRESS_Z is set, an empty array will be returned.
*
* @return array Associative array with the zmin and zmax values.
*/
private function readZRange()
{
$values = [
'zmin' => $this->readDoubleL(Shapefile::FILE_SHP),
'zmax' => $this->readDoubleL(Shapefile::FILE_SHP),
];
return $this->getOption(Shapefile::OPTION_SUPPRESS_Z) ? [] : $values;
}
/**
* Reads an M range and returns an associative array.
* If flag OPTION_SUPPRESS_M is set, an empty array will be returned.
*
* @return array Associative array with the mmin and mmax values.
*/
private function readMRange()
{
$values = [
'mmin' => $this->parseM($this->readDoubleL(Shapefile::FILE_SHP)),
'mmax' => $this->parseM($this->readDoubleL(Shapefile::FILE_SHP)),
];
return $this->getOption(Shapefile::OPTION_SUPPRESS_M) ? [] : $values;
}
/**
* Returns an empty Geometry depending on the base type of the Shapefile.
*
* @return \Shapefile\Geometry\Geometry
*/
private function readNull()
{
$geometry_classes = [
Shapefile::SHAPE_TYPE_POINT => 'Point',
Shapefile::SHAPE_TYPE_POLYLINE => 'Linestring',
Shapefile::SHAPE_TYPE_POLYGON => 'Polygon',
Shapefile::SHAPE_TYPE_MULTIPOINT => 'MultiPoint',
];
$shape_basetype = $this->getBasetype();
$geometry_class = $geometry_classes[$shape_basetype];
if ($this->getOption(Shapefile::OPTION_FORCE_MULTIPART_GEOMETRIES) && ($shape_basetype == Shapefile::SHAPE_TYPE_POLYLINE || $shape_basetype == Shapefile::SHAPE_TYPE_POLYGON)) {
$geometry_class = 'Multi' . $geometry_class;
}
$geometry_class = 'Shapefile\Geometry\\' . $geometry_class;
return new $geometry_class();
}
/**
* Reads a Point from the SHP file.
*
* @return \Shapefile\Geometry\Point
*/
private function readPoint()
{
return $this->createPoint($this->readXY());
}
/**
* Reads a PointM from the SHP file.
*
* @return \Shapefile\Geometry\Point
*/
private function readPointM()
{
return $this->createPoint($this->readXY() + $this->readM());
}
/**
* Reads a PointZ from the SHP file.
*
* @return \Shapefile\Geometry\Point
*/
private function readPointZ()
{
return $this->createPoint($this->readXY() + $this->readZ() + $this->readM());
}
/**
* Helper method to create the actual Point Geometry using data read from SHP file.
*
* @param array $data Array with "x", "y" and optional "z" and "m" values.
*
* @return \Shapefile\Geometry\Point
*/
private function createPoint($data)
{
$Geometry = new Point();
$Geometry->initFromArray($data);
return $Geometry;
}
/**
* Reads a MultiPoint from the SHP file.
*
* @param bool $flag_return_geometry Flag to control return type.
*
* @return \Shapefile\Geometry\MultiPoint|array
*/
private function readMultiPoint($flag_return_geometry = true)
{
// Header
$data = [
'bbox' => $this->readXYBoundingBox(),
'geometry' => [
'numpoints' => $this->readInt32L(Shapefile::FILE_SHP),
'points' => [],
],
];
// Points
for ($i = 0; $i < $data['geometry']['numpoints']; ++$i) {
$data['geometry']['points'][] = $this->readXY();
}
return $flag_return_geometry ? $this->createMultiPoint($data) : $data;
}
/**
* Reads a MultiPointM from the SHP file.
*
* @return \Shapefile\Geometry\MultiPoint
*/
private function readMultiPointM()
{
// MultiPoint
$data = $this->readMultiPoint(false);
// M Range
$data['bbox'] += $this->readMRange();
// M Array
for ($i = 0; $i < $data['geometry']['numpoints']; ++$i) {
$data['geometry']['points'][$i] += $this->readM();
}
return $this->createMultiPoint($data);
}
/**
* Reads a MultiPointZ from the SHP file.
*
* @return \Shapefile\Geometry\MultiPoint
*/
private function readMultiPointZ()
{
// MultiPoint
$data = $this->readMultiPoint(false);
// Z Range
$data['bbox'] += $this->readZRange();
// Z Array
for ($i = 0; $i < $data['geometry']['numpoints']; ++$i) {
$data['geometry']['points'][$i] += $this->readZ();
}
// M Range
$data['bbox'] += $this->readMRange();
// M Array
for ($i = 0; $i < $data['geometry']['numpoints']; ++$i) {
$data['geometry']['points'][$i] += $this->readM();
}
return $this->createMultiPoint($data);
}
/**
* Helper method to create the actual MultiPoint Geometry using data read from SHP file.
*
* @param array $data Array with "bbox" and "geometry" values.
*
* @return \Shapefile\Geometry\MultiPoint
*/
private function createMultiPoint($data)
{
$Geometry = new MultiPoint();
$Geometry->initFromArray($data['geometry']);
if (!$this->getOption(Shapefile::OPTION_IGNORE_GEOMETRIES_BBOXES)) {
$Geometry->setCustomBoundingBox($data['bbox']);
}
return $Geometry;
}
/**
* Reads a PolyLine from the SHP file.
*
* @param bool $flag_return_geometry Flag to control return type.
*
* @return \Shapefile\Geometry\Linestring|\Shapefile\Geometry\MultiLinestring|array
*/
private function readPolyLine($flag_return_geometry = true)
{
// Header
$data = [
'bbox' => $this->readXYBoundingBox(),
'geometry' => [
'numparts' => $this->readInt32L(Shapefile::FILE_SHP),
'parts' => [],
],
];
$tot_points = $this->readInt32L(Shapefile::FILE_SHP);
// Parts
$parts_first_index = [];
for ($i = 0; $i < $data['geometry']['numparts']; ++$i) {
$parts_first_index[$i] = $this->readInt32L(Shapefile::FILE_SHP);
$data['geometry']['parts'][$i] = [
'numpoints' => 0,
'points' => [],
];
}
// Points
$part = 0;
for ($i = 0; $i < $tot_points; ++$i) {
if (isset($parts_first_index[$part + 1]) && $parts_first_index[$part + 1] == $i) {
++$part;
}
$data['geometry']['parts'][$part]['points'][] = $this->readXY();
}
for ($i = 0; $i < $data['geometry']['numparts']; ++$i) {
$data['geometry']['parts'][$i]['numpoints'] = count($data['geometry']['parts'][$i]['points']);
}
return $flag_return_geometry ? $this->createLinestring($data) : $data;
}
/**
* Reads a PolyLineM from the SHP file.
*
* @param bool $flag_return_geometry Flag to control return type.
*
* @return \Shapefile\Geometry\Linestring|\Shapefile\Geometry\MultiLinestring|array
*/
private function readPolyLineM($flag_return_geometry = true)
{
// PolyLine
$data = $this->readPolyLine(false);
// M Range
$data['bbox'] += $this->readMRange();
// M Array
for ($i = 0; $i < $data['geometry']['numparts']; ++$i) {
for ($k = 0; $k < $data['geometry']['parts'][$i]['numpoints']; ++$k) {
$data['geometry']['parts'][$i]['points'][$k] += $this->readM();
}
}
return $flag_return_geometry ? $this->createLinestring($data) : $data;
}
/**
* Reads a PolyLineZ from the SHP file.
*
* @param bool $flag_return_geometry Flag to control return type.
*
* @return \Shapefile\Geometry\Linestring|\Shapefile\Geometry\MultiLinestring|array
*/
private function readPolyLineZ($flag_return_geometry = true)
{
// PolyLine
$data = $this->readPolyLine(false);
// Z Range
$data['bbox'] += $this->readZRange();
// Z Array
for ($i = 0; $i < $data['geometry']['numparts']; ++$i) {
for ($k = 0; $k < $data['geometry']['parts'][$i]['numpoints']; ++$k) {
$data['geometry']['parts'][$i]['points'][$k] += $this->readZ();
}
}
// M Range
$data['bbox'] += $this->readMRange();
// M Array
for ($i = 0; $i < $data['geometry']['numparts']; ++$i) {
for ($k = 0; $k < $data['geometry']['parts'][$i]['numpoints']; ++$k) {
$data['geometry']['parts'][$i]['points'][$k] += $this->readM();
}
}
return $flag_return_geometry ? $this->createLinestring($data) : $data;
}
/**
* Helper method to create the actual Linestring Geometry using data read from SHP file.
* If OPTION_FORCE_MULTIPART_GEOMETRIES is set, a MultiLinestring is returned instead.
*
* @param array $data Array with "bbox" and "geometry" values.
*
* @return \Shapefile\Geometry\Linestring|\Shapefile\Geometry\MultiLinestring
*/
private function createLinestring($data)
{
if (!$this->getOption(Shapefile::OPTION_FORCE_MULTIPART_GEOMETRIES) && $data['geometry']['numparts'] == 1) {
$data['geometry'] = $data['geometry']['parts'][0];
$Geometry = new Linestring();
} else {
$Geometry = new MultiLinestring();
}
$Geometry->initFromArray($data['geometry']);
if (!$this->getOption(Shapefile::OPTION_IGNORE_GEOMETRIES_BBOXES)) {
$Geometry->setCustomBoundingBox($data['bbox']);
}
return $Geometry;
}
/**
* Reads a Polygon from the SHP file.
*
* @return \Shapefile\Geometry\Polygon|\Shapefile\Geometry\MultiPolygon
*/
private function readPolygon()
{
return $this->createPolygon($this->readPolyLine(false));
}
/**
* Reads a PolygonM from the SHP file.
*
* @return \Shapefile\Geometry\Polygon|\Shapefile\Geometry\MultiPolygon
*/
private function readPolygonM()
{
return $this->createPolygon($this->readPolyLineM(false));
}
/**
* Reads a PolygonZ from the SHP file.
*
* @return \Shapefile\Geometry\Polygon|\Shapefile\Geometry\MultiPolygon
*/
private function readPolygonZ()
{
return $this->createPolygon($this->readPolyLineZ(false));
}
/**
* Helper method to create the actual Polygon Geometry using data read from SHP file.
* If OPTION_FORCE_MULTIPART_GEOMETRIES is set, a MultiPolygon is returned instead.
*
* @param array $data Array with "bbox" and "geometry" values.
*
* @return \Shapefile\Geometry\Polygon|\Shapefile\Geometry\MultiPolygon
*/
private function createPolygon($data)
{
$MultiPolygon = new MultiPolygon(null, $this->getOption(Shapefile::OPTION_POLYGON_CLOSED_RINGS_ACTION), $this->getOption(Shapefile::OPTION_POLYGON_OUTPUT_ORIENTATION));
$Polygon = null;
$temp_state = null;
foreach ($data['geometry']['parts'] as $part) {
$Linestring = new Linestring();
$Linestring->initFromArray($part);
$is_clockwise = $Linestring->isClockwise();
if ($Polygon === null && !$is_clockwise && !$this->getOption(Shapefile::OPTION_POLYGON_ORIENTATION_READING_AUTOSENSE)) {
throw new ShapefileException(Shapefile::ERR_GEOM_POLYGON_WRONG_ORIENTATION);
}
if ($temp_state === null || $temp_state === $is_clockwise) {
if ($Polygon !== null) {
$MultiPolygon->addPolygon($Polygon);
}
$Polygon = new Polygon(null, $this->getOption(Shapefile::OPTION_POLYGON_CLOSED_RINGS_ACTION), $this->getOption(Shapefile::OPTION_POLYGON_OUTPUT_ORIENTATION));
$temp_state = $is_clockwise;
}
$Polygon->addRing($Linestring);
}
$MultiPolygon->addPolygon($Polygon);
$Geometry = (!$this->getOption(Shapefile::OPTION_FORCE_MULTIPART_GEOMETRIES) && $MultiPolygon->getNumPolygons() == 1) ? $MultiPolygon->getPolygon(0) : $MultiPolygon;
if (!$this->getOption(Shapefile::OPTION_IGNORE_GEOMETRIES_BBOXES)) {
$Geometry->setCustomBoundingBox($data['bbox']);
}
return $Geometry;
}
}

1174
vendor/Shapefile/ShapefileWriter.php vendored Normal file

File diff suppressed because it is too large Load Diff