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

commonMain.org.jetbrains.letsPlot.gis.tileprotocol.TileService.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.tileprotocol

import org.jetbrains.letsPlot.commons.intern.async.Async
import org.jetbrains.letsPlot.commons.intern.async.ThreadSafeAsync
import org.jetbrains.letsPlot.commons.intern.concurrent.Lock
import org.jetbrains.letsPlot.commons.intern.concurrent.execute
import org.jetbrains.letsPlot.commons.intern.json.JsonSupport
import org.jetbrains.letsPlot.commons.intern.json.JsonSupport.formatJson
import org.jetbrains.letsPlot.commons.intern.spatial.LonLat
import org.jetbrains.letsPlot.commons.intern.typedGeometry.Rect
import org.jetbrains.letsPlot.gis.tileprotocol.Request.ConfigureConnectionRequest
import org.jetbrains.letsPlot.gis.tileprotocol.Request.GetBinaryGeometryRequest
import org.jetbrains.letsPlot.gis.tileprotocol.TileService.SocketStatus.*
import org.jetbrains.letsPlot.gis.tileprotocol.binary.ResponseTileDecoder
import org.jetbrains.letsPlot.gis.tileprotocol.json.MapStyleJsonParser
import org.jetbrains.letsPlot.gis.tileprotocol.json.RequestFormatter
import org.jetbrains.letsPlot.gis.tileprotocol.mapConfig.MapConfig
import org.jetbrains.letsPlot.gis.tileprotocol.socket.SafeSocketHandler
import org.jetbrains.letsPlot.gis.tileprotocol.socket.SocketBuilder
import org.jetbrains.letsPlot.gis.tileprotocol.socket.SocketHandler


open class TileService(socketBuilder: SocketBuilder, private val myTheme: Theme) {
    enum class Theme {
        COLOR,
        LIGHT,
        DARK,
        BW
    }

    private val mySocket = socketBuilder.build(SafeSocketHandler(TileSocketHandler()))
    private val myMessageQueue = ThreadSafeMessageQueue()
    private val pendingRequests = RequestMap()
    var mapConfig: MapConfig? = null
        private set
    private var myIncrement: Int = 0
    private var myStatus = NOT_CONNECTED

    open fun getTileData(bbox: Rect, zoom: Int): Async> {
        val key = myIncrement++.toString()
        val async = ThreadSafeAsync>()

        pendingRequests.put(key, async)

        try {
            GetBinaryGeometryRequest(key, zoom, bbox)
                .run(RequestFormatter::format)
                .run(JsonSupport::formatJson)
                .run(this::sendGeometryRequest)

        } catch (err: Throwable) {
            pendingRequests.poll(key).failure(err)
        }

        return async
    }

    private fun sendGeometryRequest(message: String) {
        when (myStatus) {
            NOT_CONNECTED -> {
                myMessageQueue.add(message)
                myStatus = CONNECTING
                mySocket.connect()
            }

            CONFIGURED -> mySocket.send(message)
            CONNECTING -> myMessageQueue.add(message)
            ERROR -> throw IllegalStateException("Socket error")
        }
    }

    private fun sendInitMessage() {
        ConfigureConnectionRequest(myTheme.name.lowercase())
            .run(RequestFormatter::format)
            .run(::formatJson)
            .run(mySocket::send)
    }

    private enum class SocketStatus {
        NOT_CONNECTED,
        CONFIGURED,
        CONNECTING,
        ERROR
    }

    inner class TileSocketHandler : SocketHandler {
        override fun onOpen() {
            sendInitMessage()
        }

        override fun onClose(message: String) {
            myMessageQueue.add(message)
            if (myStatus == CONFIGURED) {
                myStatus = CONNECTING
                mySocket.connect()
            }
        }

        override fun onError(cause: Throwable) {
            myStatus = ERROR; failPending(cause)
        }

        override fun onTextMessage(message: String) {
            if (mapConfig == null) {
                mapConfig = MapStyleJsonParser.parse(JsonSupport.parseJson(message))
            }
            myStatus = CONFIGURED
            myMessageQueue.run { forEach(mySocket::send); clear() }
        }

        override fun onBinaryMessage(message: ByteArray) {
            try {
                ResponseTileDecoder(message)
                    .let { (key, tiles) -> pendingRequests.poll(key).success(tiles) }
            } catch (e: Throwable) {
                failPending(e)
            }
        }

        private fun failPending(cause: Throwable) {
            pendingRequests.pollAll().values.forEach { it.failure(cause) }
        }
    }

    class RequestMap {
        private val lock = Lock()
        private val myAsyncMap = HashMap>>()

        fun put(key: String, async: ThreadSafeAsync>) = lock.execute {
            myAsyncMap[key] = async
        }

        fun pollAll(): Map>> = lock.execute {
            return HashMap(myAsyncMap).also { myAsyncMap.clear() }
        }

        fun poll(key: String): ThreadSafeAsync> = lock.execute {
            return myAsyncMap.remove(key)!!
        }
    }

    class ThreadSafeMessageQueue {
        private val myList = ArrayList()
        private val myLock = Lock()

        fun add(v: T) {
            myLock.execute {
                myList.add(v)
            }
        }

        fun forEach(f: (T) -> Unit) {
            myLock.execute {
                myList.forEach(f)
            }
        }

        fun clear() {
            myLock.execute {
                myList.clear()
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy