├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── composer.json ├── lib └── CrEOF │ └── Geo │ └── WKB │ ├── Exception │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ ├── RangeException.php │ └── UnexpectedValueException.php │ ├── Parser.php │ └── Reader.php ├── phpunit.xml.dist └── tests └── CrEOF └── Geo └── WKB └── Tests ├── ParserTest.php └── ReaderTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | phpcodesniffer: 5 | enabled: true 6 | phpmd: 7 | enabled: true 8 | ratings: 9 | paths: 10 | - "**.php" 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - 7.0 9 | - hhvm 10 | 11 | before_script: 12 | - composer self-update 13 | - composer --prefer-source install 14 | 15 | script: 16 | - ./vendor/bin/phpunit -v --coverage-clover ./build/logs/clover.xml 17 | 18 | after_script: 19 | - ./vendor/bin/test-reporter --coverage-report ./build/logs/clover.xml 20 | 21 | after_success: 22 | - ./vendor/bin/coveralls -v --exclude-no-stmt 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | ### Added 7 | 8 | ### Changed 9 | 10 | ### Removed 11 | 12 | ## [2.3.0] - 2016-05-22 13 | ### Added 14 | - Tests for empty geometry objects. 15 | - getCurrentPosition() and getLastPosition methods in Reader to get position in byte stream. 16 | - Support for OCG 1.2 encoding of 3D and 4D geometry. 17 | - Method getBadTypeInTypeMessage() in Parser to generate helpful and descriptive exception message. 18 | - Badge and configuration for Coveralls. 19 | 20 | ### Changed 21 | - NaN coordinates are not returned in point value array, empty point value now array(). 22 | - Reader::readDouble() now deprecated and calls Reader::readFloat(). 23 | - Reader::readDoubles() now deprecated and calls Reader::readFloats(). 24 | - unpack() errors are now caught in unpackInput() and a library exception thrown. 25 | - Inner types (points in multipoint, etc.) are now checked for same dimensions of parent object. 26 | - The search for 'x' in hex values beginning with 'x' or '0x' is now case-insensitive. 27 | - Supported encoding and input formats added to documentation. 28 | - References for encodings added to documentation. 29 | - Lots of additional test data and cases, and cleanup. 30 | - Library exceptions now caught in readGeometry() and rethrown appending Reader position in message. 31 | - All thrown exceptions now have a message. 32 | - Now a single return for all code paths in Parser::getMachineByteOrder(). 33 | - Tweaked tests and code for 100% coverage. 34 | - Updated travis config for coveralls. 35 | 36 | ## [2.2.0] - 2016-05-03 37 | ### Added 38 | - Added Tests namespace to Composer PSR-0 dev autoload. 39 | - Added 'dimension' key to returned array containing object dimensions (Z, M, or ZM). 40 | - Reader::getMachineByteOrder method to detect running platform endianness. 41 | 42 | ### Changed 43 | - Parser property with Reader instance no longer static. 44 | - Replaced sprintf function call in Reader::unpackInput() with string concatenation. 45 | - Updated PHPUnit config to be compliant with XSD. 46 | - Updated PHPUnit config to use Composer autoload. 47 | - Updated documentation with new usage pattern. 48 | - Type name in returned array now contains only base type without dimensions (Z, M, and ZM). 49 | - Reader::readDouble() now checks running platform endianness before byte-swapping values instead of assuming little-endian. 50 | 51 | ### Removed 52 | - Removed now unused TestInit 53 | 54 | ## [2.1.0] - 2016-02-18 55 | ### Added 56 | - Reader load() method to allow reusing a Reader instance. 57 | - Parser parse() method to allow reusing a Parser instance. 58 | - 3DZ, 3DM, and 4DZM support for all types. 59 | - Support for CIRCULARSTRING type. 60 | - Support for COMPOUNDCURVE type. 61 | - Support for CURVEPOLYGON type. 62 | - Support for MULTICURVE type. 63 | - Support for MULTISURFACE type. 64 | - Preliminary support for POLYHEDRALSURFACE type. 65 | 66 | ### Changed 67 | - Major refactoring of Parser class. 68 | - Nested types are now checked for permitted types (ie. only Points in MultiPoint, etc.) 69 | 70 | ## [2.0.0] - 2015-11-18 71 | ### Added 72 | - Change base namespace to CrEOF\Geo\WKB to avoid class collision with other CrEOF packages. 73 | 74 | ## [1.0.1] - 2015-11-17 75 | ### Changed 76 | - Replaced if/else statement with ternary operator in parseInput method of Reader. 77 | 78 | ## [1.0.0] - 2015-11-16 79 | ### Added 80 | - Initial release. 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | (this is a work in progress) 2 | 3 | - Code formatting MUST follow PSR-2. 4 | - Issues SHOULD include code and/or data to reproduce the issue. 5 | - PR's for issues SHOULD include test(s) for issue. 6 | - PR's SHOULD have adequate documentation (commit messages, comments, etc.) to readily convey what and/or why. 7 | - Code SHOULD attempt to follow [Object Calisthenics](http://www.xpteam.com/jeff/writings/objectcalisthenics.rtf) methodology. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Derek J. Lambert 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # creof/wkb-parser 2 | 3 | [![Build Status](https://travis-ci.org/creof/wkb-parser.svg?branch=master)](https://travis-ci.org/creof/wkb-parser) 4 | [![Code Climate](https://codeclimate.com/github/creof/wkb-parser/badges/gpa.svg)](https://codeclimate.com/github/creof/wkb-parser) 5 | [![Test Coverage](https://codeclimate.com/github/creof/wkb-parser/badges/coverage.svg)](https://codeclimate.com/github/creof/wkb-parser/coverage) 6 | [![Coverage Status](https://coveralls.io/repos/github/creof/wkb-parser/badge.svg?branch=master)](https://coveralls.io/github/creof/wkb-parser?branch=master) 7 | 8 | Parser library for 2D, 3D, and 4D Open Geospatial Consortium (OGC) WKB or PostGIS EWKB spatial object data. 9 | 10 | ## Usage 11 | 12 | There are two use patterns for the parser. The value to be parsed can be passed into the constructor, then parse() 13 | called on the returned ```Parser``` object: 14 | 15 | ```php 16 | $parser = new Parser($input); 17 | 18 | $value = $parser->parse(); 19 | ``` 20 | 21 | If many values need to be parsed, a single ```Parser``` instance can be used: 22 | 23 | ```php 24 | $parser = new Parser(); 25 | 26 | $value1 = $parser->parse($input1); 27 | $value2 = $parser->parse($input2); 28 | ``` 29 | 30 | ### Input value 31 | 32 | #### Encoding 33 | 34 | The parser currently supports 3 WKB encodings: 35 | 36 | - OGC v1.1 37 | - OGC v1.2 38 | - PostGIS EWKB 39 | 40 | #### Format 41 | 42 | The parser supports a number of input formats: 43 | 44 | - Binary string (as returned from database or ```pack('H*', $hexString)```) 45 | - Bare hexadecimal text string (```'01010000003D0AD7A3.....'```) 46 | - Hexadecimal test string prepended with ```x```, ```X```, ```0x```, or ```0X``` (```'0x01010000003D0AD7A3.....'```, etc.) 47 | 48 | ## Return 49 | 50 | The parser will return an array with the keys ```type```, ```value```, ```srid```, and ```dimension```. 51 | - ```type``` string, the uppercase spatial object type (```POINT```, ```LINESTRING```, etc.) without any dimension. 52 | - ```value``` array, contains integer or float values for points, nested arrays containing these based on spatial object type, or empty array for EMPTY geometry. 53 | - ```srid``` integer, the SRID if present in EWKB value, ```null``` otherwise. 54 | - ```dimension``` string, will contain ```Z```, ```M```, or ```ZM``` for the respective 3D and 4D objects, ```null``` otherwise. 55 | 56 | ## Exceptions 57 | 58 | The ```Reader``` and ```Parser``` will throw exceptions implementing interface ```CrEOF\Geo\WKB\Exception\ExceptionInterface```. 59 | 60 | ## References 61 | - PostGIS EWKB - https://github.com/postgis/postgis/blob/svn-trunk/doc/ZMSgeoms.txt 62 | - OGC Simple Feature Access, Part 1 - http://www.opengeospatial.org/standards/sfa 63 | - OGC Simple Feature Access, Part 2 - http://www.opengeospatial.org/standards/sfs 64 | 65 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/creof/wkb-parser/97ac770f241adce3c46e3feb96e0e092531c833d/TODO.md -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creof/wkb-parser", 3 | "type": "library", 4 | "description": "Parser for well-known binary (WKB/EWKB) object data", 5 | "keywords": ["parser", "string", "text", "geometry", "geography", "spatial", "wkb", "ewkb"], 6 | "authors": [ 7 | { 8 | "name": "Derek J. Lambert", 9 | "email": "dlambert@dereklambert.com" 10 | } 11 | ], 12 | "license": "MIT", 13 | "require": { 14 | "php": ">=5.3.3", 15 | "ext-SPL": "*" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": ">=4.8", 19 | "codeclimate/php-test-reporter": "dev-master", 20 | "satooshi/php-coveralls": "~1.0" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "CrEOF\\Geo\\WKB": "lib/" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-0": { 29 | "CrEOF\\Geo\\WKB\\Tests": "tests/" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 30 | * @license http://dlambert.mit-license.org MIT 31 | */ 32 | interface ExceptionInterface 33 | { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 30 | * @license http://dlambert.mit-license.org MIT 31 | */ 32 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 33 | { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Exception/RangeException.php: -------------------------------------------------------------------------------- 1 | 30 | * @license http://dlambert.mit-license.org MIT 31 | */ 32 | class RangeException extends \RangeException implements ExceptionInterface 33 | { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Exception/UnexpectedValueException.php: -------------------------------------------------------------------------------- 1 | 30 | * @license http://dlambert.mit-license.org MIT 31 | */ 32 | class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface 33 | { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Parser.php: -------------------------------------------------------------------------------- 1 | 33 | * @license http://dlambert.mit-license.org MIT 34 | */ 35 | class Parser 36 | { 37 | const WKB_TYPE_GEOMETRY = 0; 38 | const WKB_TYPE_POINT = 1; 39 | const WKB_TYPE_LINESTRING = 2; 40 | const WKB_TYPE_POLYGON = 3; 41 | const WKB_TYPE_MULTIPOINT = 4; 42 | const WKB_TYPE_MULTILINESTRING = 5; 43 | const WKB_TYPE_MULTIPOLYGON = 6; 44 | const WKB_TYPE_GEOMETRYCOLLECTION = 7; 45 | const WKB_TYPE_CIRCULARSTRING = 8; 46 | const WKB_TYPE_COMPOUNDCURVE = 9; 47 | const WKB_TYPE_CURVEPOLYGON = 10; 48 | const WKB_TYPE_MULTICURVE = 11; 49 | const WKB_TYPE_MULTISURFACE = 12; 50 | const WKB_TYPE_CURVE = 13; 51 | const WKB_TYPE_SURFACE = 14; 52 | const WKB_TYPE_POLYHEDRALSURFACE = 15; 53 | const WKB_TYPE_TIN = 16; 54 | const WKB_TYPE_TRIANGLE = 17; 55 | 56 | const WKB_FLAG_SRID = 0x20000000; 57 | const WKB_FLAG_M = 0x40000000; 58 | const WKB_FLAG_Z = 0x80000000; 59 | 60 | const TYPE_GEOMETRY = 'Geometry'; 61 | const TYPE_POINT = 'Point'; 62 | const TYPE_LINESTRING = 'LineString'; 63 | const TYPE_POLYGON = 'Polygon'; 64 | const TYPE_MULTIPOINT = 'MultiPoint'; 65 | const TYPE_MULTILINESTRING = 'MultiLineString'; 66 | const TYPE_MULTIPOLYGON = 'MultiPolygon'; 67 | const TYPE_GEOMETRYCOLLECTION = 'GeometryCollection'; 68 | const TYPE_CIRCULARSTRING = 'CircularString'; 69 | const TYPE_COMPOUNDCURVE = 'CompoundCurve'; 70 | const TYPE_CURVEPOLYGON = 'CurvePolygon'; 71 | const TYPE_MULTICURVE = 'MultiCurve'; 72 | const TYPE_MULTISURFACE = 'MultiSurface'; 73 | const TYPE_POLYHEDRALSURFACE = 'PolyhedralSurface'; 74 | const TYPE_TIN = 'Tin'; 75 | const TYPE_TRIANGLE = 'Triangle'; 76 | 77 | /** 78 | * @var int 79 | */ 80 | private $type; 81 | 82 | /** 83 | * @var int 84 | */ 85 | private $srid; 86 | 87 | /** 88 | * @var int 89 | */ 90 | private $pointSize; 91 | 92 | /** 93 | * @var int 94 | */ 95 | private $byteOrder; 96 | 97 | /** 98 | * @var int 99 | */ 100 | private $dimensions; 101 | 102 | /** 103 | * @var Reader 104 | */ 105 | private $reader; 106 | 107 | /** 108 | * @param string $input 109 | * 110 | * @throws UnexpectedValueException 111 | */ 112 | public function __construct($input = null) 113 | { 114 | $this->reader = new Reader(); 115 | 116 | if (null !== $input) { 117 | $this->reader->load($input); 118 | } 119 | } 120 | 121 | /** 122 | * Parse input data 123 | * 124 | * @param string $input 125 | * 126 | * @return array 127 | * @throws UnexpectedValueException 128 | */ 129 | public function parse($input = null) 130 | { 131 | if (null !== $input) { 132 | $this->reader->load($input); 133 | } 134 | 135 | return $this->readGeometry(); 136 | } 137 | 138 | /** 139 | * Parse geometry data 140 | * 141 | * @return array 142 | * @throws UnexpectedValueException 143 | */ 144 | private function readGeometry() 145 | { 146 | $this->srid = null; 147 | 148 | try { 149 | $this->byteOrder = $this->readByteOrder(); 150 | $this->type = $this->readType(); 151 | 152 | if ($this->hasFlag($this->type, self::WKB_FLAG_SRID)) { 153 | $this->srid = $this->readSrid(); 154 | } 155 | 156 | $this->dimensions = $this->getDimensions($this->type); 157 | $this->pointSize = 2 + strlen($this->getDimensionType($this->dimensions) || ''); 158 | 159 | $typeName = $this->getTypeName($this->type); 160 | 161 | return array( 162 | 'type' => $typeName, 163 | 'srid' => $this->srid, 164 | 'value' => $this->$typeName(), 165 | 'dimension' => $this->getDimensionType($this->dimensions) 166 | ); 167 | } catch (ExceptionInterface $e) { 168 | throw new $e($e->getMessage() . ' at byte ' . $this->reader->getLastPosition(), $e->getCode(), $e->getPrevious()); 169 | } 170 | } 171 | 172 | /** 173 | * Check type for flag 174 | * 175 | * @param int $type 176 | * @param int $flag 177 | * 178 | * @return bool 179 | */ 180 | private function hasFlag($type, $flag) 181 | { 182 | return ($type & $flag) === $flag; 183 | } 184 | 185 | /** 186 | * @param int $type 187 | * 188 | * @return bool 189 | */ 190 | private function is2D($type) 191 | { 192 | return $type < 32; 193 | } 194 | 195 | /** 196 | * @param int $type 197 | * 198 | * @return int|null 199 | */ 200 | private function getDimensions($type) 201 | { 202 | if ($this->is2D($type)) { 203 | return null; 204 | } 205 | 206 | if ($type & (self::WKB_FLAG_SRID | self::WKB_FLAG_M | self::WKB_FLAG_Z)) { 207 | return $type & (self::WKB_FLAG_M | self::WKB_FLAG_Z); 208 | } 209 | 210 | return $type - ($type % 1000); 211 | } 212 | 213 | /** 214 | * @param int $dimensions 215 | * 216 | * @return string 217 | * @throws UnexpectedValueException 218 | */ 219 | private function getDimensionType($dimensions) 220 | { 221 | if ($this->is2D($dimensions)) { 222 | return null; 223 | } 224 | 225 | switch ($dimensions) { 226 | case (1000): 227 | //no break 228 | case (self::WKB_FLAG_Z): 229 | return 'Z'; 230 | case (2000): 231 | //no break 232 | case (self::WKB_FLAG_M): 233 | return 'M'; 234 | case (3000): 235 | //no break 236 | case (self::WKB_FLAG_M | self::WKB_FLAG_Z): 237 | return 'ZM'; 238 | } 239 | 240 | throw new UnexpectedValueException(sprintf('%s with unsupported dimensions 0x%2$X (%2$d)', $this->getTypeName($this->type), $dimensions)); 241 | } 242 | 243 | /** 244 | * @param int $type 245 | * 246 | * @return int 247 | */ 248 | private function getDimensionedPrimitive($type) 249 | { 250 | if (null === $this->dimensions) { 251 | return $type; 252 | } 253 | 254 | if ($this->dimensions & (self::WKB_FLAG_Z | self::WKB_FLAG_M)) { 255 | return $type | $this->dimensions; 256 | } 257 | 258 | return $type + $this->dimensions; 259 | } 260 | 261 | /** 262 | * @param int $type 263 | * 264 | * @return int 265 | */ 266 | private function getTypePrimitive($type) 267 | { 268 | if ($this->is2D($type)) { 269 | return $type; 270 | } 271 | 272 | if ($type > 0xFFFF) { 273 | return $type & 0xFF; 274 | } 275 | 276 | return $type % 1000; 277 | } 278 | 279 | /** 280 | * Get name of data type 281 | * 282 | * @param int $type 283 | * 284 | * @return string 285 | * @throws UnexpectedValueException 286 | */ 287 | private function getTypeName($type) 288 | { 289 | switch ($this->getTypePrimitive($type)) { 290 | case (self::WKB_TYPE_POINT): 291 | $typeName = self::TYPE_POINT; 292 | break; 293 | case (self::WKB_TYPE_LINESTRING): 294 | $typeName = self::TYPE_LINESTRING; 295 | break; 296 | case (self::WKB_TYPE_POLYGON): 297 | $typeName = self::TYPE_POLYGON; 298 | break; 299 | case (self::WKB_TYPE_MULTIPOINT): 300 | $typeName = self::TYPE_MULTIPOINT; 301 | break; 302 | case (self::WKB_TYPE_MULTILINESTRING): 303 | $typeName = self::TYPE_MULTILINESTRING; 304 | break; 305 | case (self::WKB_TYPE_MULTIPOLYGON): 306 | $typeName = self::TYPE_MULTIPOLYGON; 307 | break; 308 | case (self::WKB_TYPE_GEOMETRYCOLLECTION): 309 | $typeName = self::TYPE_GEOMETRYCOLLECTION; 310 | break; 311 | case (self::WKB_TYPE_CIRCULARSTRING): 312 | $typeName = self::TYPE_CIRCULARSTRING; 313 | break; 314 | case (self::WKB_TYPE_COMPOUNDCURVE): 315 | $typeName = self::TYPE_COMPOUNDCURVE; 316 | break; 317 | case (self::WKB_TYPE_CURVEPOLYGON): 318 | $typeName = self::TYPE_CURVEPOLYGON; 319 | break; 320 | case (self::WKB_TYPE_MULTICURVE): 321 | $typeName = self::TYPE_MULTICURVE; 322 | break; 323 | case (self::WKB_TYPE_MULTISURFACE): 324 | $typeName = self::TYPE_MULTISURFACE; 325 | break; 326 | case (self::WKB_TYPE_POLYHEDRALSURFACE): 327 | $typeName = self::TYPE_POLYHEDRALSURFACE; 328 | break; 329 | default: 330 | throw new UnexpectedValueException('Unsupported WKB type "' . $this->type . '"'); 331 | } 332 | 333 | return strtoupper($typeName); 334 | } 335 | 336 | /** 337 | * Parse data byte order 338 | * 339 | * @throws UnexpectedValueException 340 | */ 341 | private function readByteOrder() 342 | { 343 | return $this->reader->readByteOrder(); 344 | } 345 | 346 | /** 347 | * Parse data type 348 | * 349 | * @throws UnexpectedValueException 350 | */ 351 | private function readType() 352 | { 353 | return $this->reader->readLong(); 354 | } 355 | 356 | /** 357 | * Parse SRID value 358 | * 359 | * @throws UnexpectedValueException 360 | */ 361 | private function readSrid() 362 | { 363 | return $this->reader->readLong(); 364 | } 365 | 366 | /** 367 | * @return int 368 | * @throws UnexpectedValueException 369 | */ 370 | private function readCount() 371 | { 372 | return $this->reader->readLong(); 373 | } 374 | 375 | /** 376 | * @param int $count 377 | * 378 | * @return array 379 | * @throws UnexpectedValueException 380 | */ 381 | private function readPoints($count) 382 | { 383 | $points = array(); 384 | 385 | for ($i = 0; $i < $count; $i++) { 386 | $points[] = $this->point(); 387 | } 388 | 389 | return $points; 390 | } 391 | 392 | /** 393 | * @param int $count 394 | * 395 | * @return array 396 | * @throws UnexpectedValueException 397 | */ 398 | private function readLinearRings($count) 399 | { 400 | $rings = array(); 401 | 402 | for ($i = 0; $i < $count; $i++) { 403 | $rings[] = $this->readPoints($this->readCount()); 404 | } 405 | 406 | return $rings; 407 | } 408 | 409 | /** 410 | * Parse POINT values 411 | * 412 | * @return float[] 413 | * @throws UnexpectedValueException 414 | */ 415 | private function point() 416 | { 417 | return $this->reader->readFloats($this->pointSize); 418 | } 419 | 420 | /** 421 | * Parse LINESTRING value 422 | * 423 | * @return array 424 | * @throws UnexpectedValueException 425 | */ 426 | private function lineString() 427 | { 428 | return $this->readPoints($this->readCount()); 429 | } 430 | 431 | /** 432 | * Parse CIRCULARSTRING value 433 | * 434 | * @return array 435 | * @throws UnexpectedValueException 436 | */ 437 | private function circularString() 438 | { 439 | return $this->readPoints($this->readCount()); 440 | } 441 | 442 | /** 443 | * Parse POLYGON value 444 | * 445 | * @return array[] 446 | * @throws UnexpectedValueException 447 | */ 448 | private function polygon() 449 | { 450 | return $this->readLinearRings($this->readCount()); 451 | } 452 | 453 | /** 454 | * Parse MULTIPOINT value 455 | * 456 | * @return array[] 457 | * @throws UnexpectedValueException 458 | */ 459 | private function multiPoint() 460 | { 461 | $values = array(); 462 | $count = $this->readCount(); 463 | 464 | for ($i = 0; $i < $count; $i++) { 465 | $this->readByteOrder(); 466 | 467 | $type = $this->readType(); 468 | 469 | if ($this->getDimensionedPrimitive(self::WKB_TYPE_POINT) !== $type) { 470 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_MULTIPOINT, array(self::WKB_TYPE_POINT))); 471 | } 472 | 473 | $values[] = $this->point(); 474 | } 475 | 476 | return $values; 477 | } 478 | 479 | /** 480 | * Parse MULTILINESTRING value 481 | * 482 | * @return array[] 483 | * @throws UnexpectedValueException 484 | */ 485 | private function multiLineString() 486 | { 487 | $values = array(); 488 | $count = $this->readCount(); 489 | 490 | for ($i = 0; $i < $count; $i++) { 491 | $this->readByteOrder(); 492 | 493 | $type = $this->readType(); 494 | 495 | if ($this->getDimensionedPrimitive(self::WKB_TYPE_LINESTRING) !== $type) { 496 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_MULTILINESTRING, array(self::WKB_TYPE_LINESTRING))); 497 | } 498 | 499 | $values[] = $this->readPoints($this->readCount()); 500 | } 501 | 502 | return $values; 503 | } 504 | 505 | /** 506 | * Parse MULTIPOLYGON value 507 | * 508 | * @return array[] 509 | * @throws UnexpectedValueException 510 | */ 511 | private function multiPolygon() 512 | { 513 | $count = $this->readCount(); 514 | $values = array(); 515 | 516 | for ($i = 0; $i < $count; $i++) { 517 | $this->readByteOrder(); 518 | 519 | $type = $this->readType(); 520 | 521 | if ($this->getDimensionedPrimitive(self::WKB_TYPE_POLYGON) !== $type) { 522 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_MULTIPOLYGON, array(self::WKB_TYPE_POLYGON))); 523 | } 524 | 525 | $values[] = $this->readLinearRings($this->readCount()); 526 | } 527 | 528 | return $values; 529 | } 530 | 531 | /** 532 | * Parse COMPOUNDCURVE value 533 | * 534 | * @return array 535 | * @throws UnexpectedValueException 536 | */ 537 | private function compoundCurve() 538 | { 539 | $values = array(); 540 | $count = $this->readCount(); 541 | 542 | for ($i = 0; $i < $count; $i++) { 543 | $this->readByteOrder(); 544 | 545 | $type = $this->readType(); 546 | 547 | switch ($type) { 548 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_LINESTRING)): 549 | // no break 550 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_CIRCULARSTRING)): 551 | $value = $this->readPoints($this->readCount()); 552 | break; 553 | default: 554 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_COMPOUNDCURVE, array(self::WKB_TYPE_LINESTRING, self::WKB_TYPE_CIRCULARSTRING))); 555 | } 556 | 557 | $values[] = array( 558 | 'type' => $this->getTypeName($type), 559 | 'value' => $value, 560 | ); 561 | } 562 | 563 | return $values; 564 | } 565 | 566 | /** 567 | * Parse CURVEPOLYGON value 568 | * 569 | * @return array 570 | * @throws UnexpectedValueException 571 | */ 572 | private function curvePolygon() 573 | { 574 | $values = array(); 575 | $count = $this->readCount(); 576 | 577 | for ($i = 0; $i < $count; $i++) { 578 | $this->readByteOrder(); 579 | 580 | $type = $this->readType(); 581 | 582 | switch ($type) { 583 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_LINESTRING)): 584 | // no break 585 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_CIRCULARSTRING)): 586 | $value = $this->readPoints($this->readCount()); 587 | break; 588 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_COMPOUNDCURVE)): 589 | $value = $this->compoundCurve(); 590 | break; 591 | default: 592 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_CURVEPOLYGON, array(self::WKB_TYPE_LINESTRING, self::WKB_TYPE_CIRCULARSTRING, self::WKB_TYPE_COMPOUNDCURVE))); 593 | } 594 | 595 | $values[] = array( 596 | 'type' => $this->getTypeName($type), 597 | 'value' => $value, 598 | ); 599 | } 600 | 601 | return $values; 602 | } 603 | 604 | /** 605 | * Parse MULTICURVE value 606 | * 607 | * @return array 608 | * @throws UnexpectedValueException 609 | */ 610 | private function multiCurve() 611 | { 612 | $values = array(); 613 | $count = $this->readCount(); 614 | 615 | for ($i = 0; $i < $count; $i++) { 616 | $this->readByteOrder(); 617 | 618 | $type = $this->readType(); 619 | 620 | switch ($type) { 621 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_LINESTRING)): 622 | // no break 623 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_CIRCULARSTRING)): 624 | $value = $this->readPoints($this->readCount()); 625 | break; 626 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_COMPOUNDCURVE)): 627 | $value = $this->compoundCurve(); 628 | break; 629 | default: 630 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_MULTICURVE, array(self::WKB_TYPE_LINESTRING, self::WKB_TYPE_CIRCULARSTRING, self::WKB_TYPE_COMPOUNDCURVE))); 631 | } 632 | 633 | $values[] = array( 634 | 'type' => $this->getTypeName($type), 635 | 'value' => $value, 636 | ); 637 | } 638 | 639 | return $values; 640 | } 641 | 642 | /** 643 | * Parse MULTISURFACE value 644 | * 645 | * @return array 646 | * @throws UnexpectedValueException 647 | */ 648 | private function multiSurface() 649 | { 650 | $values = array(); 651 | $count = $this->readCount(); 652 | 653 | for ($i = 0; $i < $count; $i++) { 654 | $this->readByteOrder(); 655 | 656 | $type = $this->readType(); 657 | 658 | switch ($type) { 659 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_POLYGON)): 660 | $value = $this->polygon(); 661 | break; 662 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_CURVEPOLYGON)): 663 | $value = $this->curvePolygon(); 664 | break; 665 | default: 666 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_MULTISURFACE, array(self::WKB_TYPE_POLYGON, self::WKB_TYPE_CURVEPOLYGON))); 667 | } 668 | 669 | $values[] = array( 670 | 'type' => $this->getTypeName($type), 671 | 'value' => $value, 672 | ); 673 | } 674 | 675 | return $values; 676 | } 677 | 678 | /** 679 | * Parse POLYHEDRALSURFACE value 680 | * 681 | * @return array 682 | * @throws UnexpectedValueException 683 | */ 684 | private function polyhedralSurface() 685 | { 686 | $values = array(); 687 | $count = $this->readCount(); 688 | 689 | for ($i = 0; $i < $count; $i++) { 690 | $this->readByteOrder(); 691 | 692 | $type = $this->readType(); 693 | 694 | switch ($type) { 695 | case ($this->getDimensionedPrimitive(self::WKB_TYPE_POLYGON)): 696 | $value = $this->polygon(); 697 | break; 698 | // is polygon the only one? 699 | default: 700 | throw new UnexpectedValueException($this->getBadTypeInTypeMessage($type, self::WKB_TYPE_POLYHEDRALSURFACE, array(self::WKB_TYPE_POLYGON))); 701 | } 702 | 703 | $values[] = array( 704 | 'type' => $this->getTypeName($type), 705 | 'value' => $value, 706 | ); 707 | } 708 | 709 | return $values; 710 | } 711 | 712 | /** 713 | * Parse GEOMETRYCOLLECTION value 714 | * 715 | * @return array[] 716 | * @throws UnexpectedValueException 717 | */ 718 | private function geometryCollection() 719 | { 720 | $values = array(); 721 | $count = $this->readCount(); 722 | 723 | for ($i = 0; $i < $count; $i++) { 724 | $this->readByteOrder(); 725 | 726 | $type = $this->readType(); 727 | $typeName = $this->getTypeName($type); 728 | 729 | $values[] = array( 730 | 'type' => $typeName, 731 | 'value' => $this->$typeName() 732 | ); 733 | } 734 | 735 | return $values; 736 | } 737 | 738 | /** 739 | * @param int $childType 740 | * @param int $parentType 741 | * @param int[] $expectedTypes 742 | * 743 | * @return string 744 | */ 745 | private function getBadTypeInTypeMessage($childType, $parentType, array $expectedTypes) 746 | { 747 | if ($this->type !== $parentType) { 748 | $parentType = $this->type; 749 | } 750 | 751 | $message = sprintf( 752 | ' %s with dimensions 0x%X (%2$d) in %3$s, expected ', 753 | $this->getTypeName($childType), 754 | $this->getDimensions($childType), 755 | $this->getTypeName($parentType) 756 | ); 757 | 758 | if (! in_array($this->getTypePrimitive($childType), $expectedTypes, true)) { 759 | if (1 === count($expectedTypes)) { 760 | $message .= $this->getTypeName($expectedTypes[0]); 761 | } else { 762 | $last = $this->getTypeName(array_pop($expectedTypes)); 763 | $message .= implode(array_map(array($this, 'getTypeName'), $expectedTypes), ', ') . ' or ' . $last; 764 | } 765 | 766 | $message = 'Unexpected' . $message . ' with '; 767 | } else { 768 | $message = 'Bad' . $message; 769 | } 770 | 771 | return $message . sprintf('dimensions 0x%X (%1$d)', $this->dimensions); 772 | } 773 | } 774 | -------------------------------------------------------------------------------- /lib/CrEOF/Geo/WKB/Reader.php: -------------------------------------------------------------------------------- 1 | 33 | * @license http://dlambert.mit-license.org MIT 34 | */ 35 | class Reader 36 | { 37 | const WKB_XDR = 0; 38 | const WKB_NDR = 1; 39 | 40 | /** 41 | * @var int 42 | */ 43 | private $byteOrder; 44 | 45 | /** 46 | * @var string 47 | */ 48 | private $input; 49 | 50 | /** 51 | * @var int 52 | */ 53 | private $position; 54 | 55 | /** 56 | * @var int 57 | */ 58 | private $previous; 59 | 60 | /** 61 | * @var int 62 | */ 63 | private $length; 64 | 65 | /** 66 | * @var int 67 | */ 68 | private static $machineByteOrder; 69 | 70 | /** 71 | * @param string $input 72 | * 73 | * @throws UnexpectedValueException 74 | */ 75 | public function __construct($input = null) 76 | { 77 | if (null !== $input) { 78 | $this->load($input); 79 | } 80 | } 81 | 82 | /** 83 | * @param string $input 84 | * 85 | * @throws UnexpectedValueException 86 | */ 87 | public function load($input) 88 | { 89 | $this->position = 0; 90 | $this->previous = 0; 91 | 92 | if (ord($input) < 32) { 93 | $this->input = $input; 94 | $this->length = strlen($input); 95 | 96 | return; 97 | } 98 | 99 | $position = stripos($input, 'x'); 100 | 101 | if (false !== $position) { 102 | $input = substr($input, $position + 1); 103 | } 104 | 105 | $this->input = pack('H*', $input); 106 | $this->length = strlen($this->input); 107 | } 108 | 109 | /** 110 | * @return int 111 | * @throws UnexpectedValueException 112 | */ 113 | public function readLong() 114 | { 115 | $value = self::WKB_NDR === $this->getByteOrder() ? $this->unpackInput('V') : $this->unpackInput('N'); 116 | $this->previous = 4; 117 | $this->position += $this->previous; 118 | 119 | return $value; 120 | } 121 | 122 | /** 123 | * @return float 124 | * @throws UnexpectedValueException 125 | * @throws RangeException 126 | * 127 | * @deprecated use readFloat() 128 | */ 129 | public function readDouble() 130 | { 131 | return $this->readFloat(); 132 | } 133 | 134 | /** 135 | * @return float 136 | * @throws RangeException 137 | * @throws UnexpectedValueException 138 | */ 139 | public function readFloat() 140 | { 141 | $double = $this->unpackInput('d'); 142 | 143 | if ($this->getMachineByteOrder() !== $this->getByteOrder()) { 144 | $double = unpack('dvalue', strrev(pack('d', $double))); 145 | $double = $double['value']; 146 | } 147 | 148 | $this->previous = 8; 149 | $this->position += $this->previous; 150 | 151 | return $double; 152 | } 153 | 154 | /** 155 | * @param int $count 156 | * 157 | * @return float[] 158 | * @throws RangeException 159 | * @throws UnexpectedValueException 160 | * 161 | * @deprecated use readFloats() 162 | */ 163 | public function readDoubles($count) 164 | { 165 | return $this->readFloats($count); 166 | } 167 | 168 | /** 169 | * @param int $count 170 | * 171 | * @return float[] 172 | * @throws RangeException 173 | * @throws UnexpectedValueException 174 | */ 175 | public function readFloats($count) 176 | { 177 | $floats = array(); 178 | 179 | for ($i = 0; $i < $count; $i++) { 180 | $float = $this->readFloat(); 181 | 182 | if (! is_nan($float)) { 183 | $floats[] = $float; 184 | } 185 | } 186 | 187 | return $floats; 188 | } 189 | 190 | /** 191 | * @return int 192 | * @throws RangeException 193 | * @throws UnexpectedValueException 194 | */ 195 | public function readByteOrder() 196 | { 197 | $byteOrder = $this->unpackInput('C'); 198 | 199 | $this->previous = 1; 200 | $this->position += $this->previous; 201 | 202 | if ($byteOrder >> 1) { 203 | throw new UnexpectedValueException('Invalid byte order "' . $byteOrder . '"'); 204 | } 205 | 206 | return $this->byteOrder = $byteOrder; 207 | } 208 | 209 | /** 210 | * @return int 211 | */ 212 | public function getCurrentPosition() 213 | { 214 | return $this->position; 215 | } 216 | 217 | /** 218 | * @return int 219 | */ 220 | public function getLastPosition() 221 | { 222 | return $this->position - $this->previous; 223 | } 224 | 225 | /** 226 | * @return int 227 | * @throws UnexpectedValueException 228 | */ 229 | private function getByteOrder() 230 | { 231 | if (null === $this->byteOrder) { 232 | throw new UnexpectedValueException('Invalid byte order "unset"'); 233 | } 234 | 235 | return $this->byteOrder; 236 | } 237 | 238 | /** 239 | * @param string $format 240 | * 241 | * @return array 242 | * @throws RangeException 243 | */ 244 | private function unpackInput($format) 245 | { 246 | $code = version_compare(PHP_VERSION, '5.5.0-dev', '>=') ? 'a' : 'A'; 247 | 248 | try { 249 | $result = unpack($format . 'result/' . $code . '*input', $this->input); 250 | } catch (\Exception $e) { 251 | throw new RangeException($e->getMessage(), $e->getCode(), $e->getPrevious()); 252 | } 253 | 254 | $this->input = $result['input']; 255 | 256 | return $result['result']; 257 | } 258 | 259 | /** 260 | * @return bool 261 | */ 262 | private function getMachineByteOrder() 263 | { 264 | if (null === self::$machineByteOrder) { 265 | $result = unpack('S', "\x01\x00"); 266 | 267 | self::$machineByteOrder = $result[1] === 1 ? self::WKB_NDR : self::WKB_XDR; 268 | } 269 | 270 | return self::$machineByteOrder; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | ./tests/CrEOF/Geo/WKB/Tests 14 | 15 | 16 | 17 | 18 | 19 | lib 20 | 21 | tests 22 | vendor 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/CrEOF/Geo/WKB/Tests/ReaderTest.php: -------------------------------------------------------------------------------- 1 | 32 | * @license http://dlambert.mit-license.org MIT 33 | * 34 | * @covers \CrEOF\Geo\WKB\Reader 35 | */ 36 | class ReaderTest extends \PHPUnit_Framework_TestCase 37 | { 38 | /** 39 | * @param mixed $value 40 | * @param array $methods 41 | * @param string $exception 42 | * @param string $message 43 | * 44 | * @dataProvider badTestData 45 | */ 46 | public function testBad($value, array $methods, $exception, $message) 47 | { 48 | if (version_compare(\PHPUnit_Runner_Version::id(), '5.0', '>=')) { 49 | $this->expectException($exception); 50 | 51 | if ('/' === $message[0]) { 52 | $this->expectExceptionMessageRegExp($message); 53 | } else { 54 | $this->expectExceptionMessage($message); 55 | } 56 | } else { 57 | if ('/' === $message[0]) { 58 | $this->setExpectedExceptionRegExp($exception, $message); 59 | } else { 60 | $this->setExpectedException($exception, $message); 61 | } 62 | } 63 | 64 | $reader = new Reader($value); 65 | 66 | foreach ($methods as $method) { 67 | $reader->$method(); 68 | } 69 | } 70 | 71 | /** 72 | * @param mixed $value 73 | * @param array $methods 74 | * 75 | * @dataProvider goodTestData 76 | */ 77 | public function testGood($value, array $methods) 78 | { 79 | $reader = new Reader($value); 80 | 81 | foreach ($methods as $test) { 82 | list($method, $argument, $expected) = $test; 83 | 84 | $actual = $reader->$method($argument); 85 | 86 | $this->assertSame($expected, $actual); 87 | } 88 | } 89 | 90 | /** 91 | * @return array[] 92 | */ 93 | public function badTestData() 94 | { 95 | return array( 96 | 'readBinaryBadByteOrder' => array( 97 | 'value' => pack('H*', '04'), 98 | 'methods' => array('readByteOrder'), 99 | 'exception' => '\CrEOF\Geo\WKB\Exception\UnexpectedValueException', 100 | 'message' => 'Invalid byte order "4"' 101 | ), 102 | 'readBinaryWithoutByteOrder' => array( 103 | 'value' => pack('H*', '0101000000'), 104 | 'methods' => array('readLong'), 105 | 'exception' => '\CrEOF\Geo\WKB\Exception\UnexpectedValueException', 106 | 'message' => 'Invalid byte order "unset"' 107 | ), 108 | 'readHexWithoutByteOrder' => array( 109 | 'value' => '0101000000', 110 | 'methods' => array('readLong'), 111 | 'exception' => '\CrEOF\Geo\WKB\Exception\UnexpectedValueException', 112 | 'message' => 'Invalid byte order "unset"' 113 | ), 114 | 'readBinaryShortFloat' => array( 115 | 'value' => pack('H*', '013D0AD'), 116 | 'methods' => array('readByteOrder', 'readFloat'), 117 | 'exception' => 'CrEOF\Geo\WKB\Exception\RangeException', 118 | 'message' => '/Type d: not enough input, need 8, have 3$/' 119 | ), 120 | ); 121 | } 122 | 123 | /** 124 | * @return array[] 125 | */ 126 | public function goodTestData() 127 | { 128 | return array( 129 | 'readBinaryByteOrder' => array( 130 | 'value' => pack('H*', '01'), 131 | 'methods' => array( 132 | array('readByteOrder', null, 1) 133 | ) 134 | ), 135 | 'readHexByteOrder' => array( 136 | 'value' => '01', 137 | 'methods' => array( 138 | array('readByteOrder', null, 1) 139 | ) 140 | ), 141 | 'readPrefixedHexByteOrder' => array( 142 | 'value' => '0x01', 143 | 'methods' => array( 144 | array('readByteOrder', null, 1) 145 | ) 146 | ), 147 | 'readNDRBinaryLong' => array( 148 | 'value' => pack('H*', '0101000000'), 149 | 'methods' => array( 150 | array('readByteOrder', null, 1), 151 | array('readLong', null, 1) 152 | ) 153 | ), 154 | 'readXDRBinaryLong' => array( 155 | 'value' => pack('H*', '0000000001'), 156 | 'methods' => array( 157 | array('readByteOrder', null, 0), 158 | array('readLong', null, 1) 159 | ) 160 | ), 161 | 'readNDRHexLong' => array( 162 | 'value' => '0101000000', 163 | 'methods' => array( 164 | array('readByteOrder', null, 1), 165 | array('readLong', null, 1) 166 | ) 167 | ), 168 | 'readXDRHexLong' => array( 169 | 'value' => '0000000001', 170 | 'methods' => array( 171 | array('readByteOrder', null, 0), 172 | array('readLong', null, 1) 173 | ) 174 | ), 175 | 'readNDRBinaryFloat' => array( 176 | 'value' => pack('H*', '013D0AD7A3701D4140'), 177 | 'methods' => array( 178 | array('readByteOrder', null, 1), 179 | array('readFloat', null, 34.23) 180 | ) 181 | ), 182 | 'readNDRBinaryDouble' => array( 183 | 'value' => pack('H*', '013D0AD7A3701D4140'), 184 | 'methods' => array( 185 | array('readByteOrder', null, 1), 186 | array('readDouble', null, 34.23) 187 | ) 188 | ), 189 | 'readXDRBinaryFloat' => array( 190 | 'value' => pack('H*', '0040411D70A3D70A3D'), 191 | 'methods' => array( 192 | array('readByteOrder', null, 0), 193 | array('readFloat', null, 34.23) 194 | ) 195 | ), 196 | 'readNDRHexFloat' => array( 197 | 'value' => '013D0AD7A3701D4140', 198 | 'methods' => array( 199 | array('readByteOrder', null, 1), 200 | array('readFloat', null, 34.23) 201 | ) 202 | ), 203 | 'readXDRHexFloat' => array( 204 | 'value' => '0040411D70A3D70A3D', 205 | 'methods' => array( 206 | array('readByteOrder', null, 0), 207 | array('readFloat', null, 34.23) 208 | ) 209 | ), 210 | 'readXDRBinaryFloats' => array( 211 | 'value' => pack('H*', '0040411D70A3D70A3D40411D70A3D70A3D'), 212 | 'methods' => array( 213 | array('readByteOrder', null, 0), 214 | array('readFloats', 2, array(34.23, 34.23)) 215 | ) 216 | ), 217 | 'readXDRBinaryDoubles' => array( 218 | 'value' => pack('H*', '0040411D70A3D70A3D40411D70A3D70A3D'), 219 | 'methods' => array( 220 | array('readByteOrder', null, 0), 221 | array('readDoubles', 2, array(34.23, 34.23)) 222 | ) 223 | ), 224 | 'readXDRPosition' => array( 225 | 'value' => pack('H*', '0040411D70A3D70A3D40411D70A3D70A3D'), 226 | 'methods' => array( 227 | array('readByteOrder', null, 0), 228 | array('getCurrentPosition', null, 1), 229 | array('getLastPosition', null, 0), 230 | array('readFloat', null, 34.23), 231 | array('getCurrentPosition', null, 9), 232 | array('getLastPosition', null, 1), 233 | array('readFloat', null, 34.23), 234 | array('getCurrentPosition', null, 17), 235 | array('getLastPosition', null, 9) 236 | ) 237 | ), 238 | ); 239 | } 240 | 241 | public function testReaderReuse() 242 | { 243 | $reader = new Reader(); 244 | 245 | $value = '01'; 246 | $value = pack('H*', $value); 247 | 248 | $reader->load($value); 249 | 250 | $result = $reader->readByteOrder(); 251 | 252 | $this->assertEquals(1, $result); 253 | 254 | $value = '01'; 255 | 256 | $reader->load($value); 257 | 258 | $result = $reader->readByteOrder(); 259 | 260 | $this->assertEquals(1, $result); 261 | 262 | $value = '0x01'; 263 | 264 | $reader->load($value); 265 | 266 | $result = $reader->readByteOrder(); 267 | 268 | $this->assertEquals(1, $result); 269 | 270 | $value = '0040411D70A3D70A3D'; 271 | $value = pack('H*', $value); 272 | 273 | $reader->load($value); 274 | 275 | $reader->readByteOrder(); 276 | 277 | $result = $reader->readFloat(); 278 | 279 | $this->assertEquals(34.23, $result); 280 | } 281 | } 282 | --------------------------------------------------------------------------------