├── .github
└── workflows
│ ├── php-cs-fixer.yml
│ └── run-tests.yml
├── .gitignore
├── .php-cs-fixer.php
├── CLA.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── changelog.md
├── composer.json
├── docs
├── installation.md
├── supported-files.md
├── tests.md
└── usage.md
├── phpunit.xml.dist
├── src
├── Auth
│ ├── Authentication.php
│ └── NullAuthentication.php
├── Contracts
│ ├── Authentication.php
│ ├── FileReader.php
│ └── Model.php
├── Exception
│ ├── DeserializationException.php
│ ├── ErrorResponseException.php
│ ├── GeoServerClientException.php
│ ├── SerializationException.php
│ ├── StoreNotFoundException.php
│ ├── StyleAlreadyExistsException.php
│ ├── StyleNotFoundException.php
│ └── UnsupportedFileException.php
├── GeoFile.php
├── GeoFormat.php
├── GeoServer.php
├── GeoType.php
├── Http
│ ├── InteractsWithHttp.php
│ ├── ResponseHelper.php
│ ├── Responses
│ │ ├── CoverageResponse.php
│ │ ├── CoverageStoreResponse.php
│ │ ├── CoverageStoresResponse.php
│ │ ├── DataStoreResponse.php
│ │ ├── FeatureResponse.php
│ │ ├── StylesResponse.php
│ │ └── WorkspaceResponse.php
│ └── Routes.php
├── Models
│ ├── BoundingBox.php
│ ├── Coverage.php
│ ├── CoverageStore.php
│ ├── DataStore.php
│ ├── Feature.php
│ ├── Resource.php
│ ├── Store.php
│ ├── Style.php
│ └── Workspace.php
├── Options.php
├── Serializer
│ ├── DeserializeBoundingBoxSubscriber.php
│ ├── DeserializeCoverageStoreResponseSubscriber.php
│ ├── DeserializeDataStoreResponseSubscriber.php
│ └── DeserializeStyleResponseSubscriber.php
├── StyleFile.php
└── Support
│ ├── BinaryReader.php
│ ├── ImageResponse.php
│ ├── TextReader.php
│ ├── TypeResolver.php
│ ├── WmsOptions.php
│ └── ZipReader.php
└── tests
├── Concern
├── GeneratesData.php
└── SetupIntegrationTest.php
├── Integration
├── GeoServerCoverageStoresTest.php
├── GeoServerDataStoresTest.php
├── GeoServerStylesTest.php
├── GeoServerVersionRetrievalTest.php
├── GeoServerWmsTest.php
└── GeoServerWorkspaceTest.php
├── Support
└── ImageDifference.php
├── TestCase.php
├── Unit
├── AuthenticationTest.php
├── GeoFileTest.php
├── GeoServerClientInstantiationTest.php
├── OptionsTest.php
├── ResponseHelperTest.php
├── StyleFileTest.php
└── WmsOptionsTest.php
├── docker-compose.yml
├── fixtures
├── buildings.zip
├── empty.gpkg
├── geojson-in-plain-json.json
├── geojson.geojson
├── geotiff.tiff
├── geotiff_thumbnail.png
├── gpx.gpx
├── kml.kml
├── kmz.kmz
├── plain.json
├── plain.zip
├── rivers.gpkg
├── shapefile.shp
├── shapefile.zip
├── shapefile_thumbnail.png
├── some_shapefile_with_cyrillicйфячыцус.zip
├── style.sld
└── tiff.tif
├── geoserver.env
├── helpers.php
└── wait.php
/.github/workflows/php-cs-fixer.yml:
--------------------------------------------------------------------------------
1 | name: Check & fix styling
2 |
3 | on: [push]
4 |
5 | jobs:
6 | php-cs-fixer:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - name: Checkout code
11 | uses: actions/checkout@v2
12 | with:
13 | ref: ${{ github.head_ref }}
14 |
15 | - name: Run PHP CS Fixer
16 | uses: docker://oskarstark/php-cs-fixer-ga
17 | with:
18 | args: --config=.php-cs-fixer.php --allow-risky=yes
19 |
20 | - name: Commit changes
21 | uses: stefanzweifel/git-auto-commit-action@v4
22 | with:
23 | commit_message: Fix styling
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | os: [ubuntu-latest]
16 | php: [7.4, 8.0, 8.1]
17 | geoserver: ["2.20.4", "2.21.0"]
18 | exclude:
19 | - geoserver: "2.20.4"
20 | php: 7.4
21 |
22 | name: P${{ matrix.php }} - G${{ matrix.geoserver }}
23 |
24 | env:
25 | GEOSERVER_TAG: ${{ matrix.geoserver }}
26 |
27 | steps:
28 | - name: Checkout code
29 | uses: actions/checkout@v2
30 |
31 | - name: Setup PHP
32 | uses: shivammathur/setup-php@v2
33 | with:
34 | php-version: ${{ matrix.php }}
35 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
36 | coverage: none
37 |
38 | - name: Setup problem matchers
39 | run: |
40 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
41 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
42 |
43 | - name: Install dependencies
44 | run: |
45 | composer update --prefer-dist --no-interaction
46 |
47 | - name: Create Environment
48 | run: |
49 | docker-compose -f ./tests/docker-compose.yml up -d
50 | php ./tests/wait.php
51 |
52 | - name: Execute tests
53 | env:
54 | GEOSERVER_URL: "http://127.0.0.1:8600/geoserver/"
55 | GEOSERVER_USER: "GEOSERVER_ADMIN_USER"
56 | GEOSERVER_PASSWORD: "GEOSERVER_ADMIN_PASSWORD"
57 | run: vendor/bin/phpunit
58 |
59 | - name: Stop Environment
60 | if: always()
61 | run: |
62 | docker-compose -f ./tests/docker-compose.yml down
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | vendor/
3 | coverage
4 | phpunit.xml
5 | composer.lock
6 | .php_cs.cache
7 | .phpunit.result.cache
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | true,
5 | 'blank_line_after_opening_tag' => true,
6 | 'braces' => true,
7 | 'concat_space' => ['spacing' => 'none'],
8 | 'no_multiline_whitespace_around_double_arrow' => true,
9 | 'elseif' => true,
10 | 'encoding' => true,
11 | 'single_blank_line_at_eof' => true,
12 | 'no_extra_blank_lines' => true,
13 | 'include' => true,
14 | 'blank_line_after_namespace' => true,
15 | 'not_operator_with_successor_space' => true,
16 | 'constant_case' => true,
17 | 'lowercase_keywords' => true,
18 | 'array_syntax' => ['syntax' => 'short'],
19 | 'no_unused_imports' => true
20 | ];
21 |
22 | $finder = PhpCsFixer\Finder::create()
23 | ->exclude('vendor')
24 | ->in(__DIR__);
25 |
26 | return (new PhpCsFixer\Config())
27 | ->setFinder($finder)
28 | ->setRules($fixers)
29 | ->setUsingCache(false);
--------------------------------------------------------------------------------
/CLA.md:
--------------------------------------------------------------------------------
1 | # Contributor License Agreement
2 |
3 | If you are an employee and have created the contribution as part of your employment,
4 | you need to have your employer approve this agreement.
5 | If you do not own the copyright in the entire work of authorship,
6 | any other author of the contribution also needs to sign this.
7 |
8 | ## Copyright License
9 |
10 | You hereby grant to Oneoff-tech UG, the maintainer of GeoServer PHP Client,
11 | a worldwide, royalty-free, non-exclusive, perpetual and irrevocable license,
12 | with the right to transfer an unlimited number of non-exclusive licenses
13 | or to grant sublicenses to third parties, under the copyright covering the contribution
14 | to use the contribution by all means, including, but not limited to:
15 |
16 | * publish the contribution,
17 | * modify the contribution,
18 | * prepare derivative works based upon or containing the contribution
19 | and/or to combine the contribution with other materials,
20 | * reproduce the contribution in original or modified form,
21 | * distribute, to make the contribution available to the public, display
22 | and publicly perform the contribution in original or modified form.
23 |
24 | ## Free Software Pledge
25 |
26 | We agree to irrevocably (sub)license the contribution
27 | or any materials containing, based on or derived from your contribution
28 | under the terms of any licenses
29 | the Free Software Foundation classifies as [Free Software licenses](https://www.gnu.org/licenses/license-list.html)
30 | and which are approved by the Open Source Initiative as [Open Source licenses](http://opensource.org/licenses).
31 | See the [LICENSE](/LICENSE.txt) file, for the current license used.
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute to GeoServer PHP Client
2 |
3 | This library is Free and Open Source Software. All Contributions to this project are most welcome, and can take many forms such as detailed bug reports, documentation, tests, features, and patches. Note that all contributions are managed by [OneOffTech](https://www.oneofftech.xyz).
4 |
5 | ## Bugs and Help
6 |
7 | GitHub [issues should only be created](https://github.com/OneOffTech/geoserver-client-php/issues/new) to log bugs or ask for help. Before posting to Github, please also search the existing issues to see if the bug has already been reported, and add any further details to the existing issue.
8 |
9 | ## Development
10 |
11 | Contributions are most easily managed via GitHub [pull requests](https://github.com/oneofftech/geoserver-client-php/pulls). To prepare one, [fork the GeoServer PHP Client](https://github.com/oneofftech/geoserver-client-php/fork) into your own GitHub repository. Then you'll be able to commit your work and submit pull requests. Once you are done, Github allows you to create a pull request and propose your changes to the original repository. Make sure you target your pull request to the `master` branch.
12 |
13 | The project follows the PSR-2 coding standard. Please run `./vendor/bin/php-cs-fixer fix` before wrapping everything up.
14 |
15 | ## Documentation
16 |
17 | All documentation is stored in the ["docs" directory of this repository](https://github.com/OneOffTech/geoserver-client-php/tree/master/docs). Any contribution on improving the documentation is highly appreciated and a good way to become a welcomed contributor.
18 |
19 | ## Contributor License Agreement
20 |
21 | The [Contributor License Agreement](./CLA.md) specifies the way how copyright of your contribution is handled. Please include in the comment on your pull request a statement like the following:
22 |
23 | > I'd like to contribute `feature X|bugfix Y|docs|something else` to GeoServer PHP Client. I confirm that my contributions to GeoServer PHP Client will be compatible with the GeoServer PHP Client Contributor License Agreement at the time of contribution.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/OneOffTech/geoserver-client-php/actions/workflows/run-tests.yml)
2 | 
3 |
4 | # GeoServer PHP Client
5 |
6 | This PHP library provides programmatic functions to access a [GeoServer](http://geoserver.org/).
7 |
8 | It is Free and Open Source Software. All contributions are most welcome. Learn more about [how to contribute](./CONTRIBUTING.md).
9 |
10 | #### Features
11 |
12 | * Obtain the [version](./docs/usage.md#get-the-geoserver-version) of the connected GeoServer instance
13 | * [Create workspace](./docs/usage.md#create-the-workspace) or retrieve existing workspace details
14 | * [Create datastores](./docs/usage.md#data-stores) and listing them
15 | * [Create coveragestores](./docs/usage.md#coverage-stores) and listing them
16 | * [Upload files](./docs/usage.md#uploading-geographic-files) in various [formats](#supported-file-formats)
17 | * [Manage styles](./docs/usage.md#styles) in SLD format
18 |
19 | For detailed information of each of the provided functions check out the [documentation on the usage](./docs/usage.md).
20 |
21 | #### Requirements
22 |
23 | * [PHP 7.4](http://www.php.net/) or above.
24 | * [GeoServer](http://geoserver.org/) 2.15.0 or above
25 |
26 | ## Installation
27 |
28 | The GeoServer PHP Client uses [Composer](http://getcomposer.org/) to manage its dependencies.
29 |
30 | ```bash
31 | composer require php-http/guzzle7-adapter guzzlehttp/psr7 http-interop/http-factory-guzzle oneofftech/geoserver-client-php
32 | ```
33 |
34 | For more information, please review the [documentation on the installation process](./docs/installation.md).
35 |
36 | ## Supported file formats
37 |
38 | The library handles:
39 |
40 | * `Shapefile`
41 | * `Shapefile` inside `zip` archive
42 | * `GeoTIFF`
43 | * `GeoPackage` format version 1.2 (can contain both vector and raster data, but will be reported as vector)
44 | * Styled Layer Descriptor `SLD` files for layer styles in XML format
45 |
46 | Read more on [supported file formats and encoding](./docs/supported-files.md).
47 |
48 | ## License
49 |
50 | This project is Free and Open Source Software, licensed under the [AGPL v3 license](./LICENSE.txt).
51 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # GeoServer client changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6 |
7 | This project adhere to Semantic Versioning.
8 |
9 | ## Unreleased
10 |
11 | ### Added
12 | ### Changed
13 | ### Deprecated
14 | ### Removed
15 | ### Fixed
16 | ### Security
17 |
18 | ## [0.4.1] - 2022-08-07
19 |
20 | ### Added
21 |
22 | - Automated testing against GeoServer 2.20.x and 2.21.x
23 |
24 | ### Changed
25 |
26 | - Ignore already existing errors while creating a workspace
27 |
28 | ## [0.4.0] - 2022-08-06
29 |
30 | ### Added
31 |
32 | - Support for PHP 8.0 and 8.1
33 | - Support for GeoServer 2.17.x
34 |
35 | ### Removed
36 |
37 | - Support for PHP 7.1, 7.2 and 7.3
38 |
39 | ## [0.3.0] - 2020-01-06
40 |
41 | ### Added
42 |
43 | - Support for PHP 7.4
44 | - Support for GeoServer 2.15.x
45 |
46 | ### Changed
47 |
48 | - Style upload original filename is not preserved anymore. The file name will be the same as the given name via `StyleFile->name()`
49 | (if not specified the default value is equal to the filename without the extension)
50 | - Style files are directly uploaded in the workspace without a first request to create an empty style
51 |
52 | ## [0.2.1] - 2020-01-02
53 |
54 | ### Fixed
55 |
56 | - Style format identification on PHP 7.2 and 7.3
57 |
58 | ## [0.2.0] - 2018-10-23
59 |
60 | ### Changed
61 |
62 | - File uploads to GeoServer now imposes a UTF-8 charset
63 |
64 | ## [0.1.0] - 2018-10-16
65 |
66 | ### Added
67 |
68 | - Connection to a Geoserver instance, with authentication support
69 | - Ability to identify Shapefile, GeoTiff and GeoPackage formats
70 | - Create workspace or retrieve existing workspace details
71 | - Create coverage stores and listing them
72 | - Create data stores and listing them
73 | - Upload files in various formats
74 | - Manage styles in SLD format
75 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oneofftech/geoserver-client-php",
3 | "description": "A GeoServer API client",
4 | "type": "library",
5 | "homepage": "https://github.com/oneofftech/geoserver-client-php",
6 | "license": "AGPL-3.0-only",
7 | "authors": [
8 | {
9 | "name": "Alessio Vertemati",
10 | "email": "alessio@oneofftech.xyz"
11 | }
12 | ],
13 |
14 | "support": {
15 | "issues": "https://github.com/oneofftech/geoserver-client-php/issues",
16 | "source": "https://github.com/oneofftech/geoserver-client-php"
17 | },
18 |
19 | "autoload": {
20 | "psr-4": {
21 | "OneOffTech\\GeoServer\\": "src/"
22 | }
23 | },
24 |
25 | "autoload-dev": {
26 | "psr-4": {
27 | "Tests\\": "tests/"
28 | },
29 |
30 | "files": [
31 | "tests/helpers.php"
32 | ]
33 | },
34 |
35 | "require": {
36 | "php": ">=7.4",
37 | "ext-fileinfo": "*",
38 | "ext-zip": "*",
39 | "jms/serializer": "^3.4",
40 | "php-http/client-implementation": "^1.0|^2.0",
41 | "php-http/message": "^1.6",
42 | "php-http/discovery": "^1.3",
43 | "doctrine/annotations": "^1.4",
44 | "doctrine/cache": "^1.6",
45 | "php-http/client-common": "^2.0"
46 | },
47 | "require-dev": {
48 | "phpunit/phpunit": "^9.5",
49 | "php-http/curl-client": "^2.0",
50 | "php-http/mock-client": "^1.0",
51 | "friendsofphp/php-cs-fixer": "^3.9",
52 | "guzzlehttp/psr7": "^1.6",
53 | "http-interop/http-factory-guzzle": "^1.0"
54 | },
55 | "extra": {
56 | "branch-alias" : {
57 | "dev-master": "0.4.0-dev"
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | The GeoServer client uses [Composer](http://getcomposer.org/) to manage its dependencies.
4 |
5 | ```bash
6 | composer require php-http/guzzle7-adapter guzzlehttp/psr7 http-interop/http-factory-guzzle oneofftech/geoserver-client-php
7 | ```
8 |
9 | The GeoServer client is not hard coupled to [Guzzle](https://github.com/guzzle/guzzle) or any other library that sends HTTP messages. It uses an abstraction called [HTTPlug](http://httplug.io/). This will give you the flexibilty to choose what PSR-7 implementation and HTTP client to use.
10 |
11 | ## Why requiring so many packages?
12 |
13 | GeoServer client has a dependency on the virtual package
14 | [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation) which requires to you install **an** adapter, but we do not care which one. That is an implementation detail in your application. We also need **a** PSR-7 implementation and **a** message factory.
15 |
16 | You do not have to use the `php-http/guzzle7-adapter` if you do not want to. You may use the `php-http/curl-client`. Read more about the virtual packages, why this is a good idea and about the flexibility it brings at the [HTTPlug docs](http://docs.php-http.org/en/latest/httplug/users.html).
17 |
--------------------------------------------------------------------------------
/docs/supported-files.md:
--------------------------------------------------------------------------------
1 | # Supported file formats
2 |
3 | The library is able to recognize:
4 |
5 | * `Shapefile`
6 | * `Shapefile` inside `zip` archive
7 | * `GeoTIFF`
8 | * `GeoPackage` format version 1.2 (can contain both vector and raster data, but will be reported as vector)
9 | * Styled Layer Descriptor `SLD` files for layer styles in XML format
10 |
11 | You can check if a file is supported using
12 |
13 | ```php
14 | use OneOffTech\GeoServer\GeoFile;
15 | use OneOffTech\GeoServer\StyleFile;
16 |
17 | $isSupported = GeoFile::isSupported($path);
18 | // => true/false
19 |
20 |
21 | // For style files, the support is available with the StyleFile class
22 | $isSupported = StyleFile::isSupported($path);
23 | // => true/false
24 | ```
25 |
26 | > The library supports only the file formats that can be uploaded to a GeoServer.
27 | > For example `Geojson`, `KML` and `GPX` are not supported out-of-the-box by GeoServer,
28 | > although plugins might be available for doing that
29 |
30 | # File Character Encoding
31 |
32 | GeoServer can deal with character encoding of layers/features, but tests highlighted
33 | a limited support when using UTF-8 for data/coverage store names.
34 |
35 | To prevent issues with UTF-8 features attributes, the client set the character encoding
36 | to UTF-8 when uploading a file. In this way GeoServer will consider UTF-8 as the
37 | character encoding for features/attributes contained in the file. This do not affect
38 | layer or store names.
39 |
40 | As by design decision the store name is the filename, or the assigned name when
41 | performing the upload, we highly reccomend to use ASCII characters for it.
42 | Use of UTF-8 encoded filenames (or store name) might prevent the client to retrieve the
43 | store/layer corresponding to the uploaded file.
--------------------------------------------------------------------------------
/docs/tests.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | The library is covered with unit and integration tests.
4 |
5 | ```
6 | vendor/bin/phpunit
7 | ```
8 |
9 | By default integration tests are not executed unless in the phpunit.xml file a GeoServer instance is specified.
10 |
11 | The `phpunit.xml.dist` define the `GEOSERVER_URL`, `GEOSERVER_USER`,
12 | `GEOSERVER_PASSWORD` for that purpose.
13 | If you want you can copy `phpunit.xml.dist` to `phpunit.xml` and edit those variables in place
14 | or define them in your environment variables.
15 |
16 | ```xml
17 |
18 |
19 |
20 | ```
21 |
22 | If you don't have a GeoServer instance to trash there is a `docker-compose.yml` file that, with
23 | the help of Docker and the [kartoza/geoserver image](https://hub.docker.com/r/kartoza/geoserver/),
24 | creates a running GeoServer instance on port 8600.
25 |
26 | > Be aware that the Kartoza GeoServer image requires [4GB of RAM](https://github.com/kartoza/docker-geoserver/blob/master/Dockerfile#L23-L25) to run
27 |
28 | ```bash
29 | docker-compose -f ./tests/docker-compose.yml up -d
30 | # here better to wait for the full startup of the geoserver
31 | vendor/bin/phpunit
32 | ```
33 |
34 | **Notes on testing files**
35 |
36 | - The GeoPackage testing file was copied from [github.com/ngageoint/geopackage-js](https://github.com/ngageoint/geopackage-js/blob/master/test/fixtures/rivers.gpkg)
37 |
--------------------------------------------------------------------------------
/docs/usage.md:
--------------------------------------------------------------------------------
1 |
2 | # Usage
3 |
4 | ## Creating a client
5 |
6 | To create a GeoServer client use the `GeoServer::build` method.
7 | It will give you back a configured client instance.
8 |
9 | As of now the client can handle 1 workspace at time by design.
10 |
11 | ```php
12 | use OneOffTech\GeoServer\GeoServer;
13 | use OneOffTech\GeoServer\Auth\Authentication;
14 |
15 | /**
16 | * Geoserver URL
17 | */
18 | $url = 'http://localhost:8600/geoserver/';
19 |
20 | /**
21 | * Define a workspace to use
22 | */
23 | $workspace = 'your-workspace';
24 |
25 | $authentication = new Authentication('geoserver_user', 'geoserver_password');
26 |
27 | $geoserver = GeoServer::build($url, $workspace, $authentication);
28 | ```
29 |
30 | ## Get the GeoServer version
31 |
32 | Once you have a client instance, you can verify the GeoServer version number using `version()`
33 |
34 | ```php
35 | // assuming to have a GeoServer instance in the $geoserver variable
36 | $version = $geoserver->version();
37 | // => 2.13.0
38 | ```
39 |
40 | ## Create the workspace
41 |
42 | The client can create the configured workspace if not available.
43 | To do so call `createWorkspace()`:
44 |
45 | ```php
46 | $workspace = $geoserver->createWorkspace();
47 | // => \OneOffTech\GeoServer\Models\Workspace
48 | ```
49 |
50 | In case the creation goes well or the workspace already exists, a `Workspace` instance is returned.
51 |
52 | ## Retrieve the workspace details
53 |
54 | The `workspace()` method retrieve the details of the configured workspace
55 |
56 | ```php
57 | $workspace = $geoserver->workspace();
58 | // => \OneOffTech\GeoServer\Models\Workspace
59 | ```
60 |
61 |
62 | ## Data stores
63 |
64 | A datastore is a container of vector data. A workspace can have multiple data stores.
65 |
66 | You can retrieve all defined datastores using the `datastores()` method:
67 |
68 | ```php
69 | $datastores = $geoserver->datastores();
70 | // => array of \OneOffTech\GeoServer\Models\DataStore
71 | ```
72 |
73 | Or retrieve a datastores by name:
74 |
75 | ```php
76 | $datastore = $geoserver->datastore($name);
77 | // => \OneOffTech\GeoServer\Models\DataStore
78 | ```
79 |
80 | You cal also delete a data store via:
81 |
82 | ```php
83 | $result = $geoserver->deleteDatastore($name);
84 | // => true || false
85 | ```
86 |
87 | ## Coverage stores
88 |
89 | A coveragestore is a container of raster data. A workspace can have multiple coverage stores.
90 |
91 | You can retrieve all defined coverage stores using the `coveragestores()` method:
92 |
93 | ```php
94 | $coveragestores = $geoserver->coveragestores();
95 | // => array of \OneOffTech\GeoServer\Models\CoverageStore
96 | ```
97 |
98 | Or retrieve a coveragestores by name:
99 |
100 | ```php
101 | $coveragestore = $geoserver->coveragestore($name);
102 | // => \OneOffTech\GeoServer\Models\CoverageStore
103 | ```
104 |
105 | You cal also delete a coverage store via:
106 |
107 | ```php
108 | $result = $geoserver->deleteCoveragestore($name);
109 | // => true || false
110 | ```
111 |
112 | ## Upload and Delete geographic files
113 |
114 | ### Uploading geographic files
115 |
116 | Uploading a file to a GeoServer instance is done via the `upload` method.
117 |
118 | The client recognizes the format and create a correct store type, e.g. shapefiles lead to a
119 | datastore creation. To do so the file path must be wrapped in a `GeoFile` object.
120 |
121 | > See [Supported files](./supported-files.md) for knowing what the library can handle
122 |
123 | ```php
124 | use OneOffTech\GeoServer\GeoFile;
125 |
126 | $file = GeoFile::load('path/to/shapefile.shp');
127 | // => OneOffTech\GeoServer\GeoFile{
128 | // + mimeType
129 | // + extension
130 | // + format
131 | // + type
132 | // + name
133 | // + originalName
134 | // }
135 | // it will throw OneOffTech\GeoServer\Exception\UnsupportedFileException in case the file cannot be recognized
136 | ```
137 |
138 | From a GeoFile instance the file `mimeType`, `format` and `type` (`vector` or `raster`, `OneOffTech\GeoServer\GeoType`) can be discovered.
139 |
140 | The `format` is used to specify the content of the file, as in some cases Geographic files do not have a standard mime type.
141 | For example a Shapefile mime type is `application/octet-stream`, which means a binary file.
142 |
143 | The `originalName` attribute contains the original filename. By default `originalName` and `name` are equals,
144 | but the name can be changed, by using the `name($value)` method. The `name` will be used as the store name inside GeoServer.
145 |
146 | Once obtained a `GeoFile` instance, the method `upload()` can be used to really upload the file to the GeoServer:
147 |
148 | ```php
149 | use OneOffTech\GeoServer\GeoFile;
150 |
151 | $file = GeoFile::load('path/to/shapefile.shp');
152 |
153 | $feature = $geoserver->upload($file);
154 | // OneOffTech\GeoServer\Models\Resource
155 | ```
156 |
157 | > For file character encoding please refer to [File Character Encoding](./supported-files.md#file-character-encoding)
158 |
159 | Once uploaded, the return value will be an instance of the `OneOffTech\GeoServer\Models\Resource`.
160 | It contains the details extracted by the GeoServer, like the bounding box.
161 |
162 | `OneOffTech\GeoServer\Models\Resource` has two sub-classes:
163 |
164 | - `OneOffTech\GeoServer\Models\Feature` A feature type is a vector based spatial resource or data set that originates from a data store
165 | - `OneOffTech\GeoServer\Models\Coverage` A coverage is a raster data set which originates from a coverage store.
166 |
167 | ### Verify if a geographic files exists
168 |
169 | As a helper method, given a `GeoFile` instance, is possible to verify that a corresponding Feature or Coverage is present.
170 |
171 | ```php
172 | use OneOffTech\GeoServer\GeoFile;
173 |
174 | $file = GeoFile::load('path/to/shapefile.shp');
175 |
176 | $exists = $geoserver->exist($file);
177 | // true || false
178 | ```
179 |
180 | > The identification currently uses the name assigned to the GeoFile
181 |
182 | ### Delete a geographic file
183 |
184 | As a helper method, given a `GeoFile` instance, is possible to delete the corresponding Feature or Coverage in the Geoserver.
185 |
186 | ```php
187 | use OneOffTech\GeoServer\GeoFile;
188 |
189 | $file = GeoFile::load('path/to/shapefile.shp');
190 |
191 | $removed = $geoserver->remove($file);
192 | // true || false
193 | ```
194 |
195 | > The identification currently uses the name assigned to the GeoFile
196 |
197 | ## Styles
198 |
199 | The client can upload, retrieve and delete styles defined within the configured workspace
200 |
201 | ### Upload style file
202 |
203 | To upload a style, a `StyleFile` instance representing the file on disk is required.
204 | Once the instance is obtained use the `uploadStyle` method on a GeoServer client instance.
205 |
206 | The style will be uploaded as part of the workspace styles.
207 |
208 | ```php
209 | use OneOffTech\GeoServer\StyleFile;
210 |
211 | $file = StyleFile::from('/path/to/style.sld');
212 | // => OneOffTech\GeoServer\StyleFile{
213 | // + name
214 | // + originalName
215 | // + mimeType
216 | // + extension
217 | // }
218 | // it will throw OneOffTech\GeoServer\Exception\UnsupportedFileException in case the file cannot be recognized
219 |
220 | // You can change the style name before uploading it to avoid collision. By default the filename will be used.
221 | $file->name('my_custom_style');
222 |
223 | $style = $geoserver->uploadStyle($file);
224 | // => OneOffTech\GeoServer\Models\Style
225 | ```
226 |
227 | ### Retrieve a style
228 |
229 | The client let you retrieve a style by its name
230 |
231 | ```php
232 | $style = $geoserver->style('style_name');
233 | // => OneOffTech\GeoServer\Models\Style
234 | ```
235 |
236 | > The name must be equal to the one given for the upload.
237 | > It might not be the file name
238 |
239 | ### Retrieve all styles
240 |
241 | You can also retrieve all styles defined in the workspace
242 |
243 | ```php
244 | $styles = $geoserver->styles();
245 | // => array of OneOffTech\GeoServer\Models\Style
246 | ```
247 |
248 | ### Remove a style
249 |
250 | Style removal is performed by giving the style name to the `removeStyle` method.
251 | The method will return the details of the deleted style.
252 |
253 | ```php
254 | $style = $geoserver->removeStyle('style_name');
255 | // => OneOffTech\GeoServer\Models\Style
256 | ```
257 |
258 | > the `$style->exists` attribute will be set to `false` after deletion
259 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src
6 |
7 |
8 |
9 |
10 | ./tests/Integration
11 |
12 |
13 | ./tests/Unit
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Auth/Authentication.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Auth;
23 |
24 | use Psr\Http\Message\RequestInterface;
25 | use Http\Message\Authentication\BasicAuth;
26 | use OneOffTech\GeoServer\Contracts\Authentication as AuthenticationContract;
27 |
28 | final class Authentication implements AuthenticationContract
29 | {
30 | /**
31 | * @var Http\Message\Authentication\BasicAuth
32 | */
33 | private $auth;
34 |
35 | /**
36 | * @param string $app_secret
37 | * @param string $app_url
38 | */
39 | public function __construct($username, $password)
40 | {
41 | $this->auth = new BasicAuth($username, $password);
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function authenticate(RequestInterface $request)
48 | {
49 | return $this->auth->authenticate($request);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Auth/NullAuthentication.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Auth;
23 |
24 | use Psr\Http\Message\RequestInterface;
25 | use OneOffTech\GeoServer\Contracts\Authentication as AuthenticationContract;
26 |
27 | final class NullAuthentication implements AuthenticationContract
28 | {
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function authenticate(RequestInterface $request)
34 | {
35 | return $request;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Contracts/Authentication.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Contracts;
23 |
24 | use Http\Message\Authentication as AuthenticationContract;
25 |
26 | interface Authentication extends AuthenticationContract
27 | {
28 | }
29 |
--------------------------------------------------------------------------------
/src/Contracts/FileReader.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Contracts;
23 |
24 | use Exception;
25 |
26 | abstract class FileReader
27 | {
28 | protected static function openFile($path)
29 | {
30 | if (! (is_readable($path) && is_file($path))) {
31 | throw new Exception("File [$path] not readable");
32 | }
33 | $handle = fopen($path, 'r');
34 | if (! $handle) {
35 | throw new Exception("Unable to read [$path] as binary file");
36 | }
37 |
38 | return $handle;
39 | }
40 |
41 | protected static function openFileBinary($path, $position = 0)
42 | {
43 | if (! (is_readable($path) && is_file($path))) {
44 | throw new Exception("File [$path] not readable");
45 | }
46 | $handle = fopen($path, 'rb');
47 | if (! $handle) {
48 | throw new Exception("Unable to read [$path] as binary file");
49 | }
50 |
51 | if ($position > 0) {
52 | fseek($handle, $position, SEEK_SET);
53 | }
54 | return $handle;
55 | }
56 |
57 | protected static function closeFile($handle)
58 | {
59 | if ($handle) {
60 | fclose($handle);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Contracts/Model.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Contracts;
23 |
24 | abstract class Model
25 | {
26 | }
27 |
--------------------------------------------------------------------------------
/src/Exception/DeserializationException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Deserialization exception
26 | *
27 | * Thrown when the JSON response from the server could not be deserialized into an object
28 | */
29 | class DeserializationException extends GeoServerClientException
30 | {
31 | /**
32 | *
33 | * @param string $message
34 | * @param string $json the original JSON that raised the deserialization error
35 | */
36 | public function __construct($message, $json)
37 | {
38 | parent::__construct("$message. $json");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Exception/ErrorResponseException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Error response Exception
26 | *
27 | * Thrown when the server respond with a failure
28 | */
29 | class ErrorResponseException extends GeoServerClientException
30 | {
31 | /**
32 | * @var mixed
33 | */
34 | private $data;
35 |
36 | /**
37 | * ErrorResponseException constructor.
38 | *
39 | * @param string $message
40 | * @param int $code
41 | * @param mixed $data
42 | */
43 | public function __construct($message, $code, $data)
44 | {
45 | parent::__construct($message, $code);
46 | $this->data = $data;
47 | }
48 |
49 | /**
50 | * @return mixed
51 | */
52 | public function getData()
53 | {
54 | return $this->data;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Exception/GeoServerClientException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | use Exception;
25 |
26 | class GeoServerClientException extends Exception
27 | {
28 | }
29 |
--------------------------------------------------------------------------------
/src/Exception/SerializationException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Serialization exception
26 | *
27 | * Thrown when an object cannot be serialized into JSON
28 | */
29 | class SerializationException extends GeoServerClientException
30 | {
31 | public function __construct($message)
32 | {
33 | parent::__construct($message);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Exception/StoreNotFoundException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Raised when a store cannot be found on the specific geoserver instance
26 | */
27 | class StoreNotFoundException extends GeoServerClientException
28 | {
29 | /**
30 | *
31 | * @param string $message
32 | */
33 | public function __construct($message)
34 | {
35 | parent::__construct($message, 404);
36 | }
37 |
38 | /**
39 | * Create a store not found exception for a data store
40 | *
41 | * @param string $name the data store name
42 | * @return StoreNotFoundException
43 | */
44 | public static function datastore($name)
45 | {
46 | return new self("Data store [$name] not found.");
47 | }
48 |
49 | /**
50 | * Create a store not found exception for a coverage store
51 | *
52 | * @param string $name the coverage store name
53 | * @return StoreNotFoundException
54 | */
55 | public static function coveragestore($name)
56 | {
57 | return new self("Coverage store [$name] not found.");
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Exception/StyleAlreadyExistsException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Raised when a style cannot be uploaded as a previous style with the same name already exists
26 | */
27 | class StyleAlreadyExistsException extends GeoServerClientException
28 | {
29 | /**
30 | *
31 | * @param string $message
32 | */
33 | public function __construct($message)
34 | {
35 | parent::__construct($message, 409);
36 | }
37 |
38 | /**
39 | * Create a style already exists exception for a given style
40 | *
41 | * @param string $name the style name
42 | * @param string $workspace the workspace that contains the style
43 | * @return StyleAlreadyExistsException
44 | */
45 | public static function style($name, $workspace)
46 | {
47 | return new self("A style named [$name] already exists in [$workspace].");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Exception/StyleNotFoundException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | /**
25 | * Raised when a style cannot be found on the specific geoserver instance
26 | */
27 | class StyleNotFoundException extends GeoServerClientException
28 | {
29 | /**
30 | *
31 | * @param string $message
32 | */
33 | public function __construct($message)
34 | {
35 | parent::__construct($message, 404);
36 | }
37 |
38 | /**
39 | * Create a style not found exception for a given style
40 | *
41 | * @param string $name the style name
42 | * @param string $workspace the workspace that was expecting to contain the style
43 | * @return StyleNotFoundException
44 | */
45 | public static function style($name, $workspace)
46 | {
47 | return new self("The style [$name] cannot be found in [$workspace].");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Exception/UnsupportedFileException.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Exception;
23 |
24 | class UnsupportedFileException extends GeoServerClientException
25 | {
26 | /**
27 | * @param string $path
28 | * @param string $format
29 | * @param string $supportedFormats
30 | */
31 | public function __construct($path, $format, $supportedFormats)
32 | {
33 | parent::__construct("The given file [$path] is not supported. Found [$format] expected [$supportedFormats]");
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/GeoFile.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer;
23 |
24 | use SplFileInfo;
25 |
26 | use OneOffTech\GeoServer\Support\TypeResolver;
27 | use OneOffTech\GeoServer\Exception\UnsupportedFileException;
28 |
29 | /**
30 | *
31 | */
32 | class GeoFile
33 | {
34 | protected $file;
35 |
36 | protected $name;
37 |
38 | protected $originalName;
39 |
40 | protected $mimeType;
41 |
42 | /**
43 | * The geodata format
44 | */
45 | protected $format;
46 |
47 | protected $extension;
48 |
49 | /**
50 | * The extension as required by GeoServer
51 | *
52 | * e.g. for a geo tiff file the extension must be .geotiff
53 | */
54 | protected $normalizedExtension;
55 |
56 | /**
57 | * The mime type as required by GeoServer
58 | *
59 | * e.g. for a geo tiff file the mime type appears to be "geotif/geotiff",
60 | * as found in https://gis.stackexchange.com/questions/218162/creating-coveragestore-geotiff-using-rest-api
61 | */
62 | protected $normalizedMimeType;
63 |
64 | /**
65 | * The type of the geodata (vector or raster)
66 | */
67 | protected $type;
68 |
69 | public function __construct($path)
70 | {
71 | $this->file = new SplFileInfo($path);
72 |
73 | list($format, $type, $mimeType) = TypeResolver::identify($path);
74 |
75 | if (! in_array($format, TypeResolver::supportedFormats())) {
76 | throw new UnsupportedFileException($path, $format, join(', ', TypeResolver::supportedFormats()));
77 | }
78 |
79 | $this->mimeType = $mimeType;
80 |
81 | $this->extension = $this->file->getExtension();
82 |
83 | $this->normalizedExtension = TypeResolver::normalizedExtensionFromFormat($format) ?? $this->extension;
84 |
85 | $this->normalizedMimeType = TypeResolver::normalizedMimeTypeFromFormat($format) ?? $mimeType;
86 |
87 | $this->format = $format;
88 |
89 | $this->type = $type;
90 |
91 | $this->name = $this->originalName = $this->file->getFileName();
92 | }
93 |
94 | /**
95 | * Set the data name.
96 | * It will be used for store name
97 | *
98 | * @param string $value
99 | * @return Data
100 | */
101 | public function name($value)
102 | {
103 | $this->name = $value;
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * Tell if the name attribute is different from the original filename
110 | *
111 | * @return bool
112 | */
113 | public function wasRenamed()
114 | {
115 | return $this->name !== $this->originalName;
116 | }
117 |
118 | /**
119 | * Get the path to the file
120 | *
121 | * @return string
122 | */
123 | public function path()
124 | {
125 | return $this->file->getRealPath();
126 | }
127 |
128 | public function content()
129 | {
130 | return file_get_contents($this->file->getRealPath());
131 | }
132 |
133 | public function __get($property)
134 | {
135 | return $this->$property;
136 | }
137 |
138 | /**
139 | * Copy the GeoFile content into a temporary folder and return the new GeoFile instance
140 | *
141 | * Please note that the temporary file is not disposed automatically
142 | *
143 | * @param string $temporaryFolder
144 | * @return GeoFile
145 | */
146 | public function copy($temporaryFolder = null)
147 | {
148 | $tmpfilename = tempnam($temporaryFolder ?? sys_get_temp_dir(), $this->name);
149 | $handle = fopen($tmpfilename, "w+b");
150 | fwrite($handle, $this->content());
151 | fclose($handle);
152 |
153 | return GeoFile::from($tmpfilename)->name($this->name);
154 | }
155 |
156 | /**
157 | * Create a Geo file instance from a given file path
158 | *
159 | * @param string $path
160 | * @return Data
161 | * @throws UnsupportedFileException if file is not supported
162 | */
163 | public static function from($path)
164 | {
165 | return new static($path);
166 | }
167 | public static function load($path)
168 | {
169 | return static::from($path);
170 | }
171 |
172 | /**
173 | * Check if the specified file is a valid geographical file
174 | *
175 | * @param string $path
176 | * @return bool
177 | */
178 | public static function isSupported(string $path)
179 | {
180 | list($format, $type, $mimeType) = TypeResolver::identify($path);
181 | return in_array($format, TypeResolver::supportedFormats());
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/GeoFormat.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer;
23 |
24 | /**
25 | * Formats of geographical files supported by GeoServer
26 | */
27 | final class GeoFormat
28 | {
29 | const SHAPEFILE = "shapefile";
30 | const SHAPEFILE_ZIP = "shapefile_zip";
31 | const GEOTIFF = "geotiff";
32 | const SLD = "SLD";
33 | const GEOPACKAGE = "geopackage";
34 | }
35 |
--------------------------------------------------------------------------------
/src/GeoType.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer;
23 |
24 | /**
25 | * The geographical data type: vector or raster
26 | */
27 | final class GeoType
28 | {
29 | /**
30 | * Vector data
31 | */
32 | const VECTOR = "vector";
33 |
34 | /**
35 | * Raster data
36 | */
37 | const RASTER = "raster";
38 |
39 | /**
40 | * Get the GeoServer store for the specified type
41 | *
42 | * @param string $type
43 | * @return string
44 | */
45 | public static function storeFor($type)
46 | {
47 | return $type === 'vector' ? 'datastores' : 'coveragestores';
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Http/InteractsWithHttp.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http;
23 |
24 | use Exception;
25 | use Throwable;
26 | use Http\Client\HttpClient;
27 | use Http\Message\MessageFactory;
28 | use Psr\Http\Message\ResponseInterface;
29 | use OneOffTech\GeoServer\Support\ImageResponse;
30 | use OneOffTech\GeoServer\Exception\ErrorResponseException;
31 | use OneOffTech\GeoServer\Exception\SerializationException;
32 | use OneOffTech\GeoServer\Exception\DeserializationException;
33 |
34 | trait InteractsWithHttp
35 | {
36 |
37 | /**
38 | * @var HttpClient
39 | */
40 | private $httpClient;
41 |
42 | /**
43 | * @var MessageFactory
44 | */
45 | private $messageFactory;
46 |
47 | /**
48 | * @var Serializer
49 | */
50 | private $serializer;
51 |
52 | /**
53 | * @param ResponseInterface $response
54 | * @throws ErrorResponseException
55 | */
56 | private function checkResponseError(ResponseInterface $response)
57 | {
58 | $responseBody = $response->getBody();
59 | $contentTypeHeader = $response->getHeader('Content-Type');
60 | $contentType = ! empty($contentTypeHeader) ? $contentTypeHeader[0] : '';
61 | if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 201 && $response->getStatusCode() !== 204) {
62 | if ($response->getStatusCode() === 500 && strpos($contentType, 'text/html')!== false) {
63 | $reason = substr((string)$responseBody, 0, 500);
64 |
65 | throw new ErrorResponseException($reason, $response->getStatusCode(), (string)$responseBody);
66 | }
67 | throw new ErrorResponseException(! empty($response->getReasonPhrase()) ? $response->getReasonPhrase() : 'There was a problem in fulfilling your request.', $response->getStatusCode(), (string)$responseBody);
68 | }
69 | }
70 |
71 | /**
72 | * Deserialize a JSON string into the given class instance
73 | *
74 | * @param string $json the JSON string to deserialized
75 | * @param string $class the fully qualified class name
76 | * @return object instance of $class
77 | * @throws DeserializationException if an error occurs during the deserialization
78 | */
79 | protected function deserialize($response, $class = null)
80 | {
81 | if (is_null($class)) {
82 | return json_decode($response->getBody());
83 | }
84 |
85 | try {
86 | return $this->serializer->deserialize($response->getBody(), $class, 'json');
87 | } catch (JMSException $ex) {
88 | throw new DeserializationException($ex->getMessage(), (string)$response->getBody());
89 | }
90 | }
91 |
92 | protected function serialize($object)
93 | {
94 | try {
95 | return $this->serializer->serialize($object, 'json');
96 | } catch (Throwable $ex) {
97 | throw new SerializationException($ex->getMessage());
98 | } catch (Exception $ex) {
99 | throw new SerializationException($ex->getMessage());
100 | }
101 | }
102 |
103 | /**
104 | * Handle and send the request to the given route.
105 | *
106 | * @param RPCRequest $request The request data
107 | * @param string $route The API route
108 | *
109 | * @return ResponseInterface
110 | * @throws SerializationException
111 | */
112 | private function handleRequest($request)
113 | {
114 | $response = $this->httpClient->sendRequest($request);
115 |
116 | $this->checkResponseError($response);
117 |
118 | return $response;
119 | }
120 |
121 | protected function get($route, $class = null)
122 | {
123 | $request = $this->messageFactory->createRequest('GET', $route, []);
124 |
125 | $response = $this->handleRequest($request);
126 |
127 | return $this->deserialize($response, $class);
128 | }
129 |
130 | protected function post($route, $data, $class = null)
131 | {
132 | $request = $this->messageFactory->createRequest('POST', $route, [], $this->serialize($data));
133 |
134 | $response = $this->handleRequest($request);
135 |
136 | return $this->deserialize($response, $class);
137 | }
138 |
139 | protected function put($route, $data, $class = null)
140 | {
141 | $request = $this->messageFactory->createRequest('PUT', $route, [], $this->serialize($data));
142 |
143 | $response = $this->handleRequest($request);
144 |
145 | return $this->deserialize($response, $class);
146 | }
147 |
148 | protected function putFile($route, $data, $class = null)
149 | {
150 | $request = $this->messageFactory->createRequest('PUT', $route, ['Content-Type' => $data->normalizedMimeType], $data->content());
151 |
152 | $response = $this->handleRequest($request);
153 |
154 | return $this->deserialize($response, $class);
155 | }
156 |
157 | protected function postFile($route, $data, $class = null)
158 | {
159 | $request = $this->messageFactory->createRequest('POST', $route, ['Content-Type' => $data->normalizedMimeType], $data->content());
160 |
161 | $response = $this->handleRequest($request);
162 |
163 | return $this->deserialize($response, $class);
164 | }
165 |
166 | protected function delete($route, $class = null)
167 | {
168 | $request = $this->messageFactory->createRequest('DELETE', $route, []);
169 |
170 | $response = $this->handleRequest($request);
171 |
172 | return $this->deserialize($response, $class);
173 | }
174 |
175 | protected function getImage($route)
176 | {
177 | $request = $this->messageFactory->createRequest('GET', $route, []);
178 |
179 | $response = $this->handleRequest($request);
180 |
181 | $contentTypeHeader = $response->getHeader('Content-Type');
182 | $contentType = ! empty($contentTypeHeader) ? $contentTypeHeader[0] : '';
183 |
184 | if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 201 && $response->getStatusCode() !== 204) {
185 | throw new ErrorResponseException(! empty($response->getReasonPhrase()) ? $response->getReasonPhrase() : 'There was a problem in fulfilling your request.', $response->getStatusCode(), (string)$responseBody);
186 | }
187 |
188 | if (strpos($contentType, 'image') === false) {
189 | throw new ErrorResponseException("Expected image response, but got [$contentType]", $response->getStatusCode(), (string)$response->getBody());
190 | }
191 |
192 | return ImageResponse::from($response);
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/Http/ResponseHelper.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http;
23 |
24 | class ResponseHelper
25 | {
26 | /**
27 | * Check if an array is associative or index based.
28 | *
29 | * An array is considered associative if all keys are strings.
30 | * Empty array or null value are not considered associative
31 | *
32 | * @param array $array the array to check
33 | * @return true if array is associative
34 | */
35 | public static function isAssociativeArray($array)
36 | {
37 | if (empty($array) || ! is_array($array)) {
38 | return false;
39 | }
40 |
41 | $keys = array_keys($array);
42 | foreach ($keys as $key) {
43 | if (is_int($key)) {
44 | return false;
45 | }
46 | }
47 | return true;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Http/Responses/CoverageResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 | use OneOffTech\GeoServer\Models\Coverage;
26 |
27 | class CoverageResponse
28 | {
29 | /**
30 | * @var \OneOffTech\GeoServer\Models\Coverage
31 | * @JMS\Type("OneOffTech\GeoServer\Models\Coverage")
32 | * @JMS\SerializedName("coverage")
33 | */
34 | public $coverage;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Http/Responses/CoverageStoreResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class CoverageStoreResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\CoverageStore
30 | * @JMS\Type("OneOffTech\GeoServer\Models\CoverageStore")
31 | * @JMS\SerializedName("coverageStore")
32 | */
33 | public $store;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Responses/CoverageStoresResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class CoverageStoresResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\CoverageStore[]
30 | * @JMS\Type("array")
31 | * @JMS\SerializedName("coverageStores")
32 | */
33 | public $stores;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Responses/DataStoreResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class DataStoreResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\DataStore
30 | * @JMS\Type("array")
31 | * @JMS\SerializedName("dataStores")
32 | */
33 | public $dataStores;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Responses/FeatureResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class FeatureResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\Feature
30 | * @JMS\Type("OneOffTech\GeoServer\Models\Feature")
31 | * @JMS\SerializedName("featureType")
32 | */
33 | public $feature;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Responses/StylesResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class StylesResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\Style[]
30 | * @JMS\Type("array")
31 | * @JMS\SerializedName("styles")
32 | */
33 | public $styles;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Http/Responses/WorkspaceResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http\Responses;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class WorkspaceResponse
27 | {
28 | /**
29 | * @var \OneOffTech\GeoServer\Models\Workspace
30 | * @JMS\Type("OneOffTech\GeoServer\Models\Workspace")
31 | */
32 | public $workspace;
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Routes.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Http;
23 |
24 | use OneOffTech\GeoServer\Support\WmsOptions;
25 |
26 | /**
27 | * Helper class for managing URL creation
28 | *
29 | * @internal
30 | */
31 | final class Routes
32 | {
33 | /** @var string */
34 | private $baseUrl;
35 |
36 | /**
37 | * @param string $baseUrl
38 | */
39 | public function __construct($baseUrl)
40 | {
41 | $this->baseUrl = trim(trim($baseUrl), '/');
42 | }
43 |
44 | /**
45 | * Helper for creating GeoServer Rest URLs
46 | *
47 | * @param string $endpoint the endpoint to attach to the base URL
48 | * @return string
49 | */
50 | public function url($endpoint)
51 | {
52 | return sprintf("%s/rest/%s", $this->baseUrl, $endpoint);
53 | }
54 |
55 | /**
56 | * Web Map Service (WMS) service url helper
57 | *
58 | * Create the URL to the WMS service based on the specified options
59 | *
60 | * @param string $workspace The workspace the URL will refer to
61 | * @param \OneOffTech\GeoServer\Support\WmsOptions $options The WMS service options
62 | * @return string
63 | */
64 | public function wms($workspace, WmsOptions $options)
65 | {
66 | return sprintf(
67 | "%s/%s/wms?service=WMS&%s",
68 | $this->baseUrl,
69 | $workspace,
70 | $options->toUrlParameters()
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Models/BoundingBox.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class BoundingBox
27 | {
28 | /**
29 | *
30 | * @var float
31 | * @JMS\Type("float")
32 | * @JMS\SerializedName("minx")
33 | */
34 | public $minX;
35 |
36 | /**
37 | *
38 | * @var float
39 | * @JMS\Type("float")
40 | * @JMS\SerializedName("miny")
41 | */
42 | public $minY;
43 |
44 | /**
45 | *
46 | * @var float
47 | * @JMS\Type("float")
48 | * @JMS\SerializedName("maxx")
49 | */
50 | public $maxX;
51 |
52 | /**
53 | *
54 | * @var float
55 | * @JMS\Type("float")
56 | * @JMS\SerializedName("maxy")
57 | */
58 | public $maxY;
59 |
60 | /**
61 | * The coordinate system in which the bounding box values are expressed
62 | *
63 | * @var string
64 | * @JMS\Type("string")
65 | */
66 | public $crs = null;
67 |
68 | public function toArray()
69 | {
70 | return [$this->minX, $this->minY, $this->maxX, $this->maxY];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Models/Coverage.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use OneOffTech\GeoServer\GeoType;
25 | use JMS\Serializer\Annotation as JMS;
26 |
27 | /**
28 | * A coverage is a raster data set which originates from a coverage store.
29 | */
30 | final class Coverage extends Resource
31 | {
32 |
33 | /**
34 | *
35 | * @var string
36 | * @JMS\Type("string")
37 | * @JMS\SerializedName("nativeFormat")
38 | */
39 | public $nativeFormat;
40 |
41 | /**
42 | * @var array
43 | * @JMS\Type("array")
44 | * @JMS\SerializedName("interpolationMethods")
45 | */
46 | public $interpolationMethods;
47 |
48 | /**
49 | * @var array
50 | * @JMS\Type("array")
51 | */
52 | public $nativeCRS;
53 |
54 | /**
55 | *
56 | * @var array
57 | * @JMS\Type("array")
58 | */
59 | public $dimensions;
60 |
61 | /**
62 | * Contains information about how to translate from the raster plan to a coordinate reference system
63 | * @var array
64 | * @JMS\Type("array")
65 | */
66 | public $grid;
67 |
68 | public function type()
69 | {
70 | return GeoType::RASTER;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Models/CoverageStore.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class CoverageStore extends Store
27 | {
28 | /**
29 | * Type of coverage store
30 | *
31 | * @var string
32 | * @JMS\Type("string")
33 | */
34 | public $type;
35 |
36 | /**
37 | * Location of the raster data source (often, but not necessarily, a file).
38 | * Can be relative to the data directory.
39 | *
40 | * @var string
41 | * @JMS\Type("string")
42 | */
43 | public $url;
44 |
45 | /**
46 | * The link to the coverages contained in this store
47 | *
48 | * @var array
49 | * @JMS\Type("array")
50 | */
51 | public $coverages;
52 | }
53 |
--------------------------------------------------------------------------------
/src/Models/DataStore.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 |
26 | class DataStore extends Store
27 | {
28 | /**
29 | *
30 | * @var string
31 | * @JMS\Type("string")
32 | * @JMS\SerializedName("featureTypes")
33 | */
34 | public $featureTypes;
35 |
36 | /**
37 | *
38 | * @var string
39 | * @JMS\Type("array")
40 | * @JMS\SerializedName("connectionParameters")
41 | */
42 | public $connectionParameters;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Models/Feature.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use OneOffTech\GeoServer\GeoType;
25 | use JMS\Serializer\Annotation as JMS;
26 |
27 | /**
28 | * A feature type is a vector based spatial resource or data set that originates from a data store
29 | */
30 | final class Feature extends Resource
31 | {
32 | /**
33 | * The identifier of coordinate reference system of the resource.
34 | * @var string
35 | * @JMS\Type("string")
36 | */
37 | public $srs;
38 |
39 | /**
40 | *
41 | * @var bool
42 | * @JMS\Type("boolean")
43 | * @JMS\SerializedName("overridingServiceSRS")
44 | */
45 | public $overridingServiceSRS = false;
46 |
47 | public function type()
48 | {
49 | return GeoType::VECTOR;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Models/Resource.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 | use OneOffTech\GeoServer\Contracts\Model;
26 |
27 | /**
28 | * A resource that describe both a Coverage or a Feature.
29 | */
30 | abstract class Resource extends Model
31 | {
32 | /**
33 | * The name of the resource.
34 | *
35 | * @var string
36 | * @JMS\Type("string")
37 | */
38 | public $name;
39 |
40 | /**
41 | * The native name of the resource.
42 | *
43 | * This name corresponds to the physical resource that is
44 | * derived from -- a shapefile name, a database table,...
45 | *
46 | * @var string
47 | * @JMS\Type("string")
48 | * @JMS\SerializedName("nativeName")
49 | */
50 | public $nativeName;
51 |
52 | /**
53 | * The title of the resource.
54 | * This is usually something that is meant to be displayed in a user interface.
55 | *
56 | * @var string
57 | * @JMS\Type("string")
58 | */
59 | public $title;
60 |
61 | /**
62 | * A description of the resource. This is usually something that is meant to be displayed in a user interface.
63 | * @var string
64 | * @JMS\Type("string")
65 | */
66 | public $abstract;
67 |
68 | /**
69 | * The store the resource is a part of.
70 | * @var array
71 | * @JMS\Type("array")
72 | */
73 | public $store;
74 |
75 | /**
76 | *
77 | * @var bool
78 | * @JMS\Type("boolean")
79 | * @JMS\SerializedName("enabled")
80 | */
81 | public $enabled = true;
82 |
83 | /**
84 | * True if this feature type info is overriding the counting of numberMatched
85 | * @var bool
86 | * @JMS\Type("boolean")
87 | * @JMS\SerializedName("skipNumberMatched")
88 | */
89 | public $skipNumberMatched = false;
90 | /**
91 | *
92 | * @var bool
93 | * @JMS\Type("boolean")
94 | * @JMS\SerializedName("circularArcPresent")
95 | */
96 | public $circularArcPresent = false;
97 |
98 | /**
99 | * A collection of keywords associated with the resource.
100 | * @var array
101 | * @JMS\Type("array")
102 | */
103 | public $keywords;
104 |
105 | /**
106 | * Returns the bounds of the resource in its declared CRS.
107 | *
108 | * @var \OneOffTech\GeoServer\Models\BoundingBox
109 | * @JMS\Type("OneOffTech\GeoServer\Models\BoundingBox")
110 | * @JMS\SerializedName("nativeBoundingBox")
111 | */
112 | public $nativeBoundingBox;
113 |
114 | /**
115 | * The bounds of the resource in lat / lon. This value represents a "fixed value" and is not calculated on the underlying dataset.
116 | *
117 | * @var \OneOffTech\GeoServer\Models\BoundingBox
118 | * @JMS\Type("OneOffTech\GeoServer\Models\BoundingBox")
119 | * @JMS\SerializedName("latLonBoundingBox")
120 | */
121 | public $boundingBox;
122 |
123 | /**
124 | * Wrapper for the derived set of attributes for the feature type
125 | *
126 | * @var array
127 | * @JMS\Type("array")
128 | */
129 | public $attributes;
130 |
131 | /**
132 | *
133 | * @var string
134 | * @JMS\Type("string")
135 | * @JMS\SerializedName("projectionPolicy")
136 | */
137 | public $projectionPolicy;
138 |
139 | /**
140 | *
141 | * @var float
142 | * @JMS\Type("float")
143 | * @JMS\SerializedName("maxFeatures")
144 | */
145 | public $maxFeatures;
146 |
147 | /**
148 | *
149 | * @var float
150 | * @JMS\Type("float")
151 | * @JMS\SerializedName("numDecimals")
152 | */
153 | public $numDecimals;
154 |
155 | /**
156 | * @var array
157 | * @JMS\Type("array")
158 | */
159 | public $namespace;
160 |
161 | /**
162 | * Return the type of the resource
163 | *
164 | * @return string The @see GeoType of the resource
165 | */
166 | abstract public function type();
167 | }
168 |
--------------------------------------------------------------------------------
/src/Models/Store.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 | use OneOffTech\GeoServer\Contracts\Model;
26 |
27 | /**
28 | * A generic store, can be a data store or a coverage store
29 | */
30 | class Store extends Model
31 | {
32 | /**
33 | * Store name
34 | *
35 | * @var string
36 | * @JMS\Type("string")
37 | */
38 | public $name;
39 |
40 | /**
41 | * The API URL to the store details
42 | *
43 | * @var string
44 | * @JMS\Type("string")
45 | */
46 | public $href;
47 |
48 | /**
49 | * If the store is enabled
50 | *
51 | * @var bool
52 | * @JMS\Type("boolean")
53 | */
54 | public $enabled;
55 |
56 | /**
57 | * If the store is the default one
58 | *
59 | * @var bool
60 | * @JMS\Type("boolean")
61 | * @JMS\SerializedName("_default")
62 | */
63 | public $default = false;
64 |
65 | /**
66 | * The workspace in which the store is located
67 | *
68 | * @var string
69 | * @JMS\Type("string")
70 | */
71 | public $workspace;
72 |
73 | /**
74 | * Indicates if the store exists.
75 | *
76 | * It is used to indicate the deletion status.
77 | * The $exists value is set to false after succesful deletion.
78 | *
79 | * @var bool
80 | * @JMS\Exclude
81 | */
82 | public $exists = true;
83 | }
84 |
--------------------------------------------------------------------------------
/src/Models/Style.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 | use OneOffTech\GeoServer\Contracts\Model;
26 |
27 | /**
28 | * A style describes how a resource is symbolized or rendered by the Web Map Service.
29 | */
30 | class Style extends Model
31 | {
32 | /**
33 | * The style name
34 | *
35 | * @var string
36 | * @JMS\Type("string")
37 | */
38 | public $name;
39 |
40 | /**
41 | * The workspace in which the style is located
42 | *
43 | * @var string
44 | * @JMS\Type("string")
45 | */
46 | public $workspace;
47 |
48 | /**
49 | * The style format
50 | *
51 | * @var string
52 | * @JMS\Type("string")
53 | */
54 | public $format;
55 |
56 | /**
57 | * The style version
58 | *
59 | * @var string
60 | * @JMS\Type("string")
61 | * @JMS\SerializedName("languageVersion")
62 | */
63 | public $version;
64 |
65 | /**
66 | * The original style file name
67 | *
68 | * @var string
69 | * @JMS\Type("string")
70 | * @JMS\SerializedName("filename")
71 | */
72 | public $filename;
73 |
74 | /**
75 | * Indicates if the style exists.
76 | *
77 | * It is used to indicate the deletion status.
78 | * The $exists value is set to false after succesful deletion.
79 | *
80 | * @var bool
81 | * @JMS\Exclude
82 | */
83 | public $exists = true;
84 | }
85 |
--------------------------------------------------------------------------------
/src/Models/Workspace.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Models;
23 |
24 | use JMS\Serializer\Annotation as JMS;
25 | use OneOffTech\GeoServer\Contracts\Model;
26 |
27 | class Workspace extends Model
28 | {
29 |
30 | /**
31 | * Name of workspace
32 | * @var string
33 | * @JMS\Type("string")
34 | */
35 | public $name;
36 |
37 | /**
38 | * The API URL to the workspace details
39 | * @var string
40 | * @JMS\Type("string")
41 | */
42 | public $href;
43 |
44 | /**
45 | * If the workspace is isolated
46 | * @var bool
47 | * @JMS\Type("boolean")
48 | */
49 | public $isolated;
50 |
51 | /**
52 | * URL to Datas tores in this workspace
53 | * @var string
54 | * @JMS\Type("string")
55 | * @JMS\SerializedName("dataStores")
56 | */
57 | public $dataStores;
58 |
59 | /**
60 | * URL to Coverage stores in this workspace
61 | * @var string
62 | * @JMS\Type("string")
63 | * @JMS\SerializedName("coverageStores")
64 | */
65 | public $coverageStores;
66 |
67 | /**
68 | * URL to WMS stores in this workspace
69 | * @var string
70 | * @JMS\Type("string")
71 | * @JMS\SerializedName("wmsStores")
72 | */
73 | public $wmsStores;
74 |
75 | /**
76 | * URL to WMS stores in this workspace
77 | * @var string
78 | * @JMS\Type("string")
79 | * @JMS\SerializedName("wmtsStores")
80 | */
81 | public $wmtsStores;
82 | }
83 |
--------------------------------------------------------------------------------
/src/Options.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer;
23 |
24 | use Http\Client\HttpClient;
25 | use JMS\Serializer\Serializer;
26 | use Http\Message\MessageFactory;
27 | use Http\Client\Common\PluginClient;
28 | use JMS\Serializer\SerializerBuilder;
29 | use Http\Discovery\HttpClientDiscovery;
30 | use Http\Discovery\MessageFactoryDiscovery;
31 | use OneOffTech\GeoServer\Contracts\Authentication;
32 | use Doctrine\Common\Annotations\AnnotationRegistry;
33 | use Http\Client\Common\Plugin\AuthenticationPlugin;
34 | use Http\Client\Common\Plugin\HeaderDefaultsPlugin;
35 | use JMS\Serializer\EventDispatcher\EventDispatcher;
36 | use OneOffTech\GeoServer\Serializer\DeserializeBoundingBoxSubscriber;
37 | use OneOffTech\GeoServer\Serializer\DeserializeStyleResponseSubscriber;
38 | use OneOffTech\GeoServer\Serializer\DeserializeDataStoreResponseSubscriber;
39 | use OneOffTech\GeoServer\Serializer\DeserializeCoverageStoreResponseSubscriber;
40 |
41 | final class Options
42 | {
43 | public $authentication;
44 |
45 | /**
46 | * @var HttpClient
47 | */
48 | public $httpClient;
49 |
50 | /**
51 | * @var MessageFactory
52 | */
53 | public $messageFactory;
54 |
55 | /**
56 | * @var Serializer
57 | */
58 | public $serializer;
59 |
60 | const FORMAT_JSON = "application/json";
61 |
62 | /**
63 | * ...
64 | */
65 | public function __construct(Authentication $authentication)
66 | {
67 | $this->authentication = $authentication;
68 | AnnotationRegistry::registerLoader('class_exists');
69 |
70 | // registering a PluginClient as the authentication and
71 | // some headers should be added to all requests
72 | $this->httpClient = new PluginClient(
73 | HttpClientDiscovery::find(),
74 | [
75 | new AuthenticationPlugin($this->authentication),
76 | new HeaderDefaultsPlugin([
77 | 'User-Agent' => 'OneOffTech GeoServer Client',
78 | 'Content-Type' => self::FORMAT_JSON,
79 | 'Accept' => self::FORMAT_JSON
80 | ]),
81 | ]
82 | );
83 |
84 | $this->messageFactory = MessageFactoryDiscovery::find();
85 | $this->serializer = SerializerBuilder::create()
86 | ->configureListeners(function (EventDispatcher $dispatcher) {
87 | $dispatcher->addSubscriber(new DeserializeDataStoreResponseSubscriber());
88 | $dispatcher->addSubscriber(new DeserializeBoundingBoxSubscriber());
89 | $dispatcher->addSubscriber(new DeserializeCoverageStoreResponseSubscriber());
90 | $dispatcher->addSubscriber(new DeserializeStyleResponseSubscriber());
91 | })
92 | ->build();
93 | ;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Serializer/DeserializeBoundingBoxSubscriber.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Serializer;
23 |
24 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
25 | use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
26 |
27 | /**
28 | * Pre-Deserialize event for @see \OneOffTech\GeoServer\Models\BoundingBox
29 | *
30 | * It make sure that data before deserialization into the BoundingBox class
31 | * is in the expected format
32 | */
33 | class DeserializeBoundingBoxSubscriber implements EventSubscriberInterface
34 | {
35 | public static function getSubscribedEvents()
36 | {
37 | return [
38 | [
39 | 'event' => 'serializer.pre_deserialize',
40 | 'method' => 'onPreDeserialize',
41 | 'class' => 'OneOffTech\\GeoServer\\Models\\BoundingBox',
42 | 'format' => 'json',
43 | 'priority' => 0,
44 | ],
45 | ];
46 | }
47 |
48 | public function onPreDeserialize(PreDeserializeEvent $event)
49 | {
50 | $data = $event->getData();
51 |
52 | // Convert a projected CSR response to string
53 |
54 | if (isset($data['crs']) && ! is_string($data['crs'])) {
55 | $crs = $data['crs'];
56 |
57 | if (isset($crs['@class']) && $crs['@class'] === 'projected') {
58 | $data['crs'] = $crs['$'];
59 | }
60 | }
61 |
62 | $event->setData($data);
63 |
64 | return true;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Serializer/DeserializeCoverageStoreResponseSubscriber.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Serializer;
23 |
24 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
25 | use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
26 |
27 | /**
28 | * Pre-Deserialize event for @see \OneOffTech\GeoServer\Models\CoverageStore
29 | *
30 | * It make sure that data before deserialization into the CoverageStore class
31 | * is in the expected format
32 | */
33 | class DeserializeCoverageStoreResponseSubscriber implements EventSubscriberInterface
34 | {
35 | public static function getSubscribedEvents()
36 | {
37 | return [
38 | [
39 | 'event' => 'serializer.pre_deserialize',
40 | 'method' => 'onPreDeserialize',
41 | 'class' => 'OneOffTech\\GeoServer\\Http\\Responses\\CoverageStoresResponse',
42 | 'format' => 'json',
43 | 'priority' => 1,
44 | ],
45 | [
46 | 'event' => 'serializer.pre_deserialize',
47 | 'method' => 'onPreDeserialize',
48 | 'class' => 'OneOffTech\\GeoServer\\Models\\CoverageStore',
49 | 'format' => 'json',
50 | 'priority' => 0,
51 | ],
52 | ];
53 | }
54 |
55 | public function onPreDeserialize(PreDeserializeEvent $event)
56 | {
57 | $data = $event->getData();
58 |
59 | // The CoverageStoreResponse has an annoying multi-level
60 | // array. This aims at flatten the array to
61 | // a single level
62 | if (isset($data['coverageStores']) && is_array($data['coverageStores'])) {
63 | $data['coverageStores'] = array_map(function ($a) {
64 | return isset($a[0]) ? $a[0] : $a;
65 | }, array_values($data['coverageStores']));
66 | }
67 |
68 | // The CoverageStore contain a reference to the workspace by
69 | // name and url. For the purpose of keeping the object
70 | // simple we transform the complex object into string
71 | if (isset($data['workspace']) && is_array($data['workspace'])) {
72 | $data['workspace'] = $data['workspace']['name'];
73 | }
74 |
75 | if (isset($data['coverages']) && is_string($data['coverages'])) {
76 | $data['coverages'] = [$data['coverages']];
77 | }
78 |
79 | $event->setData($data);
80 |
81 | return true;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Serializer/DeserializeDataStoreResponseSubscriber.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Serializer;
23 |
24 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
25 | use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
26 |
27 | /**
28 | * Pre-Deserialize event for @see \OneOffTech\GeoServer\Models\DataStore
29 | *
30 | * It make sure that data before deserialization into the DataStore class
31 | * is in the expected format
32 | */
33 | class DeserializeDataStoreResponseSubscriber implements EventSubscriberInterface
34 | {
35 | public static function getSubscribedEvents()
36 | {
37 | return [
38 | [
39 | 'event' => 'serializer.pre_deserialize',
40 | 'method' => 'onPreDeserialize',
41 | 'class' => 'OneOffTech\\GeoServer\\Models\\DataStore',
42 | 'format' => 'json',
43 | 'priority' => 0,
44 | ],
45 | [
46 | 'event' => 'serializer.pre_deserialize',
47 | 'method' => 'onPreDeserialize',
48 | 'class' => 'OneOffTech\\GeoServer\\Http\\Responses\\DataStoreResponse',
49 | 'format' => 'json',
50 | 'priority' => 0,
51 | ],
52 | ];
53 | }
54 |
55 | public function onPreDeserialize(PreDeserializeEvent $event)
56 | {
57 | $data = $event->getData();
58 |
59 | // The DataStoreResponse has an annoying multi-level
60 | // array. This aims at flatten the array to
61 | // a single level
62 | if (isset($data['dataStores']) && is_array($data['dataStores'])) {
63 | $data['dataStores'] = array_map(function ($a) {
64 | return isset($a[0]) ? $a[0] : $a;
65 | }, array_values($data['dataStores']));
66 | }
67 |
68 | // The DataStore has an annoying dataStore key that contain
69 | // the details. This aims at remove that key when
70 | // deserializing a Models\DataStore instance
71 | if (isset($data['dataStore']) && is_array($data['dataStore'])) {
72 | $data = $data['dataStore'];
73 | }
74 |
75 | // The DataStore contain a reference to the workspace by
76 | // name and url. For the purpose of keeping the object
77 | // simple we transform the complex object into string
78 | if (isset($data['workspace']) && is_array($data['workspace'])) {
79 | $data['workspace'] = $data['workspace']['name'];
80 | }
81 |
82 | $event->setData($data);
83 |
84 | return true;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Serializer/DeserializeStyleResponseSubscriber.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Serializer;
23 |
24 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
25 | use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
26 |
27 | /**
28 | * Pre-Deserialize event for @see \OneOffTech\GeoServer\Models\Style
29 | *
30 | * It make sure that data before deserialization into the Style class
31 | * is in the expected format
32 | */
33 | class DeserializeStyleResponseSubscriber implements EventSubscriberInterface
34 | {
35 | public static function getSubscribedEvents()
36 | {
37 | return [
38 | [
39 | 'event' => 'serializer.pre_deserialize',
40 | 'method' => 'onPreDeserialize',
41 | 'class' => 'OneOffTech\\GeoServer\\Models\\Style',
42 | 'format' => 'json',
43 | 'priority' => 0,
44 | ],
45 | [
46 | 'event' => 'serializer.pre_deserialize',
47 | 'method' => 'onPreDeserialize',
48 | 'class' => 'OneOffTech\\GeoServer\\Http\\Responses\\StylesResponse',
49 | 'format' => 'json',
50 | 'priority' => 0,
51 | ],
52 | ];
53 | }
54 |
55 | public function onPreDeserialize(PreDeserializeEvent $event)
56 | {
57 | $data = $event->getData();
58 |
59 | // The StyleResponse has an annoying multi-level
60 | // array. This aims at flatten the array to
61 | // a single level
62 | if (isset($data['styles']) && is_array($data['styles'])) {
63 | $data['styles'] = array_map(function ($a) {
64 | return isset($a[0]) ? $a[0] : $a;
65 | }, array_values($data['styles']));
66 | }
67 |
68 | // The Style has an annoying style key that contain
69 | // the details. This aims at remove that key when
70 | // deserializing a Models\Style instance
71 | if (isset($data['style']) && is_array($data['style'])) {
72 | $data = $data['style'];
73 | }
74 |
75 | // The Style contain a reference to the workspace by
76 | // name. For the purpose of keeping the object simple
77 | // we transform the complex object into string
78 | if (isset($data['workspace']) && is_array($data['workspace'])) {
79 | $data['workspace'] = $data['workspace']['name'] ?? null;
80 | }
81 |
82 | // The Style object contains the version number in a
83 | // sub-object. We want it to be on the first level
84 | // for easy access
85 | if (isset($data['languageVersion']) && is_array($data['languageVersion'])) {
86 | $data['languageVersion'] = $data['languageVersion']['version'] ?? null;
87 | }
88 |
89 | $event->setData($data);
90 |
91 | return true;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/StyleFile.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer;
23 |
24 | use SplFileInfo;
25 | use OneOffTech\GeoServer\Support\TypeResolver;
26 | use OneOffTech\GeoServer\Exception\UnsupportedFileException;
27 |
28 | /**
29 | * A file that contains rendering style for a Web Map Service
30 | */
31 | class StyleFile
32 | {
33 | const MIME_TYPE = "application/vnd.ogc.sld+xml";
34 |
35 | protected $file;
36 |
37 | protected $name;
38 |
39 | protected $originalName;
40 |
41 | protected $mimeType;
42 |
43 | protected $extension;
44 |
45 | /**
46 | * The mime type as required by GeoServer
47 | *
48 | * e.g. for a geo tiff file the mime type appears to be "geotif/geotiff",
49 | * as found in https://gis.stackexchange.com/questions/218162/creating-coveragestore-geotiff-using-rest-api
50 | */
51 | protected $normalizedMimeType;
52 |
53 | public function __construct($path)
54 | {
55 | $this->file = new SplFileInfo($path);
56 |
57 | list($format, $type, $mimeType) = TypeResolver::identify($path);
58 |
59 | if (! in_array($format, TypeResolver::supportedFormats())) {
60 | throw new UnsupportedFileException($path, $format, join(', ', TypeResolver::supportedFormats()));
61 | }
62 |
63 | $this->mimeType = $mimeType;
64 |
65 | $this->extension = $this->file->getExtension();
66 |
67 | $this->normalizedMimeType = $mimeType;
68 |
69 | $this->originalName = $this->file->getFileName();
70 | $this->name = str_replace('.sld', '', $this->originalName);
71 | }
72 |
73 | /**
74 | * Set the style name.
75 | * It will be used when creating the style in the GeoServer
76 | *
77 | * @param string $value
78 | * @return StyleFile
79 | */
80 | public function name($value)
81 | {
82 | $this->name = $value;
83 |
84 | return $this;
85 | }
86 |
87 | public function content()
88 | {
89 | return file_get_contents($this->file->getRealPath());
90 | }
91 |
92 | public function __get($property)
93 | {
94 | return $this->$property;
95 | }
96 |
97 | /**
98 | * Create a StyleFile instance from a given file path
99 | *
100 | * @param string $path
101 | * @return Data
102 | * @throws UnsupportedFileException if file is not supported
103 | */
104 | public static function from($path)
105 | {
106 | return new static($path);
107 | }
108 | public static function load($path)
109 | {
110 | return static::from($path);
111 | }
112 |
113 | /**
114 | * Check if the specified file is a valid style file
115 | *
116 | * @param string $path
117 | * @return bool
118 | */
119 | public static function isSupported(string $path)
120 | {
121 | list($format, $type, $mimeType) = TypeResolver::identify($path);
122 | return $mimeType === self::MIME_TYPE;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Support/BinaryReader.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use Exception;
25 | use OneOffTech\GeoServer\Contracts\FileReader;
26 |
27 | final class BinaryReader extends FileReader
28 | {
29 | private static function isBigEndianMachine()
30 | {
31 | return current(unpack('v', pack('S', 0xff))) !== 0xff;
32 | }
33 |
34 | private static function readData($path, $type, $length, $position = 0, $invert_endianness = false)
35 | {
36 | $handle = self::openFileBinary($path, $position);
37 | $data = fread($handle, $length);
38 | self::closeFile($handle);
39 |
40 | if ($data === false) {
41 | return null;
42 | }
43 | if ($invert_endianness) {
44 | $data = strrev($data);
45 | }
46 |
47 | return current(unpack($type, $data));
48 | }
49 |
50 | /**
51 | * Read a 32 bit integer from the beginning of a file
52 | *
53 | * @param string $path the file path to read from
54 | * @param bool $big_endian if the integer is in big endian notation. Default true
55 | * @return integer
56 | */
57 | public static function readInt32($path, $position = 0, $big_endian = true)
58 | {
59 | return self::readData($path, $big_endian ? 'N' : 'V', 4, $position);
60 | }
61 |
62 | public static function readShort($path, $position = 0, $big_endian = true)
63 | {
64 | return self::readData($path, 's', 2, $position);
65 | }
66 |
67 | public static function isGeoTiff($path)
68 | {
69 | $handle = self::openFileBinary($path);
70 |
71 | // https://www.awaresystems.be/imaging/tiff/specification/TIFF6.pdf
72 | // 8 bytes header:
73 | // - 2 bytes for the byte order
74 | // - 2 bytes for the TIFF header
75 | // - 4 bytes for the offset to the first IFD.
76 | $tiffHeader = fread($handle, 8);
77 |
78 | if ($tiffHeader === false) {
79 | self::closeFile($handle);
80 | return false;
81 | }
82 |
83 | $byteOrder = current(unpack('a', $tiffHeader)).current(unpack('a', $tiffHeader, 1));
84 |
85 | if (! in_array($byteOrder, ['MM', 'II'])) {
86 | // unknown byte order
87 | self::closeFile($handle);
88 | return false;
89 | }
90 |
91 | $big_endian = $byteOrder === 'MM' ? true : false;
92 | $tiffCode = current(unpack('s', $tiffHeader, 2));
93 |
94 | if ($tiffCode !== 42) {
95 | // tiff code not found
96 | self::closeFile($handle);
97 | return false;
98 | }
99 |
100 | $byteOffset = self::getBytes($tiffHeader, 4, 4, $big_endian);
101 |
102 | fseek($handle, $byteOffset);
103 |
104 | $numDirData = fread($handle, 2);
105 |
106 | $numDirEntries = self::getBytes($numDirData, 2, 0, $big_endian);
107 | fseek($handle, $byteOffset+2);
108 |
109 | $imageFileDirectoriesData = fread($handle, (12 * $numDirEntries)+12);
110 |
111 | // from the Image File Directories record in the TIFF file I need the GeoKeyDirectory
112 | // and the values in the GeoKeyDirectory, which has field code 34735
113 | // https://www.geospatialworld.net/article/geotiff-a-standard-image-file-format-for-gis-applications/
114 |
115 | // Even if from https://github.com/xlhomme/GeotiffParser.js/blob/master/js/GeotiffParser.js
116 | // seems that the GeoKeyDirectory field should have 4 values to be a valid GeoTiff
117 | // we consider the presence of the tag a valid indicator
118 |
119 | $hasGeoKeyDirectory = false;
120 | for ($i = 0, $entryCount = 0; $entryCount < $numDirEntries; $i += 12, $entryCount++) {
121 | $fieldTag = self::getBytes($imageFileDirectoriesData, 2, $i, $big_endian);
122 |
123 | if ($fieldTag === 34735) { // GeoKeyDirectory field
124 | $hasGeoKeyDirectory = true;
125 | break;
126 | }
127 | }
128 |
129 | self::closeFile($handle);
130 |
131 | return $hasGeoKeyDirectory;
132 | }
133 |
134 | public static function isGeoPackage($path)
135 | {
136 | $handle = self::openFileBinary($path);
137 | $sqliteMagic = self::getString(fread($handle, 16), 15);
138 | fseek($handle, 68);
139 | $gpkgMagic = self::getString(fread($handle, 4), 4);
140 | self::closeFile($handle);
141 |
142 | if ($sqliteMagic !== 'SQLite format 3') {
143 | return false;
144 | }
145 |
146 | if ($gpkgMagic !== 'GPKG') {
147 | return false;
148 | }
149 |
150 | return true;
151 | }
152 |
153 | private static function getBytes($data, $length, $offset = 0, $big_endian = true)
154 | {
155 | if ($length <= 2) {
156 | return current(unpack($big_endian ? 'n' : 'v', $data, $offset));
157 | } elseif ($length <= 4) {
158 | return current(unpack($big_endian ? 'N' : 'V', $data, $offset));
159 | }
160 | // unsigned short 16bit current(unpack($big_endian ? 'n' : 'v', $tiffHeader, 4));
161 | // unsigned long 32bit current(unpack($big_endian ? 'N' : 'V', $tiffHeader, 4));
162 | }
163 |
164 | private static function getString($data, $length, $offset = 0)
165 | {
166 | try {
167 | $chars = [];
168 |
169 | for ($i=$offset; $i < $length; $i++) {
170 | $chars[] = current(unpack('a', $data, $i));
171 | }
172 |
173 | return implode('', $chars);
174 | } catch (Exception $ex) {
175 | return '';
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/Support/ImageResponse.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use Psr\Http\Message\ResponseInterface;
25 |
26 | final class ImageResponse
27 | {
28 | private $response = null;
29 |
30 | public function __construct(ResponseInterface $response)
31 | {
32 | $this->response = $response;
33 | }
34 |
35 | public function mimeType()
36 | {
37 | $contentTypeHeader = $this->response->getHeader('Content-Type');
38 | return $contentTypeHeader[0] ?? 'application/octet-stream';
39 | }
40 |
41 | public function asString()
42 | {
43 | return (string)$this->response->getBody();
44 | }
45 |
46 | public static function from(ResponseInterface $response)
47 | {
48 | return new static($response);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Support/TextReader.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use OneOffTech\GeoServer\Contracts\FileReader;
25 |
26 | final class TextReader extends FileReader
27 | {
28 |
29 | /**
30 | * Read a line from file
31 | *
32 | * @param string $path the file path to read from
33 | * @param integer $lines the number of lines to read
34 | * @return array
35 | */
36 | public static function readLines($path, $lines = 1)
37 | {
38 | $data = [];
39 |
40 | $handle = self::openFile($path);
41 | for ($i=0; $i < $lines; $i++) {
42 | $data[] = fgets($handle);
43 | }
44 | self::closeFile($handle);
45 |
46 | return $data;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Support/TypeResolver.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use OneOffTech\GeoServer\GeoType;
25 | use OneOffTech\GeoServer\GeoFormat;
26 |
27 | final class TypeResolver
28 | {
29 | protected static $mimeTypes = [
30 | GeoFormat::SHAPEFILE => 'application/octet-stream', // shapefile
31 | GeoFormat::SHAPEFILE_ZIP => 'application/zip', // shapefile in ZIP container
32 | GeoFormat::GEOTIFF => 'image/tiff', // geotiff
33 | GeoFormat::SLD => 'application/vnd.ogc.sld+xml',
34 | GeoFormat::GEOPACKAGE => 'application/geopackage+sqlite3',
35 | ];
36 |
37 | protected static $mimeTypeToFormat = [];
38 |
39 | protected static $typesMap = [
40 | GeoFormat::SHAPEFILE => GeoType::VECTOR,
41 | GeoFormat::SHAPEFILE_ZIP => GeoType::VECTOR,
42 | GeoFormat::GEOTIFF => GeoType::RASTER,
43 | GeoFormat::GEOPACKAGE => GeoType::VECTOR,
44 |
45 | GeoType::VECTOR => [
46 | GeoFormat::SHAPEFILE,
47 | GeoFormat::SHAPEFILE_ZIP,
48 | GeoFormat::GEOPACKAGE,
49 | ],
50 | GeoType::RASTER => [
51 | GeoFormat::GEOTIFF,
52 | ]
53 | ];
54 |
55 | /**
56 | * The file extension, given the file format, as accepted by GeoServer
57 | */
58 | protected static $normalizedFormatFileExtensions = [
59 | GeoFormat::SHAPEFILE => 'shp',
60 | GeoFormat::SHAPEFILE_ZIP => 'shp',
61 | GeoFormat::GEOTIFF => 'geotiff',
62 | GeoFormat::GEOPACKAGE => 'gpkg',
63 | ];
64 |
65 | /**
66 | * The file mime type, given the file format, as accepted by GeoServer
67 | */
68 | protected static $normalizedMimeTypeFileFormat = [
69 | GeoFormat::GEOTIFF => 'geotif/geotiff', // as found on https://gis.stackexchange.com/questions/218162/creating-coveragestore-geotiff-using-rest-api
70 | GeoFormat::SHAPEFILE_ZIP => 'application/zip',
71 | ];
72 |
73 | public static function identify($path)
74 | {
75 | $mimeType = mime_content_type($path);
76 |
77 | // try to recognize the format from the mime type.
78 | // this works for files that have a specific mime type
79 | $format = isset(self::$mimeTypeToFormat[$mimeType]) ? self::$mimeTypeToFormat[$mimeType] : null;
80 |
81 | // for some files the mime type is too generic
82 | // so additional checks are required
83 | if ($mimeType === self::$mimeTypes[GeoFormat::SHAPEFILE]) {
84 | // According to https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
85 | // the first 4 bytes of a shapefile are always the number 9994
86 | $code = BinaryReader::readInt32($path);
87 |
88 | if ($code === 9994) {
89 | $format = GeoFormat::SHAPEFILE;
90 | }
91 | }
92 |
93 | if ($mimeType === 'application/zip') {
94 |
95 | // could be a compressed shapefile
96 | // Check if the zip file contains at least 1 shapefile
97 | $containsShp = ZipReader::containsFile($path, '.shp');
98 |
99 | if ($containsShp) {
100 | $format = GeoFormat::SHAPEFILE_ZIP;
101 | $mimeType = self::$mimeTypes[GeoFormat::SHAPEFILE_ZIP];
102 | }
103 | } elseif ($mimeType === 'image/tiff' && BinaryReader::isGeoTiff($path)) {
104 | $format = GeoFormat::GEOTIFF;
105 | } elseif ($mimeType === 'application/xml' || $mimeType === 'text/xml') {
106 |
107 | // check if Style tag is present
108 | $data = join('', TextReader::readLines($path, 2));
109 | if (strpos($data, '.
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use LogicException;
25 | use ReflectionClass;
26 | use InvalidArgumentException;
27 | use OneOffTech\GeoServer\Models\BoundingBox;
28 |
29 | /**
30 | * Options for the Web Map Service (WMS)
31 | *
32 | * Helper class to create the parameters for the WMS service call
33 | */
34 | final class WmsOptions
35 | {
36 | /**
37 | * Output format for PNG image
38 | */
39 | const OUTPUT_PNG = "image/png";
40 |
41 | /**
42 | * Same as PNG, but computes an optimal 256 color (8 bit) palette, so the image size is usually smaller
43 | */
44 | const OUTPUT_PNG8 = "image/png8";
45 |
46 | /**
47 | *
48 | */
49 | const OUTPUT_JPEG = "image/jpeg";
50 |
51 | /**
52 | * A custom format that will decide dynamically, based on the image contents, if it’s best to use a JPEG or PNG compression. The images are returned in JPEG format if fully opaque and not paletted. In order to use this format in a meaningful way the GetMap must include a “&transparent=TRUE” parameter, as without it GeoServer generates opaque images with the default/requested background color, making this format always return JPEG images (or always PNG, if they are paletted). When using the layer preview to test this format, remember to add “&transparent=TRUE” to the preview URL, as normally the preview generates non transparent images.
53 | */
54 | const OUTPUT_JPEG_PNG = "image/vnd.jpeg-png";
55 |
56 | /**
57 | *
58 | */
59 | const OUTPUT_GIF = "image/gif";
60 |
61 | /**
62 | *
63 | */
64 | const OUTPUT_TIFF = "image/tiff";
65 |
66 | /**
67 | * Same as TIFF, but computes an optimal 256 color (8 bit) palette, so the image size is usually smaller
68 | */
69 | const OUTPUT_TIFF8 = "image/tiff8";
70 |
71 | /**
72 | * Same as TIFF, but includes extra GeoTIFF metadata
73 | */
74 | const OUTPUT_GEOTIFF = "image/geotiff";
75 |
76 | /**
77 | * Same as TIFF, but includes extra GeoTIFF metadata and computes an optimal 256 color (8 bit) palette, so the image size is usually smaller
78 | */
79 | const OUTPUT_GEOTIFF8 = "image/geotiff8";
80 |
81 | /**
82 | *
83 | */
84 | const OUTPUT_SVG = "image/svg";
85 |
86 | /**
87 | *
88 | */
89 | const OUTPUT_PDF = "application/pdf";
90 |
91 | /**
92 | *
93 | */
94 | const OUTPUT_GEORSS = "rss";
95 |
96 | /**
97 | *
98 | */
99 | const OUTPUT_KML = "kml";
100 |
101 | /**
102 | *
103 | */
104 | const OUTPUT_KMZ = "kmz";
105 |
106 | /**
107 | * Generates an OpenLayers HTML application.
108 | */
109 | const OUTPUT_OPENLAYERS = "application/openlayers";
110 |
111 | /**
112 | * Generates an UTFGrid 1.3 JSON response. Requires vector output, either from a vector layer, or from a raster layer turned into vectors by a rendering transformation.
113 | */
114 | const OUTPUT_UTFGRID = "application/json;type=utfgrid";
115 |
116 | private $format = self::OUTPUT_PNG;
117 |
118 | private $bbox = null;
119 |
120 | private $layers = null;
121 |
122 | private $styles = null;
123 |
124 | private $width = 640;
125 |
126 | private $height = 480;
127 |
128 | private $srs = "EPSG:4326";
129 |
130 | private $version = "1.1.0";
131 |
132 | private $request = "GetMap";
133 |
134 | private function isFormatValid($format)
135 | {
136 | return in_array($format, $this->supportedFormats());
137 | }
138 |
139 | public function supportedFormats()
140 | {
141 | $constants = (new ReflectionClass(get_called_class()))->getConstants();
142 |
143 | return array_values($constants);
144 | }
145 |
146 | public function format($format)
147 | {
148 | if (! $this->isFormatValid($format)) {
149 | throw new InvalidArgumentException("Unrecognized format [$format] Expected one of [".join(",", $this->supportedFormats())."]");
150 | }
151 |
152 | $this->format = $format;
153 | return $this;
154 | }
155 |
156 | public function srs($srs)
157 | {
158 | $this->srs = $srs;
159 | return $this;
160 | }
161 |
162 | public function layers($layers)
163 | {
164 | $this->layers = is_array($layers) ? $layers : [$layers];
165 | return $this;
166 | }
167 |
168 | public function styles($styles)
169 | {
170 | $this->styles = is_array($styles) ? $styles : [$styles];
171 | return $this;
172 | }
173 |
174 | public function size($width, $height)
175 | {
176 | $this->width = $width;
177 | $this->height = $height;
178 | return $this;
179 | }
180 |
181 | public function boundingBox(BoundingBox $boundingBox)
182 | {
183 | $this->bbox = $boundingBox;
184 | return $this;
185 | }
186 |
187 | public function toArray()
188 | {
189 | if (empty($this->layers)) {
190 | throw new LogicException("Layers cannot be null or empty");
191 | }
192 |
193 | if (is_null($this->bbox)) {
194 | throw new LogicException("Bounding box cannot be null");
195 | }
196 |
197 | return [
198 | 'request' => $this->request,
199 | 'version' => $this->version,
200 | 'format' => $this->format,
201 | 'layers' => $this->layers,
202 | 'bbox' => $this->bbox->toArray(),
203 | 'srs' => $this->srs,
204 | 'width' => $this->width,
205 | 'height' => $this->height,
206 | 'styles' => $this->styles ?? [],
207 | ];
208 | }
209 |
210 | public function toUrlParameters()
211 | {
212 | $params = [
213 | 'version' => $this->version,
214 | 'request' => $this->request,
215 | 'layers' => join(',', $this->layers),
216 | 'bbox' => join(',', $this->bbox->toArray()),
217 | 'styles' => join(',', $this->styles ?? []),
218 | 'width' => $this->width,
219 | 'height' => $this->height,
220 | 'srs' => $this->srs,
221 | 'format' => urlencode($this->format),
222 | ];
223 |
224 | $collapsedParams = array_map(function ($key, $value) {
225 | return "$key=$value";
226 | }, array_keys($params), $params);
227 |
228 | return join("&", $collapsedParams);
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/Support/ZipReader.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace OneOffTech\GeoServer\Support;
23 |
24 | use Exception;
25 | use ZipArchive;
26 | use OneOffTech\GeoServer\Contracts\FileReader;
27 |
28 | final class ZipReader extends FileReader
29 | {
30 |
31 | /**
32 | * Read a 32 bit integer from the beginning of a file
33 | *
34 | * @param string $path the file path to read from
35 | * @param bool $big_endian if the integer is in big endian notation. Default true
36 | * @return integer
37 | */
38 | public static function contentList($path)
39 | {
40 | $entries = [];
41 |
42 | $za = new ZipArchive;
43 | $za->open($path);
44 |
45 | for ($i=0; $i < $za->numFiles; $i++) {
46 | $entry = $za->statIndex($i);
47 | $entries[] = $entry['name'];
48 | }
49 |
50 | $za->close();
51 |
52 | return $entries;
53 | }
54 |
55 | public static function containsFile($path, $name)
56 | {
57 | $entries = [];
58 |
59 | $za = new ZipArchive;
60 | $za->open($path);
61 |
62 | for ($i=0; $i < $za->numFiles; $i++) {
63 | $entry = $za->statIndex($i);
64 | if (strpos($entry['name'], $name) !== false) {
65 | $entries[] = $entry['name'];
66 | }
67 | }
68 |
69 | $za->close();
70 |
71 | return count($entries) > 0;
72 | }
73 |
74 | /**
75 | * Tap into the Zip Archive
76 | *
77 | * After the callback is executed the ZIP archive is closed and saved
78 | *
79 | * @param string The zip file path
80 | * @param callable The function to execute when the zip file is opened. This function receive a ZipArchive instance as argument
81 | * @return string The zip file path
82 | */
83 | public static function tap($path, $callback)
84 | {
85 | $za = new ZipArchive;
86 | $za->open($path);
87 |
88 | try {
89 | $callback($za);
90 | } catch (Exception $ex) {
91 | throw $ex;
92 | } finally {
93 | $za->close();
94 | }
95 |
96 | return $path;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tests/Concern/GeneratesData.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Concern;
23 |
24 | trait GeneratesData
25 | {
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Concern/SetupIntegrationTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Concern;
23 |
24 | use OneOffTech\GeoServer\GeoServer;
25 | use OneOffTech\GeoServer\Auth\Authentication;
26 |
27 | trait SetupIntegrationTest
28 | {
29 | /**
30 | * @var \OneOffTech\GeoServer\GeoServer
31 | */
32 | protected $geoserver = null;
33 |
34 | protected function setUp(): void
35 | {
36 | parent::setUp();
37 |
38 | $url = getenv('GEOSERVER_URL');
39 | $workspace = getenv('GEOSERVER_WORKSPACE');
40 |
41 | if (empty($url)) {
42 | $this->markTestSkipped('The GEOSERVER_URL is not configured.');
43 | }
44 |
45 | $auth = new Authentication(getenv('GEOSERVER_USER'), getenv('GEOSERVER_PASSWORD'));
46 |
47 | $this->geoserver = GeoServer::build($url, $workspace, $auth);
48 |
49 | $this->geoserver->createWorkspace();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Integration/GeoServerCoverageStoresTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Integration;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\GeoFile;
26 | use OneOffTech\GeoServer\GeoType;
27 | use Tests\Concern\SetupIntegrationTest;
28 | use OneOffTech\GeoServer\Models\Coverage;
29 | use OneOffTech\GeoServer\Models\CoverageStore;
30 | use OneOffTech\GeoServer\Exception\StoreNotFoundException;
31 |
32 | class GeoServerCoverageStoresTest extends TestCase
33 | {
34 | use SetupIntegrationTest;
35 |
36 | public function test_geotiff_can_be_uploaded()
37 | {
38 | $storeName = 'geotiff_test';
39 | $data = GeoFile::from(__DIR__.'/../fixtures/geotiff.tiff')->name($storeName);
40 |
41 | $coverage = $this->geoserver->upload($data);
42 |
43 | $this->assertInstanceOf(Coverage::class, $coverage);
44 | $this->assertEquals(GeoType::RASTER, $coverage->type());
45 | $this->assertEquals("geotiff_test", $coverage->name);
46 | $this->assertEquals("geotiff_test", $coverage->title);
47 | $this->assertEquals("geotiff_test", $coverage->nativeName);
48 | $this->assertEquals("GeoTIFF", $coverage->nativeFormat);
49 | $this->assertFalse($coverage->skipNumberMatched);
50 | $this->assertFalse($coverage->circularArcPresent);
51 | $this->assertNotNull($coverage->store);
52 | $this->assertNotNull($coverage->keywords);
53 | $this->assertNotNull($coverage->nativeBoundingBox);
54 | $this->assertNotNull($coverage->boundingBox);
55 | $this->assertNotEmpty($coverage->interpolationMethods);
56 | $this->assertEquals(78999, $coverage->nativeBoundingBox->minX);
57 | $this->assertEquals(1412948.0000000002, $coverage->nativeBoundingBox->minY);
58 | $this->assertEquals(101839, $coverage->nativeBoundingBox->maxX);
59 | $this->assertEquals(1439268.0000000002, $coverage->nativeBoundingBox->maxY);
60 | $this->assertEquals(-83.64980947326015, $coverage->boundingBox->minX);
61 | $this->assertEquals(42.724764597615966, $coverage->boundingBox->minY);
62 | $this->assertEquals(-83.36533095896407, $coverage->boundingBox->maxX);
63 | $this->assertEquals(42.96491963803106, $coverage->boundingBox->maxY);
64 | $this->assertEquals("EPSG:4326", $coverage->boundingBox->crs);
65 |
66 | return $storeName;
67 | }
68 |
69 | /**
70 | * @depends test_geotiff_can_be_uploaded
71 | */
72 | public function test_coveragestore_can_be_retrieved_by_name($coveragestoreName)
73 | {
74 | $coveragestore = $this->geoserver->coveragestore($coveragestoreName);
75 |
76 | $this->assertInstanceOf(CoverageStore::class, $coveragestore);
77 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $coveragestore->workspace);
78 | $this->assertEmpty($coveragestore->href);
79 | $this->assertEquals('file:data/test/geotiff_test/geotiff_test.geotiff', $coveragestore->url);
80 | $this->assertEquals('GeoTIFF', $coveragestore->type);
81 | $this->assertTrue($coveragestore->enabled);
82 | $this->assertTrue($coveragestore->exists);
83 | $this->assertCount(1, $coveragestore->coverages);
84 |
85 | return $coveragestoreName;
86 | }
87 |
88 | /**
89 | * @depends test_coveragestore_can_be_retrieved_by_name
90 | */
91 | public function test_coveragestores_are_retrieved($coveragestoreName)
92 | {
93 | $coveragestores = $this->geoserver->coveragestores();
94 |
95 | $this->assertContainsOnlyInstancesOf(CoverageStore::class, $coveragestores);
96 |
97 | return $coveragestoreName;
98 | }
99 |
100 | /**
101 | * @depends test_coveragestores_are_retrieved
102 | */
103 | public function test_coveragestore_can_be_deleted($coveragestoreName)
104 | {
105 | $coveragestore = $this->geoserver->deleteCoveragestore($coveragestoreName);
106 |
107 | $this->assertInstanceOf(CoverageStore::class, $coveragestore);
108 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $coveragestore->workspace);
109 | $this->assertTrue($coveragestore->enabled);
110 | $this->assertFalse($coveragestore->exists);
111 |
112 | return $coveragestoreName;
113 | }
114 |
115 | public function test_non_existing_coveragestore_cannot_be_retrieved()
116 | {
117 | $this->expectException(StoreNotFoundException::class);
118 |
119 | $coveragestore = $this->geoserver->coveragestore('some_name');
120 | }
121 |
122 | public function test_geotiff_upload_and_deleted()
123 | {
124 | $storeName = 'geotiff_test';
125 | $data = GeoFile::from(__DIR__.'/../fixtures/geotiff.tiff')->name($storeName);
126 |
127 | $feature = $this->geoserver->upload($data);
128 |
129 | $this->assertInstanceOf(Coverage::class, $feature);
130 |
131 | $this->assertTrue($this->geoserver->exist($data), "Data not existing after upload");
132 |
133 | $deleteResult = $this->geoserver->remove($data);
134 |
135 | $this->assertTrue($deleteResult, "GeoFile not deleted");
136 |
137 | $this->assertFalse($this->geoserver->exist($data), "Data still exists after remove");
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/tests/Integration/GeoServerStylesTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Integration;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\StyleFile;
26 | use OneOffTech\GeoServer\Models\Style;
27 | use Tests\Concern\SetupIntegrationTest;
28 | use OneOffTech\GeoServer\Exception\StyleNotFoundException;
29 |
30 | class GeoServerStylesTest extends TestCase
31 | {
32 | use SetupIntegrationTest;
33 |
34 | public function test_styles_can_be_uploaded()
35 | {
36 | $styleName = 'style_test';
37 | $data = StyleFile::from(__DIR__.'/../fixtures/style.sld')->name($styleName);
38 |
39 | $style = $this->geoserver->uploadStyle($data);
40 |
41 | $this->assertInstanceOf(Style::class, $style);
42 | $this->assertEquals($styleName, $style->name);
43 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $style->workspace);
44 | $this->assertEquals('style_test.sld', $style->filename);
45 | $this->assertEquals('sld', $style->format);
46 | $this->assertEquals('1.0.0', $style->version);
47 | $this->assertTrue($style->exists, "Style not existing");
48 |
49 | return $styleName;
50 | }
51 |
52 | /**
53 | * @depends test_styles_can_be_uploaded
54 | */
55 | public function test_style_can_be_retrieved_by_name($styleName = 'style_test')
56 | {
57 | $style = $this->geoserver->style($styleName);
58 |
59 | $this->assertInstanceOf(Style::class, $style);
60 | $this->assertEquals($styleName, $style->name);
61 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $style->workspace);
62 | $this->assertEquals('style_test.sld', $style->filename);
63 | $this->assertEquals('sld', $style->format);
64 | $this->assertEquals('1.0.0', $style->version);
65 | $this->assertTrue($style->exists, "Style not existing");
66 |
67 | return $styleName;
68 | }
69 |
70 | /**
71 | * @depends test_style_can_be_retrieved_by_name
72 | */
73 | public function test_styles_are_retrieved($datastoreName)
74 | {
75 | $styles = $this->geoserver->styles();
76 |
77 | $this->assertContainsOnlyInstancesOf(Style::class, $styles);
78 |
79 | return $datastoreName;
80 | }
81 |
82 | /**
83 | * @depends test_styles_are_retrieved
84 | */
85 | public function test_style_can_be_deleted($styleName)
86 | {
87 | $style = $this->geoserver->removeStyle($styleName);
88 |
89 | $this->assertInstanceOf(Style::class, $style);
90 | $this->assertEquals($styleName, $style->name);
91 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $style->workspace);
92 | $this->assertEquals('style_test.sld', $style->filename);
93 | $this->assertEquals('sld', $style->format);
94 | $this->assertEquals('1.0.0', $style->version);
95 | $this->assertFalse($style->exists, "Style still exists after deletion");
96 | }
97 |
98 | public function test_non_existing_style_cannot_be_retrieved()
99 | {
100 | $this->expectException(StyleNotFoundException::class);
101 |
102 | $style = $this->geoserver->style('some_name');
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/Integration/GeoServerVersionRetrievalTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Integration;
23 |
24 | use Tests\TestCase;
25 | use Tests\Concern\SetupIntegrationTest;
26 |
27 | class GeoServerVersionRetrievalTest extends TestCase
28 | {
29 | use SetupIntegrationTest;
30 |
31 | public function test_geoserver_version_is_retrieved()
32 | {
33 | $version = $this->geoserver->version();
34 |
35 | $this->assertNotEmpty($version);
36 | $this->assertTrue(is_string($version));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Integration/GeoServerWmsTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Integration;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\GeoFile;
26 | use Tests\Concern\SetupIntegrationTest;
27 | use OneOffTech\GeoServer\Support\ImageResponse;
28 | use Tests\Support\ImageDifference;
29 |
30 | class GeoServerWmsTest extends TestCase
31 | {
32 | use SetupIntegrationTest;
33 |
34 | public function test_wms_url_is_generated_for_shapefile()
35 | {
36 | $datastoreName = 'shapefile_test';
37 | $file = GeoFile::from(__DIR__.'/../fixtures/shapefile.shp')->name($datastoreName);
38 |
39 | $resource = $this->geoserver->upload($file);
40 |
41 | $expectedParams = "layers=".getenv('GEOSERVER_WORKSPACE').":shapefile_test&bbox=314618.446,5536155.822,315358.647,5536652.114&styles=&width=640&height=480&srs=EPSG:4326&format=image%2Fpng";
42 | $expectedMapUrl = sprintf("%s%s/wms?service=WMS&version=1.1.0&request=GetMap&%s", getenv('GEOSERVER_URL'), getenv('GEOSERVER_WORKSPACE'), $expectedParams);
43 |
44 | $mapUrlGeoFile = $this->geoserver->wmsMapUrl($file);
45 | $mapUrlResource = $this->geoserver->wmsMapUrl($resource);
46 |
47 | $this->assertEquals($expectedMapUrl, $mapUrlGeoFile);
48 | $this->assertEquals($mapUrlGeoFile, $mapUrlResource);
49 |
50 | $deleteResult = $this->geoserver->remove($file);
51 | }
52 |
53 | public function test_wms_url_is_generated_for_geotiff()
54 | {
55 | $datastoreName = 'geotiff_test';
56 | $file = GeoFile::from(__DIR__.'/../fixtures/geotiff.tiff')->name($datastoreName);
57 |
58 | $resource = $this->geoserver->upload($file);
59 |
60 | $expectedParams = "layers=".getenv('GEOSERVER_WORKSPACE').":geotiff_test&bbox=-83.64980947326,42.724764597616,-83.365330958964,42.964919638031&styles=&width=640&height=480&srs=EPSG:4326&format=image%2Fpng";
61 | $expectedMapUrl = sprintf("%s%s/wms?service=WMS&version=1.1.0&request=GetMap&%s", getenv('GEOSERVER_URL'), getenv('GEOSERVER_WORKSPACE'), $expectedParams);
62 |
63 | $mapUrlGeoFile = $this->geoserver->wmsMapUrl($file);
64 | $mapUrlResource = $this->geoserver->wmsMapUrl($resource);
65 |
66 | $this->assertEquals($expectedMapUrl, $mapUrlGeoFile);
67 | $this->assertEquals($mapUrlGeoFile, $mapUrlResource);
68 |
69 | $deleteResult = $this->geoserver->remove($file);
70 | }
71 |
72 | public function test_shapefile_thumbnail()
73 | {
74 | $datastoreName = 'shapefile_test';
75 | $file = GeoFile::from(__DIR__.'/../fixtures/shapefile.shp')->name($datastoreName);
76 |
77 | $resource = $this->geoserver->upload($file);
78 |
79 | $thumbnail = $this->geoserver->thumbnail($resource);
80 |
81 | $this->assertInstanceOf(ImageResponse::class, $thumbnail);
82 | $this->assertEquals('image/png', $thumbnail->mimeType());
83 |
84 | $imageAsString = $thumbnail->asString();
85 |
86 | list($width, $height) = getimagesizefromstring($imageAsString);
87 |
88 | $this->assertEquals(300, $width);
89 | $this->assertEquals(300, $height);
90 |
91 | // compare the image difference against a reference thumbnail
92 |
93 | file_put_contents(__DIR__.'/../fixtures/shapefile_thumbnail_from_geoserver.png', $thumbnail->asString());
94 |
95 | $differencePercentage = ImageDifference::calculate(
96 | __DIR__.'/../fixtures/shapefile_thumbnail.png',
97 | __DIR__.'/../fixtures/shapefile_thumbnail_from_geoserver.png'
98 | );
99 |
100 | unlink(__DIR__.'/../fixtures/shapefile_thumbnail_from_geoserver.png');
101 | $deleteResult = $this->geoserver->remove($file);
102 |
103 | // considering a 20% difference as acceptable
104 | $this->assertTrue($differencePercentage < 20);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/Integration/GeoServerWorkspaceTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Integration;
23 |
24 | use Tests\TestCase;
25 | use Tests\Concern\SetupIntegrationTest;
26 | use OneOffTech\GeoServer\Models\Workspace;
27 |
28 | class GeoServerWorkspaceTest extends TestCase
29 | {
30 | use SetupIntegrationTest;
31 |
32 | public function test_workspace_details_are_retrieved()
33 | {
34 | $workspace = $this->geoserver->workspace();
35 |
36 | $this->assertInstanceOf(Workspace::class, $workspace);
37 | $this->assertEquals(getenv('GEOSERVER_WORKSPACE'), $workspace->name);
38 | $this->assertNotEmpty($workspace->dataStores);
39 | $this->assertNotEmpty($workspace->coverageStores);
40 | $this->assertNotEmpty($workspace->wmsStores);
41 | $this->assertNotEmpty($workspace->wmtsStores);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Support/ImageDifference.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Support;
23 |
24 | use InvalidArgumentException;
25 |
26 | class ImageDifference
27 | {
28 |
29 | /**
30 | * Calculate the difference between two images
31 | *
32 | * @param string $expected The path of the image ground truth
33 | * @param string $actual The actual image to compare
34 | * @return float the percentage of difference between the two images
35 | */
36 | public static function calculate($expected, $actual)
37 | {
38 | list($expectedImage, $expectedImagewidth, $expectedImageHeight) = static::imageFromFile($expected);
39 |
40 | list($actualImage) = static::imageFromFile($actual);
41 |
42 | $differenceBitmap = static::calculateDifference(
43 | $expectedImage,
44 | $actualImage,
45 | $expectedImagewidth,
46 | $expectedImageHeight
47 | );
48 |
49 | return static::calculateDifferencePercentage($differenceBitmap, $expectedImagewidth, $expectedImageHeight);
50 | }
51 |
52 | /**
53 | * Load a bitmap array from image path.
54 | *
55 | * @param string $path
56 | *
57 | * @return array
58 | *
59 | * @throws InvalidArgumentException
60 | */
61 | private static function imageFromFile($path)
62 | {
63 | $info = getimagesize($path);
64 | $type = $info[2];
65 |
66 | $image = null;
67 |
68 | if ($type == IMAGETYPE_JPEG) {
69 | $image = imagecreatefromjpeg($path);
70 | }
71 | if ($type == IMAGETYPE_GIF) {
72 | $image = imagecreatefromgif($path);
73 | }
74 | if ($type == IMAGETYPE_PNG) {
75 | $image = imagecreatefrompng($path);
76 | }
77 |
78 | if (! $image) {
79 | throw new InvalidArgumentException("invalid image [{$path}]");
80 | }
81 |
82 | $width = imagesx($image);
83 | $height = imagesy($image);
84 |
85 | $bitmap = [];
86 |
87 | for ($y = 0; $y < $height; $y++) {
88 | $bitmap[$y] = [];
89 |
90 | for ($x = 0; $x < $width; $x++) {
91 | $color = imagecolorat($image, $x, $y);
92 |
93 | $bitmap[$y][$x] = [
94 | "r" => ($color >> 16) & 0xFF,
95 | "g" => ($color >> 8) & 0xFF,
96 | "b" => $color & 0xFF
97 | ];
98 | }
99 | }
100 |
101 | return [$bitmap, $width, $height];
102 | }
103 |
104 | /**
105 | * Difference between all pixels of two images.
106 | *
107 | * @param array $bitmap1
108 | * @param array $bitmap2
109 | * @param int $width
110 | * @param int $height
111 | *
112 | * @return array
113 | */
114 | private static function calculateDifference(array $bitmap1, array $bitmap2, $width, $height)
115 | {
116 | $new = [];
117 |
118 | for ($y = 0; $y < $height; $y++) {
119 | $new[$y] = [];
120 |
121 | for ($x = 0; $x < $width; $x++) {
122 | $new[$y][$x] = static::euclideanDistance(
123 | $bitmap1[$y][$x],
124 | $bitmap2[$y][$x]
125 | );
126 | }
127 | }
128 |
129 | return $new;
130 | }
131 |
132 | /**
133 | * RGB color distance for the same pixel in two images.
134 | *
135 | * @link https://en.wikipedia.org/wiki/Euclidean_distance
136 | *
137 | * @param array $p
138 | * @param array $q
139 | *
140 | * @return float
141 | */
142 | private static function euclideanDistance(array $p, array $q)
143 | {
144 | $r = $p["r"] - $q["r"];
145 | $r *= $r;
146 |
147 | $g = $p["g"] - $q["g"];
148 | $g *= $g;
149 |
150 | $b = $p["b"] - $q["b"];
151 | $b *= $b;
152 |
153 | return (float) sqrt($r + $g + $b);
154 | }
155 |
156 | /**
157 | * Percentage of different pixels in the bitmap.
158 | *
159 | * @param array $bitmap
160 | * @param int $width
161 | * @param int $height
162 | *
163 | * @return float
164 | */
165 | private static function calculateDifferencePercentage(array $bitmap, $width, $height)
166 | {
167 | $total = 0;
168 | $different = 0;
169 |
170 | for ($y = 0; $y < $height; $y++) {
171 | for ($x = 0; $x < $width; $x++) {
172 | $total++;
173 |
174 | if ($bitmap[$y][$x] > 0) {
175 | $different++;
176 | }
177 | }
178 | }
179 |
180 | return (float) (($different / $total) * 100);
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests;
23 |
24 | use PHPUnit\Framework\TestCase as BaseTestCase;
25 |
26 | abstract class TestCase extends BaseTestCase
27 | {
28 | use Concern\GeneratesData;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Unit/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use GuzzleHttp\Psr7\Request;
26 | use Psr\Http\Message\RequestInterface;
27 | use OneOffTech\GeoServer\Auth\Authentication;
28 | use OneOffTech\GeoServer\Auth\NullAuthentication;
29 |
30 | class AuthenticationTest extends TestCase
31 | {
32 | public function test_authentication_appends_authorization_header()
33 | {
34 | $auth = new Authentication('username', 'password');
35 |
36 | $request = new Request('GET', 'http://geoserver.local');
37 |
38 | $request_with_authentication = $auth->authenticate($request);
39 |
40 | $this->assertInstanceOf(RequestInterface::class, $request_with_authentication);
41 | $this->assertEquals([sprintf('Basic %s', base64_encode("username:password"))], $request_with_authentication->getHeader('Authorization'));
42 | }
43 |
44 | public function test_null_authentication_do_not_append_authorization_header()
45 | {
46 | $auth = new NullAuthentication();
47 |
48 | $request = new Request('GET', 'http://geoserver.local');
49 |
50 | $request_without_authentication = $auth->authenticate($request);
51 |
52 | $this->assertInstanceOf(RequestInterface::class, $request_without_authentication);
53 | $this->assertEquals(['Host' => ['geoserver.local']], $request_without_authentication->getHeaders());
54 | $this->assertEmpty($request_without_authentication->getHeader('Authorization'));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Unit/GeoFileTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\GeoFile;
26 | use OneOffTech\GeoServer\GeoType;
27 | use OneOffTech\GeoServer\GeoFormat;
28 |
29 | class GeoFileTest extends TestCase
30 | {
31 | public function supported_files()
32 | {
33 | return [
34 | [__DIR__.'/../fixtures/shapefile.shp'],
35 | [__DIR__.'/../fixtures/shapefile.zip'],
36 | [__DIR__.'/../fixtures/geotiff.tiff'],
37 | [__DIR__.'/../fixtures/empty.gpkg'],
38 | ];
39 | }
40 |
41 | public function unsupported_files()
42 | {
43 | return [
44 | [__DIR__.'/../fixtures/plain.json'],
45 | [__DIR__.'/../fixtures/plain.zip'],
46 | [__DIR__.'/../fixtures/tiff.tif'],
47 | [__DIR__.'/../fixtures/geojson.geojson'],
48 | [__DIR__.'/../fixtures/geojson-in-plain-json.json'],
49 | [__DIR__.'/../fixtures/kml.kml'],
50 | [__DIR__.'/../fixtures/kmz.kmz'],
51 | [__DIR__.'/../fixtures/gpx.gpx'],
52 | ];
53 | }
54 |
55 | /**
56 | * @dataProvider supported_files
57 | */
58 | public function test_supported_function_identifies_supported_files($file)
59 | {
60 | $this->assertTrue(GeoFile::isSupported($file));
61 | }
62 |
63 | /**
64 | * @dataProvider unsupported_files
65 | */
66 | public function test_supported_function_reject_unsupported_files($file)
67 | {
68 | $this->assertFalse(GeoFile::isSupported($file));
69 | }
70 |
71 | public function test_shapefile_is_recognized()
72 | {
73 | $file = GeoFile::from(__DIR__.'/../fixtures/shapefile.shp');
74 |
75 | $this->assertInstanceOf(GeoFile::class, $file);
76 | $this->assertEquals(GeoFormat::SHAPEFILE, $file->format);
77 | $this->assertEquals(GeoType::VECTOR, $file->type);
78 | $this->assertEquals('application/octet-stream', $file->mimeType);
79 | $this->assertEquals('shp', $file->extension);
80 | $this->assertEquals('shapefile.shp', $file->name);
81 | $this->assertEquals($file->originalName, $file->name);
82 | }
83 |
84 | public function test_shapefile_packed_in_zip_is_recognized()
85 | {
86 | $file = GeoFile::from(__DIR__.'/../fixtures/shapefile.zip');
87 |
88 | $this->assertInstanceOf(GeoFile::class, $file);
89 | $this->assertEquals(GeoFormat::SHAPEFILE_ZIP, $file->format);
90 | $this->assertEquals(GeoType::VECTOR, $file->type);
91 | $this->assertEquals('application/zip', $file->mimeType);
92 | $this->assertEquals('zip', $file->extension);
93 | $this->assertEquals('shapefile.zip', $file->name);
94 | $this->assertEquals($file->originalName, $file->name);
95 | }
96 |
97 | public function test_geotiff_is_recognized()
98 | {
99 | $file = GeoFile::from(__DIR__.'/../fixtures/geotiff.tiff');
100 |
101 | $this->assertInstanceOf(GeoFile::class, $file);
102 | $this->assertEquals(GeoFormat::GEOTIFF, $file->format);
103 | $this->assertEquals(GeoType::RASTER, $file->type);
104 | $this->assertEquals('image/tiff', $file->mimeType);
105 | $this->assertEquals('tiff', $file->extension);
106 | $this->assertEquals('geotiff.tiff', $file->name);
107 | $this->assertEquals($file->originalName, $file->name);
108 | }
109 |
110 | public function test_geopackage_is_recognized()
111 | {
112 | $file = GeoFile::from(__DIR__.'/../fixtures/empty.gpkg');
113 |
114 | $this->assertInstanceOf(GeoFile::class, $file);
115 | $this->assertEquals(GeoFormat::GEOPACKAGE, $file->format);
116 | $this->assertEquals(GeoType::VECTOR, $file->type);
117 | $this->assertEquals('application/geopackage+sqlite3', $file->mimeType);
118 | $this->assertEquals('gpkg', $file->extension);
119 | $this->assertEquals('empty.gpkg', $file->name);
120 | $this->assertEquals($file->originalName, $file->name);
121 | }
122 |
123 | public function test_copy_to_temporary()
124 | {
125 | $file = GeoFile::from(__DIR__.'/../fixtures/buildings.zip');
126 |
127 | $this->assertInstanceOf(GeoFile::class, $file);
128 | $this->assertEquals(GeoFormat::SHAPEFILE_ZIP, $file->format);
129 | $this->assertEquals(GeoType::VECTOR, $file->type);
130 | $this->assertEquals('application/zip', $file->mimeType);
131 | $this->assertEquals('zip', $file->extension);
132 | $this->assertEquals('buildings.zip', $file->name);
133 | $this->assertEquals($file->originalName, $file->name);
134 |
135 | $copy = $file->copy();
136 |
137 | $this->assertInstanceOf(GeoFile::class, $copy);
138 | $this->assertEquals(GeoFormat::SHAPEFILE_ZIP, $copy->format);
139 | $this->assertEquals(GeoType::VECTOR, $copy->type);
140 | $this->assertEquals('application/zip', $copy->mimeType);
141 | $this->assertNotEquals($copy->originalName, $copy->name);
142 | $this->assertEquals($file->name, $copy->name);
143 | $this->assertEquals($file->content(), $copy->content());
144 |
145 | unlink($copy->path());
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/tests/Unit/GeoServerClientInstantiationTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\GeoServer;
26 | use OneOffTech\GeoServer\Auth\Authentication;
27 |
28 | class GeoServerClientInstantiationTest extends TestCase
29 | {
30 | public function test_client_can_be_created_with_authentication()
31 | {
32 | $auth = new Authentication('username', 'password');
33 |
34 | $url = 'https://geoserver.local/';
35 | $workspace = 'default';
36 |
37 | $client = GeoServer::build($url, $workspace, $auth);
38 |
39 | $this->assertInstanceOf(GeoServer::class, $client);
40 | }
41 |
42 | public function test_client_can_be_created_with_no_authentication()
43 | {
44 | $url = 'https://geoserver.local/';
45 | $workspace = 'default';
46 |
47 | $client = GeoServer::build($url, $workspace);
48 |
49 | $this->assertInstanceOf(GeoServer::class, $client);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Unit/OptionsTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use Http\Client\HttpClient;
26 | use JMS\Serializer\Serializer;
27 | use Http\Message\MessageFactory;
28 | use OneOffTech\GeoServer\Options;
29 | use Http\Client\Common\PluginClient;
30 | use OneOffTech\GeoServer\Auth\NullAuthentication;
31 |
32 | class OptionsTest extends TestCase
33 | {
34 | public function test_options_contains_expected_configuration()
35 | {
36 | $auth = new NullAuthentication();
37 |
38 | $options = new Options($auth);
39 |
40 | $this->assertInstanceOf(PluginClient::class, $options->httpClient, "http client is not instantiated");
41 | $this->assertInstanceOf(HttpClient::class, $options->httpClient, "http client is not instantiated");
42 | $this->assertEquals($auth, $options->authentication, "authentication not set");
43 | $this->assertInstanceOf(MessageFactory::class, $options->messageFactory, "message factory is not instantiated");
44 | $this->assertInstanceOf(Serializer::class, $options->serializer, "serializer is not instantiated");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Unit/ResponseHelperTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\Http\ResponseHelper;
26 |
27 | class ResponseHelperTest extends TestCase
28 | {
29 | public function testAssociativeArrayIsRecognized()
30 | {
31 | $array = [
32 | 'hello' => 'value',
33 | 'key' => 'value',
34 | ];
35 |
36 | $this->assertTrue(ResponseHelper::isAssociativeArray($array));
37 | }
38 |
39 | public function testMixedArrayIsNotRecognizedAsAssociative()
40 | {
41 | $array = [
42 | 'zero' => 'value',
43 | 0 => 'value',
44 | 'key' => 'value',
45 | ];
46 |
47 | $this->assertFalse(ResponseHelper::isAssociativeArray($array));
48 | }
49 |
50 | public function testIndexArrayIsNotRecognizedAsAssociative()
51 | {
52 | $array = [
53 | 'value1',
54 | 'value2',
55 | ];
56 |
57 | $this->assertFalse(ResponseHelper::isAssociativeArray($array));
58 | }
59 |
60 | public function testNullAndEmptyAreNotRecognizedAsAssociative()
61 | {
62 | $this->assertFalse(ResponseHelper::isAssociativeArray(null));
63 | $this->assertFalse(ResponseHelper::isAssociativeArray([]));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Unit/StyleFileTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use Tests\TestCase;
25 | use OneOffTech\GeoServer\StyleFile;
26 |
27 | class StyleFileTest extends TestCase
28 | {
29 | public function test_style_is_supported()
30 | {
31 | $supported = StyleFile::isSupported(__DIR__.'/../fixtures/style.sld');
32 | $this->assertTrue($supported);
33 | }
34 |
35 | public function test_style_file_load()
36 | {
37 | $styleName = 'style_test';
38 | $file = StyleFile::from(__DIR__.'/../fixtures/style.sld')->name($styleName);
39 |
40 | $this->assertInstanceOf(StyleFile::class, $file);
41 | $this->assertEquals('application/vnd.ogc.sld+xml', $file->mimeType);
42 | $this->assertEquals('sld', $file->extension);
43 | $this->assertEquals('style_test', $file->name);
44 | $this->assertEquals('style.sld', $file->originalName);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Unit/WmsOptionsTest.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | namespace Tests\Unit;
23 |
24 | use LogicException;
25 | use Tests\TestCase;
26 | use InvalidArgumentException;
27 | use OneOffTech\GeoServer\Models\BoundingBox;
28 | use OneOffTech\GeoServer\Support\WmsOptions;
29 |
30 | class WmsOptionsTest extends TestCase
31 | {
32 | public function test_default_values_are_used()
33 | {
34 | $bbox = new BoundingBox();
35 | $bbox->minX = -83.64980947326015;
36 | $bbox->minY = 42.724764597615966;
37 | $bbox->maxX = -83.36533095896407;
38 | $bbox->maxY = 42.96491963803106;
39 |
40 | $options = (new WmsOptions())->layers('workspace:layer')->boundingBox($bbox);
41 |
42 | $this->assertInstanceOf(WmsOptions::class, $options);
43 |
44 | $this->assertEquals([
45 | 'request' => "GetMap",
46 | 'version' => "1.1.0",
47 | 'format' => "image/png",
48 | 'layers' => ["workspace:layer"],
49 | 'bbox' => [-83.64980947326015, 42.724764597615966, -83.36533095896407, 42.96491963803106],
50 | 'srs' => "EPSG:4326",
51 | 'width' => 640,
52 | 'height' => 480,
53 | 'styles' => [],
54 | ], $options->toArray());
55 | }
56 |
57 | public function test_not_setting_layer_generate_exception_when_serializing()
58 | {
59 | $options = (new WmsOptions());
60 |
61 | $this->expectException(LogicException::class);
62 |
63 | $options->toArray();
64 | }
65 |
66 | public function test_not_setting_bounding_box_generate_exception_when_serializing()
67 | {
68 | $options = (new WmsOptions())->layers('workspace:layer');
69 |
70 | $this->expectException(LogicException::class);
71 |
72 | $options->toArray();
73 | }
74 |
75 | public function test_setting_wrong_format_raises_exception()
76 | {
77 | $this->expectException(InvalidArgumentException::class);
78 |
79 | $options = (new WmsOptions())->format('workspace:layer');
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ## Test environment
2 | ## This docker compose starts the necessary services to run integration tests
3 |
4 | version: "2.1"
5 |
6 | networks:
7 | internal:
8 |
9 | services:
10 | geoserver:
11 | image: "kartoza/geoserver:${GEOSERVER_TAG:-2.21.0}"
12 | env_file:
13 | - geoserver.env
14 | ports:
15 | - "8600:8080"
16 | networks:
17 | - internal
18 |
--------------------------------------------------------------------------------
/tests/fixtures/buildings.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/buildings.zip
--------------------------------------------------------------------------------
/tests/fixtures/empty.gpkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/empty.gpkg
--------------------------------------------------------------------------------
/tests/fixtures/geojson-in-plain-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "features": [
4 | {
5 | "type": "Feature",
6 | "geometry": {
7 | "type": "Point",
8 | "coordinates": [
9 | -80.870885,
10 | 35.215151
11 | ]
12 | },
13 | "properties": {
14 | "name": "ABBOTT NEIGHBORHOOD PARK",
15 | "address": "1300 SPRUCE ST"
16 | }
17 | },
18 | {
19 | "type": "Feature",
20 | "geometry": {
21 | "type": "Point",
22 | "coordinates": [
23 | -80.837753,
24 | 35.249801
25 | ]
26 | },
27 | "properties": {
28 | "name": "DOUBLE OAKS CENTER",
29 | "address": "1326 WOODWARD AV"
30 | }
31 | },
32 | {
33 | "type": "Feature",
34 | "geometry": {
35 | "type": "Point",
36 | "coordinates": [
37 | -80.83827,
38 | 35.256747
39 | ]
40 | },
41 | "properties": {
42 | "name": "DOUBLE OAKS NEIGHBORHOOD PARK",
43 | "address": "2605 DOUBLE OAKS RD"
44 | }
45 | },
46 | {
47 | "type": "Feature",
48 | "geometry": {
49 | "type": "Point",
50 | "coordinates": [
51 | -80.836977,
52 | 35.257517
53 | ]
54 | },
55 | "properties": {
56 | "name": "DOUBLE OAKS POOL",
57 | "address": "1200 NEWLAND RD"
58 | }
59 | },
60 | {
61 | "type": "Feature",
62 | "geometry": {
63 | "type": "Point",
64 | "coordinates": [
65 | -80.816476,
66 | 35.401487
67 | ]
68 | },
69 | "properties": {
70 | "name": "DAVID B. WAYMER FLYING REGIONAL PARK",
71 | "address": "15401 HOLBROOKS RD"
72 | }
73 | },
74 | {
75 | "type": "Feature",
76 | "geometry": {
77 | "type": "Point",
78 | "coordinates": [
79 | -80.835564,
80 | 35.399172
81 | ]
82 | },
83 | "properties": {
84 | "name": "DAVID B. WAYMER COMMUNITY PARK",
85 | "address": "302 HOLBROOKS RD"
86 | }
87 | },
88 | {
89 | "type": "Feature",
90 | "geometry": {
91 | "type": "Polygon",
92 | "coordinates": [
93 | [
94 | [
95 | -80.724878,
96 | 35.265454
97 | ],
98 | [
99 | -80.722646,
100 | 35.260338
101 | ],
102 | [
103 | -80.720329,
104 | 35.260618
105 | ],
106 | [
107 | -80.718698,
108 | 35.260267
109 | ],
110 | [
111 | -80.715093,
112 | 35.260548
113 | ],
114 | [
115 | -80.71681,
116 | 35.255361
117 | ],
118 | [
119 | -80.710887,
120 | 35.255361
121 | ],
122 | [
123 | -80.703248,
124 | 35.265033
125 | ],
126 | [
127 | -80.704793,
128 | 35.268397
129 | ],
130 | [
131 | -80.70857,
132 | 35.268257
133 | ],
134 | [
135 | -80.712518,
136 | 35.270359
137 | ],
138 | [
139 | -80.715179,
140 | 35.267696
141 | ],
142 | [
143 | -80.721359,
144 | 35.267276
145 | ],
146 | [
147 | -80.724878,
148 | 35.265454
149 | ]
150 | ]
151 | ]
152 | },
153 | "properties": {
154 | "name": "Plaza Road Park"
155 | }
156 | }
157 | ]
158 | }
--------------------------------------------------------------------------------
/tests/fixtures/geojson.geojson:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "features": [
4 | {
5 | "type": "Feature",
6 | "geometry": {
7 | "type": "Point",
8 | "coordinates": [
9 | -80.870885,
10 | 35.215151
11 | ]
12 | },
13 | "properties": {
14 | "name": "ABBOTT NEIGHBORHOOD PARK",
15 | "address": "1300 SPRUCE ST"
16 | }
17 | },
18 | {
19 | "type": "Feature",
20 | "geometry": {
21 | "type": "Point",
22 | "coordinates": [
23 | -80.837753,
24 | 35.249801
25 | ]
26 | },
27 | "properties": {
28 | "name": "DOUBLE OAKS CENTER",
29 | "address": "1326 WOODWARD AV"
30 | }
31 | },
32 | {
33 | "type": "Feature",
34 | "geometry": {
35 | "type": "Point",
36 | "coordinates": [
37 | -80.83827,
38 | 35.256747
39 | ]
40 | },
41 | "properties": {
42 | "name": "DOUBLE OAKS NEIGHBORHOOD PARK",
43 | "address": "2605 DOUBLE OAKS RD"
44 | }
45 | },
46 | {
47 | "type": "Feature",
48 | "geometry": {
49 | "type": "Point",
50 | "coordinates": [
51 | -80.836977,
52 | 35.257517
53 | ]
54 | },
55 | "properties": {
56 | "name": "DOUBLE OAKS POOL",
57 | "address": "1200 NEWLAND RD"
58 | }
59 | },
60 | {
61 | "type": "Feature",
62 | "geometry": {
63 | "type": "Point",
64 | "coordinates": [
65 | -80.816476,
66 | 35.401487
67 | ]
68 | },
69 | "properties": {
70 | "name": "DAVID B. WAYMER FLYING REGIONAL PARK",
71 | "address": "15401 HOLBROOKS RD"
72 | }
73 | },
74 | {
75 | "type": "Feature",
76 | "geometry": {
77 | "type": "Point",
78 | "coordinates": [
79 | -80.835564,
80 | 35.399172
81 | ]
82 | },
83 | "properties": {
84 | "name": "DAVID B. WAYMER COMMUNITY PARK",
85 | "address": "302 HOLBROOKS RD"
86 | }
87 | },
88 | {
89 | "type": "Feature",
90 | "geometry": {
91 | "type": "Polygon",
92 | "coordinates": [
93 | [
94 | [
95 | -80.724878,
96 | 35.265454
97 | ],
98 | [
99 | -80.722646,
100 | 35.260338
101 | ],
102 | [
103 | -80.720329,
104 | 35.260618
105 | ],
106 | [
107 | -80.718698,
108 | 35.260267
109 | ],
110 | [
111 | -80.715093,
112 | 35.260548
113 | ],
114 | [
115 | -80.71681,
116 | 35.255361
117 | ],
118 | [
119 | -80.710887,
120 | 35.255361
121 | ],
122 | [
123 | -80.703248,
124 | 35.265033
125 | ],
126 | [
127 | -80.704793,
128 | 35.268397
129 | ],
130 | [
131 | -80.70857,
132 | 35.268257
133 | ],
134 | [
135 | -80.712518,
136 | 35.270359
137 | ],
138 | [
139 | -80.715179,
140 | 35.267696
141 | ],
142 | [
143 | -80.721359,
144 | 35.267276
145 | ],
146 | [
147 | -80.724878,
148 | 35.265454
149 | ]
150 | ]
151 | ]
152 | },
153 | "properties": {
154 | "name": "Plaza Road Park"
155 | }
156 | }
157 | ]
158 | }
--------------------------------------------------------------------------------
/tests/fixtures/geotiff.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/geotiff.tiff
--------------------------------------------------------------------------------
/tests/fixtures/geotiff_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/geotiff_thumbnail.png
--------------------------------------------------------------------------------
/tests/fixtures/gpx.gpx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GPS Exchange Format Sample from Wikipedia
6 |
7 |
8 |
9 |
10 | Example GPX Document
11 |
12 |
13 | 4.46
14 |
15 |
16 |
17 | 4.94
18 |
19 |
20 |
21 | 6.87
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/fixtures/kml.kml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | KML Samples
5 | 1
6 | Unleash your creativity with the help of these examples!
7 |
14 |
24 |
33 |
42 |
48 |
56 |
64 |
72 |
80 |
88 |
96 |
105 |
106 | Placemarks
107 | These are just some of the different kinds of placemarks with
108 | which you can mark your favorite places
109 |
110 | -122.0839597145766
111 | 37.42222904525232
112 | 0
113 | -148.4122922628044
114 | 40.5575073395506
115 | 500.6566641072245
116 |
117 |
118 | Simple placemark
119 | Attached to the ground. Intelligently places itself at the
120 | height of the underlying terrain.
121 |
122 | -122.0822035425683,37.42228990140251,0
123 |
124 |
125 |
126 |
127 | Styles and Markup
128 | 0
129 | With KML it is easy to create rich, descriptive markup to
130 | annotate and enrich your placemarks
131 |
132 | -122.0845787422371
133 | 37.42215078726837
134 | 0
135 | -148.4126777488172
136 | 40.55750733930874
137 | 365.2646826292919
138 |
139 | #noDrivingDirections
140 |
141 | Highlighted Icon
142 | 0
143 | Place your mouse over the icon to see it display the new
144 | icon
145 |
146 | -122.0856552124024
147 | 37.4224281311035
148 | 0
149 | 0
150 | 0
151 | 265.8520424250024
152 |
153 |
160 |
167 |
168 |
169 | normal
170 | #normalPlacemark
171 |
172 |
173 | highlight
174 | #highlightPlacemark
175 |
176 |
177 |
178 | Roll over this icon
179 | 0
180 | #exampleStyleMap
181 |
182 | -122.0856545755255,37.42243077405461,0
183 |
184 |
185 |
186 |
187 | Descriptive HTML
188 | 0
189 |
190 | Placemark descriptions can be enriched by using many standard HTML tags.
191 | For example:
192 |
193 | Styles:
194 | Italics,
195 | Bold]]>
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/tests/fixtures/kmz.kmz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/kmz.kmz
--------------------------------------------------------------------------------
/tests/fixtures/plain.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection"
3 | }
--------------------------------------------------------------------------------
/tests/fixtures/plain.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/plain.zip
--------------------------------------------------------------------------------
/tests/fixtures/rivers.gpkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/rivers.gpkg
--------------------------------------------------------------------------------
/tests/fixtures/shapefile.shp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/shapefile.shp
--------------------------------------------------------------------------------
/tests/fixtures/shapefile.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/shapefile.zip
--------------------------------------------------------------------------------
/tests/fixtures/shapefile_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/shapefile_thumbnail.png
--------------------------------------------------------------------------------
/tests/fixtures/some_shapefile_with_cyrillicйфячыцус.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/some_shapefile_with_cyrillicйфячыцус.zip
--------------------------------------------------------------------------------
/tests/fixtures/style.sld:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | generic
10 |
11 | Generic
12 | Generic style
13 |
14 |
15 | raster
16 | raster
17 |
18 |
19 |
20 | true
21 |
22 |
23 |
24 | 1.0
25 |
26 |
27 |
28 | Polygon
29 | Polygon
30 |
31 |
32 |
33 |
34 |
35 | 2
36 |
37 |
38 |
39 |
40 | #AAAAAA
41 |
42 |
43 | #000000
44 | 1
45 |
46 |
47 |
48 |
49 | Line
50 | Line
51 |
52 |
53 |
54 |
55 |
56 | 1
57 |
58 |
59 |
60 |
61 | #0000FF
62 | 1
63 |
64 |
65 |
66 |
67 | point
68 | Point
69 |
70 |
71 |
72 |
73 | square
74 |
75 | #FF0000
76 |
77 |
78 | 6
79 |
80 |
81 |
82 | first
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/tests/fixtures/tiff.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneOffTech/geoserver-client-php/317a105fce41b7272d985332df614f28197e0eb8/tests/fixtures/tiff.tif
--------------------------------------------------------------------------------
/tests/geoserver.env:
--------------------------------------------------------------------------------
1 | GEOSERVER_DATA_DIR=/opt/geoserver/data_dir
2 | ENABLE_JSONP=true
3 | MAX_FILTER_RULES=20
4 | OPTIMIZE_LINE_WIDTH=false
5 | FOOTPRINTS_DATA_DIR=/opt/footprints_dir
6 | GEOWEBCACHE_CACHE_DIR=/opt/geoserver/data_dir/gwc
--------------------------------------------------------------------------------
/tests/helpers.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | if (! function_exists('tap')) {
23 |
24 | /**
25 | * Call the given Closure with the given value then return the value.
26 | *
27 | * Borrowed from Laravel Framework https://github.com/laravel/framework/blob/407b7b085223604a82711fedb16e2ea50ac5e807/src/Illuminate/Support/helpers.php#L1029
28 | *
29 | * @param mixed $value
30 | * @param callable $callback
31 | * @return mixed
32 | */
33 | function tap($value, $callback)
34 | {
35 | $callback($value);
36 | return $value;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/wait.php:
--------------------------------------------------------------------------------
1 | .
20 | */
21 |
22 | require __DIR__.'/../vendor/autoload.php';
23 |
24 | use Http\Discovery\HttpClientDiscovery;
25 | use Http\Discovery\MessageFactoryDiscovery;
26 | use Http\Client\Exception\RequestException;
27 |
28 | $messageFactory = MessageFactoryDiscovery::find();
29 | $httpClient = HttpClientDiscovery::find();
30 |
31 | $route = 'http://127.0.0.1:8600/geoserver/rest/about/version.html';
32 |
33 | $request = $messageFactory->createRequest('GET', $route, []);
34 |
35 | $start = time();
36 |
37 | while (true) {
38 | try {
39 | $response = $httpClient->sendRequest($request);
40 |
41 | if ($response->getStatusCode() === 200 || $response->getStatusCode() === 401) {
42 | fwrite(STDOUT, 'Docker container started!'.PHP_EOL);
43 | exit(0);
44 | }
45 | } catch (RequestException $exception) {
46 | $elapsed = time() - $start;
47 |
48 | if ($elapsed > 30) {
49 | fwrite(STDERR, 'Docker container did not start in time...'.PHP_EOL);
50 | exit(1);
51 | }
52 |
53 | fwrite(STDOUT, 'Waiting for container to start...'.PHP_EOL);
54 | sleep(1);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------