com.rohittp.rentile:kmp

KMP integration guide

This guide takes you from an empty dependency declaration to PNG tile bytes. Along the way, it explains the two small adapters your app provides and the responsibilities Rentile deliberately leaves with you.

Kotlin Multiplatform Android · iOS · Linux Apache-2.0
Rentile is pre-release. The public Maven artifact is not available yet. These examples match the API on main, where the lifecycle, raster path, strict MVT decoding, vector overzoom, and the first fill/line slice are implemented. See current renderer coverage before evaluating a real style.

Step 1

Add the dependency

Rentile has one coordinate for common code. Your project also needs JetBrains' public Compose repository for the Skiko platform artifacts used by the renderer. The content filter keeps that repository limited to Skiko.

settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") {
            content { includeGroup("org.jetbrains.skiko") }
        }
    }
}
build.gradle.ktsshared module
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.rohittp.rentile:kmp:<version>")
        }
    }
}
Replace <version> with a published version. Until the first artifact is available, use the source repository to evaluate the API rather than adding a snapshot coordinate to an application build.

Step 2

Give Rentile transport and storage

Rentile does not force an HTTP client or database on your app. You provide a ResourceTransport for fetching bytes and a RawResourceStore for storing complete, validated responses. Both interfaces live in common Kotlin.

Create the renderer
val configuration = RentileConfiguration(
    transport = AppResourceTransport(httpClient),
    rawResourceStore = AppRawResourceStore(cacheDirectory),
)

val rasterizer: BasemapRasterizer =
    Rentile.create(configuration)
AppResourceTransport and AppRawResourceStore are example names for adapters in your project. Rentile does not provide those classes.

Optional configuration

The defaults are enough to start. Add a CredentialProvider or MapSessionProvider when your map provider needs them. You can also tune concurrency and safety limits, or attach redacted diagnostic and metrics sinks.

OptionUse it for
credentialProviderCredentials for exact HTTPS origins when the style does not already contain them
sessionProviderShort-lived provider session values
executionPolicyBounds for exchanges, decoding, memory, and render workers
resourceLimitsHard ceilings for untrusted encoded and decoded resources
diagnosticSinkStructured, sanitized renderer diagnostics
metricsSinkRequest, cache, decode, render, and output counters

Step 3

Prepare a style, then render a batch

Preparation is deliberately split in two. First Rentile validates and compiles the style. Then it gathers everything required for a caller-defined tile batch. Once a batch is prepared, rendering performs no network or store access.

Common Kotlinsuspending API
val style = rasterizer.prepare(
    StyleInput.Remote("https://example.com/style.json")
)

val requestedTiles = listOf(
    TileId(z = 12, x = 2203, y = 1345),
    TileId(z = 12, x = 2204, y = 1345),
)

rasterizer.prepareBatch(style, requestedTiles).use { batch ->
    // Content keys are available before drawing.
    batch.contentKeys.forEach { (tile, key) ->
        println("$tile will render as $key")
    }

    val result: RenderBatch = rasterizer.render(batch)
    result.tiles.forEach { tile ->
        savePng(tile.id, tile.pngBytes)
    }
}
1

prepare

Checks one inline, remote, or prefetched style and returns a reusable PreparedStyle.

2

prepareBatch

Resolves raw resources and exposes stable content keys for the requested tiles.

3

render

Draws all or a selected subset of the prepared tiles and returns PNG bytes.

Choose a style input

InputWhen to use it
StyleInput.Remote(url)Let Rentile acquire the style through your transport adapter
StyleInput.InlineJson(json, baseUri)You already have JSON text; use baseUri for relative references
StyleInput.Prefetched(bytes, identity, baseUri)You already have bytes and a canonical, credential-free identity

Output contract

A successful tile contains its TileId, encoded pngBytes, the same contentKey exposed before drawing, and any sanitized diagnostics. A render call is all-or-error: Rentile does not return a partial batch.

Host boundary

Keep your existing network stack

ResourceTransport is one bounded request/response exchange. Map its request onto Ktor, OkHttp, URLSession, Curl, or another client already used by your host. Return the decompressed body and the allowlisted response metadata.

ResourceTransport
fun interface ResourceTransport {
    suspend fun execute(
        request: TransportRequest
    ): TransportResponse
}

Requests include the URL, resource class, response-size limit, and a small set of cache-validation headers. Arbitrary headers do not cross the public boundary.

Credentials

If a style URL already contains provider credentials, Rentile uses them. Otherwise, it asks your CredentialProvider only for the exact HTTPS origin and query parameter it needs. Credentials and session values are kept out of logs, content keys, cache paths, exception text, and diagnostics.

Rentile removes credentials when a redirect changes origin. Your transport adapter should also avoid logging raw request URLs or headers.

Caching

Two caches, two owners

Rentile stores reusable source material. Your app stores finished PNGs. Keeping those concerns separate lets your product choose the output lifetime and eviction policy that make sense for its workflows.

Rentile owns

  • Complete encoded raw resources
  • Compiled style and decoded-source memory
  • In-process single-flight resource work
  • Validation before a cache write is committed

Your app owns

  • Rendered PNG storage and eviction
  • Cross-process output coordination
  • Priority, prefetching, and batch size
  • Fallback output namespaces

Use PreparedBatch.contentKeys to look up rendered outputs before you call render. A failed tile does not undo valid raw-resource entries acquired for other work, and incomplete entries are never committed.

Failures

Handle facts, not exception messages

Every RentileException has a stable machine-readable code, a pipeline stage, sanitized diagnostics, and affected tile identities. Human-readable messages explain the problem but are not a stable API.

Exception familyWhat it tells you
StylePreparationExceptionThe style is malformed or reaches a construct this profile cannot render
ResourceAcquisitionExceptionA resource could not be fetched; status and retry delay are included when available
ResourceDecodeExceptionFetched bytes could not be decoded safely
RasterizationExceptionDrawing failed for one or more affected tiles
PngEncodingExceptionThe rendered pixels could not be encoded as PNG
SafetyLimitExceptionAn encoded size, decoded size, dimension, or redirect ceiling was exceeded
Lifecycle exceptionsA renderer or batch is closed, foreign, or does not contain the requested tile

Rentile does not retry or fall back on its own. Use the typed fields to decide whether your app should retry, refetch, choose another renderer, or stop. CancellationException is propagated unchanged.

Errors from your transport and store adapters may contain signed URLs, headers, or file paths. Rentile intentionally does not copy their messages or causes into its public exceptions. Record redacted adapter telemetry inside the adapter itself.

Lifecycle

Close quickly, await when cleanup matters

BasemapRasterizer and PreparedBatch are common Kotlin AutoCloseables. Calling close() is idempotent, non-blocking, and non-throwing. It prevents new work and starts cancellation of owned jobs.

Renderer ownership
val rasterizer = Rentile.create(configuration)

try {
    renderNeededTiles(rasterizer)
} finally {
    rasterizer.close()
    rasterizer.awaitClosed()
}

awaitClosed() suspends until workers, native objects, leases, and secret state are released. Use it when teardown must be observed—for example, before a test ends or a long-lived service replaces its renderer.

Renderer coverage

Check your style before committing

Rentile follows a versioned rentile-v1 compatibility profile validated against an owner-controlled rolling style corpus. It does not promise support for every current or future MapLibre Style Specification feature. A retained unsupported construct fails during style preparation instead of producing a quietly incomplete map.

Implemented today

  • Style version 8 validation
  • Strict modern expressions, legacy functions, and legacy/modern filters required by the profile
  • Zoom- and feature-evaluated backgrounds, fills, and lines
  • Inline and TileJSON XYZ/TMS raster sources
  • Raster paint controls and strict pixel-equivalent PNG pass-through
  • Strict MVT decoding with bounded geometry and tag validation
  • Inline and TileJSON vector sources with source bounds
  • Vector overzoom through z22 with output-zoom style evaluation
  • Profile fill/line paint, sprite patterns, dashes, gaps, offsets, blur, and translation
  • Remote GeoJSON LineString sources through the common line renderer
  • Raster DEM hillshade with bounded neighbor acquisition
  • Flattened top-down extrusion footprints
  • Independent RGBA/SDF point and repeated-line icons without glyph requests
  • 256 px and 512 px PNG outputs
  • Inline, remote, and prefetched style inputs

Not implemented yet

  • Text, glyphs, shaping, and text-coupled icons
  • Perspective extrusion and root terrain presentation
  • Arbitrary future Style Specification behavior outside rentile-v1
  • Globe, fog, sky, and interactive camera state
The compatibility profile is intentionally strict. If preparation fails, inspect the structured diagnostics to identify the layer and construct that needs support.

Targets

Supported platforms

PlatformKMP targetExecution model
Android arm64androidTargetBackground work; no View or Looper contract
Android x86_64androidTargetBackground work; emulator-compatible native runtime
iPhone and iPadiosArm64Background work; no UIKit view
Apple Silicon SimulatoriosSimulatorArm64Background work
Linux x64linuxX64Headless native process
Linux arm64linuxArm64Headless native process