Geospatial types
Chronicle recognizes a value as geospatial by its GeoJSON shape. The client ships three types that serialize to that shape, so the sink can index and query them geospatially. A hand-rolled coordinate class serializes as an ordinary nested object and gets none of that.
They live in io.cratis.chronicle.geospatial and are usable on any event, read
model, or reducer state.
| Type | Constructor | GeoJSON type | Schema format |
|---|---|---|---|
Point | Point(longitude, latitude) | Point | point |
LineString | LineString(coordinates) | LineString | linestring |
Polygon | Polygon(shell, holes) | Polygon | polygon |
LinearRing | LinearRing(coordinates) | (a polygon ring) | — |
data class Point(val longitude: Double, val latitude: Double)
data class LineString(val coordinates: List<Point>)
data class LinearRing(val coordinates: List<Point>)
data class Polygon( val shell: LinearRing, val holes: List<LinearRing> = emptyList())Longitude comes first, matching GeoJSON coordinate order — not the “latitude, longitude” order used in everyday speech.
LineString and LinearRing also take loose points (LineString(a, b, c)),
which is what Java uses in place of a list literal. Polygon is
@JvmOverloads, so new Polygon(shell) works from Java for a polygon with no
holes.
Using them on an event
Section titled “Using them on an event”import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.geospatial.Point
@EventTypedata class WarehouseInspected( val warehouseId: String = "", val inspectedAt: Point = Point(0.0, 0.0))import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.geospatial.Point;
@EventTypepublic record WarehouseInspected(String warehouseId, Point inspectedAt) {}Appending is unchanged — the client serializes the point as GeoJSON on the way out:
import io.cratis.chronicle.geospatial.Point
store.eventLog.append( eventSourceId = "warehouse-1", event = WarehouseInspected("warehouse-1", Point(10.75, 59.91)))The event content on the wire:
{ "warehouseId": "warehouse-1", "inspectedAt": { "type": "Point", "coordinates": [10.75, 59.91] }}Building a polygon
Section titled “Building a polygon”A polygon’s shell must be closed — its last point repeats its first — and needs at least four points. Holes follow the same rule.
import io.cratis.chronicle.geospatial.LinearRingimport io.cratis.chronicle.geospatial.Pointimport io.cratis.chronicle.geospatial.Polygon
val deliveryZone = Polygon( shell = LinearRing( Point(10.70, 59.90), Point(10.80, 59.90), Point(10.80, 59.95), Point(10.70, 59.90) ))GeoJSON stores the shell and the holes in one flat array of rings, with the shell first. Reading a polygon back takes the first ring as the shell and the rest as holes.
Validation on read
Section titled “Validation on read”Deserialization rejects geometries the sink would refuse, so a malformed value fails at the client rather than deep in the kernel.
| Geometry | Rejected when |
|---|---|
Point | coordinates is not [longitude, latitude] |
LineString | fewer than 2 points |
Polygon | it carries no rings at all |
LinearRing | fewer than 4 points, or it does not close |
| all | the declared type is not the expected one |
Construction is not validated — only what comes off the wire is.
Serializing yourself
Section titled “Serializing yourself”io.cratis.chronicle.json.ChronicleGson.chronicleGson is the Gson instance
the client serializes event content and read model state with. Use it when you
need to produce the same JSON the client would.
import io.cratis.chronicle.geospatial.Pointimport io.cratis.chronicle.json.chronicleGson
val json = chronicleGson.toJson(Point(10.75, 59.91))import io.cratis.chronicle.geospatial.Point;import io.cratis.chronicle.json.ChronicleGson;
String json = ChronicleGson.chronicleGson.toJson(new Point(10.75, 59.91));