Learn how to display geometries in different projections.

A geometry projection transforms the vertices of a geometric shape from one coordinate system (a spatial reference) to another. For example, you can project a geographic coordinate system such as World Geodetic System 1984 (wkid=4326) to a projected coordinate system such as World Sinusoidal (wkid=54008). A spatial reference has a unique integer identifier, or well-known id (wkid), defined by GIS standards organizations.
Each projection can maintain one of the following: area, angle, or direction. The projection you use is based on your application's requirements. For example, if you have data centered on the North Pole, the Web Mercator (wkid=3857) spatial reference is typically not used, as the pole features are not correctly represented by the projection; there is a large area distortion. Instead, you might use the North Pole Gnomonic (wkid=102034) spatial reference because it preserves the area around the North Pole.
In this tutorial, you will access a feature service that has a spatial reference of Web Mercator (wkid=3857). The layer's polygon features will be projected on-the-fly to the new spatial reference as they are displayed in the map view. You can select a different spatial reference from a list, and the features will be reprojected to that spatial reference. Tap a location on the map to display a point buffer and a callout that shows the point's x/y coordinates. As you change spatial references, observe the buffer and coordinates change for that same location on the earth. Additionally, for a given spatial reference, you can tap around the map to see how the buffer distorts at different locations.
For detailed information on projected coordinate systems, including well-known IDs (WKIDs), areas of use, and maximum/minimum latitude and longitude, download the Coordinate systems and transformation zip file and see the Projected Coordinate System tables PDF.
For general information on spatial references, see Spatial references in Reference topics.
For specific information on spatial references in ArcGIS Maps SDKs for Native Apps, see Spatial references.
Prerequisites
Before starting this tutorial, you need the following:
-
An ArcGIS Location Platform or ArcGIS Online account.
-
A development and deployment environment that meets the system requirements.
-
An IDE for Android development in Kotlin.
Develop or download
You have two options for completing this tutorial:
Option 1: Develop the code
Open an Android Studio project
-
Open the project you created by completing the Display a map tutorial.
-
Continue with the following instructions to display geometries in different projections.
-
Modify the old project for use in this new tutorial.
-
On your file system, delete the .idea folder, if present, at the top level of your project.
-
In the Android view, open app > res > values > strings.xml.
In the
<string name="app
element, change the text content to Display projected geometries._name" > strings.xmlUse dark colors for code blocks <resources> <string name="app_name">Display projected geometries</string> </resources>
-
In the Android view, open Gradle Scripts > settings.gradle.kts.
Change the value of
root
to "Display projected geometries".Project.name settings.gradle.ktsUse dark colors for code blocks dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") } } } rootProject.name = "Display projected geometries" include(":app")
-
Click File > Sync Project with Gradle files. Android Studio will recognize your changes and create a new .idea folder.
-
Add imports
Modify import statements to reference the packages and classes required for this tutorial.
@file:OptIn(ExperimentalMaterial3Api::class)
package com.example.app.screens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import com.arcgismaps.Color
import com.arcgismaps.data.ServiceFeatureTable
import com.arcgismaps.geometry.Envelope
import com.arcgismaps.geometry.GeodeticCurveType
import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.geometry.LinearUnit
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.layers.FeatureLayer
import com.arcgismaps.mapping.symbology.SimpleFillSymbol
import com.arcgismaps.mapping.symbology.SimpleFillSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
import com.arcgismaps.mapping.symbology.SimpleLineSymbolMarkerStyle
import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.arcgismaps.toolkit.geoviewcompose.MapView
import com.arcgismaps.toolkit.geoviewcompose.theme.CalloutDefaults
import com.arcgismaps.toolkit.geoviewcompose.MapViewScope
import com.example.app.R
import kotlinx.coroutines.launch
import kotlin.math.round
Add utilities
Delete some unneeded code inherited from the Display a map tutorial. Then add some utilities.
-
In
Main
, delete the body of theScreen.kt Main
composable and the entireScreen() create
function.Map() MainScreen.ktUse dark colors for code blocks @Composable fun MainScreen() { val map = remember { createMap() } Scaffold( topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) } ) { MapView( modifier = Modifier.fillMaxSize().padding(it), arcGISMap = map ) } } fun createMap(): ArcGISMap { return ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( latitude = 34.0270, longitude = -118.8050, scale = 72000.0 ) } }
-
At the top-level of the file, create two utilities that will be used throughout this tutorial: a function to log errors and an extension function to round
Double
values to five decimal places.Top-level in MainScreen.ktUse dark colors for code blocks private fun logError(error: Throwable) { Log.e("MainScreen.kt", error.message.toString(), error.cause) } private fun Double.roundToFiveDecimals(): Double { return round(this * 100000.0) / 100000.0 }
Create a graphic and a callout
A line graphic will show the full extent of the current map. A callout will indicate a point on the map and display the point's x/y coordinates.
-
Create a
Graphic
to represent the world extent using dashed-lines to denote the boundaries. Then create aGraphicsOverlay
and add the world boundary graphic to it. The envelope for the extent is expressed in WGS84, but the vertices will be projected on-the-fly to the spatial reference of the map.Top-level in MainScreen.ktUse dark colors for code blocks private val worldBoundaryGraphic = Graphic( symbol = SimpleFillSymbol( style = SimpleFillSymbolStyle.Solid, color = Color.transparent, outline = SimpleLineSymbol().apply { color = Color.fromRgba(r = 50, g = 50, b = 50, a = 192) width = 0.5f style = SimpleLineSymbolStyle.Dash } ), // The envelope covers the world extent of the map. geometry = Envelope( xMin = -180.0, yMin = -90.0, xMax = 180.0, yMax = 90.0, spatialReference = SpatialReference.wgs84() ) ) private val boundaryGraphicsOverlay by mutableStateOf( GraphicsOverlay(graphics = listOf(worldBoundaryGraphic)) )
-
Create a variable with mutable state named
callout
. Initialize it to (10.0, -30.0), specifying the WGS84 spatial reference.Point Top-level in MainScreen.ktUse dark colors for code blocks // Keep track of the state of the callout point. private var calloutPoint: Point by mutableStateOf( Point(x = -10.0, y = 30.0, SpatialReference.wgs84()) )
-
Define a composable function named
Callout
that adds the ArcGIS Maps SDK for Kotlin ToolkitContainer Callout
component. TheCallout
takes aContainer MapViewScope
parameter.Note that the callout will be visible when the app launches.
Top-level in MainScreen.ktUse dark colors for code blocks // Show a callout using the callout point for location and text. @Composable private fun CalloutContainer(mapViewScope: MapViewScope) { mapViewScope.Callout( location = calloutPoint, // Optional parameters to customize the callout appearance. shapes = CalloutDefaults.shapes(calloutContentPadding = PaddingValues(8.dp)) ) { Column { Text( text = "x = ${calloutPoint.x.roundToFiveDecimals()}\ny = ${calloutPoint.y.roundToFiveDecimals()}", style = MaterialTheme.typography.labelSmall ) } } }
Create a blank map and a feature layer
In this tutorial, you will create a map using just a spatial reference instead of using a Basemap
. A feature layer will be then be added to the map. The World Countries (Generalized) feature service used in this tutorial has been published with Web Mercator spatial reference. When displayed in the map view, features are projected on-the-fly to the spatial reference of the current map.
First, create a new map.
-
Create a mutable state
ArcGISMap
with the WGS84 spatial reference and a white background to hide the grid. Note that the map starts out as blank.Top-level in MainScreen.ktUse dark colors for code blocks private var map by mutableStateOf( ArcGISMap(SpatialReference.wgs84()).apply { backgroundColor = Color.white } )
-
Create a
ServiceFeatureTable
from the feature service. Then create aFeatureLayer
from that table.Top-level in MainScreen.ktUse dark colors for code blocks private val serviceFeatureTable = ServiceFeatureTable( uri = "https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/World_Countries_(Generalized)/FeatureServer/0" ) private val featureLayer = FeatureLayer.createWithFeatureTable(featureTable = serviceFeatureTable)
Add feature layer and project callout point
When the map's spatial reference is ready to access, the Map
invokes a callback, where you can add the feature layer to the map and project the callout point.
-
Create the callback
on
as a suspend function that takes the spatial reference. Then add the feature layer to the map's operational layers, and load the layer.Spatial Reference Changed() Top-level in MainScreen.ktUse dark colors for code blocks private suspend fun onSpatialReferenceChanged(newSpatialReference: SpatialReference) { map.operationalLayers.add(featureLayer) featureLayer.load().onFailure { error -> logError(error) } }
-
Project the callout point to the spatial reference.
Top-level in MainScreen.ktUse dark colors for code blocks private suspend fun onSpatialReferenceChanged(newSpatialReference: SpatialReference) { map.operationalLayers.add(featureLayer) featureLayer.load().onFailure { error -> logError(error) } // Project the callout point so it has the right spatial reference when used by the callout. calloutPoint = GeometryEngine.projectOrNull( geometry = calloutPoint, spatialReference = newSpatialReference ) ?: return logError(Exception("Callout point's projection is null.")) }
Enable user to select a spatial reference
The user will select a spatial reference by name from a drop-down menu to see the feature layer projected in a different spatial reference.
The projection's name is the same as the short name of the spatial reference to which it is projecting (the output spatial reference of the projection).
Your finished app will have two drop-down menus, one above the other. The upper menu displays the projection types, such as "maintain area" or "maintain length". The lower menu displays the projection names for that type.
-
Create an enum that contains a constant for each projection that a user can select. Each enum constant has two properties:
label
andwkid
.Top-level in MainScreen.ktUse dark colors for code blocks // List of various projection names along with their respective label and wkid. // The name of a projection is the spatial reference it projects to. private enum class ProjectionName(val label: String, val wkid: Int) { WGS84(label = "WGS84 (GCS) -> pseudo Plate Carrée (Cylindrical)", wkid = 4326), WorldCassini(label = "World Cassini (Cylindrical)", wkid = 54028), WorldEquidistant(label = "World Equidistant conic (Conic)", wkid = 54027), WorldStereographic(label = "World Stereographic (Azimuthal)", wkid = 54026), WorldEckertVI(label = "World Eckert VI (Pseudocylindrical)", wkid = 54010), WorldSinusoidal(label = "World Sinusoidal (Pseudocylindrical)", wkid = 54008), NorthPoleGnomonic(label = "North Pole Gnomonic (Azimuthal", wkid = 102034), WebMercator(label = "Web Mercator Auxiliary Sphere (Cylindrical)", wkid = 3857), WorldGallStereographic(label = "World Gall Stereographic (Cylindrical)", wkid = 54016), WorldWinkelTripel(label = "World Winkel Tripel (Pseudoazimuthal)", wkid = 54042), WorldFullerDymaxionMap(label = "World Fuller / Dymaxion map (Polyhedral)", wkid = 54050) }
-
Create a list of projection types. Each item in the list is a
Pair
in which the first element is the projection type and the second element is a list of projection names that have that type. Note that each projection name belongs to only one projection type.Top-level in MainScreen.ktUse dark colors for code blocks // List of various projection types long with their respective list of spatial references. private val projectionTypes = listOf( "Equidistant (maintain length)" to listOf( ProjectionName.WGS84, ProjectionName.WorldCassini, ProjectionName.WorldEquidistant ), "Conformal (maintain angles)" to listOf( ProjectionName.WorldStereographic ), "Equal-area (maintain area)" to listOf( ProjectionName.WorldEckertVI, ProjectionName.WorldSinusoidal ), "Gnomonic (distances)" to listOf( ProjectionName.NorthPoleGnomonic ), "Compromise (distort all)" to listOf( ProjectionName.WebMercator, ProjectionName.WorldGallStereographic, ProjectionName.WorldWinkelTripel, ProjectionName.WorldFullerDymaxionMap ) )
-
Create a composable named
Projection
that will contain the two drop-down menus.By Type Dropdown Menu Define state for the drop-down menus:
is
: Boolean for whether the projection type drop-down menu is currently expanded.Projection Type Expanded is
: Boolean for whether the projection name drop-down menu is currently expanded.Projection Name Expanded selected
: Index of the current selection in the projection type menu.Projection Type Index selected
: Index of the current selection in the projection name menu.Projection Name Index
Top-level in MainScreen.ktUse dark colors for code blocks @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ProjectionByTypeDropdownMenu(onProjectionSelected: (ProjectionName) -> Unit) { // Expanded boolean for the projection type drop-down menu. var isProjectionTypeExpanded by remember { mutableStateOf(false) } // Expanded boolean for the project name drop-down menu. var isProjectionNameExpanded by remember { mutableStateOf(false) } // The index of the current selection in project types menu. var selectedProjectionTypeIndex by remember { mutableIntStateOf(0) } // The index of the current selection in the project name menu. var selectedProjectionNameIndex by remember { mutableIntStateOf(0) } }
-
Inside the new
Projection
composable, create the upper drop-down menu for selecting the projection type. Add anBy Type Dropdown Menu Exposed
.Drop Down Menu Box In ProjectionByTypeDropdownMenu()Use dark colors for code blocks ExposedDropdownMenuBox(modifier = Modifier.fillMaxWidth(), expanded = isProjectionTypeExpanded, onExpandedChange = { isProjectionTypeExpanded = !isProjectionTypeExpanded }) { TextField( label = { Text("Select a projection type") }, value = projectionTypes[selectedProjectionTypeIndex].first, onValueChange = {}, readOnly = true, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isProjectionTypeExpanded) }, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable, true) ) ExposedDropdownMenu( expanded = isProjectionTypeExpanded, onDismissRequest = { isProjectionTypeExpanded = false } ) { projectionTypes.forEachIndexed { index, selectedOption -> DropdownMenuItem( text = { Text(text = selectedOption.first) }, onClick = { selectedProjectionTypeIndex = index isProjectionTypeExpanded = false selectedProjectionNameIndex = 0 onProjectionSelected(selectedOption.second[0]) } ) } } }
-
Create the lower drop-down menu for selecting projections of a selected type. Add an
Exposed
.Drop Down Menu Box In ProjectionByTypeDropdownMenu()Use dark colors for code blocks ExposedDropdownMenuBox(expanded = isProjectionNameExpanded, onExpandedChange = { isProjectionNameExpanded = !isProjectionNameExpanded }) { TextField( textStyle = TextStyle(fontSize = TextUnit(14f, TextUnitType.Sp)), value = projectionTypes[selectedProjectionTypeIndex].second[selectedProjectionNameIndex].label, label = { Text( text = "Select spacial reference using projection type", fontSize = TextUnit(10f, TextUnitType.Sp) ) }, onValueChange = {}, readOnly = true, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isProjectionNameExpanded) }, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable, true) ) ExposedDropdownMenu( expanded = isProjectionNameExpanded, onDismissRequest = { isProjectionNameExpanded = false } ) { projectionTypes[selectedProjectionTypeIndex].second.forEachIndexed { index, selectedOption -> DropdownMenuItem( text = { Text(text = selectedOption.label) }, onClick = { selectedProjectionNameIndex = index isProjectionNameExpanded = false onProjectionSelected(selectedOption) } ) } } }
Change the spatial reference
To display the feature layer in a new spatial reference, you create a new SpatialReference
using the selected projection name, and then create a new map with that spatial reference.
-
Create a suspend function named
change
that takes theSpatial Reference() Projection
selected by the user. Create a new spatial reference, using theName wkid
of the selected option. If the new spatial reference is the same as the current one, the user has selected the same projection name in the drop-down menu. Return without creating a new map.Top-level in MainScreen.ktUse dark colors for code blocks private suspend fun changeSpatialReference(selectedOption: ProjectionName) { val newSpatialReference = SpatialReference(selectedOption.wkid) // If user clicked on the same spatial reference then, don't create a new map. if (newSpatialReference == map.spatialReference) return }
-
Remove the feature layer from the operational layers of the current map so the layer is no longer owned by that map. Then create a new
ArcGISMap
using the new spatial reference, and load the map.Top-level in MainScreen.ktUse dark colors for code blocks private suspend fun changeSpatialReference(selectedOption: ProjectionName) { val newSpatialReference = SpatialReference(selectedOption.wkid) // If user clicked on the same spatial reference then, don't create a new map. if (newSpatialReference == map.spatialReference) return // Remove the feature layer on the old map so the layer is free to be used on the new map. map.operationalLayers.remove(featureLayer) map = ArcGISMap(newSpatialReference).apply { backgroundColor = Color.white } map.load().onFailure { error -> logError(error) } }
When the map's spatial reference is ready to be accessed, the on
you created in an earlier step will be automatically invoked.
Create buffer graphics
You will need graphics to display a geodetic buffer and its point.
-
Create graphics to display a point and a geodetic buffer around the point. Then create a
GraphicsOverlay
and add the graphics to it.Top-level in MainScreen.ktUse dark colors for code blocks private var bufferPointGraphic = Graphic( geometry = null, symbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.Circle, color = Color.red, size = 5f ).apply { outline = SimpleLineSymbol( style = SimpleLineSymbolStyle.Dot, color = Color.white, width = 0.5f, markerStyle = SimpleLineSymbolMarkerStyle.None ) } ) var bufferGraphic = Graphic( geometry = null, symbol = SimpleFillSymbol( style = SimpleFillSymbolStyle.Solid, color = Color.fromRgba(r = 150, g = 130, b = 220, a = 216), outline = SimpleLineSymbol( style = SimpleLineSymbolStyle.Dash, color = Color.fromRgba(r = 255, g = 255, b = 255, a = 255), width = 0.5f, markerStyle = SimpleLineSymbolMarkerStyle.None ) ) ) private val bufferGraphicsOverlay by mutableStateOf( GraphicsOverlay(graphics = listOf(bufferPointGraphic, bufferGraphic)) )
Visualize spatial distortion effects
When you tap on the map, the buffer graphics display at the tapped point, and the Callout
moves to that location. As you tap around the map, the buffer becomes distorted in size and/or shape. The distortions vary, depending on the current spatial reference.
The screen tap results in a single-tap confirmed event, whose map
property returns the tapped point in the spatial reference of the map. Use the map point to create a point buffer and trigger the callout.
-
Create a function that takes a single-tap confirmed event.
Get the map point from the event and call
GeometryEngine.bufferGeodeticOrNull()
with the map point, a distance of 1000 kilometers, and curve type of geodesic. Next, display the graphics by assigning the buffer as the geometry of the buffer graphic, and the map point as the geometry of the buffer point graphic.Top-level in MainScreen.ktUse dark colors for code blocks private fun createBuffer(singleTapConfirmedEvent: SingleTapConfirmedEvent) { val mapPoint = singleTapConfirmedEvent.mapPoint ?: return logError(Exception("Tap event has no map point.")) val buffer = GeometryEngine.bufferGeodeticOrNull( geometry = mapPoint, distance = 1000.0, distanceUnit = LinearUnit.kilometers, maxDeviation = Double.NaN, curveType = GeodeticCurveType.Geodesic ) ?: return logError(Exception("Failed to create a buffer from the map point")) bufferGraphic.geometry = buffer bufferPointGraphic.geometry = mapPoint }
-
Assign the map point to
callout
. In response, thePoint Callout
will display at the map point, showing the point's x/y coordinates in the current spatial reference.Top-level in MainScreen.ktUse dark colors for code blocks bufferGraphic.geometry = buffer bufferPointGraphic.geometry = mapPoint calloutPoint = mapPoint
Connect your functionality to the map view
You will now connect your functionality to the MapView
composable. Pass additional parameters to the Map
so it can display UI for your top-level functions and properties.
-
In the
Main
composable, create a local variable namedScreen coroutine
.Scope In MainScreen() composableUse dark colors for code blocks @Composable fun MainScreen() { val coroutineScope = rememberCoroutineScope() }
-
Add a
Scaffold
with the following code, which adds aColumn
and theMapView
.Note that in the
Column
's content, you call theProjection
composable, passing in a lambda. The lambda takes aBy Type Dropdown Menu() dropdown
parameter and callsSelection : Projection Name change
with it.Spatial Reference() In MainScreen() composableUse dark colors for code blocks @Composable fun MainScreen() { val coroutineScope = rememberCoroutineScope() Scaffold(topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.app_name)) }) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding), verticalArrangement = Arrangement.spacedBy(12.dp) ) { ProjectionByTypeDropdownMenu( onProjectionSelected = { dropdownSelection -> coroutineScope.launch { changeSpatialReference(dropdownSelection) } } ) MapView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISMap = map, ) { } } } }
-
In the
MapView
call, pass the following additional parameters:- A list of the two graphics overlays.
- A lambda that invokes your
on
callback, if the new spatial reference is not null.Spatial Reference Changed() - A function reference to your
create
callback that handles the single tap confirmed event.Buffer()
In MainScreen() composableUse dark colors for code blocks MapView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISMap = map, graphicsOverlays = listOf(boundaryGraphicsOverlay, bufferGraphicsOverlay), onSpatialReferenceChanged = { newSpatialReference -> newSpatialReference?.let { coroutineScope.launch { onSpatialReferenceChanged(newSpatialReference) } } }, onSingleTapConfirmed = ::createBuffer ) { }
-
In
MapView
's content lambda, callCallout
, passing inContainer this
, which is a reference to theMap
.View Scope In MainScreen() composableUse dark colors for code blocks onSingleTapConfirmed = ::createBuffer ) { CalloutContainer(this) }
Click Run > Run > app to run the app.
You should see an orange feature layer with no basemap. The initial projection type is Maintain length and the initial spatial reference is WGS84. You should also see a callout pointing to the location at (x = -10, y = 30).
You can interact with this app in two ways to learn about spatial references:
-
View coordinates of the same point in different projections: When the app launches, examine the callout and the point coordinates it displays. Then select other projections from the drop-down menus. You should see that same point with different coordinates for each spatial reference.
-
View spatial distortions of the buffer: In any spatial reference, tap on the map. The point buffer should display on the map, with the tapped point's x/y coordinates showing in a callout. Tap around the map and watch how the buffer's size and shape get distorted. Try other projections and observe how the distortions differ from one spatial reference to another.
Alternatively, you can download the tutorial solution, as follows.
Option 2: Download the solution
-
Click the Download solution link in the right-hand side of this page.
-
Unzip the file to a location on your machine.
-
Run Android Studio.
-
Go to File > Open.... Navigate to the solution folder and click Open.
On Windows: If you are in the Welcome to Android Studio dialog, click Open and navigate to the solution folder. Then click Open.
Since the downloaded solution does not contain authentication credentials, you must first set up authentication to create credentials, and then add the developer credentials to the solution.
Set up authentication
To access the secure ArcGIS location services used in this tutorial, you must implement API key authentication or user authentication using an ArcGIS Location Platform or an ArcGIS Online account.
You can implement API key authentication or user authentication in this tutorial. Compare the differences below:
API key authentication
- Users are not required to sign in.
- Requires creating an API key credential with the correct privileges.
- API keys are long-lived access tokens.
- Service usage is billed to the API key owner/developer.
- Simplest authentication method to implement.
- Recommended approach for new ArcGIS developers.
Learn more in API key authentication.
User authentication
- Users are required to sign in with an ArcGIS account.
- User accounts must have privilege to access the ArcGIS services used in application.
- Requires creating OAuth credentials.
- Application uses a redirect URL and client ID.
- Service usage is billed to the organization of the user signed into the application.
Learn more in User authentication.
Create a new API key access token with privileges to access the secure resources used in this tutorial.
-
Complete the Create an API key tutorial and create an API key with the following privilege(s):
- Privileges
- Location services > Basemaps
- Privileges
-
Copy and paste the API key access token into a safe location. It will be used in a later step.
Set developer credentials in the solution
To allow your app users to access ArcGIS location services, use the developer credentials that you created in the Set up authentication step to authenticate requests for resources.
-
In the Android view of Android Studio, open app > kotlin+java > com.example.app > MainActivity. Set the
Authentication
toMode .
.API _KEY MainActivity.ktUse dark colors for code blocks class MainActivity : ComponentActivity() { private enum class AuthenticationMode { API_KEY, USER_AUTH } private val authenticationMode = AuthenticationMode.API_KEY
-
Set the
api
property with your API key access token.Key MainActivity.ktUse dark colors for code blocks override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) when (authenticationMode) { AuthenticationMode.API_KEY -> { ArcGISEnvironment.apiKey = ApiKey.create("YOUR_ACCESS_TOKEN") }
Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Run the app
Click Run > Run > app to run the app.
You should see an orange feature layer with no basemap. The initial projection type is Maintain length and the initial spatial reference is WGS84. You should also see a callout pointing to the location at (x = -10, y = 30).
You can interact with this app in two ways to learn about spatial references:
-
View coordinates of the same point in different projections: When the app launches, examine the callout and the point coordinates it displays. Then select other projections from the drop-down menus. You should see that same point with different coordinates for each spatial reference.
-
View spatial distortions of the buffer: In any spatial reference, tap on the map. The point buffer should display on the map, with the tapped point's x/y coordinates showing in a callout. Tap around the map and watch how the buffer's size and shape get distorted. Try other projections and observe how the distortions differ from one spatial reference to another.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: