MapLocation Question (bug?)

I am trying to use the new MobileMapViewer in an iOS project and have run into a problem with MapLocations. I am using Xojo 2020 r2 and Mac OS Big Sur.

I have a number of items that I want to plot, each with different location. As in the docs, I try:

Var locations() as MapLocation
for i as integer = 0 to myData.ubound
    locations.Add(New MapLocation(myData(i).latitude, myData(i).longitude))

myData(i).longitude and . latitude are declared as Double.

However, this raises an invalid argument exception with the reason: “The operation couldn’t be completed. (kCLErrorDomain error 2.)”. I have tried various permutations (e.g., create a single location to append to locations, etc) but no luck.

What am I doing wrong or is this a bug?

If latitude or longitude passed is not valid, an InvalidArgumentException will occur.

A quick google search reveals the cause:

Thanks, Greg, especially for a quick answer on a Friday night. I checked the value of i (which I should have done before) and the code is choking on i = 50 so, yup, this is the problem which kind of rules out using Apple maps in any mapping app (which is what I was trying to do).

What I might try, instead, is putting all of my data into a single picture. Then I only need to do one geocoding with a picture several hundred pixels across. Not sure how accurate this will be but we’ll see.

Update: I’m guessing this won’t work because the picture won’t scale when the user pinches to zoom in or out…

If (as it seems) you know the locations in advance the correct way is to get their coordinates in advance and save them in the app, not look them up anew every time.

Yes, that would work if the device always had a data connection (either wireless or cellular) when the data were collected. Then I could accumulate each location as it was collected and set the custom icon as well. However, this is an app for field data collection when, commonly, the user doesn’t have a data connection (or access to Apple Maps in general). The maps aren’t a problem because the the main base maps for the app are entered by the user as MBTiles (the app, GMDE Mobile, is already on the app store). So when the user is back from the field and wants to visualize their data on Apple Maps for whatever reason, they would need to batch process their measurements. I can imagine using a timer to process their measurements in chunks of 30 or so with a delay of a few seconds in-between but was hoping to avoid such a kludge.

I’m not getting that. The users collect data in the field, but while they may not have cellular reception, they should still get GPS, so the coordinates can and should be saved with each data point (it seems illogical to save a location without the coordinates). That’s how every runner’s fitness watch works, and they get into some pretty remote locations.

Could it be that the problem is not with data visualisation but data collection or retrieval (eg the coordinates ARE saved but not used)?

P.S. just in case you use Garmin devices: from my next MapKit article:

Potential problems

Your GPX should consist of trackpoints with valid timestamps. The ele(vation) tag on the other hand is optional and will default to 0.

If you are using a Garmin GPS device then many of these units have the facility to save the track. However saving the track strips out the timestamps, and many apps will not (correctly or at all) import tracks without timestamps. If you want to import saved tracks from a Garmin device you need to account for the missing time stamps! But it would be better if you upload GPX files created from the active track(s) from the device rather than any tracks you have saved.

Sorry, I didn’t describe things clearly enough. Each datum is tagged with the lat/lon provided by the iPad directly in the field (and many other attributes as well). Each datum has a custom icon, related to spatial orientation, that is set with MapLocation.icon, and a different title that is set with MapLocation.Title.

It is my impression that every call for a new MapLocation(lat, lon) triggers a call to CLGeocoder/Apple, else why would it bomb on too many calls? Thus to define a MapLocation for each datum requires a data connection. As you say, I don’t know why it needs to because I already have the lat/lon (i.e., not doing an address lookup) but that is what seems to be happening. Maybe there is something I don’t under stand about MapLocations and MobileMapViewer as I just started playing with it…

I am aware that MBS has a nice MapKit implementation and much of your writing has addressed how to use that but, since this is a free app, I’m trying to do this with what is built in to the latest versions of Xojo. [Now, if you had a tutorial on using Spatialite DBs with Xojo, I’d be really interested!]

Ah, got you now. For the benefit of others following this:

Apple strictly implements the Model-View-Controller paradigm.

Therefore Apple uses two different things: MKAnnotations and their associated MKAnnotationViews, and CLLocations (MK = MapKit, CL = CoreLocation).

CLLocation is used to get a physical address from coordinates.

MKAnnotation is any object implementing the Annotation protocol (aka interface) which means it has title, subtitle, longitude, latitude. They are used to display a location on a Map.

MKAnnotationView determines how an MKAnnotation is displayed on the map (for example whether it is a pin or an icon - the default is a red pin though user defined locations should be purple pins).

Note: MKAnnotations are ‘cheap’ objects (just two strings and two doubles), MKAnnotationViews are ‘expensive’ objects (so if you can you reuse MKAnnotationViews)

So in Swift to display your data on the map you would not use CLLocation (what is the point as you already have the coordinates) but create an MKAnnotation (you can define custom classes, eg DriverAnnotation or CarAnnotation) with the coordinates you have and add that annotation to the map.

In the ViewForAnnotation you reuse a suitable view if it already exists (eg there is already a previous CarAnnotationVIew for the CarAnnotation available that you can use) or create a new one if necessary.

So to display one of your data points on a Map with Swift does not require a call to the Apple servers for the address.

That is also what the MBS MapKit implements.

Xojo does NOT follow the Model-View-Controller paradigm. It basically has bundled CLLocation, MKAnnotation, and MKAnnotationView into ONE object: MapLocation.

Which seemingly automatically does a CLLocation search for EVERY MapLocation it creates.

For Xojo’s non-professional target audience that is probably the right thing to do (even though it severely restricts what they can do, somewhat cementing Xojo’s reputation as a toy language) - but they should warn them that if they try to display 50+ locations at once (eg ‘all my company’s cars in New York’ or a runner’s GPS track with thousands of data points) the multiple searches that initiates will get throttled (eg the app ‘fails’).

Here are some tips from Apple’s documentation (Apple Developer Documentation corelocation/clgeocoder?language=swift).

Tips for Using a Geocoder Object

Apps must be conscious of how they use geocoding. Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. (When the maximum rate is exceeded, the geocoder returns an error object with the error to the associated completion handler). Here are some rules of thumb for using this class effectively:

  • Send at most one geocoding request for any one user action.

So what are your options?

Hope that Xojo provides a MapLocation constructor without automatic lookup.

A timer is one solution but not very reliable - you might still get throttled (I would presume the map server is smart enough to allow up to 50 per second but only up to 200 per minute - it might even get you blocked completely. I guess it’s basically a defence against Denial of Service attacks).

If you have the address then you can use MKLocalSearch that does not get throttled (MBS again).

If you want more powerful and reliable then it’s MBS I’m afraid. That’s why the Pros swear on it (not that I am one).

1 Like

Thank you for taking the time to clarify things.

Since the user already has a map in my app (thanks to MBTiles), I’ve opted just to show the selected data on the Apple Map rather than all of the data. Mostly, I’m implementing the MapViewer in case the user wants to navigate outside of the MBTiles map area or wants to zoom in more than the zoom levels in their MBTiles database. We use all sorts of exotic base maps which apple doesn’t provide (LiDAR hillsides, scanned geologic or soil maps, etc.), as well as an onboard DEM, so having Apple Maps is mostly a convenience issue.

Which map tile provider are you using? Or are you running your own map tile server?

If you are using OpenStreetMap then be aware that if you use it extensively then you are supposed to use one of the MapTile providers (eg MapBox or run your own map tile server, otherwise you may run into problems there too.

Running your own map tile server might actually be a possible solution … though personally I wouldn’t want to get into that.

Neither. We make our own tiles using the program MapTiler from Klokan Technologies, or GDAL, and load the entire set onto the iPad. NAIP images at 1 m resolution are free for the entire US, or we make our own base maps from downloaded DEMs. Because of the fact that much of our field work there are no data connections (cellular or wireless), we can’t use a remote server and navigating the policies of different companies regarding caching tiles is complicated and often a gray area at best. Besides, most of the base maps we use are not available through online services, anyway. As an example, recently we’ve been making base maps filtering LiDAR topography DEMs with a fractional Laplacian operator which is very much a niche thing to do! My iPad right now has about 16 Gb of MBTiles files (to level 18), and DEMs for each one (either 1 m or 10 m resolution) for four or five different projects.

Interesting solution - hadn’t heard of it yet, so thanks for the info. :+1:

I tried many solutions online , but finally solved it by forcing close apple maps and reopen it.