Alter Version
This commit is contained in:
628
vendor/Shapefile/Geometry/Geometry.php
vendored
Normal file
628
vendor/Shapefile/Geometry/Geometry.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
182
vendor/Shapefile/Geometry/GeometryCollection.php
vendored
Normal file
182
vendor/Shapefile/Geometry/GeometryCollection.php
vendored
Normal 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
218
vendor/Shapefile/Geometry/Linestring.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
236
vendor/Shapefile/Geometry/MultiLinestring.php
vendored
Normal file
236
vendor/Shapefile/Geometry/MultiLinestring.php
vendored
Normal 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
207
vendor/Shapefile/Geometry/MultiPoint.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
435
vendor/Shapefile/Geometry/MultiPolygon.php
vendored
Normal file
435
vendor/Shapefile/Geometry/MultiPolygon.php
vendored
Normal 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
312
vendor/Shapefile/Geometry/Point.php
vendored
Normal 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
341
vendor/Shapefile/Geometry/Polygon.php
vendored
Normal 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
1419
vendor/Shapefile/Shapefile.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
49
vendor/Shapefile/ShapefileAutoloader.php
vendored
Normal file
49
vendor/Shapefile/ShapefileAutoloader.php
vendored
Normal 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
65
vendor/Shapefile/ShapefileException.php
vendored
Normal 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
957
vendor/Shapefile/ShapefileReader.php
vendored
Normal 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
1174
vendor/Shapefile/ShapefileWriter.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user