Calculate a geodesic path between two points and measure its distance.

Use case
A geodesic distance provides an accurate, real-world distance between two points. Visualizing flight paths between cities is a common example of a geodesic operation since the flight path between two airports takes into account the curvature of the earth, rather than following the planar path between those points, which appears as a straight line on a projected map.
How to use the sample
Tap anywhere on the map. A line graphic will display the geodesic line between the two points. In addition, text that indicates the geodesic distance between the two points will be updated. Tap elsewhere and a new line will be created.
How it works
- Create a
Pointand display it as aGraphic. - Obtain a new point when a tap occurs on the
MapViewand add this point as a graphic. - Create a
Polylinefrom the two points. - Execute
GeometryEngine.densifyGeodetic(...)by passing in the created polyline then create a graphic from the returnedGeometry. - Execute
GeometryEngine.distanceGeodeticOrNull(...)by passing in the two points and display the returned distance on the screen.
Relevant API
- GeometryEngine.densifyGeodetic
- GeometryEngine.distanceGeodeticOrNull
About the data
The Imagery basemap provides the global context for the displayed geodesic line.
Tags
densify, distance, geodesic, geodetic
Sample Code
/* * Copyright 2023 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.showgeodesicpathbetweentwopoints
import android.os.Bundleimport android.util.Logimport com.esri.arcgismaps.sample.sampleslib.EdgeToEdgeCompatActivityimport androidx.databinding.DataBindingUtilimport androidx.lifecycle.lifecycleScopeimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.arcgismaps.Colorimport com.arcgismaps.geometry.AngularUnitimport com.arcgismaps.geometry.GeodeticCurveTypeimport com.arcgismaps.geometry.GeometryEngineimport com.arcgismaps.geometry.LinearUnit.Companion.kilometersimport com.arcgismaps.geometry.Pointimport com.arcgismaps.geometry.Polylineimport com.arcgismaps.geometry.SpatialReferenceimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.symbology.SimpleLineSymbolimport com.arcgismaps.mapping.symbology.SimpleLineSymbolStyleimport com.arcgismaps.mapping.symbology.SimpleMarkerSymbolimport com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyleimport com.arcgismaps.mapping.view.Graphicimport com.arcgismaps.mapping.view.GraphicsOverlayimport com.esri.arcgismaps.sample.showgeodesicpathbetweentwopoints.databinding.ShowGeodesicPathBetweenTwoPointsActivityMainBindingimport com.google.android.material.snackbar.Snackbarimport kotlinx.coroutines.launchimport kotlin.math.roundToInt
class MainActivity : EdgeToEdgeCompatActivity() {
// set up data binding for the activity private val activityMainBinding: ShowGeodesicPathBetweenTwoPointsActivityMainBinding by lazy { DataBindingUtil.setContentView(this, R.layout.show_geodesic_path_between_two_points_activity_main) }
// set up data binding for the mapView private val mapView by lazy { activityMainBinding.mapView }
// shows the distance information as a textview private val infoTextView by lazy { activityMainBinding.infoTextView }
// a red marker symbol for the location points private val locationMarkerSymbol by lazy { SimpleMarkerSymbol( SimpleMarkerSymbolStyle.Circle, Color.red, 10f ) }
// the marker graphic for the starting location private val startingLocationMarkerGraphic by lazy { Graphic(startingPoint, locationMarkerSymbol) }
// marker graphic for the destination private val endLocationMarkerGraphic by lazy { Graphic(symbol = locationMarkerSymbol) }
// the geodesic path represented as line graphic private val geodesicPathGraphic by lazy { val lineSymbol = SimpleLineSymbol(SimpleLineSymbolStyle.Dash, Color.red, 5f) Graphic(symbol = lineSymbol) }
// starting location for the distance calculation private val startingPoint = Point(-73.7781, 40.6413, SpatialReference.wgs84())
// creates a graphic overlay to draw all graphics private val graphicsOverlay = GraphicsOverlay()
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) lifecycle.addObserver(mapView)
// create and add a map with a navigation night basemap style val map = ArcGISMap(BasemapStyle.ArcGISImageryStandard).apply { initialViewpoint = Viewpoint(Point(34.77, -10.24), 20e7) } // configure mapView assignments mapView.apply { this.map = map // add the graphics overlay to the mapview graphicsOverlays.add(graphicsOverlay) }
// add all our marker graphics to the graphics overlay graphicsOverlay.graphics.addAll( listOf(startingLocationMarkerGraphic, endLocationMarkerGraphic, geodesicPathGraphic) )
lifecycleScope.launch { // check if the map has loaded successfully map.load().onSuccess { // capture and collect when the user taps on the screen mapView.onSingleTapConfirmed.collect { event -> event.mapPoint?.let { point -> displayGeodesicPath(point) } } }.onFailure { // if map load failed, show the error showError("Error Loading Map") } } }
/** * Displays the destination location marker at the tapped location * and draws a geodesic path curve using GeometryEngine.densifyGeodetic * and computes the distance using GeometryEngine.lengthGeodetic */ private fun displayGeodesicPath(point: Point) { // project the tapped point into the same spatial reference as source point val destinationPoint = GeometryEngine.projectOrNull(point, SpatialReference.wgs84()) ?: return showError("Error converting point projection")
// check if the destination point is within the map bounds // isEmpty returns true if out of bounds if (!destinationPoint.isEmpty) { // update the end location marker location on map endLocationMarkerGraphic.geometry = destinationPoint // create a polyline between source and destination points val polyline = Polyline(listOf(startingPoint, destinationPoint)) // generate a geodesic curve using the polyline val pathGeometry = GeometryEngine.densifyGeodeticOrNull( geometry = polyline, maxSegmentLength = 1.0, lengthUnit = kilometers, curveType = GeodeticCurveType.Geodesic // only compute the distance if the returned curved path geometry is not null ) ?: return showError("Error creating a densified geometry") // update the path graphic geodesicPathGraphic.geometry = pathGeometry // compute the path distance in kilometers val distance = GeometryEngine.distanceGeodeticOrNull( startingPoint, destinationPoint, distanceUnit = kilometers, azimuthUnit = AngularUnit.degrees, curveType = GeodeticCurveType.Geodesic ) // display the distance result infoTextView.text = getString(R.string.distance_info_text, distance?.distance?.roundToInt()) } }
private fun showError(message: String) { Log.e(localClassName, message) Snackbar.make(mapView, message, Snackbar.LENGTH_SHORT).show() }}