Skip to content

Apply scene property expressions

View on GitHubSample viewer app

Update the orientation of a graphic using expressions based on its attributes.

Image of apply scene property expressions

Use case

Instead of reading the attribute and changing the rotation on the symbol for a single graphic (a manual CPU operation), you can bind the rotation to an expression that applies to the whole overlay (an automatic GPU operation). This usually results in a noticeable performance boost (smooth rotations).

How to use the sample

Adjust the heading and pitch sliders to rotate the cone.

How it works

  1. Create a new graphics overlay.
  2. Create a simple renderer and set its scene properties.
  3. Set the heading expression to [HEADING].
  4. Set the pitch expression to [PITCH].
  5. Apply the renderer to the graphics overlay.
  6. Create a graphic and add it to the overlay.
  7. To update the graphic's rotation, update the HEADING or PITCH property in the graphic's attributes.

Relevant API

  • Graphic.Attributes
  • GraphicsOverlay
  • SceneProperties
  • SceneProperties.headingExpression
  • SceneProperties.pitchExpression
  • SimpleRenderer
  • SimpleRenderer.SceneProperties

Tags

3D, expression, graphics, heading, pitch, rotation, scene, symbology

Sample Code

ApplyScenePropertyExpressionsViewModel.ktApplyScenePropertyExpressionsViewModel.ktMainActivity.ktApplyScenePropertyExpressionsScreen.kt
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/* 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.applyscenepropertyexpressions.components

import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.Color
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.SurfacePlacement
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleRenderer
import com.arcgismaps.mapping.view.Camera
import com.arcgismaps.mapping.view.Graphic
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.launch

class ApplyScenePropertyExpressionsViewModel(app: Application) : AndroidViewModel(app) {
    // The ArcGISScene shown in the SceneView
    val arcGISScene = ArcGISScene(BasemapStyle.ArcGISImageryStandard).apply {
        // Set initial viewpoint with a camera looking at the cone
        val point = Point(x = 83.9, y = 28.4, z = 1000.0, spatialReference = SpatialReference.wgs84())
        initialViewpoint = Viewpoint(
            latitude = 0.0,
            longitude = 0.0,
            scale = 1.0,
            camera = Camera(lookAtPoint = point, distance = 1000.0, heading = 0.0, pitch = 65.0, roll = 0.0)
        )
    }

    // UI state for heading and pitch sliders
    var heading by mutableDoubleStateOf(180.0)
        private set
    var pitch by mutableDoubleStateOf(45.0)
        private set


    // Create the cone symbol
    private val coneSymbol = SimpleMarkerSceneSymbol(
        style = SimpleMarkerSceneSymbolStyle.Cone, color = Color.red, height = 100.0, width = 100.0, depth = 100.0
    )

    // The cone graphic, with mutable heading and pitch attributes
    private val coneGraphic = Graphic(
            geometry = Point(x = 83.9, y = 28.42, z = 200.0, spatialReference = SpatialReference.wgs84()),
            attributes = mapOf(
                "HEADING" to heading, "PITCH" to pitch
            ),
            symbol = coneSymbol
        )

    // GraphicsOverlay with heading and pitch expressions
    val graphicsOverlay: GraphicsOverlay = GraphicsOverlay().apply {
        sceneProperties.surfacePlacement = SurfacePlacement.Relative
        // Set up renderer with heading and pitch expressions
        renderer = SimpleRenderer().apply {
            sceneProperties.headingExpression = "[HEADING]"
            sceneProperties.pitchExpression = "[PITCH]"
        }
        graphics.add(coneGraphic)
    }

    // Message dialog for error handling
    val messageDialogVM = MessageDialogViewModel()

    init {
        // Load the scene and handle errors
        viewModelScope.launch {
            arcGISScene.load().onFailure { messageDialogVM.showMessageDialog(it) }
        }
    }

    // Called when the user changes the heading slider
    fun updateHeading(newHeading: Double) {
        heading = newHeading
        coneGraphic.attributes["HEADING"] = heading
    }

    // Called when the user changes the pitch slider
    fun updatePitch(newPitch: Double) {
        pitch = newPitch
        coneGraphic.attributes["PITCH"] = pitch
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.