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.Bundle
import android.util.Log
import com.esri.arcgismaps.sample.sampleslib.EdgeToEdgeCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.Color
import com.arcgismaps.geometry.AngularUnit
import com.arcgismaps.geometry.GeodeticCurveType
import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.geometry.LinearUnit.Companion.kilometers
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.Polyline
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
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.esri.arcgismaps.sample.showgeodesicpathbetweentwopoints.databinding.ShowGeodesicPathBetweenTwoPointsActivityMainBinding
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import 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()
    }
}