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

jvmCommonMain.earth.worldwind.ogc.GpkgContentManager.kt Maven / Gradle / Ivy

Go to download

The WorldWind Kotlin SDK (WWK) includes the library, examples and tutorials for building multiplatform 3D virtual globe applications for Android, Web and Java.

The newest version!
package earth.worldwind.ogc

import earth.worldwind.globe.elevation.coverage.CacheableElevationCoverage
import earth.worldwind.globe.elevation.coverage.TiledElevationCoverage
import earth.worldwind.globe.elevation.coverage.WebElevationCoverage
import earth.worldwind.layer.CacheableImageLayer
import earth.worldwind.layer.TiledImageLayer
import earth.worldwind.layer.WebImageLayer
import earth.worldwind.layer.mercator.MercatorTiledSurfaceImage
import earth.worldwind.layer.mercator.WebMercatorImageLayer
import earth.worldwind.layer.mercator.WebMercatorLayerFactory
import earth.worldwind.ogc.gpkg.GeoPackage
import earth.worldwind.ogc.gpkg.GeoPackage.Companion.COVERAGE
import earth.worldwind.ogc.gpkg.GeoPackage.Companion.EPSG_3857
import earth.worldwind.ogc.gpkg.GeoPackage.Companion.TILES
import earth.worldwind.shape.TiledSurfaceImage
import earth.worldwind.util.CacheTileFactory
import earth.worldwind.util.ContentManager
import earth.worldwind.util.LevelSet
import earth.worldwind.util.Logger
import kotlinx.datetime.Instant
import java.io.File

class GpkgContentManager(val pathName: String, val isReadOnly: Boolean = false): ContentManager {
    private val geoPackage by lazy { GeoPackage(pathName, isReadOnly) }

    /**
     * Returns database connection state. If true, then the Content Manager cannot be used anymore.
     */
    val isShutdown get() = geoPackage.isShutdown

    /**
     * Shutdown GPKG database connection forever for this Content Manager instance.
     */
    fun shutdown() = geoPackage.shutdown()

    override suspend fun contentSize() = File(pathName).length()

    override suspend fun lastModifiedDate() = Instant.fromEpochMilliseconds(File(pathName).lastModified())

    override suspend fun getImageLayersCount() = geoPackage.countContent(TILES).toInt()

    override suspend fun getImageLayers(contentKeys: List?) =
        geoPackage.getContent(TILES, contentKeys).mapNotNull { content ->
            // Try to build the level set. It may fail due to unsupported projection or other requirements.
            runCatching { geoPackage.buildLevelSetConfig(content) }.onFailure {
                Logger.logMessage(Logger.WARN, "GpkgContentManager", "getImageLayers", it.message!!)
            }.getOrNull()?.let { config ->
                // Check if WEB service config available and try to create a Web Layer
                geoPackage.getWebService(content)?.let { service ->
                    runCatching {
                        when (service.type) {
                            WmsImageLayer.SERVICE_TYPE -> WmsLayerFactory.createLayer(
                                service.address, service.layerName?.split(",") ?: error("Layer not specified"),
                                service.metadata, content.identifier
                            )

                            WmtsImageLayer.SERVICE_TYPE -> WmtsLayerFactory.createLayer(
                                service.address, service.layerName ?: error("Layer not specified"),
                                service.metadata, content.identifier
                            )

                            WebMercatorImageLayer.SERVICE_TYPE -> WebMercatorLayerFactory.createLayer(
                                service.address, service.outputFormat, service.isTransparent,
                                content.identifier, config.numLevels, config.tileHeight, config.levelOffset
                            )

                            else -> null // It is not a known Web Layer type
                        }?.apply {
                            // Apply bounding sector from content
                            tiledSurfaceImage?.levelSet?.sector?.copy(config.sector)
                            // Configure cache for Web Layer
                            tiledSurfaceImage?.cacheTileFactory = GpkgTileFactory(geoPackage, content, service.outputFormat)
                        }
                    }.onFailure {
                        Logger.logMessage(Logger.WARN, "GpkgContentManager", "getImageLayers", it.message!!)
                    }.getOrNull()
                } ?: TiledImageLayer(
                    content.identifier, if (content.srs?.id == EPSG_3857) {
                        MercatorTiledSurfaceImage(GpkgTileFactory(geoPackage, content), LevelSet(config))
                    } else {
                        TiledSurfaceImage(GpkgTileFactory(geoPackage, content), LevelSet(config))
                    }
                ).apply {
                    // Set cache factory to be able to use cacheale layer interface
                    tiledSurfaceImage?.cacheTileFactory = tiledSurfaceImage?.tileFactory as? CacheTileFactory
                }
            }
        }

    override suspend fun setupImageLayerCache(
        layer: CacheableImageLayer, contentKey: String, setupWebLayer: Boolean
    ) {
        val tiledSurfaceImage = layer.tiledSurfaceImage ?: error("Surface image not defined")
        val levelSet = tiledSurfaceImage.levelSet
        val imageFormat = (layer as? WebImageLayer)?.imageFormat ?: "image/png"
        val content = geoPackage.getContent(contentKey)?.also { content ->
            // Check if the current layer fits cache content
            val config = geoPackage.buildLevelSetConfig(content)
            require(config.tileOrigin.equals(levelSet.tileOrigin, TOLERANCE)) { "Invalid tile origin" }
            require(config.firstLevelDelta.equals(levelSet.firstLevelDelta, TOLERANCE)) { "Invalid first level delta" }
            require(config.tileWidth == levelSet.tileWidth && config.tileHeight == levelSet.tileHeight) { "Invalid tile size" }
            require(content.tileMatrices?.map { it.zoomLevel }?.sorted()?.get(0) == 0) { "Invalid level offset" }
            if (imageFormat.equals("image/webp", true)) requireNotNull(geoPackage.getExtension(
                tableName = contentKey, columnName = "tile_data", extensionName = "gpkg_webp"
            )) { "WEBP extension missed" }
            // Check and update web service config
            if (layer is WebImageLayer) {
                val webService = geoPackage.getWebService(content)
                val serviceType = webService?.type
                require(serviceType == null || serviceType == layer.serviceType) { "Invalid service type" }
                val outputFormat = webService?.outputFormat
                require(outputFormat == null || outputFormat == layer.imageFormat) { "Invalid image format" }
                if (setupWebLayer && !geoPackage.isReadOnly) geoPackage.setupWebLayer(layer, content)
            }
            // Verify if all required tile matrices created
            if (!geoPackage.isReadOnly && config.numLevels < levelSet.numLevels) {
                geoPackage.setupTileMatrices(content, levelSet)
            }
            // Update content metadata
            geoPackage.updateTilesContent(layer, contentKey, levelSet, content)
        } ?: geoPackage.setupTilesContent(layer, contentKey, levelSet, setupWebLayer)

        layer.tiledSurfaceImage?.cacheTileFactory = GpkgTileFactory(geoPackage, content, imageFormat)
    }

    override suspend fun getElevationCoveragesCount() = geoPackage.countContent(COVERAGE).toInt()

    override suspend fun getElevationCoverages(contentKeys: List?) =
        geoPackage.getContent(COVERAGE, contentKeys).mapNotNull { content ->
            runCatching {
                val metadata = geoPackage.getGriddedCoverage(content)
                requireNotNull(metadata) { "Missing gridded coverage metadata for '${content.tableName}'" }
                val matrixSet = geoPackage.buildTileMatrixSet(content)
                val factory = GpkgElevationSourceFactory(geoPackage, content, metadata.datatype == "float")
                val service = geoPackage.getWebService(content)
                when (service?.type) {
                    Wcs100ElevationCoverage.SERVICE_TYPE -> Wcs100ElevationCoverage(
                        service.address, service.layerName ?: error("Coverage not specified"),
                        service.outputFormat, matrixSet.sector, matrixSet.maxResolution
                    ).apply { cacheSourceFactory = factory }

                    Wcs201ElevationCoverage.SERVICE_TYPE -> Wcs201ElevationCoverage(
                        service.address, service.layerName ?: error("Coverage not specified"),
                        service.outputFormat, service.metadata
                    ).apply { cacheSourceFactory = factory }

                    WmsElevationCoverage.SERVICE_TYPE -> WmsElevationCoverage(
                        service.address, service.layerName ?: error("Coverage not specified"),
                        service.outputFormat, matrixSet.sector, matrixSet.maxResolution
                    ).apply { cacheSourceFactory = factory }

                    else -> TiledElevationCoverage(matrixSet, factory).apply {
                        // Configure cache to be able to use cacheable coverage interface
                        cacheSourceFactory = factory
                    }
                }.apply {
                    displayName = content.identifier
                    // Apply bounding sector from content
                    geoPackage.getBoundingSector(content)?.let { sector.copy(it) }
                }
            }.onFailure {
                Logger.logMessage(Logger.WARN, "GpkgContentManager", "getElevationCoverages", it.message!!)
            }.getOrNull()
        }

    override suspend fun setupElevationCoverageCache(
        coverage: CacheableElevationCoverage, contentKey: String, setupWebCoverage: Boolean, isFloat: Boolean
    ) {
        val content = geoPackage.getContent(contentKey)?.also { content ->
            // Check if the current layer fits cache content
            val matrixSet = geoPackage.buildTileMatrixSet(content)
            require(matrixSet.sector.equals(coverage.tileMatrixSet.sector, TOLERANCE)) { "Invalid sector" }
            requireNotNull(geoPackage.getGriddedCoverage(content)?.datatype == if (isFloat) "float" else "integer") { "Invalid data type" }
            // Check and update web service config
            if (coverage is WebElevationCoverage) {
                val serviceType = geoPackage.getWebService(content)?.type
                require(serviceType == null || serviceType == coverage.serviceType) { "Invalid service type" }
                if (setupWebCoverage && !geoPackage.isReadOnly) geoPackage.setupWebCoverage(coverage, content)
            }
            // Verify if all required tile matrices created
            if (!geoPackage.isReadOnly && matrixSet.entries.size < coverage.tileMatrixSet.entries.size) {
                geoPackage.setupTileMatrices(content, coverage.tileMatrixSet)
            }
            // Update content metadata
            geoPackage.updateGriddedCoverageContent(coverage, contentKey, content)
        } ?: geoPackage.setupGriddedCoverageContent(coverage, contentKey, setupWebCoverage, isFloat)

        coverage.cacheSourceFactory = GpkgElevationSourceFactory(geoPackage, content, isFloat)
    }

    override suspend fun deleteContent(contentKey: String) = geoPackage.deleteContent(contentKey)

    companion object {
        private const val TOLERANCE = 1e-6
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy