Extrude graphics based on an attribute value.

Use case
Graphics representing features can be vertically extruded to represent properties of the data that might otherwise go unseen. Extrusion can add visual prominence to data beyond what may be offered by varying the color, size, or shape of symbol alone. For example, graphics representing wind turbines in a wind farm application can be extruded by a real-world “height” attribute so that they can be visualized in a landscape. Likewise, census data can be extruded by a thematic “population” attribute to visually convey population levels across a country.
How to use the sample
Run the sample. Note that the graphics are extruded to the level set in their height property.
How it works
- Create a
GraphicsOverlayandSimpleRenderer. - Set the extrusion mode for the renderer with
RendererSceneProperties.extrusionMode = ExtrusionMode.BaseHeight. - Specify the attribute name of the graphic that the extrusion mode will use,
RendererSceneProperties.extrusionExpression = "[height]". - Set the renderer on the graphics overlay using,
GraphicsOverlay.renderer. - Create graphics with their attribute set,
graphic.attributes["height"].
Relevant API
- ExtrusionMode
- RendererSceneProperties
- SimpleRenderer
About the data
Data is hard coded in this sample as a demonstration of how to create and set an attribute to a graphic. To extrude graphics based on pre-existing attributes (e.g. from a feature layer) see the FeatureLayerExtrusion sample.
Tags
3D, extrude, extrusion, height, scene, visualization
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.showextrudedgraphics
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport 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.showextrudedgraphics.screens.ShowExtrudedGraphicsScreen
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)
setContent { SampleAppTheme { ShowExtrudedGraphicsApp() } } }
@Composable private fun ShowExtrudedGraphicsApp() { Surface(color = MaterialTheme.colorScheme.background) { ShowExtrudedGraphicsScreen( sampleName = getString(R.string.show_extruded_graphics_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.showextrudedgraphics.components
import android.app.Applicationimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.Colorimport com.arcgismaps.geometry.Pointimport com.arcgismaps.geometry.PolygonBuilderimport com.arcgismaps.mapping.ArcGISSceneimport com.arcgismaps.mapping.ArcGISTiledElevationSourceimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.symbology.ExtrusionModeimport com.arcgismaps.mapping.view.GraphicsOverlayimport com.arcgismaps.mapping.view.SurfacePlacementimport com.arcgismaps.mapping.view.Graphicimport com.arcgismaps.mapping.symbology.SimpleFillSymbolimport com.arcgismaps.mapping.symbology.SimpleFillSymbolStyleimport com.arcgismaps.mapping.symbology.SimpleLineSymbolimport com.arcgismaps.mapping.symbology.SimpleLineSymbolStyleimport com.arcgismaps.mapping.symbology.SimpleRendererimport com.arcgismaps.mapping.symbology.RendererScenePropertiesimport com.arcgismaps.mapping.view.Cameraimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.launchimport kotlin.random.Random
/** * ViewModel for the ShowExtrudedGraphics sample. * * Prepares a 3D scene, adds an elevation source and a graphics overlay whose * renderer is configured to extrude graphics based on the "height" attribute. */class ShowExtrudedGraphicsViewModel(application: Application) : AndroidViewModel(application) {
// Create a camera and set the scene's initial viewpoint. private val camera = Camera( latitude = 28.4, longitude = 83.0, altitude = 20_000.0, heading = 10.0, pitch = 70.0, roll = 0.0 ) // A 3D scene with a topographic basemap and an initial camera viewpoint. val arcGISScene = ArcGISScene(BasemapStyle.ArcGISTopographic).apply {
initialViewpoint = Viewpoint(camera = camera, boundingGeometry = camera.location)
// Add a global elevation source to the base surface so extruded geometry has context. baseSurface.elevationSources.add( ArcGISTiledElevationSource( uri = WORLD_ELEVATION_SERVICE_URL ) ) }
// Graphics overlay that will contain extruded polygon graphics. val graphicsOverlay: GraphicsOverlay
// Message dialog view model for reporting load errors. val messageDialogVM = MessageDialogViewModel()
val squareSize = 0.01
init { // Build renderer configured to extrude using the "height" attribute. val outline = SimpleLineSymbol( style = SimpleLineSymbolStyle.Solid, color = Color.white, width = 1f )
val fillSymbol = SimpleFillSymbol( style = SimpleFillSymbolStyle.Solid, color = Color.fromRgba(220, 50, 50, 200), outline = outline )
val renderer = SimpleRenderer(symbol = fillSymbol).apply { // Set extrusion mode and expression so the renderer uses the "height" attribute. sceneProperties = RendererSceneProperties().apply { extrusionMode = ExtrusionMode.BaseHeight extrusionExpression = "[height]" } }
// Initialize the graphics overlay and assign the renderer. graphicsOverlay = GraphicsOverlay().apply { sceneProperties.surfacePlacement = SurfacePlacement.DrapedBillboarded this.renderer = renderer }
// Populate the overlay with extruded graphics. addExtrudedGraphics() viewModelScope.launch {
// Load the scene; if loading fails, show a message. arcGISScene.load().onFailure { messageDialogVM.showMessageDialog(it) } } }
// Adds a grid of square polygons to the graphics overlay and assigns each a random height. private fun addExtrudedGraphics() { val baseX = camera.location.x - 0.01 val baseY = camera.location.y + 0.25 val spacing = 0.01 val columns = 6 val rows = 4 val maxHeight = 10_000
for (column in 0 until columns) { for (row in 0 until rows) { val startX = baseX + column * (squareSize + spacing) val startY = baseY + row * (squareSize + spacing) val polygon = polygonForPoint(startX, startY) val height = Random.nextInt(0, maxHeight + 1) val graphic = Graphic(geometry = polygon).apply { // The renderer will use this attribute to extrude the graphic. attributes["height"] = height } graphicsOverlay.graphics.add(graphic) } } }
// Helper to construct a square polygon given a lower-left origin. private fun polygonForPoint(x: Double, y: Double) = PolygonBuilder().apply { addPoint(Point(x = x, y = y)) addPoint(Point(x = x, y = y + squareSize)) addPoint(Point(x = x + squareSize, y = y + squareSize)) addPoint(Point(x = x + squareSize, y = y)) }.toGeometry()
companion object { // World elevation service used to provide base surface elevation. private const val WORLD_ELEVATION_SERVICE_URL = "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer" }}/* 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.showextrudedgraphics.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.showextrudedgraphics.components.ShowExtrudedGraphicsViewModelimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
/** * Main composable screen for the ShowExtrudedGraphics sample. * Displays a SceneView bound to the scene prepared in the ViewModel. */@Composablefun ShowExtrudedGraphicsScreen(sampleName: String) { val viewModel: ShowExtrudedGraphicsViewModel = viewModel()
Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { Column(modifier = Modifier .fillMaxSize() .padding(it)) {
// SceneView displays the 3D ArcGISScene prepared by the ViewModel. SceneView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISScene = viewModel.arcGISScene, graphicsOverlays = listOf(viewModel.graphicsOverlay) )
}
// Show any message dialogs coming from the ViewModel (loading / errors) viewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } )}