commonMain.org.jetbrains.letsPlot.gis.geoprotocol.Boundary.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gis Show documentation
Show all versions of gis Show documentation
A part of the Lets-Plot library.
The newest version!
/*
* Copyright (c) 2019. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/
package org.jetbrains.letsPlot.gis.geoprotocol
import org.jetbrains.letsPlot.commons.encoding.Base64
import org.jetbrains.letsPlot.commons.intern.spatial.GeoJson
import org.jetbrains.letsPlot.commons.intern.spatial.SimpleFeature
import org.jetbrains.letsPlot.commons.intern.typedGeometry.MultiPolygon
import org.jetbrains.letsPlot.commons.intern.typedGeometry.Polygon
import org.jetbrains.letsPlot.commons.intern.typedGeometry.Untyped
import org.jetbrains.letsPlot.gis.common.twkb.Twkb
interface Boundary {
fun asMultipolygon(): MultiPolygon
companion object {
fun create(points: MultiPolygon): Boundary {
return object : Boundary {
override fun asMultipolygon(): MultiPolygon {
return points
}
}
}
}
}
object Boundaries {
fun fromTwkb(boundary: String): Boundary = TinyBoundary(boundary)
fun fromGeoJson(boundary: String): Boundary = GeoJsonBoundary(boundary)
internal fun getRawData(boundary: Boundary): String {
return (boundary as StringBoundary).rawData
}
// Used internally by GIS server for optimization.
// Workflow:
// GIS server receives encoded geometries(TWKB+Base64 or GeoJson) from PostreSQL.
// GIS server doesn't use geometries, only forwards them to a client. So instead of decoding geometries
// to List>> and encoding it back to TWKB/GeoJson before sending to client we
// just keep encoded data with help of StringGeometry type.
// Only GeometryStorageClient(PostreSQL user) and JsonFormatters/JsonParsers(client/server communication)
// should know about this optimization.
private abstract class StringBoundary internal constructor(
internal val rawData: String
) : Boundary {
private val myMultipolygon: MultiPolygon by lazy { parse(rawData) }
override fun asMultipolygon(): MultiPolygon = myMultipolygon
internal abstract fun parse(boundary: String): MultiPolygon
override fun hashCode(): Int = rawData.hashCode()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as StringBoundary
if (rawData != other.rawData) return false
return true
}
}
private class TinyBoundary internal constructor(
boundary: String
) : StringBoundary(boundary) {
override fun parse(boundary: String): MultiPolygon {
val polygons = ArrayList>()
Twkb.parse(Base64.decode(boundary), object : SimpleFeature.GeometryConsumer {
override fun onPolygon(polygon: Polygon) {
polygons.add(polygon)
}
override fun onMultiPolygon(multipolygon: MultiPolygon) {
polygons.addAll(multipolygon)
}
}
)
return MultiPolygon(polygons)
}
}
private class GeoJsonBoundary internal constructor(
boundary: String
) : StringBoundary(boundary) {
override fun parse(boundary: String): MultiPolygon {
var boundaryPolygon: MultiPolygon? = null
GeoJson.parse(boundary) {
onPolygon = { require(boundaryPolygon == null); boundaryPolygon = MultiPolygon(listOf(it)) }
onMultiPolygon = { require(boundaryPolygon == null); boundaryPolygon = it }
}
return boundaryPolygon ?: MultiPolygon(emptyList())
}
}
}