Django recipe: Use extent of a queryset to set the zoom


Here's a fun trick from Django's geospatial extensions that can come in handy if you're ultimately displaying your points with a browser-based mapping system like OpenLayers.

I sometimes find myself in a situation where I'd like the map to zoom in on a set of a points, but I'm not sure ahead of time where those points will be. Imagine I have a database of all the hotdog stands in America—and a tool that allows users to filter that set any number of ways, like state, county or address.

Here's a simple imaginary query.

>> from models import HotdogStand
>> queryset = HotdogStand.objects.filter(county='Johnson', state='Iowa')

Now here's the challenge: How can I write a OpenLayers template that will always center the map on my queryset, regardless of what filter is applied, be it Johnson County, Iowa, or every point within 10 miles of Dodger Stadium.

I'm not sure I know the best way, but here's something I cooked up.

The function below accepts a queryset, along with its srid code. It grabs the queryset's extent and loads it into an object. Then it converts that new object to srid 900913, the projection used by OpenLayers when displaying Google Maps tiles, as I do on my sites.

from django.contrib.gis.geos import fromstr

def get_extent_for_openlayers(geoqueryset, srid):
    Accepts a GeoQuerySet and SRID. 
    Returns the extent as a GEOS object in the Google Maps projection system favored by OpenLayers.
    The result can be directly passed out for direct use in a JavaScript map.
    extent = fromstr('MULTIPOINT (%s %s, %s %s)' % geoqueryset.extent(), srid=srid)
    return extent

The function can be called like so in a Django view.

>> from get_extent_for_openlayers import get_extent_for_openlayers
>> extent = get_extent_for_openlayers(queryset, 4326)

And then utilized as a dynamic method for setting the map's extent in your OpenLayers templates.

{% if extent %}
var wkt_f = new OpenLayers.Format.WKT();
var extent ='{{ extent.wkt }}');
bounds = extent.geometry.getBounds();
{% endif %}

Voilà. That's the whole trick. It seems to work for me, but if there's something I screwed up, feel free to tell me so.