vendor/gasparesganga/php-shapefile/src/Shapefile/ShapefileWriter.php line 91

Open in your IDE?
  1. <?php
  2. /**
  3.  * PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
  4.  *
  5.  * @package Shapefile
  6.  * @author  Gaspare Sganga
  7.  * @version 3.4.0
  8.  * @license MIT
  9.  * @link    https://gasparesganga.com/labs/php-shapefile/
  10.  */
  11. namespace Shapefile;
  12. /**
  13.  * ShapefileWriter class.
  14.  */
  15. class ShapefileWriter extends Shapefile
  16. {
  17.     /** SHP pack methods hash */
  18.     private static $shp_pack_methods = [
  19.         Shapefile::SHAPE_TYPE_NULL          => 'packNull',
  20.         Shapefile::SHAPE_TYPE_POINT         => 'packPoint',
  21.         Shapefile::SHAPE_TYPE_POLYLINE      => 'packPolyLine',
  22.         Shapefile::SHAPE_TYPE_POLYGON       => 'packPolygon',
  23.         Shapefile::SHAPE_TYPE_MULTIPOINT    => 'packMultiPoint',
  24.     ];
  25.     
  26.     /** Buffered file types */
  27.     private static $buffered_files = [
  28.         Shapefile::FILE_SHP,
  29.         Shapefile::FILE_SHX,
  30.         Shapefile::FILE_DBF,
  31.         Shapefile::FILE_DBT,
  32.     ];
  33.     
  34.     
  35.     /**
  36.      * @var array   File writing buffers.
  37.      */
  38.     private $buffers = [];
  39.     
  40.     /**
  41.      * @var int     Buffered records count.
  42.      */
  43.     private $buffered_record_count 0;
  44.      
  45.     /**
  46.      * @var int     Current offset in SHP file and buffer (in 16-bit words).
  47.      *              First 50 16-bit are reserved for file header.
  48.      */
  49.     private $shp_current_offset 50;
  50.     
  51.     /**
  52.      * @var int     Next available block in DBT file.
  53.      */
  54.     private $dbt_next_available_block 0;
  55.     
  56.     /**
  57.      * @var bool    Flag representing whether file headers have been initialized or not.
  58.      */
  59.     private $flag_init_headers false;
  60.     
  61.     
  62.     
  63.     /////////////////////////////// PUBLIC ///////////////////////////////
  64.     /**
  65.      * Constructor.
  66.      *
  67.      * @param   string|array    $files      Path to SHP file / Array of paths / Array of handles of individual files.
  68.      * @param   array           $options    Optional associative array of options.
  69.      */
  70.     public function __construct($files$options = [])
  71.     {
  72.         // Options
  73.         $this->initOptions([
  74.             Shapefile::OPTION_BUFFERED_RECORDS,
  75.             Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET,
  76.             Shapefile::OPTION_DBF_ALLOW_FIELD_SIZE_255,
  77.             Shapefile::OPTION_DBF_FORCE_ALL_CAPS,
  78.             Shapefile::OPTION_DBF_NULL_PADDING_CHAR,
  79.             Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES,
  80.             Shapefile::OPTION_DELETE_EMPTY_FILES,
  81.             Shapefile::OPTION_ENFORCE_GEOMETRY_DATA_STRUCTURE,
  82.             Shapefile::OPTION_EXISTING_FILES_MODE,
  83.             Shapefile::OPTION_SUPPRESS_M,
  84.             Shapefile::OPTION_SUPPRESS_Z,
  85.         ], $options);
  86.         
  87.         // Open files
  88.         $this->openFiles($filestrue);
  89.         
  90.         // Init Buffers
  91.         $this->buffers array_fill_keys(array_intersect(self::$buffered_filesarray_keys($this->getFiles())), '');
  92.         
  93.         // Mode overwrite
  94.         if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_OVERWRITE) {
  95.             $this->truncateFiles();
  96.         }
  97.         
  98.         // Mode append
  99.         if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_APPEND && $this->getFileSize(Shapefile::FILE_SHP) > 0) {
  100.             // Open Shapefile in reading mode
  101.             $ShapefileReader = new ShapefileReader($this->getFiles(), [
  102.                 Shapefile::OPTION_DBF_CONVERT_TO_UTF8   => false,
  103.                 Shapefile::OPTION_DBF_FORCE_ALL_CAPS    => $this->getOption(Shapefile::OPTION_DBF_FORCE_ALL_CAPS),
  104.                 Shapefile::OPTION_DBF_IGNORED_FIELDS    => [],
  105.                 Shapefile::OPTION_IGNORE_SHAPEFILE_BBOX => false,
  106.             ]);
  107.             // Shape type
  108.             $this->setShapeType($ShapefileReader->getShapeType(Shapefile::FORMAT_INT));
  109.             // PRJ
  110.             $this->setPRJ($ShapefileReader->getPRJ());
  111.             // Charset
  112.             $this->setCharset($ShapefileReader->getCharset());
  113.             // Bounding Box
  114.             $this->overwriteComputedBoundingBox($ShapefileReader->getBoundingBox());
  115.             // Fields
  116.             foreach ($ShapefileReader->getFields() as $name => $field) {
  117.                 $this->addField($name$field['type'], $field['size'], $field['decimals']);
  118.             }
  119.             // Next DBT available block
  120.             if ($this->isFileOpen(Shapefile::FILE_DBT) && $this->getFileSize(Shapefile::FILE_DBT) > 0) {
  121.                 $this->dbt_next_available_block = ($this->getFileSize(Shapefile::FILE_DBT) / Shapefile::DBT_BLOCK_SIZE);
  122.             }
  123.             // Number of records
  124.             $this->setTotRecords($ShapefileReader->getTotRecords());
  125.             // Close Shapefile in reading mode
  126.             $ShapefileReader null;
  127.             
  128.             if ($this->getTotRecords() > 0) {
  129.                 // Mark Shapefile as initialized
  130.                 $this->setFlagInitialized(true);
  131.                 // Set flag init headers
  132.                 $this->flag_init_headers true;
  133.                 // SHP current offset (in 16-bit words)
  134.                 $this->shp_current_offset $this->getFileSize(Shapefile::FILE_SHP) / 2;
  135.                 // Remove DBF EOF marker
  136.                 $dbf_size_without_eof $this->getFileSize(Shapefile::FILE_DBF) - 1;
  137.                 $this->setFilePointer(Shapefile::FILE_DBF$dbf_size_without_eof);
  138.                 if ($this->readData(Shapefile::FILE_DBF1) === $this->packChar(Shapefile::DBF_EOF_MARKER)) {
  139.                     $this->fileTruncate(Shapefile::FILE_DBF$dbf_size_without_eof);
  140.                 }
  141.                 // Reset pointers
  142.                 foreach (array_keys($this->getFiles()) as $file_type) {
  143.                     $this->resetFilePointer($file_type);
  144.                 }
  145.             } else {
  146.                 $this->truncateFiles();
  147.             }
  148.         }
  149.     }
  150.     
  151.     /**
  152.      * Destructor.
  153.      *
  154.      * Finalizes open files.
  155.      * If files were NOT passed as stream resources, empty useless files will be removed.
  156.      */
  157.     public function __destruct()
  158.     {
  159.         // Flush buffers
  160.         $this->writeBuffers();
  161.         
  162.         // Try setting Shapefile as NULL SHAPE if it hasn't been initialized yet (no records written)
  163.         if (!$this->isInitialized()) {
  164.             try {
  165.                 $this->setShapeType(Shapefile::SHAPE_TYPE_NULL);
  166.             } catch (ShapefileException $e) {
  167.                 // Nothing.
  168.             }
  169.         }
  170.         
  171.         // Set buffered file pointers to beginning of files
  172.         foreach (array_keys($this->buffers) as $file_type) {
  173.             $this->setFilePointer($file_type0);
  174.         }
  175.         // Write SHP, SHX, DBF and DBT headers to buffers
  176.         $this->bufferData(Shapefile::FILE_SHP$this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHP)));
  177.         $this->bufferData(Shapefile::FILE_SHX$this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHX)));
  178.         $this->bufferData(Shapefile::FILE_DBF$this->packDBFHeader());
  179.         if ($this->dbt_next_available_block 0) {
  180.             $this->bufferData(Shapefile::FILE_DBT$this->packDBTHeader());
  181.         }
  182.         // Write buffers containing the headers
  183.         $this->writeBuffers();
  184.         // Reset buffered file pointers
  185.         foreach (array_keys($this->buffers) as $file_type) {
  186.             $this->resetFilePointer($file_type);
  187.         }
  188.         
  189.         // Write DBF EOF marker
  190.         $this->writeData(Shapefile::FILE_DBF$this->packChar(Shapefile::DBF_EOF_MARKER));
  191.         
  192.         // Write PRJ file
  193.         if ($this->isFileOpen(Shapefile::FILE_PRJ)) {
  194.             $this->fileTruncate(Shapefile::FILE_PRJ);
  195.             $this->writeData(Shapefile::FILE_PRJ$this->packString($this->getPRJ()));
  196.         }
  197.         
  198.         // Write CPG file
  199.         if ($this->isFileOpen(Shapefile::FILE_CPG)) {
  200.             $this->fileTruncate(Shapefile::FILE_CPG);
  201.             if ($this->getCharset() !== Shapefile::DBF_DEFAULT_CHARSET || $this->getOption(Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET)) {
  202.                 $this->writeData(Shapefile::FILE_CPG$this->packString($this->getCharset()));
  203.             }
  204.         }
  205.         
  206.         // Close files and delete empty ones
  207.         $this->closeFiles();
  208.         if ($this->getOption(Shapefile::OPTION_DELETE_EMPTY_FILES)) {
  209.             foreach ($this->getFilenames() as $filename) {
  210.                 if (filesize($filename) === 0) {
  211.                     unlink($filename);
  212.                 }
  213.             }
  214.         }
  215.     }
  216.     
  217.     
  218.     public function setShapeType($type)
  219.     {
  220.         return parent::setShapeType($type);
  221.     }
  222.     
  223.     public function setCustomBoundingBox($bounding_box)
  224.     {
  225.         return parent::setCustomBoundingBox($bounding_box);
  226.     }
  227.     
  228.     public function resetCustomBoundingBox()
  229.     {
  230.         return parent::resetCustomBoundingBox();
  231.     }
  232.     
  233.     public function setPRJ($prj)
  234.     {
  235.         return parent::setPRJ($prj);
  236.     }
  237.     
  238.     
  239.     public function addField($name$type$size$decimals)
  240.     {
  241.         return parent::addField($name$type$size$decimals);
  242.     }
  243.     
  244.     /**
  245.      * Adds a char field to the Shapefile definition.
  246.      * Returns the effective field name after eventual sanitization.
  247.      *
  248.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  249.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  250.      *                                      Only letters, numbers and underscores are allowed.
  251.      * @param   int     $size               Lenght of the field, between 1 and 254 characters. Defaults to 254.
  252.      *
  253.      * @return  string
  254.      */
  255.     public function addCharField($name$size 254)
  256.     {
  257.         return $this->addField($nameShapefile::DBF_TYPE_CHAR$size0);
  258.     }
  259.     
  260.     /**
  261.      * Adds a date field to the Shapefile definition.
  262.      * Returns the effective field name after eventual sanitization.
  263.      *
  264.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  265.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  266.      *                                      Only letters, numbers and underscores are allowed.
  267.      *
  268.      * @return  string
  269.      */
  270.     public function addDateField($name)
  271.     {
  272.         return $this->addField($nameShapefile::DBF_TYPE_DATE80);
  273.     }
  274.     
  275.     /**
  276.      * Adds a logical/boolean field to the Shapefile definition.
  277.      * Returns the effective field name after eventual sanitization.
  278.      *
  279.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  280.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  281.      *                                      Only letters, numbers and underscores are allowed.
  282.      *
  283.      * @return  string
  284.      */
  285.     public function addLogicalField($name)
  286.     {
  287.         return $this->addField($nameShapefile::DBF_TYPE_LOGICAL10);
  288.     }
  289.     
  290.     /**
  291.      * Adds a memo field to the Shapefile definition.
  292.      * Returns the effective field name after eventual sanitization.
  293.      *
  294.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  295.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  296.      *                                      Only letters, numbers and underscores are allowed.
  297.      *
  298.      * @return  string
  299.      */
  300.     public function addMemoField($name)
  301.     {
  302.         return $this->addField($nameShapefile::DBF_TYPE_MEMO100);
  303.     }
  304.     
  305.     /**
  306.      * Adds numeric to the Shapefile definition.
  307.      * Returns the effective field name after eventual sanitization.
  308.      *
  309.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  310.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  311.      *                                      Only letters, numbers and underscores are allowed.
  312.      * @param   int     $size               Lenght of the field, between 1 and 254 characters. Defaults to 10.
  313.      * @param   int     $decimals           Optional number of decimal digits. Defaults to 0.
  314.      *
  315.      * @return  string
  316.      */
  317.     public function addNumericField($name$size 10$decimals 0)
  318.     {
  319.         return $this->addField($nameShapefile::DBF_TYPE_NUMERIC$size$decimals);
  320.     }
  321.     
  322.     /**
  323.      * Adds floating point to the Shapefile definition.
  324.      * Returns the effective field name after eventual sanitization.
  325.      *
  326.      * @param   string  $name               Name of the field. Invalid names will be sanitized
  327.      *                                      (maximum 10 characters, only letters, numbers and underscores are allowed).
  328.      *                                      Only letters, numbers and underscores are allowed.
  329.      * @param   int     $size               Lenght of the field, between 1 and 254 characters. Defaults to 20.
  330.      * @param   int     $decimals           Number of decimal digits. Defaults to 10.
  331.      *
  332.      * @return  string
  333.      */
  334.     public function addFloatField($name$size 20$decimals 10)
  335.     {
  336.         return $this->addField($nameShapefile::DBF_TYPE_FLOAT$size$decimals);
  337.     }
  338.     
  339.     
  340.     /**
  341.      * Writes a record to the Shapefile.
  342.      *
  343.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Geometry to write.
  344.      *
  345.      * @return  self    Returns $this to provide a fluent interface.
  346.      */
  347.     public function writeRecord(Geometry\Geometry $Geometry)
  348.     {
  349.         // Init headers
  350.         if (!$this->flag_init_headers) {
  351.             $this->bufferData(Shapefile::FILE_SHP$this->packNulPadding(Shapefile::SHP_HEADER_SIZE));
  352.             $this->bufferData(Shapefile::FILE_SHX$this->packNulPadding(Shapefile::SHX_HEADER_SIZE));
  353.             $this->bufferData(Shapefile::FILE_DBF$this->packNulPadding($this->getDBFHeaderSize()));
  354.             if (in_array(Shapefile::DBF_TYPE_MEMO$this->arrayColumn($this->getFields(), 'type'))) {
  355.                 if (!$this->isFileOpen(Shapefile::FILE_DBT)) {
  356.                     throw new ShapefileException(Shapefile::ERR_FILE_MISSINGstrtoupper(Shapefile::FILE_DBT));
  357.                 }
  358.                 $this->bufferData(Shapefile::FILE_DBT$this->packNulPadding(Shapefile::DBT_BLOCK_SIZE));
  359.                 ++$this->dbt_next_available_block;
  360.             }
  361.             $this->flag_init_headers true;
  362.         }
  363.         
  364.         // Pair with Geometry
  365.         $this->pairGeometry($Geometry);
  366.         
  367.         // Write data to temporary buffers to make sure no exceptions are raised within current record
  368.         $temp $this->packSHPAndSHXData($Geometry) + $this->packDBFAndDBTData($Geometry);
  369.         // Write data to real buffers
  370.         foreach (array_keys($this->buffers) as $file_type) {
  371.             $this->bufferData($file_type$temp[$file_type]);
  372.         }
  373.         $this->shp_current_offset       $temp['shp_current_offset'];
  374.         $this->dbt_next_available_block $temp['dbt_next_available_block'];
  375.         $this->setTotRecords($this->getTotRecords() + 1);
  376.         ++$this->buffered_record_count;
  377.         
  378.         // Eventually flush buffers
  379.         $option_buffered_records $this->getOption(Shapefile::OPTION_BUFFERED_RECORDS);
  380.         if ($option_buffered_records && $this->buffered_record_count == $option_buffered_records) {
  381.             $this->writeBuffers();
  382.         }
  383.         
  384.         return $this;
  385.     }
  386.     
  387.     /**
  388.      * Writes buffers to files.
  389.      *
  390.      * @return  self    Returns $this to provide a fluent interface.
  391.      */
  392.     public function flushBuffer()
  393.     {
  394.         $this->writeBuffers();
  395.         return $this;
  396.     }
  397.     
  398.     
  399.     
  400.     /////////////////////////////// PRIVATE ///////////////////////////////
  401.     /**
  402.      * Truncates open files.
  403.      */
  404.     private function truncateFiles()
  405.     {
  406.         foreach (array_keys($this->getFiles()) as $file_type) {
  407.             if ($this->getFileSize($file_type) > 0) {
  408.                 $this->fileTruncate($file_type);
  409.                 $this->setFilePointer($file_type0);
  410.             }
  411.         }
  412.     }
  413.     
  414.     
  415.     /**
  416.      * Stores binary string packed data into a buffer.
  417.      *
  418.      * @param   string  $file_type      File type.
  419.      * @param   string  $data           String value to write.
  420.      *
  421.      * @return  self    Returns $this to provide a fluent interface.
  422.      */
  423.     private function bufferData($file_type$data)
  424.     {
  425.         $this->buffers[$file_type] .= $data;
  426.         return $this;
  427.     }
  428.     
  429.     /**
  430.      * Writes buffers to files.
  431.      *
  432.      * @return  self    Returns $this to provide a fluent interface.
  433.      */
  434.     private function writeBuffers()
  435.     {
  436.         foreach ($this->buffers as $file_type => $buffer) {
  437.             if ($buffer !== '') {
  438.                 $this->writeData($file_type$buffer);
  439.                 $this->buffers[$file_type] = '';
  440.             }
  441.         }
  442.         $this->buffered_record_count 0;
  443.         return $this;
  444.     }
  445.     
  446.     
  447.     /**
  448.      * Packs an unsigned char into binary string.
  449.      *
  450.      * @param   string  $data       Value to pack.
  451.      *
  452.      * @return  string
  453.      */
  454.     private function packChar($data)
  455.     {
  456.         return pack('C'$data);
  457.     }
  458.     
  459.     /**
  460.      * Packs an unsigned short, 16 bit, little endian byte order, into binary string.
  461.      *
  462.      * @param   string  $data       Value to pack.
  463.      *
  464.      * @return  string
  465.      */
  466.     private function packInt16L($data)
  467.     {
  468.         return pack('v'$data);
  469.     }
  470.     
  471.     /**
  472.      * Packs an unsigned long, 32 bit, big endian byte order, into binary string.
  473.      *
  474.      * @param   string  $data       Value to pack.
  475.      *
  476.      * @return  string
  477.      */
  478.     private function packInt32B($data)
  479.     {
  480.         return pack('N'$data);
  481.     }
  482.     
  483.     /**
  484.      * Packs an unsigned long, 32 bit, little endian byte order, into binary string.
  485.      *
  486.      * @param   string  $data       Value to pack.
  487.      *
  488.      * @return  string
  489.      */
  490.     private function packInt32L($data)
  491.     {
  492.         return pack('V'$data);
  493.     }
  494.     
  495.     /**
  496.      * Packs a double, 64 bit, little endian byte order, into binary string.
  497.      *
  498.      * @param   string  $data       Value to pack.
  499.      *
  500.      * @return  string
  501.      */
  502.     private function packDoubleL($data)
  503.     {
  504.         $data pack('d'$data);
  505.         if ($this->isBigEndianMachine()) {
  506.             $data strrev($data);
  507.         }
  508.         return $data;
  509.     }
  510.     
  511.     /**
  512.      * Packs a string into binary string.
  513.      *
  514.      * @param   string  $data       Value to pack.
  515.      *
  516.      * @return  string
  517.      */
  518.     private function packString($data)
  519.     {
  520.         return pack('A*'$data);
  521.     }
  522.     
  523.     /**
  524.      * Packs a NUL-padding of given length into binary string.
  525.      *
  526.      * @param   string  $lenght     Length of the padding to pack.
  527.      *
  528.      * @return  string
  529.      */
  530.     private function packNulPadding($lenght)
  531.     {
  532.         return pack('a*'str_repeat("\0"$lenght));
  533.     }
  534.     
  535.     
  536.     /**
  537.      * Packs some XY coordinates into binary string.
  538.      *
  539.      * @param   array   $coordinates    Array with "x" and "y" coordinates.
  540.      *
  541.      * @return  string
  542.      */
  543.     private function packXY($coordinates)
  544.     {
  545.         return $this->packDoubleL($coordinates['x'])
  546.              . $this->packDoubleL($coordinates['y']);
  547.     }
  548.     
  549.     /**
  550.      * Packs a Z coordinate into binary string.
  551.      *
  552.      * @param   array   $coordinates    Array with "z" coordinate.
  553.      *
  554.      * @return  string
  555.      */
  556.     private function packZ($coordinates)
  557.     {
  558.         return $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_Z) ? $coordinates['z']);
  559.     }
  560.     
  561.     /**
  562.      * Packs an M coordinate into binary string.
  563.      *
  564.      * @param   array   $coordinates    Array with "m" coordinate.
  565.      *
  566.      * @return  string
  567.      */
  568.     private function packM($coordinates)
  569.     {
  570.         return $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_M) ? $this->parseM($coordinates['m']));
  571.     }
  572.     
  573.     /**
  574.      * Parses an M coordinate according to the ESRI specs:
  575.      * Â«Any floating point number smaller than â€“10^38 is considered by a shapefile reader to represent a "no data" value»
  576.      * This library uses bool value false to represent "no data".
  577.      *
  578.      * @param   float|bool  $value  Value to parse.
  579.      *
  580.      * @return  float
  581.      */
  582.     private function parseM($value)
  583.     {
  584.         return ($value === false) ? Shapefile::SHP_NO_DATA_VALUE $value;
  585.     }
  586.     
  587.     
  588.     /**
  589.      * Packs an XY bounding box into binary string.
  590.      *
  591.      * @param   array   $bounding_box   Associative array with xmin, xmax, ymin, ymax values.
  592.      *
  593.      * @return  string
  594.      */
  595.     private function packXYBoundingBox($bounding_box)
  596.     {
  597.         return $this->packDoubleL($bounding_box['xmin'])
  598.              . $this->packDoubleL($bounding_box['ymin'])
  599.              . $this->packDoubleL($bounding_box['xmax'])
  600.              . $this->packDoubleL($bounding_box['ymax']);
  601.     }
  602.     
  603.     /**
  604.      * Packs a Z range into binary string.
  605.      *
  606.      * @param   array   $bounding_box   Associative array with zmin and zmax values.
  607.      *
  608.      * @return  string
  609.      */
  610.     private function packZRange($bounding_box)
  611.     {
  612.         return $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_Z) ? $bounding_box['zmin'])
  613.              . $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_Z) ? $bounding_box['zmax']);
  614.     }
  615.     
  616.     /**
  617.      * Packs an M range into binary string.
  618.      *
  619.      * @param   array   $bounding_box   Associative array with mmin and mmax values.
  620.      *
  621.      * @return  string
  622.      */
  623.     private function packMRange($bounding_box)
  624.     {
  625.         return $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_M) ? $this->parseM($bounding_box['mmin']))
  626.              . $this->packDoubleL($this->getOption(Shapefile::OPTION_SUPPRESS_M) ? $this->parseM($bounding_box['mmax']));
  627.     }
  628.     
  629.     
  630.     /**
  631.      * Packs a Null shape into binary string.
  632.      *
  633.      * @return  string
  634.      */
  635.     private function packNull()
  636.     {
  637.         // Shape type
  638.         return $this->packInt32L(Shapefile::SHAPE_TYPE_NULL);
  639.     }
  640.     
  641.     /**
  642.      * Packs a Point, PointM or PointZ shape into binary string.
  643.      *
  644.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Geometry to pack.
  645.      *
  646.      * @return  string
  647.      */
  648.     private function packPoint(Geometry\Geometry $Geometry)
  649.     {
  650.         $array      $Geometry->getArray();
  651.         $is_m       $this->isM();
  652.         $is_z       $this->isZ();
  653.         $shape_type $is_z Shapefile::SHAPE_TYPE_POINTZ : ($is_m Shapefile::SHAPE_TYPE_POINTM Shapefile::SHAPE_TYPE_POINT);
  654.         
  655.         // Shape type
  656.         $ret $this->packInt32L($shape_type);
  657.         // XY Coordinates
  658.         $ret .= $this->packXY($array);
  659.         
  660.         if ($is_z) {
  661.             // Z Coordinate
  662.             $ret .= $this->packZ($array);
  663.         }
  664.         
  665.         if ($is_m) {
  666.             // M Coordinate
  667.             $ret .= $this->packM($array);
  668.         }
  669.         
  670.         return $ret;
  671.     }
  672.     
  673.     /**
  674.      * Packs a MultiPoint, MultiPointM or MultiPointZ shape into binary string.
  675.      *
  676.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Geometry to pack.
  677.      *
  678.      * @return  string
  679.      */
  680.     private function packMultiPoint(Geometry\Geometry $Geometry)
  681.     {
  682.         $array          $Geometry->getArray();
  683.         $bounding_box   $Geometry->getBoundingBox();
  684.         $is_m           $this->isM();
  685.         $is_z           $this->isZ();
  686.         $shape_type     $is_z Shapefile::SHAPE_TYPE_MULTIPOINTZ : ($is_m Shapefile::SHAPE_TYPE_MULTIPOINTM Shapefile::SHAPE_TYPE_MULTIPOINT);
  687.         
  688.         // Shape type
  689.         $ret $this->packInt32L($shape_type);
  690.         // XY Bounding Box
  691.         $ret .= $this->packXYBoundingBox($bounding_box);
  692.         // NumPoints
  693.         $ret .= $this->packInt32L($array['numpoints']);
  694.         // Points
  695.         foreach ($array['points'] as $coordinates) {
  696.             $ret .= $this->packXY($coordinates);
  697.         }
  698.         
  699.         if ($is_z) {
  700.             // Z Range
  701.             $ret .= $this->packZRange($bounding_box);
  702.             // Z Array
  703.             foreach ($array['points'] as $coordinates) {
  704.                 $ret .= $this->packZ($coordinates);
  705.             }
  706.         }
  707.         
  708.         if ($is_m) {
  709.             // M Range
  710.             $ret .= $this->packMRange($bounding_box);
  711.             // M Array
  712.             foreach ($array['points'] as $coordinates) {
  713.                 $ret .= $this->packM($coordinates);
  714.             }
  715.         }
  716.         
  717.         return $ret;
  718.     }
  719.     
  720.     /**
  721.      * Packs a PolyLine, PolyLineM or PolyLineZ shape into binary string.
  722.      *
  723.      * @param   \Shapefile\Geometry\Geometry    $Geometry       Geometry to pack.
  724.      * @param   bool                            $flag_polygon   Optional flag to pack Polygon shapes.
  725.      *
  726.      * @return  string
  727.      */
  728.     private function packPolyLine(Geometry\Geometry $Geometry$flag_polygon false)
  729.     {
  730.         $array          $Geometry->getArray();
  731.         $bounding_box   $Geometry->getBoundingBox();
  732.         $is_m           $this->isM();
  733.         $is_z           $this->isZ();
  734.         if ($flag_polygon) {
  735.             $shape_type $is_z Shapefile::SHAPE_TYPE_POLYGONZ : ($is_m Shapefile::SHAPE_TYPE_POLYGONM Shapefile::SHAPE_TYPE_POLYGON);
  736.         } else {
  737.             $shape_type $is_z Shapefile::SHAPE_TYPE_POLYLINEZ : ($is_m Shapefile::SHAPE_TYPE_POLYLINEM Shapefile::SHAPE_TYPE_POLYLINE);
  738.         }
  739.         
  740.         // PolyLines and Polygons are always MultiLinestrings and MultiPolygons in Shapefiles
  741.         if (!isset($array['parts'])) {
  742.             $array = [
  743.                 'numparts'  => 1,
  744.                 'parts'     => [$array],
  745.             ];
  746.         }
  747.         
  748.         // Polygons need to be reduced as PolyLines
  749.         if ($flag_polygon) {
  750.             $parts = [];
  751.             foreach ($array['parts'] as $part) {
  752.                 foreach ($part['rings'] as $ring) {
  753.                     $parts[] = $ring;
  754.                 }
  755.             }
  756.             $array = [
  757.                 'numparts'  => count($parts),
  758.                 'parts'     => $parts,
  759.             ];
  760.         }
  761.         
  762.         // Shape type
  763.         $ret $this->packInt32L($shape_type);
  764.         // XY Bounding Box
  765.         $ret .= $this->packXYBoundingBox($bounding_box);
  766.         // NumParts
  767.         $ret .= $this->packInt32L($array['numparts']);
  768.         // NumPoints
  769.         $ret .= $this->packInt32L(array_sum($this->arrayColumn($array['parts'], 'numpoints')));
  770.         // Parts
  771.         $part_first_index 0;
  772.         foreach ($array['parts'] as $part) {
  773.             $ret .= $this->packInt32L($part_first_index);
  774.             $part_first_index += $part['numpoints'];
  775.         }
  776.         // Points
  777.         foreach ($array['parts'] as $part) {
  778.             foreach ($part['points'] as $coordinates) {
  779.                 $ret .= $this->packXY($coordinates);
  780.             }
  781.         }
  782.         
  783.         if ($is_z) {
  784.             // Z Range
  785.             $ret .= $this->packZRange($bounding_box);
  786.             // Z Array
  787.             foreach ($array['parts'] as $part) {
  788.                 foreach ($part['points'] as $coordinates) {
  789.                     $ret .= $this->packZ($coordinates);
  790.                 }
  791.             }
  792.         }
  793.         
  794.         if ($is_m) {
  795.             // M Range
  796.             $ret .= $this->packMRange($bounding_box);
  797.             // M Array
  798.             foreach ($array['parts'] as $part) {
  799.                 foreach ($part['points'] as $coordinates) {
  800.                     $ret .= $this->packM($coordinates);
  801.                 }
  802.             }
  803.         }
  804.         
  805.         return $ret;
  806.     }
  807.     
  808.     /**
  809.      * Packs a Polygon, PolygonM or PolygonZ shape into binary string.
  810.      * It forces closed rings and clockwise orientation in order to comply with ESRI Shapefile specifications.
  811.      *
  812.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Geometry to pack.
  813.      *
  814.      * @return  string
  815.      */
  816.     private function packPolygon(Geometry\Geometry $Geometry)
  817.     {
  818.         $Geometry->forceClosedRings();
  819.         $Geometry->forceClockwise();
  820.         return $this->packPolyLine($Geometrytrue);
  821.     }
  822.     
  823.     
  824.     /**
  825.      * Packs SHP and SHX data from a Geometry object into binary strings and returns an array with SHP, SHX and "shp_current_offset" members.
  826.      *
  827.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Input Geometry.
  828.      *
  829.      * @return  array
  830.      */
  831.     private function packSHPAndSHXData(Geometry\Geometry $Geometry)
  832.     {
  833.         // Pack Geometry data
  834.         $method self::$shp_pack_methods[$Geometry->isEmpty() ? Shapefile::SHAPE_TYPE_NULL $this->getBasetype()];
  835.         $shp_data $this->{$method}($Geometry);
  836.         
  837.         // Compute content lenght in 16-bit words
  838.         $shp_content_length strlen($shp_data) / 2;
  839.         
  840.         return [
  841.             Shapefile::FILE_SHP     => $this->packInt32B($this->getTotRecords() + 1)
  842.                                      . $this->packInt32B($shp_content_length)
  843.                                      . $shp_data,
  844.             Shapefile::FILE_SHX     => $this->packInt32B($this->shp_current_offset)
  845.                                      . $this->packInt32B($shp_content_length),
  846.             'shp_current_offset'    => $this->shp_current_offset $shp_content_length 4,
  847.         ];
  848.     }
  849.     
  850.     /**
  851.      * Packs DBF and DBT data from a Geometry object into binary strings and returns an array with DBF, DBT and "dbt_next_available_block" members.
  852.      *
  853.      * @param   \Shapefile\Geometry\Geometry    $Geometry   Input Geometry.
  854.      *
  855.      * @return  array
  856.      */
  857.     private function packDBFAndDBTData(Geometry\Geometry $Geometry)
  858.     {
  859.         $ret = [
  860.             Shapefile::FILE_DBF         => '',
  861.             Shapefile::FILE_DBT         => '',
  862.             'dbt_next_available_block'  => $this->dbt_next_available_block,
  863.         ];
  864.         
  865.         // Deleted flag
  866.         $ret[Shapefile::FILE_DBF] = $this->packChar($Geometry->isDeleted() ? Shapefile::DBF_DELETED_MARKER Shapefile::DBF_BLANK);
  867.         
  868.         // Data
  869.         $data $Geometry->getDataArray();
  870.         if ($this->getOption(Shapefile::OPTION_DBF_FORCE_ALL_CAPS)) {
  871.             $data array_change_key_case($dataCASE_UPPER);
  872.         }
  873.         foreach ($this->getFields() as $name => $field) {
  874.             if (!array_key_exists($name$data)) {
  875.                 if ($this->getOption(Shapefile::OPTION_ENFORCE_GEOMETRY_DATA_STRUCTURE)) {
  876.                     throw new ShapefileException(Shapefile::ERR_GEOM_MISSING_FIELD$name);
  877.                 }
  878.                 $data[$name] = null;
  879.             }
  880.             $value $this->encodeFieldValue($field['type'], $field['size'], $field['decimals'], $data[$name]);
  881.             // Memo (DBT)
  882.             if ($field['type'] == Shapefile::DBF_TYPE_MEMO && $value !== null) {
  883.                 $dbt    $this->packDBTData($value$field['size']);
  884.                 $value  str_pad($ret['dbt_next_available_block'], $field['size'], chr(Shapefile::DBF_BLANK), STR_PAD_LEFT);
  885.                 $ret[Shapefile::FILE_DBT]           .= $dbt['data'];
  886.                 $ret['dbt_next_available_block']    += $dbt['blocks'];
  887.             }
  888.             // Null
  889.             if ($value === null) {
  890.                 $value str_repeat(($this->getOption(Shapefile::OPTION_DBF_NULL_PADDING_CHAR) !== null $this->getOption(Shapefile::OPTION_DBF_NULL_PADDING_CHAR) : chr(Shapefile::DBF_BLANK)), $field['size']);
  891.             }
  892.             // Add packed value to temp buffer
  893.             $ret[Shapefile::FILE_DBF] .= $this->packString($value);
  894.         }
  895.         
  896.         return $ret;
  897.     }
  898.     
  899.     /**
  900.      * Packs DBT data into a binary string and return an array with "blocks" and "data" members.
  901.      *
  902.      * @param   string  $data           Data to write
  903.      * @param   int     $field_size     Size of the DBF field.
  904.      *
  905.      * @return  array
  906.      */
  907.     private function packDBTData($data$field_size)
  908.     {
  909.         $ret = [
  910.             'blocks'    => 0,
  911.             'data'      => '',
  912.         ];
  913.         
  914.         // Ignore empty values
  915.         if ($data === '') {
  916.             $ret['data'] = str_repeat(chr(Shapefile::DBF_BLANK), $field_size);
  917.         } else {
  918.             // Corner case: there's not enough space at the end of the last block for 2 field terminators. Add a space and switch to the next block!
  919.             if (strlen($data) % Shapefile::DBT_BLOCK_SIZE == Shapefile::DBT_BLOCK_SIZE 1) {
  920.                 $data .= chr(Shapefile::DBF_BLANK);
  921.             }
  922.             // Add TWO field terminators
  923.             $data .= str_repeat(chr(Shapefile::DBT_FIELD_TERMINATOR), 2);
  924.             // Write data to DBT buffer
  925.             foreach (str_split($dataShapefile::DBT_BLOCK_SIZE) as $block) {
  926.                 $ret['blocks']  += 1;
  927.                 $ret['data']    .= $this->packString(str_pad($blockShapefile::DBT_BLOCK_SIZE"\0"STR_PAD_RIGHT));
  928.             }
  929.         }
  930.         
  931.         return $ret;
  932.     }
  933.     
  934.     /**
  935.      * Encodes a value to be written into a DBF field as a raw string.
  936.      *
  937.      * @param   string  $type       Type of the field.
  938.      * @param   int     $size       Lenght of the field.
  939.      * @param   int     $decimals   Number of decimal digits for numeric type.
  940.      * @param   string  $value      Value to encode.
  941.      *
  942.      * @return  string|null
  943.      */
  944.     private function encodeFieldValue($type$size$decimals$value)
  945.     {
  946.         switch ($type) {
  947.             case Shapefile::DBF_TYPE_CHAR:
  948.                 if ($value !== null) {
  949.                     $value $this->truncateOrPadString($value$size);
  950.                 }
  951.                 break;
  952.             
  953.             case Shapefile::DBF_TYPE_DATE:
  954.                 if (is_a($value'DateTime')) {
  955.                     $value $value->format('Ymd');
  956.                 } elseif ($value !== null) {
  957.                     // Try YYYY-MM-DD format
  958.                     $DateTime   = \DateTime::createFromFormat('Y-m-d'$value);
  959.                     $errors     = \DateTime::getLastErrors();
  960.                     if ($errors['warning_count'] || $errors['error_count']) {
  961.                         // Try YYYYMMDD format
  962.                         $DateTime   = \DateTime::createFromFormat('Ymd'$value);
  963.                         $errors     = \DateTime::getLastErrors();
  964.                     }
  965.                     if ($errors['warning_count'] || $errors['error_count']) {
  966.                         $value $this->getOption(Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES) ? null $this->truncateOrPadString($this->sanitizeNumber($value), $size);
  967.                     } else {
  968.                         $value $DateTime->format('Ymd');
  969.                     }
  970.                 }
  971.                 break;
  972.             
  973.             case Shapefile::DBF_TYPE_LOGICAL:
  974.                 if (is_string($value)) {
  975.                     $value substr(trim($value), 01);
  976.                     if (strpos(Shapefile::DBF_VALUE_MASK_TRUE$value) !== false) {
  977.                         $value Shapefile::DBF_VALUE_TRUE;
  978.                     } elseif (strpos(Shapefile::DBF_VALUE_MASK_FALSE$value) !== false) {
  979.                         $value Shapefile::DBF_VALUE_FALSE;
  980.                     } else {
  981.                         $value Shapefile::DBF_VALUE_NULL;
  982.                     }
  983.                 } elseif (is_bool($value) || is_int($value) || is_float($value)) {
  984.                     $value $value Shapefile::DBF_VALUE_TRUE Shapefile::DBF_VALUE_FALSE;
  985.                 } else {
  986.                     $value Shapefile::DBF_VALUE_NULL;
  987.                 }
  988.                 break;
  989.             
  990.             case Shapefile::DBF_TYPE_MEMO:
  991.                 if ($value !== null) {
  992.                     $value = (string) $value;
  993.                 }
  994.                 break;
  995.             
  996.             case Shapefile::DBF_TYPE_NUMERIC:
  997.             case Shapefile::DBF_TYPE_FLOAT:
  998.                 if ($value !== null) {
  999.                     if (is_string($value)) {
  1000.                         $value          trim($value);
  1001.                         $flag_negative  substr($value01) === '-';
  1002.                         $intpart        $this->sanitizeNumber(strpos($value'.') === false $value strstr($value'.'true));
  1003.                         $decpart        $this->sanitizeNumber(substr(strstr($value'.'false), 1));
  1004.                         $decpart        strlen($decpart) > $decimals substr($decpart0$decimals) : str_pad($decpart$decimals'0'STR_PAD_RIGHT);
  1005.                         $value          = ($flag_negative '-' '') . $intpart . ($decimals '.' '') . $decpart;
  1006.                     } else {
  1007.                         $value number_format(floatval($value), $decimals'.''');
  1008.                     }
  1009.                     if (strlen($value) > $size) {
  1010.                         throw new ShapefileException(Shapefile::ERR_INPUT_NUMERIC_VALUE_OVERFLOW"value:$intpart - size:($size.$decimals)");
  1011.                     }
  1012.                     $value str_pad($value$sizechr(Shapefile::DBF_BLANK), STR_PAD_LEFT);
  1013.                 }
  1014.                 break;
  1015.         }
  1016.         
  1017.         return $value;
  1018.     }
  1019.     
  1020.     /**
  1021.      * Truncates long input strings and right-pads short ones to maximum/minimun lenght.
  1022.      *
  1023.      * @param   string  $value      Value to pad.
  1024.      * @param   int     $size       Lenght of the field.
  1025.      *
  1026.      * @return  string
  1027.      */
  1028.     private function truncateOrPadString($value$size)
  1029.     {
  1030.         return str_pad(substr($value0$size), $sizechr(Shapefile::DBF_BLANK), STR_PAD_RIGHT);
  1031.     }
  1032.     
  1033.     /**
  1034.      * Removes illegal characters from a numeric string.
  1035.      *
  1036.      * @param   string  $value      Value to sanitize.
  1037.      *
  1038.      * @return  string
  1039.      */
  1040.     private function sanitizeNumber($value)
  1041.     {
  1042.         return preg_replace('/[^0-9]/'''$value);
  1043.     }
  1044.     
  1045.     
  1046.     /**
  1047.      * Packs SHP or SHX file header into a binary string.
  1048.      *
  1049.      * @param   int     $file_size      File size in bytes.
  1050.      *
  1051.      * @return  string
  1052.      */
  1053.     private function packSHPOrSHXHeader($file_size)
  1054.     {
  1055.         $ret '';
  1056.         
  1057.         // File Code
  1058.         $ret .= $this->packInt32B(Shapefile::SHP_FILE_CODE);
  1059.         
  1060.         // Unused bytes
  1061.         $ret .= $this->packNulPadding(20);
  1062.         
  1063.         // File Length (in 16-bit words)
  1064.         $ret .= $this->packInt32B($file_size 2);
  1065.         
  1066.         // Version
  1067.         $ret .= $this->packInt32L(Shapefile::SHP_VERSION);
  1068.         
  1069.         // Shape Type
  1070.         $ret .= $this->packInt32L($this->getShapeType(Shapefile::FORMAT_INT));
  1071.         
  1072.         //Bounding Box (Defaults to all zeros for empty Shapefile)
  1073.         $bounding_box $this->getBoundingBox() ?: [
  1074.             'xmin' => -Shapefile::SHP_NO_DATA_VALUE,
  1075.             'ymin' => -Shapefile::SHP_NO_DATA_VALUE,
  1076.             'xmax' => Shapefile::SHP_NO_DATA_VALUE,
  1077.             'ymax' => Shapefile::SHP_NO_DATA_VALUE,
  1078.             'zmin' => -Shapefile::SHP_NO_DATA_VALUE,
  1079.             'zmax' => Shapefile::SHP_NO_DATA_VALUE,
  1080.             'mmin' => -Shapefile::SHP_NO_DATA_VALUE,
  1081.             'mmax' => Shapefile::SHP_NO_DATA_VALUE,
  1082.         ];
  1083.         $ret .= $this->packXYBoundingBox($bounding_box);
  1084.         $ret .= $this->packZRange($this->isZ() ? $bounding_box : ['zmin' => 0'zmax' => 0]);
  1085.         $ret .= $this->packMRange($this->isM() ? $bounding_box : ['mmin' => 0'mmax' => 0]);
  1086.         
  1087.         return $ret;
  1088.     }
  1089.     
  1090.     /**
  1091.      * Packs DBF file header into a binary string.
  1092.      *
  1093.      * @return  string
  1094.      */
  1095.     private function packDBFHeader()
  1096.     {
  1097.         $ret '';
  1098.         
  1099.         // Version number
  1100.         $ret .= $this->packChar($this->dbt_next_available_block Shapefile::DBF_VERSION_WITH_DBT Shapefile::DBF_VERSION);
  1101.         
  1102.         // Date of last update
  1103.         $ret .= $this->packChar(intval(date('Y')) - 1900);
  1104.         $ret .= $this->packChar(intval(date('m')));
  1105.         $ret .= $this->packChar(intval(date('d')));
  1106.         
  1107.         // Number of records
  1108.         $ret .= $this->packInt32L($this->getTotRecords());
  1109.         // Header size
  1110.         $ret .= $this->packInt16L($this->getDBFHeaderSize());
  1111.         
  1112.          // Record size
  1113.         $ret .= $this->packInt16L($this->getDBFRecordSize());
  1114.         
  1115.         // Reserved bytes
  1116.         $ret .= $this->packNulPadding(20);
  1117.         
  1118.         // Field descriptor array
  1119.         foreach ($this->getFields() as $name => $field) {
  1120.             // Name
  1121.             $ret .= $this->packString(str_pad($name10"\0"STR_PAD_RIGHT));
  1122.             $ret .= $this->packNulPadding(1);
  1123.             // Type
  1124.             $ret .= $this->packString($field['type']);
  1125.             $ret .= $this->packNulPadding(4);
  1126.             // Size
  1127.             $ret .= $this->packChar($field['size']);
  1128.             // Decimals
  1129.             $ret .= $this->packChar($field['decimals']);
  1130.             $ret .= $this->packNulPadding(14);
  1131.         }
  1132.         
  1133.         // Field terminator
  1134.         $ret .= $this->packChar(Shapefile::DBF_FIELD_TERMINATOR);
  1135.         
  1136.         return $ret;
  1137.     }
  1138.     
  1139.     /**
  1140.      * Packs DBT file header into a binary string.
  1141.      *
  1142.      * @return  string
  1143.      */
  1144.     private function packDBTHeader()
  1145.     {
  1146.         $ret '';
  1147.         
  1148.         // Next available block
  1149.         $ret .= $this->packInt32L($this->dbt_next_available_block);
  1150.         
  1151.         // Reserved bytes
  1152.         $ret .= $this->packNulPadding(12);
  1153.         
  1154.         // Version number
  1155.         $ret .= $this->packChar(Shapefile::DBF_VERSION);
  1156.         
  1157.         return $ret;
  1158.     }
  1159.     
  1160.     /**
  1161.      * Computes DBF header size.
  1162.      * 32bytes + (number of fields x 32) + 1 (field terminator character).
  1163.      *
  1164.      * @return  int
  1165.      */
  1166.     private function getDBFHeaderSize()
  1167.     {
  1168.         return 33 + (32 count($this->getFields()));
  1169.     }
  1170.     
  1171.     /**
  1172.      * Computes DBF record size.
  1173.      * Sum of all fields sizes + 1 (record deleted flag).
  1174.      *
  1175.      * @return  int
  1176.      */
  1177.     private function getDBFRecordSize()
  1178.     {
  1179.         return array_sum($this->arrayColumn($this->getFields(), 'size')) + 1;
  1180.     }
  1181.         
  1182.     
  1183.     /**
  1184.      * Substitute for PHP 5.5 array_column() function.
  1185.      *
  1186.      * @param   array   $array      Multidimensional array.
  1187.      * @param   string  $key        Key of the column to return.
  1188.      *
  1189.      * @return  array
  1190.      */
  1191.     private function arrayColumn($array$key)
  1192.     {
  1193.         return array_map(function ($element) use ($key) {
  1194.             return $element[$key];
  1195.         }, $array);
  1196.     }
  1197. }