GeoJSON¶
The geojson module contains an implementation of
RFC 7946: The GeoJSON Format.
See below for constructing GeoJSON objects using the DSL.
Installation¶
commonMain {
dependencies {
implementation("org.maplibre.spatialk:geojson:0.4.0")
}
}
dependencies {
implementation("org.maplibre.spatialk:geojson-jvm:0.4.0")
}
GeoJSON Objects¶
The GeoJsonObject interface represents all GeoJSON objects. All GeoJSON
objects can have a bbox property specified on them which is a BoundingBox
that represents the bounds of that object's geometry.
Geometry¶
Geometry objects are a sealed hierarchy of classes that inherit from the
Geometry class. This allows for exhaustive type checks in Kotlin using a
when block.
val geometry: Geometry = getSomeGeometry()
val type =
when (geometry) {
is Point -> "Point"
is MultiPoint -> "MultiPoint"
is LineString -> "LineString"
is MultiLineString -> "MultiLineString"
is Polygon -> "Polygon"
is MultiPolygon -> "MultiPolygon"
is GeometryCollection<*> -> "GeometryCollection"
}
All seven types of GeoJSON geometries are implemented and summarized below. Full documentation can be found in the API pages.
Position¶
Position is a DoubleArray-backed class where longitude, latitude, and
optionally an altitude are accessible as properties. Coordinates follow the
order specified in RFC 7946: [longitude, latitude, altitude?]. The class
supports destructuring in Kotlin.
val position = Position(-75.0, 45.0)
val (longitude, latitude, altitude) = position
// Access values
position.longitude
position.latitude
position.altitude
Position position = new Position(-75.0, 45.0);
// Access values
double longitude = position.getLongitude();
double latitude = position.getLatitude();
Double altitude = position.getAltitude();
[-75.0, 45.0]
Point¶
A Point is a single Position.
val point = Point(Position(-75.0, 45.0))
println(point.coordinates.longitude)
// Prints: -75.0
Point point = new Point(-75.0, 45.0);
System.out.println(point.getCoordinates().getLongitude());
// Prints: -75.0
{
"type": "Point",
"coordinates": [-75.0, 45.0]
}
MultiPoint¶
A MultiPoint is an array of Positions.
val multiPoint = MultiPoint(Position(-75.0, 45.0), Position(-79.0, 44.0))
MultiPoint multiPoint = new MultiPoint(new Position(-75.0, 45.0), new Position(-79.0, 44.0));
{
"type": "MultiPoint",
"coordinates": [[-75.0, 45.0], [-79.0, 44.0]]
}
LineString¶
A LineString is a sequence of two or more Positions.
val lineString = LineString(Position(-75.0, 45.0), Position(-79.0, 44.0))
LineString lineString = new LineString(new Position(-75.0, 45.0), new Position(-79.0, 44.0));
{
"type": "LineString",
"coordinates": [[-75.0, 45.0], [-79.0, 44.0]]
}
MultiLineString¶
A MultiLineString is an array of LineStrings.
val multiLineString =
MultiLineString(
listOf(Position(12.3, 45.6), Position(78.9, 12.3)),
listOf(Position(87.6, 54.3), Position(21.9, 56.4)),
)
MultiLineString multiLineString =
new MultiLineString(
new LineString(new Position(12.3, 45.6), new Position(78.9, 12.3)),
new LineString(new Position(87.6, 54.3), new Position(21.9, 56.4)));
{
"type": "MultiLineString",
"coordinates": [
[[12.3, 45.6], [78.9, 12.3]],
[[87.6, 54.3], [21.9, 56.4]]
]
}
Polygon¶
A Polygon is an array of rings. Each ring is a sequence of points with the
last point matching the first point to indicate a closed area. The first ring
defines the outer shape of the polygon, while all the following rings define
"holes" inside the polygon.
val polygon =
Polygon(
listOf(
Position(-79.87, 43.42),
Position(-78.89, 43.49),
Position(-79.07, 44.02),
Position(-79.95, 43.87),
Position(-79.87, 43.42),
),
listOf(
Position(-79.75, 43.81),
Position(-79.56, 43.85),
Position(-79.7, 43.88),
Position(-79.75, 43.81),
),
)
Polygon polygon =
new Polygon(
new LineString(
new Position(-79.87, 43.42),
new Position(-78.89, 43.49),
new Position(-79.07, 44.02),
new Position(-79.95, 43.87),
new Position(-79.87, 43.42)),
new LineString(
new Position(-79.75, 43.81),
new Position(-79.56, 43.85),
new Position(-79.7, 43.88),
new Position(-79.75, 43.81)));
{
"type": "Polygon",
"coordinates": [
[[-79.87, 43.42], [-78.89, 43.49], [-79.07, 44.02], [-79.95, 43.87], [-79.87, 43.42]],
[[-79.75, 43.81], [-79.56, 43.85], [-79.7, 43.88], [-79.75, 43.81]]
]
}
MultiPolygon¶
A MultiPolygon is an array of Polygons.
val polygon =
listOf(
listOf(
Position(-79.87, 43.42),
Position(-78.89, 43.49),
Position(-79.07, 44.02),
Position(-79.95, 43.87),
Position(-79.87, 43.42),
),
listOf(
Position(-79.75, 43.81),
Position(-79.56, 43.85),
Position(-79.7, 43.88),
Position(-79.75, 43.81),
),
)
val multiPolygon = MultiPolygon(polygon, polygon)
Polygon polygon =
new Polygon(
new LineString(
new Position(-79.87, 43.42),
new Position(-78.89, 43.49),
new Position(-79.07, 44.02),
new Position(-79.95, 43.87),
new Position(-79.87, 43.42)),
new LineString(
new Position(-79.75, 43.81),
new Position(-79.56, 43.85),
new Position(-79.7, 43.88),
new Position(-79.75, 43.81)));
MultiPolygon multiPolygon = new MultiPolygon(polygon, polygon);
{
"type": "MultiPolygon",
"coordinates": [
[
[[-79.87, 43.42], [-78.89, 43.49], [-79.07, 44.02], [-79.95, 43.87], [-79.87, 43.42]],
[[-79.75, 43.81], [-79.56, 43.85], [-79.7, 43.88], [-79.75, 43.81]]
],
[
[[-79.87, 43.42], [-78.89, 43.49], [-79.07, 44.02], [-79.95, 43.87], [-79.87, 43.42]],
[[-79.75, 43.81], [-79.56, 43.85], [-79.7, 43.88], [-79.75, 43.81]]
]
]
}
GeometryCollection¶
A GeometryCollection contains multiple, heterogeneous geometries.
val point = Point(Position(-75.0, 45.0))
val lineString = LineString(Position(-75.0, 45.0), Position(-79.0, 44.0))
val geometryCollection = GeometryCollection(point, lineString)
// Can be iterated over and used in any way a Collection<T> can be
geometryCollection.forEach { geometry ->
// ...
}
Point point = new Point(-75.0, 45.0);
LineString lineString = new LineString(new Position(-75.0, 45.0), new Position(-79.0, 44.0));
GeometryCollection<Geometry> geometryCollection = new GeometryCollection<>(point, lineString);
{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [-75.0, 45.0]
},
{
"type": "LineString",
"coordinates": [[-75.0, 45.0], [-79.0, 44.0]]
}
]
}
Feature¶
A Feature can contain a Geometry object, as well as a set of data
properties, and optionally a commonly used identifier (id).
Properties can be any object that serializes into a JSON object. For dynamic or
unknown property schemas, use JsonObject. For known schemas, use a
@Serializable data class. Helper methods for accessing properties are
available when properties are of type JsonObject (see the
API documentation for details).
val point = Point(Position(-75.0, 45.0))
val feature = Feature(point, properties = buildJsonObject { put("size", 9999) })
val size: Number? = feature.properties["size"]?.jsonPrimitive?.doubleOrNull // 9999
val geometry: Point = feature.geometry
Point point = new Point(-75.0, 45.0);
JsonObjectBuilder properties = new JsonObjectBuilder();
JsonElementBuildersKt.put(properties, "size", 9999);
Feature<Point, JsonObject> feature = new Feature<>(point, properties.build(), null, null);
Integer size = Feature.getIntProperty(feature, "size");
Point geometry = feature.getGeometry(); // point
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-75.0, 45.0]
},
"properties": {
"size": 9999
}
}
FeatureCollection¶
A FeatureCollection is a collection of multiple features. It implements the
Collection interface and can be used in any place that a collection can be
used.
val point = Point(Position(-75.0, 45.0))
val pointFeature = Feature(point, null)
val featureCollection = FeatureCollection(pointFeature)
featureCollection.forEach { feature ->
// ...
}
Point point = new Point(-75.0, 45.0);
Feature<Point, JsonObject> pointFeature = new Feature<>(point, null, null, null);
FeatureCollection<Point, JsonObject> featureCollection = new FeatureCollection<>(pointFeature);
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-75.0, 45.0]
},
"properties":null
}
]
}
BoundingBox¶
The BoundingBox class is used to represent the bounding boxes that can be set
for any GeoJsonObject. Like the Position class, bounding boxes are backed by
a DoubleArray with each component accessible by its propery (southwest and
northeast). Bounding boxes also support destructuring.
val bbox = BoundingBox(west = 11.6, south = 45.1, east = 12.7, north = 45.7)
val (southwest, northeast) = bbox // Two Positions
BoundingBox bbox = new BoundingBox(11.6, 45.1, 12.7, 45.7);
Position southwest = bbox.getSouthwest();
Position northeast = bbox.getNortheast();
[11.6,45.1,12.7,45.7]
Serialization¶
To JSON¶
Any GeoJsonObject can be serialized to a JSON string using the toJson()
method.
val point = Point(Position(-75.0, 45.0))
val feature = Feature(point, null)
val featureCollection = FeatureCollection(feature)
val json = featureCollection.toJson()
println(json)
Point point = new Point(-75.0, 45.0);
Feature<Point, JsonObject> feature = new Feature<>(point, null, null, null);
FeatureCollection<Point, JsonObject> featureCollection = new FeatureCollection<>(feature);
String json = FeatureCollection.toJson(featureCollection);
System.out.println(json);
From JSON¶
The fromJson and fromJsonOrNull companion (or static) functions are
available on each GeoJsonObject class to decode each type of object from a
JSON string.
// Throws exception if the JSON cannot be deserialized to a Point
val myPoint: Point =
Point.fromJson("""{"type": "MultiPoint", "coordinates": [[-75.0, 45.0]]}""")
// Returns null if an error occurs
val nullable: Point? =
Point.fromJsonOrNull("""{"type": "MultiPoint", "coordinates": [[-75.0, 45.0]]}""")
// Throws exception if the JSON cannot be deserialized to a Point
Point myPoint =
Point.fromJson("{\"type\": \"MultiPoint\", \"coordinates\": [[-75.0, 45.0]]}");
// Returns null if an error occurs
Point nullable =
Point.fromJsonOrNull("{\"type\": \"MultiPoint\", \"coordinates\": [[-75.0, 45.0]]}");
Like with encoding, Spatial-K objects can also be decoded using
kotlinx.serialization using the GeoJson serializer.
val feature: Feature<*, *> =
GeoJson.jsonFormat.decodeFromString(
serializer<Feature<Geometry, JsonObject?>>(),
"""
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 20.0]
},
"properties": {
"name": "point name"
}
}
""",
)
GeoJSON Builders¶
It's recommended to construct GeoJSON objects in-code using builder classes. In Kotlin, these are available through a convenient DSL. In Java, use the builder classes directly.
Geometry¶
Each geometry type more complex than Point has a corresponding DSL.
A GeoJSON object's bbox value can be assigned in any of the DSLs.
MultiPoint¶
The MultiPoint builder uses add() to add positions or Point geometries.
val myPoint = Point(88.0, 34.0)
val multiPoint = buildMultiPoint {
add(-75.0, 45.0)
add(Position(-78.0, 44.0))
add(myPoint)
}
MultiPointBuilder builder = new MultiPointBuilder();
builder.add(-75.0, 45.0, null);
builder.add(new Position(-78.0, 44.0));
builder.add(new Point(88.0, 34.0));
MultiPoint multiPoint = builder.build();
{
"type": "MultiPoint",
"coordinates": [
[-75.0, 45.0],
[-78.0, 44.0],
[88.0, 34.0]
]
}
LineString¶
A LineString contains two or more positions, in order. The builder uses
add() to add positions. The order in which positions are added is the order
that the line will follow.
val lineString = buildLineString {
add(45.0, 45.0)
add(0.0, 0.0)
}
LineStringBuilder builder = new LineStringBuilder();
builder.add(45.0, 45.0, null);
builder.add(0.0, 0.0, null);
LineString lineString = builder.build();
{
"type": "LineString",
"coordinates": [[45.0, 45.0], [0.0, 0.0]]
}
MultiLineString¶
The MultiLineString builder uses addLineString() to define line strings
inline, or add() to add existing LineString objects.
val simpleLine = buildLineString {
add(45.0, 45.0)
add(0.0, 0.0)
}
val multiLineString = buildMultiLineString {
add(simpleLine)
// Inline LineString creation
addLineString {
add(44.4, 55.5)
add(55.5, 66.6)
}
}
LineStringBuilder lineBuilder = new LineStringBuilder();
lineBuilder.add(45.0, 45.0, null);
lineBuilder.add(0.0, 0.0, null);
LineString simpleLine = lineBuilder.build();
MultiLineStringBuilder builder = new MultiLineStringBuilder();
builder.add(simpleLine);
LineStringBuilder lineBuilder2 = new LineStringBuilder();
lineBuilder2.add(44.4, 55.5, null);
lineBuilder2.add(55.5, 66.6, null);
builder.add(lineBuilder2.build());
MultiLineString multiLineString = builder.build();
{
"type": "MultiLineString",
"coordinates": [
[[45.0, 45.0], [0.0, 0.0]],
[[44.4, 55.5], [55.5, 66.6]]
]
}
Polygon¶
The Polygon builder uses addRing() (Kotlin DSL) or add() with LineString
objects (Java/Kotlin) to define linear rings. The first ring is the exterior
ring with four or more positions. The last position must be the same as the
first position. All subsequent rings represent interior rings (i.e., holes) in
the polygon.
val simpleLine = buildLineString {
add(45.0, 45.0)
add(0.0, 0.0)
}
val polygon = buildPolygon {
addRing {
// LineStrings can be used as part of a ring
add(Position(45.0, 45.0))
add(Position(0.0, 0.0))
add(12.0, 12.0)
}
addRing {
add(4.0, 4.0)
add(2.0, 2.0)
add(3.0, 3.0)
}
}
PolygonBuilder builder = new PolygonBuilder();
LineString ring1 =
new LineString(
new Position(45.0, 45.0),
new Position(0.0, 0.0),
new Position(12.0, 12.0),
new Position(45.0, 45.0));
builder.add(ring1);
LineString ring2 =
new LineString(
new Position(4.0, 4.0),
new Position(2.0, 2.0),
new Position(3.0, 3.0),
new Position(4.0, 4.0));
builder.add(ring2);
Polygon polygon = builder.build();
{
"type": "Polygon",
"coordinates": [
[[45.0, 45.0], [0.0, 0.0], [12.0, 12.0], [45.0, 45.0]],
[[4.0, 4.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]]
]
}
MultiPolygon¶
The MultiPolygon builder uses addPolygon() to define polygons inline, or
add() to add existing Polygon objects.
val simplePolygon = buildPolygon {
addRing {
add(45.0, 45.0)
add(0.0, 0.0)
add(12.0, 12.0)
}
addRing {
add(4.0, 4.0)
add(2.0, 2.0)
add(3.0, 3.0)
}
}
val multiPolygon = buildMultiPolygon {
add(simplePolygon)
addPolygon {
addRing {
add(12.0, 0.0)
add(0.0, 12.0)
add(-12.0, 0.0)
add(5.0, 5.0)
}
}
}
PolygonBuilder polyBuilder = new PolygonBuilder();
LineString ring1 =
new LineString(
new Position(45.0, 45.0),
new Position(0.0, 0.0),
new Position(12.0, 12.0),
new Position(45.0, 45.0));
polyBuilder.add(ring1);
LineString ring2 =
new LineString(
new Position(4.0, 4.0),
new Position(2.0, 2.0),
new Position(3.0, 3.0),
new Position(4.0, 4.0));
polyBuilder.add(ring2);
Polygon simplePolygon = polyBuilder.build();
MultiPolygonBuilder builder = new MultiPolygonBuilder();
builder.add(simplePolygon);
PolygonBuilder polyBuilder2 = new PolygonBuilder();
LineString ring3 =
new LineString(
new Position(12.0, 0.0),
new Position(0.0, 12.0),
new Position(-12.0, 0.0),
new Position(5.0, 5.0),
new Position(12.0, 0.0));
polyBuilder2.add(ring3);
builder.add(polyBuilder2.build());
MultiPolygon multiPolygon = builder.build();
{
"type": "MultiPolygon",
"coordinates": [
[
[[45.0, 45.0], [0.0, 0.0], [12.0, 12.0], [45.0, 45.0]],
[[4.0, 4.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]]
], [
[[12.0, 0.0], [0.0, 12.0], [-12.0, 0.0], [5.0, 5.0], [12.0, 0.0]]
]
]
}
GeometryCollection¶
The GeometryCollection builder provides addPoint(), addLineString(),
addPolygon(), addMultiPoint(), addMultiLineString(), addMultiPolygon(),
and addGeometryCollection() to define geometries inline (Kotlin only), or
add() to add existing geometry objects.
val simplePoint = Point(-75.0, 45.0, 100.0)
val simpleLine = buildLineString {
add(45.0, 45.0)
add(0.0, 0.0)
}
val simplePolygon = buildPolygon {
addRing {
add(45.0, 45.0)
add(0.0, 0.0)
add(12.0, 12.0)
}
addRing {
add(4.0, 4.0)
add(2.0, 2.0)
add(3.0, 3.0)
}
}
val geometryCollection = buildGeometryCollection {
add(simplePoint)
add(simpleLine)
add(simplePolygon)
}
Point simplePoint = new Point(-75.0, 45.0, 100.0);
LineString simpleLine = new LineString(new Position(45.0, 45.0), new Position(0.0, 0.0));
PolygonBuilder polyBuilder = new PolygonBuilder();
LineString ring1 =
new LineString(
new Position(45.0, 45.0),
new Position(0.0, 0.0),
new Position(12.0, 12.0),
new Position(45.0, 45.0));
polyBuilder.add(ring1);
LineString ring2 =
new LineString(
new Position(4.0, 4.0),
new Position(2.0, 2.0),
new Position(3.0, 3.0),
new Position(4.0, 4.0));
polyBuilder.add(ring2);
Polygon simplePolygon = polyBuilder.build();
GeometryCollectionBuilder<Geometry> builder = new GeometryCollectionBuilder<>();
builder.add(simplePoint);
builder.add(simpleLine);
builder.add(simplePolygon);
GeometryCollection<Geometry> geometryCollection = builder.build();
{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [-75.0, 45.0, 100.0]
},
{
"type": "LineString",
"coordinates": [[45.0, 45.0], [0.0, 0.0]]
},
{
"type": "Polygon",
"coordinates": [
[[45.0, 45.0], [0.0, 0.0], [12.0, 12.0], [45.0, 45.0]],
[[4.0, 4.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]]
]
}
]
}
Feature¶
The Feature builder constructs a Feature object with a geometry, bounding
box, id, and properties. Properties can be any serializable object, such as a
JsonObject built with buildJsonObject from kotlinx.serialization.
val feature =
buildFeature(geometry = Point(-75.0, 45.0)) {
id = "point1"
bbox = BoundingBox(-76.9, 44.1, -74.2, 45.7)
properties = buildJsonObject {
put("name", "Hello World")
put("value", 13)
put("cool", true)
}
}
Point point = new Point(-75.0, 45.0);
JsonObjectBuilder propsBuilder = new JsonObjectBuilder();
JsonElementBuildersKt.put(propsBuilder, "name", "Hello World");
JsonElementBuildersKt.put(propsBuilder, "value", 13);
JsonElementBuildersKt.put(propsBuilder, "cool", true);
FeatureBuilder<Point, JsonObject> builder = new FeatureBuilder<>(point, propsBuilder.build());
builder.setId("point1");
builder.setBbox(new BoundingBox(-76.9, 44.1, -74.2, 45.7));
Feature<Point, JsonObject> feature = builder.build();
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-75.0, 45.0]
},
"properties": {
"name": "Hello World",
"value": 13,
"cool": true
},
"id": "point1",
"bbox": [-76.9, 44.1, -74.2, 45.7]
}
FeatureCollection¶
The FeatureCollection builder uses addFeature() to define features inline
(Kotlin only), or add() to add existing Feature objects.
val featureCollection = buildFeatureCollection {
addFeature {
geometry = Point(-75.0, 45.0)
properties = buildJsonObject { put("name", "Hello") }
}
}
Point point = new Point(-75.0, 45.0);
JsonObjectBuilder propsBuilder = new JsonObjectBuilder();
JsonElementBuildersKt.put(propsBuilder, "name", "Hello");
FeatureBuilder<Point, JsonObject> featureBuilder =
new FeatureBuilder<>(point, propsBuilder.build());
Feature<Point, JsonObject> feature = featureBuilder.build();
FeatureCollectionBuilder<Point, JsonObject> builder = new FeatureCollectionBuilder<>();
builder.add(feature);
FeatureCollection<Point, JsonObject> featureCollection = builder.build();
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-75.0, 45.0]
},
"properties": {
"name": "Hello"
}
}
]
}