Skip to content

Commit 54ed953

Browse files
authored
Merge pull request #356 from Esri/Caleb/New-AugmentRealityToShowHiddenInfrastructure
[New] Augment reality to show hidden infrastructure
2 parents 2172506 + 8b11c2e commit 54ed953

File tree

7 files changed

+554
-0
lines changed

7 files changed

+554
-0
lines changed

Samples.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,14 @@
294294
D7781D4C2B7ECCC800E53C51 /* NavigateRouteWithReroutingView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7781D4A2B7ECCB700E53C51 /* NavigateRouteWithReroutingView.Model.swift */; };
295295
D77BC5392B59A2D3007B49B6 /* StylePointWithDistanceCompositeSceneSymbolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BC5362B59A2D3007B49B6 /* StylePointWithDistanceCompositeSceneSymbolView.swift */; };
296296
D77BC53C2B59A309007B49B6 /* StylePointWithDistanceCompositeSceneSymbolView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D77BC5362B59A2D3007B49B6 /* StylePointWithDistanceCompositeSceneSymbolView.swift */; };
297+
D77D9C002BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77D9BFF2BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift */; };
298+
D77D9C012BB2439400B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D77D9BFF2BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift */; };
297299
D78666AD2A2161F100C60110 /* FindNearestVertexView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78666AC2A2161F100C60110 /* FindNearestVertexView.swift */; };
298300
D78666AE2A21629200C60110 /* FindNearestVertexView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D78666AC2A2161F100C60110 /* FindNearestVertexView.swift */; };
299301
D79EE76E2A4CEA5D005A52AE /* SetUpLocationDrivenGeotriggersView.Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79EE76D2A4CEA5D005A52AE /* SetUpLocationDrivenGeotriggersView.Model.swift */; };
300302
D79EE76F2A4CEA7F005A52AE /* SetUpLocationDrivenGeotriggersView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D79EE76D2A4CEA5D005A52AE /* SetUpLocationDrivenGeotriggersView.Model.swift */; };
303+
D7A737E02BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A737DC2BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift */; };
304+
D7A737E32BABBA2200B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7A737DC2BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift */; };
301305
D7ABA2F92A32579C0021822B /* MeasureDistanceInSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ABA2F82A32579C0021822B /* MeasureDistanceInSceneView.swift */; };
302306
D7ABA2FA2A32760D0021822B /* MeasureDistanceInSceneView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7ABA2F82A32579C0021822B /* MeasureDistanceInSceneView.swift */; };
303307
D7ABA2FF2A32881C0021822B /* ShowViewshedFromGeoelementInSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ABA2FE2A32881C0021822B /* ShowViewshedFromGeoelementInSceneView.swift */; };
@@ -437,6 +441,8 @@
437441
dstPath = "";
438442
dstSubfolderSpec = 7;
439443
files = (
444+
D77D9C012BB2439400B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift in Copy Source Code Files */,
445+
D7A737E32BABBA2200B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift in Copy Source Code Files */,
440446
1C2538542BABACB100337307 /* AugmentRealityToNavigateRouteView.RoutePlannerView.swift in Copy Source Code Files */,
441447
1C2538552BABACB100337307 /* AugmentRealityToNavigateRouteView.swift in Copy Source Code Files */,
442448
000D43182B993A030003D3C2 /* ConfigureBasemapStyleParametersView.swift in Copy Source Code Files */,
@@ -763,8 +769,10 @@
763769
D7781D482B7EB03400E53C51 /* SanDiegoTourPath.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = SanDiegoTourPath.json; sourceTree = "<group>"; };
764770
D7781D4A2B7ECCB700E53C51 /* NavigateRouteWithReroutingView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigateRouteWithReroutingView.Model.swift; sourceTree = "<group>"; };
765771
D77BC5362B59A2D3007B49B6 /* StylePointWithDistanceCompositeSceneSymbolView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StylePointWithDistanceCompositeSceneSymbolView.swift; sourceTree = "<group>"; };
772+
D77D9BFF2BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift; sourceTree = "<group>"; };
766773
D78666AC2A2161F100C60110 /* FindNearestVertexView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindNearestVertexView.swift; sourceTree = "<group>"; };
767774
D79EE76D2A4CEA5D005A52AE /* SetUpLocationDrivenGeotriggersView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetUpLocationDrivenGeotriggersView.Model.swift; sourceTree = "<group>"; };
775+
D7A737DC2BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AugmentRealityToShowHiddenInfrastructureView.swift; sourceTree = "<group>"; };
768776
D7ABA2F82A32579C0021822B /* MeasureDistanceInSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeasureDistanceInSceneView.swift; sourceTree = "<group>"; };
769777
D7ABA2FE2A32881C0021822B /* ShowViewshedFromGeoelementInSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowViewshedFromGeoelementInSceneView.swift; sourceTree = "<group>"; };
770778
D7AE861D2AC39DC50049B626 /* DisplayAnnotationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayAnnotationView.swift; sourceTree = "<group>"; };
@@ -936,6 +944,7 @@
936944
D75362CC2A1E862B00D83028 /* Apply unique value renderer */,
937945
D7084FA42AD771AA00EC7F4F /* Augment reality to fly over scene */,
938946
1C2538472BABAC7B00337307 /* Augment reality to navigate route */,
947+
D7A737DF2BABB9FE00B7C7FC /* Augment reality to show hidden infrastructure */,
939948
D72F27292ADA1E4400F906DA /* Augment reality to show tabletop scene */,
940949
218F35B229C28F4A00502022 /* Authenticate with OAuth */,
941950
E0FE32E528747762002C6ACA /* Browse building floors */,
@@ -1877,6 +1886,15 @@
18771886
path = "Find nearest vertex";
18781887
sourceTree = "<group>";
18791888
};
1889+
D7A737DF2BABB9FE00B7C7FC /* Augment reality to show hidden infrastructure */ = {
1890+
isa = PBXGroup;
1891+
children = (
1892+
D7A737DC2BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift */,
1893+
D77D9BFF2BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift */,
1894+
);
1895+
path = "Augment reality to show hidden infrastructure";
1896+
sourceTree = "<group>";
1897+
};
18801898
D7ABA2F52A3256610021822B /* Measure distance in scene */ = {
18811899
isa = PBXGroup;
18821900
children = (
@@ -2595,6 +2613,7 @@
25952613
79302F852A1ED4E30002336A /* CreateAndSaveKMLView.Model.swift in Sources */,
25962614
D7C3AB4A2B683291008909B9 /* SetFeatureRequestModeView.swift in Sources */,
25972615
D7058FB12ACB423C00A40F14 /* Animate3DGraphicView.Model.swift in Sources */,
2616+
D77D9C002BB2438200B38A6C /* AugmentRealityToShowHiddenInfrastructureView.ARSceneView.swift in Sources */,
25982617
0044CDDF2995C39E004618CE /* ShowDeviceLocationHistoryView.swift in Sources */,
25992618
E041ABC0287CA9F00056009B /* WebView.swift in Sources */,
26002619
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */,
@@ -2654,6 +2673,7 @@
26542673
00A7A14A2A2FC5B700F035F7 /* DisplayContentOfUtilityNetworkContainerView.Model.swift in Sources */,
26552674
E004A6F0284E4B9B002A1FE6 /* DownloadVectorTilesToLocalCacheView.swift in Sources */,
26562675
1CAB8D4E2A3CEAB0002AA649 /* RunValveIsolationTraceView.swift in Sources */,
2676+
D7A737E02BABB9FE00B7C7FC /* AugmentRealityToShowHiddenInfrastructureView.swift in Sources */,
26572677
4D2ADC4329C26D05003B367F /* AddDynamicEntityLayerView.swift in Sources */,
26582678
D70082EB2ACF900100E0C3C2 /* IdentifyKMLFeaturesView.swift in Sources */,
26592679
D7635FFB2B9277DC0044AB97 /* ConfigureClustersView.Model.swift in Sources */,
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2024 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+
// https://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+
import ArcGIS
16+
import ArcGISToolkit
17+
import SwiftUI
18+
19+
extension AugmentRealityToShowHiddenInfrastructureView {
20+
/// A world scale scene view displaying pipe graphics from a given model.
21+
struct ARPipesSceneView: View {
22+
/// The view model for scene view in the sample.
23+
@ObservedObject var model: SceneModel
24+
25+
/// A Boolean value indicating whether the shadow graphics are visible.
26+
@State private var shadowsAreVisible = true
27+
28+
/// A Boolean value indicating whether the leader line graphics are visible.
29+
@State private var leadersAreVisible = true
30+
31+
var body: some View {
32+
VStack(spacing: 0) {
33+
WorldScaleSceneView { _ in
34+
SceneView(scene: model.scene, graphicsOverlays: [
35+
model.pipeGraphicsOverlay,
36+
model.shadowGraphicsOverlay,
37+
model.leaderGraphicsOverlay
38+
])
39+
}
40+
.calibrationButtonAlignment(.bottomLeading)
41+
.onCalibratingChanged { newCalibrating in
42+
model.scene.baseSurface.opacity = newCalibrating ? 0.6 : 0
43+
}
44+
45+
Divider()
46+
}
47+
.toolbar {
48+
ToolbarItem(placement: .bottomBar) {
49+
settingsMenu
50+
}
51+
}
52+
}
53+
54+
/// The settings menu.
55+
private var settingsMenu: some View {
56+
Menu("Settings") {
57+
Toggle("Shadows", isOn: $shadowsAreVisible)
58+
.onChange(of: shadowsAreVisible) { newValue in
59+
model.shadowGraphicsOverlay.isVisible = newValue
60+
}
61+
Toggle("Leaders", isOn: $leadersAreVisible)
62+
.onChange(of: leadersAreVisible) { newValue in
63+
model.leaderGraphicsOverlay.isVisible = newValue
64+
}
65+
}
66+
}
67+
}
68+
}
69+
70+
extension AugmentRealityToShowHiddenInfrastructureView {
71+
// MARK: Scene Model
72+
73+
/// The view model for scene view in the sample.
74+
class SceneModel: ObservableObject {
75+
/// A scene with an imagery basemap style and an elevation surface.
76+
let scene: ArcGIS.Scene = {
77+
let scene = Scene(basemapStyle: .arcGISImagery)
78+
79+
// Create a surface with an elevation source and set it to the scene's base surface.
80+
let surface = Surface()
81+
surface.navigationConstraint = .unconstrained
82+
surface.opacity = 0
83+
surface.backgroundGrid.isVisible = false
84+
85+
let elevationSource = ArcGISTiledElevationSource(url: .worldElevationService)
86+
surface.addElevationSource(elevationSource)
87+
scene.baseSurface = surface
88+
89+
return scene
90+
}()
91+
92+
/// The graphics overlay for the pipe graphics.
93+
let pipeGraphicsOverlay: GraphicsOverlay = {
94+
let graphicsOverlay = GraphicsOverlay()
95+
graphicsOverlay.sceneProperties.surfacePlacement = .absolute
96+
97+
let strokeSymbolLayer = SolidStrokeSymbolLayer(
98+
width: 0.3,
99+
color: .red,
100+
lineStyle3D: .tube
101+
)
102+
let polylineSymbol = MultilayerPolylineSymbol(symbolLayers: [strokeSymbolLayer])
103+
graphicsOverlay.renderer = SimpleRenderer(symbol: polylineSymbol)
104+
105+
return graphicsOverlay
106+
}()
107+
108+
/// The graphics overlay for the shadow graphics of the underground pipes.
109+
let shadowGraphicsOverlay: GraphicsOverlay = {
110+
let graphicsOverlay = GraphicsOverlay()
111+
graphicsOverlay.sceneProperties.surfacePlacement = .drapedFlat
112+
113+
let yellowLineSymbol = SimpleLineSymbol(style: .solid, color: .systemYellow, width: 0.3)
114+
graphicsOverlay.renderer = SimpleRenderer(symbol: yellowLineSymbol)
115+
116+
return graphicsOverlay
117+
}()
118+
119+
/// The graphics overlay for the pipe leader line graphics.
120+
let leaderGraphicsOverlay: GraphicsOverlay = {
121+
let graphicsOverlay = GraphicsOverlay()
122+
graphicsOverlay.sceneProperties.surfacePlacement = .absolute
123+
124+
let dashedRedLineSymbol = SimpleLineSymbol(style: .dash, color: .systemRed, width: 0.3)
125+
graphicsOverlay.renderer = SimpleRenderer(symbol: dashedRedLineSymbol)
126+
127+
return graphicsOverlay
128+
}()
129+
130+
init() {
131+
Task {
132+
try? await scene.load()
133+
}
134+
}
135+
136+
/// Adds graphics created from a given polyline and elevation offset to the graphics overlays.
137+
/// - Parameters:
138+
/// - polyline: The polyline representing a pipe.
139+
/// - elevationOffset: An elevation to offset the pipe with.
140+
func addGraphics(for polyline: Polyline, elevationOffset: Double) async {
141+
guard let firstPoint = polyline.parts.first?.startPoint,
142+
let elevation = try? await scene.baseSurface.elevation(at: firstPoint) else { return }
143+
144+
// Add the elevation with the offset to the polyline.
145+
let elevatedPolyline = GeometryEngine.makeGeometry(
146+
from: polyline,
147+
z: elevation + elevationOffset
148+
)
149+
150+
// Add a pipe graphic using the elevated polyline.
151+
let pipeGraphic = Graphic(geometry: elevatedPolyline)
152+
pipeGraphicsOverlay.addGraphic(pipeGraphic)
153+
154+
// Add graphics for the leader lines.
155+
let leaderLineGraphics = elevatedPolyline.parts.map { part in
156+
part.points.map { point in
157+
let offsetPoint = GeometryEngine.makeGeometry(
158+
from: point,
159+
z: (point.z ?? 0) - elevationOffset
160+
)
161+
let leaderLine = Polyline(points: [point, offsetPoint])
162+
return Graphic(geometry: leaderLine)
163+
}
164+
}
165+
leaderGraphicsOverlay.addGraphics(Array(leaderLineGraphics.joined()))
166+
167+
// Add a shadow graphic for the pipe if it is below ground.
168+
if elevationOffset < 0 {
169+
let shadowGraphic = Graphic(geometry: polyline)
170+
shadowGraphicsOverlay.addGraphic(shadowGraphic)
171+
}
172+
}
173+
}
174+
}
175+
176+
private extension URL {
177+
/// The URL of the Terrain 3D ArcGIS REST Service.
178+
static var worldElevationService: URL {
179+
URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!
180+
}
181+
}

0 commit comments

Comments
 (0)