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"
}
}
]
}