geo-io/interface
A lightweight set of PHP interfaces for geometric objects such as points, linestrings, polygons and collections, giving geo-io geometry implementations and parsers a shared contract so spatial code stays decoupled and interoperable.
geo-io/interface is a contracts-only package: it ships interfaces, enums, and a couple of small value objects for describing geometries (points, line strings, polygons, collections) and their metadata. It does not parse, validate, serialise, or do spatial math — for that you pair it with geo-io/wkt, geo-io/wkb, or your own readers/writers.
composer require geo-io/interface
Where to look first, in order:
GeometryInterface`` / PointInterface/LineStringInterface/PolygonInterface/GeometryCollectionInterface` — the actual shape of the model. Skim the getters; they tell you what the rest of the geo-io ecosystem expects from a geometry.GeometryType — the enum used everywhere to discriminate between POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION. Most real code is match ($geometry->getType()) { ... }.Coordinate (and BoundingBox if present in your version) — the only value objects you're likely to construct by hand. Confirm whether Z/M dimensions are supported by the version you pulled in.composer.json — check the PHP constraint. The enum usage means PHP 8.1+ in recent releases; older tags may not have enums at all.First use case: define a boundary in your own application — a service or DTO that accepts anything implementing GeometryInterface and returns a WKT string via geo-io/wkt. Type-hint the interface, not the concrete class, and you can swap in geometries coming from your ORM, a GeoJSON decoder, or a WKB parser without changing your signature.
use GeoIO\Geometry\GeometryInterface;
use GeoIO\Geometry\GeometryType;
final class GeometryDescriber
{
public function describe(GeometryInterface $geometry): string
{
return match ($geometry->getType()) {
GeometryType::POINT => 'a point',
GeometryType::LINESTRING => 'a line',
GeometryType::POLYGON => 'a polygon',
default => 'a collection',
};
}
}
Code against the interfaces, never against the package's internals. The whole point of geo-io/interface is that your domain can be the implementation. A Warehouse, Route, or DeliveryZone entity can implement PolygonInterface directly, exposing getExteriorRing() / getInteriorRings() and letting you hand it straight to a WKT writer. This is the highest-leverage pattern with this package: your model stays your model, and geo-io becomes a serialisation detail rather than a parallel object graph.
Enum-driven dispatch instead of instanceof chains. GeometryType gives you a stable, serialisable discriminator. Handlers keyed by the enum case scale better than if ($g instanceof MultiPolygon) branches, and the enum case names map cleanly onto WKT/WKB type strings when you do GeometryType::from($typeString).
Adapter pattern for foreign geometries. When data arrives from PostGIS, a GeoJSON library, or an existing GIS SDK, wrap it in a thin class that implements PointInterface/PolygonInterface by delegating. You get one conversion point in your codebase instead of one per call site, and the wrapper is trivial enough to unit test with a fake coordinate list.
Factory + immutable value objects. Build geometries through a single factory or named constructors (GeometryFactory::point($x, $y)) rather than new scattered everywhere. Coordinate is a value object — treat it as immutable, compare it with an epsilon helper you write yourself, and use it as an array/cache key by casting to a canonical string (sprintf('%.9F %.9F', $x, $y)).
Test doubles are one-liners. Because the package is nothing but interfaces, you can create anonymous classes inside tests to stub a geometry, rather than pulling in a full geometry library. That keeps test suites fast and free of fixture files.
$stub = new class implements \GeoIO\Geometry\PointInterface {
public function getType(): \GeoIO\Geometry\GeometryType { return \GeoIO\Geometry\GeometryType::POINT; }
public function getCoordinate(): \GeoIO\Geometry\CoordinateInterface { /* ... */ }
public function getDimension(): int { return 2; }
public function isEmpty(): bool { return false; }
};
Integrate at the container boundary. In Symfony/Laravel, alias the geo-io interfaces to your concrete factory, and tag your writers/parsers so a GeometryRenderer can be swapped between WKT, WKB, and GeoJSON without touching controllers.
GeometryType enum; older tags may use class constants or strings. GeometryType::from() throws on unknown input — use tryFrom() when parsing untrusted WKT and handle null. Also confirm whether comparisons are case-sensitive (POINT vs point).Coordinate stores x/y; whether that means longitude/latitude (GeoJSON, EPSG:4326) or northing/easting is a convention you must document and enforce. A single swapped axis is nearly impossible to spot in logs.==. Use a tolerance-based comparison (abs($a - $b) < 1e-9) and never in_array() or array_unique() on raw coordinates. For hash keys, normalise to a fixed precision string first.Coordinate objects from a getter, callers can mutate them. Prefer returning fresh arrays/copies, or make your coordinates readonly where the language allows.equals() contract. If you need equality across two geometries from different libraries, write a comparator; don't assume the package provides one.json_encode() will not give you GeoJSON. The interfaces expose no JsonSerializable behaviour. Add your own normaliser if you need JSON in an API response — and be aware the GeoJSON spec requires [x, y] arrays, not objects.geo-io/wkt and geo-io/wkb are released against specific interface versions; a minor bump in the interface could add methods your concrete classes then fail to implement. Pin both, and run your test suite after any composer update.var_dump(get_class($geometry)) plus a dump of getType() and the first coordinate usually localises the problem faster than reading the writer's source — mismatched types and empty child collections are the two most common culprits.How can I help you explore Laravel packages today?