Skip to content

Augment reality to show hidden infrastructure #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
android:exported="true" />
<!-- Insert your Google API key in order to use Geospatial tracking. -->
<meta-data
android:name="com.google.android.ar.API_KEY"
android:value="${GOOGLE_API_KEY}"/>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal fun Project.configureKotlinAndroid(

defaultConfig {
buildConfigField("String", "ACCESS_TOKEN", project.properties["ACCESS_TOKEN"].toString())
manifestPlaceholders["GOOGLE_API_KEY"] = project.properties["GOOGLE_API_KEY"].toString()
minSdk = libs.findVersion("minSdk").get().toString().toInt()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,15 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<application><activity
android:name=".DownloadActivity"
android:exported="true"
android:label="@string/augment_reality_to_navigate_route_app_name">

</activity>
<application>
<activity
android:name=".DownloadActivity"
android:exported="true"
android:label="@string/augment_reality_to_navigate_route_app_name"/>
<activity
android:name=".MainActivity"
android:label="@string/augment_reality_to_navigate_route_app_name">

</activity>
<!-- Insert your Google API key in order to use Geospatial tracking. -->
<meta-data
android:name="com.google.android.ar.API_KEY"
android:value="Insert Google API Key here"/>
android:exported="true"
android:label="@string/augment_reality_to_navigate_route_app_name" />
</application>

</manifest>
53 changes: 53 additions & 0 deletions samples/augment-reality-to-show-hidden-infrastructure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Augment reality to show hidden infrastructure

Visualize hidden infrastructure in its real-world location using augmented reality.

![Image of augment reality to show hidden infrastructure](augment-reality-to-show-hidden-infrastructure.png)

## Use case

You can use AR to "x-ray" the ground to see pipes, wiring, or other infrastructure that isn't otherwise visible. For example, you could use this feature to trace the flow of water through a building to help identify the source of a leak.

## How to use the sample

When you open the sample, you'll see a map centered on your current location. Tap on the map to draw pipes around your location. After drawing the pipes, input an elevation offset value to place the drawn infrastructure above or below ground. When you are ready, tap the camera button to view the infrastructure you drew in AR.

## How it works

1. Draw pipes on the map. See the "Create and edit geometries" sample to learn how to use the geometry editor for creating graphics.
2. Add a `WorldScaleSceneView` composable to the augmented reality screen, available in the [ArcGIS Maps SDK for Kotlin toolkit](https://github.com/Esri/arcgis-maps-sdk-kotlin-toolkit/tree/main/microapps/ArWorldScaleApp).
* The component is available both in `World tracking` and `Geospatial tracking` modes. Geospatial tracking uses street view data to calibrate augmented reality positioning and is available with an [ARCORE API key](https://developers.google.com/ar/develop/authorization?platform=android#api-key-android).
3. Pass a `SceneView` into the world scale scene view and set the base surface background grid to not be visible and the base surface opacity to 0.0.
4. Create an `ArcGISTiledElevationSource` and add it to the scene's base surface. Set the navigation constraint to unconstrained to allow going underground if needed.
5. Configure a graphics overlay and renderer for showing the drawn pipes. This sample uses a `SolidStrokeSymbolLayer` with a `MultilayerPolylineSymbol` to draw the pipes.

## Relevant API

* GeometryEditor
* GraphicsOverlay
* MultilayerPolylineSymbol
* SolidStrokeSymbolLayer
* Surface
* WorldScaleSceneView

## About the data

This sample uses Esri's [world elevation service](https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer) to ensure that the infrastructure you create is accurately placed beneath the ground.

Real-scale AR relies on having data in real-world locations near the user. It isn't practical to provide pre-made data like other ArcGIS Maps SDKs for Native Apps samples, so you must draw your own nearby sample "pipe infrastructure" prior to starting the AR experience.

## Additional information

You may notice that pipes you draw underground appear to float more than you would expect. That floating is a normal result of the parallax effect that looks unnatural because you're not used to being able to see underground/obscured objects. Compare the behavior of underground pipes with equivalent pipes drawn above the surface - the behavior is the same, but probably feels more natural above ground because you see similar scenes day-to-day (e.g. utility wires).

This sample requires a device that is compatible with [ARCore](https://developers.google.com/ar/devices).

Unlike other scene samples, there's no need for a basemap while navigating, because context is provided by the camera feed showing the real environment. The base surface's opacity is set to zero to prevent it from interfering with the AR experience.

**World-scale AR** is one of two main patterns for working with geographic information in augmented reality currently available in the [toolkit](https://github.com/Esri/arcgis-maps-sdk-kotlin-toolkit/tree/main).

Note that apps using ARCore must comply with ARCore's user privacy requirements. See [this page](https://developers.google.com/ar/develop/privacy-requirements) for more information.

## Tags

augmented reality, full-scale, infrastructure, lines, mixed reality, pipes, real-scale, underground, visualization, visualize, world-scale
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"category": "Augmented Reality",
"description": "Visualize hidden infrastructure in its real-world location using augmented reality.",
"formal_name": "AugmentRealityToShowHiddenInfrastructure",
"ignore": false,
"images": [
"augment-reality-to-show-hidden-infrastructure.png"
],
"keywords": [
"augmented reality",
"full-scale",
"infrastructure",
"lines",
"mixed reality",
"pipes",
"real-scale",
"underground",
"visualization",
"visualize",
"world-scale",
"GeometryEditor",
"GraphicsOverlay",
"MultilayerPolylineSymbol",
"SolidStrokeSymbolLayer",
"Surface",
"WorldScaleSceneView"
],
"language": "kotlin",
"redirect_from": "",
"relevant_apis": [
"GeometryEditor",
"GraphicsOverlay",
"MultilayerPolylineSymbol",
"SolidStrokeSymbolLayer",
"Surface",
"WorldScaleSceneView"
],
"snippets": [
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/components/AugmentedRealityViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/components/MapViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/MainActivity.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/navigation/AugmentRealityToShowHiddenInfrastructureRouteNavGraph.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/components/SharedRepository.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/screens/MapScreen.kt",
"src/main/java/com/esri/arcgismaps/sample/augmentrealitytoshowhiddeninfrastructure/screens/AugmentedRealityScreen.kt"
],
"title": "Augment reality to show hidden infrastructure"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
alias(libs.plugins.arcgismaps.android.library)
alias(libs.plugins.arcgismaps.android.library.compose)
alias(libs.plugins.arcgismaps.kotlin.sample)
alias(libs.plugins.gradle.secrets)
}

secrets {
// this file doesn't contain secrets, it just provides defaults which can be committed into git.
defaultPropertiesFileName = "secrets.defaults.properties"
}

android {
namespace = "com.esri.arcgismaps.sample.augmentrealitytoshowhiddeninfrastructure"
buildFeatures {
buildConfig = true
}
}

dependencies {
// Only module specific dependencies needed here
implementation(libs.androidx.navigation.compose)
implementation(libs.ar.core)
implementation(libs.arcgis.maps.kotlin.toolkit.ar)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<application>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/augment_reality_to_show_hidden_infrastructure_app_name" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* 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.augmentrealitytoshowhiddeninfrastructure

import android.Manifest
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.navigation.compose.rememberNavController
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.esri.arcgismaps.sample.augmentrealitytoshowhiddeninfrastructure.components.SharedRepository
import com.esri.arcgismaps.sample.augmentrealitytoshowhiddeninfrastructure.navigation.AugmentRealityToShowHiddenInfrastructureNavGraph
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme

class MainActivity : ComponentActivity() {

private var isLocationPermissionGranted = false

private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
isLocationPermissionGranted = true
} else {
Toast.makeText(this, "Location permission is required to run this sample!", Toast.LENGTH_SHORT).show()
}

setContent {
SampleAppTheme {
AugmentRealityToNavigateRoute(isLocationPermissionGranted)
}
}
}

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)
ArcGISEnvironment.applicationContext = applicationContext

requestLocationPermission()

SharedRepository.pipeInfoList.clear()
}

private fun requestLocationPermission() {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}

}

@Composable
fun AugmentRealityToNavigateRoute(isLocationPermissionGranted: Boolean) {
val navController = rememberNavController()
AugmentRealityToShowHiddenInfrastructureNavGraph(
navController = navController,
isLocationPermissionGranted = isLocationPermissionGranted,
)
}
Loading
Loading