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 Point and display it as a Graphic.
Obtain a new point when a tap occurs on the MapView and add this point as a graphic.
Create a Polyline from the two points.
Execute GeometryEngine.densifyGeodetic(...) by passing in the created polyline then create a graphic from the returned Geometry.
Execute GeometryEngine.lengthGeodetic(...) by passing in the two points and display the returned length on the screen.
Relevant API
GeometryEngine.densifyGeodetic
GeometryEngine.lengthGeodetic
About the data
The Imagery basemap provides the global context for the displayed geodesic line.
Tags
densify, distance, geodesic, geodetic
Sample Code
MainActivity.kt
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* 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 androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.Color
import com.arcgismaps.geometry.LinearUnit
import com.arcgismaps.geometry.LinearUnitId
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.geometry.Polyline
import com.arcgismaps.geometry.GeodeticCurveType
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.ActivityMainBinding
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
classMainActivity : AppCompatActivity() {
// set up data binding for the activityprivateval activityMainBinding: ActivityMainBinding by lazy {
DataBindingUtil.setContentView(this, R.layout.activity_main)
}
// set up data binding for the mapViewprivateval mapView by lazy {
activityMainBinding.mapView
}
// shows the distance information as a textviewprivateval infoTextView by lazy {
activityMainBinding.infoTextView
}
// a red marker symbol for the location pointsprivateval locationMarkerSymbol by lazy {
SimpleMarkerSymbol(
SimpleMarkerSymbolStyle.Circle,
Color.red,
10f )
}
// the marker graphic for the starting locationprivateval startingLocationMarkerGraphic by lazy {
Graphic(startingPoint, locationMarkerSymbol)
}
// marker graphic for the destinationprivateval endLocationMarkerGraphic by lazy {
Graphic(symbol = locationMarkerSymbol)
}
// the geodesic path represented as line graphicprivateval geodesicPathGraphic by lazy {
val lineSymbol = SimpleLineSymbol(SimpleLineSymbolStyle.Dash, Color.red, 5f)
Graphic(symbol = lineSymbol)
}
// the unit of distance measurement in kilometersprivateval unitsOfMeasurement = LinearUnit(LinearUnitId.Kilometers)
// starting location for the distance calculationprivateval startingPoint = Point(-73.7781, 40.6413, SpatialReference.wgs84())
// creates a graphic overlay to draw all graphicsprivateval graphicsOverlay = GraphicsOverlay()
overridefunonCreate(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.API_KEY)
lifecycle.addObserver(mapView)
// create and add a map with a navigation night basemap styleval 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
*/privatefundisplayGeodesicPath(point: Point) {
// project the tapped point into the same spatial reference as source pointval 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 boundsif (!destinationPoint.isEmpty) {
// update the end location marker location on map endLocationMarkerGraphic.geometry = destinationPoint
// create a polyline between source and destination pointsval polyline = Polyline(listOf(startingPoint, destinationPoint))
// generate a geodesic curve using the polylineval pathGeometry = GeometryEngine.densifyGeodeticOrNull(
geometry = polyline,
maxSegmentLength = 1.0,
lengthUnit = unitsOfMeasurement,
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 kilometersval distance = GeometryEngine.lengthGeodetic(
geometry = pathGeometry,
lengthUnit = unitsOfMeasurement,
curveType = GeodeticCurveType.Geodesic
)
// display the distance result infoTextView.text = getString(R.string.distance_info_text, distance.roundToInt())
}
}
privatefunshowError(message: String) {
Log.e(localClassName, message)
Snackbar.make(mapView, message, Snackbar.LENGTH_SHORT).show()
}
}