Skip to content

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