Skip to content

Commit a4cd0de

Browse files
sargunvOpenCode
andauthored
Add convenience APIs and enhance serialization support (#267)
Co-authored-by: OpenCode <[email protected]>
1 parent 64fe3a8 commit a4cd0de

File tree

44 files changed

+813
-244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+813
-244
lines changed

buildSrc/src/main/kotlin/published-library.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ group = "org.maplibre.spatialk"
1414

1515
kotlin {
1616
explicitApi()
17+
compilerOptions {
18+
freeCompilerArgs =
19+
listOf(
20+
// Will be the default soon: https://youtrack.jetbrains.com/issue/KT-11914
21+
"-Xconsistent-data-class-copy-visibility"
22+
)
23+
}
1724
abiValidation {
1825
@OptIn(ExperimentalAbiValidation::class)
1926
enabled = true

geojson/api/geojson.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public final class org/maplibre/spatialk/geojson/FeatureCollection : java/util/C
9999
public fun equals (Ljava/lang/Object;)Z
100100
public static final fun fromJson (Ljava/lang/String;)Lorg/maplibre/spatialk/geojson/FeatureCollection;
101101
public static final fun fromJsonOrNull (Ljava/lang/String;)Lorg/maplibre/spatialk/geojson/FeatureCollection;
102+
public final fun get (I)Lorg/maplibre/spatialk/geojson/Feature;
102103
public fun getBbox ()Lorg/maplibre/spatialk/geojson/BoundingBox;
103104
public final fun getFeatures ()Ljava/util/List;
104105
public fun getSize ()I
@@ -449,6 +450,8 @@ public final class org/maplibre/spatialk/geojson/Point : org/maplibre/spatialk/g
449450
public static final fun fromJsonOrNull (Ljava/lang/String;)Lorg/maplibre/spatialk/geojson/Point;
450451
public fun getBbox ()Lorg/maplibre/spatialk/geojson/BoundingBox;
451452
public final fun getCoordinates ()Lorg/maplibre/spatialk/geojson/Position;
453+
public final fun getLatitude ()D
454+
public final fun getLongitude ()D
452455
public fun hashCode ()I
453456
public final fun toGeoUri ()Ljava/lang/String;
454457
public static final fun toJson (Lorg/maplibre/spatialk/geojson/Point;)Ljava/lang/String;

geojson/api/geojson.klib.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ final class <#A: out org.maplibre.spatialk.geojson/Geometry?, #B: out kotlin/Any
212212
final fun containsAll(kotlin.collections/Collection<org.maplibre.spatialk.geojson/Feature<#A, #B>>): kotlin/Boolean // org.maplibre.spatialk.geojson/FeatureCollection.containsAll|containsAll(kotlin.collections.Collection<org.maplibre.spatialk.geojson.Feature<1:0,1:1>>){}[0]
213213
final fun copy(kotlin.collections/List<org.maplibre.spatialk.geojson/Feature<#A, #B>> = ..., org.maplibre.spatialk.geojson/BoundingBox? = ...): org.maplibre.spatialk.geojson/FeatureCollection<#A, #B> // org.maplibre.spatialk.geojson/FeatureCollection.copy|copy(kotlin.collections.List<org.maplibre.spatialk.geojson.Feature<1:0,1:1>>;org.maplibre.spatialk.geojson.BoundingBox?){}[0]
214214
final fun equals(kotlin/Any?): kotlin/Boolean // org.maplibre.spatialk.geojson/FeatureCollection.equals|equals(kotlin.Any?){}[0]
215+
final fun get(kotlin/Int): org.maplibre.spatialk.geojson/Feature<#A, #B> // org.maplibre.spatialk.geojson/FeatureCollection.get|get(kotlin.Int){}[0]
215216
final fun hashCode(): kotlin/Int // org.maplibre.spatialk.geojson/FeatureCollection.hashCode|hashCode(){}[0]
216217
final fun isEmpty(): kotlin/Boolean // org.maplibre.spatialk.geojson/FeatureCollection.isEmpty|isEmpty(){}[0]
217218
final fun iterator(): kotlin.collections/Iterator<org.maplibre.spatialk.geojson/Feature<#A, #B>> // org.maplibre.spatialk.geojson/FeatureCollection.iterator|iterator(){}[0]
@@ -466,6 +467,10 @@ final class org.maplibre.spatialk.geojson/Point : org.maplibre.spatialk.geojson/
466467
final fun <get-bbox>(): org.maplibre.spatialk.geojson/BoundingBox? // org.maplibre.spatialk.geojson/Point.bbox.<get-bbox>|<get-bbox>(){}[0]
467468
final val coordinates // org.maplibre.spatialk.geojson/Point.coordinates|{}coordinates[0]
468469
final fun <get-coordinates>(): org.maplibre.spatialk.geojson/Position // org.maplibre.spatialk.geojson/Point.coordinates.<get-coordinates>|<get-coordinates>(){}[0]
470+
final val latitude // org.maplibre.spatialk.geojson/Point.latitude|{}latitude[0]
471+
final fun <get-latitude>(): kotlin/Double // org.maplibre.spatialk.geojson/Point.latitude.<get-latitude>|<get-latitude>(){}[0]
472+
final val longitude // org.maplibre.spatialk.geojson/Point.longitude|{}longitude[0]
473+
final fun <get-longitude>(): kotlin/Double // org.maplibre.spatialk.geojson/Point.longitude.<get-longitude>|<get-longitude>(){}[0]
469474

470475
final fun component1(): org.maplibre.spatialk.geojson/Position // org.maplibre.spatialk.geojson/Point.component1|component1(){}[0]
471476
final fun component2(): org.maplibre.spatialk.geojson/BoundingBox? // org.maplibre.spatialk.geojson/Point.component2|component2(){}[0]

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/FeatureCollection.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ constructor(
4141
bbox: BoundingBox? = null,
4242
) : this(features.toMutableList(), bbox)
4343

44+
/**
45+
* Get the feature at the specified index.
46+
*
47+
* @param index The index of the feature to retrieve.
48+
* @return The feature at the specified index.
49+
*/
50+
public operator fun get(index: Int): Feature<G, P> = features[index]
51+
4452
/** Factory methods for creating and serializing [FeatureCollection] objects. */
4553
public companion object {
4654
/**

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/Point.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ constructor(
2525
/** The [BoundingBox] of this [Point]. */
2626
override val bbox: BoundingBox? = null,
2727
) : SingleGeometry, PointGeometry {
28+
29+
/** The longitude value of this [Point] in degrees. */
30+
public val longitude: Double
31+
get() = coordinates.longitude
32+
33+
/** The latitude value of this [Point] in degrees. */
34+
public val latitude: Double
35+
get() = coordinates.latitude
36+
2837
/**
2938
* Create a [Point] from individual coordinate components.
3039
*

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/serialization/BaseGeometrySerializer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import kotlinx.serialization.encoding.Decoder
1313
import kotlinx.serialization.encoding.Encoder
1414
import kotlinx.serialization.encoding.decodeStructure
1515
import kotlinx.serialization.encoding.encodeStructure
16+
import kotlinx.serialization.json.JsonEncoder
1617
import org.maplibre.spatialk.geojson.BoundingBox
1718
import org.maplibre.spatialk.geojson.Geometry
1819

@@ -33,7 +34,7 @@ internal abstract class BaseGeometrySerializer<G : Geometry, C>(
3334
override fun serialize(encoder: Encoder, value: G) {
3435
encoder.encodeStructure(descriptor) {
3536
encodeSerializableElement(descriptor, 0, typeSerializer, serialName)
36-
if (value.bbox != null)
37+
if (value.bbox != null || encoder !is JsonEncoder)
3738
encodeSerializableElement(descriptor, 1, bboxSerializer, value.bbox)
3839
encodeSerializableElement(descriptor, 2, coordinatesSerializer, getCoordinates(value))
3940
}

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/serialization/FeatureCollectionSerializer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import kotlinx.serialization.encoding.Decoder
1414
import kotlinx.serialization.encoding.Encoder
1515
import kotlinx.serialization.encoding.decodeStructure
1616
import kotlinx.serialization.encoding.encodeStructure
17+
import kotlinx.serialization.json.JsonEncoder
1718
import org.maplibre.spatialk.geojson.BoundingBox
1819
import org.maplibre.spatialk.geojson.Feature
1920
import org.maplibre.spatialk.geojson.FeatureCollection
@@ -39,7 +40,7 @@ internal class FeatureCollectionSerializer<T : Geometry?, P : @Serializable Any?
3940
override fun serialize(encoder: Encoder, value: FeatureCollection<T, P>) {
4041
encoder.encodeStructure(descriptor) {
4142
encodeSerializableElement(descriptor, 0, typeSerializer, serialName)
42-
if (value.bbox != null)
43+
if (value.bbox != null || encoder !is JsonEncoder)
4344
encodeSerializableElement(descriptor, 1, bboxSerializer, value.bbox)
4445
encodeSerializableElement(descriptor, 2, featuresSerializer, value.features)
4546
}

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/serialization/FeatureSerializer.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.serialization.encoding.Decoder
1515
import kotlinx.serialization.encoding.Encoder
1616
import kotlinx.serialization.encoding.decodeStructure
1717
import kotlinx.serialization.encoding.encodeStructure
18+
import kotlinx.serialization.json.JsonEncoder
1819
import org.maplibre.spatialk.geojson.BoundingBox
1920
import org.maplibre.spatialk.geojson.Feature
2021
import org.maplibre.spatialk.geojson.Geometry
@@ -52,8 +53,9 @@ internal class FeatureSerializer<T : Geometry?, P : @Serializable Any?>(
5253
encodeSerializableElement(descriptor, 0, typeSerializer, serialName)
5354
encodeSerializableElement(descriptor, 1, geometrySerializer, value.geometry)
5455
encodeSerializableElement(descriptor, 2, propertiesSerializer, value.properties)
55-
if (value.id != null) encodeSerializableElement(descriptor, 3, idSerializer, value.id)
56-
if (value.bbox != null)
56+
if (value.id != null || encoder !is JsonEncoder)
57+
encodeSerializableElement(descriptor, 3, idSerializer, value.id)
58+
if (value.bbox != null || encoder !is JsonEncoder)
5759
encodeSerializableElement(descriptor, 4, bboxSerializer, value.bbox)
5860
}
5961
}

geojson/src/commonMain/kotlin/org/maplibre/spatialk/geojson/serialization/GeometryCollectionSerializer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import kotlinx.serialization.encoding.Decoder
1414
import kotlinx.serialization.encoding.Encoder
1515
import kotlinx.serialization.encoding.decodeStructure
1616
import kotlinx.serialization.encoding.encodeStructure
17+
import kotlinx.serialization.json.JsonEncoder
1718
import org.maplibre.spatialk.geojson.BoundingBox
1819
import org.maplibre.spatialk.geojson.Geometry
1920
import org.maplibre.spatialk.geojson.GeometryCollection
@@ -35,7 +36,7 @@ internal class GeometryCollectionSerializer<T : Geometry>(geometrySerializer: KS
3536
override fun serialize(encoder: Encoder, value: GeometryCollection<T>) {
3637
encoder.encodeStructure(descriptor) {
3738
encodeSerializableElement(descriptor, 0, typeSerializer, serialName)
38-
if (value.bbox != null)
39+
if (value.bbox != null || encoder !is JsonEncoder)
3940
encodeSerializableElement(descriptor, 1, bboxSerializer, value.bbox)
4041
encodeSerializableElement(descriptor, 2, geometriesSerializer, value.geometries)
4142
}

geojson/src/commonTest/kotlin/org/maplibre/spatialk/geojson/NonJsonFormatTest.kt

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,76 @@ package org.maplibre.spatialk.geojson
33
import kotlin.test.Ignore
44
import kotlin.test.Test
55
import kotlin.test.assertEquals
6+
import kotlinx.serialization.BinaryFormat
67
import kotlinx.serialization.ExperimentalSerializationApi
8+
import kotlinx.serialization.Serializable
79
import kotlinx.serialization.cbor.Cbor
810
import kotlinx.serialization.decodeFromByteArray
911
import kotlinx.serialization.encodeToByteArray
1012
import kotlinx.serialization.protobuf.ProtoBuf
1113
import org.maplibre.spatialk.geojson.dsl.buildLineString
1214

15+
@OptIn(ExperimentalSerializationApi::class)
1316
class NonJsonFormatTest {
1417
// NOTE: polymorphic types are not yet supported with non-json formats
15-
// That's Geometry, GeometryCollection, GeoJsonObject, FeatureCollection
16-
// Also Feature (because of properties: JsonObject) is not yet supported
18+
// That's Geometry (and related interfaces), GeoJsonObject
1719

18-
fun assertRoundTrip(roundTrip: (LineString) -> LineString) {
19-
val original = buildLineString {
20-
add(Position(1.0, 2.0))
21-
add(Position(2.0, 3.0))
22-
bbox = BoundingBox(1.0, 2.0, 2.0, 3.0)
23-
}
20+
private val testLineString = buildLineString {
21+
add(Position(1.0, 2.0))
22+
add(Position(2.0, 3.0))
23+
bbox = BoundingBox(1.0, 2.0, 2.0, 3.0)
24+
}
25+
26+
private val testGeometryCollection = GeometryCollection(testLineString)
27+
28+
private val testFeatureNullProps = Feature(testLineString, null)
29+
30+
@Serializable private data class ExampleProps(val x: Double = 1.0, val y: String = "two")
31+
32+
private val testFeatureClassProps = Feature(testLineString, ExampleProps())
33+
34+
private val testFeatureCollectionNullProps = FeatureCollection(testFeatureNullProps)
35+
private val testFeatureCollectionClassProps = FeatureCollection(testFeatureClassProps)
2436

25-
assertEquals(original, roundTrip(original))
37+
private inline fun <reified T> assertRoundTrip(format: BinaryFormat, value: T) {
38+
val encoded = format.encodeToByteArray(value)
39+
val decoded = format.decodeFromByteArray<T>(encoded)
40+
assertEquals(value, decoded)
2641
}
2742

43+
@Test fun testCborGeometry() = assertRoundTrip(Cbor, testLineString)
44+
45+
@Test fun testCborGeometryCollection() = assertRoundTrip(Cbor, testGeometryCollection)
46+
47+
@Test fun testCborFeature() = assertRoundTrip(Cbor, testFeatureNullProps)
48+
49+
@Test fun testCborFeatureProps() = assertRoundTrip(Cbor, testFeatureClassProps)
50+
51+
@Test fun testCborFeatureCollection() = assertRoundTrip(Cbor, testFeatureCollectionNullProps)
52+
2853
@Test
29-
fun testProtoBufSerialization() {
30-
assertRoundTrip { obj ->
31-
@OptIn(ExperimentalSerializationApi::class)
32-
ProtoBuf.decodeFromByteArray(ProtoBuf.encodeToByteArray(obj))
33-
}
34-
}
54+
fun testCborFeatureCollectionProps() = assertRoundTrip(Cbor, testFeatureCollectionClassProps)
55+
56+
@Test fun testProtobufGeometry() = assertRoundTrip(ProtoBuf, testLineString)
57+
58+
// Below are disabled because ProtoBuf doesn't support null collection types
59+
// Every GeoJSON object has a nullable bbox. Our test LineString above works
60+
// because we set a bbox.
3561

3662
@Test
37-
@Ignore // TODO
38-
fun testCborSerialization() {
39-
assertRoundTrip { obj ->
40-
@OptIn(ExperimentalSerializationApi::class)
41-
Cbor.decodeFromByteArray(ProtoBuf.encodeToByteArray(obj))
42-
}
43-
}
63+
@Ignore
64+
fun testProtobufGeometryCollection() = assertRoundTrip(ProtoBuf, testGeometryCollection)
65+
66+
@Test @Ignore fun testProtobufFeature() = assertRoundTrip(ProtoBuf, testFeatureNullProps)
67+
68+
@Test @Ignore fun testProtobufFeatureProps() = assertRoundTrip(ProtoBuf, testFeatureClassProps)
69+
70+
@Test
71+
@Ignore
72+
fun testProtobufFeatureCollection() = assertRoundTrip(ProtoBuf, testFeatureCollectionNullProps)
73+
74+
@Test
75+
@Ignore
76+
fun testProtobufFeatureCollectionProps() =
77+
assertRoundTrip(ProtoBuf, testFeatureCollectionClassProps)
4478
}

0 commit comments

Comments
 (0)