'Null Shape', self::SHAPE_TYPE_POINT => 'Point', self::SHAPE_TYPE_POLYLINE => 'PolyLine', self::SHAPE_TYPE_POLYGON => 'Polygon', self::SHAPE_TYPE_MULTIPOINT => 'MultiPoint', self::SHAPE_TYPE_POINTZ => 'PointZ', self::SHAPE_TYPE_POLYLINEZ => 'PolyLineZ', self::SHAPE_TYPE_POLYGONZ => 'PolygonZ', self::SHAPE_TYPE_MULTIPOINTZ => 'MultiPointZ', self::SHAPE_TYPE_POINTM => 'PointM', self::SHAPE_TYPE_POLYLINEM => 'PolyLineM', self::SHAPE_TYPE_POLYGONM => 'PolygonM', self::SHAPE_TYPE_MULTIPOINTM => 'MultiPointM', ]; /////////////////////////////// PRIVATE VARIABLES /////////////////////////////// /** * @var int|null Shapefile type. */ private $shape_type = null; /** * @var array|null Custom bounding box set with setCustomBoundingBox() method. */ private $custom_bounding_box = null; /** * @var array|null Computed bounding box. */ private $computed_bounding_box = null; /** * @var string|null PRJ well-known-text. */ private $prj = null; /** * @var string|null DBF charset. */ private $charset = null; /** * @var array Fields definition. * Every field is represented by an array with the following structure: * [ * "type" => string * "size" => int * "decimals" => int * ] */ private $fields = []; /** * @var array Array of FileInterface instances. */ private $files = []; /** * @var array Array of canonicalized absolute pathnames of open files. * It will be populated only if files are NOT passed as stream resources. */ private $filenames = []; /** * @var array Options. */ private $options = []; /** * @var int Total number of records. */ private $tot_records; /** * @var bool|null Flag to store whether the machine is big endian or not. */ private $flag_big_endian_machine = null; /** * @var bool Flag representing whether the Shapefile has been initialized with any Geometry or not. */ private $flag_initialized = false; /////////////////////////////// PUBLIC /////////////////////////////// /** * Checks if Shapefile is of type Z. * * @return bool */ public function isZ() { $shape_type = $this->getShapeType(Shapefile::FORMAT_INT); return $shape_type > 10 && $shape_type < 20; } /** * Checks if Shapefile is of type M. * * @return bool */ public function isM() { return $this->getShapeType(Shapefile::FORMAT_INT) > 10; } /** * Gets shape type either as integer or string. * * @param int $format Optional desired output format. * It can be on of the following: * - Shapefile::FORMAT_INT [default] * - Shapefile::FORMAT_STR * * @return int|string */ public function getShapeType($format = Shapefile::FORMAT_INT) { if ($this->shape_type === null) { throw new ShapefileException(Shapefile::ERR_SHP_TYPE_NOT_SET); } if ($format == Shapefile::FORMAT_STR) { return Shapefile::$shape_types[$this->shape_type]; } else { return $this->shape_type; } } /** * Gets Shapefile bounding box. * * @return array */ public function getBoundingBox() { return $this->custom_bounding_box ?: $this->computed_bounding_box; } /** * Gets PRJ well-known-text. * * @return string */ public function getPRJ() { return $this->prj; } /** * Gets DBF charset. * * @return string */ public function getCharset() { return $this->charset ?: Shapefile::DBF_DEFAULT_CHARSET; } /** * Sets or resets DBF charset. * * @param mixed $charset Name of the charset. * Pass a falsy value (eg. false or "") to reset it to default. * * @return self Returns $this to provide a fluent interface. */ public function setCharset($charset) { $this->charset = $charset ?: Shapefile::DBF_DEFAULT_CHARSET; return $this; } /** * Gets all fields names. * * @return array */ public function getFieldsNames() { return array_keys($this->fields); } /** * Gets all fields definitions. * * @return array */ public function getFields() { return $this->fields; } /** * Gets a field type. * * @param string $name Name of the field. * * @return string */ public function getFieldType($name) { return $this->getField($name)['type']; } /** * Gets a field size. * * @param string $name Name of the field. * * @return int */ public function getFieldSize($name) { return $this->getField($name)['size']; } /** * Gets a field decimals. * * @param string $name Name of the field. * * @return int */ public function getFieldDecimals($name) { return $this->getField($name)['decimals']; } /** * Gets a complete field definition. * * The returned array contains the following elements: * [ * "type" => string * "size" => int * "decimals" => int * ] * * @param string $name Name of the field. * * @return array */ public function getField($name) { $name = $this->normalizeDBFFieldNameCase($name); if (!isset($this->fields[$name])) { throw new ShapefileException(Shapefile::ERR_INPUT_FIELD_NOT_FOUND, $name); } return $this->fields[$name]; } /** * Gets total number of records in SHP and DBF files. * * @return int */ public function getTotRecords() { return $this->tot_records; } /////////////////////////////// PROTECTED /////////////////////////////// /** * Opens files with binary read or write access. * * Filenames are mapped here because files are closed in destructors and working directory might be different! * * @param string|array $files Path to SHP file / Array of paths / Array of resource handles / Array of FileInterface instances. * @param bool $write_access Access type: false = read; true = write. * @param array $ignored_files Optional map of files to ignore [filetype => bool]. * * @return self Returns $this to provide a fluent interface. */ protected function openFiles($files, $write_access, $ignored_files = []) { // Create $files array from single string (SHP filename) if (is_string($files)) { $basename = (substr($files, -4) == '.' . Shapefile::FILE_SHP) ? substr($files, 0, -4) : $files; $files = [ Shapefile::FILE_SHP => $basename . '.' . Shapefile::FILE_SHP, Shapefile::FILE_SHX => $basename . '.' . Shapefile::FILE_SHX, Shapefile::FILE_DBF => $basename . '.' . Shapefile::FILE_DBF, Shapefile::FILE_DBT => $basename . '.' . Shapefile::FILE_DBT, Shapefile::FILE_PRJ => $basename . '.' . Shapefile::FILE_PRJ, Shapefile::FILE_CPG => $basename . '.' . Shapefile::FILE_CPG, ]; } // Ignored files $ignored_files = $ignored_files + [ Shapefile::FILE_SHX => false, Shapefile::FILE_DBF => false, ]; // Make sure required files are specified foreach ( [ Shapefile::FILE_SHP, Shapefile::FILE_SHX, Shapefile::FILE_DBF, ] as $type ) { if (!is_array($files) || (!isset($files[$type]) && (!isset($ignored_files[$type]) || !$ignored_files[$type]))) { throw new ShapefileException(Shapefile::ERR_FILE_MISSING, strtoupper($type)); } } $this->filenames = []; if (array_reduce($files, function($ret, $item) { return $ret && $item instanceof FileInterface; }, true)) { // FileInterface instances foreach ($files as $type => $File) { if (!isset($ignored_files[$type]) || !$ignored_files[$type]) { if ((!$write_access && !$File->isReadable()) || ($write_access && !$File->isWritable())) { throw new ShapefileException(Shapefile::ERR_FILE_PERMISSIONS, strtoupper($type)); } $this->files[$type] = $File; } } } elseif ($files === array_filter($files, 'is_resource')) { // Resource handles foreach ($files as $type => $file) { if (!isset($ignored_files[$type]) || !$ignored_files[$type]) { try { $this->files[$type] = new StreamResourceFile($file, $write_access); } catch (ShapefileException $e) { throw new ShapefileException($e->getErrorType(), strtoupper($type)); } } } } else { // Filenames foreach ( [ Shapefile::FILE_SHP => true, Shapefile::FILE_SHX => !$ignored_files[Shapefile::FILE_SHX], Shapefile::FILE_DBF => !$ignored_files[Shapefile::FILE_DBF], Shapefile::FILE_DBT => false, Shapefile::FILE_PRJ => false, Shapefile::FILE_CPG => false, ] as $type => $required ) { if (isset($files[$type]) && (!isset($ignored_files[$type]) || !$ignored_files[$type])) { if (!is_string($files[$type])){ throw new ShapefileException(Shapefile::ERR_FILE_PATH_NOT_VALID, strtoupper($type)); } if ( (!$write_access && is_readable($files[$type]) && is_file($files[$type])) || ($write_access && is_writable(dirname($files[$type])) && (!file_exists($files[$type]) || (is_file($files[$type]) && is_writable($files[$type]) && (($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_APPEND && is_readable($files[$type])) || ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_OVERWRITE))))) ) { try { $this->files[$type] = new StreamResourceFile($files[$type], $write_access); $this->filenames[$type] = $this->files[$type]->getFilepath(); } catch (ShapefileException $e) { throw new ShapefileException($e->getErrorType(), $files[$type]); } } elseif ($required) { throw new ShapefileException(Shapefile::ERR_FILE_EXISTS, $files[$type]); } } } } // Set files pointers to start foreach ($this->files as $File) { $File->setPointer(0); } return $this; } /** * Closes all open files. * Actually, this just destroys the reference to FileInterface instance, letting it handle the situation. * * @return self Returns $this to provide a fluent interface. */ protected function closeFiles() { foreach (array_keys($this->files) as $type) { $this->files[$type] = null; } return $this; } /** * Truncates file to given length. * * @param string $file_type File type. * @param int $size Optional size to truncate to. * * @return self Returns $this to provide a fluent interface. */ protected function fileTruncate($file_type, $size = 0) { $this->files[$file_type]->truncate($size); return $this; } /** * Checks if file type has been opened. * * @param string $file_type File type (member of $this->files array). * * @return bool */ protected function isFileOpen($file_type) { return isset($this->files[$file_type]); } /** * Gets an array of the open files. * * @return array */ protected function getFiles() { return $this->files; } /** * Gets an array of canonicalized absolute pathnames. * If files were passed as stream resource handles or FileInterface instances, an empty array is returned. * * @return array */ protected function getFilenames() { return $this->filenames; } /** * Gets file size. * * @param string $file_type File type (member of $this->files array). * * @return int */ protected function getFileSize($file_type) { return $this->files[$file_type]->getSize(); } /** * Gets file current pointer position. * * @param string $file_type File type (member of $this->files array). * * @return int */ protected function getFilePointer($file_type) { return $this->files[$file_type]->getPointer(); } /** * Sets file pointer to specified position. * * @param string $file_type File type (member of $this->files array). * @param int $position The position to set the pointer to. * * @return self Returns $this to provide a fluent interface. */ protected function setFilePointer($file_type, $position) { $this->files[$file_type]->setPointer($position); return $this; } /** * Resets file pointer position to its end. * * @param string $file_type File type (member of $this->files array). * * @return self Returns $this to provide a fluent interface. */ protected function resetFilePointer($file_type) { $this->files[$file_type]->resetPointer(); return $this; } /** * Increases file pointer position of specified offset. * * @param string $file_type File type (member of $this->files array). * @param int $offset The offset to move the pointer for. * * @return self Returns $this to provide a fluent interface. */ protected function setFileOffset($file_type, $offset) { $this->files[$file_type]->setOffset($offset); return $this; } /** * Reads data from file. * * @param string $file_type File type. * @param int $length Number of bytes to read. * * @return string */ protected function readData($file_type, $length) { $ret = $this->files[$file_type]->read($length); if ($ret === false) { throw new ShapefileException(Shapefile::ERR_FILE_READING); } return $ret; } /** * Writes binary string packed data to file. * * @param string $file_type File type. * @param string $data Binary string packed data to write. * * @return self Returns $this to provide a fluent interface. */ protected function writeData($file_type, $data) { if ($this->files[$file_type]->write($data) === false) { throw new ShapefileException(Shapefile::ERR_FILE_WRITING); } return $this; } /** * Checks if machine is big endian. * * @return bool */ protected function isBigEndianMachine() { if ($this->flag_big_endian_machine === null) { $this->flag_big_endian_machine = current(unpack('v', pack('S', 0xff))) !== 0xff; } return $this->flag_big_endian_machine; } /** * Initializes options with default and user-provided values. * * @param array $options_list Array of options to initialize. * @param array $user_values User-provided options values. * * @return self Returns $this to provide a fluent interface. */ protected function initOptions($options_list, $user_values) { // Make sure compulsory options used in this abstract class are defined $options_list = array_unique(array_merge($options_list, [ Shapefile::OPTION_DBF_ALLOW_FIELD_SIZE_255, Shapefile::OPTION_DBF_FORCE_ALL_CAPS, Shapefile::OPTION_SUPPRESS_M, Shapefile::OPTION_SUPPRESS_Z, ])); // Defaults $defaults = []; foreach ($options_list as $option) { $defaults[$option] = constant('Shapefile\Shapefile::' . $option . '_DEFAULT'); } // Filter custom options $user_values = array_intersect_key(array_change_key_case($user_values, CASE_UPPER), $defaults); // Initialize option array $this->options = $user_values + $defaults; // Use only the first character of OPTION_DBF_NULL_PADDING_CHAR if it's set and is not false or empty $k = Shapefile::OPTION_DBF_NULL_PADDING_CHAR; if (array_key_exists($k, $this->options)) { $this->options[$k] = ($this->options[$k] === false || $this->options[$k] === null || $this->options[$k] === '') ? null : substr($this->options[$k], 0, 1); } // Parse OPTION_DBF_IGNORED_FIELDS $k = Shapefile::OPTION_DBF_IGNORED_FIELDS; if (array_key_exists($k, $this->options)) { $this->options[$k] = is_array($this->options[$k]) ? array_map([$this, 'normalizeDBFFieldNameCase'], $this->options[$k]) : []; } return $this; } /** * Gets option value. * * @param string $option Name of the option. * * @return string */ protected function getOption($option) { return $this->options[$option]; } /** * Sets shape type. * It can be called just once for an instance of the class. * * @param int $type Shape type. It can be on of the following: * - Shapefile::SHAPE_TYPE_NULL * - Shapefile::SHAPE_TYPE_POINT * - Shapefile::SHAPE_TYPE_POLYLINE * - Shapefile::SHAPE_TYPE_POLYGON * - Shapefile::SHAPE_TYPE_MULTIPOINT * - Shapefile::SHAPE_TYPE_POINTZ * - Shapefile::SHAPE_TYPE_POLYLINEZ * - Shapefile::SHAPE_TYPE_POLYGONZ * - Shapefile::SHAPE_TYPE_MULTIPOINTZ * - Shapefile::SHAPE_TYPE_POINTM * - Shapefile::SHAPE_TYPE_POLYLINEM * - Shapefile::SHAPE_TYPE_POLYGONM * - Shapefile::SHAPE_TYPE_MULTIPOINTM * * @return self Returns $this to provide a fluent interface. */ protected function setShapeType($type) { if ($this->shape_type !== null) { throw new ShapefileException(Shapefile::ERR_SHP_TYPE_ALREADY_SET); } if (!isset(Shapefile::$shape_types[$type])) { throw new ShapefileException(Shapefile::ERR_SHP_TYPE_NOT_SUPPORTED, $type); } $this->shape_type = $type; return $this; } /** * Gets Shapefile base type, regardless of Z and M dimensions. * * @return int */ protected function getBasetype() { return $this->getShapeType(Shapefile::FORMAT_INT) % 10; } /** * Overwrites computed bounding box for the Shapefile. * 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. */ protected function overwriteComputedBoundingBox($bounding_box) { if ($bounding_box) { $this->computed_bounding_box = $this->sanitizeBoundingBox($bounding_box); } return $this; } /** * Sets a custom bounding box for the Shapefile. * 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. */ protected function setCustomBoundingBox($bounding_box) { $this->custom_bounding_box = $this->sanitizeBoundingBox($bounding_box); return $this; } /** * Resets custom bounding box for the Shapefile. * 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. */ protected function resetCustomBoundingBox() { $this->custom_bounding_box = null; return $this; } /** * Sets PRJ well-known-text. * * @param string $prj PRJ well-known-text. * Pass a falsy value (eg. false or "") to delete it. * * @return self Returns $this to provide a fluent interface. */ protected function setPRJ($prj) { $this->prj = $prj ?: null; return $this; } /** * Sets current total number of records. * * @param int $tot_records Total number of records currently in the files. * * @return self Returns $this to provide a fluent interface. */ protected function setTotRecords($tot_records) { $this->tot_records = $tot_records; return $this; } /** * Gets the state of the initialized flag. * * @return bool */ protected function isInitialized() { return $this->flag_initialized; } /** * Sets the state of the initialized flag. * * @param bool $value * * @return self Returns $this to provide a fluent interface. */ protected function setFlagInitialized($value) { $this->flag_initialized = $value; return $this; } /** * Adds a field to the shapefile definition. * Returns the effective field name after eventual sanitization. * * @param string $name Name of the field. Invalid names will be sanitized * (maximum 10 characters, only letters, numbers and underscores are allowed). * @param string $type Type of the field. It can be on of the following: * - Shapefile::DBF_TYPE_CHAR * - Shapefile::DBF_TYPE_DATE * - Shapefile::DBF_TYPE_LOGICAL * - Shapefile::DBF_TYPE_MEMO * - Shapefile::DBF_TYPE_NUMERIC * - Shapefile::DBF_TYPE_FLOAT * @param int $size Lenght of the field, depending on the type. * @param int $decimals Optional number of decimal digits for numeric type. * * @return string */ protected function addField($name, $type, $size, $decimals) { // Check init if ($this->isInitialized()) { throw new ShapefileException(Shapefile::ERR_SHP_FILE_ALREADY_INITIALIZED); } // Check filed count if (count($this->fields) >= Shapefile::DBF_MAX_FIELD_COUNT) { throw new ShapefileException(Shapefile::ERR_DBF_MAX_FIELD_COUNT_REACHED, Shapefile::DBF_MAX_FIELD_COUNT); } // Sanitize name and normalize case $name = $this->normalizeDBFFieldNameCase($this->sanitizeDBFFieldName($name)); // Check type if ( $type !== Shapefile::DBF_TYPE_CHAR && $type !== Shapefile::DBF_TYPE_DATE && $type !== Shapefile::DBF_TYPE_LOGICAL && $type !== Shapefile::DBF_TYPE_MEMO && $type !== Shapefile::DBF_TYPE_NUMERIC && $type !== Shapefile::DBF_TYPE_FLOAT ) { throw new ShapefileException(Shapefile::ERR_DBF_FIELD_TYPE_NOT_VALID, "$name - $type"); } // Check size $size = intval($size); $max_size = $this->getOption(Shapefile::OPTION_DBF_ALLOW_FIELD_SIZE_255) ? 255 : 254; if ( ($size < 1) || ($type == Shapefile::DBF_TYPE_CHAR && $size > $max_size) || ($type == Shapefile::DBF_TYPE_DATE && $size !== 8) || ($type == Shapefile::DBF_TYPE_LOGICAL && $size !== 1) || ($type == Shapefile::DBF_TYPE_MEMO && $size !== 10) || ($type == Shapefile::DBF_TYPE_NUMERIC && $size > $max_size) || ($type == Shapefile::DBF_TYPE_FLOAT && $size > $max_size) ) { throw new ShapefileException(Shapefile::ERR_DBF_FIELD_SIZE_NOT_VALID, "$name - $type - $size"); } // Minimal decimal formal check $decimals = intval($decimals); if ( ($type != Shapefile::DBF_TYPE_NUMERIC && $type != Shapefile::DBF_TYPE_FLOAT && $decimals !== 0) || ($type == Shapefile::DBF_TYPE_FLOAT && $decimals === 0) || ($decimals < 0) || ($decimals > 0 && $size - 1 <= $decimals) ) { throw new ShapefileException(Shapefile::ERR_DBF_FIELD_DECIMALS_NOT_VALID, "$name - $type - $decimals"); } // Add field $this->fields[$name] = [ 'type' => $type, 'size' => $size, 'decimals' => $decimals, ]; return $name; } /** * Normalize field name case according to OPTION_DBF_FORCE_ALL_CAPS status. * * @param string $input Field name to be case-normalized. * * @return string */ protected function normalizeDBFFieldNameCase($input) { return $this->getOption(Shapefile::OPTION_DBF_FORCE_ALL_CAPS) ? strtoupper($input) : $input; } /** * Pairs a Geometry with the Shapefile. * It enforces the Geometry type and computes Shapefile bounding box. * After that the Shapefile will be considered as "initialized" and no changes will be allowd to its structure. * * @param \Shapefile\Geometry\Geometry $Geometry Geometry to pair with. * * @return self Returns $this to provide a fluent interface. */ protected function pairGeometry(Geometry\Geometry $Geometry) { // Geometry type if ( $this->getBasetype() !== $Geometry->getSHPBasetype() || (!$Geometry->isEmpty() && $Geometry->isZ() !== $this->isZ() && !$this->getOption(Shapefile::OPTION_SUPPRESS_Z)) || (!$Geometry->isEmpty() && $Geometry->isM() !== $this->isM() && !$this->getOption(Shapefile::OPTION_SUPPRESS_M)) ) { throw new ShapefileException(Shapefile::ERR_SHP_GEOMETRY_TYPE_NOT_COMPATIBLE, $this->getShapeType(Shapefile::FORMAT_INT) . ' - ' . $this->getShapeType(Shapefile::FORMAT_STR)); } // Bounding box $bbox = $Geometry->getBoundingBox(); if (!$this->computed_bounding_box && $bbox) { if ($this->getOption(Shapefile::OPTION_SUPPRESS_Z)) { unset($bbox['zmin'], $bbox['zmax']); } if ($this->getOption(Shapefile::OPTION_SUPPRESS_M)) { unset($bbox['mmin'], $bbox['mmax']); } $this->computed_bounding_box = $bbox; } elseif ($bbox) { if ($bbox['xmin'] < $this->computed_bounding_box['xmin']) { $this->computed_bounding_box['xmin'] = $bbox['xmin']; } if ($bbox['xmax'] > $this->computed_bounding_box['xmax']) { $this->computed_bounding_box['xmax'] = $bbox['xmax']; } if ($bbox['ymin'] < $this->computed_bounding_box['ymin']) { $this->computed_bounding_box['ymin'] = $bbox['ymin']; } if ($bbox['ymax'] > $this->computed_bounding_box['ymax']) { $this->computed_bounding_box['ymax'] = $bbox['ymax']; } if ($this->isZ() && !$this->getOption(Shapefile::OPTION_SUPPRESS_Z)) { if ($bbox['zmin'] < $this->computed_bounding_box['zmin']) { $this->computed_bounding_box['zmin'] = $bbox['zmin']; } if ($bbox['zmax'] > $this->computed_bounding_box['zmax']) { $this->computed_bounding_box['zmax'] = $bbox['zmax']; } } if ($this->isM() && !$this->getOption(Shapefile::OPTION_SUPPRESS_M)) { if ($this->computed_bounding_box['mmin'] === false || $bbox['mmin'] < $this->computed_bounding_box['mmin']) { $this->computed_bounding_box['mmin'] = $bbox['mmin']; } if ($this->computed_bounding_box['mmax'] === false || $bbox['mmax'] > $this->computed_bounding_box['mmax']) { $this->computed_bounding_box['mmax'] = $bbox['mmax']; } } } // Mark Shapefile as initialized $this->setFlagInitialized(true); return $this; } /////////////////////////////// PRIVATE /////////////////////////////// /** * Checks formal compliance of a bounding box dimensions. * * @param array $bounding_box Associative array with the xmin, xmax, ymin, ymax and optional zmin, zmax, mmin, mmax values. */ private function sanitizeBoundingBox($bounding_box) { $bounding_box = array_intersect_key($bounding_box, array_flip(['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax', 'mmin', 'mmax'])); if ($this->getOption(Shapefile::OPTION_SUPPRESS_Z)) { unset($bounding_box['zmin'], $bounding_box['zmax']); } if ($this->getOption(Shapefile::OPTION_SUPPRESS_M)) { unset($bounding_box['mmin'], $bounding_box['mmax']); } if ( !isset($bounding_box['xmin'], $bounding_box['xmax'], $bounding_box['ymin'], $bounding_box['ymax']) || ( ($this->isZ() && !$this->getOption(Shapefile::OPTION_SUPPRESS_Z) && !isset($bounding_box['zmin'], $bounding_box['zmax'])) || (!$this->isZ() && (isset($bounding_box['zmin']) || isset($bounding_box['zmax']))) ) || ( ($this->isM() && !$this->getOption(Shapefile::OPTION_SUPPRESS_M) && !isset($bounding_box['mmin'], $bounding_box['mmax'])) || (!$this->isM() && (isset($bounding_box['mmin']) || isset($bounding_box['mmax']))) ) ) { throw new ShapefileException(Shapefile::ERR_SHP_MISMATCHED_BBOX); } return $bounding_box; } /** * Returns a valid name for a DBF field. * * Only letters, numbers and underscores are allowed, everything else is converted to underscores. * Field names get truncated to 10 characters and conflicting ones are truncated to 8 characters adding a number from 1 to 99. * * @param string $input Raw name to be sanitized. * * @return string */ private function sanitizeDBFFieldName($input) { if ($input === '') { return $input; } $ret = substr(preg_replace('/[^a-zA-Z0-9]/', '_', $input), 0, 10); $fieldnames = array_fill_keys(array_keys(array_change_key_case($this->fields, CASE_UPPER)), true); if (isset($fieldnames[strtoupper($ret)])) { $ret = substr($ret, 0, 8) . '_1'; while (isset($fieldnames[strtoupper($ret)])) { $n = intval(trim(substr($ret, -2), '_')) + 1; if ($n > 99) { throw new ShapefileException(Shapefile::ERR_DBF_FIELD_NAME_NOT_VALID, $input); } $ret = substr($ret, 0, -2) . str_pad($n, 2, '_', STR_PAD_LEFT); } } return $ret; } }