├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── DbfFile.php ├── Exception │ ├── DbfFileException.php │ └── ShapeFileException.php ├── ShapeReader.php ├── ShapeRecord.php └── dbase.php └── tests ├── muka └── ShapeReader │ └── ShapeReaderTest.php └── support └── worldcities ├── worldcities.dbf ├── worldcities.qix ├── worldcities.shp └── worldcities.shx /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject/ 2 | vendor/ 3 | composer.lock 4 | /.buildpath 5 | /.project 6 | /.settings/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # see http://about.travis-ci.org/docs/user/languages/php/ for more hints 2 | language: php 3 | 4 | # list any PHP version you want to test against 5 | php: 6 | # using major version aliases 7 | 8 | 9 | # aliased to a recent 5.4.x version 10 | - 5.4 11 | # aliased to a recent 5.5.x version 12 | - 5.5 13 | # aliased to a recent 5.6.x version 14 | - 5.6 15 | # aliased to a recent 7.x version 16 | #- 7.0 17 | # aliased to a recent hhvm version 18 | #- hhvm 19 | 20 | # omitting "script:" will default to phpunit 21 | 22 | before_script: 23 | - composer install 24 | - pecl install dbase 25 | - echo 'extension = dbase.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini 26 | 27 | script: phpunit --configuration phpunit.xml 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShapeReader 2 | A PHP library to parse ESRI Shape files. 3 | 4 | Based on the great work of Juan Carlos Gonzalez Ulloa and David Granqvist 5 | 6 | A copy of the original work is available at http://www.phpclasses.org/package/1741-PHP-Read-vectorial-data-from-geographic-shape-files.html 7 | 8 | `This library is meant to read vectorial information from shape files in the SHP format.` 9 | `The SHP file format is an open standard for storing vectorial information that is used to distribute geographical information.` 10 | `Plenty of commercial and open source applications are able to read from it.` 11 | 12 | ## Requirements 13 | PHP version should be > 5.3.2 14 | 15 | To open the DBF related database you need the dbase extension available as PECL package. 16 | 17 | pecl install dbase 18 | echo "extension=dbase.so" > /etc/php5/conf.d/dbase.ini 19 | 20 | ## Usage 21 | See examples folder for details. 22 | 23 | $shpReader = new ShapeReader("./somewhere.shp"); 24 | 25 | $i = 0; 26 | while ($record = $shpReader->getNext() and $i < 5) { 27 | 28 | //Dump SHP information 29 | $shp_data = $record->getData(); 30 | var_dump($shp_data); 31 | 32 | //Dump DBF information 33 | $dbf_data = $record->getDbfData(); 34 | var_dump($dbf_data); 35 | 36 | $i++; 37 | } 38 | 39 | ## Changelog 40 | 2013-08-24 - Base refactor, added namespace support, composer and test cases 41 | 42 | ## License 43 | GNU General Public License 44 | http://opensource.org/licenses/GPL-2.0 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "muka/shape-reader", 3 | "description": "A PHP library to parse ESRI Shape files", 4 | "keywords": ["Shapefile", "shp", "dbf", "geospatial"], 5 | "license": "GPL", 6 | "authors": [ 7 | { 8 | "name": "Lopez Mateos" 9 | }, 10 | { 11 | "name": "David Granqvist" 12 | }, 13 | { 14 | "name": "Luca Capra" 15 | }, 16 | { 17 | "name": "Peter Haza", 18 | "email": "peter.haza@gmail.com" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.4" 23 | }, 24 | "autoload": { 25 | "psr-4": {"muka\\ShapeReader\\": "src/"} 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "~4.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/DbfFile.php: -------------------------------------------------------------------------------- 1 | filename = $this->getFilename($shpFilename); 17 | $this->options = $options; 18 | 19 | } 20 | 21 | public function getFilename($filename) 22 | { 23 | 24 | if ($this->filename) { 25 | return $this->filename; 26 | } 27 | 28 | if (!strstr($filename, ".")) { 29 | $filename .= ".dbf"; 30 | } 31 | 32 | if (substr($filename, strlen($filename) - 3, 3) != "dbf") { 33 | $filename = substr($filename, 0, strlen($filename) - 3) . "dbf"; 34 | } 35 | 36 | return $filename; 37 | } 38 | 39 | public function getData($record_number) 40 | { 41 | 42 | $this->record_number = $record_number; 43 | $this->load(); 44 | 45 | return $this->data; 46 | } 47 | 48 | public function setData(array $row) 49 | { 50 | 51 | $this->open(true); 52 | unset($row["deleted"]); 53 | 54 | if (!dbase_replace_record($this->dbf, array_values($row), $this->record_number)) { 55 | throw new Exception\DbfFileException("Error writing data to file."); 56 | } else { 57 | $this->data = $row; 58 | } 59 | 60 | $this->close(); 61 | } 62 | 63 | private function open($check_writeable = false) 64 | { 65 | $check_function = $check_writeable ? "is_writable" : "is_readable"; 66 | if ($check_function($this->filename)) { 67 | $this->dbf = dbase_open($this->filename, ($check_writeable ? 2 : 0)); 68 | if (!$this->dbf) { 69 | throw new Exception\DbfFileException(sprintf("Error loading %s", $this->filename)); 70 | } 71 | } else { 72 | throw new Exception\DbfFileException(sprintf("File doesn't exists (%s)", $this->filename)); 73 | } 74 | } 75 | 76 | public function __destruct() 77 | { 78 | $this->close(); 79 | } 80 | 81 | private function close() 82 | { 83 | if ($this->dbf) { 84 | dbase_close($this->dbf); 85 | $this->dbf = null; 86 | } 87 | } 88 | 89 | private function load() 90 | { 91 | 92 | if (!$this->dbf) { 93 | $this->open(); 94 | } 95 | //$this->open(); 96 | $this->data = dbase_get_record_with_names($this->dbf, $this->record_number); 97 | if (!isset($this->options['normalize']) 98 | || (isset($this->options['normalize']) && $this->options['normalize'])) { 99 | $this->normalize(); 100 | } 101 | //$this->close(); 102 | } 103 | 104 | private function normalize() 105 | { 106 | foreach ($this->data as $key => &$value) { 107 | $value = trim(utf8_encode($value)); 108 | } 109 | } 110 | 111 | } 112 | 113 | if (!function_exists('dbase_open')) { 114 | include __DIR__ . DIRECTORY_SEPARATOR . 'dbase.php'; 115 | } 116 | -------------------------------------------------------------------------------- /src/Exception/DbfFileException.php: -------------------------------------------------------------------------------- 1 | 6 | * Innovacion Inteligente S de RL de CV (Innox) 7 | * Lopez Mateos Sur 2077 - Z16 8 | * Col. Jardines de Plaza del Sol 9 | * Guadalajara, Jalisco 10 | * CP 44510 11 | * Mexico 12 | * 13 | * Edited by David Granqvist March 2008 for better performance on large files 14 | * Refactored by Luca Capra 15 | */ 16 | namespace muka\ShapeReader; 17 | 18 | use muka\ShapeReader\Exception\ShapeFileException; 19 | 20 | class ShapeReader { 21 | private $filename; 22 | protected $fp; 23 | private $dbf; 24 | private $fpos = 100; 25 | private $fsize = 0; 26 | private $options; 27 | private $bbox = []; 28 | private $point_count = 0; 29 | public $XY_POINT_RECORD_LENGTH = 16; 30 | 31 | // $XYM_POINT_RECORD_LENGTH represents xy point plus measure. 32 | // xy points are seperated from m points by mmin[8], mmax[8] 33 | // this only reflects the size of one xy and m point 34 | public $XYM_POINT_RECORD_LENGTH = 24; 35 | 36 | // xyz represents xy point plus measure(m), and z. 37 | // xy points are seperated from z points by zmin[8], zmax[8] and m points are 38 | // seperated from z points by mmin[8], mmax[8] 39 | // this only reflects the size of one xy m z point 40 | public $XYZ_POINT_RECORD_LENGTH = 32; 41 | 42 | // the size of [zmin, zmax], or [mmin, mmax] 43 | public $RANGE_LENGTH = 16; 44 | protected $data; 45 | private $shp_type = 0; 46 | 47 | public function __construct($filename, $options = []) { 48 | 49 | $this->filename = $filename; 50 | 51 | $this->fopen(); 52 | $this->readConfig(); 53 | $this->options = $options; 54 | $this->dbf = new DbfFile($this->filename, $this->options); 55 | } 56 | 57 | public function __destruct() { 58 | 59 | $this->closeFile(); 60 | } 61 | 62 | private function closeFile() { 63 | 64 | if ($this->fp) { 65 | fclose($this->fp); 66 | } 67 | } 68 | 69 | private function fopen() { 70 | 71 | if (!is_readable($this->filename)) { 72 | throw new ShapeFileException(sprintf("%s is not readable.", $this->filename)); 73 | } 74 | $this->fp = fopen($this->filename, "rb"); 75 | $fstat = fstat($this->fp); 76 | $this->fsize = $fstat['size']; 77 | } 78 | 79 | private function readConfig() { 80 | 81 | fseek($this->fp, 32, SEEK_SET); 82 | $this->shp_type = $this->readAndUnpack("i", fread($this->fp, 4)); 83 | $this->bbox = $this->readBoundingBox(); 84 | } 85 | 86 | public function getNext() { 87 | 88 | if (!feof($this->fp) && $this->fpos < $this->fsize) { 89 | 90 | fseek($this->fp, $this->fpos); 91 | $record = new ShapeRecord($this->fp, $this->filename, $this->options, $this->dbf); 92 | $this->fpos = $record->getNextRecordPosition(); 93 | 94 | return $record; 95 | } 96 | 97 | return false; 98 | } 99 | 100 | protected function readBoundingBox() { 101 | 102 | $data = []; 103 | $data["xmin"] = $this->readAndUnpack("d", fread($this->fp, 8)); 104 | $data["ymin"] = $this->readAndUnpack("d", fread($this->fp, 8)); 105 | $data["xmax"] = $this->readAndUnpack("d", fread($this->fp, 8)); 106 | $data["ymax"] = $this->readAndUnpack("d", fread($this->fp, 8)); 107 | 108 | return $data; 109 | } 110 | 111 | protected function readAndUnpack($type, $data) { 112 | 113 | if (!$data) { 114 | return $data; 115 | } 116 | 117 | return current(unpack($type, $data)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/ShapeRecord.php: -------------------------------------------------------------------------------- 1 | "RecordNull", 34 | 1 => "RecordPoint", 35 | 3 => "RecordPolyLine", 36 | 5 => "RecordPolygon", 37 | 8 => "RecordMultiPoint", 38 | 11 => "RecordPointZ", 39 | 13 => "RecordPolyLineZ", 40 | 15 => "RecordPolygonZ", 41 | 18 => "RecordMultiPointZ", 42 | 21 => "RecordPointM", 43 | 23 => "RecordPolyLineM", 44 | 25 => "RecordPolygonM", 45 | 28 => "RecordMultiPointM", 46 | 31 => "RecordMultiPatch" 47 | ]; 48 | 49 | 50 | /** 51 | * 52 | * @var DbfFile 53 | */ 54 | private $dbf; 55 | 56 | public function __construct(&$fp, $filename, $options, $dbf = null) { 57 | 58 | $this->fp = $fp; 59 | $this->fpos = ftell($fp); 60 | $this->options = $options; 61 | $this->filename = $filename; 62 | $this->dbf = $dbf; 63 | $this->readHeader(); 64 | } 65 | 66 | public function __destruct() { 67 | // overriden to prevent closing shared file pointer. 68 | } 69 | 70 | public function getNextRecordPosition() { 71 | 72 | $nextRecordPosition = $this->fpos + ((4 + $this->content_length) * 2); 73 | return $nextRecordPosition; 74 | } 75 | 76 | public function getDbfData() { 77 | 78 | if ($this->dbf) { 79 | return $this->dbf->getData($this->record_number); 80 | } 81 | return []; 82 | } 83 | 84 | public function getShpData() { 85 | 86 | return $this->getData(); 87 | } 88 | 89 | public function getData() { 90 | 91 | if (!$this->data) { 92 | $recordType = $this->getRecordClass(); 93 | $function_name = "read{$recordType}"; 94 | 95 | $this->data = $this->{$function_name}($this->options); 96 | $this->data['type'] = $this->getTypeLabel(); 97 | $this->data['typeCode'] = $this->getTypeCode(); 98 | } 99 | 100 | return $this->data; 101 | } 102 | 103 | private function readHeader() { 104 | 105 | $this->record_number = $this->readAndUnpack("N", fread($this->fp, 4)); 106 | $this->content_length = $this->readAndUnpack("N", fread($this->fp, 4)); 107 | $this->record_shape_type = $this->readAndUnpack("i", fread($this->fp, 4)); 108 | } 109 | 110 | public function getTypeCode() { 111 | 112 | return $this->record_shape_type; 113 | } 114 | 115 | public function getTypeLabel() { 116 | 117 | return str_replace("Record", "", $this->getRecordClass()); 118 | } 119 | 120 | private function getRecordClass() { 121 | 122 | if (!isset($this->record_class[$this->record_shape_type])) { 123 | throw new ShapeFileException(sprintf("Unsupported record type encountered.")); 124 | } 125 | 126 | if (!method_exists($this, "read" . $this->record_class[$this->record_shape_type])) { 127 | throw new ShapeFileException(sprintf("Record type %s not implemented.", $this->record_shape_type)); 128 | } 129 | 130 | return $this->record_class[$this->record_shape_type]; 131 | } 132 | 133 | /** 134 | * Reading functions 135 | */ 136 | private function readRecordNull($options = null) { 137 | 138 | return array(); 139 | } 140 | 141 | private function readRecordPoint($options = null) { 142 | 143 | $data = []; 144 | 145 | $data["x"] = $this->readAndUnpack("d", fread($this->fp, 8)); 146 | $data["y"] = $this->readAndUnpack("d", fread($this->fp, 8)); 147 | 148 | $this->point_count ++; 149 | 150 | return $data; 151 | } 152 | 153 | private function readRecordPointM($options = null) { 154 | 155 | $data = $this->readRecordPoint($this->fp); 156 | $nodata = -pow(10, 38); // any m smaller than this is considered "no data" 157 | $data["m"] = $this->readAndUnpack("d", fread($this->fp, 8)); 158 | if ($data['m'] < $nodata) { 159 | unset($data['m']); 160 | } 161 | return $data; 162 | } 163 | 164 | private function readRecordPointZ($create_object = false, $options = null) { 165 | 166 | $data = $this->readRecordPoint($this->fp); 167 | $data["z"] = $this->readAndUnpack("d", fread($this->fp, 8)); 168 | $nodata = -pow(10, 38); // any m smaller than this is considered "no data" 169 | $data["m"] = $this->readAndUnpack("d", fread($this->fp, 8)); 170 | if ($data['m'] < $nodata) { 171 | unset($data['m']); 172 | } 173 | return $data; 174 | } 175 | 176 | private function _readNumPoints(&$data) { 177 | 178 | $count = $this->readAndUnpack("i", fread($this->fp, 4)); 179 | $data["numpoints"] = $count; 180 | return $count; 181 | } 182 | 183 | private function _readNumParts(&$data) { 184 | 185 | $count = $this->readAndUnpack("i", fread($this->fp, 4)); 186 | $data["numparts"] = $count; 187 | return $count; 188 | } 189 | 190 | private function _readPoints(&$data) { 191 | 192 | for ($i = 0; $i <= $data["numpoints"]; $i ++) { 193 | $data["points"][] = $this->readRecordPoint($this->fp); 194 | } 195 | } 196 | 197 | private function _readMPoints(&$data) { 198 | 199 | // read mmin, mmax 200 | $nodata = -pow(10, 38); // any m smaller than this is considered "no data" 201 | $data['mmin'] = $this->readAndUnpack("d", fread($this->fp, 8)); 202 | if ($data['mmin'] < $nodata) { 203 | unset($data['mmin']); 204 | } 205 | $data['mmax'] = $this->readAndUnpack("d", fread($this->fp, 8)); 206 | if ($data['mmax'] < $nodata) { 207 | unset($data['mmax']); 208 | } 209 | 210 | for ($i = 0; $i <= $data["numpoints"]; $i ++) { 211 | $data["points"][$i]['m'] = $this->readAndUnpack("d", fread($this->fp, 8)); 212 | if ($data["points"][$i]['m'] < $nodata) { 213 | unset($data["points"][$i]['m']); 214 | } 215 | } 216 | } 217 | 218 | private function _readZPoints(&$data) { 219 | 220 | // read zmin, zmax 221 | $data['zmin'] = $this->readAndUnpack("d", fread($this->fp, 8)); 222 | $data['zmax'] = $this->readAndUnpack("d", fread($this->fp, 8)); 223 | 224 | for ($i = 0; $i <= $data["numpoints"]; $i ++) { 225 | $data["points"][$i]['z'] = $this->readAndUnpack("d", fread($this->fp, 8)); 226 | } 227 | } 228 | 229 | private function readRecordMultiPoint($options = null) { 230 | 231 | $data = $this->readBoundingBox(); 232 | $data["numpoints"] = $this->readAndUnpack("i", fread($this->fp, 4)); 233 | 234 | $this->_readPoints($data); 235 | 236 | return $data; 237 | } 238 | 239 | private function readRecordMultiPointM($options = null) { 240 | 241 | // [bounds:32], 242 | // [numpoints:4], 243 | // [point(1):16], 244 | // ... 245 | // [point(numpoints):16], 246 | // [mmin:8], 247 | // [mmax:8] 248 | // [m(1):16], 249 | // ... 250 | // [m(numpoints):16] 251 | $data = $this->readBoundingBox(); 252 | $this->_readNumPoints($data); 253 | $this->_readPoints($data); 254 | $this->_readMPoints($data); 255 | 256 | return $data; 257 | } 258 | 259 | private function readRecordMultiPointZ($options = null) { 260 | 261 | // [bounds:32], 262 | // [numpoints:4], 263 | // [point(1):16], 264 | // ... 265 | // [point(numpoints):16], 266 | // [zmin:8], 267 | // [zmax:8] 268 | // [z(1):16], 269 | // ... 270 | // [z(numpoints):16] 271 | // [mmin:8], 272 | // [mmax:8] 273 | // [m(1):16], 274 | // ... 275 | // [m(numpoints):16] 276 | $data = $this->readBoundingBox(); 277 | 278 | $this->_readNumPoints($data); 279 | $this->_readPoints($data); 280 | $this->_readZPoints($data); 281 | $this->_readMPoints($data); 282 | 283 | return $data; 284 | } 285 | 286 | private function _readPartIndexes(&$data) { 287 | 288 | $parts = []; 289 | $data['parts'] = []; 290 | for ($i = 0; $i < $data['numparts']; $i ++) { 291 | $parts[$i] = $this->readAndUnpack("i", fread($this->fp, 4)); 292 | $data["parts"][$i] = [ 293 | "points" => [] 294 | ]; 295 | } 296 | $data['partinfo']=$parts; 297 | 298 | return $parts; 299 | } 300 | 301 | private function _readPartPoints(&$data, $parts) { 302 | 303 | $points_read = 0; 304 | foreach ($parts as $part_index => $point_index) { 305 | 306 | $maxIndex=-1; 307 | if(count($parts)>$part_index+1){ 308 | $maxIndex=$parts[$part_index+1]; 309 | } 310 | 311 | while (($maxIndex<0||$points_read<$maxIndex) && $points_read < $data["numpoints"] && !feof($this->fp)) { 312 | 313 | $data["parts"][$part_index]["points"][] = $this->readRecordPoint($this->fp, true); 314 | 315 | $points_read ++; 316 | } 317 | } 318 | } 319 | 320 | private function _readPartMPoints(&$data, $parts) { 321 | // read mmin, mmax 322 | $nodata = -pow(10, 38); // any m smaller than this is considered "no data" 323 | $data['mmin'] = $this->readAndUnpack("d", fread($this->fp, 8)); 324 | if ($data['mmin'] < $nodata) { 325 | unset($data['mmin']); 326 | } 327 | $data['mmax'] = $this->readAndUnpack("d", fread($this->fp, 8)); 328 | if ($data['mmax'] < $nodata) { 329 | unset($data['mmax']); 330 | } 331 | 332 | $points_read = 0; 333 | 334 | foreach ($parts as $part_index => $point_index) { 335 | $point = 0; 336 | while (!in_array($points_read, $data["parts"]) && $points_read < $data["numpoints"] && !feof($this->fp)) { 337 | $data["parts"][$part_index]["points"][$point]['m'] = $this->readAndUnpack("d", fread($this->fp, 8)); 338 | if ($data["parts"][$part_index]["points"][$point]['m'] < $nodata) { 339 | unset($data["parts"][$part_index]["points"][$point]['m']); 340 | } 341 | $points_read ++; 342 | $point ++; 343 | } 344 | } 345 | } 346 | 347 | private function _readPartZPoints(&$data, $parts) { 348 | 349 | // read zmin, zmax 350 | $data['zmin'] = $this->readAndUnpack("d", fread($this->fp, 8)); 351 | $data['zmax'] = $this->readAndUnpack("d", fread($this->fp, 8)); 352 | 353 | $points_read = 0; 354 | foreach ($parts as $part_index => $point_index) { 355 | $point = 0; 356 | while (!in_array($points_read, $data["parts"]) && $points_read < $data["numpoints"] && !feof($this->fp)) { 357 | $data["parts"][$part_index]["points"][$point]['z'] = $this->readAndUnpack("d", fread($this->fp, 8)); 358 | $points_read ++; 359 | $point ++; 360 | } 361 | } 362 | } 363 | 364 | private function readRecordPolyLine($options = null) { 365 | 366 | $data = $this->readBoundingBox(); 367 | 368 | $countparts = $this->_readNumParts($data); 369 | $countpoints = $this->_readNumPoints($data); 370 | 371 | if (isset($options['noparts']) && $options['noparts'] == true) { 372 | fseek($this->fp, ftell($this->fp) + (4 * $countparts) + ($countpoints * $this->XY_POINT_RECORD_LENGTH)); 373 | } else { 374 | $parts = $this->_readPartIndexes($data); 375 | $this->_readPartPoints($data, $parts); 376 | } 377 | 378 | return $data; 379 | } 380 | 381 | private function readRecordPolyLineM($options = null) { 382 | 383 | // [bounds:32], 384 | // [numparts:4], 385 | // [numpoints:4], 386 | // [parts(1):4], 387 | // ... 388 | // [parts(numparts):4] 389 | // [point(1):16], 390 | // ... 391 | // [point(numpoints):16], 392 | // [mmin:8], 393 | // [mmax:8] 394 | // [m(1):16], 395 | // ... 396 | // [m(numpoints):16] 397 | $data = $this->readBoundingBox(); 398 | 399 | $countparts = $this->_readNumParts($data); 400 | $countpoints = $this->_readNumPoints($data); 401 | 402 | if (isset($options['noparts']) && $options['noparts'] == true) { 403 | // Skip the parts 404 | fseek($this->fp, 405 | ftell($this->fp) + (4 * $countparts) + ($countpoints * $this->XYM_POINT_RECORD_LENGTH) + 406 | $this->RANGE_LENGTH); 407 | } else { 408 | 409 | $parts = $this->_readPartIndexes($data); 410 | $this->_readPartPoints($data, $parts); 411 | $this->_readPartMPoints($data, $parts); 412 | } 413 | 414 | return $data; 415 | } 416 | 417 | private function readRecordPolyLineZ($options = null) { 418 | 419 | // [bounds:32], 420 | // [numparts:4], 421 | // [numpoints:4], 422 | // [parts(1):4], 423 | // ... 424 | // [parts(numparts):4] 425 | // [point(1):16], 426 | // ... 427 | // [point(numpoints):16], 428 | // [zmin:8], 429 | // [zmax:8] 430 | // [z(1):16], 431 | // ... 432 | // [z(numpoints):16] 433 | // [mmin:8], 434 | // [mmax:8] 435 | // [m(1):16], 436 | // ... 437 | // [m(numpoints):16] 438 | $data = $this->readBoundingBox(); 439 | 440 | $countparts = $this->_readNumParts($data); 441 | $countpoints = $this->_readNumPoints($data); 442 | 443 | if (isset($options['noparts']) && $options['noparts'] == true) { 444 | // Skip the parts 445 | fseek($this->fp, 446 | ftell($this->fp) + (4 * $countparts) + ($countpoints * $this->XYZ_POINT_RECORD_LENGTH) + 447 | (2 * $this->RANGE_LENGTH)); 448 | } else { 449 | 450 | $parts = $this->_readPartIndexes($data); 451 | $this->_readPartPoints($data, $parts); 452 | $this->_readPartZPoints($data, $parts); 453 | $this->_readPartMPoints($data, $parts); 454 | } 455 | 456 | return $data; 457 | } 458 | 459 | private function readRecordPolygon($options = null) { 460 | 461 | return $this->readRecordPolyLine($options); 462 | } 463 | 464 | private function readRecordPolygonM($options = null) { 465 | 466 | return $this->readRecordPolyLineM($options); 467 | } 468 | 469 | private function readRecordPolygonZ($options = null) { 470 | 471 | return $this->readRecordPolyLineZ($options); 472 | } 473 | 474 | private function _readPartTypes(&$data, $parts) { 475 | 476 | for ($i = 0; $i < $data["numparts"]; $i ++) { 477 | $data["parts"][$i]['type'] = $this->readAndUnpack("i", fread($this->fp, 4)); 478 | } 479 | } 480 | 481 | private function readRecordMultipatch($options = null) { 482 | 483 | // [bounds:32], 484 | // [numparts:4], 485 | // [numpoints:4], 486 | // [parts(1):4], 487 | // ... 488 | // [parts(numparts):4] 489 | // [parttypes(1):4], 490 | // ... 491 | // [parttypes(numparts):4] 492 | // [point(1):16], 493 | // ... 494 | // [point(numpoints):16], 495 | // [zmin:8], 496 | // [zmax:8] 497 | // [z(1):16], 498 | // ... 499 | // [z(numpoints):16] 500 | // [mmin:8], 501 | // [mmax:8] 502 | // [m(1):16], 503 | // ... 504 | // [m(numpoints):16] 505 | $data = $this->readBoundingBox(); 506 | 507 | $countparts = $this->_readNumParts($data); 508 | $countpoints = $this->_readNumPoints($data); 509 | 510 | if (isset($options['noparts']) && $options['noparts'] == true) { 511 | // Skip the parts 512 | fseek($this->fp, 513 | ftell($this->fp) + (8 * $countparts) + ($countpoints * $this->XYZ_POINT_RECORD_LENGTH) + 514 | (2 * $this->RANGE_LENGTH)); 515 | } else { 516 | 517 | $parts = $this->_readPartIndexes($data); 518 | $this->_readPartTypes($data, $parts); 519 | $this->_readPartPoints($data, $parts); 520 | $this->_readPartZPoints($data, $parts); 521 | $this->_readPartMPoints($data, $parts); 522 | } 523 | 524 | return $data; 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /src/dbase.php: -------------------------------------------------------------------------------- 1 | '; 14 | $goon = true; 15 | $unpackString = ''; 16 | while ($goon && !feof($fdbf)) { 17 | // read fields: 18 | $buf = fread($fdbf, 32); 19 | if (substr($buf, 0, 1) == chr(13)) {$goon = false;} // end of field list 20 | else { 21 | $field = unpack("a11fieldname/A1fieldtype/Voffset/Cfieldlen/Cfielddec", substr($buf, 0, 18)); 22 | //echo 'Field: ' . json_encode($field) . '
'; 23 | $unpackString .= "A$field[fieldlen]$field[fieldname]/"; 24 | array_push($fields, $field);}} 25 | fseek($fdbf, $header['FirstRecord'] + 1); // move back to the start of the first record (after the field definitions) 26 | 27 | $records = array(); 28 | for ($i = 1; $i <= $header['RecordCount']; $i++) { 29 | $buf = fread($fdbf, $header['RecordLength']); 30 | $record = unpack($unpackString, $buf); 31 | $records[] = unpack($unpackString, $buf); 32 | //echo 'record: '+$i+':' . json_encode($record) . '
'; 33 | //echo $i . $buf . '
'; 34 | } //raw record 35 | return array($fdbf, $fields, $records); 36 | } 37 | 38 | function dbase_get_record_with_names($dbf, $record) 39 | { 40 | 41 | //print_r(array_slice(debug_backtrace(),0,3)); 42 | //echo 'GET: '.$record.'=>'. print_r($dbf[2][$record-1],true)."
"; 43 | return $dbf[2][$record - 1]; 44 | 45 | } 46 | function dbase_close($dbf) 47 | { 48 | fclose($dbf[0]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/muka/ShapeReader/ShapeReaderTest.php: -------------------------------------------------------------------------------- 1 | worldcities_shape = __DIR__."/../../support/worldcities/worldcities.shp"; 21 | 22 | $this->shpReader = new ShapeReader($this->worldcities_shape); 23 | } 24 | 25 | public function testLoadShapefile() { 26 | 27 | $rows = 0; 28 | while ($record = $this->shpReader->getNext()) { 29 | $dbf = $record->getDbfData(); 30 | $rows++; 31 | } 32 | 33 | $this->assertEquals(12686, $rows); 34 | 35 | } 36 | 37 | public function testGetDbfData() { 38 | 39 | $rows = 0; 40 | $rowIndex = 11358; 41 | while ($record = $this->shpReader->getNext()) { 42 | $dbf = $record->getDbfData(); 43 | if($dbf['ID'] == $rowIndex) { 44 | break; 45 | } 46 | $rows++; 47 | } 48 | 49 | $this->assertEquals('Trento', $dbf['NAME']); 50 | 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /tests/support/worldcities/worldcities.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muka/ShapeReader/5706a123329ea02547ebcfcc3c533528a13e8939/tests/support/worldcities/worldcities.dbf -------------------------------------------------------------------------------- /tests/support/worldcities/worldcities.qix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muka/ShapeReader/5706a123329ea02547ebcfcc3c533528a13e8939/tests/support/worldcities/worldcities.qix -------------------------------------------------------------------------------- /tests/support/worldcities/worldcities.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muka/ShapeReader/5706a123329ea02547ebcfcc3c533528a13e8939/tests/support/worldcities/worldcities.shp -------------------------------------------------------------------------------- /tests/support/worldcities/worldcities.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muka/ShapeReader/5706a123329ea02547ebcfcc3c533528a13e8939/tests/support/worldcities/worldcities.shx --------------------------------------------------------------------------------