Repository: jmikola/geojson Branch: master Commit: e28f3855bb61 Files: 48 Total size: 99.5 KB Directory structure: gitextract_kuvb7uxu/ ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── coding-standards.yml │ └── tests.yml ├── .gitignore ├── .scrutinizer.yml ├── LICENSE ├── README.md ├── USAGE.md ├── composer.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── src/ │ ├── BoundingBox.php │ ├── CoordinateReferenceSystem/ │ │ ├── CoordinateReferenceSystem.php │ │ ├── Linked.php │ │ └── Named.php │ ├── Exception/ │ │ ├── Exception.php │ │ ├── InvalidArgumentException.php │ │ └── UnserializationException.php │ ├── Feature/ │ │ ├── Feature.php │ │ └── FeatureCollection.php │ ├── GeoJson.php │ ├── Geometry/ │ │ ├── Geometry.php │ │ ├── GeometryCollection.php │ │ ├── LineString.php │ │ ├── LinearRing.php │ │ ├── MultiLineString.php │ │ ├── MultiPoint.php │ │ ├── MultiPolygon.php │ │ ├── Point.php │ │ └── Polygon.php │ └── JsonUnserializable.php └── tests/ ├── BaseGeoJsonTest.php ├── BoundingBoxTest.php ├── CoordinateReferenceSystem/ │ ├── CoordinateReferenceSystemTest.php │ ├── LinkedTest.php │ └── NamedTest.php ├── Feature/ │ ├── FeatureCollectionTest.php │ └── FeatureTest.php ├── GeoJsonTest.php └── Geometry/ ├── GeometryCollectionTest.php ├── GeometryTest.php ├── LineStringTest.php ├── LinearRingTest.php ├── MultiLineStringTest.php ├── MultiPointTest.php ├── MultiPolygonTest.php ├── PointTest.php └── PolygonTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.php diff=php /tests export-ignore /.gitattributes export-ignore /.github export-ignore /.gitignore export-ignore /.scrutinizer.yml export-ignore /phpcs.xml.dist export-ignore /phpunit.xml.dist export-ignore ================================================ FILE: .github/workflows/coding-standards.yml ================================================ name: "Coding Standards" on: pull_request: branches: - "v*.*" - "master" push: branches: - "v*.*" - "master" jobs: coding-standards: name: "Coding Standards" runs-on: "ubuntu-20.04" strategy: matrix: php-version: - "7.4" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: coverage: "none" php-version: "${{ matrix.php-version }}" tools: "cs2pr" - name: "Cache dependencies installed with Composer" uses: "actions/cache@v2" with: path: "~/.composer/cache" key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - name: "Install dependencies with Composer" run: "composer install --no-interaction --no-progress --no-suggest" # The -q option is required until phpcs v4 is released - name: "Run PHP_CodeSniffer" run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr" ================================================ FILE: .github/workflows/tests.yml ================================================ name: "Tests" on: pull_request: push: jobs: phpunit: name: PHPUnit Tests runs-on: ubuntu-20.04 strategy: matrix: php: - "7.4" - "8.0" - "8.1" - "8.2" dependency-versions: - "highest" include: - php: "7.4" dependency-versions: "lowest" steps: - uses: actions/checkout@v2 with: fetch-depth: 2 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - name: Install Dependencies uses: "ramsey/composer-install@v2" with: dependency-versions: "${{ matrix.dependency-versions }}" composer-options: "${{ matrix.composer-options }}" - name: Run PHPUnit run: "vendor/bin/phpunit --coverage-clover=coverage.clover" - name: Upload Coverage if: github.repository_owner == 'jmikola' run: "vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover" ================================================ FILE: .gitignore ================================================ .phpcs-cache .phpunit.result.cache composer.lock phpunit.xml vendor ================================================ FILE: .scrutinizer.yml ================================================ filter: excluded_paths: [ "vendor/*", "tests/*" ] tools: # https://scrutinizer-ci.com/docs/tools/external-code-coverage/ external_code_coverage: true # https://scrutinizer-ci.com/docs/tools/php/php-analyzer/ php_analyzer: true # https://scrutinizer-ci.com/docs/tools/php/change-tracking-analyzer/ php_changetracking: true # https://scrutinizer-ci.com/docs/tools/php/code-sniffer/ php_code_sniffer: config: standard: "PSR1" # https://scrutinizer-ci.com/docs/tools/php/mess-detector/ php_mess_detector: true # https://scrutinizer-ci.com/docs/tools/php/pdepend/ php_pdepend: true # https://scrutinizer-ci.com/docs/tools/php/code-similarity-analyzer/ php_sim: true sensiolabs_security_checker: true ================================================ FILE: LICENSE ================================================ Copyright (c) 2013-present Jeremy Mikola Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # GeoJson PHP Library [![Build Status](https://github.com/jmikola/geojson/actions/workflows/tests.yml/badge.svg)](https://github.com/jmikola/geojson/actions) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jmikola/geojson/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jmikola/geojson/?branch=master) [![Code Coverage](https://scrutinizer-ci.com/g/jmikola/geojson/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/jmikola/geojson/?branch=master) This library implements the [GeoJSON format specification](https://geojson.org/). ## Installation The library is published as a [package](https://packagist.org/packages/jmikola/geojson) and is installable via [Composer](https://getcomposer.org/): ``` $ composer require "jmikola/geojson=^1.0" ``` ## Additional Resources * [Usage Documentation](./USAGE.md) * [License](./LICENSE) ================================================ FILE: USAGE.md ================================================ # GeoJson PHP Library This library implements the [GeoJSON format specification](https://geojson.org/). The `GeoJson` namespace includes classes for each data structure defined in the GeoJSON specification. Core GeoJSON objects include geometries, features, and collections. Geometries range from primitive points to more complex polygons. Classes also exist for bounding boxes and coordinate reference systems. ## Installation The library is published as a [package](https://packagist.org/packages/jmikola/geojson) and is installable via [Composer](https://getcomposer.org/): ``` $ composer require "jmikola/geojson=^1.0" ``` ## Usage Classes in this library are immutable. ### GeoJson Constructors Geometry objects are constructed using a single coordinates array. This may be a tuple in the case of a `Point`, an array of tuples for a `LineString`, etc. Constructors for each class will validate the coordinates array and throw an `InvalidArgumentException` on error. More primitive geometry objects may also be used for constructing complex objects. For instance, a `LineString` may be constructed from an array of `Point` objects. Feature objects are constructed from a geometry object, associative properties array, and an identifier, all of which are optional. Feature and geometry collection objects are constructed from an array of their respective types. #### Specifying a Bounding Box or CRS All GeoJson constructors support `BoundingBox` and `CoordinateReferenceSystem` objects as optional arguments beyond those explicitly listed in their prototype. These objects may appear in any order *after* the explicit arguments. ```php $crs = new \GeoJson\CoordinateReferenceSystem\Named('urn:ogc:def:crs:OGC:1.3:CRS84'); $box = new \GeoJson\BoundingBox([-180, -90, 180, 90]); $point = new \GeoJson\Geometry\Point([0, 0], $crs, $box); ``` Note that the `Feature` class is unique in that it has three arguments, all with default values. In order to construct a `Feature` with a bounding box or CRS, all three arguments must be explicitly listed (e.g. with `null` placeholders). ```php $box = new \GeoJson\BoundingBox([-180, -90, 180, 90]); $feature = new \GeoJson\Feature\Feature(null, null, null, $box); ``` ### JSON Serialization Each class in the library implements the [JsonSerializable](https://php.net/manual/en/class.jsonserializable.php) interface, which allows objects to be passed directly to `json_encode()`. ```php $point = new \GeoJson\Geometry\Point([1, 1]); $json = json_encode($point); ``` Printing the `$json` variable would yield (sans whitespace): ```json { "type": "Point", "coordinates": [1, 1] } ``` ### JSON Unserialization The core `GeoJson` class implements an internal `JsonUnserializable` interface, which defines a static factory method, `jsonUnserialize()`, that can be used to create objects from the return value of `json_decode()`. ```php $json = '{ "type": "Point", "coordinates": [1, 1] }'; $json = json_decode($json); $point = \GeoJson\GeoJson::jsonUnserialize($json); ``` If errors are encountered during unserialization, an `UnserializationException` will be thrown by `jsonUnserialize()`. Possible errors include: * Missing properties (e.g. `type` is not present) * Unexpected values (e.g. `coordinates` property is not an array) * Unsupported `type` string when parsing a GeoJson object or CRS ================================================ FILE: composer.json ================================================ { "name": "jmikola/geojson", "type": "library", "description": "GeoJSON implementation for PHP", "keywords": ["geo", "geospatial", "geojson"], "homepage": "https://github.com/jmikola/geojson", "license": "MIT", "authors": [ { "name": "Jeremy Mikola", "email": "jmikola@gmail.com" } ], "require": { "php": "^7.4 || ^8.0", "ext-json": "*", "symfony/polyfill-php80": "^1.25" }, "require-dev": { "phpunit/phpunit": "^9.5", "scrutinizer/ocular": "^1.8.1", "squizlabs/php_codesniffer": "^3.6", "slevomat/coding-standard": "^8.0" }, "autoload": { "psr-4": { "GeoJson\\": "src" } }, "autoload-dev": { "psr-4": { "GeoJson\\Tests\\": "tests" } }, "extra": { "branch-alias": { "dev-master": "1.1-dev" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } ================================================ FILE: phpcs.xml.dist ================================================ src tests tests tests tests src src src ================================================ FILE: phpunit.xml.dist ================================================ ./tests/ ./src/ ================================================ FILE: src/BoundingBox.php ================================================ */ protected array $bounds; /** * @param array $bounds */ public function __construct(array $bounds) { $count = count($bounds); if ($count < 4) { throw new InvalidArgumentException('BoundingBox requires at least four values'); } if ($count % 2) { throw new InvalidArgumentException('BoundingBox requires an even number of values'); } foreach ($bounds as $value) { if (! is_int($value) && ! is_float($value)) { throw new InvalidArgumentException('BoundingBox values must be integers or floats'); } } for ($i = 0; $i < ($count / 2); $i++) { if ($bounds[$i] > $bounds[$i + ($count / 2)]) { throw new InvalidArgumentException('BoundingBox min values must precede max values'); } } $this->bounds = $bounds; } /** * Return the bounds for this BoundingBox object. * * @return array */ public function getBounds(): array { return $this->bounds; } public function jsonSerialize(): array { return $this->bounds; } /** * @param array $json */ final public static function jsonUnserialize($json): self { if (! is_array($json)) { throw UnserializationException::invalidValue('BoundingBox', $json, 'array'); } return new self($json); } } ================================================ FILE: src/CoordinateReferenceSystem/CoordinateReferenceSystem.php ================================================ properties; } /** * Return the type for this CRS object. */ public function getType(): string { return $this->type; } public function jsonSerialize(): array { return [ 'type' => $this->type, 'properties' => $this->properties, ]; } /** * @param array|object $json */ final public static function jsonUnserialize($json): self { if (! is_array($json) && ! is_object($json)) { throw UnserializationException::invalidValue('CRS', $json, 'array or object'); } $json = new ArrayObject($json); if (! $json->offsetExists('type')) { throw UnserializationException::missingProperty('CRS', 'type', 'string'); } if (! $json->offsetExists('properties')) { throw UnserializationException::missingProperty('CRS', 'properties', 'array or object'); } $type = (string) $json['type']; $properties = $json['properties']; switch ($type) { case 'link': return Linked::jsonUnserializeFromProperties($properties); case 'name': return Named::jsonUnserializeFromProperties($properties); } throw UnserializationException::unsupportedType('CRS', $type); } /** * Factory method for creating a CRS object from properties. * * This method must be overridden in a child class. * * @param array|object $properties * * @throws BadMethodCallException */ protected static function jsonUnserializeFromProperties($properties): CoordinateReferenceSystem { throw new BadMethodCallException(sprintf('%s must be overridden in a child class', __METHOD__)); } } ================================================ FILE: src/CoordinateReferenceSystem/Linked.php ================================================ properties = ['href' => $href]; if ($type !== null) { $this->properties['type'] = $type; } } /** * Factory method for creating a Linked CRS object from properties. * * @param array|object $properties * * @throws UnserializationException */ protected static function jsonUnserializeFromProperties($properties): self { if (! is_array($properties) && ! is_object($properties)) { throw UnserializationException::invalidProperty('Linked CRS', 'properties', $properties, 'array or object'); } $properties = new ArrayObject($properties); if (! $properties->offsetExists('href')) { throw UnserializationException::missingProperty('Linked CRS', 'properties.href', 'string'); } $href = (string) $properties['href']; $type = isset($properties['type']) ? (string) $properties['type'] : null; return new self($href, $type); } } ================================================ FILE: src/CoordinateReferenceSystem/Named.php ================================================ properties = ['name' => $name]; } /** * Factory method for creating a Named CRS object from properties. * * @param array|object $properties * * @throws UnserializationException */ protected static function jsonUnserializeFromProperties($properties): self { if (! is_array($properties) && ! is_object($properties)) { throw UnserializationException::invalidProperty('Named CRS', 'properties', $properties, 'array or object'); } $properties = new ArrayObject($properties); if (! $properties->offsetExists('name')) { throw UnserializationException::missingProperty('Named CRS', 'properties.name', 'string'); } $name = (string) $properties['name']; return new self($name); } } ================================================ FILE: src/Exception/Exception.php ================================================ geometry = $geometry; $this->properties = $properties; $this->id = $id; $this->setOptionalConstructorArgs($args); } /** * Return the Geometry object for this Feature object. */ public function getGeometry(): ?Geometry { return $this->geometry; } /** * Return the identifier for this Feature object. * * @return int|string|null */ public function getId() { return $this->id; } /** * Return the properties for this Feature object. */ public function getProperties(): ?array { return $this->properties; } public function jsonSerialize(): array { $json = parent::jsonSerialize(); $json['geometry'] = isset($this->geometry) ? $this->geometry->jsonSerialize() : null; $json['properties'] = $this->properties ?? null; // Ensure empty associative arrays are encoded as JSON objects if ($json['properties'] === []) { $json['properties'] = new stdClass(); } if (isset($this->id)) { $json['id'] = $this->id; } return $json; } } ================================================ FILE: src/Feature/FeatureCollection.php ================================================ */ protected array $features; /** * @param array $features * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $features, ...$args) { foreach ($features as $feature) { if (! $feature instanceof Feature) { throw new InvalidArgumentException('FeatureCollection may only contain Feature objects'); } } $this->features = array_values($features); $this->setOptionalConstructorArgs($args); } public function count(): int { return count($this->features); } /** * Return the Feature objects in this collection. * * @return array */ public function getFeatures(): array { return $this->features; } public function getIterator(): Traversable { return new ArrayIterator($this->features); } public function jsonSerialize(): array { return array_merge( parent::jsonSerialize(), ['features' => array_map( static fn(Feature $feature) => $feature->jsonSerialize(), $this->features )] ); } } ================================================ FILE: src/GeoJson.php ================================================ boundingBox; } /** * Return the CoordinateReferenceSystem for this GeoJson object. */ public function getCrs(): ?CoordinateReferenceSystem { return $this->crs; } /** * Return the type for this GeoJson object. */ public function getType(): string { return $this->type; } public function jsonSerialize(): array { $json = ['type' => $this->type]; if (isset($this->crs)) { $json['crs'] = $this->crs->jsonSerialize(); } if (isset($this->boundingBox)) { $json['bbox'] = $this->boundingBox->jsonSerialize(); } return $json; } /** * @param array|object $json */ final public static function jsonUnserialize($json): self { if (! is_array($json) && ! is_object($json)) { throw UnserializationException::invalidValue('GeoJson', $json, 'array or object'); } $json = new ArrayObject($json); if (! $json->offsetExists('type')) { throw UnserializationException::missingProperty('GeoJson', 'type', 'string'); } $type = (string) $json['type']; $args = []; switch ($type) { case self::TYPE_LINE_STRING: case self::TYPE_MULTI_LINE_STRING: case self::TYPE_MULTI_POINT: case self::TYPE_MULTI_POLYGON: case self::TYPE_POINT: case self::TYPE_POLYGON: if (! $json->offsetExists('coordinates')) { throw UnserializationException::missingProperty($type, 'coordinates', 'array'); } if (! is_array($json['coordinates'])) { throw UnserializationException::invalidProperty($type, 'coordinates', $json['coordinates'], 'array'); } $args[] = $json['coordinates']; break; case self::TYPE_FEATURE: $geometry = $json['geometry'] ?? null; $properties = $json['properties'] ?? null; $id = $json['id'] ?? null; if ($geometry !== null && ! is_array($geometry) && ! is_object($geometry)) { throw UnserializationException::invalidProperty($type, 'geometry', $geometry, 'array or object'); } if ($properties !== null && ! is_array($properties) && ! is_object($properties)) { throw UnserializationException::invalidProperty($type, 'properties', $properties, 'array or object'); } // TODO: Validate non-null $id as int or string in 2.0 $args[] = $geometry !== null ? self::jsonUnserialize($geometry) : null; $args[] = $properties !== null ? (array) $properties : null; $args[] = $id; break; case self::TYPE_FEATURE_COLLECTION: if (! $json->offsetExists('features')) { throw UnserializationException::missingProperty($type, 'features', 'array'); } if (! is_array($json['features'])) { throw UnserializationException::invalidProperty($type, 'features', $json['features'], 'array'); } $args[] = array_map([self::class, 'jsonUnserialize'], $json['features']); break; case self::TYPE_GEOMETRY_COLLECTION: if (! $json->offsetExists('geometries')) { throw UnserializationException::missingProperty($type, 'geometries', 'array'); } if (! is_array($json['geometries'])) { throw UnserializationException::invalidProperty($type, 'geometries', $json['geometries'], 'array'); } $args[] = array_map([self::class, 'jsonUnserialize'], $json['geometries']); break; default: throw UnserializationException::unsupportedType('GeoJson', $type); } if (isset($json['bbox'])) { $args[] = BoundingBox::jsonUnserialize($json['bbox']); } if (isset($json['crs'])) { $args[] = CoordinateReferenceSystem::jsonUnserialize($json['crs']); } $class = sprintf('GeoJson\%s\%s', (strncmp('Feature', $type, 7) === 0 ? 'Feature' : 'Geometry'), $type); return new $class(... $args); } /** * Set optional CRS and BoundingBox arguments passed to a constructor. * * @todo Decide if multiple CRS or BoundingBox instances should override a * previous value or be ignored */ protected function setOptionalConstructorArgs(array $args): void { foreach ($args as $arg) { if ($arg instanceof CoordinateReferenceSystem) { $this->crs = $arg; } if ($arg instanceof BoundingBox) { $this->boundingBox = $arg; } } } } ================================================ FILE: src/Geometry/Geometry.php ================================================ coordinates; } public function jsonSerialize(): array { $json = parent::jsonSerialize(); if (isset($this->coordinates)) { $json['coordinates'] = $this->coordinates; } return $json; } } ================================================ FILE: src/Geometry/GeometryCollection.php ================================================ */ protected array $geometries; /** * @param array $geometries * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $geometries, ...$args) { foreach ($geometries as $geometry) { if (! $geometry instanceof Geometry) { throw new InvalidArgumentException('GeometryCollection may only contain Geometry objects'); } } $this->geometries = array_values($geometries); $this->setOptionalConstructorArgs($args); } public function count(): int { return count($this->geometries); } /** * Return the Geometry objects in this collection. * * @return array */ public function getGeometries(): array { return $this->geometries; } public function getIterator(): Traversable { return new ArrayIterator($this->geometries); } public function jsonSerialize(): array { return array_merge( parent::jsonSerialize(), ['geometries' => array_map( static fn(Geometry $geometry) => $geometry->jsonSerialize(), $this->geometries )] ); } } ================================================ FILE: src/Geometry/LineString.php ================================================ > $positions * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $positions, ...$args) { if (count($positions) < 2) { throw new InvalidArgumentException('LineString requires at least two positions'); } parent::__construct($positions, ... $args); } } ================================================ FILE: src/Geometry/LinearRing.php ================================================ > $positions * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $positions, ...$args) { if (count($positions) < 4) { throw new InvalidArgumentException('LinearRing requires at least four positions'); } $lastPosition = end($positions); $firstPosition = reset($positions); $lastPosition = $lastPosition instanceof Point ? $lastPosition->getCoordinates() : $lastPosition; $firstPosition = $firstPosition instanceof Point ? $firstPosition->getCoordinates() : $firstPosition; if ($lastPosition !== $firstPosition) { throw new InvalidArgumentException('LinearRing requires the first and last positions to be equivalent'); } parent::__construct($positions, ... $args); } } ================================================ FILE: src/Geometry/MultiLineString.php ================================================ >> $lineStrings * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $lineStrings, ...$args) { $this->coordinates = array_map( static function ($lineString) { if (! $lineString instanceof LineString) { $lineString = new LineString($lineString); } return $lineString->getCoordinates(); }, $lineStrings ); $this->setOptionalConstructorArgs($args); } } ================================================ FILE: src/Geometry/MultiPoint.php ================================================ > $positions * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $positions, ...$args) { $this->coordinates = array_map( static function ($point) { if (! $point instanceof Point) { $point = new Point($point); } return $point->getCoordinates(); }, $positions ); $this->setOptionalConstructorArgs($args); } } ================================================ FILE: src/Geometry/MultiPolygon.php ================================================ >>> $polygons * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $polygons, ...$args) { $this->coordinates = array_map( static function ($polygon) { if (! $polygon instanceof Polygon) { $polygon = new Polygon($polygon); } return $polygon->getCoordinates(); }, $polygons ); $this->setOptionalConstructorArgs($args); } } ================================================ FILE: src/Geometry/Point.php ================================================ $position * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $position, ...$args) { if (count($position) < 2) { throw new InvalidArgumentException('Position requires at least two elements'); } foreach ($position as $value) { if (! is_int($value) && ! is_float($value)) { throw new InvalidArgumentException('Position elements must be integers or floats'); } } $this->coordinates = $position; $this->setOptionalConstructorArgs($args); } } ================================================ FILE: src/Geometry/Polygon.php ================================================ >> $linearRings * @param CoordinateReferenceSystem|BoundingBox $args */ public function __construct(array $linearRings, ...$args) { foreach ($linearRings as $linearRing) { if (! $linearRing instanceof LinearRing) { $linearRing = new LinearRing($linearRing); } $this->coordinates[] = $linearRing->getCoordinates(); } $this->setOptionalConstructorArgs($args); } } ================================================ FILE: src/JsonUnserializable.php ================================================ getMockBoundingBox(); $crs = $this->getMockCoordinateReferenceSystem(); $sut = $this->createSubjectWithExtraArguments(); $this->assertNull($sut->getBoundingBox()); $this->assertNull($sut->getCrs()); $sut = $this->createSubjectWithExtraArguments($box); $this->assertSame($box, $sut->getBoundingBox()); $this->assertNull($sut->getCrs()); $sut = $this->createSubjectWithExtraArguments($crs); $this->assertNull($sut->getBoundingBox()); $this->assertSame($crs, $sut->getCrs()); $sut = $this->createSubjectWithExtraArguments($box, $crs); $this->assertSame($box, $sut->getBoundingBox()); $this->assertSame($crs, $sut->getCrs()); $sut = $this->createSubjectWithExtraArguments($crs, $box); $this->assertSame($box, $sut->getBoundingBox()); $this->assertSame($crs, $sut->getCrs()); // Not that you would, but you could… $sut = $this->createSubjectWithExtraArguments(null, null, $box, $crs); $this->assertSame($box, $sut->getBoundingBox()); $this->assertSame($crs, $sut->getCrs()); } public function testSerializationWithCrsAndBoundingBox(): void { $box = $this->getMockBoundingBox(); $box->method('jsonSerialize')->willReturn(['boundingBox']); $crs = $this->getMockCoordinateReferenceSystem(); $crs->method('jsonSerialize')->willReturn(['coordinateReferenceSystem']); $sut = $this->createSubjectWithExtraArguments($box, $crs); $json = $sut->jsonSerialize(); $this->assertArrayHasKey('bbox', $json); $this->assertArrayHasKey('crs', $json); $this->assertSame(['boundingBox'], $json['bbox']); $this->assertSame(['coordinateReferenceSystem'], $json['crs']); } protected function getMockBoundingBox() { return $this->createMock(BoundingBox::class); } protected function getMockCoordinateReferenceSystem() { return $this->createMock(CoordinateReferenceSystem::class); } protected function getMockFeature() { return $this->createMock(Feature::class); } protected function getMockGeometry() { return $this->createMock(Geometry::class); } } ================================================ FILE: tests/BoundingBoxTest.php ================================================ assertInstanceOf('JsonSerializable', new BoundingBox([0, 0, 1, 1])); } public function testIsJsonUnserializable(): void { $this->assertInstanceOf(JsonUnserializable::class, new BoundingBox([0, 0, 1, 1])); } public function testConstructorShouldRequireAtLeastFourValues(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('BoundingBox requires at least four values'); new BoundingBox([0, 0]); } public function testConstructorShouldRequireAnEvenNumberOfValues(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('BoundingBox requires an even number of values'); new BoundingBox([0, 0, 1, 1, 2]); } /** * @dataProvider provideBoundsWithInvalidTypes */ public function testConstructorShouldRequireIntegerOrFloatValues(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('BoundingBox values must be integers or floats'); new BoundingBox(func_get_args()); } public function provideBoundsWithInvalidTypes() { return [ 'strings' => ['0', '0.0', '1', '1.0'], 'objects' => [new stdClass(), new stdClass(), new stdClass(), new stdClass()], 'arrays' => [[], [], [], []], ]; } public function testConstructorShouldRequireMinBeforeMaxValues(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('BoundingBox min values must precede max values'); new BoundingBox([-90.0, -95.0, -92.5, 90.0]); } public function testSerialization(): void { $bounds = [-180.0, -90.0, 0.0, 180.0, 90.0, 100.0]; $boundingBox = new BoundingBox($bounds); $this->assertSame($bounds, $boundingBox->getBounds()); $this->assertSame($bounds, $boundingBox->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = '[-180.0, -90.0, 180.0, 90.0]'; $json = json_decode($json, $assoc); $boundingBox = BoundingBox::jsonUnserialize($json); $this->assertInstanceOf(BoundingBox::class, $boundingBox); $this->assertSame([-180.0, -90.0, 180.0, 90.0], $boundingBox->getBounds()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } /** * @dataProvider provideInvalidUnserializationValues */ public function testUnserializationShouldRequireArray($value): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('BoundingBox expected value of type array'); BoundingBox::jsonUnserialize($value); } public function provideInvalidUnserializationValues() { return [ [null], [1], ['foo'], [new stdClass()], ]; } } ================================================ FILE: tests/CoordinateReferenceSystem/CoordinateReferenceSystemTest.php ================================================ assertInstanceOf( JsonSerializable::class, $this->createMock(CoordinateReferenceSystem::class) ); } public function testIsJsonUnserializable(): void { $this->assertInstanceOf( JsonUnserializable::class, $this->createMock(CoordinateReferenceSystem::class) ); } public function testUnserializationShouldRequireArrayOrObject(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('CRS expected value of type array or object'); CoordinateReferenceSystem::jsonUnserialize(null); } public function testUnserializationShouldRequireTypeField(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('CRS expected "type" property of type string, none given'); CoordinateReferenceSystem::jsonUnserialize(['properties' => []]); } public function testUnserializationShouldRequirePropertiesField(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('CRS expected "properties" property of type array or object, none given'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'foo']); } public function testUnserializationShouldRequireValidType(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Invalid CRS type "foo"'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'foo', 'properties' => []]); } } ================================================ FILE: tests/CoordinateReferenceSystem/LinkedTest.php ================================================ assertTrue(is_subclass_of(Linked::class, CoordinateReferenceSystem::class)); } public function testSerialization(): void { $crs = new Linked('https://example.com/crs/42', 'proj4'); $expected = [ 'type' => 'link', 'properties' => [ 'href' => 'https://example.com/crs/42', 'type' => 'proj4', ], ]; $this->assertSame('link', $crs->getType()); $this->assertSame($expected['properties'], $crs->getProperties()); $this->assertSame($expected, $crs->jsonSerialize()); } public function testSerializationWithoutHrefType(): void { $crs = new Linked('https://example.com/crs/42'); $expected = [ 'type' => 'link', 'properties' => [ 'href' => 'https://example.com/crs/42', ], ]; $this->assertSame($expected, $crs->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "link", "properties": { "href": "https://example.com/crs/42", "type": "proj4" } } JSON; $json = json_decode($json, $assoc); $crs = CoordinateReferenceSystem::jsonUnserialize($json); $expectedProperties = [ 'href' => 'https://example.com/crs/42', 'type' => 'proj4', ]; $this->assertInstanceOf(Linked::class, $crs); $this->assertSame('link', $crs->getType()); $this->assertSame($expectedProperties, $crs->getProperties()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserializationWithoutHrefType($assoc): void { $json = <<<'JSON' { "type": "link", "properties": { "href": "https://example.com/crs/42" } } JSON; $json = json_decode($json, $assoc); $crs = CoordinateReferenceSystem::jsonUnserialize($json); $expectedProperties = ['href' => 'https://example.com/crs/42']; $this->assertInstanceOf(Linked::class, $crs); $this->assertSame('link', $crs->getType()); $this->assertSame($expectedProperties, $crs->getProperties()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } public function testUnserializationShouldRequirePropertiesArrayOrObject(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Linked CRS expected "properties" property of type array or object'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'link', 'properties' => null]); } public function testUnserializationShouldRequireHrefProperty(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Linked CRS expected "properties.href" property of type string'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'link', 'properties' => []]); } } ================================================ FILE: tests/CoordinateReferenceSystem/NamedTest.php ================================================ assertTrue(is_subclass_of(Named::class, CoordinateReferenceSystem::class)); } public function testSerialization(): void { $crs = new Named('urn:ogc:def:crs:OGC:1.3:CRS84'); $expected = [ 'type' => 'name', 'properties' => [ 'name' => 'urn:ogc:def:crs:OGC:1.3:CRS84' ], ]; $this->assertSame('name', $crs->getType()); $this->assertSame($expected['properties'], $crs->getProperties()); $this->assertSame($expected, $crs->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } } JSON; $json = json_decode($json, $assoc); $crs = CoordinateReferenceSystem::jsonUnserialize($json); $expectedProperties = ['name' => 'urn:ogc:def:crs:OGC:1.3:CRS84']; $this->assertInstanceOf(Named::class, $crs); $this->assertSame('name', $crs->getType()); $this->assertSame($expectedProperties, $crs->getProperties()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } public function testUnserializationShouldRequirePropertiesArrayOrObject(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Named CRS expected "properties" property of type array or object'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'name', 'properties' => null]); } public function testUnserializationShouldRequireNameProperty(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Named CRS expected "properties.name" property of type string'); CoordinateReferenceSystem::jsonUnserialize(['type' => 'name', 'properties' => []]); } } ================================================ FILE: tests/Feature/FeatureCollectionTest.php ================================================ assertTrue(is_subclass_of(FeatureCollection::class, GeoJson::class)); } public function testConstructorShouldRequireArrayOfFeatureObjects(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('FeatureCollection may only contain Feature objects'); new FeatureCollection([new stdClass()]); } public function testConstructorShouldReindexFeaturesArrayNumerically(): void { $feature1 = $this->getMockFeature(); $feature2 = $this->getMockFeature(); $features = [ 'one' => $feature1, 'two' => $feature2, ]; $collection = new FeatureCollection($features); $this->assertSame([$feature1, $feature2], iterator_to_array($collection)); } public function testIsTraversable(): void { $features = [ $this->getMockFeature(), $this->getMockFeature(), ]; $collection = new FeatureCollection($features); $this->assertInstanceOf('Traversable', $collection); $this->assertSame($features, iterator_to_array($collection)); } public function testIsCountable(): void { $features = [ $this->getMockFeature(), $this->getMockFeature(), ]; $collection = new FeatureCollection($features); $this->assertInstanceOf('Countable', $collection); $this->assertCount(2, $collection); } public function testSerialization(): void { $features = [ $this->getMockFeature(), $this->getMockFeature(), ]; $features[0]->method('jsonSerialize')->willReturn(['feature1']); $features[1]->method('jsonSerialize')->willReturn(['feature2']); $collection = new FeatureCollection($features); $expected = [ 'type' => GeoJson::TYPE_FEATURE_COLLECTION, 'features' => [['feature1'], ['feature2']], ]; $this->assertSame(GeoJson::TYPE_FEATURE_COLLECTION, $collection->getType()); $this->assertSame($features, $collection->getFeatures()); $this->assertSame($expected, $collection->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": "test.feature.1", "geometry": { "type": "Point", "coordinates": [1, 1] } } ] } JSON; $json = json_decode($json, $assoc); $collection = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(FeatureCollection::class, $collection); $this->assertSame(GeoJson::TYPE_FEATURE_COLLECTION, $collection->getType()); $this->assertCount(1, $collection); $features = iterator_to_array($collection); $feature = $features[0]; $this->assertInstanceOf(Feature::class, $feature); $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType()); $this->assertSame('test.feature.1', $feature->getId()); $this->assertNull($feature->getProperties()); $geometry = $feature->getGeometry(); $this->assertInstanceOf(Point::class, $geometry); $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType()); $this->assertSame([1, 1], $geometry->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } public function testUnserializationShouldRequireFeaturesProperty(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('FeatureCollection expected "features" property of type array, none given'); GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_FEATURE_COLLECTION]); } public function testUnserializationShouldRequireFeaturesArray(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('FeatureCollection expected "features" property of type array'); GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_FEATURE_COLLECTION, 'features' => null]); } } ================================================ FILE: tests/Feature/FeatureTest.php ================================================ assertTrue(is_subclass_of(Feature::class, GeoJson::class)); } public function testSerialization(): void { $geometry = $this->getMockGeometry(); $geometry->method('jsonSerialize')->willReturn(['geometry']); $properties = ['key' => 'value']; $id = 'identifier'; $feature = new Feature($geometry, $properties, $id); $expected = [ 'type' => GeoJson::TYPE_FEATURE, 'geometry' => ['geometry'], 'properties' => $properties, 'id' => 'identifier', ]; $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType()); $this->assertSame($geometry, $feature->getGeometry()); $this->assertSame($id, $feature->getId()); $this->assertSame($properties, $feature->getProperties()); $this->assertSame($expected, $feature->jsonSerialize()); } public function testSerializationWithNullConstructorArguments(): void { $feature = new Feature(); $expected = [ 'type' => GeoJson::TYPE_FEATURE, 'geometry' => null, 'properties' => null, ]; $this->assertSame($expected, $feature->jsonSerialize()); } public function testSerializationShouldConvertEmptyPropertiesArrayToObject(): void { $feature = new Feature(null, []); $expected = [ 'type' => GeoJson::TYPE_FEATURE, 'geometry' => null, 'properties' => new stdClass(), ]; $this->assertEquals($expected, $feature->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "Feature", "id": "test.feature.1", "properties": { "key": "value" }, "geometry": { "type": "Point", "coordinates": [1, 1] } } JSON; $json = json_decode($json, $assoc); $feature = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(Feature::class, $feature); $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType()); $this->assertSame('test.feature.1', $feature->getId()); $this->assertSame(['key' => 'value'], $feature->getProperties()); $geometry = $feature->getGeometry(); $this->assertInstanceOf(Point::class, $geometry); $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType()); $this->assertSame([1, 1], $geometry->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/GeoJsonTest.php ================================================ assertInstanceOf(JsonSerializable::class, $this->createMock(GeoJson::class)); } public function testIsJsonUnserializable(): void { $this->assertInstanceOf(JsonUnserializable::class, $this->createMock(GeoJson::class)); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserializationWithBoundingBox($assoc): void { $json = <<<'JSON' { "type": "Point", "coordinates": [1, 1], "bbox": [-180.0, -90.0, 180.0, 90.0] } JSON; $json = json_decode($json, $assoc); $point = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(Point::class, $point); $this->assertSame(GeoJson::TYPE_POINT, $point->getType()); $this->assertSame([1, 1], $point->getCoordinates()); $boundingBox = $point->getBoundingBox(); $this->assertInstanceOf(BoundingBox::class, $boundingBox); $this->assertSame([-180.0, -90.0, 180.0, 90.0], $boundingBox->getBounds()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserializationWithCrs($assoc): void { $json = <<<'JSON' { "type": "Point", "coordinates": [1, 1], "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } } } JSON; $json = json_decode($json, $assoc); $point = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(Point::class, $point); $this->assertSame(GeoJson::TYPE_POINT, $point->getType()); $this->assertSame([1, 1], $point->getCoordinates()); $crs = $point->getCrs(); $expectedProperties = ['name' => 'urn:ogc:def:crs:OGC:1.3:CRS84']; $this->assertInstanceOf(Named::class, $crs); $this->assertSame('name', $crs->getType()); $this->assertSame($expectedProperties, $crs->getProperties()); } public function testUnserializationWithInvalidArgument(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('GeoJson expected value of type array or object, string given'); GeoJson::jsonUnserialize('must be array or object, but this is a string'); } public function testUnserializationWithUnknownType(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Invalid GeoJson type "Unknown"'); GeoJson::jsonUnserialize(['type' => 'Unknown']); } public function testUnserializationWithMissingType(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('GeoJson expected "type" property of type string, none given'); GeoJson::jsonUnserialize([]); } /** * @dataProvider provideGeoJsonTypesWithCoordinates */ public function testUnserializationWithMissingCoordinates(string $type): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage($type . ' expected "coordinates" property of type array, none given'); GeoJson::jsonUnserialize([ 'type' => $type, ]); } /** * @dataProvider provideInvalidCoordinates * * @param mixed $value */ public function testUnserializationWithInvalidCoordinates($value): void { $valueType = is_object($value) ? get_class($value) : gettype($value); $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Point expected "coordinates" property of type array, ' . $valueType . ' given'); GeoJson::jsonUnserialize([ 'type' => GeoJson::TYPE_POINT, 'coordinates' => $value, ]); } public function testFeatureUnserializationWithInvalidGeometry(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Feature expected "geometry" property of type array or object, string given'); GeoJson::jsonUnserialize([ 'type' => GeoJson::TYPE_FEATURE, 'geometry' => 'must be array or object, but this is a string', ]); } public function testFeatureUnserializationWithInvalidProperties(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('Feature expected "properties" property of type array or object, string given'); GeoJson::jsonUnserialize([ 'type' => GeoJson::TYPE_FEATURE, 'properties' => 'must be array or object, but this is a string', ]); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } public function provideGeoJsonTypesWithCoordinates() { return [ GeoJson::TYPE_LINE_STRING => [GeoJson::TYPE_LINE_STRING], GeoJson::TYPE_MULTI_LINE_STRING => [GeoJson::TYPE_MULTI_LINE_STRING], GeoJson::TYPE_MULTI_POINT => [GeoJson::TYPE_MULTI_POINT], GeoJson::TYPE_MULTI_POLYGON => [GeoJson::TYPE_MULTI_POLYGON], GeoJson::TYPE_POINT => [GeoJson::TYPE_POINT], GeoJson::TYPE_POLYGON => [GeoJson::TYPE_POLYGON], ]; } public function provideInvalidCoordinates() { return [ 'string' => ['1,1'], 'int' => [1], 'bool' => [false], ]; } } ================================================ FILE: tests/Geometry/GeometryCollectionTest.php ================================================ assertTrue(is_subclass_of(GeometryCollection::class, Geometry::class)); } public function testConstructorShouldRequireArrayOfGeometryObjects(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('GeometryCollection may only contain Geometry objects'); new GeometryCollection([new stdClass()]); } public function testConstructorShouldReindexGeometriesArrayNumerically(): void { $geometry1 = $this->getMockGeometry(); $geometry2 = $this->getMockGeometry(); $geometries = [ 'one' => $geometry1, 'two' => $geometry2, ]; $collection = new GeometryCollection($geometries); $this->assertSame([$geometry1, $geometry2], iterator_to_array($collection)); } public function testIsTraversable(): void { $geometries = [ $this->getMockGeometry(), $this->getMockGeometry(), ]; $collection = new GeometryCollection($geometries); $this->assertInstanceOf('Traversable', $collection); $this->assertSame($geometries, iterator_to_array($collection)); } public function testIsCountable(): void { $geometries = [ $this->getMockGeometry(), $this->getMockGeometry(), ]; $collection = new GeometryCollection($geometries); $this->assertInstanceOf('Countable', $collection); $this->assertCount(2, $collection); } public function testSerialization(): void { $geometries = [ $this->getMockGeometry(), $this->getMockGeometry(), ]; $geometries[0]->method('jsonSerialize')->willReturn(['geometry1']); $geometries[1]->method('jsonSerialize')->willReturn(['geometry2']); $collection = new GeometryCollection($geometries); $expected = [ 'type' => GeoJson::TYPE_GEOMETRY_COLLECTION, 'geometries' => [['geometry1'], ['geometry2']], ]; $this->assertSame(GeoJson::TYPE_GEOMETRY_COLLECTION, $collection->getType()); $this->assertSame($geometries, $collection->getGeometries()); $this->assertSame($expected, $collection->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [1, 1] } ] } JSON; $json = json_decode($json, $assoc); $collection = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(GeometryCollection::class, $collection); $this->assertSame(GeoJson::TYPE_GEOMETRY_COLLECTION, $collection->getType()); $this->assertCount(1, $collection); $geometries = iterator_to_array($collection); $geometry = $geometries[0]; $this->assertInstanceOf(Point::class, $geometry); $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType()); $this->assertSame([1, 1], $geometry->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } public function testUnserializationShouldRequireGeometriesProperty(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('GeometryCollection expected "geometries" property of type array, none given'); GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_GEOMETRY_COLLECTION]); } public function testUnserializationShouldRequireGeometriesArray(): void { $this->expectException(UnserializationException::class); $this->expectExceptionMessage('GeometryCollection expected "geometries" property of type array'); GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_GEOMETRY_COLLECTION, 'geometries' => null]); } } ================================================ FILE: tests/Geometry/GeometryTest.php ================================================ assertTrue(is_subclass_of(Geometry::class, GeoJson::class)); } } ================================================ FILE: tests/Geometry/LineStringTest.php ================================================ assertTrue(is_subclass_of(LineString::class, MultiPoint::class)); } public function testConstructorShouldRequireAtLeastTwoPositions(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('LineString requires at least two positions'); new LineString([[1, 1]]); } public function testSerialization(): void { $coordinates = [[1, 1], [2, 2]]; $lineString = new LineString($coordinates); $expected = [ 'type' => GeoJson::TYPE_LINE_STRING, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_LINE_STRING, $lineString->getType()); $this->assertSame($coordinates, $lineString->getCoordinates()); $this->assertSame($expected, $lineString->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "LineString", "coordinates": [ [1, 1], [2, 2] ] } JSON; $json = json_decode($json, $assoc); $lineString = GeoJson::jsonUnserialize($json); $expectedCoordinates = [[1, 1], [2, 2]]; $this->assertInstanceOf(LineString::class, $lineString); $this->assertSame(GeoJson::TYPE_LINE_STRING, $lineString->getType()); $this->assertSame($expectedCoordinates, $lineString->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/Geometry/LinearRingTest.php ================================================ assertTrue(is_subclass_of(LinearRing::class, LineString::class)); } public function testConstructorShouldRequireAtLeastFourPositions(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('LinearRing requires at least four positions'); new LinearRing([ [1, 1], [2, 2], [3, 3], ]); } public function testConstructorShouldRequireEquivalentFirstAndLastPositions(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('LinearRing requires the first and last positions to be equivalent'); new LinearRing([ [1, 1], [2, 2], [3, 3], [4, 4], ]); } /** * @doesNotPerformAssertions */ public function testConstructorShouldAcceptEquivalentPointObjectsAndPositionArrays(): void { new LinearRing([ [1, 1], [2, 2], [3, 3], new Point([1, 1]), ]); } public function testSerialization(): void { $coordinates = [[1, 1], [2, 2], [3, 3], [1, 1]]; $linearRing = new LinearRing($coordinates); $expected = [ 'type' => GeoJson::TYPE_LINE_STRING, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_LINE_STRING, $linearRing->getType()); $this->assertSame($coordinates, $linearRing->getCoordinates()); $this->assertSame($expected, $linearRing->jsonSerialize()); } } ================================================ FILE: tests/Geometry/MultiLineStringTest.php ================================================ assertTrue(is_subclass_of(MultiLineString::class, Geometry::class)); } public function testConstructionFromLineStringObjects(): void { $multiLineString1 = new MultiLineString([ new LineString([[1, 1], [2, 2]]), new LineString([[3, 3], [4, 4]]), ]); $multiLineString2 = new MultiLineString([ [[1, 1], [2, 2]], [[3, 3], [4, 4]], ]); $this->assertSame($multiLineString1->getCoordinates(), $multiLineString2->getCoordinates()); } public function testSerialization(): void { $coordinates = [ [[1, 1], [2, 2]], [[3, 3], [4, 4]], ]; $multiLineString = new MultiLineString($coordinates); $expected = [ 'type' => GeoJson::TYPE_MULTI_LINE_STRING, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_MULTI_LINE_STRING, $multiLineString->getType()); $this->assertSame($coordinates, $multiLineString->getCoordinates()); $this->assertSame($expected, $multiLineString->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "MultiLineString", "coordinates": [ [ [1, 1], [2, 2] ], [ [3, 3], [4, 4] ] ] } JSON; $json = json_decode($json, $assoc); $multiLineString = GeoJson::jsonUnserialize($json); $expectedCoordinates = [ [[1, 1], [2, 2]], [[3, 3], [4, 4]], ]; $this->assertInstanceOf(MultiLineString::class, $multiLineString); $this->assertSame(GeoJson::TYPE_MULTI_LINE_STRING, $multiLineString->getType()); $this->assertSame($expectedCoordinates, $multiLineString->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/Geometry/MultiPointTest.php ================================================ assertTrue(is_subclass_of(MultiPoint::class, Geometry::class)); } public function testConstructionFromPointObjects(): void { $multiPoint1 = new MultiPoint([ new Point([1, 1]), new Point([2, 2]), ]); $multiPoint2 = new MultiPoint([ [1, 1], [2, 2], ]); $this->assertSame($multiPoint1->getCoordinates(), $multiPoint2->getCoordinates()); } public function testSerialization(): void { $coordinates = [[1, 1], [2, 2]]; $multiPoint = new MultiPoint($coordinates); $expected = [ 'type' => GeoJson::TYPE_MULTI_POINT, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_MULTI_POINT, $multiPoint->getType()); $this->assertSame($coordinates, $multiPoint->getCoordinates()); $this->assertSame($expected, $multiPoint->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "MultiPoint", "coordinates": [ [1, 1], [2, 2] ] } JSON; $json = json_decode($json, $assoc); $multiPoint = GeoJson::jsonUnserialize($json); $expectedCoordinates = [[1, 1], [2, 2]]; $this->assertInstanceOf(MultiPoint::class, $multiPoint); $this->assertSame(GeoJson::TYPE_MULTI_POINT, $multiPoint->getType()); $this->assertSame($expectedCoordinates, $multiPoint->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/Geometry/MultiPolygonTest.php ================================================ assertTrue(is_subclass_of(MultiPolygon::class, Geometry::class)); } public function testConstructionFromPolygonObjects(): void { $multiPolygon1 = new MultiPolygon([ new Polygon([[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]]), new Polygon([[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]]), ]); $multiPolygon2 = new MultiPolygon([ [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]], [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]], ]); $this->assertSame($multiPolygon1->getCoordinates(), $multiPolygon2->getCoordinates()); } public function testSerialization(): void { $coordinates = [ [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]], [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]], ]; $multiPolygon = new MultiPolygon($coordinates); $expected = [ 'type' => GeoJson::TYPE_MULTI_POLYGON, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_MULTI_POLYGON, $multiPolygon->getType()); $this->assertSame($coordinates, $multiPolygon->getCoordinates()); $this->assertSame($expected, $multiPolygon->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "MultiPolygon", "coordinates": [ [ [ [0, 0], [0, 4], [4, 4], [4, 0], [0, 0] ] ], [ [ [1, 1], [1, 3], [3, 3], [3, 1], [1, 1] ] ] ] } JSON; $json = json_decode($json, $assoc); $multiPolygon = GeoJson::jsonUnserialize($json); $expectedCoordinates = [ [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]], [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]], ]; $this->assertInstanceOf(MultiPolygon::class, $multiPolygon); $this->assertSame(GeoJson::TYPE_MULTI_POLYGON, $multiPolygon->getType()); $this->assertSame($expectedCoordinates, $multiPolygon->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/Geometry/PointTest.php ================================================ assertTrue(is_subclass_of(Point::class, Geometry::class)); } public function testConstructorShouldRequireAtLeastTwoElementsInPosition(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Position requires at least two elements'); new Point([1]); } /** * @dataProvider providePositionsWithInvalidTypes */ public function testConstructorShouldRequireIntegerOrFloatElementsInPosition(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Position elements must be integers or floats'); new Point(func_get_args()); } public function providePositionsWithInvalidTypes() { return [ 'strings' => ['1.0', '2'], 'objects' => [new stdClass(), new stdClass()], 'arrays' => [[], []], ]; } public function testConstructorShouldAllowMoreThanTwoElementsInAPosition(): void { $point = new Point([1, 2, 3, 4]); $this->assertEquals([1, 2, 3, 4], $point->getCoordinates()); } public function testSerialization(): void { $coordinates = [1, 1]; $point = new Point($coordinates); $expected = [ 'type' => GeoJson::TYPE_POINT, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_POINT, $point->getType()); $this->assertSame($coordinates, $point->getCoordinates()); $this->assertSame($expected, $point->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "Point", "coordinates": [1, 1] } JSON; $json = json_decode($json, $assoc); $point = GeoJson::jsonUnserialize($json); $this->assertInstanceOf(Point::class, $point); $this->assertSame(GeoJson::TYPE_POINT, $point->getType()); $this->assertSame([1, 1], $point->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } } ================================================ FILE: tests/Geometry/PolygonTest.php ================================================ assertTrue(is_subclass_of(Polygon::class, Geometry::class)); } public function testConstructionFromLinearRingObjects(): void { $polygon1 = new Polygon([ new LinearRing([[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]), new LinearRing([[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]), ]); $polygon2 = new Polygon([ [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], ]); $this->assertSame($polygon1->getCoordinates(), $polygon2->getCoordinates()); } public function testSerialization(): void { $coordinates = [ [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], ]; $polygon = new Polygon($coordinates); $expected = [ 'type' => GeoJson::TYPE_POLYGON, 'coordinates' => $coordinates, ]; $this->assertSame(GeoJson::TYPE_POLYGON, $polygon->getType()); $this->assertSame($coordinates, $polygon->getCoordinates()); $this->assertSame($expected, $polygon->jsonSerialize()); } /** * @dataProvider provideJsonDecodeAssocOptions * @group functional */ public function testUnserialization($assoc): void { $json = <<<'JSON' { "type": "Polygon", "coordinates": [ [ [0, 0], [0, 4], [4, 4], [4, 0], [0, 0] ], [ [1, 1], [1, 3], [3, 3], [3, 1], [1, 1] ] ] } JSON; $json = json_decode($json, $assoc); $polygon = GeoJson::jsonUnserialize($json); $expectedCoordinates = [ [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], ]; $this->assertInstanceOf(Polygon::class, $polygon); $this->assertSame(GeoJson::TYPE_POLYGON, $polygon->getType()); $this->assertSame($expectedCoordinates, $polygon->getCoordinates()); } public function provideJsonDecodeAssocOptions() { return [ 'assoc=true' => [true], 'assoc=false' => [false], ]; } }