├── .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
--------------------------------------------------------------------------------