Search by

ici-be / ici-tools

aKKennedy

Spatial PHP tools. The WfsLayer class helps create requests on a WFS layer using a PHP object. Web Feature Service (WFS) is a standard of the Open Geospatial Consortium.

Package info

github.com/ici-be/ici-tools

pkg:composer/ici-be/ici-tools

Statistics

Installs: 5 429

Dependents: 2

Suggesters: 0

Stars: 2

Open Issues: 0

0.4.0 2026-10-19 06:27 UTC

This package is auto-updated.

Last update: 2026-10-19 06:32:23 UTC


README

ici-tools is a small PHP library providing geospatial tools. Contributions welcome :)

Features

  • WfsLayer builds and runs GetFeature requests on a WFS layer (GeoServer, ArcGIS Server, MapServer, QGIS Server…) through a fluent PHP object. Web Feature Service (WFS) is a standard of the Open Geospatial Consortium.
  • GeomSvg renders a WKT POLYGON / MULTIPOLYGON as a small inline SVG (thumbnails, previews, lists…).

Requirements

Installation

composer require ici-be/ici-tools

WfsLayer

Query a WFS layer with PHP.

The examples below use the public Urbis WFS of the Brussels-Capital Region (Belgium) and its layer of the 19 municipalities.

A. Typical usage with method chaining

<?php

use ici\ici_tools\WfsLayer;

// Base URL of the WFS service and layer name (version defaults to 2.0.0)
$wfs = new WfsLayer('https://geoservices-urbis.irisnet.be/geoserver/urbisvector/wfs', 'urbisvector:Municipalities');

// Municipalities larger than 10 km², ordered by French name DESC, keeping only the French name
$wfs->setCqlFilter('AREA>10000000')->setSortBy('NAMEFRE', 'DESC')->setPropertyName('NAMEFRE');

var_dump($wfs->getHits()); // int(4)

var_dump($wfs->getPropertiesArray());
/*
array(4) {
  [0] => ["NAMEFRE" => "Watermael-Boitsfort"]
  [1] => ["NAMEFRE" => "Uccle"]
  [2] => ["NAMEFRE" => "Bruxelles"]
  [3] => ["NAMEFRE" => "Anderlecht"]
}
*/

B. Setters

All setters return $this (they can be chained) and have a matching getter. Getters of options that were never set return null.

setVersion(string $version);              // WFS version (default "2.0.0"). With 1.x, setCount() is sent as maxFeatures.
setLayerName(string $layer_name);         // Layer (typeName), ex: "workspace:layer"
setBasePath(string $base_path);           // URL of the WFS service
setPropertyName(string $property_name);   // Restrict returned attributes. One attribute, or several separated by commas.
setCqlFilter(string $cql_filter);         // CQL filter (GeoServer vendor parameter), see https://docs.geoserver.org/stable/en/user/tutorials/cql/cql_tutorial.html
setOutputSrs(int $output_srs);            // Reproject the output (ex: 4326 for EPSG:4326)
setSortBy(string $sort_by, string $order = 'ASC'); // Sort by an attribute, ASC or DESC
setStartIndex(int $start_index);          // Skip the first x features (pagination)
setCount(int $count);                     // Maximum number of features returned
setMethod(string $method);                // "POST" (default) or "GET"
setOutputFormat(string $output_format);   // Default "json". Must be a JSON/GeoJSON format: check the GetCapabilities
                                          // of your server if needed (ex: "GEOJSON" on ArcGIS, "application/json"...)
setResultType(string $result_type);       // "results" (default) or "hits". You shouldn't need it, see getHits().
setHttpClient(HttpClientInterface $client); // Since 0.4.0. Use your own Symfony HTTP client (timeout, proxy, auth, mock...)

C. Running the query

getResults();           // Decoded JSON response (stdClass), or null on error
getPropertiesArray(bool $geometry = false, bool $bbox = false); // Properties of each feature as an array ([] on error).
                        // $geometry = true adds a "geometry" key (GeoJSON geometry object)
getHits();              // Number of matching features (int), or null if unknown / on error
getQueryUrl();          // The GET URL of the query (handy for debugging in a browser)
getQueryFields();       // The query parameters as an array

Error handling: these methods never throw on network or server errors. They return null (or [] for getPropertiesArray()) when the request fails, the status code is not 200, or the server answers something that is not JSON (an OGC ExceptionReport for example, which GeoServer sends with a 200 status). When you get null, open getQueryUrl() in a browser to see the server's message.

D. Helper: filter by distance to a point

// "DWITHIN(GEOM, POINT(150000 170000), 10, meters)"
$filter = WfsLayer::filterByPointDistance('POINT(150000 170000)', 'GEOM', 10);

$wfs->setCqlFilter($filter)->setPropertyName('NAMEFRE');
$wfs->getPropertiesArray(); // [["NAMEFRE" => "Bruxelles"]]

The point must be expressed in the coordinate system of the layer (EPSG:31370 here). Note that GeoServer may interpret the distance in the units of the layer's CRS rather than in meters.

E. More examples

<?php

use ici\ici_tools\WfsLayer;

$wfs = new WfsLayer('https://geoservices-urbis.irisnet.be/geoserver/urbisvector/wfs', 'urbisvector:Municipalities');

// 1. Everything as an object
var_dump($wfs->getResults());
/*
  +"type": "FeatureCollection"
  +"features": array:19 [▶]
  +"totalFeatures": 19
  +"numberMatched": 19
  +"numberReturned": 19
  +"timeStamp": "2026-10-19T06:22:07.111Z"
  +"crs": {#600 ▶}
  +"bbox": array:4 [▶]
*/

// 2. Municipalities whose French name starts with "Woluwe"
$wfs->setCqlFilter("NAMEFRE LIKE 'Woluwe%'");

// 3. Keep only the French name, the Dutch name and the NIS code
$wfs->setPropertyName('NAMEFRE,NAMEDUT,NISCODE');

// 4. URL of the generated query
$wfs->getQueryUrl();
// https://geoservices-urbis.irisnet.be/geoserver/urbisvector/wfs?service=WFS&version=2.0.0&request=GetFeature&typeName=urbisvector%3AMunicipalities&outputFormat=json&resultType=results&propertyname=NAMEFRE%2CNAMEDUT%2CNISCODE&cql_filter=NAMEFRE+LIKE+%27Woluwe%25%27

// 5. Rows and their properties
$wfs->getPropertiesArray();
/*
array(2) {
  [0] => ["NISCODE" => "21019", "NAMEFRE" => "Woluwe-Saint-Pierre", "NAMEDUT" => "Sint-Pieters-Woluwe"]
  [1] => ["NISCODE" => "21018", "NAMEFRE" => "Woluwe-Saint-Lambert", "NAMEDUT" => "Sint-Lambrechts-Woluwe"]
}
*/

// 6. Pagination: features 21 to 30
$wfs->setStartIndex(20)->setCount(10);

// 7. Custom HTTP client (ex: timeout)
use Symfony\Component\HttpClient\HttpClient;
$wfs->setHttpClient(HttpClient::create(['timeout' => 5]));

F. POST or GET?

POST is the default for historical reasons, but the parameters are sent in the query string in both cases (not in the request body). If a server refuses POST requests, use setMethod('GET').

GeomSvg

Draws a WKT POLYGON or MULTIPOLYGON as an inline SVG, scaled and centered in a square of $size pixels. Only exterior rings are drawn (holes are ignored).

<?php

use ici\ici_tools\GeomSvg;

echo GeomSvg::toSvg('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 100);
// <svg height="100" width="100" style=""><polygon points="0,100 100,100 100,0 0,0 0,100" style="fill:#3388ff;fill-opacity:0.3;stroke:#3388ff;stroke-width:1" /></svg>

Parameters:

GeomSvg::toSvg(
    $polygon,              // WKT string (or null)
    $size = 200,           // Width and height in pixels
    $fill = "#3388ff",
    $fillOpacity = 0.3,
    $stroke = "#3388ff",
    $strokeWidth = 1,
    $style = null          // Inline CSS of the <svg> element
);

Returns null when the input is null, empty, not a (multi)polygon, or has no width/height. Invalid WKT throws a geoPHP exception.

Combined with WfsLayer (GeoJSON geometry → WKT → SVG):

use geoPHP\geoPHP;

$features = $wfs->setCqlFilter("NAMEFRE='Ixelles'")->getPropertiesArray(true);
$wkt = geoPHP::load(json_encode($features[0]['geometry']), 'json')->out('wkt');

echo GeomSvg::toSvg($wkt, 100);

Tests

composer install
composer test

Changelog

See CHANGELOG.md.

License

MIT