'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; } }