[
  {
    "path": ".gitattributes",
    "content": "*.php diff=php\n\n/tests export-ignore\n\n/.gitattributes export-ignore\n/.github export-ignore\n/.gitignore export-ignore\n/.scrutinizer.yml export-ignore\n/phpcs.xml.dist export-ignore\n/phpunit.xml.dist export-ignore\n"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "content": "name: \"Coding Standards\"\n\non:\n  pull_request:\n    branches:\n      - \"v*.*\"\n      - \"master\"\n  push:\n    branches:\n      - \"v*.*\"\n      - \"master\"\n\njobs:\n  coding-standards:\n    name: \"Coding Standards\"\n    runs-on: \"ubuntu-20.04\"\n\n    strategy:\n      matrix:\n        php-version:\n          - \"7.4\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@v2\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          tools: \"cs2pr\"\n\n      - name: \"Cache dependencies installed with Composer\"\n        uses: \"actions/cache@v2\"\n        with:\n          path: \"~/.composer/cache\"\n          key: \"php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}\"\n          restore-keys: \"php-${{ matrix.php-version }}-composer-locked-\"\n\n      - name: \"Install dependencies with Composer\"\n        run: \"composer install --no-interaction --no-progress --no-suggest\"\n\n      # The -q option is required until phpcs v4 is released\n      - name: \"Run PHP_CodeSniffer\"\n        run: \"vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr\""
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "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    strategy:\n      matrix:\n        php:\n          - \"7.4\"\n          - \"8.0\"\n          - \"8.1\"\n          - \"8.2\"\n        dependency-versions:\n          - \"highest\"\n        include:\n          - php: \"7.4\"\n            dependency-versions: \"lowest\"\n\n    steps:\n        - uses: actions/checkout@v2\n          with:\n            fetch-depth: 2\n\n        - name: Setup PHP\n          uses: shivammathur/setup-php@v2\n          with:\n            php-version: ${{ matrix.php }}\n\n        - name: Install Dependencies\n          uses: \"ramsey/composer-install@v2\"\n          with:\n            dependency-versions: \"${{ matrix.dependency-versions }}\"\n            composer-options: \"${{ matrix.composer-options }}\"\n\n        - name: Run PHPUnit\n          run: \"vendor/bin/phpunit --coverage-clover=coverage.clover\"\n\n        - name: Upload Coverage\n          if: github.repository_owner == 'jmikola'\n          run: \"vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover\"\n"
  },
  {
    "path": ".gitignore",
    "content": ".phpcs-cache\n.phpunit.result.cache\ncomposer.lock\nphpunit.xml\nvendor\n"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "filter:\n    excluded_paths: [ \"vendor/*\", \"tests/*\" ]\n\ntools:\n    # https://scrutinizer-ci.com/docs/tools/external-code-coverage/\n    external_code_coverage: true\n\n    # https://scrutinizer-ci.com/docs/tools/php/php-analyzer/\n    php_analyzer: true\n\n    # https://scrutinizer-ci.com/docs/tools/php/change-tracking-analyzer/\n    php_changetracking: true\n\n    # https://scrutinizer-ci.com/docs/tools/php/code-sniffer/\n    php_code_sniffer:\n        config:\n            standard: \"PSR1\"\n\n    # https://scrutinizer-ci.com/docs/tools/php/mess-detector/\n    php_mess_detector: true\n\n    # https://scrutinizer-ci.com/docs/tools/php/pdepend/\n    php_pdepend: true\n\n    # https://scrutinizer-ci.com/docs/tools/php/code-similarity-analyzer/\n    php_sim: true\n\n    sensiolabs_security_checker: true\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013-present Jeremy Mikola\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# GeoJson PHP Library\n\n[![Build Status](https://github.com/jmikola/geojson/actions/workflows/tests.yml/badge.svg)](https://github.com/jmikola/geojson/actions)\n[![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)\n[![Code Coverage](https://scrutinizer-ci.com/g/jmikola/geojson/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/jmikola/geojson/?branch=master)\n\nThis library implements the\n[GeoJSON format specification](https://geojson.org/).\n\n## Installation\n\nThe library is published as a\n[package](https://packagist.org/packages/jmikola/geojson) and is installable via\n[Composer](https://getcomposer.org/):\n\n```\n$ composer require \"jmikola/geojson=^1.0\"\n```\n\n## Additional Resources\n\n * [Usage Documentation](./USAGE.md)\n * [License](./LICENSE)\n"
  },
  {
    "path": "USAGE.md",
    "content": "# GeoJson PHP Library\n\nThis library implements the\n[GeoJSON format specification](https://geojson.org/).\n\nThe `GeoJson` namespace includes classes for each data structure defined in the\nGeoJSON specification. Core GeoJSON objects include geometries, features, and\ncollections. Geometries range from primitive points to more complex polygons.\nClasses also exist for bounding boxes and coordinate reference systems.\n\n## Installation\n\nThe library is published as a\n[package](https://packagist.org/packages/jmikola/geojson) and is installable via\n[Composer](https://getcomposer.org/):\n\n```\n$ composer require \"jmikola/geojson=^1.0\"\n```\n\n## Usage\n\nClasses in this library are immutable.\n\n### GeoJson Constructors\n\nGeometry objects are constructed using a single coordinates array. This may be\na tuple in the case of a `Point`, an array of tuples for a `LineString`, etc.\nConstructors for each class will validate the coordinates array and throw an\n`InvalidArgumentException` on error.\n\nMore primitive geometry objects may also be used for constructing complex\nobjects. For instance, a `LineString` may be constructed from an array of\n`Point` objects.\n\nFeature objects are constructed from a geometry object, associative properties\narray, and an identifier, all of which are optional.\n\nFeature and geometry collection objects are constructed from an array of their\nrespective types.\n\n#### Specifying a Bounding Box or CRS\n\nAll GeoJson constructors support `BoundingBox` and `CoordinateReferenceSystem`\nobjects as optional arguments beyond those explicitly listed in their prototype.\nThese objects may appear in any order *after* the explicit arguments.\n\n```php\n$crs = new \\GeoJson\\CoordinateReferenceSystem\\Named('urn:ogc:def:crs:OGC:1.3:CRS84');\n$box = new \\GeoJson\\BoundingBox([-180, -90, 180, 90]);\n$point = new \\GeoJson\\Geometry\\Point([0, 0], $crs, $box);\n```\n\nNote that the `Feature` class is unique in that it has three arguments, all with\ndefault values. In order to construct a `Feature` with a bounding box or CRS,\nall three arguments must be explicitly listed (e.g. with `null` placeholders).\n\n```php\n$box = new \\GeoJson\\BoundingBox([-180, -90, 180, 90]);\n$feature = new \\GeoJson\\Feature\\Feature(null, null, null, $box);\n```\n\n### JSON Serialization\n\nEach class in the library implements the\n[JsonSerializable](https://php.net/manual/en/class.jsonserializable.php)\ninterface, which allows objects to be passed directly to `json_encode()`.\n\n```php\n$point = new \\GeoJson\\Geometry\\Point([1, 1]);\n$json = json_encode($point);\n```\n\nPrinting the `$json` variable would yield (sans whitespace):\n\n```json\n{\n    \"type\": \"Point\",\n    \"coordinates\": [1, 1]\n}\n```\n\n### JSON Unserialization\n\nThe core `GeoJson` class implements an internal `JsonUnserializable` interface,\nwhich defines a static factory method, `jsonUnserialize()`, that can be used to\ncreate objects from the return value of `json_decode()`.\n\n```php\n$json = '{ \"type\": \"Point\", \"coordinates\": [1, 1] }';\n$json = json_decode($json);\n$point = \\GeoJson\\GeoJson::jsonUnserialize($json);\n```\n\nIf errors are encountered during unserialization, an `UnserializationException`\nwill be thrown by `jsonUnserialize()`. Possible errors include:\n\n * Missing properties (e.g. `type` is not present)\n * Unexpected values (e.g. `coordinates` property is not an array)\n * Unsupported `type` string when parsing a GeoJson object or CRS\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"jmikola/geojson\",\n    \"type\": \"library\",\n    \"description\": \"GeoJSON implementation for PHP\",\n    \"keywords\": [\"geo\", \"geospatial\", \"geojson\"],\n    \"homepage\": \"https://github.com/jmikola/geojson\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        { \"name\": \"Jeremy Mikola\", \"email\": \"jmikola@gmail.com\" }\n    ],\n    \"require\": {\n        \"php\": \"^7.4 || ^8.0\",\n        \"ext-json\": \"*\",\n        \"symfony/polyfill-php80\": \"^1.25\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^9.5\",\n        \"scrutinizer/ocular\": \"^1.8.1\",\n        \"squizlabs/php_codesniffer\": \"^3.6\",\n        \"slevomat/coding-standard\": \"^8.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"GeoJson\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"GeoJson\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-master\": \"1.1-dev\"\n        }\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"dealerdirect/phpcodesniffer-composer-installer\": true\n        }\n    }\n}\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset>\n    <arg name=\"basepath\" value=\".\" />\n    <arg name=\"extensions\" value=\"php\" />\n    <arg name=\"cache\" value=\".phpcs-cache\" />\n    <arg name=\"colors\" />\n\n    <!-- Ignore warnings (n), show progress of the run (p), and show sniff names (s) -->\n    <arg value=\"nps\"/>\n\n    <file>src</file>\n    <file>tests</file>\n\n    <rule ref=\"PSR12\">\n\n        <!-- ********************************************** -->\n        <!-- Exclude sniffs that require newer PHP versions -->\n        <!-- ********************************************** -->\n\n        <!-- Requires PHP 8.0 -->\n        <exclude name=\"SlevomatCodingStandard.Classes.ModernClassNameReference.ClassNameReferencedViaFunctionCall\" />\n\n\n        <!-- *********************************** -->\n        <!-- Exclude sniffs that cause BC breaks -->\n        <!-- *********************************** -->\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming\" />\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousExceptionNaming\" />\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming\" />\n        <exclude name=\"SlevomatCodingStandard.Classes.SuperfluousTraitNaming\" />\n\n\n        <!-- **************************************** -->\n        <!-- Exclude sniffs that force unwanted style -->\n        <!-- **************************************** -->\n        <exclude name=\"Generic.Formatting.MultipleStatementAlignment\" />\n        <exclude name=\"Squiz.Commenting.FunctionComment.ThrowsNoFullStop\" />\n\n        <!-- Keep long typehints (for now) -->\n        <exclude name=\"SlevomatCodingStandard.PHP.TypeCast.InvalidCastUsed\" />\n        <exclude name=\"SlevomatCodingStandard.TypeHints.LongTypeHints\" />\n\n\n        <!-- ************************************************ -->\n        <!-- Exclude sniffs that may cause functional changes -->\n        <!-- ************************************************ -->\n        <exclude name=\"Generic.PHP.ForbiddenFunctions.FoundWithAlternative\" />\n        <exclude name=\"SlevomatCodingStandard.ControlStructures.DisallowYodaComparison\" />\n        <exclude name=\"SlevomatCodingStandard.ControlStructures.EarlyExit\" />\n        <exclude name=\"SlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturn\" />\n        <exclude name=\"SlevomatCodingStandard.Functions.StaticClosure\" />\n        <exclude name=\"SlevomatCodingStandard.Functions.UnusedInheritedVariablePassedToClosure\" />\n        <exclude name=\"SlevomatCodingStandard.Operators.DisallowEqualOperators\" />\n\n\n        <!-- ********************************************************* -->\n        <!-- Exclude sniffs that cause a huge diff - enable separately -->\n        <!-- ********************************************************* -->\n        <exclude name=\"SlevomatCodingStandard.Commenting.DocCommentSpacing.IncorrectAnnotationsGroup\" />\n        <exclude name=\"Squiz.Strings.DoubleQuoteUsage\" />\n\n\n        <!-- ********************* -->\n        <!-- Exclude broken sniffs -->\n        <!-- ********************* -->\n\n        <!-- Sniff currently broken when casting arrays, see https://github.com/squizlabs/PHP_CodeSniffer/issues/2937#issuecomment-615498860 -->\n        <exclude name=\"Squiz.Arrays.ArrayDeclaration.ValueNoNewline\" />\n\n        <!-- Disable forbidden annotation sniff as excluding @api from the list doesn't work -->\n        <exclude name=\"SlevomatCodingStandard.Commenting.ForbiddenAnnotations.AnnotationForbidden\" />\n    </rule>\n\n\n    <!-- ***************************************************** -->\n    <!-- Forbid fully qualified names even for colliding names -->\n    <!-- ***************************************************** -->\n    <rule ref=\"SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly\">\n        <properties>\n            <property name=\"allowFallbackGlobalConstants\" value=\"false\"/>\n            <property name=\"allowFallbackGlobalFunctions\" value=\"false\"/>\n            <property name=\"allowFullyQualifiedGlobalClasses\" value=\"false\"/>\n            <property name=\"allowFullyQualifiedGlobalConstants\" value=\"false\"/>\n            <property name=\"allowFullyQualifiedGlobalFunctions\" value=\"false\"/>\n            <property phpcs-only=\"true\" name=\"allowFullyQualifiedNameForCollidingClasses\" value=\"true\"/>\n            <property phpcs-only=\"true\" name=\"allowFullyQualifiedNameForCollidingConstants\" value=\"false\"/>\n            <property phpcs-only=\"true\" name=\"allowFullyQualifiedNameForCollidingFunctions\" value=\"false\"/>\n            <property name=\"searchAnnotations\" value=\"true\"/>\n        </properties>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses\"/>\n\n    <!-- **************************************************************************** -->\n    <!-- Exclude BC breaking type hints for parameters, properties, and return values -->\n    <!-- **************************************************************************** -->\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint\">\n        <properties>\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableMixedTypeHint\" value=\"false\" />\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableUnionTypeHint\" value=\"false\" />\n        </properties>\n\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification\" />\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint.UselessAnnotation\" />\n    </rule>\n\n    <rule ref=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint\">\n        <properties>\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableMixedTypeHint\" value=\"false\" />\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableUnionTypeHint\" value=\"false\" />\n        </properties>\n\n        <exclude name=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification\" />\n        <exclude name=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.UselessAnnotation\" />\n    </rule>\n\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint\">\n        <properties>\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableStaticTypeHint\" value=\"false\" />\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableMixedTypeHint\" value=\"false\" />\n            <!-- Requires PHP 8.0 -->\n            <property name=\"enableUnionTypeHint\" value=\"false\" />\n        </properties>\n\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingTraversableTypeHintSpecification\" />\n        <exclude name=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint.UselessAnnotation\" />\n    </rule>\n\n\n    <!-- ************************************************************************** -->\n    <!-- Require type hints for all parameters, properties, and return types in src -->\n    <!-- ************************************************************************** -->\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint\">\n        <exclude-pattern>tests</exclude-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint\">\n        <exclude-pattern>tests</exclude-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint\">\n        <exclude-pattern>tests</exclude-pattern>\n    </rule>\n\n\n    <!-- *********************************************************************************** -->\n    <!-- Require native type hints for all parameters, properties, and return types in tests -->\n    <!-- *********************************************************************************** -->\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint\">\n        <exclude-pattern>src</exclude-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint\">\n        <exclude-pattern>src</exclude-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint\">\n        <exclude-pattern>src</exclude-pattern>\n    </rule>\n    <rule ref=\"SlevomatCodingStandard.TypeHints.DeclareStrictTypes\">\n        <properties>\n            <property name=\"newlinesCountBetweenOpenTagAndDeclare\" type=\"int\" value=\"2\" />\n            <property name=\"spacesCountAroundEqualsSign\" type=\"int\" value=\"0\" />\n        </properties>\n    </rule>\n\n    <rule ref=\"Generic.Arrays.DisallowLongArraySyntax\" />\n</ruleset>"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n    colors=\"true\"\n>\n    <testsuites>\n        <testsuite name=\"GeoJson Test Suite\">\n            <directory>./tests/</directory>\n        </testsuite>\n    </testsuites>\n\n    <coverage ignoreDeprecatedCodeUnits=\"true\" processUncoveredFiles=\"true\">\n        <include>\n            <directory suffix=\".php\">./src/</directory>\n        </include>\n    </coverage>\n</phpunit>\n"
  },
  {
    "path": "src/BoundingBox.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\Exception\\UnserializationException;\nuse JsonSerializable;\n\nuse function count;\nuse function is_array;\nuse function is_float;\nuse function is_int;\n\n/**\n * BoundingBox object.\n *\n * @see http://www.geojson.org/geojson-spec.html#bounding-boxes\n * @since 1.0\n */\nclass BoundingBox implements JsonSerializable, JsonUnserializable\n{\n    /**\n     * @var array<float|int>\n     */\n    protected array $bounds;\n\n    /**\n     * @param array<float|int> $bounds\n     */\n    public function __construct(array $bounds)\n    {\n        $count = count($bounds);\n\n        if ($count < 4) {\n            throw new InvalidArgumentException('BoundingBox requires at least four values');\n        }\n\n        if ($count % 2) {\n            throw new InvalidArgumentException('BoundingBox requires an even number of values');\n        }\n\n        foreach ($bounds as $value) {\n            if (! is_int($value) && ! is_float($value)) {\n                throw new InvalidArgumentException('BoundingBox values must be integers or floats');\n            }\n        }\n\n        for ($i = 0; $i < ($count / 2); $i++) {\n            if ($bounds[$i] > $bounds[$i + ($count / 2)]) {\n                throw new InvalidArgumentException('BoundingBox min values must precede max values');\n            }\n        }\n\n        $this->bounds = $bounds;\n    }\n\n    /**\n     * Return the bounds for this BoundingBox object.\n     *\n     * @return array<float|int>\n     */\n    public function getBounds(): array\n    {\n        return $this->bounds;\n    }\n\n    public function jsonSerialize(): array\n    {\n        return $this->bounds;\n    }\n\n    /**\n     * @param array $json\n     */\n    final public static function jsonUnserialize($json): self\n    {\n        if (! is_array($json)) {\n            throw UnserializationException::invalidValue('BoundingBox', $json, 'array');\n        }\n\n        return new self($json);\n    }\n}\n"
  },
  {
    "path": "src/CoordinateReferenceSystem/CoordinateReferenceSystem.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse BadMethodCallException;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\JsonUnserializable;\nuse JsonSerializable;\n\nuse function is_array;\nuse function is_object;\nuse function sprintf;\n\n/**\n * Coordinate reference system object.\n *\n * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,\n *                 the 'crs' member of [GJ2008] is no longer used.\n *\n * @see https://www.rfc-editor.org/rfc/rfc7946#appendix-B.1\n * @see http://www.geojson.org/geojson-spec.html#coordinate-reference-system-objects\n * @since 1.0\n */\nabstract class CoordinateReferenceSystem implements JsonSerializable, JsonUnserializable\n{\n    protected array $properties;\n\n    protected string $type;\n\n    /**\n     * Return the properties for this CRS object.\n     */\n    public function getProperties(): array\n    {\n        return $this->properties;\n    }\n\n    /**\n     * Return the type for this CRS object.\n     */\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    public function jsonSerialize(): array\n    {\n        return [\n            'type' => $this->type,\n            'properties' => $this->properties,\n        ];\n    }\n\n    /**\n     * @param array|object $json\n     */\n    final public static function jsonUnserialize($json): self\n    {\n        if (! is_array($json) && ! is_object($json)) {\n            throw UnserializationException::invalidValue('CRS', $json, 'array or object');\n        }\n\n        $json = new ArrayObject($json);\n\n        if (! $json->offsetExists('type')) {\n            throw UnserializationException::missingProperty('CRS', 'type', 'string');\n        }\n\n        if (! $json->offsetExists('properties')) {\n            throw UnserializationException::missingProperty('CRS', 'properties', 'array or object');\n        }\n\n        $type = (string) $json['type'];\n        $properties = $json['properties'];\n\n        switch ($type) {\n            case 'link':\n                return Linked::jsonUnserializeFromProperties($properties);\n\n            case 'name':\n                return Named::jsonUnserializeFromProperties($properties);\n        }\n\n        throw UnserializationException::unsupportedType('CRS', $type);\n    }\n\n    /**\n     * Factory method for creating a CRS object from properties.\n     *\n     * This method must be overridden in a child class.\n     *\n     * @param array|object $properties\n     *\n     * @throws BadMethodCallException\n     */\n    protected static function jsonUnserializeFromProperties($properties): CoordinateReferenceSystem\n    {\n        throw new BadMethodCallException(sprintf('%s must be overridden in a child class', __METHOD__));\n    }\n}\n"
  },
  {
    "path": "src/CoordinateReferenceSystem/Linked.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse GeoJson\\Exception\\UnserializationException;\n\nuse function is_array;\nuse function is_object;\n\n/**\n * Linked coordinate reference system object.\n *\n * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,\n *                 the 'crs' member of [GJ2008] is no longer used.\n *\n * @see https://www.rfc-editor.org/rfc/rfc7946#appendix-B.1\n * @see http://www.geojson.org/geojson-spec.html#linked-crs\n * @since 1.0\n */\nclass Linked extends CoordinateReferenceSystem\n{\n    protected string $type = 'link';\n\n    public function __construct(string $href, ?string $type = null)\n    {\n        $this->properties = ['href' => $href];\n\n        if ($type !== null) {\n            $this->properties['type'] = $type;\n        }\n    }\n\n    /**\n     * Factory method for creating a Linked CRS object from properties.\n     *\n     * @param array|object $properties\n     *\n     * @throws UnserializationException\n     */\n    protected static function jsonUnserializeFromProperties($properties): self\n    {\n        if (! is_array($properties) && ! is_object($properties)) {\n            throw UnserializationException::invalidProperty('Linked CRS', 'properties', $properties, 'array or object');\n        }\n\n        $properties = new ArrayObject($properties);\n\n        if (! $properties->offsetExists('href')) {\n            throw UnserializationException::missingProperty('Linked CRS', 'properties.href', 'string');\n        }\n\n        $href = (string) $properties['href'];\n        $type = isset($properties['type']) ? (string) $properties['type'] : null;\n\n        return new self($href, $type);\n    }\n}\n"
  },
  {
    "path": "src/CoordinateReferenceSystem/Named.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\CoordinateReferenceSystem;\n\nuse ArrayObject;\nuse GeoJson\\Exception\\UnserializationException;\n\nuse function is_array;\nuse function is_object;\n\n/**\n * Named coordinate reference system object.\n *\n * @deprecated 1.1 Specification of coordinate reference systems has been removed, i.e.,\n *                 the 'crs' member of [GJ2008] is no longer used.\n *\n * @see http://www.geojson.org/geojson-spec.html#named-crs\n * @since 1.0\n */\nclass Named extends CoordinateReferenceSystem\n{\n    protected string $type = 'name';\n\n    public function __construct(string $name)\n    {\n        $this->properties = ['name' => $name];\n    }\n\n    /**\n     * Factory method for creating a Named CRS object from properties.\n     *\n     * @param array|object $properties\n     *\n     * @throws UnserializationException\n     */\n    protected static function jsonUnserializeFromProperties($properties): self\n    {\n        if (! is_array($properties) && ! is_object($properties)) {\n            throw UnserializationException::invalidProperty('Named CRS', 'properties', $properties, 'array or object');\n        }\n\n        $properties = new ArrayObject($properties);\n\n        if (! $properties->offsetExists('name')) {\n            throw UnserializationException::missingProperty('Named CRS', 'properties.name', 'string');\n        }\n\n        $name = (string) $properties['name'];\n\n        return new self($name);\n    }\n}\n"
  },
  {
    "path": "src/Exception/Exception.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\ninterface Exception\n{\n}\n"
  },
  {
    "path": "src/Exception/InvalidArgumentException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\nclass InvalidArgumentException extends \\InvalidArgumentException implements Exception\n{\n}\n"
  },
  {
    "path": "src/Exception/UnserializationException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Exception;\n\nuse RuntimeException;\n\nuse function get_class;\nuse function get_debug_type;\nuse function gettype;\nuse function is_object;\nuse function sprintf;\n\nclass UnserializationException extends RuntimeException implements Exception\n{\n    /**\n     * Creates an UnserializationException for a value with an invalid type.\n     *\n     * @param mixed $value\n     */\n    public static function invalidValue(string $context, $value, string $expectedType): self\n    {\n        return new self(sprintf(\n            '%s expected value of type %s, %s given',\n            $context,\n            $expectedType,\n            get_debug_type($value)\n        ));\n    }\n\n    /**\n     * Creates an UnserializationException for a property with an invalid type.\n     *\n     * @param mixed $value\n     */\n    public static function invalidProperty(string $context, string $property, $value, string $expectedType): self\n    {\n        return new self(sprintf(\n            '%s expected \"%s\" property of type %s, %s given',\n            $context,\n            $property,\n            $expectedType,\n            is_object($value) ? get_class($value) : gettype($value)\n        ));\n    }\n\n    /**\n     * Creates an UnserializationException for a missing property.\n     */\n    public static function missingProperty(string $context, string $property, string $expectedType): self\n    {\n        return new self(sprintf(\n            '%s expected \"%s\" property of type %s, none given',\n            $context,\n            $property,\n            $expectedType\n        ));\n    }\n\n    /**\n     * Creates an UnserializationException for an unsupported \"type\" property.\n     */\n    public static function unsupportedType(string $context, string $value): self\n    {\n        return new self(sprintf('Invalid %s type \"%s\"', $context, $value));\n    }\n}\n"
  },
  {
    "path": "src/Feature/Feature.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Feature;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse stdClass;\n\n/**\n * Feature object.\n *\n * @see http://www.geojson.org/geojson-spec.html#feature-objects\n * @since 1.0\n */\nclass Feature extends GeoJson\n{\n    protected string $type = self::TYPE_FEATURE;\n\n    protected ?Geometry $geometry;\n\n    /**\n     * Properties are a JSON object, which corresponds to an associative array, or null.\n     *\n     * @see https://www.rfc-editor.org/rfc/rfc7946#section-3.2\n     */\n    protected ?array $properties;\n\n    /**\n     * The identifier is either a JSON string or a number.\n     *\n     * @see https://www.rfc-editor.org/rfc/rfc7946#section-3.2\n     *\n     * @var int|string|null\n     */\n    protected $id;\n\n    /**\n     * @param int|string|null $id\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(?Geometry $geometry = null, ?array $properties = null, $id = null, ...$args)\n    {\n        $this->geometry = $geometry;\n        $this->properties = $properties;\n        $this->id = $id;\n\n        $this->setOptionalConstructorArgs($args);\n    }\n\n    /**\n     * Return the Geometry object for this Feature object.\n     */\n    public function getGeometry(): ?Geometry\n    {\n        return $this->geometry;\n    }\n\n    /**\n     * Return the identifier for this Feature object.\n     *\n     * @return int|string|null\n     */\n    public function getId()\n    {\n        return $this->id;\n    }\n\n    /**\n     * Return the properties for this Feature object.\n     */\n    public function getProperties(): ?array\n    {\n        return $this->properties;\n    }\n\n    public function jsonSerialize(): array\n    {\n        $json = parent::jsonSerialize();\n\n        $json['geometry'] = isset($this->geometry) ? $this->geometry->jsonSerialize() : null;\n        $json['properties'] = $this->properties ?? null;\n\n        // Ensure empty associative arrays are encoded as JSON objects\n        if ($json['properties'] === []) {\n            $json['properties'] = new stdClass();\n        }\n\n        if (isset($this->id)) {\n            $json['id'] = $this->id;\n        }\n\n        return $json;\n    }\n}\n"
  },
  {
    "path": "src/Feature/FeatureCollection.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Feature;\n\nuse ArrayIterator;\nuse Countable;\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\GeoJson;\nuse IteratorAggregate;\nuse Traversable;\n\nuse function array_map;\nuse function array_merge;\nuse function array_values;\nuse function count;\n\n/**\n * Collection of Feature objects.\n *\n * @see http://www.geojson.org/geojson-spec.html#feature-collection-objects\n * @since 1.0\n */\nclass FeatureCollection extends GeoJson implements Countable, IteratorAggregate\n{\n    protected string $type = self::TYPE_FEATURE_COLLECTION;\n\n    /**\n     * @var array<Feature>\n     */\n    protected array $features;\n\n    /**\n     * @param array<Feature> $features\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $features, ...$args)\n    {\n        foreach ($features as $feature) {\n            if (! $feature instanceof Feature) {\n                throw new InvalidArgumentException('FeatureCollection may only contain Feature objects');\n            }\n        }\n\n        $this->features = array_values($features);\n\n        $this->setOptionalConstructorArgs($args);\n    }\n\n    public function count(): int\n    {\n        return count($this->features);\n    }\n\n    /**\n     * Return the Feature objects in this collection.\n     *\n     * @return array<Feature>\n     */\n    public function getFeatures(): array\n    {\n        return $this->features;\n    }\n\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator($this->features);\n    }\n\n    public function jsonSerialize(): array\n    {\n        return array_merge(\n            parent::jsonSerialize(),\n            ['features' => array_map(\n                static fn(Feature $feature) => $feature->jsonSerialize(),\n                $this->features\n            )]\n        );\n    }\n}\n"
  },
  {
    "path": "src/GeoJson.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse ArrayObject;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\UnserializationException;\nuse JsonSerializable;\n\nuse function array_map;\nuse function is_array;\nuse function is_object;\nuse function sprintf;\nuse function strncmp;\n\n/**\n * Base GeoJson object.\n *\n * @see http://www.geojson.org/geojson-spec.html#geojson-objects\n * @since 1.0\n */\nabstract class GeoJson implements JsonSerializable, JsonUnserializable\n{\n    public const TYPE_LINE_STRING = 'LineString';\n    public const TYPE_MULTI_LINE_STRING = 'MultiLineString';\n    public const TYPE_MULTI_POINT = 'MultiPoint';\n    public const TYPE_MULTI_POLYGON = 'MultiPolygon';\n    public const TYPE_POINT = 'Point';\n    public const TYPE_POLYGON = 'Polygon';\n    public const TYPE_FEATURE = 'Feature';\n    public const TYPE_FEATURE_COLLECTION = 'FeatureCollection';\n    public const TYPE_GEOMETRY_COLLECTION = 'GeometryCollection';\n\n    protected ?BoundingBox $boundingBox = null;\n\n    protected ?CoordinateReferenceSystem $crs = null;\n\n    protected string $type;\n\n    /**\n     * Return the BoundingBox for this GeoJson object.\n     */\n    public function getBoundingBox(): ?BoundingBox\n    {\n        return $this->boundingBox;\n    }\n\n    /**\n     * Return the CoordinateReferenceSystem for this GeoJson object.\n     */\n    public function getCrs(): ?CoordinateReferenceSystem\n    {\n        return $this->crs;\n    }\n\n    /**\n     * Return the type for this GeoJson object.\n     */\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    public function jsonSerialize(): array\n    {\n        $json = ['type' => $this->type];\n\n        if (isset($this->crs)) {\n            $json['crs'] = $this->crs->jsonSerialize();\n        }\n\n        if (isset($this->boundingBox)) {\n            $json['bbox'] = $this->boundingBox->jsonSerialize();\n        }\n\n        return $json;\n    }\n\n    /**\n     * @param array|object $json\n     */\n    final public static function jsonUnserialize($json): self\n    {\n        if (! is_array($json) && ! is_object($json)) {\n            throw UnserializationException::invalidValue('GeoJson', $json, 'array or object');\n        }\n\n        $json = new ArrayObject($json);\n\n        if (! $json->offsetExists('type')) {\n            throw UnserializationException::missingProperty('GeoJson', 'type', 'string');\n        }\n\n        $type = (string) $json['type'];\n        $args = [];\n\n        switch ($type) {\n            case self::TYPE_LINE_STRING:\n            case self::TYPE_MULTI_LINE_STRING:\n            case self::TYPE_MULTI_POINT:\n            case self::TYPE_MULTI_POLYGON:\n            case self::TYPE_POINT:\n            case self::TYPE_POLYGON:\n                if (! $json->offsetExists('coordinates')) {\n                    throw UnserializationException::missingProperty($type, 'coordinates', 'array');\n                }\n\n                if (! is_array($json['coordinates'])) {\n                    throw UnserializationException::invalidProperty($type, 'coordinates', $json['coordinates'], 'array');\n                }\n\n                $args[] = $json['coordinates'];\n                break;\n\n            case self::TYPE_FEATURE:\n                $geometry = $json['geometry'] ?? null;\n                $properties = $json['properties'] ?? null;\n                $id = $json['id'] ?? null;\n\n                if ($geometry !== null && ! is_array($geometry) && ! is_object($geometry)) {\n                    throw UnserializationException::invalidProperty($type, 'geometry', $geometry, 'array or object');\n                }\n\n                if ($properties !== null && ! is_array($properties) && ! is_object($properties)) {\n                    throw UnserializationException::invalidProperty($type, 'properties', $properties, 'array or object');\n                }\n\n                // TODO: Validate non-null $id as int or string in 2.0\n\n                $args[] = $geometry !== null ? self::jsonUnserialize($geometry) : null;\n                $args[] = $properties !== null ? (array) $properties : null;\n                $args[] = $id;\n                break;\n\n            case self::TYPE_FEATURE_COLLECTION:\n                if (! $json->offsetExists('features')) {\n                    throw UnserializationException::missingProperty($type, 'features', 'array');\n                }\n\n                if (! is_array($json['features'])) {\n                    throw UnserializationException::invalidProperty($type, 'features', $json['features'], 'array');\n                }\n\n                $args[] = array_map([self::class, 'jsonUnserialize'], $json['features']);\n                break;\n\n            case self::TYPE_GEOMETRY_COLLECTION:\n                if (! $json->offsetExists('geometries')) {\n                    throw UnserializationException::missingProperty($type, 'geometries', 'array');\n                }\n\n                if (! is_array($json['geometries'])) {\n                    throw UnserializationException::invalidProperty($type, 'geometries', $json['geometries'], 'array');\n                }\n\n                $args[] = array_map([self::class, 'jsonUnserialize'], $json['geometries']);\n                break;\n\n            default:\n                throw UnserializationException::unsupportedType('GeoJson', $type);\n        }\n\n        if (isset($json['bbox'])) {\n            $args[] = BoundingBox::jsonUnserialize($json['bbox']);\n        }\n\n        if (isset($json['crs'])) {\n            $args[] = CoordinateReferenceSystem::jsonUnserialize($json['crs']);\n        }\n\n        $class = sprintf('GeoJson\\%s\\%s', (strncmp('Feature', $type, 7) === 0 ? 'Feature' : 'Geometry'), $type);\n\n        return new $class(... $args);\n    }\n\n    /**\n     * Set optional CRS and BoundingBox arguments passed to a constructor.\n     *\n     * @todo Decide if multiple CRS or BoundingBox instances should override a\n     *       previous value or be ignored\n     */\n    protected function setOptionalConstructorArgs(array $args): void\n    {\n        foreach ($args as $arg) {\n            if ($arg instanceof CoordinateReferenceSystem) {\n                $this->crs = $arg;\n            }\n\n            if ($arg instanceof BoundingBox) {\n                $this->boundingBox = $arg;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Geometry/Geometry.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\GeoJson;\n\n/**\n * Base geometry object.\n *\n * @see http://www.geojson.org/geojson-spec.html#geometry-objects\n * @since 1.0\n */\nabstract class Geometry extends GeoJson\n{\n    protected array $coordinates;\n\n    /**\n     * Return the coordinates for this Geometry object.\n     */\n    public function getCoordinates(): array\n    {\n        return $this->coordinates;\n    }\n\n    public function jsonSerialize(): array\n    {\n        $json = parent::jsonSerialize();\n\n        if (isset($this->coordinates)) {\n            $json['coordinates'] = $this->coordinates;\n        }\n\n        return $json;\n    }\n}\n"
  },
  {
    "path": "src/Geometry/GeometryCollection.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse ArrayIterator;\nuse Countable;\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse IteratorAggregate;\nuse Traversable;\n\nuse function array_map;\nuse function array_merge;\nuse function array_values;\nuse function count;\n\n/**\n * Collection of Geometry objects.\n *\n * @see http://www.geojson.org/geojson-spec.html#geometry-collection\n * @since 1.0\n */\nclass GeometryCollection extends Geometry implements Countable, IteratorAggregate\n{\n    protected string $type = self::TYPE_GEOMETRY_COLLECTION;\n\n    /**\n     * @var array<Geometry>\n     */\n    protected array $geometries;\n\n    /**\n     * @param array<Geometry> $geometries\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $geometries, ...$args)\n    {\n        foreach ($geometries as $geometry) {\n            if (! $geometry instanceof Geometry) {\n                throw new InvalidArgumentException('GeometryCollection may only contain Geometry objects');\n            }\n        }\n\n        $this->geometries = array_values($geometries);\n\n        $this->setOptionalConstructorArgs($args);\n    }\n\n    public function count(): int\n    {\n        return count($this->geometries);\n    }\n\n    /**\n     * Return the Geometry objects in this collection.\n     *\n     * @return array<Geometry>\n     */\n    public function getGeometries(): array\n    {\n        return $this->geometries;\n    }\n\n    public function getIterator(): Traversable\n    {\n        return new ArrayIterator($this->geometries);\n    }\n\n    public function jsonSerialize(): array\n    {\n        return array_merge(\n            parent::jsonSerialize(),\n            ['geometries' => array_map(\n                static fn(Geometry $geometry) => $geometry->jsonSerialize(),\n                $this->geometries\n            )]\n        );\n    }\n}\n"
  },
  {
    "path": "src/Geometry/LineString.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\InvalidArgumentException;\n\nuse function count;\n\n/**\n * LineString geometry object.\n *\n * Coordinates consist of an array of at least two positions.\n *\n * @see http://www.geojson.org/geojson-spec.html#linestring\n * @since 1.0\n */\nclass LineString extends MultiPoint\n{\n    protected string $type = self::TYPE_LINE_STRING;\n\n    /**\n     * @param array<Point|array<float|int>> $positions\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $positions, ...$args)\n    {\n        if (count($positions) < 2) {\n            throw new InvalidArgumentException('LineString requires at least two positions');\n        }\n\n        parent::__construct($positions, ... $args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/LinearRing.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\InvalidArgumentException;\n\nuse function count;\nuse function end;\nuse function reset;\n\n/**\n * LinearRing is a special kind of LineString geometry object.\n *\n * Coordinates consist of an array of at least four positions, where the first\n * and last positions are equivalent.\n *\n * @see http://www.geojson.org/geojson-spec.html#linestring\n * @since 1.0\n */\nclass LinearRing extends LineString\n{\n    /**\n     * @param array<Point|array<int|float>> $positions\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $positions, ...$args)\n    {\n        if (count($positions) < 4) {\n            throw new InvalidArgumentException('LinearRing requires at least four positions');\n        }\n\n        $lastPosition = end($positions);\n        $firstPosition = reset($positions);\n\n        $lastPosition = $lastPosition instanceof Point ? $lastPosition->getCoordinates() : $lastPosition;\n        $firstPosition = $firstPosition instanceof Point ? $firstPosition->getCoordinates() : $firstPosition;\n\n        if ($lastPosition !== $firstPosition) {\n            throw new InvalidArgumentException('LinearRing requires the first and last positions to be equivalent');\n        }\n\n        parent::__construct($positions, ... $args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/MultiLineString.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\n\nuse function array_map;\n\n/**\n * MultiLineString geometry object.\n *\n * Coordinates consist of an array of LineString coordinates.\n *\n * @see http://www.geojson.org/geojson-spec.html#multilinestring\n * @since 1.0\n */\nclass MultiLineString extends Geometry\n{\n    protected string $type = self::TYPE_MULTI_LINE_STRING;\n\n    /**\n     * @param array<LineString|array<Point|array<int|float>>> $lineStrings\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $lineStrings, ...$args)\n    {\n        $this->coordinates = array_map(\n            static function ($lineString) {\n                if (! $lineString instanceof LineString) {\n                    $lineString = new LineString($lineString);\n                }\n\n                return $lineString->getCoordinates();\n            },\n            $lineStrings\n        );\n\n        $this->setOptionalConstructorArgs($args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/MultiPoint.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\n\nuse function array_map;\n\n/**\n * MultiPoint geometry object.\n *\n * Coordinates consist of an array of positions.\n *\n * @see http://www.geojson.org/geojson-spec.html#multipoint\n * @since 1.0\n */\nclass MultiPoint extends Geometry\n{\n    protected string $type = self::TYPE_MULTI_POINT;\n\n    /**\n     * @param array<Point|array<float|int>> $positions\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $positions, ...$args)\n    {\n        $this->coordinates = array_map(\n            static function ($point) {\n                if (! $point instanceof Point) {\n                    $point = new Point($point);\n                }\n\n                return $point->getCoordinates();\n            },\n            $positions\n        );\n\n        $this->setOptionalConstructorArgs($args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/MultiPolygon.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\n\nuse function array_map;\n\n/**\n * MultiPolygon geometry object.\n *\n * Coordinates consist of an array of Polygon coordinates.\n *\n * @see http://www.geojson.org/geojson-spec.html#multipolygon\n * @since 1.0\n */\nclass MultiPolygon extends Geometry\n{\n    protected string $type = self::TYPE_MULTI_POLYGON;\n\n    /**\n     * @param array<Polygon|array<LinearRing|array<Point|array<int|float>>>> $polygons\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $polygons, ...$args)\n    {\n        $this->coordinates = array_map(\n            static function ($polygon) {\n                if (! $polygon instanceof Polygon) {\n                    $polygon = new Polygon($polygon);\n                }\n\n                return $polygon->getCoordinates();\n            },\n            $polygons\n        );\n\n        $this->setOptionalConstructorArgs($args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/Point.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\InvalidArgumentException;\n\nuse function count;\nuse function is_float;\nuse function is_int;\n\n/**\n * Point geometry object.\n *\n * Coordinates consist of a single position.\n *\n * @see http://www.geojson.org/geojson-spec.html#point\n * @since 1.0\n */\nclass Point extends Geometry\n{\n    protected string $type = self::TYPE_POINT;\n\n    /**\n     * @param array<float|int> $position\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $position, ...$args)\n    {\n        if (count($position) < 2) {\n            throw new InvalidArgumentException('Position requires at least two elements');\n        }\n\n        foreach ($position as $value) {\n            if (! is_int($value) && ! is_float($value)) {\n                throw new InvalidArgumentException('Position elements must be integers or floats');\n            }\n        }\n\n        $this->coordinates = $position;\n\n        $this->setOptionalConstructorArgs($args);\n    }\n}\n"
  },
  {
    "path": "src/Geometry/Polygon.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Geometry;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\n\n/**\n * Polygon geometry object.\n *\n * Coordinates consist of an array of LinearRing coordinates.\n *\n * @see http://www.geojson.org/geojson-spec.html#polygon\n * @since 1.0\n */\nclass Polygon extends Geometry\n{\n    protected string $type = self::TYPE_POLYGON;\n\n    /**\n     * @param array<LinearRing|array<Point|array<int|float>>> $linearRings\n     * @param CoordinateReferenceSystem|BoundingBox $args\n     */\n    public function __construct(array $linearRings, ...$args)\n    {\n        foreach ($linearRings as $linearRing) {\n            if (! $linearRing instanceof LinearRing) {\n                $linearRing = new LinearRing($linearRing);\n            }\n            $this->coordinates[] = $linearRing->getCoordinates();\n        }\n\n        $this->setOptionalConstructorArgs($args);\n    }\n}\n"
  },
  {
    "path": "src/JsonUnserializable.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson;\n\nuse GeoJson\\Exception\\UnserializationException;\n\n/**\n * JsonUnserializable interface for creating an object from decoded JSON.\n *\n * This is used as a factory method for GeoJson, BoundingBox, and CRS classes.\n *\n * @since 1.0\n */\ninterface JsonUnserializable\n{\n    /**\n     * Factory method for creating an object from a decoded JSON value.\n     *\n     * @param mixed $json\n     * @return mixed\n     * @throws UnserializationException\n     */\n    public static function jsonUnserialize($json);\n}\n"
  },
  {
    "path": "tests/BaseGeoJsonTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Feature\\Feature;\nuse GeoJson\\Geometry\\Geometry;\nuse PHPUnit\\Framework\\TestCase;\n\nabstract class BaseGeoJsonTest extends TestCase\n{\n    /**\n     * @param ...$extraArgs\n     *\n     * @return mixed\n     */\n    abstract public function createSubjectWithExtraArguments(...$extraArgs);\n\n    public function testConstructorShouldScanExtraArgumentsForCrsAndBoundingBox(): void\n    {\n        $box = $this->getMockBoundingBox();\n        $crs = $this->getMockCoordinateReferenceSystem();\n\n        $sut = $this->createSubjectWithExtraArguments();\n        $this->assertNull($sut->getBoundingBox());\n        $this->assertNull($sut->getCrs());\n\n        $sut = $this->createSubjectWithExtraArguments($box);\n        $this->assertSame($box, $sut->getBoundingBox());\n        $this->assertNull($sut->getCrs());\n\n        $sut = $this->createSubjectWithExtraArguments($crs);\n        $this->assertNull($sut->getBoundingBox());\n        $this->assertSame($crs, $sut->getCrs());\n\n        $sut = $this->createSubjectWithExtraArguments($box, $crs);\n        $this->assertSame($box, $sut->getBoundingBox());\n        $this->assertSame($crs, $sut->getCrs());\n\n        $sut = $this->createSubjectWithExtraArguments($crs, $box);\n        $this->assertSame($box, $sut->getBoundingBox());\n        $this->assertSame($crs, $sut->getCrs());\n\n        // Not that you would, but you could…\n        $sut = $this->createSubjectWithExtraArguments(null, null, $box, $crs);\n        $this->assertSame($box, $sut->getBoundingBox());\n        $this->assertSame($crs, $sut->getCrs());\n    }\n\n    public function testSerializationWithCrsAndBoundingBox(): void\n    {\n        $box = $this->getMockBoundingBox();\n        $box->method('jsonSerialize')->willReturn(['boundingBox']);\n\n        $crs = $this->getMockCoordinateReferenceSystem();\n        $crs->method('jsonSerialize')->willReturn(['coordinateReferenceSystem']);\n\n        $sut = $this->createSubjectWithExtraArguments($box, $crs);\n\n        $json = $sut->jsonSerialize();\n\n        $this->assertArrayHasKey('bbox', $json);\n        $this->assertArrayHasKey('crs', $json);\n        $this->assertSame(['boundingBox'], $json['bbox']);\n        $this->assertSame(['coordinateReferenceSystem'], $json['crs']);\n    }\n\n    protected function getMockBoundingBox()\n    {\n        return $this->createMock(BoundingBox::class);\n    }\n\n    protected function getMockCoordinateReferenceSystem()\n    {\n        return $this->createMock(CoordinateReferenceSystem::class);\n    }\n\n    protected function getMockFeature()\n    {\n        return $this->createMock(Feature::class);\n    }\n\n    protected function getMockGeometry()\n    {\n        return $this->createMock(Geometry::class);\n    }\n}\n"
  },
  {
    "path": "tests/BoundingBoxTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\JsonUnserializable;\nuse PHPUnit\\Framework\\TestCase;\nuse stdClass;\n\nuse function func_get_args;\nuse function json_decode;\n\nclass BoundingBoxTest extends TestCase\n{\n    public function testIsJsonSerializable(): void\n    {\n        $this->assertInstanceOf('JsonSerializable', new BoundingBox([0, 0, 1, 1]));\n    }\n\n    public function testIsJsonUnserializable(): void\n    {\n        $this->assertInstanceOf(JsonUnserializable::class, new BoundingBox([0, 0, 1, 1]));\n    }\n\n    public function testConstructorShouldRequireAtLeastFourValues(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('BoundingBox requires at least four values');\n\n        new BoundingBox([0, 0]);\n    }\n\n    public function testConstructorShouldRequireAnEvenNumberOfValues(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('BoundingBox requires an even number of values');\n\n        new BoundingBox([0, 0, 1, 1, 2]);\n    }\n\n    /**\n     * @dataProvider provideBoundsWithInvalidTypes\n     */\n    public function testConstructorShouldRequireIntegerOrFloatValues(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('BoundingBox values must be integers or floats');\n        new BoundingBox(func_get_args());\n    }\n\n    public function provideBoundsWithInvalidTypes()\n    {\n        return [\n            'strings' => ['0', '0.0', '1', '1.0'],\n            'objects' => [new stdClass(), new stdClass(), new stdClass(), new stdClass()],\n            'arrays' => [[], [], [], []],\n        ];\n    }\n\n    public function testConstructorShouldRequireMinBeforeMaxValues(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('BoundingBox min values must precede max values');\n\n        new BoundingBox([-90.0, -95.0, -92.5, 90.0]);\n    }\n\n    public function testSerialization(): void\n    {\n        $bounds = [-180.0, -90.0, 0.0, 180.0, 90.0, 100.0];\n        $boundingBox = new BoundingBox($bounds);\n\n        $this->assertSame($bounds, $boundingBox->getBounds());\n        $this->assertSame($bounds, $boundingBox->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = '[-180.0, -90.0, 180.0, 90.0]';\n\n        $json = json_decode($json, $assoc);\n        $boundingBox = BoundingBox::jsonUnserialize($json);\n\n        $this->assertInstanceOf(BoundingBox::class, $boundingBox);\n        $this->assertSame([-180.0, -90.0, 180.0, 90.0], $boundingBox->getBounds());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    /**\n     * @dataProvider provideInvalidUnserializationValues\n     */\n    public function testUnserializationShouldRequireArray($value): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('BoundingBox expected value of type array');\n\n        BoundingBox::jsonUnserialize($value);\n    }\n\n    public function provideInvalidUnserializationValues()\n    {\n        return [\n            [null],\n            [1],\n            ['foo'],\n            [new stdClass()],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/CoordinateReferenceSystem/CoordinateReferenceSystemTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\JsonUnserializable;\nuse JsonSerializable;\nuse PHPUnit\\Framework\\TestCase;\n\nclass CoordinateReferenceSystemTest extends TestCase\n{\n    public function testIsJsonSerializable(): void\n    {\n        $this->assertInstanceOf(\n            JsonSerializable::class,\n            $this->createMock(CoordinateReferenceSystem::class)\n        );\n    }\n\n    public function testIsJsonUnserializable(): void\n    {\n        $this->assertInstanceOf(\n            JsonUnserializable::class,\n            $this->createMock(CoordinateReferenceSystem::class)\n        );\n    }\n\n    public function testUnserializationShouldRequireArrayOrObject(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('CRS expected value of type array or object');\n\n        CoordinateReferenceSystem::jsonUnserialize(null);\n    }\n\n    public function testUnserializationShouldRequireTypeField(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('CRS expected \"type\" property of type string, none given');\n\n        CoordinateReferenceSystem::jsonUnserialize(['properties' => []]);\n    }\n\n    public function testUnserializationShouldRequirePropertiesField(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('CRS expected \"properties\" property of type array or object, none given');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'foo']);\n    }\n\n    public function testUnserializationShouldRequireValidType(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Invalid CRS type \"foo\"');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'foo', 'properties' => []]);\n    }\n}\n"
  },
  {
    "path": "tests/CoordinateReferenceSystem/LinkedTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\CoordinateReferenceSystem\\Linked;\nuse GeoJson\\Exception\\UnserializationException;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass LinkedTest extends TestCase\n{\n    public function testIsSubclassOfCoordinateReferenceSystem(): void\n    {\n        $this->assertTrue(is_subclass_of(Linked::class, CoordinateReferenceSystem::class));\n    }\n\n    public function testSerialization(): void\n    {\n        $crs = new Linked('https://example.com/crs/42', 'proj4');\n\n        $expected = [\n            'type' => 'link',\n            'properties' => [\n                'href' => 'https://example.com/crs/42',\n                'type' => 'proj4',\n            ],\n        ];\n\n        $this->assertSame('link', $crs->getType());\n        $this->assertSame($expected['properties'], $crs->getProperties());\n        $this->assertSame($expected, $crs->jsonSerialize());\n    }\n\n    public function testSerializationWithoutHrefType(): void\n    {\n        $crs = new Linked('https://example.com/crs/42');\n\n        $expected = [\n            'type' => 'link',\n            'properties' => [\n                'href' => 'https://example.com/crs/42',\n            ],\n        ];\n\n        $this->assertSame($expected, $crs->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"link\",\n    \"properties\": {\n        \"href\": \"https://example.com/crs/42\",\n        \"type\": \"proj4\"\n    }\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $crs = CoordinateReferenceSystem::jsonUnserialize($json);\n\n        $expectedProperties = [\n            'href' => 'https://example.com/crs/42',\n            'type' => 'proj4',\n        ];\n\n        $this->assertInstanceOf(Linked::class, $crs);\n        $this->assertSame('link', $crs->getType());\n        $this->assertSame($expectedProperties, $crs->getProperties());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserializationWithoutHrefType($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"link\",\n    \"properties\": {\n        \"href\": \"https://example.com/crs/42\"\n    }\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $crs = CoordinateReferenceSystem::jsonUnserialize($json);\n\n        $expectedProperties = ['href' => 'https://example.com/crs/42'];\n\n        $this->assertInstanceOf(Linked::class, $crs);\n        $this->assertSame('link', $crs->getType());\n        $this->assertSame($expectedProperties, $crs->getProperties());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    public function testUnserializationShouldRequirePropertiesArrayOrObject(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Linked CRS expected \"properties\" property of type array or object');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'link', 'properties' => null]);\n    }\n\n    public function testUnserializationShouldRequireHrefProperty(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Linked CRS expected \"properties.href\" property of type string');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'link', 'properties' => []]);\n    }\n}\n"
  },
  {
    "path": "tests/CoordinateReferenceSystem/NamedTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\CoordinateReferenceSystem;\n\nuse GeoJson\\CoordinateReferenceSystem\\CoordinateReferenceSystem;\nuse GeoJson\\CoordinateReferenceSystem\\Named;\nuse GeoJson\\Exception\\UnserializationException;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass NamedTest extends TestCase\n{\n    public function testIsSubclassOfCoordinateReferenceSystem(): void\n    {\n        $this->assertTrue(is_subclass_of(Named::class, CoordinateReferenceSystem::class));\n    }\n\n    public function testSerialization(): void\n    {\n        $crs = new Named('urn:ogc:def:crs:OGC:1.3:CRS84');\n\n        $expected = [\n            'type' => 'name',\n            'properties' => [\n                'name' => 'urn:ogc:def:crs:OGC:1.3:CRS84'\n            ],\n        ];\n\n        $this->assertSame('name', $crs->getType());\n        $this->assertSame($expected['properties'], $crs->getProperties());\n        $this->assertSame($expected, $crs->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"name\",\n    \"properties\": {\n        \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"\n    }\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $crs = CoordinateReferenceSystem::jsonUnserialize($json);\n\n        $expectedProperties = ['name' => 'urn:ogc:def:crs:OGC:1.3:CRS84'];\n\n        $this->assertInstanceOf(Named::class, $crs);\n        $this->assertSame('name', $crs->getType());\n        $this->assertSame($expectedProperties, $crs->getProperties());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    public function testUnserializationShouldRequirePropertiesArrayOrObject(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Named CRS expected \"properties\" property of type array or object');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'name', 'properties' => null]);\n    }\n\n    public function testUnserializationShouldRequireNameProperty(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Named CRS expected \"properties.name\" property of type string');\n\n        CoordinateReferenceSystem::jsonUnserialize(['type' => 'name', 'properties' => []]);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/FeatureCollectionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Feature;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\Feature\\Feature;\nuse GeoJson\\Feature\\FeatureCollection;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\nuse stdClass;\n\nuse function is_subclass_of;\nuse function iterator_to_array;\nuse function json_decode;\n\nclass FeatureCollectionTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new FeatureCollection([], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeoJson(): void\n    {\n        $this->assertTrue(is_subclass_of(FeatureCollection::class, GeoJson::class));\n    }\n\n\n    public function testConstructorShouldRequireArrayOfFeatureObjects(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('FeatureCollection may only contain Feature objects');\n\n        new FeatureCollection([new stdClass()]);\n    }\n\n    public function testConstructorShouldReindexFeaturesArrayNumerically(): void\n    {\n        $feature1 = $this->getMockFeature();\n        $feature2 = $this->getMockFeature();\n\n        $features = [\n            'one' => $feature1,\n            'two' => $feature2,\n        ];\n\n        $collection = new FeatureCollection($features);\n\n        $this->assertSame([$feature1, $feature2], iterator_to_array($collection));\n    }\n\n    public function testIsTraversable(): void\n    {\n        $features = [\n            $this->getMockFeature(),\n            $this->getMockFeature(),\n        ];\n\n        $collection = new FeatureCollection($features);\n\n        $this->assertInstanceOf('Traversable', $collection);\n        $this->assertSame($features, iterator_to_array($collection));\n    }\n\n    public function testIsCountable(): void\n    {\n        $features = [\n            $this->getMockFeature(),\n            $this->getMockFeature(),\n        ];\n\n        $collection = new FeatureCollection($features);\n\n        $this->assertInstanceOf('Countable', $collection);\n        $this->assertCount(2, $collection);\n    }\n\n    public function testSerialization(): void\n    {\n        $features = [\n            $this->getMockFeature(),\n            $this->getMockFeature(),\n        ];\n\n        $features[0]->method('jsonSerialize')->willReturn(['feature1']);\n        $features[1]->method('jsonSerialize')->willReturn(['feature2']);\n\n        $collection = new FeatureCollection($features);\n\n        $expected = [\n            'type' => GeoJson::TYPE_FEATURE_COLLECTION,\n            'features' => [['feature1'], ['feature2']],\n        ];\n\n        $this->assertSame(GeoJson::TYPE_FEATURE_COLLECTION, $collection->getType());\n        $this->assertSame($features, $collection->getFeatures());\n        $this->assertSame($expected, $collection->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"FeatureCollection\",\n    \"features\": [\n        {\n            \"type\": \"Feature\",\n            \"id\": \"test.feature.1\",\n            \"geometry\": {\n                \"type\": \"Point\",\n                \"coordinates\": [1, 1]\n            }\n        }\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $collection = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(FeatureCollection::class, $collection);\n        $this->assertSame(GeoJson::TYPE_FEATURE_COLLECTION, $collection->getType());\n        $this->assertCount(1, $collection);\n\n        $features = iterator_to_array($collection);\n        $feature = $features[0];\n\n        $this->assertInstanceOf(Feature::class, $feature);\n        $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType());\n        $this->assertSame('test.feature.1', $feature->getId());\n        $this->assertNull($feature->getProperties());\n\n        $geometry = $feature->getGeometry();\n\n        $this->assertInstanceOf(Point::class, $geometry);\n        $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType());\n        $this->assertSame([1, 1], $geometry->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    public function testUnserializationShouldRequireFeaturesProperty(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('FeatureCollection expected \"features\" property of type array, none given');\n\n        GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_FEATURE_COLLECTION]);\n    }\n\n    public function testUnserializationShouldRequireFeaturesArray(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('FeatureCollection expected \"features\" property of type array');\n\n        GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_FEATURE_COLLECTION, 'features' => null]);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/FeatureTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Feature;\n\nuse GeoJson\\Feature\\Feature;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\nuse stdClass;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass FeatureTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new Feature(null, null, null, ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeoJson(): void\n    {\n        $this->assertTrue(is_subclass_of(Feature::class, GeoJson::class));\n    }\n\n    public function testSerialization(): void\n    {\n        $geometry = $this->getMockGeometry();\n\n        $geometry->method('jsonSerialize')->willReturn(['geometry']);\n\n        $properties = ['key' => 'value'];\n        $id = 'identifier';\n\n        $feature = new Feature($geometry, $properties, $id);\n\n        $expected = [\n            'type' => GeoJson::TYPE_FEATURE,\n            'geometry' => ['geometry'],\n            'properties' => $properties,\n            'id' => 'identifier',\n        ];\n\n        $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType());\n        $this->assertSame($geometry, $feature->getGeometry());\n        $this->assertSame($id, $feature->getId());\n        $this->assertSame($properties, $feature->getProperties());\n        $this->assertSame($expected, $feature->jsonSerialize());\n    }\n\n    public function testSerializationWithNullConstructorArguments(): void\n    {\n        $feature = new Feature();\n\n        $expected = [\n            'type' => GeoJson::TYPE_FEATURE,\n            'geometry' => null,\n            'properties' => null,\n        ];\n\n        $this->assertSame($expected, $feature->jsonSerialize());\n    }\n\n    public function testSerializationShouldConvertEmptyPropertiesArrayToObject(): void\n    {\n        $feature = new Feature(null, []);\n\n        $expected = [\n            'type' => GeoJson::TYPE_FEATURE,\n            'geometry' => null,\n            'properties' => new stdClass(),\n        ];\n\n        $this->assertEquals($expected, $feature->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"Feature\",\n    \"id\": \"test.feature.1\",\n    \"properties\": {\n        \"key\": \"value\"\n    },\n    \"geometry\": {\n        \"type\": \"Point\",\n        \"coordinates\": [1, 1]\n    }\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $feature = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(Feature::class, $feature);\n        $this->assertSame(GeoJson::TYPE_FEATURE, $feature->getType());\n        $this->assertSame('test.feature.1', $feature->getId());\n        $this->assertSame(['key' => 'value'], $feature->getProperties());\n\n        $geometry = $feature->getGeometry();\n\n        $this->assertInstanceOf(Point::class, $geometry);\n        $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType());\n        $this->assertSame([1, 1], $geometry->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/GeoJsonTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests;\n\nuse GeoJson\\BoundingBox;\nuse GeoJson\\CoordinateReferenceSystem\\Named;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\JsonUnserializable;\nuse JsonSerializable;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function get_class;\nuse function gettype;\nuse function is_object;\nuse function json_decode;\n\nclass GeoJsonTest extends TestCase\n{\n    public function testIsJsonSerializable(): void\n    {\n        $this->assertInstanceOf(JsonSerializable::class, $this->createMock(GeoJson::class));\n    }\n\n    public function testIsJsonUnserializable(): void\n    {\n        $this->assertInstanceOf(JsonUnserializable::class, $this->createMock(GeoJson::class));\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserializationWithBoundingBox($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"Point\",\n    \"coordinates\": [1, 1],\n    \"bbox\": [-180.0, -90.0, 180.0, 90.0]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $point = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(Point::class, $point);\n        $this->assertSame(GeoJson::TYPE_POINT, $point->getType());\n        $this->assertSame([1, 1], $point->getCoordinates());\n\n        $boundingBox = $point->getBoundingBox();\n\n        $this->assertInstanceOf(BoundingBox::class, $boundingBox);\n        $this->assertSame([-180.0, -90.0, 180.0, 90.0], $boundingBox->getBounds());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserializationWithCrs($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"Point\",\n    \"coordinates\": [1, 1],\n    \"crs\": {\n        \"type\": \"name\",\n        \"properties\": {\n            \"name\": \"urn:ogc:def:crs:OGC:1.3:CRS84\"\n        }\n    }\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $point = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(Point::class, $point);\n        $this->assertSame(GeoJson::TYPE_POINT, $point->getType());\n        $this->assertSame([1, 1], $point->getCoordinates());\n\n        $crs = $point->getCrs();\n\n        $expectedProperties = ['name' => 'urn:ogc:def:crs:OGC:1.3:CRS84'];\n\n        $this->assertInstanceOf(Named::class, $crs);\n        $this->assertSame('name', $crs->getType());\n        $this->assertSame($expectedProperties, $crs->getProperties());\n    }\n\n    public function testUnserializationWithInvalidArgument(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('GeoJson expected value of type array or object, string given');\n\n        GeoJson::jsonUnserialize('must be array or object, but this is a string');\n    }\n\n    public function testUnserializationWithUnknownType(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Invalid GeoJson type \"Unknown\"');\n\n        GeoJson::jsonUnserialize(['type' => 'Unknown']);\n    }\n\n    public function testUnserializationWithMissingType(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('GeoJson expected \"type\" property of type string, none given');\n\n        GeoJson::jsonUnserialize([]);\n    }\n\n    /**\n     * @dataProvider provideGeoJsonTypesWithCoordinates\n     */\n    public function testUnserializationWithMissingCoordinates(string $type): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage($type . ' expected \"coordinates\" property of type array, none given');\n\n        GeoJson::jsonUnserialize([\n            'type' => $type,\n        ]);\n    }\n\n    /**\n     * @dataProvider provideInvalidCoordinates\n     *\n     * @param mixed $value\n     */\n    public function testUnserializationWithInvalidCoordinates($value): void\n    {\n        $valueType = is_object($value) ? get_class($value) : gettype($value);\n\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Point expected \"coordinates\" property of type array, ' . $valueType . ' given');\n\n        GeoJson::jsonUnserialize([\n            'type' => GeoJson::TYPE_POINT,\n            'coordinates' => $value,\n        ]);\n    }\n\n    public function testFeatureUnserializationWithInvalidGeometry(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Feature expected \"geometry\" property of type array or object, string given');\n\n        GeoJson::jsonUnserialize([\n            'type' => GeoJson::TYPE_FEATURE,\n            'geometry' => 'must be array or object, but this is a string',\n        ]);\n    }\n\n    public function testFeatureUnserializationWithInvalidProperties(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('Feature expected \"properties\" property of type array or object, string given');\n\n        GeoJson::jsonUnserialize([\n            'type' => GeoJson::TYPE_FEATURE,\n            'properties' => 'must be array or object, but this is a string',\n        ]);\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    public function provideGeoJsonTypesWithCoordinates()\n    {\n        return [\n            GeoJson::TYPE_LINE_STRING => [GeoJson::TYPE_LINE_STRING],\n            GeoJson::TYPE_MULTI_LINE_STRING => [GeoJson::TYPE_MULTI_LINE_STRING],\n            GeoJson::TYPE_MULTI_POINT => [GeoJson::TYPE_MULTI_POINT],\n            GeoJson::TYPE_MULTI_POLYGON => [GeoJson::TYPE_MULTI_POLYGON],\n            GeoJson::TYPE_POINT => [GeoJson::TYPE_POINT],\n            GeoJson::TYPE_POLYGON => [GeoJson::TYPE_POLYGON],\n        ];\n    }\n\n    public function provideInvalidCoordinates()\n    {\n        return [\n            'string' => ['1,1'],\n            'int' => [1],\n            'bool' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/GeometryCollectionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\Exception\\UnserializationException;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\GeometryCollection;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\nuse stdClass;\n\nuse function is_subclass_of;\nuse function iterator_to_array;\nuse function json_decode;\n\nclass GeometryCollectionTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new GeometryCollection([], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(GeometryCollection::class, Geometry::class));\n    }\n\n    public function testConstructorShouldRequireArrayOfGeometryObjects(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('GeometryCollection may only contain Geometry objects');\n\n        new GeometryCollection([new stdClass()]);\n    }\n\n    public function testConstructorShouldReindexGeometriesArrayNumerically(): void\n    {\n        $geometry1 = $this->getMockGeometry();\n        $geometry2 = $this->getMockGeometry();\n\n        $geometries = [\n            'one' => $geometry1,\n            'two' => $geometry2,\n        ];\n\n        $collection = new GeometryCollection($geometries);\n        $this->assertSame([$geometry1, $geometry2], iterator_to_array($collection));\n    }\n\n    public function testIsTraversable(): void\n    {\n        $geometries = [\n            $this->getMockGeometry(),\n            $this->getMockGeometry(),\n        ];\n\n        $collection = new GeometryCollection($geometries);\n\n        $this->assertInstanceOf('Traversable', $collection);\n        $this->assertSame($geometries, iterator_to_array($collection));\n    }\n\n    public function testIsCountable(): void\n    {\n        $geometries = [\n            $this->getMockGeometry(),\n            $this->getMockGeometry(),\n        ];\n\n        $collection = new GeometryCollection($geometries);\n\n        $this->assertInstanceOf('Countable', $collection);\n        $this->assertCount(2, $collection);\n    }\n\n    public function testSerialization(): void\n    {\n        $geometries = [\n            $this->getMockGeometry(),\n            $this->getMockGeometry(),\n        ];\n\n        $geometries[0]->method('jsonSerialize')->willReturn(['geometry1']);\n        $geometries[1]->method('jsonSerialize')->willReturn(['geometry2']);\n\n        $collection = new GeometryCollection($geometries);\n\n        $expected = [\n            'type' => GeoJson::TYPE_GEOMETRY_COLLECTION,\n            'geometries' => [['geometry1'], ['geometry2']],\n        ];\n\n        $this->assertSame(GeoJson::TYPE_GEOMETRY_COLLECTION, $collection->getType());\n        $this->assertSame($geometries, $collection->getGeometries());\n        $this->assertSame($expected, $collection->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"GeometryCollection\",\n    \"geometries\": [\n        {\n            \"type\": \"Point\",\n            \"coordinates\": [1, 1]\n        }\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $collection = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(GeometryCollection::class, $collection);\n        $this->assertSame(GeoJson::TYPE_GEOMETRY_COLLECTION, $collection->getType());\n        $this->assertCount(1, $collection);\n\n        $geometries = iterator_to_array($collection);\n        $geometry = $geometries[0];\n\n        $this->assertInstanceOf(Point::class, $geometry);\n        $this->assertSame(GeoJson::TYPE_POINT, $geometry->getType());\n        $this->assertSame([1, 1], $geometry->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n\n    public function testUnserializationShouldRequireGeometriesProperty(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('GeometryCollection expected \"geometries\" property of type array, none given');\n\n        GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_GEOMETRY_COLLECTION]);\n    }\n\n    public function testUnserializationShouldRequireGeometriesArray(): void\n    {\n        $this->expectException(UnserializationException::class);\n        $this->expectExceptionMessage('GeometryCollection expected \"geometries\" property of type array');\n\n        GeoJson::jsonUnserialize(['type' => GeoJson::TYPE_GEOMETRY_COLLECTION, 'geometries' => null]);\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/GeometryTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function is_subclass_of;\n\nclass GeometryTest extends TestCase\n{\n    public function testIsSubclassOfGeoJson(): void\n    {\n        $this->assertTrue(is_subclass_of(Geometry::class, GeoJson::class));\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/LineStringTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\LineString;\nuse GeoJson\\Geometry\\MultiPoint;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass LineStringTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new LineString(\n            [[1, 1], [2, 2]],\n            ... $extraArgs\n        );\n    }\n\n    public function testIsSubclassOfMultiPoint(): void\n    {\n        $this->assertTrue(is_subclass_of(LineString::class, MultiPoint::class));\n    }\n\n    public function testConstructorShouldRequireAtLeastTwoPositions(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('LineString requires at least two positions');\n\n        new LineString([[1, 1]]);\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [[1, 1], [2, 2]];\n        $lineString = new LineString($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_LINE_STRING,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_LINE_STRING, $lineString->getType());\n        $this->assertSame($coordinates, $lineString->getCoordinates());\n        $this->assertSame($expected, $lineString->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"LineString\",\n    \"coordinates\": [\n        [1, 1],\n        [2, 2]\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $lineString = GeoJson::jsonUnserialize($json);\n\n        $expectedCoordinates = [[1, 1], [2, 2]];\n\n        $this->assertInstanceOf(LineString::class, $lineString);\n        $this->assertSame(GeoJson::TYPE_LINE_STRING, $lineString->getType());\n        $this->assertSame($expectedCoordinates, $lineString->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/LinearRingTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\LinearRing;\nuse GeoJson\\Geometry\\LineString;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\n\nclass LinearRingTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new LinearRing(\n            [[1, 1], [2, 2], [3, 3], [1, 1]],\n            ... $extraArgs\n        );\n    }\n\n    public function testIsSubclassOfLineString(): void\n    {\n        $this->assertTrue(is_subclass_of(LinearRing::class, LineString::class));\n    }\n\n    public function testConstructorShouldRequireAtLeastFourPositions(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('LinearRing requires at least four positions');\n\n        new LinearRing([\n            [1, 1],\n            [2, 2],\n            [3, 3],\n        ]);\n    }\n\n    public function testConstructorShouldRequireEquivalentFirstAndLastPositions(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('LinearRing requires the first and last positions to be equivalent');\n\n        new LinearRing([\n            [1, 1],\n            [2, 2],\n            [3, 3],\n            [4, 4],\n        ]);\n    }\n\n    /**\n     * @doesNotPerformAssertions\n     */\n    public function testConstructorShouldAcceptEquivalentPointObjectsAndPositionArrays(): void\n    {\n        new LinearRing([\n            [1, 1],\n            [2, 2],\n            [3, 3],\n            new Point([1, 1]),\n        ]);\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [[1, 1], [2, 2], [3, 3], [1, 1]];\n        $linearRing = new LinearRing($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_LINE_STRING,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_LINE_STRING, $linearRing->getType());\n        $this->assertSame($coordinates, $linearRing->getCoordinates());\n        $this->assertSame($expected, $linearRing->jsonSerialize());\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/MultiLineStringTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\LineString;\nuse GeoJson\\Geometry\\MultiLineString;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass MultiLineStringTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new MultiLineString([], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(MultiLineString::class, Geometry::class));\n    }\n\n    public function testConstructionFromLineStringObjects(): void\n    {\n        $multiLineString1 = new MultiLineString([\n            new LineString([[1, 1], [2, 2]]),\n            new LineString([[3, 3], [4, 4]]),\n        ]);\n\n        $multiLineString2 = new MultiLineString([\n            [[1, 1], [2, 2]],\n            [[3, 3], [4, 4]],\n        ]);\n\n        $this->assertSame($multiLineString1->getCoordinates(), $multiLineString2->getCoordinates());\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [\n            [[1, 1], [2, 2]],\n            [[3, 3], [4, 4]],\n        ];\n\n        $multiLineString = new MultiLineString($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_MULTI_LINE_STRING,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_MULTI_LINE_STRING, $multiLineString->getType());\n        $this->assertSame($coordinates, $multiLineString->getCoordinates());\n        $this->assertSame($expected, $multiLineString->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"MultiLineString\",\n    \"coordinates\": [\n        [ [1, 1], [2, 2] ],\n        [ [3, 3], [4, 4] ]\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $multiLineString = GeoJson::jsonUnserialize($json);\n\n        $expectedCoordinates = [\n            [[1, 1], [2, 2]],\n            [[3, 3], [4, 4]],\n        ];\n\n        $this->assertInstanceOf(MultiLineString::class, $multiLineString);\n        $this->assertSame(GeoJson::TYPE_MULTI_LINE_STRING, $multiLineString->getType());\n        $this->assertSame($expectedCoordinates, $multiLineString->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/MultiPointTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\MultiPoint;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass MultiPointTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new MultiPoint([], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(MultiPoint::class, Geometry::class));\n    }\n\n    public function testConstructionFromPointObjects(): void\n    {\n        $multiPoint1 = new MultiPoint([\n            new Point([1, 1]),\n            new Point([2, 2]),\n        ]);\n\n        $multiPoint2 = new MultiPoint([\n            [1, 1],\n            [2, 2],\n        ]);\n\n        $this->assertSame($multiPoint1->getCoordinates(), $multiPoint2->getCoordinates());\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [[1, 1], [2, 2]];\n        $multiPoint = new MultiPoint($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_MULTI_POINT,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_MULTI_POINT, $multiPoint->getType());\n        $this->assertSame($coordinates, $multiPoint->getCoordinates());\n        $this->assertSame($expected, $multiPoint->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"MultiPoint\",\n    \"coordinates\": [\n        [1, 1],\n        [2, 2]\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $multiPoint = GeoJson::jsonUnserialize($json);\n\n        $expectedCoordinates = [[1, 1], [2, 2]];\n\n        $this->assertInstanceOf(MultiPoint::class, $multiPoint);\n        $this->assertSame(GeoJson::TYPE_MULTI_POINT, $multiPoint->getType());\n        $this->assertSame($expectedCoordinates, $multiPoint->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/MultiPolygonTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\MultiPolygon;\nuse GeoJson\\Geometry\\Polygon;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass MultiPolygonTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new MultiPolygon([], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(MultiPolygon::class, Geometry::class));\n    }\n\n    public function testConstructionFromPolygonObjects(): void\n    {\n        $multiPolygon1 = new MultiPolygon([\n            new Polygon([[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]]),\n            new Polygon([[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]]),\n        ]);\n\n        $multiPolygon2 = new MultiPolygon([\n            [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]],\n            [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]],\n        ]);\n\n        $this->assertSame($multiPolygon1->getCoordinates(), $multiPolygon2->getCoordinates());\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [\n            [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]],\n            [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]],\n        ];\n\n        $multiPolygon = new MultiPolygon($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_MULTI_POLYGON,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_MULTI_POLYGON, $multiPolygon->getType());\n        $this->assertSame($coordinates, $multiPolygon->getCoordinates());\n        $this->assertSame($expected, $multiPolygon->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"MultiPolygon\",\n    \"coordinates\": [\n        [ [ [0, 0], [0, 4], [4, 4], [4, 0], [0, 0] ] ],\n        [ [ [1, 1], [1, 3], [3, 3], [3, 1], [1, 1] ] ]\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $multiPolygon = GeoJson::jsonUnserialize($json);\n\n        $expectedCoordinates = [\n            [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]],\n            [[[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]],\n        ];\n\n        $this->assertInstanceOf(MultiPolygon::class, $multiPolygon);\n        $this->assertSame(GeoJson::TYPE_MULTI_POLYGON, $multiPolygon->getType());\n        $this->assertSame($expectedCoordinates, $multiPolygon->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/PointTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\Exception\\InvalidArgumentException;\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\Point;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\nuse stdClass;\n\nuse function func_get_args;\nuse function is_subclass_of;\nuse function json_decode;\n\nclass PointTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new Point([1, 1], ... $extraArgs);\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(Point::class, Geometry::class));\n    }\n\n    public function testConstructorShouldRequireAtLeastTwoElementsInPosition(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Position requires at least two elements');\n\n        new Point([1]);\n    }\n\n    /**\n     * @dataProvider providePositionsWithInvalidTypes\n     */\n    public function testConstructorShouldRequireIntegerOrFloatElementsInPosition(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Position elements must be integers or floats');\n\n        new Point(func_get_args());\n    }\n\n    public function providePositionsWithInvalidTypes()\n    {\n        return [\n            'strings' => ['1.0', '2'],\n            'objects' => [new stdClass(), new stdClass()],\n            'arrays' => [[], []],\n        ];\n    }\n\n    public function testConstructorShouldAllowMoreThanTwoElementsInAPosition(): void\n    {\n        $point = new Point([1, 2, 3, 4]);\n\n        $this->assertEquals([1, 2, 3, 4], $point->getCoordinates());\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [1, 1];\n        $point = new Point($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_POINT,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_POINT, $point->getType());\n        $this->assertSame($coordinates, $point->getCoordinates());\n        $this->assertSame($expected, $point->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"Point\",\n    \"coordinates\": [1, 1]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $point = GeoJson::jsonUnserialize($json);\n\n        $this->assertInstanceOf(Point::class, $point);\n        $this->assertSame(GeoJson::TYPE_POINT, $point->getType());\n        $this->assertSame([1, 1], $point->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Geometry/PolygonTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GeoJson\\Tests\\Geometry;\n\nuse GeoJson\\GeoJson;\nuse GeoJson\\Geometry\\Geometry;\nuse GeoJson\\Geometry\\LinearRing;\nuse GeoJson\\Geometry\\Polygon;\nuse GeoJson\\Tests\\BaseGeoJsonTest;\n\nuse function is_subclass_of;\nuse function json_decode;\n\nclass PolygonTest extends BaseGeoJsonTest\n{\n    public function createSubjectWithExtraArguments(...$extraArgs)\n    {\n        return new Polygon(\n            [\n                [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]],\n                [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]],\n            ],\n            ... $extraArgs\n        );\n    }\n\n    public function testIsSubclassOfGeometry(): void\n    {\n        $this->assertTrue(is_subclass_of(Polygon::class, Geometry::class));\n    }\n\n    public function testConstructionFromLinearRingObjects(): void\n    {\n        $polygon1 = new Polygon([\n            new LinearRing([[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]),\n            new LinearRing([[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]]),\n        ]);\n\n        $polygon2 = new Polygon([\n            [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]],\n            [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]],\n        ]);\n\n        $this->assertSame($polygon1->getCoordinates(), $polygon2->getCoordinates());\n    }\n\n    public function testSerialization(): void\n    {\n        $coordinates = [\n            [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]],\n            [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]],\n        ];\n\n        $polygon = new Polygon($coordinates);\n\n        $expected = [\n            'type' => GeoJson::TYPE_POLYGON,\n            'coordinates' => $coordinates,\n        ];\n\n        $this->assertSame(GeoJson::TYPE_POLYGON, $polygon->getType());\n        $this->assertSame($coordinates, $polygon->getCoordinates());\n        $this->assertSame($expected, $polygon->jsonSerialize());\n    }\n\n    /**\n     * @dataProvider provideJsonDecodeAssocOptions\n     * @group functional\n     */\n    public function testUnserialization($assoc): void\n    {\n        $json = <<<'JSON'\n{\n    \"type\": \"Polygon\",\n    \"coordinates\": [\n        [ [0, 0], [0, 4], [4, 4], [4, 0], [0, 0] ],\n        [ [1, 1], [1, 3], [3, 3], [3, 1], [1, 1] ]\n    ]\n}\nJSON;\n\n        $json = json_decode($json, $assoc);\n        $polygon = GeoJson::jsonUnserialize($json);\n\n        $expectedCoordinates = [\n            [[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]],\n            [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]],\n        ];\n\n        $this->assertInstanceOf(Polygon::class, $polygon);\n        $this->assertSame(GeoJson::TYPE_POLYGON, $polygon->getType());\n        $this->assertSame($expectedCoordinates, $polygon->getCoordinates());\n    }\n\n    public function provideJsonDecodeAssocOptions()\n    {\n        return [\n            'assoc=true' => [true],\n            'assoc=false' => [false],\n        ];\n    }\n}\n"
  }
]