ici-be / ici-tools
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.
Requires
- php: ^7.1 || ^8.0
- ext-json: *
- symfony/http-client: 5.* || 6.* || 7.* || 8.*
- wgirhad/geophp: ^3.0
Requires (Dev)
- phpunit/phpunit: ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
ici-tools is a small PHP library providing geospatial tools. Contributions welcome :)
Features
- WfsLayer builds and runs
GetFeaturerequests 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/MULTIPOLYGONas a small inline SVG (thumbnails, previews, lists…).
Requirements
- PHP 7.1+ or 8.x (the effective minimum depends on the
symfony/http-clientversion Composer selects) - symfony/http-client 5.x → 8.x
- wgirhad/geophp 3.x
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