Le type geography a fait son apparition avec SQL Server 2008R2. Son but est de stocker et manipuler des données spatiales. En gros il permet de représenter des points, lignes ou polygônes sur le globe et de faire des calculs du type distances entre 2 figures, intersection, etc.

Nous allons réaliser une application web permettant de proposer des lieux intéressants près de là où se situe l’utilisateur grâce à la géolocalisation. Ces lieux sont présentés sur un carte Google Maps sous la forme de points ou de surfaces.

Création de la base de données

La base de données contient une seule table contenant pour chaque lieu, un nom, une description et sa position :

CREATE TABLE [dbo].[Place] (
 [Id]          [uniqueidentifier] NOT NULL,
 [Name]        [nvarchar] (256) NOT NULL,
 [Location]    [geography] NULL,
 [Description] [nvarchar](256) NULL,
)

Et ajoutons quelques lignes :

INSERT [dbo].[Place] ([Id], [Name], [Location], [Description]) 
VALUES (N'9dcae9a1-67a1-472c-9e4e-84b3aef44159', N'SoftFluent', geography::STGeomFromText('POINT (2.299617 48.761214)', 4326), N'Une équipe d''experts en développement sur les technologies Microsoft')

INSERT [dbo].[Place] ([Id], [Name], [Location], [Description]) 
VALUES (N'584cdd45-6afe-418f-9491-84eefb31b1d0', N'Tour Eiffel', geography::STGeomFromText('POINT (2.294423 48.858399)', 4326), N'La tour Eiffel est une tour de fer puddlé de 324 mètres de hauteur située à Paris, à l''extrémité nord-ouest du parc du Champ-de-Mars en bordure de la Seine dans le 7? arrondissement.')

INSERT [dbo].[Place] ([Id], [Name], [Location], [Description]) 
VALUES (N'dce96053-00a6-47ca-9fe3-36af39b3ae29', N'Parc de sceaux', geography::STGeomFromText('POLYGON ((2.299424 48.763629, 2.299896 48.762158, 2.303898 48.762908, 2.306269 48.765722, 2.304778 48.766309, 2.305666 48.767943, 2.303597 48.774081, 2.304488 48.774222, 2.30334 48.777538, 2.29729 48.776651, 2.297156 48.777022, 2.296544 48.776987, 2.296582 48.776113, 2.2956 48.776333, 2.294425 48.775148, 2.294608 48.774618, 2.286411 48.773253, 2.290579 48.766338, 2.299424 48.763629))', 4326), N'Un endroit paisible pour se reposer')

Nous avons également besoin de créer une procédure stockée pour sélectionner uniquemenent les emplacements proches de la position de l’utilisateur :

CREATE PROCEDURE [dbo].[Place_LoadByDistance]
(
 @Location [geography],
 @maxDistance [float]
)
AS
SET NOCOUNT ON
SELECT [Place].[Id], [Place].[Name], [Place].[Location], [Place].[Description] 
    FROM [Place] 
    WHERE (@Location.STDistance([Place].[Location]) < @maxDistance)

RETURN
GO

On voit qu’avec le type geography le calcul de la distance est très simple : quelque soit la figure (Point, Line, Polygon) il suffit d’appeler la méthode STDistance. Cette méthode renvoie la distance en mètres car nous avons utilisé le SRID 4326 (voir les 3 INSERT ci-dessus). On peut d’ailleur vérifier cela avec la requête suivante :

SELECT * FROM sys.spatial_reference_systems 
WHERE spatial_reference_id = 4326
STDistance

Création de l’API

L’API utilise Web API. La seule problématique est de transmettre le type SqlGeography. Le plus simple dans notre cas (limité à des Points ou des Polygônes) est de transformer ce type en une liste de points réprésentant la latitude et la longitude :

public class PlaceController : ApiController
{
    [HttpGet]
    public IEnumerable<PlaceModel> Get(double latitude, double longitude, double distance)
    {
        SqlGeography location = SqlGeography.Point(latitude.Value, longitude.Value, 4326);
        return PlaceCollection.LoadByDistance(location, distance.Value).Select(point => new PlaceModel(point));
    }
}

public class PlaceModel
{
    public PlaceModel(Place pointOfInterest)
    {
        Name = pointOfInterest.Name;
        Description = pointOfInterest.Description;
        SqlInt32 pointCount = pointOfInterest.Location.STNumPoints();
        Location = new LatitudeLongitude[(int)pointCount];
        for (int i = 0; i < pointCount; i++)
        {
            SqlGeography point = pointOfInterest.Location.STPointN(i + 1); // 1 based index
            Location[i] = new LatitudeLongitude((double)point.Long, (double)point.Lat);
        }
    }

    public string Name { get; }
    public string Description { get; }
    public LatitudeLongitude[] Location { get; }
}

public class LatitudeLongitude
{
    public LatitudeLongitude(double longitude, double lat)
    {
        Lat = lat;
        Long = longitude;
    }

    public double Lat { get; }
    public double Long { get; }
}

Il ne reste plus qu’à créer la page web.

Création de l’interface graphique

La première étape est d’enregistrer l’application auprès de Google afin d’obtenir une clé publique :

carte d'interface graphique

On peut ensuite ajouter le script dans notre page (replacez <public key> par votre clé) :

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<public key>"></script>

Puis on initialise la carte :

<div id="map-canvas"></div>
<style type="text/css">
    #map-canvas {
        height: 500px;
        margin: 0;
        padding: 0;
    }
</style>

<script type="text/javascript">
    function initialize() {
        var mapOptions = {
            center: new google.maps.LatLng(48.761214, 2.299617),
            zoom: 14
        };
        var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
        }
    }
    google.maps.event.addDomListener(window, 'load', initialize);
</script>

Maintenant il faut récupérer la position de l’utilisateur via l’API de géolocalisation :

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function (position) {
        var req = new XMLHttpRequest();
        req.open('GET', '/api/place?latitude=' + position.coords.latitude + '&longitude=' + position.coords.longitude + '&distance=2000', true);
        req.onreadystatechange = function () {
            if (req.readyState === 4) {
                if (req.status === 200) {
                    var places = JSON.parse(req.responseText);

                    for (var i = 0; i < places.length; i++) {
                        var place = places[i];
                        addPlace(map, place);
                    }

                } else {
                    console.error("Erreur pendant le chargement de la page.");
                }
            }
        };
        req.send(null);
    });
}

Pour ajouter un élement sur la carte il faut différencier s’il s’agit d’un simple point (Marker dans le jargon Google Maps) ou d’une zone (Polygon). Par défaut rien ne se passe lorsque l’on clique sur un marker. On ajoute donc une popup (InfoWindow) au clic :

function addPlace(map, place) {

    // Popup on click
    var infowindow = new google.maps.InfoWindow({
        content: place.Description
    });

    if (place.Location.length === 1) {
        // Point
        var marker = new google.maps.Marker({
            position: locationToLatLng(place.Location[0]),
            map: map,
            title: place.Name
        });

        google.maps.event.addListener(marker, 'click', function () {
            infowindow.open(map, marker);
        });

    } else {
        // Polygon
        var paths = place.Location.map(locationToLatLng);
        var polygon = new google.maps.Polygon({
            paths: paths,
            map: map,
            strokeColor: '#00FFFF',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#00FFFF',
            fillOpacity: 0.35,
            title: place.Name
        });

        google.maps.event.addListener(polygon, 'click', function () {
            infowindow.open(map, polygon);
        });
    }
}

Conclusion

Grâce au type geography, Google Maps et la géolocalisation, il est possible de mettre en place rapidement une interface graphique présentant des données géographiques spécifiques à la position de l’utilisateur.

Ne ratez plus aucunes actualités avec la newsletter mensuelle de SoftFluent