Créer un cercle dont le centre et le rayon sont déplaçables

Polygone Polygon exemples et tutoriels en Français

API Google Maps JavaScript version 3

Partager ce tutoriel sur les réseaux sociaux
Signaler une erreur dans cet article

Créer un cercle dont le centre et le rayon peuvent être modifiés

L'API Google Maps Javascript V3 a introduit la mise en œuvre du Modèle Vue Contrôleur (MVC) qui permet aux objets carte de stocker leur état et de mettre à jour automatiquement leur présentation, ce qui est génial, mais comment procéder ?

Cet article présente une introduction sur l'utilisation de base des objets MVC dans la Version 3. Vous apprendrez à utiliser le framework MVC de l'API Google Maps Javascript V3 pour créer des objets qui répondent automatiquement aux changements d'état. Vous allez construire un widget permettant de redimensionner une 'distance' et à l'issue, vous appréhendrez mieux les objets MVC, comment les utiliser, et pourquoi ils sont tellement pratiques.

Avant de commencer, jetez un coup d'oeil sur la référence de l'API, et en particulier sur la classe MVCObject.

Créer une carte basique sans cercle

Commençons par créer une page HTML5 affichant une carte.

<!DOCTYPE html>
<html lang="fr">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
		<meta charset="UTF-8" />
		<title>les objets MVC c'est Fun</title>
		<style type="text/css">
		#EmplacementDeMaCarte {
			height: 500px;
		}
		</style>
		<script type="text/javascript"	src="http://maps.googleapis.com/jsapi?autoload={'modules':[{name:'maps',version:3,other_params:'sensor=false'}]}"></script>
		<script type="text/javascript">
			function init(){
				optionsCarte = {
					center: new google.maps.LatLng(47.389982, 0.688877),
					zoom: 8,
					mapTypeId: google.maps.MapTypeId.ROADMAP
				};
				var maCarte = new google.maps.Map(document.getElementById('EmplacementDeMaCarte'), optionsCarte);
			}
			google.maps.event.addDomListener(window, 'load', init);
		</script>
	</head>
	<body>
		<div id="EmplacementDeMaCarte"></div>
	</body>
</html>

Explication succincte du code ci-dessous :

  • Ajouter un DOCTYPE en haut de la page, ici un DOCTYPE HTML5. Cette insruction indique au navigateur comment afficher la page.
  • Créer une section de style CSS pour fixer la hauteur de la carte à '500px. Il n'est pas nécessaire d'indiquer la largeur d'affichage de la carte car, par défaut, la propriété d'une balise <div/> est 'block' ce qui signifie qu'elle occupera toute la place disponible dans le sens de la largeur.
  • Ajouter une balise <script/> pour inclure le code de l'API Google Maps Version 3.
  • Ajouter une autre balise <script/> qui contiendra le code minimal pour le chargement d'une carte. L'élément clé ici est l'auditeur d'événement qui appelle la fonction init() une fois la page chargée. Il s'agit d'une bonne habitude à prendre, plutôt que d'utiliser des techniques telles que le onload, car elle permet la séparation du code HTML du code Javascript et permet également à d'autres fonctions de joindre le même événement.
Carte simple

Création du widget DistanceWidget

L'étape suivante consiste à créer le widget DistanceWidget.

Ce widget est constitué essentiellement :

  • d'un cercle,
  • d'un premier marqueur placé au centre du cercle nommé marqueurCentreCercle,
  • d'un second marqueur situé sur la circonférence du cercle nommé marqueurRayonCercle.

Lorsque le marqueur situé au centre du cercle est déplacé, il déplacera également :

  • le cercle
  • et le marqueur situé sur sa circonférence.

Lorsque le marqueur situé sur la circonférence du cercle est déplacé, le rayon du cercle augmentera ou diminuera selon que le marqueur est éloigné ou rapproché du centre.

Tout d'abord, créons le constructeur DistanceWidget qui aura pour paramètre 'objCarte', qui est une instance de la classe google.maps.Map.

Le widget sera également une sous classe de l'instance google.maps.MVCObject et qui se fait en mettant le prototype de l'objet sur MVCObject.

Ensuite, créer un marqueur déplaçable, le placer au centre de la carte et utiliser les techniques MVC pour lier les propriétés.

/**
 * @description DistanceWidget permet d'afficher un cercle qui peut être redimensionné et fourni le rayon en km.
 * @param {google.maps.Map} : objCarte : Carte à la quelle on attache le widget distance.
 * @constructor
 */
function DistanceWidget(objCarte) {
	/**
	 * Définit la propriété 'map' du DistanceWidget sur objCarte
	 */
	this.set('map', objCarte);
	
	/**
	 * Définit la propriété 'position' du DistanceWidget sur objCarte.getCenter());
	 */
	this.set('position', objCarte.getCenter());
	
	/**
	 * Options du marqueur nommées optionsMarqueurCentreCercle
	 * - draggable : true - Le marqueur est déplaçable
	 * - title : 'Déplacez moi !' - titre s'affichant lors du survol du marqueur
	 */
	var optionsMarqueurCentreCercle = {
		draggable: true,
		title: 'Déplacez moi !'
	}
	
	/**
	 * Création d'un nouveau marqueur nommé marqueurCentreCercle
	 * auquel on applique les options nommées optionsMarqueurCentreCercle
	 */
	var marqueurCentreCercle = new google.maps.Marker(optionsMarqueurCentreCercle);
	
	/**
	 * Relie la propriété 'map' du marqueurCentreCercle
	 * à la propriété 'map' du DistanceWidget
	 */
	marqueurCentreCercle.bindTo('map', this);
	
	/**
	 * Relie la propriété 'position' du marqueurCentreCercle
	 * à la propriété 'position' du DistanceWidget
	 */
	marqueurCentreCercle.bindTo('position', this);
}

/**
 * Le DistanceWidget hérite de la classe MVCObject
 */
DistanceWidget.prototype = new google.maps.MVCObject();

Ajouter à la fonction init() du code pour créer un nouveau DistanceWidget.

var distanceWidget = new DistanceWidget(maCarte);

Les éléments importants à retenir sont les deux méthodes bindTo() qui ont été utilisées. En liant la propriété 'map' du marqueurCentreCercle au DistanceWidget, cela signifie que ces valeurs sont désormais indissociables. Donc, si la carte est changée en utilisant distanceWidget.set('map', monAutreCarte); alors le DistanceWidget changera de carte sans avoir à ajouter de code supplémentaire. Plutôt cool non ;) ?

Cela peut paraître déroutant au début, mais il s'agit de la façon la plus simple de penser à ce qu'il est. Étant donné qu'il y a deux objets, A et B, liés par la même propriété, si l'objet A met à jour la propriété liée, alors la propriété de l'objet B sera mise à jour pour refléter la nouvelle valeur de l'objet A et vice versa.

Maintenant, vous avez une carte avec un marqueurCentreCercle que vous pouvez faire glisser. Lors du déplacement, l'objet MVC met à jour la propriété 'position' du DistanceWidget avec la propriété 'position' du marqueurCentreCercle. Cela peut sembler peu de chose, mais c'est un début ! Dans la section suivante, vous créerez un RadiusWidget qui affichera un cercle.

Carte avec un marqueur déplaçable

Lier les propriétés entre elles

Créons un autre widget appelé RadiusWidget qui sera responsable de l'affichage d'un cercle qui sera éventuellement redimensionnable. Cette étape est très similaire à la précédente, mais au lieu de créer un marqueur, on créé un cercle.

Ajouter le code suivant à votre section javascript.

/**
 * @description RadiusWidget est un widget qui ajoute un cercle sur la carte et le centre sur un marqueur.
 * @constructor
 */
function RadiusWidget() {

	/**
	 * Option du cercle nommée optionsCercle :
	 * - épaisseur du trait : 2 pixels
	 */
	var optionsCercle = {
		strokeWeight: 2
	}

	/**
	 * Création d'un nouveau cercle nommé monCercle
	 * auquel on applique les options optionsCercle
	 */
	var monCercle = new google.maps.Circle(optionsCercle);

	/**
	 * Définit la valeur de la propriété 'distance': par défaut 50 km.
	 */
	this.set('distance', 50);

	/**
	 * Relie la propriété 'bounds' du RadiusWidget à la propriété 'bounds' du cercle.
	 */
	this.bindTo('bounds', monCercle);

	/**
	 * Relie la propriété 'center' du cercle à la propriété 'center' du RadiusWidget
	 */
	monCercle.bindTo('center', this);

	/**
	 * Relie la propriété 'map' du cercle à la propriété 'map' du RadiusWidget
	 */
	monCercle.bindTo('map', this);

	/**
	 * Relie la propriété 'radius' du cercle à la propriété 'radius' du RadiusWidget
	 */
	monCercle.bindTo('radius', this);
}

/**
 * Le RadiusWidget hérite de la classe MVCObject
 */
RadiusWidget.prototype = new google.maps.MVCObject();


/**
 * Mise à jour du rayon lorsque la distance change.
 */
RadiusWidget.prototype.distance_changed = function(){
	this.set('radius', this.get('distance') * 1000);
};

Ensuite, ajoutez ce code au constructeur du DistanceWidget pour créer le radiusWidget.

/**
 * Création d'un nouveau radiusWidget
 */
var radiusWidget = new RadiusWidget();

/**
 * Relie la propriété 'map' du radiusWidget à la propriété 'map' du DistanceWidget
 */
radiusWidget.bindTo('map', this);

/**
 * Relie la propriété 'center' du radiusWidget à la propriété 'position' du DistanceWidget
 */
radiusWidget.bindTo('center', this, 'position');

En analysant le code vous pouvez constater que :

  • Le RadiusWidget est lié au DistanceWidget par la propriété 'map',
  • Le RadiusWidget a lié sa propriété 'center' à la propriété 'position' du DistanceWidget,
  • Le RadiusWidget définit une propriété 'distance' sur lui-même,
  • Le cercle est lié aux propriétés 'center', 'map' et 'radius' du RadiusWidget.

Cette étape présente la méthodologie permettant de surveiller les changements intervenant au niveau d'un objet MVC. Chaque fois que la propriété 'distance' sera définie à l'aide de la méthode set(), un événement nommé distance_changed est déclenché. Vous pouvez écouter cet événement en utilisant google.maps.event.addListener. La fonction distance_changed() met à jour la propriété 'radius' du cercle.

Parce que le widget utilise le 'kilomètre' comme unité de mesure, alors que le cercle utilise le 'mètre', cette fonction vous permet de convertir et définir la propriété 'radius' du cercle. Si vous souhaitez toujours travailler avec des mètres, vous pouvez lier le 'radius' directement à la propriété 'distance' et ne plus du tout avoir besoin de la fonction distance_changed().

À ce stade, vous disposez d'un marqueur et d'un cercle et quand vous déplacez le marqueur, le cercle se déplace avec lui. Amusant n'est-ce pas ?

Le marqueur est déplaçable. Le centre du cercle est lié au point d'ancrage du marqueur.
Par conséquent, le centre du cercle suit les déplacements du marqueur.

Ajouter un marqueur pour redimensionner le cercle

Il est temps maintenant d'ajouter un autre marqueur à l'ensemble. Cette fois ci, nous allons ajouter un marqueur au radiusWidget qui sera utilisé pour ajuster le rayon du cercle. Ce second marqueur sera désigné par la suite sous le nom de : 'marqueurRayonCercle'.

Ces étapes consistent à :

  • Ajouter un marqueur au radiusWidget
  • Lier la propriété 'map' du marqueur à la propriété 'map' du radiusWidget'
  • Lier la propriété 'position' du marqueur au radiusWidget via une nouvelle propriété appelée sizer_position
  • Ajouter une fonction center_changed() qui va positionner le marqueur sur la circonférence du cercle

Créer une fonction appelée addSizer_() qui permettra de créer un marqueur et le reliera à la propriété 'map' du radiusWidget. Ajouter un autre bindTo() de sorte que la propriété 'position' du 'marqueurRayonCercle' et la propriété 'sizer_position' du radiusWidget soient reliées ensemble. Ceci nous conduit à noter un autre point intéressant de MVC. Vous pouvez lier une propriété à n'importe qu'elle autre propriété, et si elle n'existe pas déjà elle sera automatiquement créée.

/**
 * @description Ajouter le marqueur nommé 'marqueurRayonCercle' sur la carte.
 * @private
 */
RadiusWidget.prototype.addSizer_ = function(){

	/**
	 * Options du marqueur nommées optionsMarqueurRayonCercle
	 * - draggable : true - Le marqueur est déplaçable
	 * - title: 'Déplacez moi !' - titre s'affichant lors du survol du marqueur
	 */
	var optionsMarqueurRayonCercle = {
		draggable: true,
		titre: 'Déplacez moi !'
	}
	
	/**
	 * Création d'un nouveau marqueur nommé marqueurRayonCercle
	 * auquel on applique les options nommées optionsMarqueurRayonCercle
	 */
	var marqueurRayonCercle = new google.maps.Marker(optionsMarqueurRayonCercle);
	
	/**
	 * Relie la propriété 'map' du marqueur nommé marqueurRayonCercle
	 * à la propriété 'map' du RadiusWidget
	 */
	marqueurRayonCercle.bindTo('map', this);
	
	/**
	 * Relie la propriété 'position' du marqueur nommé marqueurRayonCercle
	 * à la propriété 'sizer_position' du RadiusWidget
	 */
	marqueurRayonCercle.bindTo('position', this, 'sizer_position');
};

Ajouter this.addSizer_(); à la fin du constructeur radiusWidget.

/**
 * Mise à jour du 'center' du cercle et place le marqueur 'marqueurRayonCercle' de nouveau sur la circonférence.
 * 'position' est liée au DistanceWidget donc cela devrait changer lorsque la position du widget distance est modifiée.
 */
RadiusWidget.prototype.center_changed = function() {

	/**
	 *
	 */
	var bounds = this.get('bounds');

	/**
	 * Les limites 'bounds' ne seront pas toujours définies,
	 * donc vérifier en premier que 'bounds' existe
	 */
	if (bounds) {

		/**
		 *
		 */
		var lng = bounds.getNorthEast().lng();

		/**
		 * Placer le marqueur nommé 'marqueurRayonCercle' au centre 'center',
		 * et à droite sur le cercle.
		 */
		var position = new google.maps.LatLng(this.get('center').lat(), lng);
		
		/**
		 *
		 */
		this.set('sizer_position', position);
	}
};

Créer une fonction sur radiusWidget appelée center_changed(). Cette fonction sera appelée chaque fois que la valeur 'center' est mise à jour et positionnera le marqueur 'marqueurRayonCercle' sur le bord droit du cercle.

Sur la carte ci-dessous, faites glisser le marqueur central. Remarquez comment le marqueur 'marqueurRayonCercle' est maintenant placé sur le bord du cercle et bouge lorsque vous le faites glisser. Belle fantaisie n'est-ce pas ?

Un nouveau marqueur déplaçable est ajouté sur la circonférence du cercle. Son rôle sera de pouvoir modifier le rayon du cercle.

Mettre en œuvre

Dans la section suivante vous déterminerez la distance entre le marqueur central et le marqueur 'marqueurRayonCercle' chaque fois que le marqueur 'marqueurRayonCercle' sera déplacé, puis cette distance entre les deux marqueurs définira le rayon du cercle. Cette technique implique ce qui suit :

  • Ajouter une fonction qui calcule la distance entre deux emplacements google.maps.LatLng
  • Ajouter une fonction qui fixe la propriété distance du cercle en se basant sur la distance séparant les deux marqueurs
  • Ajouter un auditeur d'événement à l'événement drag du marqueur marqueurRayonCercle pour définir le rayon du cercle
  • Lier le DistanceWidget aux propriétés distance et bounds du radiusWidget

Ajoutez le code suivant :

/**
 * @description Calcule la distance en km entre deux emplacements latlng.
 * @see http://www.movable-type.co.uk/scripts/latlong.html
 * @param {google.maps.Latlng} p1 Le premier point lat lng.
 * @param {google.maps.Latlng} p2 Le second point lat lng.
 * @return {numéro} La distance en km entre les deux points.
 * @private
 */
RadiusWidget.prototype.distanceBetweenPoints_ = function(p1, p2) {
	if (!p1 || !p2) {return 0;}
	var R = 6371; // Rayon de la terre en km
	var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
	var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
	var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
	Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
	Math.sin(dLon / 2) * Math.sin(dLon / 2);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	var d = R * c;
	return d;
};


/**
 * Définit la 'distance' du cercle en fonction de la 'position' du marqueur 'marqueurRayonCercle'.
 */
RadiusWidget.prototype.setDistance = function() {
	/**
	 * Lorsque l'on déplace le marqueur nommé 'marqueurRayonCercle',
	 * sa 'position' change.
	 * Parce que 'sizer_position' du RadiusWidget est liée à la 'position'
	 * du marqueur 'marqueurRayonCercle', Il changera aussi.
	 */
	var pos = this.get('sizer_position');
	var center = this.get('center');
	var distance = this.distanceBetweenPoints_(center, pos);

	/**
	 * Définit la propriété 'distance' pour tous les objets qui lui sont liés
	 */
	this.set('distance', distance);
};

La fonction distanceBetweenPoints_() qui a été ajoutée, convertira en kilomètres la distance entre les deux emplacements google.maps.LatLng. Pour plus d'informations sur cette fonction et d'autres semblables, consultez http://www.movable-type.co.uk/scripts/latlong.html. Ensuite, créez une fonction appelée setDistance_ qui fixe la propriété 'distance' du radiusWidget en se basant sur la distance entre les deux marqueurs.

Ensuite, dans la fonction addSizer_ ajouter un auditeur à l'événement 'drag' du marqueur 'marqueurRayonCercle'.

var that = this;
google.maps.event.addListener(marqueurRayonCercle, 'drag', function() {
	/**
	 * Définit la distance orthodromique (rayon)
	 */
	that.setDistance ();
});

Enfin, dans le constructeur du DistanceWidget, lier les propriétés 'distance' et 'bounds' du radiusWidget.

/**
 * Relie la propriété 'distance' du DistanceWidget à la propriété 'distance' radiusWidgets
 */
this.bindTo ('distance', radiusWidget);

/**
 * Relie la propriété 'bounds' du DistanceWidget à la propriété 'bounds' radiusWidgets
 */
this.bindTo ('bounds', radiusWidget);

Maintenant vous disposez d'un marqueur central qui, lorsqu'il est déplacé, déplace également le cercle et le marqueur nommé marqueurRayonCercle. Vous avez également un marqueur marqueurRayonCercle qui, lorsqu'il est déplacé, redimensionne automatiquement le cercle.

Le marqueur déplaçable situé sur la circonférence du cercle permet désormais de modifier le rayon du cercle.

Exploiter les informations

La dernière partie montre comment les propriétés de MVC agissent aussi comme des événements. Dans le contexte du DistanceWidget , vous pouvez ajouter des auditeurs d'événements aux propriétés que vous souhaitez surveiller. Dans ce cas, ajoutez un auditeur aux événements distance_changed et position_changed qui appelleront une fonction qui affichera les valeurs.

À la fin de la fonction init ajouter :

google.maps.event.addListener(distanceWidget, 'distance_changed', function (){
	displayInfo(distanceWidget);
});

Ajouter aussi une nouvelle fonction displayInfo()

function displayInfo(widget) {
	var info = document.getElementById('EmplacementInfos');
	info.innerHTML = 'Position:' + widget.get('position') + ', Distance: " + widget.get('distance');
}

Enfin, ajouter une balise <div/> avec un identifiant id='EmplacementInfos' après la balise <div/> de la carte pour disposer d'un emplacement où afficher ces informations.

<div id="EmplacementDeMaCarte"></div>
<div id="EmplacementInfos"></div> 

Maintenant, lorsque vous ferez glisser le marqueur central, vous verrez la valeur de 'position' changer, et lorsque vous redimensionnerez le cercle, vous verrez la valeur de 'distance' changer.

Affichage instantané des coordonnées du centre du cercle et de son rayon.

Que peut-on en faire ?

Vous pouvez connecter cet exemple à un autre service, tel que Twitter, qui dispose d'une API vous permettant de rechercher des tweets dans un rayon donnée par rapport à un emplacement latlng ( lire la suite ).

Vous pouvez voir le résultat final de la connexion à l'API de Twitter.