Nearest vertex

View on GitHub
Sample viewer app

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

Nearest vertex

Use case

Determine the shortest distance between a location and the boundary of an area. For example, developers can snap imprecise user taps to a geometry if the tap is within a certain distance of the geometry.

How to use the sample

Tap anywhere on the map. An orange cross will show at that location. A blue circle will show the polygon's nearest vertex to the point that was tapped. A red diamond will appear at the coordinate on the geometry that is nearest to the point that was tapped. If tapped inside the geometry, the red and orange markers will overlap. Tap again to dismiss the callout. The information callout showing distance between the tapped point and the nearest vertex/coordinate will be updated with every new location tapped.

How it works

  1. Get an AGSGeometry and an AGSPoint to check the nearest vertex against.
  2. Call class AGSGeometryEngine.nearestVertex(in:to:).
  3. Use the returned AGSProximityResult to get the AGSPoint representing the polygon vertex, and to determine the distance between that vertex and the tapped point.
  4. Call class AGSGeometryEngine.nearestCoordinate(in:to:).
  5. Use the returned AGSProximityResult to get the AGSPoint representing the coordinate on the polygon, and to determine the distance between that coordinate and the tapped point.

Relevant API

  • AGSGeometry
  • AGSGeometryEngine
  • AGSProximityResult

Tags

analysis, coordinate, geometry, nearest, proximity, vertex

Sample Code

NearestVertexViewController.swift
                                                                                                                                                      
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
// Copyright 2020 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.

import UIKit
import ArcGIS

class NearestVertexViewController: UIViewController {
    /// The graphics and symbology for the tapped point, the nearest vertex and the nearest coordinate.
    let tappedLocationSymbol = AGSSimpleMarkerSymbol(style: .X, color: .orange, size: 15)
    let nearestCoordinateSymbol = AGSSimpleMarkerSymbol(style: .diamond, color: .red, size: 10)
    let nearestVertexSymbol = AGSSimpleMarkerSymbol(style: .circle, color: .blue, size: 15)

    /// The symbology for the example polygon area.
    let polygonFillSymbol = AGSSimpleFillSymbol(
        style: .forwardDiagonal,
        color: .green,
        outline: AGSSimpleLineSymbol(style: .solid, color: .green, width: 2)
    )

    /// The graphics overlay for the polygon and points.
    let graphicsOverlay = AGSGraphicsOverlay()

    /// A formatter to convert units for distance.
    let distanceFormatter: MeasurementFormatter = {
        let formatter = MeasurementFormatter()
        formatter.numberFormatter.maximumFractionDigits = 1
        formatter.numberFormatter.minimumFractionDigits = 1
        return formatter
    }()

    /// The point collection that defines the polygon.
    let createdPolygon: AGSPolygon = {
        let polygonBuilder = AGSPolygonBuilder(spatialReference: .webMercator())
        polygonBuilder.addPointWith(x: -5991501.677830, y: 5599295.131468)
        polygonBuilder.addPointWith(x: -6928550.398185, y: 2087936.739807)
        polygonBuilder.addPointWith(x: -3149463.800709, y: 1840803.011362)
        polygonBuilder.addPointWith(x: -1563689.043184, y: 3714900.452072)
        polygonBuilder.addPointWith(x: -3180355.516764, y: 5619889.608838)
        return polygonBuilder.toGeometry()
    }()

    /// The graphic for the polygon, tapped point, nearest coordinate point and nearest vertex point.
    lazy var polygonGraphic = AGSGraphic(geometry: createdPolygon, symbol: polygonFillSymbol)
    lazy var tappedLocationGraphic = AGSGraphic(geometry: nil, symbol: tappedLocationSymbol)
    lazy var nearestCoordinateGraphic = AGSGraphic(geometry: nil, symbol: nearestCoordinateSymbol)
    lazy var nearestVertexGraphic = AGSGraphic(geometry: nil, symbol: nearestVertexSymbol)

    /// The map view managed by the view controller.
    @IBOutlet weak var mapView: AGSMapView! {
        didSet {
            mapView.map = makeMap()
            mapView.graphicsOverlays.add(graphicsOverlay)
            mapView.setViewpointCenter(
                AGSPoint(
                    x: -4487263.495911,
                    y: 3699176.480377,
                    spatialReference: .webMercator()
                ),
                scale: 1e8
            )
            mapView.touchDelegate = self
        }
    }

    /// Creates a map.
    ///
    /// - Returns: A new `AGSMap` object.
    func makeMap() -> AGSMap {
        let map = AGSMap(basemapStyle: .arcGISTopographic)
        return map
    }

    /// Adds the graphics to the graphics overlay.
    func addGraphicsToOverlay() {
        graphicsOverlay.graphics.addObjects(from: [
            polygonGraphic,
            nearestCoordinateGraphic,
            tappedLocationGraphic,
            nearestVertexGraphic
        ])
    }

    // MARK: UIViewController

    override func viewDidLoad() {
        super.viewDidLoad()
        addGraphicsToOverlay()
        // Add the source code button item to the right of navigation bar.
        (self.navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["NearestVertexViewController"]
    }
}

// MARK: - AGSGeoViewTouchDelegate

extension NearestVertexViewController: AGSGeoViewTouchDelegate {
    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        if mapView.callout.isHidden {
            // If the callout is not shown, show the callout with the coordinates of the normalized map point.
            if let normalizedPoint = AGSGeometryEngine.normalizeCentralMeridian(of: mapPoint) as? AGSPoint,
                let nearestVertexResult = AGSGeometryEngine.nearestVertex(in: polygonGraphic.geometry!, to: normalizedPoint),
                let nearestCoordinateResult = AGSGeometryEngine.nearestCoordinate(in: polygonGraphic.geometry!, to: normalizedPoint) {
                // Set the geometry for the tapped point, nearest coordinate point and nearest vertex point.
                tappedLocationGraphic.geometry = normalizedPoint
                nearestVertexGraphic.geometry = nearestVertexResult.point
                nearestCoordinateGraphic.geometry = nearestCoordinateResult.point
                // Get the distance to the nearest vertex in the polygon.
                let distanceVertex = Measurement(
                    value: nearestVertexResult.distance,
                    unit: UnitLength.meters
                )
                // Get the distance to the nearest coordinate in the polygon.
                let distanceCoordinate = Measurement(
                    value: nearestCoordinateResult.distance,
                    unit: UnitLength.meters
                )
                // Display the results on a callout of the tapped point.
                mapView.callout.title = "Proximity result"
                mapView.callout.detail = String(
                    format: "Vertex dist: %@; Point dist: %@",
                    distanceFormatter.string(from: distanceVertex),
                    distanceFormatter.string(from: distanceCoordinate)
                )
                mapView.callout.isAccessoryButtonHidden = true
                mapView.callout.show(
                    at: normalizedPoint,
                    screenOffset: .zero,
                    rotateOffsetWithMap: false,
                    animated: true
                )
            }
        } else {
            // Dismiss the callout and reset geometry for all simple marker graphics.
            mapView.callout.dismiss()
            tappedLocationGraphic.geometry = nil
            nearestVertexGraphic.geometry = nil
            nearestCoordinateGraphic.geometry = nil
        }
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.