Skip to content

Commit 4282253

Browse files
authored
Merge pull request #298 from Esri/Caleb/New-FindAddressWithReverseGeocode
[New] Find address with reverse geocode
2 parents d6bb88e + c966d5d commit 4282253

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed

Samples.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@
196196
D73F8CF42AB1089900CD39DA /* Restaurant.stylx in Resources */ = {isa = PBXBuildFile; fileRef = D73F8CF32AB1089900CD39DA /* Restaurant.stylx */; settings = {ASSET_TAGS = (StyleFeaturesWithCustomDictionary, ); }; };
197197
D73FC0FD2AD4A18D0067A19B /* CreateMobileGeodatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73FC0FC2AD4A18D0067A19B /* CreateMobileGeodatabaseView.swift */; };
198198
D73FC0FE2AD4A19A0067A19B /* CreateMobileGeodatabaseView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D73FC0FC2AD4A18D0067A19B /* CreateMobileGeodatabaseView.swift */; };
199+
D73FCFF72B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73FCFF42B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift */; };
200+
D73FCFFA2B02A3C50006360D /* FindAddressWithReverseGeocodeView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D73FCFF42B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift */; };
199201
D73FCFFF2B02C7630006360D /* FindRouteAroundBarriersView.Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73FCFFE2B02C7630006360D /* FindRouteAroundBarriersView.Views.swift */; };
200202
D73FD0002B02C9610006360D /* FindRouteAroundBarriersView.Views.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D73FCFFE2B02C7630006360D /* FindRouteAroundBarriersView.Views.swift */; };
201203
D742E4922B04132B00690098 /* DisplayWebSceneFromPortalItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D742E48F2B04132B00690098 /* DisplayWebSceneFromPortalItemView.swift */; };
@@ -378,6 +380,7 @@
378380
dstPath = "";
379381
dstSubfolderSpec = 7;
380382
files = (
383+
D73FCFFA2B02A3C50006360D /* FindAddressWithReverseGeocodeView.swift in Copy Source Code Files */,
381384
D742E4952B04134C00690098 /* DisplayWebSceneFromPortalItemView.swift in Copy Source Code Files */,
382385
D7010EC12B05618400D43F55 /* DisplaySceneFromMobileScenePackageView.swift in Copy Source Code Files */,
383386
D737237B2AF5AE1A00846884 /* FindRouteInMobileMapPackageView.Models.swift in Copy Source Code Files */,
@@ -625,6 +628,7 @@
625628
D73723782AF5ADD700846884 /* FindRouteInMobileMapPackageView.MobileMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteInMobileMapPackageView.MobileMapView.swift; sourceTree = "<group>"; };
626629
D73F8CF32AB1089900CD39DA /* Restaurant.stylx */ = {isa = PBXFileReference; lastKnownFileType = file; path = Restaurant.stylx; sourceTree = "<group>"; };
627630
D73FC0FC2AD4A18D0067A19B /* CreateMobileGeodatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateMobileGeodatabaseView.swift; sourceTree = "<group>"; };
631+
D73FCFF42B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindAddressWithReverseGeocodeView.swift; sourceTree = "<group>"; };
628632
D73FCFFE2B02C7630006360D /* FindRouteAroundBarriersView.Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteAroundBarriersView.Views.swift; sourceTree = "<group>"; };
629633
D742E48F2B04132B00690098 /* DisplayWebSceneFromPortalItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayWebSceneFromPortalItemView.swift; sourceTree = "<group>"; };
630634
D744FD162A2112D90084A66C /* CreateConvexHullAroundPointsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateConvexHullAroundPointsView.swift; sourceTree = "<group>"; };
@@ -838,6 +842,7 @@
838842
E070A0A1286F3B3400F2B606 /* Download preplanned map area */,
839843
E004A6EE284E4B7A002A1FE6 /* Download vector tiles to local cache */,
840844
1C26ED122A859525009B7721 /* Filter features in scene */,
845+
D73FCFF32B02A3AA0006360D /* Find address with reverse geocode */,
841846
D7705D5F2AFC570700CC0335 /* Find closest facility from point */,
842847
D7705D542AFC244E00CC0335 /* Find closest facility to multiple points */,
843848
D78666A92A21616D00C60110 /* Find nearest vertex */,
@@ -1412,6 +1417,14 @@
14121417
path = "Create mobile geodatabase";
14131418
sourceTree = "<group>";
14141419
};
1420+
D73FCFF32B02A3AA0006360D /* Find address with reverse geocode */ = {
1421+
isa = PBXGroup;
1422+
children = (
1423+
D73FCFF42B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift */,
1424+
);
1425+
path = "Find address with reverse geocode";
1426+
sourceTree = "<group>";
1427+
};
14151428
D742E48E2B04132B00690098 /* Display web scene from portal item */ = {
14161429
isa = PBXGroup;
14171430
children = (
@@ -2252,6 +2265,7 @@
22522265
0042E24328E4BF8F001F33D6 /* ShowViewshedFromPointInSceneView.Model.swift in Sources */,
22532266
D7E557682A1D768800B9FB09 /* AddWMSLayerView.swift in Sources */,
22542267
D7497F3C2AC4B4C100167AD2 /* DisplayDimensionsView.swift in Sources */,
2268+
D73FCFF72B02A3AA0006360D /* FindAddressWithReverseGeocodeView.swift in Sources */,
22552269
0005580A2817C51E00224BC6 /* SampleDetailView.swift in Sources */,
22562270
D75C35672AB50338003CD55F /* GroupLayersTogetherView.GroupLayerListView.swift in Sources */,
22572271
4D2ADC6229C5071C003B367F /* ChangeMapViewBackgroundView.Model.swift in Sources */,
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright 2023 Esri
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import ArcGIS
16+
import SwiftUI
17+
18+
struct FindAddressWithReverseGeocodeView: View {
19+
/// The view model for the sample.
20+
@StateObject private var model = Model()
21+
22+
/// The point on the map where the user tapped.
23+
@State private var tapLocation: Point?
24+
25+
/// The placement of the callout on the map.
26+
@State private var calloutPlacement: CalloutPlacement?
27+
28+
/// The text shown in the callout.
29+
@State private var calloutText: String?
30+
31+
/// The error shown in the error alert.
32+
@State private var error: Error?
33+
34+
var body: some View {
35+
MapView(map: model.map, graphicsOverlays: [model.graphicsOverlay])
36+
.callout(placement: $calloutPlacement.animation(.default.speed(2))) { _ in
37+
Text(calloutText ?? "No address found.")
38+
.font(.callout)
39+
.padding(8)
40+
}
41+
.onSingleTapGesture { _, mapPoint in
42+
tapLocation = mapPoint
43+
}
44+
.task(id: tapLocation) {
45+
guard let tapLocation else { return }
46+
await reverseGeocode(tapLocation)
47+
}
48+
.task {
49+
do {
50+
try await model.locatorTask.load()
51+
} catch {
52+
self.error = error
53+
}
54+
}
55+
.overlay(alignment: .top) {
56+
Text("Tap on the map to get the address for point.")
57+
.frame(maxWidth: .infinity, alignment: .center)
58+
.padding(8)
59+
.background(.thinMaterial, ignoresSafeAreaEdges: .horizontal)
60+
}
61+
.errorAlert(presentingError: $error)
62+
}
63+
64+
/// Reverse geocodes a given point and updates the marker with the result.
65+
/// - Parameter mapPoint: The point on the map to reverse geocode.
66+
private func reverseGeocode(_ mapPoint: Point) async {
67+
// Normalize the map point.
68+
guard let normalizedPoint = GeometryEngine.normalizeCentralMeridian(
69+
of: mapPoint
70+
) as? Point else { return }
71+
72+
do {
73+
// Perform reverse geocode using the locator task with the point and parameters.
74+
let geocodeResults = try await model.locatorTask.reverseGeocode(
75+
forLocation: normalizedPoint,
76+
parameters: model.reverseGeocodeParameters
77+
)
78+
79+
// Update the callout text using the first result from the reverse geocode.
80+
updateCalloutText(using: geocodeResults.first)
81+
} catch {
82+
self.error = error
83+
calloutText = nil
84+
}
85+
86+
// Update the marker and callout location.
87+
model.markerGraphic.geometry = normalizedPoint
88+
calloutPlacement = .geoElement(model.markerGraphic, tapLocation: normalizedPoint)
89+
}
90+
91+
/// Updates the callout text using the address from a given geocode result.
92+
/// - Parameter geocodeResult: The result to get the address from.
93+
private func updateCalloutText(using geocodeResult: GeocodeResult?) {
94+
// Get the address from the result's attributes.
95+
let addressText = model.reverseGeocodeParameters.resultAttributeNames
96+
.compactMap { geocodeResult?.attributes[$0] as? String }
97+
.filter { !$0.isEmpty }
98+
.joined(separator: ", ")
99+
100+
// Update the callout text if an address was found.
101+
calloutText = !addressText.isEmpty ? addressText : nil
102+
}
103+
}
104+
105+
private extension FindAddressWithReverseGeocodeView {
106+
/// The model used to store the geo model and other expensive objects used in this view.
107+
class Model: ObservableObject {
108+
/// A map with a topographic basemap initially centered on Redlands, CA, USA.
109+
let map = {
110+
let map = Map(basemapStyle: .arcGISTopographic)
111+
map.initialViewpoint = Viewpoint(
112+
center: Point(x: -117.195, y: 34.058, spatialReference: .wgs84),
113+
scale: 5e4
114+
)
115+
return map
116+
}()
117+
118+
/// The graphics overlay for the marker graphic.
119+
let graphicsOverlay = GraphicsOverlay()
120+
121+
/// The red map marker graphic used to indicate a tap location on the map.
122+
let markerGraphic = {
123+
// Create a symbol using the image from the project assets.
124+
guard let markerImage = UIImage(named: "RedMarker") else { return Graphic() }
125+
let markerSymbol = PictureMarkerSymbol(image: markerImage)
126+
127+
// Change the symbol's offsets, so it aligns properly to a given point.
128+
markerSymbol.leaderOffsetY = markerImage.size.height / 2
129+
markerSymbol.offsetY = markerImage.size.height / 2
130+
131+
// Create a graphic with the symbol.
132+
return Graphic(symbol: markerSymbol)
133+
}()
134+
135+
/// The locator task for reverse geocoding.
136+
let locatorTask = LocatorTask(url: .geocodeServer)
137+
138+
/// The parameters for the reverse geocode operation.
139+
let reverseGeocodeParameters = {
140+
let parameters = ReverseGeocodeParameters()
141+
parameters.addResultAttributeNames(["Address", "City", "RegionAbbr"])
142+
parameters.maxResults = 1
143+
return parameters
144+
}()
145+
146+
init() {
147+
graphicsOverlay.addGraphic(markerGraphic)
148+
}
149+
}
150+
}
151+
152+
private extension URL {
153+
/// A URL to a geocode server on ArcGIS Online.
154+
static var geocodeServer: URL {
155+
URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!
156+
}
157+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Find address with reverse geocode
2+
3+
Use an online service to find the address for a tapped point.
4+
5+
![Image of find address with reverse geocode](find-address-with-reverse-geocode.png)
6+
7+
## Use case
8+
9+
You might use a geocoder to find a customer's delivery address based on the location returned by their device's GPS.
10+
11+
## How to use the sample
12+
13+
Tap the map to see the nearest address displayed in a callout.
14+
15+
## How it works
16+
17+
1. Create a `LocatorTask` object using a URL to a geocoder service.
18+
2. Create an instance of `ReverseGeocodeParameters` and set `ReverseGeocodeParameters.maxResults` to 1.
19+
3. Pass the `ReverseGeocodeParameters` into `LocatorTask.reverseGeocode(forLocation:parameters:)` and get the matching results from the `GeocodeResult`.
20+
4. Show the results using a `PictureMarkerSymbol` and add the symbol to a `Graphic` in the `GraphicsOverlay`.
21+
22+
## Relevant API
23+
24+
* GeocodeResult
25+
* LocatorTask
26+
* ReverseGeocodeParameters
27+
28+
## Additional information
29+
30+
This sample uses the World Geocoding Service. For more information, see the [Geocoding service](https://developers.arcgis.com/documentation/mapping-apis-and-services/search/services/geocoding-service/) help topic on the ArcGIS Developer website.
31+
32+
## Tags
33+
34+
address, geocode, locate, reverse geocode, search
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"category": "Search and Query",
3+
"description": "Use an online service to find the address for a tapped point.",
4+
"ignore": false,
5+
"images": [
6+
"find-address-with-reverse-geocode.png"
7+
],
8+
"keywords": [
9+
"address",
10+
"geocode",
11+
"locate",
12+
"reverse geocode",
13+
"search",
14+
"GeocodeResult",
15+
"LocatorTask",
16+
"ReverseGeocodeParameters"
17+
],
18+
"redirect_from": [],
19+
"relevant_apis": [
20+
"GeocodeResult",
21+
"LocatorTask",
22+
"ReverseGeocodeParameters"
23+
],
24+
"snippets": [
25+
"FindAddressWithReverseGeocodeView.swift"
26+
],
27+
"title": "Find address with reverse geocode"
28+
}
185 KB
Loading

0 commit comments

Comments
 (0)