Skip to content

Commit 5ff0aff

Browse files
rchintapallisorenoidshubham7109
authored
Show extruded graphics sample (#427)
Co-authored-by: Soren Roth <sroth@esri.com> Co-authored-by: Shubham Sharma <shubhamsharma@esri.com>
1 parent a7eae78 commit 5ff0aff

File tree

9 files changed

+387
-0
lines changed

9 files changed

+387
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Show extruded graphics
2+
3+
Extrude graphics based on an attribute value.
4+
5+
![Image of show extruded graphics](show-extruded-graphics.png)
6+
7+
## Use case
8+
9+
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.
10+
11+
## How to use the sample
12+
13+
Run the sample. Note that the graphics are extruded to the level set in their height property.
14+
15+
## How it works
16+
17+
1. Create a `GraphicsOverlay` and `SimpleRenderer`.
18+
2. Set the extrusion mode for the renderer with `RendererSceneProperties.extrusionMode = ExtrusionMode.BaseHeight`.
19+
3. Specify the attribute name of the graphic that the extrusion mode will use, `RendererSceneProperties.extrusionExpression = "[height]"`.
20+
4. Set the renderer on the graphics overlay using, `GraphicsOverlay.renderer`.
21+
5. Create graphics with their attribute set, `graphic.attributes["height"]`.
22+
23+
## Relevant API
24+
25+
* ExtrusionMode
26+
* RendererSceneProperties
27+
* SimpleRenderer
28+
29+
## About the data
30+
31+
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.
32+
33+
## Tags
34+
35+
3D, extrude, extrusion, height, scene, visualization
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"category": "Scenes",
3+
"description": "Extrude graphics based on an attribute value.",
4+
"formal_name": "ShowExtrudedGraphics",
5+
"ignore": false,
6+
"images": [
7+
"show-extruded-graphics.png"
8+
],
9+
"keywords": [
10+
"3D",
11+
"extrude",
12+
"extrusion",
13+
"height",
14+
"scene",
15+
"visualization",
16+
"ExtrusionMode",
17+
"RendererSceneProperties",
18+
"SimpleRenderer"
19+
],
20+
"language": "kotlin",
21+
"redirect_from": "TODO",
22+
"relevant_apis": [
23+
"ExtrusionMode",
24+
"RendererSceneProperties",
25+
"SimpleRenderer"
26+
],
27+
"snippets": [
28+
"src/main/java/com/esri/arcgismaps/sample/showextrudedgraphics/components/ShowExtrudedGraphicsViewModel.kt",
29+
"src/main/java/com/esri/arcgismaps/sample/showextrudedgraphics/MainActivity.kt",
30+
"src/main/java/com/esri/arcgismaps/sample/showextrudedgraphics/screens/ShowExtrudedGraphicsScreen.kt"
31+
],
32+
"title": "Show extruded graphics"
33+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
alias(libs.plugins.arcgismaps.android.library)
3+
alias(libs.plugins.arcgismaps.android.library.compose)
4+
alias(libs.plugins.arcgismaps.kotlin.sample)
5+
alias(libs.plugins.gradle.secrets)
6+
}
7+
8+
secrets {
9+
// this file doesn't contain secrets, it just provides defaults which can be committed into git.
10+
defaultPropertiesFileName = "secrets.defaults.properties"
11+
}
12+
13+
android {
14+
namespace = "com.esri.arcgismaps.sample.showextrudedgraphics"
15+
buildFeatures {
16+
buildConfig = true
17+
}
18+
}
19+
20+
dependencies {
21+
// Only module specific dependencies needed here
22+
}
702 KB
Loading
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
6+
<application><activity
7+
android:exported="true"
8+
android:name=".MainActivity"
9+
android:label="@string/show_extruded_graphics_app_name">
10+
11+
</activity>
12+
</application>
13+
14+
</manifest>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* Copyright 2025 Esri
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
17+
package com.esri.arcgismaps.sample.showextrudedgraphics
18+
19+
import android.os.Bundle
20+
import androidx.activity.ComponentActivity
21+
import androidx.activity.compose.setContent
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.Surface
24+
import androidx.compose.runtime.Composable
25+
import com.arcgismaps.ApiKey
26+
import com.arcgismaps.ArcGISEnvironment
27+
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
28+
import com.esri.arcgismaps.sample.showextrudedgraphics.screens.ShowExtrudedGraphicsScreen
29+
30+
class MainActivity : ComponentActivity() {
31+
32+
override fun onCreate(savedInstanceState: Bundle?) {
33+
super.onCreate(savedInstanceState)
34+
// authentication with an API key or named user is
35+
// required to access basemaps and other location services
36+
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)
37+
38+
setContent {
39+
SampleAppTheme {
40+
ShowExtrudedGraphicsApp()
41+
}
42+
}
43+
}
44+
45+
@Composable
46+
private fun ShowExtrudedGraphicsApp() {
47+
Surface(color = MaterialTheme.colorScheme.background) {
48+
ShowExtrudedGraphicsScreen(
49+
sampleName = getString(R.string.show_extruded_graphics_app_name)
50+
)
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/* Copyright 2025 Esri
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
17+
package com.esri.arcgismaps.sample.showextrudedgraphics.components
18+
19+
import android.app.Application
20+
import androidx.lifecycle.AndroidViewModel
21+
import androidx.lifecycle.viewModelScope
22+
import com.arcgismaps.Color
23+
import com.arcgismaps.geometry.Point
24+
import com.arcgismaps.geometry.PolygonBuilder
25+
import com.arcgismaps.mapping.ArcGISScene
26+
import com.arcgismaps.mapping.ArcGISTiledElevationSource
27+
import com.arcgismaps.mapping.BasemapStyle
28+
import com.arcgismaps.mapping.Viewpoint
29+
import com.arcgismaps.mapping.symbology.ExtrusionMode
30+
import com.arcgismaps.mapping.view.GraphicsOverlay
31+
import com.arcgismaps.mapping.view.SurfacePlacement
32+
import com.arcgismaps.mapping.view.Graphic
33+
import com.arcgismaps.mapping.symbology.SimpleFillSymbol
34+
import com.arcgismaps.mapping.symbology.SimpleFillSymbolStyle
35+
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
36+
import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
37+
import com.arcgismaps.mapping.symbology.SimpleRenderer
38+
import com.arcgismaps.mapping.symbology.RendererSceneProperties
39+
import com.arcgismaps.mapping.view.Camera
40+
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
41+
import kotlinx.coroutines.launch
42+
import kotlin.random.Random
43+
44+
/**
45+
* ViewModel for the ShowExtrudedGraphics sample.
46+
*
47+
* Prepares a 3D scene, adds an elevation source and a graphics overlay whose
48+
* renderer is configured to extrude graphics based on the "height" attribute.
49+
*/
50+
class ShowExtrudedGraphicsViewModel(application: Application) : AndroidViewModel(application) {
51+
52+
// Create a camera and set the scene's initial viewpoint.
53+
private val camera = Camera(
54+
latitude = 28.4,
55+
longitude = 83.0,
56+
altitude = 20_000.0,
57+
heading = 10.0,
58+
pitch = 70.0,
59+
roll = 0.0
60+
)
61+
// A 3D scene with a topographic basemap and an initial camera viewpoint.
62+
val arcGISScene = ArcGISScene(BasemapStyle.ArcGISTopographic).apply {
63+
64+
initialViewpoint = Viewpoint(camera = camera, boundingGeometry = camera.location)
65+
66+
// Add a global elevation source to the base surface so extruded geometry has context.
67+
baseSurface.elevationSources.add(
68+
ArcGISTiledElevationSource(
69+
uri = WORLD_ELEVATION_SERVICE_URL
70+
)
71+
)
72+
}
73+
74+
75+
// Graphics overlay that will contain extruded polygon graphics.
76+
val graphicsOverlay: GraphicsOverlay
77+
78+
// Message dialog view model for reporting load errors.
79+
val messageDialogVM = MessageDialogViewModel()
80+
81+
val squareSize = 0.01
82+
83+
init {
84+
// Build renderer configured to extrude using the "height" attribute.
85+
val outline = SimpleLineSymbol(
86+
style = SimpleLineSymbolStyle.Solid,
87+
color = Color.white,
88+
width = 1f
89+
)
90+
91+
val fillSymbol = SimpleFillSymbol(
92+
style = SimpleFillSymbolStyle.Solid,
93+
color = Color.fromRgba(220, 50, 50, 200),
94+
outline = outline
95+
)
96+
97+
val renderer = SimpleRenderer(symbol = fillSymbol).apply {
98+
// Set extrusion mode and expression so the renderer uses the "height" attribute.
99+
sceneProperties = RendererSceneProperties().apply {
100+
extrusionMode = ExtrusionMode.BaseHeight
101+
extrusionExpression = "[height]"
102+
}
103+
}
104+
105+
// Initialize the graphics overlay and assign the renderer.
106+
graphicsOverlay = GraphicsOverlay().apply {
107+
sceneProperties.surfacePlacement = SurfacePlacement.DrapedBillboarded
108+
this.renderer = renderer
109+
}
110+
111+
// Populate the overlay with extruded graphics.
112+
addExtrudedGraphics()
113+
viewModelScope.launch {
114+
115+
// Load the scene; if loading fails, show a message.
116+
arcGISScene.load().onFailure { messageDialogVM.showMessageDialog(it) }
117+
}
118+
}
119+
120+
// Adds a grid of square polygons to the graphics overlay and assigns each a random height.
121+
private fun addExtrudedGraphics() {
122+
val baseX = camera.location.x - 0.01
123+
val baseY = camera.location.y + 0.25
124+
val spacing = 0.01
125+
val columns = 6
126+
val rows = 4
127+
val maxHeight = 10_000
128+
129+
for (column in 0 until columns) {
130+
for (row in 0 until rows) {
131+
val startX = baseX + column * (squareSize + spacing)
132+
val startY = baseY + row * (squareSize + spacing)
133+
val polygon = polygonForPoint(startX, startY)
134+
val height = Random.nextInt(0, maxHeight + 1)
135+
val graphic = Graphic(geometry = polygon).apply {
136+
// The renderer will use this attribute to extrude the graphic.
137+
attributes["height"] = height
138+
}
139+
graphicsOverlay.graphics.add(graphic)
140+
}
141+
}
142+
}
143+
144+
// Helper to construct a square polygon given a lower-left origin.
145+
private fun polygonForPoint(x: Double, y: Double) =
146+
PolygonBuilder().apply {
147+
addPoint(Point(x = x, y = y))
148+
addPoint(Point(x = x, y = y + squareSize))
149+
addPoint(Point(x = x + squareSize, y = y + squareSize))
150+
addPoint(Point(x = x + squareSize, y = y))
151+
}.toGeometry()
152+
153+
companion object {
154+
// World elevation service used to provide base surface elevation.
155+
private const val WORLD_ELEVATION_SERVICE_URL =
156+
"https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
157+
}
158+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* Copyright 2025 Esri
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
17+
package com.esri.arcgismaps.sample.showextrudedgraphics.screens
18+
19+
import androidx.compose.foundation.layout.Column
20+
import androidx.compose.foundation.layout.fillMaxSize
21+
import androidx.compose.foundation.layout.padding
22+
import androidx.compose.material3.Scaffold
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.ui.Modifier
25+
import androidx.lifecycle.viewmodel.compose.viewModel
26+
import com.arcgismaps.toolkit.geoviewcompose.SceneView
27+
import com.esri.arcgismaps.sample.showextrudedgraphics.components.ShowExtrudedGraphicsViewModel
28+
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog
29+
import com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
30+
31+
/**
32+
* Main composable screen for the ShowExtrudedGraphics sample.
33+
* Displays a SceneView bound to the scene prepared in the ViewModel.
34+
*/
35+
@Composable
36+
fun ShowExtrudedGraphicsScreen(sampleName: String) {
37+
val viewModel: ShowExtrudedGraphicsViewModel = viewModel()
38+
39+
Scaffold(
40+
topBar = { SampleTopAppBar(title = sampleName) },
41+
content = {
42+
Column(modifier = Modifier
43+
.fillMaxSize()
44+
.padding(it)) {
45+
46+
// SceneView displays the 3D ArcGISScene prepared by the ViewModel.
47+
SceneView(
48+
modifier = Modifier
49+
.fillMaxSize()
50+
.weight(1f),
51+
arcGISScene = viewModel.arcGISScene,
52+
graphicsOverlays = listOf(viewModel.graphicsOverlay)
53+
)
54+
55+
}
56+
57+
// Show any message dialogs coming from the ViewModel (loading / errors)
58+
viewModel.messageDialogVM.apply {
59+
if (dialogStatus) {
60+
MessageDialog(
61+
title = messageTitle,
62+
description = messageDescription,
63+
onDismissRequest = ::dismissDialog
64+
)
65+
}
66+
}
67+
}
68+
)
69+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<resources>
2+
<string name="show_extruded_graphics_app_name">Show extruded graphics</string>
3+
</resources>

0 commit comments

Comments
 (0)