Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GeoJSON foreign members #129

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.dellisd.spatialk.geojson

import io.github.dellisd.spatialk.geojson.serialization.FeatureSerializer
import io.github.dellisd.spatialk.geojson.serialization.foreignMembers
import io.github.dellisd.spatialk.geojson.serialization.foreignMembersJsonProps
import io.github.dellisd.spatialk.geojson.serialization.idProp
import io.github.dellisd.spatialk.geojson.serialization.jsonProp
import io.github.dellisd.spatialk.geojson.serialization.toBbox
Expand Down Expand Up @@ -36,11 +38,15 @@ class Feature(
val geometry: Geometry?,
properties: Map<String, JsonElement> = emptyMap(),
val id: String? = null,
override val bbox: BoundingBox? = null
override val bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : GeoJson {
private val _properties: MutableMap<String, JsonElement> = properties.toMutableMap()
val properties: Map<String, JsonElement> get() = _properties

private val _foreignMembers: MutableMap<String, JsonElement> = foreignMembers.toMutableMap()
override val foreignMembers: MutableMap<String, JsonElement> get() = _foreignMembers

fun setStringProperty(key: String, value: String?) {
_properties[key] = JsonPrimitive(value)
}
Expand Down Expand Up @@ -86,6 +92,7 @@ class Feature(
if (id != other.id) return false
if (bbox != other.bbox) return false
if (_properties != other._properties) return false
if (_foreignMembers != other._foreignMembers) return false

return true
}
Expand All @@ -95,13 +102,15 @@ class Feature(
result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + (bbox?.hashCode() ?: 0)
result = 31 * result + _properties.hashCode()
result = 31 * result + _foreignMembers.hashCode()
return result
}

operator fun component1() = geometry
operator fun component2() = properties
operator fun component3() = id
operator fun component4() = bbox
operator fun component5() = foreignMembers

override fun toString(): String = json()

Expand All @@ -113,16 +122,18 @@ class Feature(
JsonElement.serializer()
), properties
)
}}"""
}${foreignMembersJsonProps()}}"""

fun copy(
geometry: Geometry? = this.geometry,
properties: Map<String, JsonElement> = this.properties,
id: String? = this.id,
bbox: BoundingBox? = this.bbox
): Feature = Feature(geometry, properties, id, bbox)
bbox: BoundingBox? = this.bbox,
foreignMembers: Map<String, JsonElement> = this.foreignMembers
): Feature = Feature(geometry, properties, id, bbox, foreignMembers)

companion object {

@JvmStatic
fun fromJson(json: String): Feature = fromJson(Json.decodeFromString(JsonObject.serializer(), json))

Expand All @@ -145,7 +156,10 @@ class Feature(
val geom = json["geometry"]?.jsonObject
val geometry: Geometry? = if (geom != null) Geometry.fromJson(geom) else null

return Feature(geometry, json["properties"]?.jsonObject ?: emptyMap(), id, bbox)
val properties = json["properties"]?.jsonObject ?: emptyMap()
val foreignMembers = json.foreignMembers()

return Feature(geometry, properties, id, bbox, foreignMembers)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.github.dellisd.spatialk.geojson

import io.github.dellisd.spatialk.geojson.serialization.FeatureCollectionSerializer
import io.github.dellisd.spatialk.geojson.serialization.foreignMembers
import io.github.dellisd.spatialk.geojson.serialization.foreignMembersJsonProps
import io.github.dellisd.spatialk.geojson.serialization.jsonJoin
import io.github.dellisd.spatialk.geojson.serialization.jsonProp
import io.github.dellisd.spatialk.geojson.serialization.toBbox
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
Expand All @@ -24,10 +27,18 @@ import kotlin.jvm.JvmStatic
@Serializable(with = FeatureCollectionSerializer::class)
class FeatureCollection(
val features: List<Feature> = emptyList(),
override val bbox: BoundingBox? = null
override val bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : Collection<Feature> by features, GeoJson {

constructor(vararg features: Feature, bbox: BoundingBox? = null) : this(features.toMutableList(), bbox)
private val _foreignMembers: MutableMap<String, JsonElement> = foreignMembers.toMutableMap()
override val foreignMembers: MutableMap<String, JsonElement> get() = _foreignMembers

constructor(
vararg features: Feature,
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : this(features.toMutableList(), bbox, foreignMembers)

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -37,25 +48,30 @@ class FeatureCollection(

if (features != other.features) return false
if (bbox != other.bbox) return false
if (_foreignMembers != other._foreignMembers) return false

return true
}

override fun hashCode(): Int {
var result = features.hashCode()
result = 31 * result + (bbox?.hashCode() ?: 0)
result = 31 * result + _foreignMembers.hashCode()
return result
}

override fun toString(): String = json()

@Suppress("MaxLineLength")
override fun json(): String =
"""{"type":"FeatureCollection",${bbox.jsonProp()}"features":${features.jsonJoin { it.json() }}}"""
"""{"type":"FeatureCollection",${bbox.jsonProp()}"features":${features.jsonJoin { it.json() }}${foreignMembersJsonProps()}}"""

operator fun component1(): List<Feature> = features
operator fun component2(): BoundingBox? = bbox
operator fun component3(): Map<String, JsonElement> = foreignMembers

companion object {

@JvmStatic
public fun fromJson(json: String): FeatureCollection =
fromJson(Json.decodeFromString(JsonObject.serializer(), json))
Expand All @@ -75,8 +91,9 @@ class FeatureCollection(

val bbox = json["bbox"]?.jsonArray?.toBbox()
val features = json.getValue("features").jsonArray.map { Feature.fromJson(it.jsonObject) }
val foreignMembers = json.foreignMembers()

return FeatureCollection(features, bbox)
return FeatureCollection(features, bbox, foreignMembers)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package io.github.dellisd.spatialk.geojson

import kotlinx.serialization.json.JsonElement

/**
* A GeoJSON object represents a [Geometry], [Feature], or [collection of Features][FeatureCollection].
*
* @property bbox An optional bounding box used to represent the limits of the object's geometry.
*/
interface GeoJson {
val bbox: BoundingBox?
val foreignMembers: MutableMap<String, JsonElement>
Copy link
Contributor

@elcolto elcolto May 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is always be defined by a backing field in the implementations, this property should be an immutable Map
Or is it desired to be modified after creation? Seems strange

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does not make much sense right now. I copied this MutableMap approach from the Feature.properties, but it makes more sense in there since it has mutation methods for it. Should I change foreignMembers to be immutable everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for public APIs I would recommend this 🙂


/**
* Gets a JSON representation of this object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,41 @@ package io.github.dellisd.spatialk.geojson
import io.github.dellisd.spatialk.geojson.serialization.GeometrySerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlin.jvm.JvmStatic

@Serializable(with = GeometrySerializer::class)
sealed class Geometry protected constructor() : GeoJson {
abstract override val bbox: BoundingBox?
sealed class Geometry protected constructor(
override val bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : GeoJson {

protected abstract val coordinatesOrGeometries: Any

private val _foreignMembers: MutableMap<String, JsonElement> = foreignMembers.toMutableMap()
final override val foreignMembers: MutableMap<String, JsonElement> get() = _foreignMembers

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Geometry

if (coordinatesOrGeometries != other.coordinatesOrGeometries) return false
if (bbox != other.bbox) return false
if (_foreignMembers != other._foreignMembers) return false

return true
}

override fun hashCode(): Int {
var result = coordinatesOrGeometries.hashCode()
result = 31 * result + (bbox?.hashCode() ?: 0)
result = 31 * result + _foreignMembers.hashCode()
return result
}

override fun toString(): String = json()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.github.dellisd.spatialk.geojson

import io.github.dellisd.spatialk.geojson.serialization.GeometrySerializer
import io.github.dellisd.spatialk.geojson.serialization.foreignMembers
import io.github.dellisd.spatialk.geojson.serialization.foreignMembersJsonProps
import io.github.dellisd.spatialk.geojson.serialization.jsonJoin
import io.github.dellisd.spatialk.geojson.serialization.jsonProp
import io.github.dellisd.spatialk.geojson.serialization.toBbox
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
Expand All @@ -17,31 +20,21 @@ import kotlin.jvm.JvmStatic
@Serializable(with = GeometrySerializer::class)
class GeometryCollection @JvmOverloads constructor(
val geometries: List<Geometry>,
override val bbox: BoundingBox? = null
) : Geometry(), Collection<Geometry> by geometries {
@JvmOverloads
constructor(vararg geometries: Geometry, bbox: BoundingBox? = null) : this(geometries.toList(), bbox)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as GeometryCollection

if (geometries != other.geometries) return false
if (bbox != other.bbox) return false
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : Geometry(bbox, foreignMembers), Collection<Geometry> by geometries {
override val coordinatesOrGeometries get() = geometries

return true
}

override fun hashCode(): Int {
var result = geometries.hashCode()
result = 31 * result + (bbox?.hashCode() ?: 0)
return result
}
@JvmOverloads
constructor(
vararg geometries: Geometry,
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : this(geometries.toList(), bbox, foreignMembers)

@Suppress("MaxLineLength")
override fun json(): String =
"""{"type":"GeometryCollection",${bbox.jsonProp()}"geometries":${geometries.jsonJoin { it.json() }}}"""
"""{"type":"GeometryCollection",${bbox.jsonProp()}"geometries":${geometries.jsonJoin { it.json() }}${foreignMembersJsonProps()}}"""

companion object {
@JvmStatic
Expand All @@ -66,8 +59,9 @@ class GeometryCollection @JvmOverloads constructor(
}

val bbox = json["bbox"]?.jsonArray?.toBbox()
val foreignMembers = json.foreignMembers()

return GeometryCollection(geometries, bbox)
return GeometryCollection(geometries, bbox, foreignMembers)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package io.github.dellisd.spatialk.geojson

import io.github.dellisd.spatialk.geojson.serialization.GeometrySerializer
import io.github.dellisd.spatialk.geojson.serialization.foreignMembers
import io.github.dellisd.spatialk.geojson.serialization.foreignMembersJsonProps
import io.github.dellisd.spatialk.geojson.serialization.jsonJoin
import io.github.dellisd.spatialk.geojson.serialization.jsonProp
import io.github.dellisd.spatialk.geojson.serialization.toBbox
import io.github.dellisd.spatialk.geojson.serialization.toPosition
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
Expand All @@ -17,41 +20,32 @@ import kotlin.jvm.JvmStatic
@Serializable(with = GeometrySerializer::class)
class LineString @JvmOverloads constructor(
val coordinates: List<Position>,
override val bbox: BoundingBox? = null
) : Geometry() {
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : Geometry(bbox, foreignMembers) {
override val coordinatesOrGeometries get() = coordinates

@JvmOverloads
constructor(vararg coordinates: Position, bbox: BoundingBox? = null) : this(coordinates.toList(), bbox)
constructor(
vararg coordinates: Position,
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : this(coordinates.toList(), bbox, foreignMembers)

@JvmOverloads
constructor(
coordinates: Array<DoubleArray>,
bbox: BoundingBox? = null
) : this(coordinates.map(::Position), bbox)
bbox: BoundingBox? = null,
foreignMembers: Map<String, JsonElement> = emptyMap()
) : this(coordinates.map(::Position), bbox, foreignMembers)

init {
require(coordinates.size >= 2) { "LineString must have at least two positions" }
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as LineString

if (coordinates != other.coordinates) return false
if (bbox != other.bbox) return false

return true
}

override fun hashCode(): Int {
var result = coordinates.hashCode()
result = 31 * result + (bbox?.hashCode() ?: 0)
return result
}

@Suppress("MaxLineLength")
override fun json(): String =
"""{"type":"LineString",${bbox.jsonProp()}"coordinates":${coordinates.jsonJoin(transform = Position::json)}}"""
"""{"type":"LineString",${bbox.jsonProp()}"coordinates":${coordinates.jsonJoin(transform = Position::json)}${foreignMembersJsonProps()}}"""

companion object {
@JvmStatic
Expand All @@ -72,8 +66,9 @@ class LineString @JvmOverloads constructor(

val coords = json.getValue("coordinates").jsonArray.map { it.jsonArray.toPosition() }
val bbox = json["bbox"]?.jsonArray?.toBbox()
val foreignMembers = json.foreignMembers()

return LineString(coords, bbox)
return LineString(coords, bbox, foreignMembers)
}
}
}
Loading