From 0c63ce6733d2e0e4ae636b78d3d0f298b8b9ed68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 30 May 2026 12:31:27 +0200 Subject: [PATCH] feat(bson): Geo types implement BsonFieldWriteable --- .../src/commonMain/kotlin/geo/GeoTest.kt | 119 ++++++++++++++++++ bson/src/commonMain/kotlin/types/Geo.kt | 99 ++++++++++++++- 2 files changed, 217 insertions(+), 1 deletion(-) diff --git a/bson-tests/src/commonMain/kotlin/geo/GeoTest.kt b/bson-tests/src/commonMain/kotlin/geo/GeoTest.kt index 3d5b3fa6..b3ee8749 100644 --- a/bson-tests/src/commonMain/kotlin/geo/GeoTest.kt +++ b/bson-tests/src/commonMain/kotlin/geo/GeoTest.kt @@ -57,6 +57,10 @@ private fun SuiteDsl.geoPoint(factory: Prepared) = suite("Point") { writeDouble(3.5) } }, + document { + Geo.Point(Geo.Longitude(2.0), Geo.Latitude(3.5)) + .writeTo(this) + }, json("""{"type": "Point", "coordinates": [2.0, 3.5]}"""), verify("The longitude is correct") { check(decode().x == Geo.Longitude(2.0)) @@ -91,6 +95,10 @@ private fun SuiteDsl.geoLineString(factory: Prepared) = suite("Line } } }, + document { + Geo.LineString(Geo.Point(Geo.Longitude(40.0), Geo.Latitude(5.0)), Geo.Point(Geo.Longitude(41.0), Geo.Latitude(6.0))) + .writeTo(this) + }, json("""{"type": "LineString", "coordinates": [[40.0, 5.0], [41.0, 6.0]]}"""), verify("The coordinates are correct") { check(decode().points[0] == Geo.Point(Geo.Longitude(40.0), Geo.Latitude(5.0))) @@ -124,6 +132,14 @@ private fun SuiteDsl.geoLineString(factory: Prepared) = suite("Line Geo.Point(Geo.Longitude(40.0), Geo.Latitude(5.0)), ) as Geo ), + document { + Geo.LineString( + Geo.Point(Geo.Longitude(40.0), Geo.Latitude(5.0)), + Geo.Point(Geo.Longitude(41.0), Geo.Latitude(6.0)), + Geo.Point(Geo.Longitude(41.5), Geo.Latitude(6.0)), + Geo.Point(Geo.Longitude(40.0), Geo.Latitude(5.0)), + ).writeTo(this) + }, document { writeString("type", "LineString") writeArray("coordinates") { @@ -189,6 +205,14 @@ private fun SuiteDsl.geoPolygon(factory: Prepared) = suite("Polygon Geo.Point(Geo.Longitude(0.0), Geo.Latitude(0.0)), ) as Geo ), + document { + Geo.Polygon( + Geo.Point(Geo.Longitude(0.0), Geo.Latitude(0.0)), + Geo.Point(Geo.Longitude(3.0), Geo.Latitude(6.0)), + Geo.Point(Geo.Longitude(6.0), Geo.Latitude(1.0)), + Geo.Point(Geo.Longitude(0.0), Geo.Latitude(0.0)), + ).writeTo(this) + }, document { writeString("type", "Polygon") writeArray("coordinates") { @@ -258,6 +282,24 @@ private fun SuiteDsl.geoPolygon(factory: Prepared) = suite("Polygon ), ) ), + document { + Geo.Polygon( + Geo.LineString( + Geo.Point(Geo.Longitude(0.0), Geo.Latitude(0.0)), + Geo.Point(Geo.Longitude(10.0), Geo.Latitude(0.0)), + Geo.Point(Geo.Longitude(10.0), Geo.Latitude(10.0)), + Geo.Point(Geo.Longitude(0.0), Geo.Latitude(10.0)), + Geo.Point(Geo.Longitude(0.0), Geo.Latitude(0.0)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(2.0), Geo.Latitude(2.0)), + Geo.Point(Geo.Longitude(8.0), Geo.Latitude(2.0)), + Geo.Point(Geo.Longitude(8.0), Geo.Latitude(8.0)), + Geo.Point(Geo.Longitude(2.0), Geo.Latitude(8.0)), + Geo.Point(Geo.Longitude(2.0), Geo.Latitude(2.0)), + ), + ).writeTo(this) + }, document { writeString("type", "Polygon") writeArray("coordinates") { @@ -356,6 +398,14 @@ private fun SuiteDsl.geoMultiPoint(factory: Prepared) = suite("Mult Geo.Point(Geo.Longitude(-73.9814), Geo.Latitude(40.7681)), ) as Geo ), + document { + Geo.MultiPoint( + Geo.Point(Geo.Longitude(-73.9580), Geo.Latitude(40.8003)), + Geo.Point(Geo.Longitude(-73.9498), Geo.Latitude(40.7968)), + Geo.Point(Geo.Longitude(-73.9737), Geo.Latitude(40.7648)), + Geo.Point(Geo.Longitude(-73.9814), Geo.Latitude(40.7681)), + ).writeTo(this) + }, document { writeString("type", "MultiPoint") writeArray("coordinates") { @@ -445,6 +495,26 @@ private fun SuiteDsl.geoMultiLineString(factory: Prepared) = suite( ), ) as Geo ), + document { + Geo.MultiLineString( + Geo.LineString( + Geo.Point(Geo.Longitude(-73.96943), Geo.Latitude(40.78519)), + Geo.Point(Geo.Longitude(-73.96082), Geo.Latitude(40.78095)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.96415), Geo.Latitude(40.79229)), + Geo.Point(Geo.Longitude(-73.95544), Geo.Latitude(40.78854)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.97162), Geo.Latitude(40.78205)), + Geo.Point(Geo.Longitude(-73.96374), Geo.Latitude(40.77715)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.97880), Geo.Latitude(40.77247)), + Geo.Point(Geo.Longitude(-73.97036), Geo.Latitude(40.76811)), + ), + ).writeTo(this) + }, document { writeString("type", "MultiLineString") writeArray("coordinates") { @@ -576,6 +646,27 @@ private fun SuiteDsl.geoMultiPolygon(factory: Prepared) = suite("Mu ), ) as Geo ), + document { + Geo.MultiPolygon( + Geo.Polygon( + Geo.LineString( + Geo.Point(Geo.Longitude(-73.958), Geo.Latitude(40.8003)), + Geo.Point(Geo.Longitude(-73.9498), Geo.Latitude(40.7968)), + Geo.Point(Geo.Longitude(-73.9737), Geo.Latitude(40.7648)), + Geo.Point(Geo.Longitude(-73.9814), Geo.Latitude(40.7681)), + Geo.Point(Geo.Longitude(-73.958), Geo.Latitude(40.8003)), + ), + ), + Geo.Polygon( + Geo.LineString( + Geo.Point(Geo.Longitude(-73.958), Geo.Latitude(40.8003)), + Geo.Point(Geo.Longitude(-73.9498), Geo.Latitude(40.7968)), + Geo.Point(Geo.Longitude(-73.9737), Geo.Latitude(40.7648)), + Geo.Point(Geo.Longitude(-73.958), Geo.Latitude(40.8003)), + ), + ), + ).writeTo(this) + }, document { writeString("type", "MultiPolygon") writeArray("coordinates") { @@ -699,6 +790,34 @@ private fun SuiteDsl.geoGeometryCollection(factory: Prepared) = sui ) ) ), + document { + Geo.GeometryCollection( + Geo.MultiPoint( + Geo.Point(Geo.Longitude(-73.9580), Geo.Latitude(40.8003)), + Geo.Point(Geo.Longitude(-73.9498), Geo.Latitude(40.7968)), + Geo.Point(Geo.Longitude(-73.9737), Geo.Latitude(40.7648)), + Geo.Point(Geo.Longitude(-73.9814), Geo.Latitude(40.7681)), + ), + Geo.MultiLineString( + Geo.LineString( + Geo.Point(Geo.Longitude(-73.96943), Geo.Latitude(40.78519)), + Geo.Point(Geo.Longitude(-73.96082), Geo.Latitude(40.78095)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.96415), Geo.Latitude(40.79229)), + Geo.Point(Geo.Longitude(-73.95544), Geo.Latitude(40.78854)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.97162), Geo.Latitude(40.78205)), + Geo.Point(Geo.Longitude(-73.96374), Geo.Latitude(40.77715)), + ), + Geo.LineString( + Geo.Point(Geo.Longitude(-73.97880), Geo.Latitude(40.77247)), + Geo.Point(Geo.Longitude(-73.97036), Geo.Latitude(40.76811)), + ), + ) + ).writeTo(this) + }, json("""{"type": "GeometryCollection", "geometries": [{"type": "MultiPoint", "coordinates": [[-73.958, 40.8003], [-73.9498, 40.7968], [-73.9737, 40.7648], [-73.9814, 40.7681]]}, {"type": "MultiLineString", "coordinates": [[[-73.96943, 40.78519], [-73.96082, 40.78095]], [[-73.96415, 40.79229], [-73.95544, 40.78854]], [[-73.97162, 40.78205], [-73.96374, 40.77715]], [[-73.9788, 40.77247], [-73.97036, 40.76811]]]}]}"""), document { writeString("type", "GeometryCollection") diff --git a/bson/src/commonMain/kotlin/types/Geo.kt b/bson/src/commonMain/kotlin/types/Geo.kt index df9275db..3dc388fe 100644 --- a/bson/src/commonMain/kotlin/types/Geo.kt +++ b/bson/src/commonMain/kotlin/types/Geo.kt @@ -26,6 +26,8 @@ import kotlinx.serialization.descriptors.buildSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import opensavvy.ktmongo.bson.BsonDocument +import opensavvy.ktmongo.bson.BsonFieldWriteable +import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.bson.decode import opensavvy.ktmongo.bson.types.Geo.CoordinateReferenceSystem.Companion.MongoDB import opensavvy.ktmongo.dsl.LowLevelApi @@ -50,7 +52,7 @@ annotation class ExperimentalGeoBsonApi @OptIn(LowLevelApi::class) @ExperimentalGeoBsonApi @Serializable(with = Geo.Serializer::class) -sealed class Geo { +sealed class Geo : BsonFieldWriteable { /** * A longitude. @@ -177,6 +179,14 @@ sealed class Geo { override fun toString() = "Point(${x.degrees}° E, ${y.degrees}° N)" + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "Point") + writeArray("coordinates") { + writeDouble(x.degrees) + writeDouble(y.degrees) + } + } + @Serializable private data class Surrogate( val type: String, @@ -258,6 +268,18 @@ sealed class Geo { val isClosed: Boolean get() = points.first() == points.last() + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "LineString") + writeArray("coordinates") { + for (point in points) { + writeArray { + writeDouble(point.x.degrees) + writeDouble(point.y.degrees) + } + } + } + } + override fun toString() = "LineString(${points.joinToString(", ")})" @Serializable @@ -395,6 +417,22 @@ sealed class Geo { } } + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "Polygon") + writeArray("coordinates") { + for (ring in rings) { + writeArray { + for (point in ring.points) { + writeArray { + writeDouble(point.x.degrees) + writeDouble(point.y.degrees) + } + } + } + } + } + } + override fun toString() = "Polygon(${rings.joinToString(", ")})" @Serializable @@ -471,6 +509,18 @@ sealed class Geo { */ constructor(vararg points: Point) : this(points.asList()) + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "MultiPoint") + writeArray("coordinates") { + for (point in points) { + writeArray { + writeDouble(point.x.degrees) + writeDouble(point.y.degrees) + } + } + } + } + override fun toString() = "MultiPoint(${points.joinToString(", ")})" @Serializable @@ -539,6 +589,22 @@ sealed class Geo { */ constructor(vararg lineStrings: LineString) : this(lineStrings.asList()) + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "MultiLineString") + writeArray("coordinates") { + for (lineString in lineStrings) { + writeArray { + for (point in lineString.points) { + writeArray { + writeDouble(point.x.degrees) + writeDouble(point.y.degrees) + } + } + } + } + } + } + override fun toString() = "MultiLineString(${lineStrings.joinToString(", ")})" @Serializable @@ -614,6 +680,26 @@ sealed class Geo { */ constructor(vararg polygons: Polygon) : this(polygons.asList()) + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "MultiPolygon") + writeArray("coordinates") { + for (polygon in polygons) { + writeArray { + for (ring in polygon.rings) { + writeArray { + for (point in ring.points) { + writeArray { + writeDouble(point.x.degrees) + writeDouble(point.y.degrees) + } + } + } + } + } + } + } + } + override fun toString() = "MultiPolygon(${polygons.joinToString(", ")})" @Serializable @@ -701,6 +787,17 @@ sealed class Geo { constructor(vararg geometries: Geo) : this(geometries.asList()) + override fun writeTo(writer: BsonFieldWriter) = with(writer) { + writeString("type", "GeometryCollection") + writeArray("geometries") { + for (geometry in geometries) { + writeDocument { + geometry.writeTo(this) + } + } + } + } + override fun toString(): String = "GeometryCollection(${geometries.joinToString(", ")})" @Serializable -- 2.51.2