Sunday 6 September 2009

MonoTouch MapKit 101

UPDATE 27-Sep:MapKit 103 has the MKAnnotation working - DEVICE ONLY.
UPDATE 25-Sep:MapKit 102 has the MKPlacemark working.
UPDATE 14-Sep:This post was written against MonoTouch beta 0.8; some issues have been addressed in the released version. Updated blog post coming soon...

MonoTouch MapKitMapKit seems to have the following 'features' which were supposed to be covered by this sample:
• Display a map
• Change map appearance
• Interact with the user's location via GPS
• Reverse-geocode a location
• Place pins (with popups) on the map

The sample manages to cover four out of five of those, but pin-placement just refused to work...

Firstly, you can download the MapKit.zip (16Kb) if you want to give it a try or just view Main.cs and MainWindow.xib.designer.cs.htm online.

To start with, don't forget you will need using MonoTouch.MapKit; using MonoTouch.CoreLocation; in your c#.

Some of the 'code highlights' are shown below - I'm not sure if this is the best way to do it, just that it "works" (and if you have any ideas why MKAnnotation is so problematic, let me know).

Show current location
Actually this sample doesn't use MapKit's built-in feature to put a little blue pin on your current position, but it is VERY easy to configure:
mapView.ShowsUserLocation = true;

Change map location
It's also pretty easy to set the map's current center-point (and optionally animate the transition):
mapView.SetCenterCoordinate(new CLLocationCoordinate2D(
Convert.ToDouble(textfieldLatitude.Text),
Convert.ToDouble(textfieldLongitude.Text)),
true);

Respond to map being 'dragged'
When the map is dragged, a MKMapViewDelegate subclass is provided to 'listen' for various things and take some action. The MapViewDelegate class takes a reference to the AppDelegate so it can interact with the UI.
Firstly you must 'attach' the delegate (NOTE: this is NOT a c# delegate but a different concept with the same name!)
mapView.Delegate = new MapViewDelegate(this);
and then do our UI update in the class itself
public class MapViewDelegate : MKMapViewDelegate
{
public override void RegionChanged(MKMapView mapView, bool animated)
{
Console.WriteLine("Region did change");
_appd.labelCurrent.Text = "Map Center " +
mapView.CenterCoordinate.Latitude + ", " +
mapView.CenterCoordinate.Longitude;
}

Reverse geocoding a location
MKReverseGeocoder follows the same delegate pattern as MKMapView, first you 'configure' the geocoder
geoCoder = new MKReverseGeocoder(mapView.CenterCoordinate);
geoCoder.Delegate = new GeoCoderDelegate(this);
geoCoder.Start();
then provide the implementation in another class. In this case MonoTouch.MapKit does not currently provide the method we need, so here is the full class with special MonoTouch attributes included (meaning the c# name FoundPlacemark is not important - just make sure you get the parameters correct)
public class GeoCoderDelegate : MKReverseGeocoderDelegate
{
AppDelegate _appd;
public GeoCoderDelegate(AppDelegate appd) {_appd = appd;}
// Not currently exposed by MonoTouch, use ExportAttribute
[Export("reverseGeocoder:didFindPlacemark:")]
public void FoundPlacemark(MKReverseGeocoder geocoder
, MKPlacemark placemark
)
{
Console.WriteLine("Found in " + placemark.Country);
_appd.labelPlacemark.Text = placemark.SubThoroughfare
+ " " + placemark.Thoroughfare
+ " " + placemark.Locality
+ " " + placemark.AdministrativeArea
+ " " + placemark.Country;
}

GPS Tracking with CoreLocation
You can spot the CoreLocation classes with their CL prefix (as opposed to MK). Once again we have the 'delegate pattern' in play, first creating it with some custom constructor parameters (and not forgetting to start the tracking)
locationManager = new CLLocationManager();
locationManager.Delegate = new LocationManagerDelegate(mapView, this);
locationManager.StartUpdatingLocation();
with the implementation in another class
private class LocationManagerDelegate : CLLocationManagerDelegate
{
private MKMapView _mapview;
private AppDelegate _appd;
public LocationManagerDelegate(MKMapView mapview, AppDelegate appd)
{
_mapview = mapview; _appd = appd;
}
public override void UpdatedLocation(CLLocationManager manager
, CLLocation newLocation, CLLocation oldLocation)
{
MKCoordinateSpan span = new MKCoordinateSpan(0.2, 0.2);
MKCoordinateRegion region =
new MKCoordinateRegion(newLocation.Coordinate, span);
_appd.mylocation = newLocation;
_mapview.SetRegion(region, true);
_appd.labelInfo.Text = "UserLocation "
+ newLocation.Coordinate.Latitude + ", "
+ newLocation.Coordinate.Longitude;
Console.WriteLine("Location updated");
}
You can stop it when you are done 'tracking', and you can also set locationManager.DesiredAccuracy (if you can figure out what the CONST values need to be!).

Here's the class diagram (the three delegates are actually nested in AppDelegate, as is a subclass of MKAnnotation which isn't working just yet...)


And yes, the user-interface on this sample isn't exactly intuitive... so here's the "manual" :)

6 comments:

  1. A couple of minor changes required to use the latest release of monotouch. Excellent post on using the map kit.

    ReplyDelete
  2. Thanks for this great example. Were you able to get the Annotations working with the latest MonoTouch? Looking forward to your updated blog post.

    ReplyDelete
  3. I am getting numerous errors trying to build. Does anyone have any suggestions?

    ReplyDelete
  4. See MapKit 102 for MKPlacemark. Still figuring out MKAnnotation issues...

    ReplyDelete
  5. Hi Craig, any idea where i can find the values of the const for dessired accuracy.

    thankx and really good post!
    david

    ReplyDelete
  6. davichu, I'd assumed that the values for the CLLocationAccuracy const were equivalent the meters value in their name, however I hadn't seen anything confirming this until I stumbled across this pyObjC post recently. Dave says:

    kCLLocationAccuracyBest = -1.0
    kCLLocationAccuracyNearestTenMeters = 10.0
    kCLLocationAccuracyHundredMeters = 100.0
    kCLLocationAccuracyKilometer = 1000.0
    kCLLocationAccuracyThreeKilometers = 3000.0

    ReplyDelete

Note: only a member of this blog may post a comment.