Select to view content in your preferred language

The Foundation of Your iOS Mapping App

1890
8
10-19-2017 07:45 PM
Labels (1)
Nicholas-Furness
Esri Regular Contributor
0 8 1,890

Each of our Example Apps is designed to provide some inspiration for your own. If you’re interested in building your own custom app because one of our off-the-shelf apps isn’t quite right for you, then the Example Apps are a great place to start.

With the Maps App for iOS, we show how you might build the foundation of your own ArcGIS Runtime mapping app using Swift.

The open source app (which you can download and build from GitHub) highlights fundamental Runtime functionality and showcases some approaches to coding against asynchronous services. It includes a robust internal framework and a modern, decoupled UI.

In later posts, we’ll take a look at the UI/UX and what it took to put the app together, but for now let’s take a look at using the app.

Search & Geocode

To search, simply start typing into the Search Bar at the top of the screen.

As you type, you’ll see suggestions appear, and you can either pick a suggestion or search for the text you’ve typed.

By default, you can search for places or addresses using the ArcGIS  World Geocoder, and the suggestions will prioritize matches close to the center of the map.

Reverse Geocode

If you tap and hold on the map, you’ll see a magnifier. Use this to pick a point on the map and when you’re done, you’ll get the address of that point.

Turn-by-turn Directions

Whether you’ve searched or reverse-geocoded, the results panel includes a “Directions” button. Tap this to calculate directions from your current location to the search result.

 

At the top of the screen you’ll see an overview of the entire route, and at the bottom you can see turn-by-turn directions. Just swipe through them and the map will update to display the current step. If you ever want to go back to the entire route, simply tap the route summary at the top of the screen.

 

Note: Since the routing service consumes ArcGIS  credits, you'll need to log in to get directions. The ArcGIS Runtime includes a Credentials Cache and by default, if you've already logged in, the Runtime is able to intelligently make use of cached credentials to avoid prompting you for a login.

Switch Basemaps

The application also allows you to pick from a set of basemaps. If you are logged in to an ArcGIS  Organization or to an ArcGIS Portal, then the list of basemaps will reflect those configured for your account. If not, then you'll get to pick from the default ArcGIS  basemaps.

Browse your Web Maps

The last bit of functionality the app provides is the ability to browse and open your Web Maps. When logged in to ArcGIS  the Maps App makes use of the Runtime Portal API to query your content and present you with a list of Web Maps. Simply tap one to open it in the app.

What did we learn building this functionality?

There are some interesting points to consider from all this.

Authentication

In the case of getting directions and browsing Web Maps, the user must log in. But how should your app behave when the user isn't logged in?

When not logged in, we decided to allow the user to search and geocode using the ArcGIS  World Geocoding Service (which is free to use, as long as you're not storing the results).

But once the user is logged in, the Maps App uses the Portal API to determine which Geocoding Service and Routing Service to use and, as mentioned above, which basemaps to list. Your ArcGIS  Organization's Administrator can configure these settings, so it's important that your app reads and honors that configuration.

Lastly, consider how a user should be prompted to log in. For the Maps App we opted to make use of the ArcGIS Runtime's integration with OAuth 2.0, which made implementing login really straightforward.

iOS Location Permissions

It's also important for an iOS app to behave properly if the user hasn't enabled Location Services or has explicitly denied the app access to their location. When possible, asking for directions will get directions from the current location, but if that's not available then the app will get directions from the center of the current map view.

8 Comments
ManasaParida
Occasional Contributor

Hi Nicholas,

Thanks for the this awesome blog and its really good one to boost one level up experience in Runtime SDK.

I have some points and need to understand which i could't found from the sample code. Lets an example:-

1. var routeTask = AGSRouteTask(url: AppSettings.worldRoutingServiceURL)

2. routeTask to get default parameter : routeTask.defaultRouteParameters() in this block i also set few customized routeTask parameter to get the best RouteResult.

3. setStops( [ from , to ] ) // set the source and destination stops.

4. then i have started the solveRoute block to get the route results.

self.routeTask.solveRoute(with: params) { result, error in }

6. Then i got the AGSRouteResult object having route and DirectionManeuvers result array.


Now my problem statement is

1. Whenever my device started moving then usually i get the new location update from the block called @ mapView.locationDisplay.locatidHandler {newLocation} this is AGSLocation object.

from this AGSLocation which is the actual properties/attribute to compare with my AGSRouteResult object which i had received before from the routeTask.solveRoute API to reRoute if my device is not following the route which routeTask.solveRoute has been provided before.

2. from the AGSLocation object which is the attribute/variable i need to compare with the result receive earlier from routeTask.solveRoute API to make sure my current device location is already under the boundaries of my AGSRouteResult and DirectionManeuvers to implement text to speech functionality or before any turn by turn i need to inform to the user as voice command.

3. For an example each of my maneuver.directionText i have to speech before i reach at this point so probably to calculate the distance from maneuver.length to my device location ( i am unable to understand which is the correct object from the newLocation update to compare with either AGSRouteResult.route objects property or DirectionManeuvers any property to get the differences or device boundaries inside my direction) else if there is data mismatch then i have to start calling again the solveRoute API to re-Route with Current Device location to source Stop.

Help Would be appreciate.

 

Nicholas-Furness
Esri Regular Contributor

Hi Manasa Parida.

Thanks for the questions.

We distinguish the functionality that you're talking about as Navigation vs Routing.

The Routing API that we already provide (and which you're making use of) gives you the components you need to get from A to B. But a full Navigation experience that tracks that progress and adjusts the route on the fly and intelligently (such as that provided by the Navigator app) is something you will have to build yourself for the time being.‌

Some high-level thoughts, based off your questions:

  • Using the locationDisplay.locatidHandler is the correct approach to get updates to the user's position.
  • Use AGSGeometryEngine.distanceBetweenGeometry1:geometry2() to calculate the distance of the user's location from the planned route.  Perhaps look at the distance between the AGSLocation.position and the AGSDirectionManeuver.geometry to see if the user has moved too far from the route.
  • Consider building some logic to work out which maneuver the user is currently on rather than treating the route as a whole. This can help with cases where the route doubles back on itself, or crosses over itself. By working on a maneuver-by-maneuver basis, you can work out when to read the next maneuver step. E.g. get the end point of the current maneuver, and use GeometryEngine to check when the user has approached within a certain distance of it before reading the next maneuver's text.
  • Realize the limitations of your GPS data. Does the environment cause bad data from time-to-time? How frequently do you get updates (you can control this through the AGSCLLocationDataSource.locationManager in the default case)? And you should probably smooth out bad data rather than allow it to trigger thresholds.
  • Variables like GPS accuracy/quality/frequency or complexity of the route all play into making the work tricky.

Nick.

ManasaParida
Occasional Contributor

Hi Nicholas Furness‌,

Thanks for the update, meanwhile Why are GIS features using one spatial reference whereas the location listener is receiving coordinates in different spatial reference?

 po location.position.toBuilder().toGeometry()

AGSPoint: (51.509900, 25.270500), sr: 4326

 po route.directionManeuvers[0].geometry!

AGSPoint: (5734036.672824, 2909039.865549, 0.000000, 0.000000), sr: 3857

po AGSGeometryEngine.geometry(location.position.toBuilder().toGeometry(), intersects: route.directionManeuvers[0].geometry!)

Expected result it should provide true/false but somehow it was saying...

Additional Message=geometry1 and geometry2 must have equivalent spatial references.}

If you could have any other additional way to suggest, please suggest have go through the entire AGSGeometryEngine class and found this could be the solution for me to get either my current location is already in my planned route or not.

Nicholas-Furness
Esri Regular Contributor
Why are GIS features using one spatial reference whereas the location listener is receiving coordinates in different spatial reference?

iOS's CoreLocation returns Lat/Lon, i.e. wgs84 (4326). We can't assume you need it in a specific spatial reference so we don't translate it. But you can use AGSGeometryEngine.projectGeometry() to project it to the route geometry's spatial reference.

As for why the route is in Web Mercator (3857): Our major public services (World Geocoder, World Route Task) output in 3857 by default because that matches our default basemaps and so the returned geometries can be overlaid on the map with no extra transformation. If you're working with a different spatial reference in your map, you can specify the AGSRouteParameters.outputSpatialReference property when calling AGSRouteTask.solveRouteWithParameters().

One note: your logic above is likely to always return false though. The chances of the GPS location actually being precisely on the line are vanishingly small. You should buffer around your location by a few meters using AGSGeometryEngine.Buffer() or AGSGeometryEngine.geodeticBuffer() (perhaps adjusting the buffer size by considering AGSLocation.horizontalAccuracy) and check if that buffer polygon intersects with your maneuver line. Or else, as I suggested earlier, measure the distance between the location's point and the maneuver line.

Lastly, may I suggest that if you have further questions about this that you post them in the iOS Forums where the community will be more likely to see them? Thanks!

ManasaParida
Occasional Contributor

sure Nicholas, thanks.

ManasaParida
Occasional Contributor

Hi Nicholas Furness

Suddenly i am getting an error and app closed in my Xcode console after updating the SDK version to 100.4.

pod 'ArcGIS-Runtime-SDK-iOS', '100.4'

here is the error details.

dyld: Library not loaded: @rpath/ArcGIS.framework/ArcGIS

  Referenced from: /Users/manasaparida/Library/Developer/CoreSimulator/Devices/7C896FC8-43B4-410B-BF0A-6B0CB347D5FD/data/Containers/Bundle/Application/66D74621-8A56-44B6-84B3-8F56113AA039/maps-app-ios.app/maps-app-ios

  Reason: image not found

Message from debugger: Terminated due to signal 6

Please let me know what is this issue caused by.

Thanks

Manas

Nicholas-Furness
Esri Regular Contributor

This project doesn't use Cocoapods. If this is a general question about Cocoapods and the iOS Runtime SDK, please post the question at here. But this suggestion might be helpful in case your Cocoapods cache has got confused.

Nicholas-Furness
Esri Regular Contributor

Ah. Actually I see that this could be a Maps App issue and that you must have modified the project to use Cocoapods. Please open an issue on the repo and describe in better detail how you've modified the project to use Cocoapods and some more detail about the crash (e.g. stack trace).

About the Author
Product Manager for the ArcGIS Maps SDKs for Native Apps, focusing on the Swift SDK, as well as the ArcGIS Maps SDKs for Game Engines. My background is in computer science, but my professional career has always been GIS, in particular Utilities.