Full Code of jmikola/geojson for AI

master e28f3855bb61 cached
48 files
99.5 KB
25.3k tokens
213 symbols
1 requests
Download .txt
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
================================================
<?xml version="1.0"?>
<ruleset>
    <arg name="basepath" value="." />
    <arg name="extensions" value="php" />
    <arg name="cache" value=".phpcs-cache" />
    <arg name="colors" />

    <!-- Ignore warnings (n), show progress of the run (p), and show sniff names (s) -->
    <arg value="nps"/>

    <file>src</file>
    <file>tests</file>

    <rule ref="PSR12">

        <!-- ********************************************** -->
        <!-- Exclude sniffs that require newer PHP versions -->
        <!-- ********************************************** -->

        <!-- Requires PHP 8.0 -->
        <exclude name="SlevomatCodingStandard.Classes.ModernClassNameReference.ClassNameReferencedViaFunctionCall" />


        <!-- *********************************** -->
        <!-- Exclude sniffs that cause BC breaks -->
        <!-- *********************************** -->
        <exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming" />
        <exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming" />
        <exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming" />
        <exclude name="SlevomatCodingStandard.Classes.SuperfluousTraitNaming" />


        <!-- **************************************** -->
        <!-- Exclude sniffs that force unwanted style -->
        <!-- **************************************** -->
        <exclude name="Generic.Formatting.MultipleStatementAlignment" />
        <exclude name="Squiz.Commenting.FunctionComment.ThrowsNoFullStop" />

        <!-- Keep long typehints (for now) -->
        <exclude name="SlevomatCodingStandard.PHP.TypeCast.InvalidCastUsed" />
        <exclude name="SlevomatCodingStandard.TypeHints.LongTypeHints" />


        <!-- ************************************************ -->
        <!-- Exclude sniffs that may cause functional changes -->
        <!-- ************************************************ -->
        <exclude name="Generic.PHP.ForbiddenFunctions.FoundWithAlternative" />
        <exclude name="SlevomatCodingStandard.ControlStructures.DisallowYodaComparison" />
        <exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit" />
        <exclude name="SlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturn" />
        <exclude name="SlevomatCodingStandard.Functions.StaticClosure" />
        <exclude name="SlevomatCodingStandard.Functions.UnusedInheritedVariablePassedToClosure" />
        <exclude name="SlevomatCodingStandard.Operators.DisallowEqualOperators" />


        <!-- ********************************************************* -->
        <!-- Exclude sniffs that cause a huge diff - enable separately -->
        <!-- ********************************************************* -->
        <exclude name="SlevomatCodingStandard.Commenting.DocCommentSpacing.IncorrectAnnotationsGroup" />
        <exclude name="Squiz.Strings.DoubleQuoteUsage" />


        <!-- ********************* -->
        <!-- Exclude broken sniffs -->
        <!-- ********************* -->

        <!-- Sniff currently broken when casting arrays, see https://github.com/squizlabs/PHP_CodeSniffer/issues/2937#issuecomment-615498860 -->
        <exclude name="Squiz.Arrays.ArrayDeclaration.ValueNoNewline" />

        <!-- Disable forbidden annotation sniff as excluding @api from the list doesn't work -->
        <exclude name="SlevomatCodingStandard.Commenting.ForbiddenAnnotations.AnnotationForbidden" />
    </rule>


    <!-- ***************************************************** -->
    <!-- Forbid fully qualified names even for colliding names -->
    <!-- ***************************************************** -->
    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
        <properties>
            <property name="allowFallbackGlobalConstants" value="false"/>
            <property name="allowFallbackGlobalFunctions" value="false"/>
            <property name="allowFullyQualifiedGlobalClasses" value="false"/>
            <property name="allowFullyQualifiedGlobalConstants" value="false"/>
            <property name="allowFullyQualifiedGlobalFunctions" value="false"/>
            <property phpcs-only="true" name="allowFullyQualifiedNameForCollidingClasses" value="true"/>
            <property phpcs-only="true" name="allowFullyQualifiedNameForCollidingConstants" value="false"/>
            <property phpcs-only="true" name="allowFullyQualifiedNameForCollidingFunctions" value="false"/>
            <property name="searchAnnotations" value="true"/>
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses"/>

    <!-- **************************************************************************** -->
    <!-- Exclude BC breaking type hints for parameters, properties, and return values -->
    <!-- **************************************************************************** -->
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint">
        <properties>
            <!-- Requires PHP 8.0 -->
            <property name="enableMixedTypeHint" value="false" />
            <!-- Requires PHP 8.0 -->
            <property name="enableUnionTypeHint" value="false" />
        </properties>

        <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification" />
        <exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.UselessAnnotation" />
    </rule>

    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint">
        <properties>
            <!-- Requires PHP 8.0 -->
            <property name="enableMixedTypeHint" value="false" />
            <!-- Requires PHP 8.0 -->
            <property name="enableUnionTypeHint" value="false" />
        </properties>

        <exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification" />
        <exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.UselessAnnotation" />
    </rule>

    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint">
        <properties>
            <!-- Requires PHP 8.0 -->
            <property name="enableStaticTypeHint" value="false" />
            <!-- Requires PHP 8.0 -->
            <property name="enableMixedTypeHint" value="false" />
            <!-- Requires PHP 8.0 -->
            <property name="enableUnionTypeHint" value="false" />
        </properties>

        <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification" />
        <exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.UselessAnnotation" />
    </rule>


    <!-- ************************************************************************** -->
    <!-- Require type hints for all parameters, properties, and return types in src -->
    <!-- ************************************************************************** -->
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint">
        <exclude-pattern>tests</exclude-pattern>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint">
        <exclude-pattern>tests</exclude-pattern>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint">
        <exclude-pattern>tests</exclude-pattern>
    </rule>


    <!-- *********************************************************************************** -->
    <!-- Require native type hints for all parameters, properties, and return types in tests -->
    <!-- *********************************************************************************** -->
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint">
        <exclude-pattern>src</exclude-pattern>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint">
        <exclude-pattern>src</exclude-pattern>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint">
        <exclude-pattern>src</exclude-pattern>
    </rule>
    <rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
        <properties>
            <property name="newlinesCountBetweenOpenTagAndDeclare" type="int" value="2" />
            <property name="spacesCountAroundEqualsSign" type="int" value="0" />
        </properties>
    </rule>

    <rule ref="Generic.Arrays.DisallowLongArraySyntax" />
</ruleset>

================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>

<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    colors="true"
>
    <testsuites>
        <testsuite name="GeoJson Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>

    <coverage ignoreDeprecatedCodeUnits="true" processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./src/</directory>
        </include>
    </coverage>
</phpunit>


================================================
FILE: src/BoundingBox.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\Exception\UnserializationException;
use JsonSerializable;

use function count;
use function is_array;
use function is_float;
use function is_int;

/**
 * BoundingBox object.
 *
 * @see http://www.geojson.org/geojson-spec.html#bounding-boxes
 * @since 1.0
 */
class BoundingBox implements JsonSerializable, JsonUnserializable
{
    /**
     * @var array<float|int>
     */
    protected array $bounds;

    /**
     * @param array<float|int> $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<float|int>
     */
    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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\CoordinateReferenceSystem;

use ArrayObject;
use BadMethodCallException;
use GeoJson\Exception\UnserializationException;
use GeoJson\JsonUnserializable;
use JsonSerializable;

use function is_array;
use function is_object;
use function sprintf;

/**
 * Coordinate reference system object.
 *
 * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,
 *                 the 'crs' member of [GJ2008] is no longer used.
 *
 * @see https://www.rfc-editor.org/rfc/rfc7946#appendix-B.1
 * @see http://www.geojson.org/geojson-spec.html#coordinate-reference-system-objects
 * @since 1.0
 */
abstract class CoordinateReferenceSystem implements JsonSerializable, JsonUnserializable
{
    protected array $properties;

    protected string $type;

    /**
     * Return the properties for this CRS object.
     */
    public function getProperties(): array
    {
        return $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\CoordinateReferenceSystem;

use ArrayObject;
use GeoJson\Exception\UnserializationException;

use function is_array;
use function is_object;

/**
 * Linked coordinate reference system object.
 *
 * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,
 *                 the 'crs' member of [GJ2008] is no longer used.
 *
 * @see https://www.rfc-editor.org/rfc/rfc7946#appendix-B.1
 * @see http://www.geojson.org/geojson-spec.html#linked-crs
 * @since 1.0
 */
class Linked extends CoordinateReferenceSystem
{
    protected string $type = 'link';

    public function __construct(string $href, ?string $type = null)
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\CoordinateReferenceSystem;

use ArrayObject;
use GeoJson\Exception\UnserializationException;

use function is_array;
use function is_object;

/**
 * Named coordinate reference system object.
 *
 * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,
 *                 the 'crs' member of [GJ2008] is no longer used.
 *
 * @see http://www.geojson.org/geojson-spec.html#named-crs
 * @since 1.0
 */
class Named extends CoordinateReferenceSystem
{
    protected string $type = 'name';

    public function __construct(string $name)
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Exception;

interface Exception
{
}


================================================
FILE: src/Exception/InvalidArgumentException.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Exception;

class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}


================================================
FILE: src/Exception/UnserializationException.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Exception;

use RuntimeException;

use function get_class;
use function get_debug_type;
use function gettype;
use function is_object;
use function sprintf;

class UnserializationException extends RuntimeException implements Exception
{
    /**
     * Creates an UnserializationException for a value with an invalid type.
     *
     * @param mixed $value
     */
    public static function invalidValue(string $context, $value, string $expectedType): self
    {
        return new self(sprintf(
            '%s expected value of type %s, %s given',
            $context,
            $expectedType,
            get_debug_type($value)
        ));
    }

    /**
     * Creates an UnserializationException for a property with an invalid type.
     *
     * @param mixed $value
     */
    public static function invalidProperty(string $context, string $property, $value, string $expectedType): self
    {
        return new self(sprintf(
            '%s expected "%s" property of type %s, %s given',
            $context,
            $property,
            $expectedType,
            is_object($value) ? get_class($value) : gettype($value)
        ));
    }

    /**
     * Creates an UnserializationException for a missing property.
     */
    public static function missingProperty(string $context, string $property, string $expectedType): self
    {
        return new self(sprintf(
            '%s expected "%s" property of type %s, none given',
            $context,
            $property,
            $expectedType
        ));
    }

    /**
     * Creates an UnserializationException for an unsupported "type" property.
     */
    public static function unsupportedType(string $context, string $value): self
    {
        return new self(sprintf('Invalid %s type "%s"', $context, $value));
    }
}


================================================
FILE: src/Feature/Feature.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Feature;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use stdClass;

/**
 * Feature object.
 *
 * @see http://www.geojson.org/geojson-spec.html#feature-objects
 * @since 1.0
 */
class Feature extends GeoJson
{
    protected string $type = self::TYPE_FEATURE;

    protected ?Geometry $geometry;

    /**
     * Properties are a JSON object, which corresponds to an associative array, or null.
     *
     * @see https://www.rfc-editor.org/rfc/rfc7946#section-3.2
     */
    protected ?array $properties;

    /**
     * The identifier is either a JSON string or a number.
     *
     * @see https://www.rfc-editor.org/rfc/rfc7946#section-3.2
     *
     * @var int|string|null
     */
    protected $id;

    /**
     * @param int|string|null $id
     * @param CoordinateReferenceSystem|BoundingBox $args
     */
    public function __construct(?Geometry $geometry = null, ?array $properties = null, $id = null, ...$args)
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Feature;

use ArrayIterator;
use Countable;
use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\InvalidArgumentException;
use GeoJson\GeoJson;
use IteratorAggregate;
use Traversable;

use function array_map;
use function array_merge;
use function array_values;
use function count;

/**
 * Collection of Feature objects.
 *
 * @see http://www.geojson.org/geojson-spec.html#feature-collection-objects
 * @since 1.0
 */
class FeatureCollection extends GeoJson implements Countable, IteratorAggregate
{
    protected string $type = self::TYPE_FEATURE_COLLECTION;

    /**
     * @var array<Feature>
     */
    protected array $features;

    /**
     * @param array<Feature> $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<Feature>
     */
    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
================================================
<?php

declare(strict_types=1);

namespace GeoJson;

use ArrayObject;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\UnserializationException;
use JsonSerializable;

use function array_map;
use function is_array;
use function is_object;
use function sprintf;
use function strncmp;

/**
 * Base GeoJson object.
 *
 * @see http://www.geojson.org/geojson-spec.html#geojson-objects
 * @since 1.0
 */
abstract class GeoJson implements JsonSerializable, JsonUnserializable
{
    public const TYPE_LINE_STRING = 'LineString';
    public const TYPE_MULTI_LINE_STRING = 'MultiLineString';
    public const TYPE_MULTI_POINT = 'MultiPoint';
    public const TYPE_MULTI_POLYGON = 'MultiPolygon';
    public const TYPE_POINT = 'Point';
    public const TYPE_POLYGON = 'Polygon';
    public const TYPE_FEATURE = 'Feature';
    public const TYPE_FEATURE_COLLECTION = 'FeatureCollection';
    public const TYPE_GEOMETRY_COLLECTION = 'GeometryCollection';

    protected ?BoundingBox $boundingBox = null;

    protected ?CoordinateReferenceSystem $crs = null;

    protected string $type;

    /**
     * Return the BoundingBox for this GeoJson object.
     */
    public function getBoundingBox(): ?BoundingBox
    {
        return $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\GeoJson;

/**
 * Base geometry object.
 *
 * @see http://www.geojson.org/geojson-spec.html#geometry-objects
 * @since 1.0
 */
abstract class Geometry extends GeoJson
{
    protected array $coordinates;

    /**
     * Return the coordinates for this Geometry object.
     */
    public function getCoordinates(): array
    {
        return $this->coordinates;
    }

    public function jsonSerialize(): array
    {
        $json = parent::jsonSerialize();

        if (isset($this->coordinates)) {
            $json['coordinates'] = $this->coordinates;
        }

        return $json;
    }
}


================================================
FILE: src/Geometry/GeometryCollection.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use ArrayIterator;
use Countable;
use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\InvalidArgumentException;
use IteratorAggregate;
use Traversable;

use function array_map;
use function array_merge;
use function array_values;
use function count;

/**
 * Collection of Geometry objects.
 *
 * @see http://www.geojson.org/geojson-spec.html#geometry-collection
 * @since 1.0
 */
class GeometryCollection extends Geometry implements Countable, IteratorAggregate
{
    protected string $type = self::TYPE_GEOMETRY_COLLECTION;

    /**
     * @var array<Geometry>
     */
    protected array $geometries;

    /**
     * @param array<Geometry> $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<Geometry>
     */
    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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\InvalidArgumentException;

use function count;

/**
 * LineString geometry object.
 *
 * Coordinates consist of an array of at least two positions.
 *
 * @see http://www.geojson.org/geojson-spec.html#linestring
 * @since 1.0
 */
class LineString extends MultiPoint
{
    protected string $type = self::TYPE_LINE_STRING;

    /**
     * @param array<Point|array<float|int>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\InvalidArgumentException;

use function count;
use function end;
use function reset;

/**
 * LinearRing is a special kind of LineString geometry object.
 *
 * Coordinates consist of an array of at least four positions, where the first
 * and last positions are equivalent.
 *
 * @see http://www.geojson.org/geojson-spec.html#linestring
 * @since 1.0
 */
class LinearRing extends LineString
{
    /**
     * @param array<Point|array<int|float>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;

use function array_map;

/**
 * MultiLineString geometry object.
 *
 * Coordinates consist of an array of LineString coordinates.
 *
 * @see http://www.geojson.org/geojson-spec.html#multilinestring
 * @since 1.0
 */
class MultiLineString extends Geometry
{
    protected string $type = self::TYPE_MULTI_LINE_STRING;

    /**
     * @param array<LineString|array<Point|array<int|float>>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;

use function array_map;

/**
 * MultiPoint geometry object.
 *
 * Coordinates consist of an array of positions.
 *
 * @see http://www.geojson.org/geojson-spec.html#multipoint
 * @since 1.0
 */
class MultiPoint extends Geometry
{
    protected string $type = self::TYPE_MULTI_POINT;

    /**
     * @param array<Point|array<float|int>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;

use function array_map;

/**
 * MultiPolygon geometry object.
 *
 * Coordinates consist of an array of Polygon coordinates.
 *
 * @see http://www.geojson.org/geojson-spec.html#multipolygon
 * @since 1.0
 */
class MultiPolygon extends Geometry
{
    protected string $type = self::TYPE_MULTI_POLYGON;

    /**
     * @param array<Polygon|array<LinearRing|array<Point|array<int|float>>>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\InvalidArgumentException;

use function count;
use function is_float;
use function is_int;

/**
 * Point geometry object.
 *
 * Coordinates consist of a single position.
 *
 * @see http://www.geojson.org/geojson-spec.html#point
 * @since 1.0
 */
class Point extends Geometry
{
    protected string $type = self::TYPE_POINT;

    /**
     * @param array<float|int> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Geometry;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;

/**
 * Polygon geometry object.
 *
 * Coordinates consist of an array of LinearRing coordinates.
 *
 * @see http://www.geojson.org/geojson-spec.html#polygon
 * @since 1.0
 */
class Polygon extends Geometry
{
    protected string $type = self::TYPE_POLYGON;

    /**
     * @param array<LinearRing|array<Point|array<int|float>>> $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
================================================
<?php

declare(strict_types=1);

namespace GeoJson;

use GeoJson\Exception\UnserializationException;

/**
 * JsonUnserializable interface for creating an object from decoded JSON.
 *
 * This is used as a factory method for GeoJson, BoundingBox, and CRS classes.
 *
 * @since 1.0
 */
interface JsonUnserializable
{
    /**
     * Factory method for creating an object from a decoded JSON value.
     *
     * @param mixed $json
     * @return mixed
     * @throws UnserializationException
     */
    public static function jsonUnserialize($json);
}


================================================
FILE: tests/BaseGeoJsonTest.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Feature\Feature;
use GeoJson\Geometry\Geometry;
use PHPUnit\Framework\TestCase;

abstract class BaseGeoJsonTest extends TestCase
{
    /**
     * @param ...$extraArgs
     *
     * @return mixed
     */
    abstract public function createSubjectWithExtraArguments(...$extraArgs);

    public function testConstructorShouldScanExtraArgumentsForCrsAndBoundingBox(): void
    {
        $box = $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests;

use GeoJson\BoundingBox;
use GeoJson\Exception\InvalidArgumentException;
use GeoJson\Exception\UnserializationException;
use GeoJson\JsonUnserializable;
use PHPUnit\Framework\TestCase;
use stdClass;

use function func_get_args;
use function json_decode;

class BoundingBoxTest extends TestCase
{
    public function testIsJsonSerializable(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\CoordinateReferenceSystem;

use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\Exception\UnserializationException;
use GeoJson\JsonUnserializable;
use JsonSerializable;
use PHPUnit\Framework\TestCase;

class CoordinateReferenceSystemTest extends TestCase
{
    public function testIsJsonSerializable(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\CoordinateReferenceSystem;

use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\CoordinateReferenceSystem\Linked;
use GeoJson\Exception\UnserializationException;
use PHPUnit\Framework\TestCase;

use function is_subclass_of;
use function json_decode;

class LinkedTest extends TestCase
{
    public function testIsSubclassOfCoordinateReferenceSystem(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\CoordinateReferenceSystem;

use GeoJson\CoordinateReferenceSystem\CoordinateReferenceSystem;
use GeoJson\CoordinateReferenceSystem\Named;
use GeoJson\Exception\UnserializationException;
use PHPUnit\Framework\TestCase;

use function is_subclass_of;
use function json_decode;

class NamedTest extends TestCase
{
    public function testIsSubclassOfCoordinateReferenceSystem(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Feature;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\Exception\UnserializationException;
use GeoJson\Feature\Feature;
use GeoJson\Feature\FeatureCollection;
use GeoJson\GeoJson;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;
use stdClass;

use function is_subclass_of;
use function iterator_to_array;
use function json_decode;

class FeatureCollectionTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new FeatureCollection([], ... $extraArgs);
    }

    public function testIsSubclassOfGeoJson(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Feature;

use GeoJson\Feature\Feature;
use GeoJson\GeoJson;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;
use stdClass;

use function is_subclass_of;
use function json_decode;

class FeatureTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new Feature(null, null, null, ... $extraArgs);
    }

    public function testIsSubclassOfGeoJson(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests;

use GeoJson\BoundingBox;
use GeoJson\CoordinateReferenceSystem\Named;
use GeoJson\Exception\UnserializationException;
use GeoJson\GeoJson;
use GeoJson\Geometry\Point;
use GeoJson\JsonUnserializable;
use JsonSerializable;
use PHPUnit\Framework\TestCase;

use function get_class;
use function gettype;
use function is_object;
use function json_decode;

class GeoJsonTest extends TestCase
{
    public function testIsJsonSerializable(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\Exception\UnserializationException;
use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\GeometryCollection;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;
use stdClass;

use function is_subclass_of;
use function iterator_to_array;
use function json_decode;

class GeometryCollectionTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new GeometryCollection([], ... $extraArgs);
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use PHPUnit\Framework\TestCase;

use function is_subclass_of;

class GeometryTest extends TestCase
{
    public function testIsSubclassOfGeoJson(): void
    {
        $this->assertTrue(is_subclass_of(Geometry::class, GeoJson::class));
    }
}


================================================
FILE: tests/Geometry/LineStringTest.php
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\GeoJson;
use GeoJson\Geometry\LineString;
use GeoJson\Geometry\MultiPoint;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;
use function json_decode;

class LineStringTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new LineString(
            [[1, 1], [2, 2]],
            ... $extraArgs
        );
    }

    public function testIsSubclassOfMultiPoint(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\GeoJson;
use GeoJson\Geometry\LinearRing;
use GeoJson\Geometry\LineString;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;

class LinearRingTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new LinearRing(
            [[1, 1], [2, 2], [3, 3], [1, 1]],
            ... $extraArgs
        );
    }

    public function testIsSubclassOfLineString(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\LineString;
use GeoJson\Geometry\MultiLineString;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;
use function json_decode;

class MultiLineStringTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new MultiLineString([], ... $extraArgs);
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\MultiPoint;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;
use function json_decode;

class MultiPointTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new MultiPoint([], ... $extraArgs);
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\MultiPolygon;
use GeoJson\Geometry\Polygon;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;
use function json_decode;

class MultiPolygonTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new MultiPolygon([], ... $extraArgs);
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\Exception\InvalidArgumentException;
use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\Point;
use GeoJson\Tests\BaseGeoJsonTest;
use stdClass;

use function func_get_args;
use function is_subclass_of;
use function json_decode;

class PointTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new Point([1, 1], ... $extraArgs);
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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
================================================
<?php

declare(strict_types=1);

namespace GeoJson\Tests\Geometry;

use GeoJson\GeoJson;
use GeoJson\Geometry\Geometry;
use GeoJson\Geometry\LinearRing;
use GeoJson\Geometry\Polygon;
use GeoJson\Tests\BaseGeoJsonTest;

use function is_subclass_of;
use function json_decode;

class PolygonTest extends BaseGeoJsonTest
{
    public function createSubjectWithExtraArguments(...$extraArgs)
    {
        return new Polygon(
            [
                [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]],
                [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]],
            ],
            ... $extraArgs
        );
    }

    public function testIsSubclassOfGeometry(): void
    {
        $this->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],
        ];
    }
}
Download .txt
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
Download .txt
SYMBOL INDEX (213 symbols across 37 files)

FILE: src/BoundingBox.php
  class BoundingBox (line 22) | class BoundingBox implements JsonSerializable, JsonUnserializable
    method __construct (line 32) | public function __construct(array $bounds)
    method getBounds (line 64) | public function getBounds(): array
    method jsonSerialize (line 69) | public function jsonSerialize(): array
    method jsonUnserialize (line 77) | final public static function jsonUnserialize($json): self

FILE: src/CoordinateReferenceSystem/CoordinateReferenceSystem.php
  class CoordinateReferenceSystem (line 27) | abstract class CoordinateReferenceSystem implements JsonSerializable, Js...
    method getProperties (line 36) | public function getProperties(): array
    method getType (line 44) | public function getType(): string
    method jsonSerialize (line 49) | public function jsonSerialize(): array
    method jsonUnserialize (line 60) | final public static function jsonUnserialize($json): self
    method jsonUnserializeFromProperties (line 99) | protected static function jsonUnserializeFromProperties($properties): ...

FILE: src/CoordinateReferenceSystem/Linked.php
  class Linked (line 23) | class Linked extends CoordinateReferenceSystem
    method __construct (line 27) | public function __construct(string $href, ?string $type = null)
    method jsonUnserializeFromProperties (line 43) | protected static function jsonUnserializeFromProperties($properties): ...

FILE: src/CoordinateReferenceSystem/Named.php
  class Named (line 22) | class Named extends CoordinateReferenceSystem
    method __construct (line 26) | public function __construct(string $name)
    method jsonUnserializeFromProperties (line 38) | protected static function jsonUnserializeFromProperties($properties): ...

FILE: src/Exception/Exception.php
  type Exception (line 7) | interface Exception

FILE: src/Exception/InvalidArgumentException.php
  class InvalidArgumentException (line 7) | class InvalidArgumentException extends \InvalidArgumentException impleme...

FILE: src/Exception/UnserializationException.php
  class UnserializationException (line 15) | class UnserializationException extends RuntimeException implements Excep...
    method invalidValue (line 22) | public static function invalidValue(string $context, $value, string $e...
    method invalidProperty (line 37) | public static function invalidProperty(string $context, string $proper...
    method missingProperty (line 51) | public static function missingProperty(string $context, string $proper...
    method unsupportedType (line 64) | public static function unsupportedType(string $context, string $value)...

FILE: src/Feature/Feature.php
  class Feature (line 19) | class Feature extends GeoJson
    method __construct (line 45) | public function __construct(?Geometry $geometry = null, ?array $proper...
    method getGeometry (line 57) | public function getGeometry(): ?Geometry
    method getId (line 67) | public function getId()
    method getProperties (line 75) | public function getProperties(): ?array
    method jsonSerialize (line 80) | public function jsonSerialize(): array

FILE: src/Feature/FeatureCollection.php
  class FeatureCollection (line 27) | class FeatureCollection extends GeoJson implements Countable, IteratorAg...
    method __construct (line 40) | public function __construct(array $features, ...$args)
    method count (line 53) | public function count(): int
    method getFeatures (line 63) | public function getFeatures(): array
    method getIterator (line 68) | public function getIterator(): Traversable
    method jsonSerialize (line 73) | public function jsonSerialize(): array

FILE: src/GeoJson.php
  class GeoJson (line 24) | abstract class GeoJson implements JsonSerializable, JsonUnserializable
    method getBoundingBox (line 45) | public function getBoundingBox(): ?BoundingBox
    method getCrs (line 53) | public function getCrs(): ?CoordinateReferenceSystem
    method getType (line 61) | public function getType(): string
    method jsonSerialize (line 66) | public function jsonSerialize(): array
    method jsonUnserialize (line 84) | final public static function jsonUnserialize($json): self
    method setOptionalConstructorArgs (line 184) | protected function setOptionalConstructorArgs(array $args): void

FILE: src/Geometry/Geometry.php
  class Geometry (line 15) | abstract class Geometry extends GeoJson
    method getCoordinates (line 22) | public function getCoordinates(): array
    method jsonSerialize (line 27) | public function jsonSerialize(): array

FILE: src/Geometry/GeometryCollection.php
  class GeometryCollection (line 26) | class GeometryCollection extends Geometry implements Countable, Iterator...
    method __construct (line 39) | public function __construct(array $geometries, ...$args)
    method count (line 52) | public function count(): int
    method getGeometries (line 62) | public function getGeometries(): array
    method getIterator (line 67) | public function getIterator(): Traversable
    method jsonSerialize (line 72) | public function jsonSerialize(): array

FILE: src/Geometry/LineString.php
  class LineString (line 21) | class LineString extends MultiPoint
    method __construct (line 29) | public function __construct(array $positions, ...$args)

FILE: src/Geometry/LinearRing.php
  class LinearRing (line 24) | class LinearRing extends LineString
    method __construct (line 30) | public function __construct(array $positions, ...$args)

FILE: src/Geometry/MultiLineString.php
  class MultiLineString (line 20) | class MultiLineString extends Geometry
    method __construct (line 28) | public function __construct(array $lineStrings, ...$args)

FILE: src/Geometry/MultiPoint.php
  class MultiPoint (line 20) | class MultiPoint extends Geometry
    method __construct (line 28) | public function __construct(array $positions, ...$args)

FILE: src/Geometry/MultiPolygon.php
  class MultiPolygon (line 20) | class MultiPolygon extends Geometry
    method __construct (line 28) | public function __construct(array $polygons, ...$args)

FILE: src/Geometry/Point.php
  class Point (line 23) | class Point extends Geometry
    method __construct (line 31) | public function __construct(array $position, ...$args)

FILE: src/Geometry/Polygon.php
  class Polygon (line 18) | class Polygon extends Geometry
    method __construct (line 26) | public function __construct(array $linearRings, ...$args)

FILE: src/JsonUnserializable.php
  type JsonUnserializable (line 16) | interface JsonUnserializable
    method jsonUnserialize (line 25) | public static function jsonUnserialize($json);

FILE: tests/BaseGeoJsonTest.php
  class BaseGeoJsonTest (line 13) | abstract class BaseGeoJsonTest extends TestCase
    method createSubjectWithExtraArguments (line 20) | abstract public function createSubjectWithExtraArguments(...$extraArgs);
    method testConstructorShouldScanExtraArgumentsForCrsAndBoundingBox (line 22) | public function testConstructorShouldScanExtraArgumentsForCrsAndBoundi...
    method testSerializationWithCrsAndBoundingBox (line 53) | public function testSerializationWithCrsAndBoundingBox(): void
    method getMockBoundingBox (line 71) | protected function getMockBoundingBox()
    method getMockCoordinateReferenceSystem (line 76) | protected function getMockCoordinateReferenceSystem()
    method getMockFeature (line 81) | protected function getMockFeature()
    method getMockGeometry (line 86) | protected function getMockGeometry()

FILE: tests/BoundingBoxTest.php
  class BoundingBoxTest (line 17) | class BoundingBoxTest extends TestCase
    method testIsJsonSerializable (line 19) | public function testIsJsonSerializable(): void
    method testIsJsonUnserializable (line 24) | public function testIsJsonUnserializable(): void
    method testConstructorShouldRequireAtLeastFourValues (line 29) | public function testConstructorShouldRequireAtLeastFourValues(): void
    method testConstructorShouldRequireAnEvenNumberOfValues (line 37) | public function testConstructorShouldRequireAnEvenNumberOfValues(): void
    method testConstructorShouldRequireIntegerOrFloatValues (line 48) | public function testConstructorShouldRequireIntegerOrFloatValues(): void
    method provideBoundsWithInvalidTypes (line 55) | public function provideBoundsWithInvalidTypes()
    method testConstructorShouldRequireMinBeforeMaxValues (line 64) | public function testConstructorShouldRequireMinBeforeMaxValues(): void
    method testSerialization (line 72) | public function testSerialization(): void
    method testUnserialization (line 85) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 96) | public function provideJsonDecodeAssocOptions()
    method testUnserializationShouldRequireArray (line 107) | public function testUnserializationShouldRequireArray($value): void
    method provideInvalidUnserializationValues (line 115) | public function provideInvalidUnserializationValues()

FILE: tests/CoordinateReferenceSystem/CoordinateReferenceSystemTest.php
  class CoordinateReferenceSystemTest (line 13) | class CoordinateReferenceSystemTest extends TestCase
    method testIsJsonSerializable (line 15) | public function testIsJsonSerializable(): void
    method testIsJsonUnserializable (line 23) | public function testIsJsonUnserializable(): void
    method testUnserializationShouldRequireArrayOrObject (line 31) | public function testUnserializationShouldRequireArrayOrObject(): void
    method testUnserializationShouldRequireTypeField (line 39) | public function testUnserializationShouldRequireTypeField(): void
    method testUnserializationShouldRequirePropertiesField (line 47) | public function testUnserializationShouldRequirePropertiesField(): void
    method testUnserializationShouldRequireValidType (line 55) | public function testUnserializationShouldRequireValidType(): void

FILE: tests/CoordinateReferenceSystem/LinkedTest.php
  class LinkedTest (line 15) | class LinkedTest extends TestCase
    method testIsSubclassOfCoordinateReferenceSystem (line 17) | public function testIsSubclassOfCoordinateReferenceSystem(): void
    method testSerialization (line 22) | public function testSerialization(): void
    method testSerializationWithoutHrefType (line 39) | public function testSerializationWithoutHrefType(): void
    method testUnserialization (line 57) | public function testUnserialization($assoc): void
    method testUnserializationWithoutHrefType (line 86) | public function testUnserializationWithoutHrefType($assoc): void
    method provideJsonDecodeAssocOptions (line 107) | public function provideJsonDecodeAssocOptions()
    method testUnserializationShouldRequirePropertiesArrayOrObject (line 115) | public function testUnserializationShouldRequirePropertiesArrayOrObjec...
    method testUnserializationShouldRequireHrefProperty (line 123) | public function testUnserializationShouldRequireHrefProperty(): void

FILE: tests/CoordinateReferenceSystem/NamedTest.php
  class NamedTest (line 15) | class NamedTest extends TestCase
    method testIsSubclassOfCoordinateReferenceSystem (line 17) | public function testIsSubclassOfCoordinateReferenceSystem(): void
    method testSerialization (line 22) | public function testSerialization(): void
    method testUnserialization (line 42) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 63) | public function provideJsonDecodeAssocOptions()
    method testUnserializationShouldRequirePropertiesArrayOrObject (line 71) | public function testUnserializationShouldRequirePropertiesArrayOrObjec...
    method testUnserializationShouldRequireNameProperty (line 79) | public function testUnserializationShouldRequireNameProperty(): void

FILE: tests/Feature/FeatureCollectionTest.php
  class FeatureCollectionTest (line 20) | class FeatureCollectionTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 22) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeoJson (line 27) | public function testIsSubclassOfGeoJson(): void
    method testConstructorShouldRequireArrayOfFeatureObjects (line 33) | public function testConstructorShouldRequireArrayOfFeatureObjects(): void
    method testConstructorShouldReindexFeaturesArrayNumerically (line 41) | public function testConstructorShouldReindexFeaturesArrayNumerically()...
    method testIsTraversable (line 56) | public function testIsTraversable(): void
    method testIsCountable (line 69) | public function testIsCountable(): void
    method testSerialization (line 82) | public function testSerialization(): void
    method testUnserialization (line 108) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 148) | public function provideJsonDecodeAssocOptions()
    method testUnserializationShouldRequireFeaturesProperty (line 156) | public function testUnserializationShouldRequireFeaturesProperty(): void
    method testUnserializationShouldRequireFeaturesArray (line 164) | public function testUnserializationShouldRequireFeaturesArray(): void

FILE: tests/Feature/FeatureTest.php
  class FeatureTest (line 16) | class FeatureTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeoJson (line 23) | public function testIsSubclassOfGeoJson(): void
    method testSerialization (line 28) | public function testSerialization(): void
    method testSerializationWithNullConstructorArguments (line 53) | public function testSerializationWithNullConstructorArguments(): void
    method testSerializationShouldConvertEmptyPropertiesArrayToObject (line 66) | public function testSerializationShouldConvertEmptyPropertiesArrayToOb...
    method testUnserialization (line 83) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 114) | public function provideJsonDecodeAssocOptions()

FILE: tests/GeoJsonTest.php
  class GeoJsonTest (line 21) | class GeoJsonTest extends TestCase
    method testIsJsonSerializable (line 23) | public function testIsJsonSerializable(): void
    method testIsJsonUnserializable (line 28) | public function testIsJsonUnserializable(): void
    method testUnserializationWithBoundingBox (line 37) | public function testUnserializationWithBoundingBox($assoc): void
    method testUnserializationWithCrs (line 64) | public function testUnserializationWithCrs($assoc): void
    method testUnserializationWithInvalidArgument (line 95) | public function testUnserializationWithInvalidArgument(): void
    method testUnserializationWithUnknownType (line 103) | public function testUnserializationWithUnknownType(): void
    method testUnserializationWithMissingType (line 111) | public function testUnserializationWithMissingType(): void
    method testUnserializationWithMissingCoordinates (line 122) | public function testUnserializationWithMissingCoordinates(string $type...
    method testUnserializationWithInvalidCoordinates (line 137) | public function testUnserializationWithInvalidCoordinates($value): void
    method testFeatureUnserializationWithInvalidGeometry (line 150) | public function testFeatureUnserializationWithInvalidGeometry(): void
    method testFeatureUnserializationWithInvalidProperties (line 161) | public function testFeatureUnserializationWithInvalidProperties(): void
    method provideJsonDecodeAssocOptions (line 172) | public function provideJsonDecodeAssocOptions()
    method provideGeoJsonTypesWithCoordinates (line 180) | public function provideGeoJsonTypesWithCoordinates()
    method provideInvalidCoordinates (line 192) | public function provideInvalidCoordinates()

FILE: tests/Geometry/GeometryCollectionTest.php
  class GeometryCollectionTest (line 20) | class GeometryCollectionTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 22) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 27) | public function testIsSubclassOfGeometry(): void
    method testConstructorShouldRequireArrayOfGeometryObjects (line 32) | public function testConstructorShouldRequireArrayOfGeometryObjects(): ...
    method testConstructorShouldReindexGeometriesArrayNumerically (line 40) | public function testConstructorShouldReindexGeometriesArrayNumerically...
    method testIsTraversable (line 54) | public function testIsTraversable(): void
    method testIsCountable (line 67) | public function testIsCountable(): void
    method testSerialization (line 80) | public function testSerialization(): void
    method testUnserialization (line 106) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 135) | public function provideJsonDecodeAssocOptions()
    method testUnserializationShouldRequireGeometriesProperty (line 143) | public function testUnserializationShouldRequireGeometriesProperty(): ...
    method testUnserializationShouldRequireGeometriesArray (line 151) | public function testUnserializationShouldRequireGeometriesArray(): void

FILE: tests/Geometry/GeometryTest.php
  class GeometryTest (line 13) | class GeometryTest extends TestCase
    method testIsSubclassOfGeoJson (line 15) | public function testIsSubclassOfGeoJson(): void

FILE: tests/Geometry/LineStringTest.php
  class LineStringTest (line 16) | class LineStringTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfMultiPoint (line 26) | public function testIsSubclassOfMultiPoint(): void
    method testConstructorShouldRequireAtLeastTwoPositions (line 31) | public function testConstructorShouldRequireAtLeastTwoPositions(): void
    method testSerialization (line 39) | public function testSerialization(): void
    method testUnserialization (line 58) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 80) | public function provideJsonDecodeAssocOptions()

FILE: tests/Geometry/LinearRingTest.php
  class LinearRingTest (line 16) | class LinearRingTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfLineString (line 26) | public function testIsSubclassOfLineString(): void
    method testConstructorShouldRequireAtLeastFourPositions (line 31) | public function testConstructorShouldRequireAtLeastFourPositions(): void
    method testConstructorShouldRequireEquivalentFirstAndLastPositions (line 43) | public function testConstructorShouldRequireEquivalentFirstAndLastPosi...
    method testConstructorShouldAcceptEquivalentPointObjectsAndPositionArrays (line 59) | public function testConstructorShouldAcceptEquivalentPointObjectsAndPo...
    method testSerialization (line 69) | public function testSerialization(): void

FILE: tests/Geometry/MultiLineStringTest.php
  class MultiLineStringTest (line 16) | class MultiLineStringTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 23) | public function testIsSubclassOfGeometry(): void
    method testConstructionFromLineStringObjects (line 28) | public function testConstructionFromLineStringObjects(): void
    method testSerialization (line 43) | public function testSerialization(): void
    method testUnserialization (line 66) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 91) | public function provideJsonDecodeAssocOptions()

FILE: tests/Geometry/MultiPointTest.php
  class MultiPointTest (line 16) | class MultiPointTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 23) | public function testIsSubclassOfGeometry(): void
    method testConstructionFromPointObjects (line 28) | public function testConstructionFromPointObjects(): void
    method testSerialization (line 43) | public function testSerialization(): void
    method testUnserialization (line 62) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 84) | public function provideJsonDecodeAssocOptions()

FILE: tests/Geometry/MultiPolygonTest.php
  class MultiPolygonTest (line 16) | class MultiPolygonTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 23) | public function testIsSubclassOfGeometry(): void
    method testConstructionFromPolygonObjects (line 28) | public function testConstructionFromPolygonObjects(): void
    method testSerialization (line 43) | public function testSerialization(): void
    method testUnserialization (line 66) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 91) | public function provideJsonDecodeAssocOptions()

FILE: tests/Geometry/PointTest.php
  class PointTest (line 18) | class PointTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 20) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 25) | public function testIsSubclassOfGeometry(): void
    method testConstructorShouldRequireAtLeastTwoElementsInPosition (line 30) | public function testConstructorShouldRequireAtLeastTwoElementsInPositi...
    method testConstructorShouldRequireIntegerOrFloatElementsInPosition (line 41) | public function testConstructorShouldRequireIntegerOrFloatElementsInPo...
    method providePositionsWithInvalidTypes (line 49) | public function providePositionsWithInvalidTypes()
    method testConstructorShouldAllowMoreThanTwoElementsInAPosition (line 58) | public function testConstructorShouldAllowMoreThanTwoElementsInAPositi...
    method testSerialization (line 65) | public function testSerialization(): void
    method testUnserialization (line 84) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 101) | public function provideJsonDecodeAssocOptions()

FILE: tests/Geometry/PolygonTest.php
  class PolygonTest (line 16) | class PolygonTest extends BaseGeoJsonTest
    method createSubjectWithExtraArguments (line 18) | public function createSubjectWithExtraArguments(...$extraArgs)
    method testIsSubclassOfGeometry (line 29) | public function testIsSubclassOfGeometry(): void
    method testConstructionFromLinearRingObjects (line 34) | public function testConstructionFromLinearRingObjects(): void
    method testSerialization (line 49) | public function testSerialization(): void
    method testUnserialization (line 72) | public function testUnserialization($assoc): void
    method provideJsonDecodeAssocOptions (line 97) | public function provideJsonDecodeAssocOptions()
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (109K chars).
[
  {
    "path": ".gitattributes",
    "chars": 211,
    "preview": "*.php diff=php\n\n/tests export-ignore\n\n/.gitattributes export-ignore\n/.github export-ignore\n/.gitignore export-ignore\n/.s"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "chars": 1172,
    "preview": "name: \"Coding Standards\"\n\non:\n  pull_request:\n    branches:\n      - \"v*.*\"\n      - \"master\"\n  push:\n    branches:\n      "
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 1083,
    "preview": "name: \"Tests\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  phpunit:\n    name: PHPUnit Tests\n    runs-on: ubuntu-20.04\n\n    stra"
  },
  {
    "path": ".gitignore",
    "chars": 68,
    "preview": ".phpcs-cache\n.phpunit.result.cache\ncomposer.lock\nphpunit.xml\nvendor\n"
  },
  {
    "path": ".scrutinizer.yml",
    "chars": 787,
    "preview": "filter:\n    excluded_paths: [ \"vendor/*\", \"tests/*\" ]\n\ntools:\n    # https://scrutinizer-ci.com/docs/tools/external-code-"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "Copyright (c) 2013-present Jeremy Mikola\n\nPermission is hereby granted, free of charge, to any person obtaining a copy o"
  },
  {
    "path": "README.md",
    "chars": 873,
    "preview": "# GeoJson PHP Library\n\n[![Build Status](https://github.com/jmikola/geojson/actions/workflows/tests.yml/badge.svg)](https"
  },
  {
    "path": "USAGE.md",
    "chars": 3374,
    "preview": "# GeoJson PHP Library\n\nThis library implements the\n[GeoJSON format specification](https://geojson.org/).\n\nThe `GeoJson` "
  },
  {
    "path": "composer.json",
    "chars": 1043,
    "preview": "{\n    \"name\": \"jmikola/geojson\",\n    \"type\": \"library\",\n    \"description\": \"GeoJSON implementation for PHP\",\n    \"keywor"
  },
  {
    "path": "phpcs.xml.dist",
    "chars": 8469,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset>\n    <arg name=\"basepath\" value=\".\" />\n    <arg name=\"extensions\" value=\"php\" />\n    <arg"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 542,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNam"
  },
  {
    "path": "src/BoundingBox.php",
    "chars": 1978,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\Excepti"
  },
  {
    "path": "src/CoordinateReferenceSystem/CoordinateReferenceSystem.php",
    "chars": 2752,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse BadMethodCallExcepti"
  },
  {
    "path": "src/CoordinateReferenceSystem/Linked.php",
    "chars": 1706,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse GeoJson\\Exception\\Un"
  },
  {
    "path": "src/CoordinateReferenceSystem/Named.php",
    "chars": 1441,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse GeoJson\\Exception\\Un"
  },
  {
    "path": "src/Exception/Exception.php",
    "chars": 87,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\ninterface Exception\n{\n}\n"
  },
  {
    "path": "src/Exception/InvalidArgumentException.php",
    "chars": 153,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\nclass InvalidArgumentException extends \\InvalidArgumentEx"
  },
  {
    "path": "src/Exception/UnserializationException.php",
    "chars": 1855,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\nuse RuntimeException;\n\nuse function get_class;\nuse functi"
  },
  {
    "path": "src/Feature/Feature.php",
    "chars": 2267,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Feature;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSys"
  },
  {
    "path": "src/Feature/FeatureCollection.php",
    "chars": 1936,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Feature;\n\nuse ArrayIterator;\nuse Countable;\nuse GeoJson\\BoundingBox;\n"
  },
  {
    "path": "src/GeoJson.php",
    "chars": 6279,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse ArrayObject;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateRe"
  },
  {
    "path": "src/Geometry/Geometry.php",
    "chars": 669,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\GeoJson;\n\n/**\n * Base geometry object.\n *\n * @"
  },
  {
    "path": "src/Geometry/GeometryCollection.php",
    "chars": 1949,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse ArrayIterator;\nuse Countable;\nuse GeoJson\\BoundingBox;"
  },
  {
    "path": "src/Geometry/LineString.php",
    "chars": 892,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/LinearRing.php",
    "chars": 1443,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/MultiLineString.php",
    "chars": 1076,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/MultiPoint.php",
    "chars": 983,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/MultiPolygon.php",
    "chars": 1045,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/Point.php",
    "chars": 1139,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/Geometry/Polygon.php",
    "chars": 939,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSy"
  },
  {
    "path": "src/JsonUnserializable.php",
    "chars": 549,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse GeoJson\\Exception\\UnserializationException;\n\n/**\n * JsonUnseria"
  },
  {
    "path": "tests/BaseGeoJsonTest.php",
    "chars": 2830,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSyste"
  },
  {
    "path": "tests/BoundingBoxTest.php",
    "chars": 3613,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\Exception\\InvalidArgumen"
  },
  {
    "path": "tests/CoordinateReferenceSystem/CoordinateReferenceSystemTest.php",
    "chars": 2024,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSyst"
  },
  {
    "path": "tests/CoordinateReferenceSystem/LinkedTest.php",
    "chars": 3687,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSyst"
  },
  {
    "path": "tests/CoordinateReferenceSystem/NamedTest.php",
    "chars": 2547,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSyst"
  },
  {
    "path": "tests/Feature/FeatureCollectionTest.php",
    "chars": 5100,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Feature;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse G"
  },
  {
    "path": "tests/Feature/FeatureTest.php",
    "chars": 3243,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Feature;\n\nuse GeoJson\\Feature\\Feature;\nuse GeoJson\\GeoJson;\nuse"
  },
  {
    "path": "tests/GeoJsonTest.php",
    "chars": 6113,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSyste"
  },
  {
    "path": "tests/Geometry/GeometryCollectionTest.php",
    "chars": 4780,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse "
  },
  {
    "path": "tests/Geometry/GeometryTest.php",
    "chars": 363,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\n"
  },
  {
    "path": "tests/Geometry/LineStringTest.php",
    "chars": 2262,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse "
  },
  {
    "path": "tests/Geometry/LinearRingTest.php",
    "chars": 2218,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse "
  },
  {
    "path": "tests/Geometry/MultiLineStringTest.php",
    "chars": 2605,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\n"
  },
  {
    "path": "tests/Geometry/MultiPointTest.php",
    "chars": 2287,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\n"
  },
  {
    "path": "tests/Geometry/MultiPolygonTest.php",
    "chars": 2782,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\n"
  },
  {
    "path": "tests/Geometry/PointTest.php",
    "chars": 2850,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse "
  },
  {
    "path": "tests/Geometry/PolygonTest.php",
    "chars": 2810,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\n"
  }
]

About this extraction

This page contains the full source code of the jmikola/geojson GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (99.5 KB), approximately 25.3k tokens, and a symbol index with 213 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!