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>
			html, body{
				height:100%;
				margin:0px;
				padding:0px
			}
			#EmplacementDeMaCarte{
				height:100%
			}
		</style>
	</head>
	<body>
		<div id="EmplacementDeMaCarte"></div>
		<noscript>
			<p>Attention : </p>
			<p>Afin de pouvoir utiliser Google Maps, JavaScript doit être activé.</p>
			<p>Or, il semble que JavaScript est désactivé ou qu'il ne soit pas supporté par votre navigateur.</p>
			<p>Pour afficher Google Maps, activez JavaScript en modifiant les options de votre navigateur, puis essayez à nouveau.</p>
		</noscript>
		<script>
			function initialisation(){
				optionsCarte = {
					center: {lat: 47.389982, lng: 0.688877},
					zoom: 8
				};
				var maCarte = new google.maps.Map(document.getElementById('EmplacementDeMaCarte'), optionsCarte);
			}
		</script>
		<script async defer src="https://maps.googleapis.com/maps/api/js?key=InsérezVotreCléApiGoogleMapsIci&callback=initialisation"></script>
	</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 autre balise <script/> qui contiendra le code pour le chargement d'une carte.
  • Ajouter une balise <script/> pour inclure le code de l'API Google Maps Version 3. L'attribut async permet de charger/exécuter le script de façon asynchrone et l'attribut defer permet de différer l'exécution à la fin du chargement du document.

Carte simple

Création du widget DistanceWidget

L'étape suivante consiste à créer le widget nommé 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 Map.

Le widget sera également une sous classe de l'instance 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 que vous n'ayez à ajouter de code supplémentaire.

É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 cette propriété, alors la propriété de l'objet B sera mise à jour automatiquement afin d'être identique à 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. Dans la section suivante, nous allons créer 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 nommé RadiusWidget qui sera responsable de l'affichage d'un cercle, qui sera éventuellement redimensionnable. Cette étape est pratiquement similaire à la précédente, mais au lieu de créer un marqueur, nous allons créer 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, nous ajoutons 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.

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 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.

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 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 nommé 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.