Build geometries

Pass coordinates directly to the geometry constructor as lists or tuples. Positions use longitude, latitude order. Each model supplies its GeoJSON type automatically and validates its input using the existing CQL2 model constraints.

Construct a point

import cql2json_pydantic as cql

point = cql.Point((-115.81, 37.24))
print(point.model_dump_json(exclude_none=True))

Output:

{"type":"Point","coordinates":[-115.81,37.24]}

Construct lines, polygons, and multi-geometries

Use nested sequences for more complex shapes. A polygon contains an exterior ring followed by any interior rings (holes). Each multi-geometry adds a sequence around its component coordinates.

import cql2json_pydantic as cql

line = cql.Linestring([(0, 0), (1, 1)])
polygon = cql.Polygon([
    [(0, 0), (4, 0), (4, 4), (0, 4), (0, 0)],
    [(1, 1), (1, 2), (2, 2), (2, 1), (1, 1)],
])
points = cql.Multipoint([(0, 0), (1, 1)])
lines = cql.Multilinestring([
    [(0, 0), (1, 1)],
    [(2, 2), (3, 3)],
])
polygons = cql.Multipolygon([
    [[(0, 0), (1, 0), (1, 1), (0, 0)]],
    [[(2, 2), (3, 2), (3, 3), (2, 2)]],
])

No coordinate wrapper objects are needed for these inputs. The resulting objects remain Pydantic models; their stored coordinate fields retain the generated types.

Group geometries

Pass model instances to Geometrycollection. The CQL2 schema requires at least two members, each a point, line, polygon, or multi-geometry; nested geometry collections are not accepted.

import cql2json_pydantic as cql

collection = cql.Geometrycollection([
    cql.Point((0, 0)),
    cql.Linestring([(0, 0), (1, 1)]),
])

Use any of these geometry models as an operand in a spatial predicate, as shown in Intersect a point.

Supply optional fields or use keyword construction

Optional fields remain keyword arguments. Existing keyword-only calls and coordinate wrappers continue to work:

import cql2json_pydantic as cql

point = cql.Point((0, 0), bbox=[-1, -1, 1, 1])
same_point = cql.Point(coordinates=[0, 0], bbox=[-1, -1, 1, 1])
assert point == same_point

line = cql.Linestring(coordinates=[
    cql.LinestringCoordinate(root=[0, 0]),
    cql.LinestringCoordinate(root=[1, 1]),
])
assert line == cql.Linestring([(0, 0), (1, 1)])

BboxLiteral continues to use a keyword argument: cql.BboxLiteral(bbox=[-1, -1, 1, 1]).

Validate and serialize

Invalid input raises pydantic.ValidationError during construction. For example, a position needs at least two numbers, a line needs at least two positions, and a polygon ring needs at least four positions. Validation enforces the existing schema constraints; it does not perform geometric topology checks.

import cql2json_pydantic as cql
from pydantic import ValidationError

try:
    cql.Point((0,))
except ValidationError:
    print("A point needs at least two coordinates.")

Use model_dump_json(exclude_none=True) for JSON text, or model_dump(mode="json", exclude_none=True) for a JSON-compatible dictionary. exclude_none=True omits an unset bbox. Avoid exclude_defaults=True or exclude_unset=True when producing GeoJSON: these may omit the automatically supplied type field.

Read serialized geometry back using the corresponding model:

import cql2json_pydantic as cql

point = cql.Point((-115.81, 37.24))
payload = point.model_dump(mode="json", exclude_none=True)
restored = cql.Point.model_validate(payload)
assert restored == point
assert cql.Point.model_validate_json(point.model_dump_json(exclude_none=True)) == point