Accueil Nos publications Blog [iOS] : Tour d’horizon du framework MapKit

[iOS] : Tour d’horizon du framework MapKit

Xcode Map2Les applications mobiles qui utilisent les services de localisation réussissent toujours à capter mon attention. La géolocalisation est l’une des fonctionnalités les plus sollicitées dans une application mobile. J’ai pris beaucoup de plaisir à développer ma première application intégrant le framework MapKit, dont Google était seul à tenir les rênes à cette époque. Aujourd’hui, Apple possède sa propre application de cartographie mais, en parallèle, Mapkit ne cesse d’évoluer et de faciliter la vie aux développeurs. Dans cet article, nous allons, entre autres, mettre en exergue l’une des fonctionnalités les plus importantes, qui a été mise à disposition à partir d’iOS 7 : Dessiner un itinéraire entre deux points GPS sans avoir à quitter l’application.

 

Le Framework MapKit

 

“The MapKit framework provides an interface for embedding maps directly into your own windows and views. This framework also provides support for annotating the map, adding overlays, and performing reverse-geocoding lookups to determine placemark information for a given map coordinate”– iOS Developer Reference.

 

MapKit est un framework livré avec le SDK iOS, qui permet de localiser et de naviguer à travers des cartes géographiques, d’ajouter des annotations à des lieux spécifiques, et même de dessiner des itinéraires entre deux lieux. Dans ce tutoriel, nous allons voir certaines fonctionnalités qu’offre ce framework. Mon but principal est de vous montrer comment afficher un itinéraire entre deux endroits, et pour cela nous allons principalement utiliser la classe MKDirections, qui est disponible à partir d’iOS 7. Plutôt que de passer par la théorie, nous allons travailler sur une application simple, dont je vais tenter de décortiquer chaque bout de code, pour avoir une bonne vision de MapKit.

Je suppose que vous savez tous que, avant iOS 6.0, Apple était plus ou moins dépendante de l’application Google Maps. Et puisque la marque à la pomme était toujours soucieuse de conserver le contrôle de son destin, elle a tout mis en oeuvre pour non seulement exterminer cette dépendance, mais aussi gagner du terrain et reconvertir ses clients, et ce à partir d’iOS 6, en annonçant sa nouvelle application de cartographie Plans. Dans ce cadre-là, le SDK a également introduit de nouvelles classes au cours du temps, conçues uniquement dans le but de faciliter l’intégration des cartes et des directions dans les applications iOS. Nous allons faire connaissance de quelques classes du framework Mapkit, telles que MKMapItem et MKDirections. A vos Xcode.

 

Création du projet

 

Ouvrez votre Xcode, créez un nouveau projet de type “Single View Application”, nommez le “PocMapKit”, sélectionnez iPhone comme type de device et spécifiez Objective-C comme langage de développement.

Maintenant, ouvrez le fichier “Main.storyboard”, sélectionnez ViewController dans la scène qui existe. Dans l’inspecteur des attributs, changez l’attribut size en iPhone 4-inch, pour avoir un aperçu de ce que ça donnera sur un iPhone 5, puis placez un MapKit View depuis la libraire d’objets :

 

Visualiser la vue en 4-inch

 

Je vous laisse le soin de créer vos contraintes autolayout. Pour ma part, j’ai choisi d’en faire quatre, une par rapport à chaque bord de l’écran.

 

Créer les autolayout

 

Essayez de changer la taille des écrans, comme nous venons de le faire, pour visualiser la scène dans les dimensions d’un écran iPhone 4-inch et regardez ce que ça donne. Si vous faites comme moi, vous allez avoir une Map toujours centrée dans l’écran.

Maintenant, connectez le delegate de la MapView au ViewController, on aura besoin des méthodes de délégation en rapport avec la classe MKMapView. Ensuite, créez une IBOutlet pour la MapView et connectez-la à votre ViewController. Pour cela, vous pouvez utiliser l’assistant d’édition directement à partir du storyboard. Si le mode automatique est sélectionné (vous pouvez le visualiser dans la barre du haut de la fenêtre du fichier ouvert), il vous affichera le fichier d’en-tête de votre ViewController.m par défaut, sinon, vous pouvez spécifier le fichier souhaité vous-mêmes. Ensuite, il suffit d’appuyer sur la touche contrôle de votre clavier et de glisser votre MapView vers le contrôleur. Appelez-la mapView.

 

Créer une IBOutlet

 

À ce stade, vous allez remarquer que votre projet présente une erreur du type : “Unkown type name ‘MKMapView’”. Pas de panique, c’est normal, puisque cet objet appartient au framework MapKit. Il suffit d’ajouter la ligne d’importation de cette librairie dans le fichier d’en-tête et le tour est joué.

Une dernière chose : puisqu’on a désigné notre ViewController comme délégué, ce dernier se chargera d’implémenter les méthodes de délégation, ce qu’il faut mentionner dans notre fichier d’en-tête, en ajoutant <MKMapViewDelegate>

 

#import
#import

@interface ViewController : UIViewController<MKMapViewDelegate>

@end

 

Afficher la Map

 

Jusque-là, si vous exécutez votre application, vous aurez simplement une Map centrée sur la région que vous avez spécifiée dans les réglages de l’iPhone (Réglages > Général > Langue et région > Région). Nous allons, dans cette partie, ajouter deux localisations entre lesquelles nous allons tracer notre trajet. Ouvrez le ficher ViewController.m et, dans l’extension de la classe, ajoutez deux propriétés d’objet de type CLLocation :

 

@property (strong, nonatomic) CLLocation *sourceLocation;
@property (strong, nonatomic) CLLocation *destinationLocation;

 

Un objet CLLocation représente les données de localisation générées par un objet CLLocationManager. Cet objet comporte, entre autres, les coordonnées géographiques et l’altitude du device, ainsi que les valeurs indiquant la précision et la date des mesures. En iOS, cette classe signale également des informations concernant la vitesse et la position du device.

Créez une méthode pour déclarer et afficher deux points GPS sur notre carte :

 

- (void)setupGPSLocations
{
 // 1
 _sourceLocation = [[CLLocation alloc] initWithLatitude:48.831881 longitude:2.379604];
 _destinationLocation = [[CLLocation alloc] initWithLatitude:48.858403 longitude:2.294456];

 // 2
 MKPointAnnotation *point1 = [[MKPointAnnotation alloc] init];
 point1.coordinate = _sourceLocation.coordinate;
 point1.title = @"SOAT";
 point1.subtitle = @"C'est la source";
 [self.mapView addAnnotation:point1];

 MKPointAnnotation *point2 = [[MKPointAnnotation alloc] init];
 point2.coordinate = _destinationLocation.coordinate;
 point2.title = @"Tour Eiffel";
 point2.subtitle = @"C'est la destination";
 [self.mapView addAnnotation:point2];
}

 

  1. Nous allouons ici deux points GPS que nous allons utiliser dans des structures CLLocation. J’ai choisi comme source l’adresse de SOAT et comme destination la tour Eiffel. Eh oui, il m’arrive parfois de vouloir faire le touriste en plein boulot !

     

    Note : Pour ceux qui ne savent pas comment extraire des points GPS à partir de Google Maps, il suffit de chercher l’adresse désirée, de faire un clic droit sur le pin, sélectionnez plus d’infos sur cet endroit et une fiche contenant les coordonnées GPS apparaît au bas de l’écran.

     

  2. Ici, on affecte nos deux points GPS à des objets de type MKPointAnnotation. On ajoutera le titre et la description de chaque annotation. Selon le guide du développeur iOS, afin d’afficher une annotation sur une carte, votre application doit fournir deux objets distincts :
    • Un objet qui est conforme au protocole MKAnnotation et qui gère les données d’annotation. (objet d’annotation)
    • Une vue (dérivée de la classe MKAnnotationView) utilisée pour dessiner la représentation visuelle de l’annotation sur la surface de la carte. (vue d’annotation)

 

En iOS un objet d’annotation est déjà intégré dans la librairie MapKit : MKPointAnnotation. Cet objet fournit déjà une mise en œuvre concrète du protocole MKAnnotation. Vous pouvez faire usage de ces objets simples plutôt que de définir le vôtre chaque fois que vous souhaitez associer un point sur la carte avec tout simplement un titre. Par ailleurs, vous pouvez toujours personnaliser votre annotation en implémentant le protocole et la vue en question. Ici, nous allons garder les choses simples : notre but principal est de montrer comment dessiner la trajectoire entre deux points GPS.

Maintenant appelez votre méthode dans le viewDidLoad :

 

- (void)viewDidLoad
{
 [super viewDidLoad];
 [self setupGPSLocations];
}

 

Et là, si vous exécutez votre code, vous allez pouvoir afficher deux pins en plus sur votre carte.

Mais, attendez un instant, je suis obligé de zoomer pour pouvoir identifier les deux zones géographiques! N’y a-t-il pas un moyen de le faire automatiquement?

 

Votre souhait sera immédiatement exaucé, cher lecteur.

 

Ajouter le zoom sur une région spécifique

 

La méthode qui permet de zoomer sur une zone géographique spécifique est la méthode de la classe MKMapView :

 

- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated;

 

MKCoordinateRegion est, comme son nom l’indique, une structure qui contient les coordonnées d’une région. Nous allons calculer la région sur laquelle il faudra appliquer le zoom pour garder nos deux points en vue. Pour cela, ajoutez cette méthode dans votre fichier ViewController.m :

 

- (void)resizeRegionWithSource:(CLLocation *)source andDestination:(CLLocation *)destination
{
 // 1
 double scaleFactor = 1.5;
 CLLocationDegrees latitudeDelta = (destination.coordinate.latitude - source.coordinate.latitude) * scaleFactor;
 CLLocationDegrees longitudeDelta = (destination.coordinate.longitude - source.coordinate.longitude) * scaleFactor;

 // 2
 if (latitudeDelta &lt; 0)
 {
  latitudeDelta *= -1;
 }

 if (longitudeDelta &lt; 0)
 {
  longitudeDelta *= -1;
 }

 // 3
 MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);

 // 4
 CLLocationDegrees averageLatitude = (destination.coordinate.latitude + source.coordinate.latitude)/2;
 CLLocationDegrees averageLongitude = (destination.coordinate.longitude + source.coordinate.longitude)/2;
 CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(averageLatitude, averageLongitude);

 // 5
 MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
 [_mapView setRegion:[_mapView regionThatFits:region] animated:YES];
}

 

  1. Nous avons passé les deux coordonnées en paramètres et, ici, nous allons calculer le delta entre les latitudes et les longitudes des deux points GPS. Nous allons également les multiplier par un facteur qui correspondra au niveau de zoom voulu. Pour un facteur de 1, vous allez visualiser les deux points sur les bords de l’écran.
  2. Ici, nous nous assurons que le delta des latitudes et celui des longitudes ne sera pas négatif.
  3. Ensuite, nous spécifions le span avec lequel nous allons créer notre région.
  4. Nous calculons les coordonnées GPS du point centre entre la source et la destination, en divisant respectivement les latitudes et longitudes par 2. 
  5. Et enfin, nous créons notre région en nous basant sur la structure MKCoordinateRegion qui va contenir notre span et les coordonnées de notre point central. Nous passons cette région à la méthode SetRegion et voilà!

Pour tester la fonctionnalité qu’on vient de développer, nous allons la mettre dans une IBAction. Rendez-vous dans votre storyboard, ajoutez un UIButton juste en bas de la Map View et créez une IBAction en utilisant la méthode qu’on avait utilisée pour créer l’IBOutlet de la Map View :

 

créer l'iBAction

 

Appelez votre méthode “showDirections”, puisqu’on va l’utiliser également pour afficher l’itinéraire entre nos deux points GPS :

 

- (IBAction)showDirections:(id)sender
{
 [self resizeRegionWithSource:_sourceLocation andDestination:_destinationLocation];
}

 

Exécutez le code, cliquez sur le bouton et contemplez la magie.

 

Santé

 

Et Enfin l’itinéraire

 

Dans cette partie, je vais vous balancer tout le code qui s’occupe de dessiner l’itinéraire dans la Map et je vais le détailler tout de suite après. Ajoutez ce code dans votre méthode showDirections après l’appel de la méthode de zoom :

 

- (IBAction)showDirections:(id)sender
{
 [self resizeRegionWithSource:_sourceLocation andDestination:_destinationLocation];

 // 1
 MKPlacemark *source = [[MKPlacemark alloc] initWithCoordinate:_sourceLocation.coordinate addressDictionary:nil];
 MKMapItem *sourceMapItem = [[MKMapItem alloc] initWithPlacemark:source];

 MKPlacemark *destination = [[MKPlacemark alloc] initWithCoordinate:_destinationLocation.coordinate addressDictionary:nil];
 MKMapItem *distMapItem = [[MKMapItem alloc] initWithPlacemark:destination];

 // 2
 MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
 [request setSource:sourceMapItem];
 [request setDestination:distMapItem];
 [request setTransportType:MKDirectionsTransportTypeAutomobile];

 // 3
 MKDirections *direction = [[MKDirections alloc] initWithRequest:request];
 [direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error)
 {
  if (!error)
  {
   for (MKRoute *route in [response routes])
   {
    [_mapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads];
   }
  }
 }];
}

 

  1. Là, nous découvrons deux nouveaux types d’objets de la librairie MapKit. Pour chaque point GPS, nous déclarons deux objets de type MKMapItem. Cette classe a été introduite à partir d’iOS 6.0. Le but était de faciliter la vie aux développeurs. En effet, cette classe a simplifié l’ouverture des maps avec des URL spéciales et cela, en faisant appel tout simplement à la méthode -openInMapsWithLaunchOptions. En plus, elle permet de tracer des itinéraires et même d’afficher les directions d’une manière exhaustive. MKMapItem fonctionne en conjonction avec la classe MKPlacemark, et ce sont des instances qui sont transmises à MKMapItem pour définir les endroits à afficher dans la map. Dans la méthode d’initialisation du MKPlacemark, j’ai mis “nil” dans le dictionnaire. En fait, il s’agit d’un dictionnaire d’adresse où il faut mettre, si vous le souhaitez, tous les éléments d’adresse du point GPS en question (numéro, rue…).
  2. Comme son nom l’indique, la classe MKDirectionsRequest est une classe qui englobe la requête qui sera passée à la classe MKDirections. Lorsque l’application Plans envoie une URL avec des directions pré-remplies à votre application, vous utilisez justement cette classe pour décoder les contenus de l’URL et de déterminer les points de début et de fin de l’itinéraire. Nous allons, dans notre cas, l’utiliser dans le sens inverse. Pour cela, on aura à définir la source, la destination et le type de transport (“en voiture”).
  3. Enfin nous initialisons un objet de type MKDirections avec l’objet de requête précédemment rempli et on appelle la méthode -calculateDirectionsWithCompletionHandler pour calculer l’itinéraire. La classe MKDirections a été introduite à partir d’iOS 7 et est utilisée pour générer les directions d’un lieu géographique à un autre. Une fois que les points GPS voulus ont été remplis, la classe MKDirections contacte les serveurs d’Apple et attend une réponse. Lors de la réception d’une réponse, un “completion Handler” reçoit la réponse sous la forme d’un objet MKDirectionsResponse. En supposant que les directions ont été trouvées pour l’itinéraire, cet objet contiendra un ou plusieurs objets MKRoute. Chaque objet MKRoute contient la distance, le temps de voyage prévu, les notes de consultation et une MKPolyline qui peut être utilisée pour dessiner l’itinéraire sur la carte. En plus, chaque objet MKRoute contient un tableau d’objets MKRouteStep, qui à leur tour contiennent des informations telles que le texte de description d’une étape virage par virage et les coordonnées pour chaque étape. Dans notre exemple, on est en train d’accéder aux objets MKPolyline et de les dessiner sur notre map.

Rien ne sera dessiné tant que la méthode du delegate rendererForOverlay ne sera pas redéfinie. Cette méthode crée une instance de la classe MKPolylineRenderer puis définit les propriétés telles que la couleur et la largeur de la ligne. Ajoutez cette méthode à la fin de votre contrôleur :

 

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id&lt;MKOverlay&gt;)overlay
{
 if ([overlay isKindOfClass:[MKPolyline class]])
 {
  MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
  [renderer setStrokeColor:[UIColor redColor]];
  [renderer setLineWidth:3.0];
  return renderer;
 }
 return nil;
}

 

Exécutez votre code et contemplez la magie de cette librairie. Si tout se passe bien de votre côté, lorsque vous allez appuyer sur le bouton, vous aurez un itinéraire en rouge entre vos deux points GPS.

mapkit_5

 

Et si vous voulez avoir plus d’informations virage par virage, il suffit d’ajouter une boucle for et d’itérer dans le NSArray steps de l’objet MKRoute :

 

MKDirections *direction = [[MKDirections alloc] initWithRequest:request];
[direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error)
{
 if (!error)
 {
  for (MKRoute *route in [response routes])
  {
   [_mapView addOverlay:[route polyline] level:MKOverlayLevelAboveRoads];
   for (MKRouteStep *step in route.steps)
   {
    NSLog(@"%@", step.instructions);
   }
  }
 }
}];

 

Exécutez votre code et vous allez obtenir les instructions de guidage dans votre log :

mapkit_6

 

Et voilà !

Il y a encore bien des choses à raconter sur cette magnifique librairie. Je pense en avoir introduit une bonne partie, pour vous mettre un peu sur la bonne voie. Je ne peux plus que vous souhaiter un bon développement.

 

Conclusion

 
Les outils utilisés pour cartographier le monde autour de nous sont devenus de plus en plus sophistiqués et omniprésents. Avant la mise à disposition de la classe MKDirections, dessiner un itinéraire dans une map était exténuant. Il est clair qu’Apple a fait beaucoup d’efforts, à partir d’iOS 7, pour introduire plusieurs nouvelles classes dans l’API MapKit, en vue d’améliorer l’expérience utilisateur, de faciliter la vie aux développeurs et surtout de concurrencer les spécialistes des technologies de cartographie.