Position graphics relative to a surface using different surface placement modes.
Use case
Depending on the use case, data might be displayed at an absolute height (e.g. flight data recorded with altitude information), at a relative height to the terrain (e.g. transmission lines positioned relative to the ground), at a relative height to objects in the scene (e.g. extruded polygons, integrated mesh scene layer), or draped directly onto the terrain (e.g. location markers, area boundaries).
How to use the sample
The sample loads a scene showing four points that use the individual surface placement rules (absolute, relative, relative to scene, and either draped billboarded or draped flat). Use the toggle to change the draped mode and the slider to dynamically adjust the z value of the graphics. Explore the scene by zooming in/out and by panning around to observe the effects of the surface placement rules.
How it works
- Create an
AGSGraphicsOverlay
for each placement mode, specifying thesurfacePlacement
:absolute
, position graphic using only its z value.relative
, position graphic using its z value plus the elevation of the surface.relativeToScene
, position graphic using its z value plus the altitude values of the scene.drapedBillboarded
, position graphic upright on the surface and always facing the camera, not using its z value.drapedFlat
, position graphic flat on the surface, not using its z value.
- Add graphics to the graphics overlay's
graphics
array. - Add each graphics overlay to the scene view.
Relevant API
- AGSGraphic
- AGSGraphicsOverlay
- AGSLayerSceneProperties
- AGSSurface
- AGSSurfacePlacement
- class AGSGeometryEngine.geometry(bySettingZ:in:)
About the data
The scene shows a view of Brest, France. Four points are shown hovering with positions defined by each of the different surface placement modes (absolute, relative, relative to scene, and either draped billboarded or draped flat).
Additional information
This sample uses an elevation service to add elevation/terrain to the scene. Graphics are positioned relative to that surface for the drapedBillboarded
, drapedFlat
, and relative
surface placement modes. It also uses a scene layer containing 3D models of buildings. Graphics are positioned relative to that scene layer for the relativeToScene
surface placement mode.
Tags
3D, absolute, altitude, draped, elevation, floating, relative, scenes, sea level, surface placement
Sample Code
//
// Copyright 2016 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.
//
import UIKit
import ArcGIS
/// A view controller that manages the interface of the Surface Placements
/// sample.
class SurfacePlacementsViewController: UIViewController {
// MARK: Instance properties
/// A label to show the value of the slider.
@IBOutlet weak var zValueLabel: UILabel!
/// The slider to change z-value of `AGSPoint` geometries, from 0 to 140 in meters.
@IBOutlet weak var zValueSlider: UISlider! {
didSet {
zValueSlider.value = (zValueSlider.maximumValue + zValueSlider.minimumValue) / 2
}
}
/// The segmented control to toggle the visibility of two draped mode graphics overlays.
@IBOutlet weak var drapedModeSegmentedControl: UISegmentedControl!
/// The scene view managed by the view controller.
@IBOutlet var sceneView: AGSSceneView! {
didSet {
sceneView.scene = makeScene()
sceneView.setViewpointCamera(AGSCamera(latitude: 48.3889, longitude: -4.4595, altitude: 80, heading: 330, pitch: 97, roll: 0))
// Add graphics overlays of different surface placement modes to the scene.
let surfacePlacements: [AGSSurfacePlacement] = [
.drapedBillboarded,
.drapedFlat,
.relative,
.relativeToScene,
.absolute
]
let overlays = surfacePlacements.map(makeGraphicsOverlay)
overlaysBySurfacePlacement = Dictionary(uniqueKeysWithValues: zip(surfacePlacements, overlays))
sceneView.graphicsOverlays.addObjects(from: overlays)
}
}
/// A dictionary for graphics overlays of different surface placement modes.
var overlaysBySurfacePlacement = [AGSSurfacePlacement: AGSGraphicsOverlay]()
/// A formatter to format z-value strings.
let zValueFormatter: MeasurementFormatter = {
let formatter = MeasurementFormatter()
formatter.unitStyle = .short
formatter.unitOptions = .naturalScale
formatter.numberFormatter.maximumFractionDigits = 0
return formatter
}()
// MARK: - Actions
@IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
// Toggle the visibility of two draped mode graphics overlays respectively.
let isDrapedFlat = sender.selectedSegmentIndex == 1
overlaysBySurfacePlacement[.drapedFlat]!.isVisible = isDrapedFlat
overlaysBySurfacePlacement[.drapedBillboarded]!.isVisible = !isDrapedFlat
}
@IBAction func sliderValueChanged(_ sender: UISlider) {
let zValue = Double(sender.value)
zValueLabel.text = zValueFormatter.string(from: Measurement<UnitLength>(value: zValue, unit: .meters))
// Set the z-value of each geometry of surface placement graphics.
overlaysBySurfacePlacement.values.forEach { graphicOverlay in
graphicOverlay.graphics.forEach { graphic in
let g = graphic as! AGSGraphic
g.geometry = AGSGeometryEngine.geometry(bySettingZ: zValue, in: g.geometry!)
}
}
}
// MARK: Initialize scene and make graphics overlays
/// Create a scene.
///
/// - Returns: A new `AGSScene` object.
func makeScene() -> AGSScene {
let scene = AGSScene(basemapStyle: .arcGISImagery)
// Add a base surface for elevation data.
let surface = AGSSurface()
// Create elevation source from the Terrain 3D ArcGIS REST Service.
let worldElevationServiceURL = URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!
let elevationSource = AGSArcGISTiledElevationSource(url: worldElevationServiceURL)
surface.elevationSources.append(elevationSource)
// Create scene layer from the Brest, France scene server.
let sceneServiceURL = URL(string: "https://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Brest/SceneServer")!
let sceneLayer = AGSArcGISSceneLayer(url: sceneServiceURL)
scene.baseSurface = surface
scene.operationalLayers.add(sceneLayer)
return scene
}
/// Create a graphics overlay for the given surface placement.
///
/// - Parameter surfacePlacement: The surface placement for which to create a graphics overlay.
/// - Returns: A new `AGSGraphicsOverlay` object.
func makeGraphicsOverlay(surfacePlacement: AGSSurfacePlacement) -> AGSGraphicsOverlay {
let markerSymbol = AGSSimpleMarkerSymbol(style: .triangle, color: .red, size: 20)
let textSymbol = AGSTextSymbol(text: surfacePlacement.title, color: .blue, size: 20, horizontalAlignment: .left, verticalAlignment: .middle)
// Add offset to avoid overlapping text and marker.
textSymbol.offsetY = 20
// Add offset to x and y of the geometry, to better differentiate certain geometries.
let offset = surfacePlacement == .relativeToScene ? 2e-4 : 0
let surfaceRelatedPoint = AGSPoint(x: -4.4609257 + offset, y: 48.3903965 + offset, z: 70, spatialReference: .wgs84())
let graphics = [markerSymbol, textSymbol].map { AGSGraphic(geometry: surfaceRelatedPoint, symbol: $0) }
let graphicsOverlay = AGSGraphicsOverlay()
graphicsOverlay.sceneProperties?.surfacePlacement = surfacePlacement
graphicsOverlay.graphics.addObjects(from: graphics)
return graphicsOverlay
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// Add the source code button item to the right of navigation bar.
(self.navigationItem.rightBarButtonItem as! SourceCodeBarButtonItem).filenames = ["SurfacePlacementsViewController"]
// Initialize the slider and draped mode visibility.
segmentedControlValueChanged(drapedModeSegmentedControl)
sliderValueChanged(zValueSlider)
}
}
private extension AGSSurfacePlacement {
/// The human readable name of the surface placement.
var title: String {
switch self {
case .drapedBillboarded: return "Draped Billboarded"
case .absolute: return "Absolute"
case .relative: return "Relative"
case .relativeToScene: return "Relative to Scene"
case .drapedFlat: return "Draped Flat"
@unknown default: return "Unknown"
}
}
}