[GIS] How to use Google Maps projection with GeoDjango/PostGIS

coordinate systemgeodjangogeosgoogle mapspostgis

I am building a web service with GeoDjango which involves user submitted events including a location. I created a model field for my location using django.contrib.gis.db:

location = models.PointField(srid=900913)

I chose to specify 900913 as I read that this is the projection used by Google Maps and I am getting the location by placing a marker on a Google map. The coordinate is presented to me in lat/lng form. I am storing this point in a PostGIS DB using GEOS:

location = geos.Point(data['lng'], data['lat'], srid=900913)

If I want to view this point on a map, I get the lat and lng from my DB (e.g. lat = location.coords[1]) and use them to centre my map and this works fine.

When I want to browse a map and display events from DB which lie inside the map bounds I use GeoDjango's within query and the maps bounds (in lat/lng format):

    bounds = geos.Polygon.from_bbox((swLng, swLat, neLng, neLat))
    events = Event.objects.filter(location__within=bounds)

This appears to work fine and I display pins on the map corresponding to the lat/lng of these events.

So far nothing would suggest that anything is going wrong, however I am completely new to this stuff and I want to ensure that I'm getting it right for when I inevitably want to use my location data in more complex ways. The reason I'm suspicious is that my Django admin pane does not display the location correctly on the OpenStreetMap. It shows a point which appears to correspond to the (0,0) point shown here. The displayed text version of the location field is SRID=900913;POINT(-1.277482509613037 50.874104373286066), which is clearly still in lat/lng. As I move my mouse around the admin map I can see the displayed coords in the bottom right corner are in 900913 format, and not in lat/lng.

Please can you explain how I can store my location points in the correct format, and what advantages this has over simply using lat/lng (my guess is that if I want to specify say a distance in km for lookups, I can't use my lat/lng locations).

Best Answer

The points being returned to you by Google Maps are in lat/lng, which almost always these days means SRID=4326. So change your geometry SRID in your PostGIS table to that:

location = geos.Point(data['lng'], data['lat'], srid=4326)

Then, when displaying points on a web map the projection of the map is commonly set to the Spherical Mercator coordinate system, because it seems to look better. Coordinates in this projection are in meters from an origin, not lat/lng. Set your web map to EPSG:3857 (the same definition as 900913, which has been deprecated). You will need to reproject any geometry (your points) into this projection - fortunately, web map APIs do this pretty simply and effeciently. The way you do it is slightly different in each web map API, but you should set the map projection when you create it (OpenLayers example):

 var mOptions = {
   projection: new OpenLayers.Projection("EPSG:3857"),
   displayProjection: new OpenLayers.Projection("EPSG:4326"),
   units: "m",
   numZoomLevels: 18,
   maxResolution: 156543.0339,
   maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508)
   };
   map = new OpenLayers.Map("map", mOptions);

Then when you add your points to the map transform them from lat/lng to the map coordinate system:

 var geojson_format = new OpenLayers.Format.GeoJSON({
    'internalProjection': new OpenLayers.Projection('EPSG:3857'),
    'externalProjection': new OpenLayers.Projection('EPSG:4326')
    });
 var theFeature = geojson_format.read(json[i].geometry);