Fly Between Different Locations - Kotlin SDK
This example leverages the mobile SDKs to animate transitions between major U.S. locations and visualize school distribution using a single, high-performance cluster layer. The FlyTo feature ensures smooth, clear movement between locations.
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.maptiler.maptilersdk.MTConfig
import com.maptiler.maptilersdk.events.MTEvent
import com.maptiler.maptilersdk.map.LngLat
import com.maptiler.maptilersdk.map.MTMapOptions
import com.maptiler.maptilersdk.map.MTMapView
import com.maptiler.maptilersdk.map.MTMapViewController
import com.maptiler.maptilersdk.map.MTMapViewDelegate
import com.maptiler.maptilersdk.map.options.MTCameraOptions
import com.maptiler.maptilersdk.map.options.MTFlyToOptions
import com.maptiler.maptilersdk.map.style.MTMapReferenceStyle
import com.maptiler.maptilersdk.map.style.MTMapStyleVariant
import com.maptiler.maptilersdk.map.style.MTStyle
import com.maptiler.maptilersdk.map.style.dsl.Expression
import com.maptiler.maptilersdk.map.style.dsl.Filter
import com.maptiler.maptilersdk.map.style.dsl.MTFeatureKey
import com.maptiler.maptilersdk.map.style.dsl.PropertyValue
import com.maptiler.maptilersdk.map.style.layer.circle.MTCircleLayer
import com.maptiler.maptilersdk.map.style.layer.circle.colorExpr
import com.maptiler.maptilersdk.map.style.layer.circle.radiusExpr
import com.maptiler.maptilersdk.map.style.source.MTGeoJSONSource
import com.maptiler.maptilersdk.map.types.MTData
import java.net.URL
class FlyToAnimationsComposeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Replace with your MapTiler API key
MTConfig.apiKey = "MY_API_KEY"
setContent {
FlyToAnimationsComposeMap()
}
}
}
@Composable
fun FlyToAnimationsComposeMap() {
val controller = remember { MTMapViewController(baseContext) }
LaunchedEffect(controller) {
controller.delegate =
object : MTMapViewDelegate {
override fun onMapViewInitialized() {
setupSchoolLayers(controller.style!!)
// Start simple camera animation after layers are added
startLocationAnimation(controller)
}
override fun onEventTriggered(event: MTEvent, data: MTData?) {
// no-op for this example
}
}
}
DisposableEffect(controller) { onDispose { controller.delegate = null } }
MTMapView(
referenceStyle = MTMapReferenceStyle.DATAVIZ,
options = MTMapOptions(),
controller = controller,
modifier = Modifier.fillMaxSize(),
styleVariant = MTMapStyleVariant.LIGHT,
)
}
private fun setupSchoolLayers(style: MTStyle) {
val src =
MTGeoJSONSource.fromUrl(
identifier = "schools",
url = URL("https://docs.maptiler.com/sdk-js/assets/Public_School_Characteristics_2020-21_no_prop.geojson"),
).apply {
isCluster = true
clusterRadius = 90.0
clusterMaxZoom = 12.0
}
style.addSource(src)
val clusters =
MTCircleLayer(identifier = "schools-clusters", sourceIdentifier = src.identifier)
.apply {
withFilter(
PropertyValue.array(
PropertyValue.Str("all"),
Filter.clusters(),
PropertyValue.array(
PropertyValue.Str(">="),
Expression.get(MTFeatureKey.POINT_COUNT),
PropertyValue.Num(100.0),
),
),
)
maxZoom = 10.5
radiusExpr(
Expression.step(
input = Expression.get(MTFeatureKey.POINT_COUNT),
default = PropertyValue.Num(16.0),
stops = listOf(
100.0 to PropertyValue.Num(22.0),
750.0 to PropertyValue.Num(30.0),
),
),
)
colorExpr(
Expression.step(
input = Expression.get(MTFeatureKey.POINT_COUNT),
default = PropertyValue.Color(Color.parseColor("#f1f075")), // yellow (low)
stops = listOf(
100.0 to PropertyValue.Color(Color.parseColor("#f59e0b")), // orange (mid)
750.0 to PropertyValue.Color(Color.parseColor("#ef4444")), // red (high)
),
),
)
}
style.addLayer(clusters)
style.setPaintProperty(
layerId = clusters.identifier,
name = "circle-opacity",
value =
PropertyValue.array(
PropertyValue.Str("interpolate"),
PropertyValue.array(PropertyValue.Str("linear")),
PropertyValue.array(PropertyValue.Str("zoom")),
PropertyValue.Num(3.0), PropertyValue.Num(0.85),
PropertyValue.Num(10.0), PropertyValue.Num(0.85),
PropertyValue.Num(10.5), PropertyValue.Num(0.0),
),
)
}
private fun startLocationAnimation(controller: MTMapViewController) {
data class Location(val zoom: Double, val center: LngLat)
val locations = listOf(
Location(11.37, LngLat(-73.9757, 40.7684)), // NYC
Location(11.82, LngLat(-71.07515, 42.37131)), // Boston
Location(11.48, LngLat(-87.6922, 41.8699)), // Chicago
Location(3.9, LngLat(-97.04, 38.17)), // USA overview
Location(10.62, LngLat(-122.3241, 47.6127)), // Seattle
Location(10.3, LngLat(-122.3634, 37.6689)), // SF Bay
Location(11.15, LngLat(-95.3843, 29.75)), // Houston
Location(3.9, LngLat(-97.04, 38.17)), // USA overview
)
val handler = Handler(Looper.getMainLooper())
var idx = 0
val flyOptions = MTFlyToOptions(curve = null, minZoom = null, speed = 0.2, screenSpeed = null, maxDuration = null)
val runnable = object : Runnable {
override fun run() {
val loc = locations[idx % locations.size]
controller.flyTo(
cameraOptions = MTCameraOptions(center = loc.center, zoom = loc.zoom),
flyToOptions = flyOptions,
)
idx++
handler.postDelayed(this, 5000L)
}
}
// Initial delay to ensure map is fully idle
handler.postDelayed(runnable, 2500L)
}
On this page