All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.jetbrains.letsPlot.gis.geoprotocol.Boundary.kt Maven / Gradle / Ivy

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())
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy