GeoJson builders

This commit is contained in:
Alexander Nozik 2023-01-12 14:50:10 +03:00
parent 0f00abb1b2
commit b6a3ce0fe7
14 changed files with 201 additions and 51 deletions

View File

@ -8,11 +8,7 @@ plugins {
allprojects { allprojects {
group = "center.sciprog" group = "center.sciprog"
version = "0.2.1-dev-2" version = "0.2.1-dev-3"
}
apiValidation{
validationDisabled = true
} }
ksciencePublish{ ksciencePublish{

View File

@ -23,7 +23,7 @@ internal data class GmcRectangle(
} }
public val Rectangle<Gmc>.center: GeodeticMapCoordinates public val Rectangle<Gmc>.center: GeodeticMapCoordinates
get() = GeodeticMapCoordinates( get() = GeodeticMapCoordinates.normalized(
(a.latitude + b.latitude) / 2, (a.latitude + b.latitude) / 2,
(a.longitude + b.longitude) / 2 (a.longitude + b.longitude) / 2
) )
@ -51,8 +51,8 @@ public val Rectangle<Gmc>.bottom: Angle get() = minOf(a.latitude, b.latitude)
public val Rectangle<Gmc>.longitudeDelta: Angle get() = abs(a.longitude - b.longitude) public val Rectangle<Gmc>.longitudeDelta: Angle get() = abs(a.longitude - b.longitude)
public val Rectangle<Gmc>.latitudeDelta: Angle get() = abs(a.latitude - b.latitude) public val Rectangle<Gmc>.latitudeDelta: Angle get() = abs(a.latitude - b.latitude)
public val Rectangle<Gmc>.topLeft: GeodeticMapCoordinates get() = GeodeticMapCoordinates(top, left) public val Rectangle<Gmc>.topLeft: Gmc get() = Gmc.normalized(top, left)
public val Rectangle<Gmc>.bottomRight: GeodeticMapCoordinates get() = GeodeticMapCoordinates(bottom, right) public val Rectangle<Gmc>.bottomRight: Gmc get() = Gmc.normalized(bottom, right)
//public fun GmcRectangle.enlarge( //public fun GmcRectangle.enlarge(
// top: Distance, // top: Distance,

View File

@ -3,7 +3,6 @@ package center.sciprog.maps.compose
import center.sciprog.maps.coordinates.GeodeticMapCoordinates import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.WebMercatorProjection import center.sciprog.maps.coordinates.WebMercatorProjection
import center.sciprog.maps.coordinates.radians
import center.sciprog.maps.features.ViewPoint import center.sciprog.maps.features.ViewPoint
/** /**
@ -16,6 +15,6 @@ internal data class MapViewPoint(
val scaleFactor: Float by lazy { WebMercatorProjection.scaleFactor(zoom) } val scaleFactor: Float by lazy { WebMercatorProjection.scaleFactor(zoom) }
public companion object{ public companion object{
public val globe: MapViewPoint = MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1f) public val globe: MapViewPoint = MapViewPoint(Gmc.ofRadians(0.0, 0.0), 1f)
} }
} }

View File

@ -66,7 +66,7 @@ public class MapViewScope internal constructor(
override fun ViewPoint<Gmc>.moveBy(x: Dp, y: Dp): ViewPoint<Gmc> { override fun ViewPoint<Gmc>.moveBy(x: Dp, y: Dp): ViewPoint<Gmc> {
val deltaX = x.value / tileScale val deltaX = x.value / tileScale
val deltaY = y.value / tileScale val deltaY = y.value / tileScale
val newCoordinates = GeodeticMapCoordinates( val newCoordinates = Gmc.normalized(
(focus.latitude + (deltaY / scaleFactor).radians).coerceIn( (focus.latitude + (deltaY / scaleFactor).radians).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE, -MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE MercatorProjection.MAXIMUM_LATITUDE

View File

@ -29,7 +29,7 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
override fun ViewPoint(center: Gmc, zoom: Float): ViewPoint<Gmc> = MapViewPoint(center, zoom) override fun ViewPoint(center: Gmc, zoom: Float): ViewPoint<Gmc> = MapViewPoint(center, zoom)
override fun ViewPoint<Gmc>.moveBy(delta: Gmc): ViewPoint<Gmc> { override fun ViewPoint<Gmc>.moveBy(delta: Gmc): ViewPoint<Gmc> {
val newCoordinates = GeodeticMapCoordinates( val newCoordinates = Gmc.normalized(
(focus.latitude + delta.latitude).coerceIn( (focus.latitude + delta.latitude).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE, -MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE MercatorProjection.MAXIMUM_LATITUDE
@ -43,7 +43,7 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
ViewPoint(focus, (zoom + zoomDelta).coerceIn(2f, 18f)) ViewPoint(focus, (zoom + zoomDelta).coerceIn(2f, 18f))
} else { } else {
val difScale = (1 - 2f.pow(-zoomDelta)) val difScale = (1 - 2f.pow(-zoomDelta))
val newCenter = GeodeticMapCoordinates( val newCenter = Gmc.normalized(
focus.latitude + (invariant.latitude - focus.latitude) * difScale, focus.latitude + (invariant.latitude - focus.latitude) * difScale,
focus.longitude + (invariant.longitude - focus.longitude) * difScale focus.longitude + (invariant.longitude - focus.longitude) * difScale
) )
@ -60,7 +60,7 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
val maxLat = maxOf { it.top } val maxLat = maxOf { it.top }
val minLong = minOf { it.left } val minLong = minOf { it.left }
val maxLong = maxOf { it.right } val maxLong = maxOf { it.right }
return GmcRectangle(GeodeticMapCoordinates(minLat, minLong), GeodeticMapCoordinates(maxLat, maxLong)) return GmcRectangle(Gmc.normalized(minLat, minLong), Gmc.normalized(maxLat, maxLong))
} }
override fun Collection<Gmc>.wrapPoints(): Rectangle<Gmc>? { override fun Collection<Gmc>.wrapPoints(): Rectangle<Gmc>? {
@ -70,7 +70,7 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
val maxLat = maxOf { it.latitude } val maxLat = maxOf { it.latitude }
val minLong = minOf { it.longitude } val minLong = minOf { it.longitude }
val maxLong = maxOf { it.longitude } val maxLong = maxOf { it.longitude }
return GmcRectangle(GeodeticMapCoordinates(minLat, minLong), GeodeticMapCoordinates(maxLat, maxLong)) return GmcRectangle(Gmc.normalized(minLat, minLong), Gmc.normalized(maxLat, maxLong))
} }
override fun Gmc.offsetTo(b: Gmc, zoom: Float): DpOffset { override fun Gmc.offsetTo(b: Gmc, zoom: Float): DpOffset {
@ -122,11 +122,11 @@ public fun CoordinateSpace<Gmc>.Rectangle(
height: Angle, height: Angle,
width: Angle, width: Angle,
): Rectangle<Gmc> { ): Rectangle<Gmc> {
val a = GeodeticMapCoordinates( val a = Gmc.normalized(
center.latitude - (height / 2), center.latitude - (height / 2),
center.longitude - (width / 2) center.longitude - (width / 2)
) )
val b = GeodeticMapCoordinates( val b = Gmc.normalized(
center.latitude + (height / 2), center.latitude + (height / 2),
center.longitude + (width / 2) center.longitude + (width / 2)
) )

View File

@ -5,13 +5,12 @@ package center.sciprog.maps.coordinates
*/ */
public class GeodeticMapCoordinates( public class GeodeticMapCoordinates(
public val latitude: Angle, public val latitude: Angle,
longitude: Angle, public val longitude: Angle,
public val elevation: Distance = 0.kilometers public val elevation: Distance = 0.kilometers,
) { ) {
public val longitude: Angle = longitude.normalized(Angle.zero)
init { init {
require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" } require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" }
require(longitude in (-Angle.pi..Angle.pi)) { "Longitude $longitude is not in (-PI..PI) range" }
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -38,14 +37,29 @@ public class GeodeticMapCoordinates(
public companion object { public companion object {
public fun ofRadians(latitude: Double, longitude: Double): GeodeticMapCoordinates = public fun normalized(
GeodeticMapCoordinates(latitude.radians, longitude.radians) latitude: Angle,
longitude: Angle,
elevation: Distance = 0.kilometers,
): GeodeticMapCoordinates = GeodeticMapCoordinates(
latitude, longitude.normalized(Angle.zero), elevation
)
public fun ofDegrees(latitude: Double, longitude: Double): GeodeticMapCoordinates = public fun ofRadians(
GeodeticMapCoordinates(latitude.degrees.radians, longitude.degrees.radians) latitude: Double,
longitude: Double,
elevation: Distance = 0.kilometers,
): GeodeticMapCoordinates = normalized(latitude.radians, longitude.radians, elevation)
public fun ofDegrees(
latitude: Double,
longitude: Double,
elevation: Distance = 0.kilometers,
): GeodeticMapCoordinates = normalized(latitude.degrees.radians, longitude.degrees.radians, elevation)
} }
} }
/** /**
* Short name for GeodeticMapCoordinates * Short name for GeodeticMapCoordinates
*/ */

View File

@ -64,8 +64,8 @@ public fun GeoEllipsoid.meridianCurve(
} }
return GmcCurve( return GmcCurve(
forward = GmcPose(Gmc(fromLatitude, longitude), if (up) zero else pi), forward = GmcPose(Gmc.normalized(fromLatitude, longitude), if (up) zero else pi),
backward = GmcPose(Gmc(toLatitude, longitude), if (up) pi else zero), backward = GmcPose(Gmc.normalized(toLatitude, longitude), if (up) pi else zero),
distance = s distance = s
) )
} }
@ -77,8 +77,8 @@ public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toL
require(latitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" } require(latitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
val right = toLongitude > fromLongitude val right = toLongitude > fromLongitude
return GmcCurve( return GmcCurve(
forward = GmcPose(Gmc(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians), forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians),
backward = GmcPose(Gmc(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians), backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians),
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians.value) distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians.value)
) )
} }
@ -193,7 +193,7 @@ public fun GeoEllipsoid.curveInDirection(
val L = lambda - (1 - C) * f * sinAlpha * val L = lambda - (1 - C) * f * sinAlpha *
(sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2))) (sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)))
val endPoint = Gmc(phi2, start.longitude + L.radians) val endPoint = Gmc.normalized(phi2, start.longitude + L.radians)
// eq. 12 // eq. 12

View File

@ -13,4 +13,10 @@ kotlin {
} }
} }
} }
}
kscience{
useSerialization {
json()
}
} }

View File

@ -3,12 +3,14 @@ package center.sciprog.maps.geojson
import center.sciprog.maps.geojson.GeoJson.Companion.PROPERTIES_KEY import center.sciprog.maps.geojson.GeoJson.Companion.PROPERTIES_KEY
import center.sciprog.maps.geojson.GeoJson.Companion.TYPE_KEY import center.sciprog.maps.geojson.GeoJson.Companion.TYPE_KEY
import center.sciprog.maps.geojson.GeoJsonFeatureCollection.Companion.FEATURES_KEY import center.sciprog.maps.geojson.GeoJsonFeatureCollection.Companion.FEATURES_KEY
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
/** /**
* A utility class to work with GeoJson (https://geojson.org/) * A utility class to work with GeoJson (https://geojson.org/)
*/ */
@Serializable(GeoJsonSerializer::class)
public sealed interface GeoJson { public sealed interface GeoJson {
public val json: JsonObject public val json: JsonObject
public val type: String get() = json[TYPE_KEY]?.jsonPrimitive?.content ?: error("Not a GeoJson") public val type: String get() = json[TYPE_KEY]?.jsonPrimitive?.content ?: error("Not a GeoJson")
@ -34,6 +36,23 @@ public value class GeoJsonFeature(override val json: JsonObject) : GeoJson {
} }
} }
/**
* A builder function for [GeoJsonFeature]
*/
public fun GeoJsonFeature(
geometry: GeoJsonGeometry?,
properties: JsonObject? = null,
builder: JsonObjectBuilder.() -> Unit = {},
): GeoJsonFeature = GeoJsonFeature(
buildJsonObject {
put(TYPE_KEY, "Feature")
geometry?.json?.let { put(GeoJsonFeature.GEOMETRY_KEY, it) }
properties?.let { put(PROPERTIES_KEY, it) }
builder()
}
)
public fun GeoJsonFeature.getProperty(key: String): JsonElement? = json[key] ?: properties?.get(key) public fun GeoJsonFeature.getProperty(key: String): JsonElement? = json[key] ?: properties?.get(key)
@JvmInline @JvmInline
@ -60,20 +79,33 @@ public value class GeoJsonFeatureCollection(override val json: JsonObject) : Geo
} }
} }
/**
* A builder for [GeoJsonFeatureCollection]
*/
public fun GeoJsonFeatureCollection(
features: List<GeoJsonFeature>,
properties: JsonObject? = null,
builder: JsonObjectBuilder.() -> Unit = {},
): GeoJsonFeatureCollection = GeoJsonFeatureCollection(
buildJsonObject {
put(TYPE_KEY, "FeatureCollection")
putJsonArray(FEATURES_KEY) {
features.forEach {
add(it.json)
}
}
properties?.let { put(PROPERTIES_KEY, it) }
builder()
}
)
/**
* Generic Json to GeoJson converter
*/
public fun GeoJson(json: JsonObject): GeoJson = public fun GeoJson(json: JsonObject): GeoJson =
when (json[TYPE_KEY]?.jsonPrimitive?.contentOrNull ?: error("Not a GeoJson")) { when (json[TYPE_KEY]?.jsonPrimitive?.contentOrNull ?: error("Not a GeoJson")) {
"Feature" -> GeoJsonFeature(json) "Feature" -> GeoJsonFeature(json)
"FeatureCollection" -> GeoJsonFeatureCollection(json) "FeatureCollection" -> GeoJsonFeatureCollection(json)
else -> GeoJsonGeometry(json) else -> GeoJsonGeometry(json)
} }
/**
* Combine a collection of features to a new [GeoJsonFeatureCollection]
*/
public fun GeoJsonFeatureCollection(features: Collection<GeoJsonFeature>): GeoJsonFeatureCollection =
GeoJsonFeatureCollection(
buildJsonObject {
put(TYPE_KEY, "FeatureCollection")
put(FEATURES_KEY, JsonArray(features.map { it.json }))
}
)

View File

@ -1,6 +1,8 @@
package center.sciprog.maps.geojson package center.sciprog.maps.geojson
import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.kilometers
import center.sciprog.maps.coordinates.meters
import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
@ -25,7 +27,31 @@ public fun GeoJsonGeometry(json: JsonObject): GeoJsonGeometry {
} }
internal fun JsonElement.toGmc() = jsonArray.run { internal fun JsonElement.toGmc() = jsonArray.run {
Gmc.ofDegrees(get(1).jsonPrimitive.double, get(0).jsonPrimitive.double) Gmc.ofDegrees(
get(1).jsonPrimitive.double,
get(0).jsonPrimitive.double,
get(2).jsonPrimitive.doubleOrNull?.meters ?: 0.kilometers
)
}
internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray {
add(longitude.degrees.value)
add(latitude.degrees.value)
if (elevation.kilometers != 0.0) {
add(elevation.meters)
}
}
private fun List<Gmc>.listToJsonArray(): JsonArray = buildJsonArray {
forEach {
add(it.toJsonArray())
}
}
private fun List<List<Gmc>>.listOfListsToJsonArray(): JsonArray = buildJsonArray {
forEach {
add(it.listToJsonArray())
}
} }
@JvmInline @JvmInline
@ -39,6 +65,13 @@ public value class GeoJsonPoint(override val json: JsonObject) : GeoJsonGeometry
?: error("Coordinates are not provided") ?: error("Coordinates are not provided")
} }
public fun GeoJsonPoint(coordinates: Gmc): GeoJsonPoint = GeoJsonPoint(
buildJsonObject {
put(GeoJson.TYPE_KEY, "Point")
put(COORDINATES_KEY, coordinates.toJsonArray())
}
)
@JvmInline @JvmInline
public value class GeoJsonMultiPoint(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonMultiPoint(override val json: JsonObject) : GeoJsonGeometry {
init { init {
@ -51,6 +84,13 @@ public value class GeoJsonMultiPoint(override val json: JsonObject) : GeoJsonGeo
?: error("Coordinates are not provided") ?: error("Coordinates are not provided")
} }
public fun GeoJsonMultiPoint(coordinates: List<Gmc>): GeoJsonMultiPoint = GeoJsonMultiPoint(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiPoint")
put(COORDINATES_KEY, coordinates.listToJsonArray())
}
)
@JvmInline @JvmInline
public value class GeoJsonLineString(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonLineString(override val json: JsonObject) : GeoJsonGeometry {
init { init {
@ -63,6 +103,13 @@ public value class GeoJsonLineString(override val json: JsonObject) : GeoJsonGeo
?: error("Coordinates are not provided") ?: error("Coordinates are not provided")
} }
public fun GeoJsonLineString(coordinates: List<Gmc>): GeoJsonLineString = GeoJsonLineString(
buildJsonObject {
put(GeoJson.TYPE_KEY, "LineString")
put(COORDINATES_KEY, coordinates.listToJsonArray())
}
)
@JvmInline @JvmInline
public value class GeoJsonMultiLineString(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonMultiLineString(override val json: JsonObject) : GeoJsonGeometry {
init { init {
@ -77,6 +124,13 @@ public value class GeoJsonMultiLineString(override val json: JsonObject) : GeoJs
} ?: error("Coordinates are not provided") } ?: error("Coordinates are not provided")
} }
public fun GeoJsonMultiLineString(coordinates: List<List<Gmc>>): GeoJsonMultiLineString = GeoJsonMultiLineString(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiLineString")
put(COORDINATES_KEY, coordinates.listOfListsToJsonArray())
}
)
@JvmInline @JvmInline
public value class GeoJsonPolygon(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonPolygon(override val json: JsonObject) : GeoJsonGeometry {
init { init {
@ -91,6 +145,13 @@ public value class GeoJsonPolygon(override val json: JsonObject) : GeoJsonGeomet
} ?: error("Coordinates are not provided") } ?: error("Coordinates are not provided")
} }
public fun GeoJsonPolygon(coordinates: List<List<Gmc>>): GeoJsonPolygon = GeoJsonPolygon(
buildJsonObject {
put(GeoJson.TYPE_KEY, "Polygon")
put(COORDINATES_KEY, coordinates.listOfListsToJsonArray())
}
)
@JvmInline @JvmInline
public value class GeoJsonMultiPolygon(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonMultiPolygon(override val json: JsonObject) : GeoJsonGeometry {
init { init {
@ -107,11 +168,31 @@ public value class GeoJsonMultiPolygon(override val json: JsonObject) : GeoJsonG
} ?: error("Coordinates are not provided") } ?: error("Coordinates are not provided")
} }
public fun GeoJsonMultiPolygon(coordinates: List<List<List<Gmc>>>): GeoJsonMultiPolygon = GeoJsonMultiPolygon(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiPolygon")
put(COORDINATES_KEY, buildJsonArray { coordinates.forEach { add(it.listOfListsToJsonArray()) } })
}
)
@JvmInline @JvmInline
public value class GeoJsonGeometryCollection(override val json: JsonObject) : GeoJsonGeometry { public value class GeoJsonGeometryCollection(override val json: JsonObject) : GeoJsonGeometry {
init { init {
require(type == "GeometryCollection") { "Not a GeoJson GeometryCollection geometry" } require(type == "GeometryCollection") { "Not a GeoJson GeometryCollection geometry" }
} }
public val geometries: List<GeoJsonGeometry> get() = json.jsonArray.map { GeoJsonGeometry(it.jsonObject) } public val geometries: List<GeoJsonGeometry>
} get() = json["geometries"]?.jsonArray?.map { GeoJsonGeometry(it.jsonObject) } ?: emptyList()
}
public fun GeoJsonGeometryCollection(geometries: List<GeoJsonGeometry>): GeoJsonGeometryCollection =
GeoJsonGeometryCollection(
buildJsonObject {
put(GeoJson.TYPE_KEY, "GeometryCollection")
put("geometries", buildJsonArray {
geometries.forEach {
add(it.json)
}
})
}
)

View File

@ -1,6 +0,0 @@
package center.sciprog.maps.geojson
import center.sciprog.attributes.Attribute
import kotlinx.serialization.json.JsonObject
public object GeoJsonPropertiesAttribute : Attribute<JsonObject>

View File

@ -0,0 +1,7 @@
package center.sciprog.maps.geojson
import center.sciprog.attributes.SerializableAttribute
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.serializer
public object GeoJsonPropertiesAttribute : SerializableAttribute<JsonObject>("properties", serializer())

View File

@ -0,0 +1,21 @@
package center.sciprog.maps.geojson
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonObject
public object GeoJsonSerializer : KSerializer<GeoJson> {
private val serializer = JsonObject.serializer()
override val descriptor: SerialDescriptor
get() = serializer.descriptor
override fun deserialize(decoder: Decoder): GeoJson = GeoJson(serializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: GeoJson) {
serializer.serialize(encoder, value.json)
}
}