Find the closest vertex and coordinate of a geometry to a point.

Use case
Determine the shortest distance between a location and the boundary of an area. For example, developers can snap imprecise user clicks to a geometry if the click is within a certain distance of the geometry.
How to use the sample
Click anywhere on the map. A magenta cross will show at that location. A blue circle will show the polygon’s nearest vertex to the point that was clicked. A red diamond will appear at the coordinate on the geometry that is nearest to the point that was clicked. If clicked inside the geometry, the red and magenta markers will overlap. The information box showing distance between the clicked point and the nearest vertex/coordinate will be updated with every new location clicked.
How it works
- Get a
Geometryand aPointto check the nearest vertex against. - Call
GeometryEngine.nearestVertex(inputGeometry, point). - Use the returned
ProximityResultto get thePointrepresenting the polygon vertex, and to determine the distance between that vertex and the clicked point. - Call
GeometryEngine.nearestCoordinate(inputGeometry, point). - Use the returned
ProximityResultto get thePointrepresenting the coordinate on the polygon, and to determine the distance between that coordinate and the clicked point.
Relevant API
- GeometryEngine
- ProximityResult
Additional information
The value of ProximityResult.distance is planar (Euclidean) distance. Planar distances are only accurate for geometries that have a defined projected coordinate system, which maintain the desired level of accuracy. The example polygon in this sample is defined in California State Plane Coordinate System - Zone 5 (WKID 2229), which maintains accuracy near Southern California. Accuracy declines outside the state plane zone.
This sample uses the GeoView-Compose Toolkit module to be able to implement a composable MapView.
Tags
analysis, coordinate, geometry, geoview-compose, nearest, proximity, toolkit, vertex
Sample Code
/* Copyright 2022 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.findnearestvertex
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.findnearestvertex.screens.FindNearestVertexScreen
class MainActivity : ComponentActivity() {
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)
enableEdgeToEdge() setContent { SampleAppTheme { FindNearestVertexApp() } } }
@Composable private fun FindNearestVertexApp() { Surface(color = MaterialTheme.colorScheme.background) { FindNearestVertexScreen( sampleName = getString(R.string.find_nearest_vertex_app_name) ) } }}/* 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.findnearestvertex.components
import android.app.Applicationimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.Colorimport com.arcgismaps.geometry.GeometryEngineimport com.arcgismaps.geometry.Pointimport com.arcgismaps.geometry.Polygonimport com.arcgismaps.geometry.PolygonBuilderimport com.arcgismaps.geometry.SpatialReferenceimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.PortalItemimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.layers.FeatureLayerimport com.arcgismaps.mapping.symbology.SimpleFillSymbolimport com.arcgismaps.mapping.symbology.SimpleFillSymbolStyleimport 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.arcgismaps.mapping.view.SingleTapConfirmedEventimport com.arcgismaps.portal.Portalimport com.arcgismaps.toolkit.geoviewcompose.MapViewProxyimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.launch
class FindNearestVertexViewModel(application: Application) : AndroidViewModel(application) { // California zone 5 (ftUS) state plane coordinate system. private val statePlaneCaliforniaZone5SpatialReference = SpatialReference(2229)
// create a portal private val portal = Portal("https://arcgisruntime.maps.arcgis.com")
// get the USA States Generalized Boundaries layer from the portal using its ID private val portalItem = PortalItem( portal = portal, itemId = "8c2d6d7df8fa4142b0a1211c8dd66903" )
// create a feature layer from the portal item private val usStatesGeneralizedLayer = FeatureLayer.createWithItemAndLayerId( item = portalItem, layerId = 0 )
// create a MapViewProxy to interact with the MapView val mapViewProxy = MapViewProxy()
// create graphic symbol for the tapped location private val tappedLocationGraphic = Graphic( symbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.X, color = Color.magenta, size = 15f ) )
// create graphic symbol for the nearest coordinate private val nearestCoordinateGraphic = Graphic( symbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.Diamond, color = Color.red, size = 10f ) )
// create graphic symbol for the nearest vertex private val nearestVertexGraphic = Graphic( symbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.Circle, color = Color.blue, size = 15f ) )
// create a polygon geometry private val polygon = PolygonBuilder(statePlaneCaliforniaZone5SpatialReference) { addPoint(Point(x = 6627416.41469281, y = 1804532.53233782)) addPoint(Point(x = 6669147.89779046, y = 2479145.16609522)) addPoint(Point(x = 7265673.02678292, y = 2484254.50442408)) addPoint(Point(x = 7676192.55880379, y = 2001458.66365744)) }.toGeometry()
// set up the outline and fill color of the polygon private val polygonOutlineSymbol = SimpleLineSymbol( style = SimpleLineSymbolStyle.Solid, color = Color.green, width = 2f )
private val polygonFillSymbol = SimpleFillSymbol( style = SimpleFillSymbolStyle.ForwardDiagonal, color = Color.green, outline = polygonOutlineSymbol )
// create a polygon graphic private val polygonGraphic = Graphic( geometry = polygon, symbol = polygonFillSymbol )
// create a graphics overlay to show the polygon, tapped location, and nearest vertex/coordinate val graphicsOverlay = GraphicsOverlay( listOf( polygonGraphic, tappedLocationGraphic, nearestCoordinateGraphic, nearestVertexGraphic ) )
val arcGISMap by mutableStateOf( ArcGISMap(statePlaneCaliforniaZone5SpatialReference).apply { // set the map viewpoint to the polygon initialViewpoint = Viewpoint( center = polygon.extent.center, scale = 8e6 ) } )
// state flow of nearest vertex distance and nearest coordinate distance for presentation in UI private val _distanceInformationFlow = MutableStateFlow<DistanceInformation?>(null) val distanceInformationFlow = _distanceInformationFlow.asStateFlow()
// create a message dialog view model for handling error messages val messageDialogVM = MessageDialogViewModel()
init { viewModelScope.launch { arcGISMap.load().onSuccess { // add the USA States Generalized Boundaries feature layer to the map's operational layers arcGISMap.operationalLayers.add(usStatesGeneralizedLayer) }.onFailure { error -> messageDialogVM.showMessageDialog( title = "Failed to load map", description = error.message ?: "No description" ) } } }
/** * Handle a tap from the user and call [findNearestVertex]. */ fun onMapViewTapped(event: SingleTapConfirmedEvent) { event.mapPoint?.let { point -> findNearestVertex(tapPoint = point, polygon = polygon) } }
/** * Finds the nearest vertex to the [tapPoint] on the [polygon]. */ private fun findNearestVertex(tapPoint: Point, polygon: Polygon) { // show where the user clicked tappedLocationGraphic.geometry = tapPoint // use the geometry engine to get the nearest vertex val nearestVertexResult = GeometryEngine.nearestVertex(geometry = polygon, point = tapPoint) // set the nearest vertex graphic's geometry to the nearest vertex nearestVertexGraphic.geometry = nearestVertexResult?.coordinate // use the geometry engine to get the nearest coordinate val nearestCoordinateResult = GeometryEngine.nearestCoordinate(geometry = polygon, point = tapPoint) // set the nearest coordinate graphic's geometry to the nearest coordinate nearestCoordinateGraphic.geometry = nearestCoordinateResult?.coordinate // calculate the distances to the nearest vertex and nearest coordinate then convert to kilometers val vertexDistance = nearestVertexResult?.distance?.metersToKilometers()?.toInt() val coordinateDistance = nearestCoordinateResult?.distance?.metersToKilometers()?.toInt() if (vertexDistance != null && coordinateDistance != null) { // update the distance information so the UI can display it _distanceInformationFlow.value = DistanceInformation( vertexDistance = vertexDistance, coordinateDistance = coordinateDistance ) } }
/** * Converts a quantity in meters to kilometers. */ private fun Double.metersToKilometers(): Double { return this / 1000.0 }
/** * Data class representing vertex distance and coordinate distance. */ data class DistanceInformation(val vertexDistance: Int, val coordinateDistance: Int)}/* 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.findnearestvertex.screens
import androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.text.style.TextAlignimport androidx.lifecycle.compose.collectAsStateWithLifecycleimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.esri.arcgismaps.sample.findnearestvertex.Rimport com.esri.arcgismaps.sample.findnearestvertex.components.FindNearestVertexViewModelimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
/** * Main screen layout for the sample app. */@Composablefun FindNearestVertexScreen(sampleName: String) { val mapViewModel: FindNearestVertexViewModel = viewModel() val distanceInfo by mapViewModel.distanceInformationFlow.collectAsStateWithLifecycle() val displayText = distanceInfo?.let { (vertexDistance, coordinateDistance) -> stringResource(R.string.nearest_vertex_and_coordinate, vertexDistance, coordinateDistance) } ?: stringResource(R.string.tap_on_the_map_to_find_vertex) Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { Column( modifier = Modifier .fillMaxSize() .padding(it), ) { MapView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISMap = mapViewModel.arcGISMap, mapViewProxy = mapViewModel.mapViewProxy, graphicsOverlays = listOf(mapViewModel.graphicsOverlay), onSingleTapConfirmed = mapViewModel::onMapViewTapped ) Text( modifier = Modifier.fillMaxWidth(), text = displayText, textAlign = TextAlign.Center, minLines = 2 ) }
mapViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } )}