Display an ArcGIS scene layer from a URL.

Use case
Adding a scene layer from a URL allows you to author the scene layer elsewhere in the platform, such as with ArcGIS Pro or CityEngine, and then add that scene layer to a scene in ArcGIS Maps SDK for Kotlin. Loading a scene layer from a URL also permits the layer source to change dynamically without updating the code. Each scene layer added to a scene can assist in performing helpful visual analysis. For example, if presenting the results of a shadow analysis of a major metropolitan downtown area in 3D, adding a scene layer of 3D buildings to the scene that could be toggled on/off would help to better contextualize the source of the shadows.
How to use the sample
Pan and zoom to explore the scene.
How it works
- Create an
ArcGISSceneLayerby passing in the URL to a scene layer service. - Create an
ArcGISSceneand add the scene layer to its operational layers. - Create a
Surfaceand add anArcGISTiledElevationSourceto it. - Set the surface as the scene’s base surface.
- Set the scene’s initial viewpoint to center the scene view on the scene layer.
- Display the scene in a
SceneViewcomposable.
Relevant API
- ArcGISScene
- ArcGISSceneLayer
- ArcGISTiledElevationSource
- SceneView
- Surface
About the data
This sample shows a Portland, Oregon USA Scene hosted on ArcGIS Online.
Tags
3D, buildings, model, scene, service, URL
Sample Code
/* Copyright 2025 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.addscenelayerfromservice
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.addscenelayerfromservice.screens.AddSceneLayerFromServiceScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // authentication with an API key or named user is // required to access basemaps and other location services ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)
enableEdgeToEdge() setContent { SampleAppTheme { AddSceneLayerFromServiceApp() } } }
@Composable private fun AddSceneLayerFromServiceApp() { Surface(color = MaterialTheme.colorScheme.background) { AddSceneLayerFromServiceScreen( sampleName = getString(R.string.add_scene_layer_from_service_app_name) ) } }}/* Copyright 2025 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.addscenelayerfromservice.components
import android.app.Applicationimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.geometry.Pointimport com.arcgismaps.geometry.SpatialReferenceimport com.arcgismaps.mapping.ArcGISSceneimport com.arcgismaps.mapping.ArcGISTiledElevationSourceimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Surfaceimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.layers.ArcGISSceneLayerimport com.arcgismaps.mapping.view.Cameraimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.launch
class AddSceneLayerFromServiceViewModel(app: Application) : AndroidViewModel(app) { // URL of the Portland buildings scene server private val portlandBuildingsSceneLayerUrl = "https://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Portland/SceneServer"
// URL of the world elevation tiled elevation source private val worldElevationServiceUrl = "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
// ArcGISTiledElevationSource for world elevation private val elevationSource: ArcGISTiledElevationSource by lazy { ArcGISTiledElevationSource(worldElevationServiceUrl) }
// ArcGISSceneLayer for Portland buildings private val sceneLayer: ArcGISSceneLayer by lazy { ArcGISSceneLayer(portlandBuildingsSceneLayerUrl) }
// Camera location point (Portland, OR) private val cameraLocation: Point by lazy { Point( x = -122.66949, y = 45.51869, z = 227.0, spatialReference = SpatialReference.wgs84() ) }
// Camera to view the scene private val camera: Camera by lazy { Camera( locationPoint = cameraLocation, heading = 219.0, pitch = 82.0, roll = 0.0 ) }
// Create the ArcGISScene with imagery basemap val arcGISScene: ArcGISScene = ArcGISScene(BasemapStyle.ArcGISImagery).apply { // Add the Portland buildings scene layer operationalLayers.add(sceneLayer) // Add the elevation source to the base surface baseSurface = Surface().apply { elevationSources.add(elevationSource) } // Set the viewpoint camera at Portland initialViewpoint = Viewpoint( boundingGeometry = cameraLocation, camera = camera ) }
// Create a message dialog view model for handling error messages val messageDialogVM = MessageDialogViewModel()
init { viewModelScope.launch { arcGISScene.load().onFailure { messageDialogVM.showMessageDialog(it) } } }}/* Copyright 2025 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.esri.arcgismaps.sample.addscenelayerfromservice.screens
import androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.Scaffoldimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.geoviewcompose.SceneViewimport com.esri.arcgismaps.sample.addscenelayerfromservice.components.AddSceneLayerFromServiceViewModelimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
@Composablefun AddSceneLayerFromServiceScreen(sampleName: String) { val sceneViewModel: AddSceneLayerFromServiceViewModel = viewModel() Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { padding -> Column( modifier = Modifier .fillMaxSize() .padding(padding), ) { SceneView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISScene = sceneViewModel.arcGISScene ) }
sceneViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } )}