OGC WFS for Django released on GitHub

The project can be found at  https://github.com/JeffHeard/ga_ows

Core to Geoanalytics are the Open Geographic Consortium’s Open Web Services. ga_ows is a reusable GeoDjango webapp that provides you the ability to expose GeoDjango models and Python objects as geographic webservices.

A geographic webservice allows you to access data in your GeoDjango models by bounding box and other filters; these data can then be imported into other geographic databases, or perhaps more importantly as layers on a map. For layering WFS services, see the OpenLayers project .

How does it work? OWS is based on Django’s class-based generic views. The following in your urls.py will create a WFS service for a the models in your app:

from ga_ows.views.wfs import WFS
from myapp import models as m

urlpatterns = patterns('',

# ...

    url(r'^wfs/?', WFS.as_view(
        models=[m.MyModel1, m.MyModel2], # everything but this is optional.
        title='My app\'s WFS',
        keywords=['some','keywords'],
        fees='one dollar',
        provider_name='RENCI',
        addr_street='100 Europa Dr. ste 529',
        addr_city='Chapel Hill',
        addr_admin_area='NC',
        addr_postcode='27515',
        addr_country='USA',
        addr_email='jeff@renci.org'
    )),

# ...

)

This will create a WFS endpoint at $django_server/$myapp_root/wfs that serves up features in GML and any of the formats that support the creation of single-file dataset in OGR_ (note that for now this means shapefiles are not supported for output since they require multiple files, although they will be in the near future).

Adding style to your OpenLayers

OpenLayers supports a subset of the SLD standard for stylesheets. Anything in an OpenLayers StyleMap can also be put into an SLD and parsed by the OpenLayers SLD parser. SLDs are limited in that they cannot perform smooth gradients, cannot combine multiple properties into a single output (you can get around this by imputing properties), as well as other limitations, but for many rendering tasks they can be quite useful.

The SLD is generally stored as a separate XML file on the server, then loaded asynchronously, applying the styles to layers as they are added to the map:

OpenLayers.loadURL("tasmania/sld-tasmania.xml", null, null, complete);

function getDefaultStyle(sld, layerName) {
	var styles = sld.namedLayers[layerName].userStyles;
	var style;
	for(var i=0; i

OpenLayers.loadURL is a proxy for the asynchronous XmlHTTPRequest method, just like in WFS, and this means that they must reside on the same server as the one serving the page, or they must be accessible via proxy. It also means that you cannot directly apply an SLD, but must instead wait to load layers until the SLD is completely loaded. Generally this is achieved with a callback method to the loadURL call – in this case, complete.

The getDefaultStyle function is a convenience function that traverses through a style document and finds a named style which can be associated with a layer.

Data driven feature styling

When the SLD standard fails to encompass your styling needs, and it often will, you’re going to have to style features by hand. In OpenLayers, a style is a bare Javascript object with no required methods and a number of optional properties attached to either a layer as a whole or to a single feature (unstyled features inherit the style of the layer). A complete list of those style properties is given at the end of this chapter, but the meat of this section will be exploring best practices.

Styling your own features means creating a number of style objects and attaching those to the feature data or a whole layer. OpenLayers provides a way to both use constants and to use feature properties to style a layer. If the features don’t contain all the properties in the form you need to do your styling, pass the features through a pre-processing function that adds attributes to the feature containing the styling values:

function styleByCityAttributes(feature) { 
	var loColor = parseInt(“0x0000FF”)
	var hiColor = parseInt(“0xFF0000”)
	var v = feature.attributes.population;
	var minV = 1000.0;
	var maxV = 1000000.0;
	if (v > maxV) { v=maxV; }
	if (v < minV) { v=minV; } 
	var x = (v-minV);
	features.attributes.fillColor = 
		“#” + Math.round((v-minV) / (maxV-minV)).toString(16); 
	features.attributes.opacity = (v-minV) / (maxV-minV);
	if( v  < lt; 100000 ) { 
		features.attributes.graphicName = “circle”;
	} else if(v  < lt; 500000) { 
		features.attributes.graphicName = “square”;
	}
	if( feature.attributes.isCapital ) {
		features.attributes.graphicName = “star”;
	}
}

for(var f in features) { 
	if(features.hasOwnAttribute(f) {
		styleByCityAttributes(features[f]);
	}
}

Style objects can either take literal values or use feature attributes as their inputs using the notation "{property}":

var style = { 
	label : ‘${cityName}’,
	fillColor : ‘${fillColor}’,
	fillOpacity : ‘${opacity},
	strokeColor : ‘#fffccd’,
	strokeOpacity : 0.7,
	graphicName : ‘${graphicName}’,
	graphic : true
}

Doing all your attribute based styling in a single function is sensible for smaller datasets and less complex applications, but a more reusable way to do it is to define your styles piecemeal and assemble them. In this way, styles can be “built up” gradually. The following function creates a data driven styler object:

function DataDrivenStyler() { return {
 	style : { }
	add : function(styleAttr, func) {
		if(typeOf(func) === ‘function’) {
			funcs.push({attr : styleAttr, func : func});
			style[styleAttr] = “${“ + styleAttr “}”;
		}
		else {
			style[styleAttr] = func;
		}
 	}, 
 	applyStyle : function(layer) {
		var i, p, param, f, feature;
		for(f=0; f

The applyStyle method takes an OpenLayers.Layer.Vector and styles each feature individually. The add method takes a style attribute and a function for use by the styler to style that attribute. For every feature passed to the styler, the function is passed the feature’s associated data and should return a value. That value is assigned to the attribute named in the add function.

We use this object like this:

var featureLayer = //OpenLayers.Layer.Vector definition
var property = function(p) { return function(f) { return f[p]; } };
var pointSizeByDollars = //scale point size by feature property ‘dollars’
var fillColorByCompanies = //function blending fill color by feature property “num companies”

var fstyler = DataDrivenStyler();
fstyler.add(‘fillColor’, fillColorByNumCompanies);
fstyler.add(‘fillOpacity’, 0.7);
fstyler.add(‘label’, ‘${caption}’));
fstyler.add(‘pointRadius’, pointSizeByDollars);

fstyler.style(featureLayer.features)
map.addLayers([featureLayer]);

Here we have a layer, featureLayer, that we pass through our custom data-driven styler, fstyler. We create a number of functions including the trivial but useful constant and property functions that return either a constant value or an unmodified data value from the feature as the value for a style attribute.

Here, we modify from the default the fill color, fill opacity, text label, and point size based on three data attributes that come in all our features.

The cost of doing things this way might look on the face of things to be quite high, but Javascript optimizers are powerful and furthermore the cost of actually rendering a feature on the screen is much higher than calling a series of lightweight-functions to style each feature. The rewards of “functional programming” applied in this way are consistency and readability, and they outweigh the negatives for all but the more extreme cases. This technique is used to great effectiveness in the general visualization toolkit, Protovis

Semantic feature styling

When large groups of features all need to have the exact same style, the data-driven styler seems a bit overkill. Instead, you may want to define a number of custom style objects and assign them based on semantically meaningful categories of your data.

var fireTruckStyle = { externalGraphic: ‘/images/fireTruck.png’ }; 
var policeCarStyle = { externalGraphic: ‘/images/policeCar.png’ };
var blockedRoadSegment = { strokeColor: “#ffff00” };

var roadsLayer = // OpenLayers.Layer.Vector
var carsLayer = // OpenLayers.Layer.Vector

var x;
for(x=0; x

The following reference material is provided here in hopes that it will be useful, but the definitive reference can be found at OpenLayers.org

OpenLayers StyleMap attribute reference

Fill attributes

  • fill Boolean true for filled features, false for unfilled.
  • fillColor String HTML fill color.  Default is “#ee9900”.
  • fillOpacity Number Fill opacity (0-1).  Default is 0.4

Outline attributes

  • stroke Boolean True for stroked features, false for nonstroked.
  • strokeColor String HTML stroke color.  Default is “#ee9900”.
  • strokeOpacity Number Stroke opacity (0-1).  Default is 1.
  • strokeWidth Number Stroke width in pixels.  Default is 1.
  • strokeLinecap String Line cap type.  Default is “round”.  Options are butt | round | square
  • strokeDashstyle String Line dash style.  Default is “solid”.  Options are dot | dash | dashdot | longdash | longdashdot | solid

Rendering external graphics at points

  • externalGraphic String URL for an graphic for a point.
  • graphicWidth Number Width in pixels for re-sizing an externalGraphic. If not provided, the image width will be used.
  • graphicHeight Number Height in pxiels for re-sizing an externalGraphic. If not provided, the image height will be used.
  • graphicOpacity Number Opacity (0-1) for an externalGraphic.
  • graphicXOffset Number Pixel offset west-to-east from the feature location for placing an externalGraphic.
  • graphicYOffset Number Pixel offset north-to-south from the feature for placing an externalGraphic.
  • graphicTitle String Tooltip for an external graphic. Some browsers do not support this.
  • backgroundGraphic String URL to a graphic to be used as the background under an externalGraphic. This can be used for drop shadows.
  • backgroundGraphicZIndex Number The integer z-index value to use in rendering the background graphic. Higher z-indexes are “closer” to the user.
  • backgroundXOffset Number The x offset west-to-east from the feature location (not from the externalGraphic) for the background graphic.
  • backgroundYOffset Number The y offset north-to-south from the feature location (not the externalGraphic) for the background graphic.
  • backgroundHeight Number The height in pixels of the background graphic.  If not provided, the graphicHeight will be used.
  • backgroundWidth Number The width in pixels of the background width.  If not provided, the graphicWidth will be used.

Rendering points

  • pointRadius Number Point radius in pixels.  Default is 6.
  • rotation Number For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point or any point off center as specified by graphicXOffset and graphicYOffset.
  • graphicZIndex Number The integer z-index value to use when rendering the point. Use higher indices for bringing it closer to the user. The default is the feature layer’s z-index.
  • graphicName String Named graphic to use when rendering points.  Options are “circle” (default) | “square” | “star” | “x” | “cross” | “triangle”

Rendering feature labels

  • label String The text for an optional label.  For browsers that use the canvas renderer, this requires either fillText or mozDrawText to be available. This works in Firefox, Chrome, and Internet Explorer 8 and above.
  • labelAlign String Whee the insertion point is relative to the text.  It’s a two character string where the first character is the horizontal alignment, and the second gives the vertical alignment.
    • Horizontal alignment options: “l” | “c” | “r”.
    • Vertical alignment options: “t”=top, “m”=middle, “b”=bottom.  Not supported by the canvas renderer.
  • labelXOffset Number Pixel offset east-to-west from the feaature origin.
  • labelYOffset Number Pixel offset north-to-south from the feature origin.
  • labelSelect Boolean Whether or not labels will be selectable using SelectFeature or similar controls.  Default is false.
  • fontColor String The font color for the label.
  • fontOpacity Number Opacity (0-1) for the label.
  • fontFamily String The CSS font family for the label.
  • fontSize String The CSS font size for the label.
  • fontWeight String The CSS font weight for the label.
  • <

Miscellaneous attributes

  • graphic Boolean Whether or not to draw the feature graphic. This is useful if you only want to draw text, but don’t need an anchor point for it.
  • cursor String Default is “”.
  • pointerEvents String Default is “visiblePainted”.
  • display String If this is set to “none”, the feature will not be displayed at all. Useful for hiding or for marking features as deleted while maintaining undo-able state.

OpenLayers: Vector Layers

Adding Features by Hand

The most straightforward if un-modular way of creating a feature layer in OpenLayers is to create individual features by hand and add them.  This can be quite tedious for complex features, but for the simplest of layers, it may work well. Use the OpenLayers.Feature.Vector and OpenLayers.Geometry.* references to create these features.

New geometry types can be defined by inheriting from OpenLayers.Geometry. Currently supported geometry types include:

  • Collection – A collection of geometries of different types.
  • Curve – A smooth spline curve.
  • LinearRing – A string of line segments where the last segment connects with the first.
  • LineString – A string of line segments.
  • MultiLineString – A collection of strings of line segments where the collection members aren’t contiguous.
  • Point – A single point
  • MultiPoint – A collection of points. If you have more than 50 or so points, it is best to use this and not create multiple Point geometries. It is much more efficient.
  • Polygon – A polygon, possibly with holes.
  • MultiPolygon – A set of polygons.
  • Rectangle – A single rectangle.

GeoJSON

JSON is both a subset of Javascript’s object syntaxanda subset of Python’s (much of GIS code is written in Python these days) dictionary literal syntax.  It is much simpler and easier to read than XML while remaining as general as OGC’s GML for expressing geographical data.  The GeoJSON standard is the JSON standard for feature data.  GeoJSON can be used directly by OpenLayers using the HTTP protocol object and the GeoJSON format object.

At the top level of a GeoJSON document is a “type” property that is either “Feature” or “FeatureCollection”.  If the document is a feature, then it contains one geometry object and one set of properties containing the data.  If it’s a feature collection, then the document will contain a “features” property that contains an array of GeoJSON features or nested feature collections, which are themselves objects.

All features in GeoJSON are objects containing properties for ”type”, ”geometry”, and ”properties”“properties”  is entirely arbitrary.  It is the data associated with each feature, and can be any valid JSON object.  The “geometry” property contains the actual geometry itself.

A geometry object describes the physical shape of the feature in the world.  The “type” property contains the geometry type, and is generally one of the following values: ”Point”, “LineString”, “LinearRing”, “Polygon”,  or ”GeometryCollection”.  There are other kinds of geometries, but for now we’ll focus on these, because they are core and OpenLayers supports them well.  These feature types correspond to the OpenLayers.Feature.* classes in the OpenLayers class hierarchy.  The “coordinates” property will vary according to the type of geometry.  Points will contain a single array of two or three values: x, y, and possibly z.  LineStrings will contain an array of point arrays.  Polygons will contain arrays of LineStrings that describe something called a linear ring. A linear ring is simply a LineString whose last point is always also the first point.  It can either be explicitly specified, or if not, the first point is appended for you.  A GeometryCollection is simply a way of nesting geometries.  Instead of a “coordinates” property, it contains a “geometries” property whose contents are other geometry (or nested geometry collections) objects.

Here are some examples of GeoJSON:

// Representing a point in GeoJSON
{ “type” : “Feature”,
  “geometry” : {
      “type” : “Point”,
      “coordinates” : [-85,35]
  },
  “properties” : { “name” : “Sir Lancelot” }
}

// Representing a line string in GeoJSON

{ “type” : “LineString”,
  “geometry” : {
     “type” : “LineString”,
     “coordinates” : [[-85,35], [-84,35.357], [-83,35.789]]
  },
  “properties” : { “name” : “The Tale of Sir Lancelot” }
}

// Representing complex feature collections in GeoJSON

{ “type” : “FeatureCollection”,
  “features” : [
    { “type” : “Feature”,
      “geometry” : { 
         “type” : “Point”, 
         “coordinates” : [-85, 35], 
      },
      "properties" : { “name” : “The Tale of Sir Lancelot” } 
    }
  ]
}

GeoJSON is probably the most popular way to write feature data by hand, because it provides the best balance between flexibility and simplicity.  GML, by contrast is a complex but complete standard for representing features in XML, and is meant to be generated entirely by machine.

To load a GeoJSON file as a layer, we create an HTTP protocol object in OpenLayers and use it to access the file.  Then we pass this into the GeoJSON Format object and the format object turns it into OpenLayers features:

var layer = new OpenLayers.Layer.Vector("Photos", {
   strategies: [new OpenLayers.Strategy.BBOX()],
   protocol: new OpenLayers.Protocol.HTTP({
       url: “http://jeffersonheard.com/examples/geojson/data.json”,
       params: {},
       format: new OpenLayers.Format.GeoJSON()
   }),
   styleMap: { externalGraphic : “${marker}” }
});

map.addLayers([layer]);

WFS

The Open Geographic Consortium’s WFS is the most widely adopted standard for transmitting feature data across the wire.  WFS stands for Web Feature Service and its reference implementation is the open source Geoserver although it is also implemented by commercial tools such as ArcGIS and GO Publisher.  It is composed of two components.  One is a standard for how to query a geographic web-service, consisting of a set of methods and parameters for those methods.  The other is a standard for how data comes back.  In the case of the feature data itself, data, by convention is returned as GML, an XML standard for conveying geographic data.

The two most important methods for using WFS are GetFeature and GetCapabilities.  The former method actually retrieves the features in the layer.  The latter provides the user or another program the list of what layers are available and how to query them.  GetCapabilites is highly useful when you want to explore a feature server that you don’t have administrative access to.

The most common open source software for serving up WFS are Mapserver and Geoserver.  Geoserver is a Java-based solution and runs in a standard Java web container, like Tomcat.  Mapserver is native-compiled. Both integrate with Apache well.

Integration with Apache is important when using WMS and WFS with OpenLayers, because OpenLayers uses the XMLHttpRequest method (the AJAX method) to retrieve certain data on the fly.  The advantage of this is a nice smooth interface that doesn’t require reloading the page to get more feature data.  The disadvantage of it is that all XMLHttpRequests must be directed to the same server (and port) of the the page that the map data is displayed on.

This means one of two things:  To use WFS with Geoserver, either you must deploy your web applications in the same container as Geoserver or somehow via MapServer, or a much cleaner solution is to use Apache’s mod_proxy, mod_rewrite, or other integration to treat Mapserver/Geoserver as part of the same package.  mod_proxy can also be used to proxy in requests to other WFS servers from third party providers (such as government geography, weather, or census sites or commercial providers).  As a last resort. OpenLayers.org provides a proxy CGI-script written in Python.  It is neither terribly efficient nor terribly secure, however, and is best used for testing purposes only.

OpenLayers consumes WFS via the OpenLayers.Layer.Vector class and the OpenLayers.Protocol.WFS object.  Combining the two like this:

var layer = new OpenLayers.Layer.Vector("WFS", {
   strategies: [new OpenLayers.Strategy.BBOX(), new OpenLayers.Strategy.Cluster()],
   protocol: new OpenLayers.Protocol.WFS({
      url:  “http://localhost:8080/geoserver/wfs”,
      featureType: “census_centroids”,
      featureNS: “http://nc.gov”,
      srsName: “EPSG:900913”,
      version: “1.1.0”
   })
});

map.addLayers([layer]);

will query your local Geoserver for the layer entitled census_centroids.  Geoserver layer names are usually of two parts, however, a part before the colon and a part after.  The first part is a shorthand for the namespace of the feature collection in geoserver.  For your layer, this can either be determined in the administrative interface for Geoserver or as part of the results of a GetCapabilities request.  The fully qualified namespace name goes into “featureNS”.  Of special importance is the “strategies parameter which tells OpenLayers how to query the WFS server.  Common strategies include:

  • Cluster – A strategy that groups features that are too close together to show distinctly on the map at the current zoom level.
  • BBOX – A strategy that loads new features when the viewing box is changed
  • Filter – A strategy that filters the features that will show on the map.

Adding the layer to our map, we see a bunch of yellow dots show up over North Carolina.  Not too extraordinary right now, but we’ll get there later.

In the next post, we will use OpenLayers to style our features.

More about OpenLayers.Map

We’ve shown several ways of getting a base layer into the map, but what about the map object itself? The map object is where you go when you need control over the viewing pane, extents, layer order, projection, and access to the layers. It’s a collection of functionality that applies to the map as a whole to all of its parts equally.

Adding, querying and acting upon layers

If you use the map object for nothing else, you will want to add layers to your map. Otherwise, all you have is the base map, and where’s the fun in that? First, let’s add a layer to the map:

var census = new OpenLayers.Layer.WMS( “Census Blocks”,
	“http://descartes.renci.org/geoserver/wms”,
	{ layers: “oreilly:census”,
          srs: map.projection,
          transparent: true },
	{ isBaseLayer: false }
);

var boundaries = new OpenLayers.Layer.WMS( “State Boundaries”,
	“http://descartes.renci.org/geoserver/wms”,
	{ layers: “oreilly:state_boundaries”,
          srs: map.projection,
          transparent: true },
	{ isBaseLayer: false }
);

map.addLayers([layer, boundaries]);

Here we add two layers: “Census Blocks” and “State Boundaries.” In the layer constructor, the first argument is always the name of the layer. The second argument, in the case of WMS, is the URL of the WMS server. The third argument is an object that gives the parameters to send to the WMS server. In general, the parameter name is the property name, and the value is the property value. Other arguments may be added when actual querying takes place, such as the image format requested or the bounding box, but these are extra arguments that help you customize your WMS object. The final argument is an “options” argument, where named options, usually the name of properties of the layer object, are passed to the constructor so they don’t have to be set later. OpenLayers constructors follow this “options passing” object as the final parameter quite often.

OpenLayers provides us a number of ways to get at layers once they’ve been added to the map. The querying functions are:

* Map.getLayersBy(property, value)
* Map.getLayersByName(name)
* Map.getLayersByClass(class)

The first function, getLayersBy, is the most general function, allowing you to access any layers who have a particular property set, such as isBaseLayer or sphericalMercator. Arbitrary properties can be set on a layer by either adding them to the options object in the constructor, or by adding them post-hoc after construction. This allows you to customize things like whether layers show in a layer switcher, whether they’re shared by multiple users, or other kinds of information data that the OpenLayers folks didn’t think of.

The getLayerByName function does what it sounds like it does and gets a list of layers who all share the same name. It’s an exact query, but since OpenLayers makes no restrictions on how many layers can have a given name, it returns an array and not a single layer object.

The getLayersByClass function takes a class name string, such as OpenLayers.Layer.WFS and returns all layers whose class is that. It does not return subclasses of that class name, so you cannot pass OpenLayers.Layer.Layer and expect to retrieve all layers.

If you want to iterate over all the layers in a map, simply access the .layers property of the map object, and all the layers, including the base layers, are contained therein. It is generally advisable to manipulate layers after they’ve been added to the map, as the act of adding them to the map changes a few properties of the layer.

Another common task on layers that is accessed through the map object is manipulating the Z-index of layers. Z-indexing decides which layers are “over” other layers. Generally speaking, a layer’s Z-index is the same as the order in which it was added to the layer, assuming it’s not a base layer. It would seem intuitive that the Z-index of the layer would be stored on the layer, but this is not the case: Z-indexing is stored and adjusted by the map object.

The lowest base layer is index 0, and others are sequentially numbered above that. There is no need for them to be contiguous – OpenLayers merely uses this number for sorting. It is possible and sometimes desirable to set a layer’s index to some large number to ensure that it remains over the top of any other layers that may be added to the map during use. The functions for manipulating the Z-index of layers are:

* map.getLayerIndex(layer) – Get the index of the layer.
* map.setLayerIndex(layer, ix) – Set the index of the layer to .
* map.raiseLayer(layer, delta) – Add “delta” to the index of the layer.

It is possible, but not desirable to raise base layers over the top of overlays using these functions, so to avoid this, always check the .isBaseLayer property of a layer before adjusting the Z-index.

Removing a layer should always be performed using the map.removeLayer(layer) method of the map object. This method removes the layer from the map object, sets the layer’s map property to null, and also removes the layer’s <div> tag from the browser’s HTML document. Without doing all three of these, you risk leaking resources and leaving dangling references to outdated layer or map objects, or introducing strange and hard-to-debug visual artifacts into your page.

A final caveat: depending on your application, you may find yourself wanting to serialize layers or map objects for layer use. A layer should always be removed from a map before serializing the layer, and all layers should be removed from a map before serializing the map, as the layer object has a map property set to the map that it’s attached to, creating a circular reference in the object structure. Failing to do this will result in strange and hard-to-debug errors, especially using popular JSON serializers.

Getting and setting map projection and extents

Generally, when a map is created, unless the property controls is passed into the options object in the constructor, a navigational control is added to the map, allowing a user to use mouse navigation the way one would familiar web mapping products like google. However, this may well not be enough for the power-user of OpenLayers. Programmatically setting the viewport and extent is something you can do within the map object.

The following options can be passed in the options object of the Map constructor to control the way the map behaves from the start (many of these may also be adjusted automatically when you add a base layer to the map):

  • minScale – The denominator of the ratio of real-world-size to map-size. This can often be left out, but if not, it should be smaller than the maxScale number. This means, counterintuitively, that the minScale is not the most zoomed out, but the most zoomed in scale. Think of it as the “minimum amount of scaling applied”
  • maxScale – Same as minScale, but the largest denominator instead of the smallest. This will refer to the most zoomed out portion of the map.
  • minExtent – The minimum viewable extent, in map units, given as an . This area must always be viewable in the map.
  • maxExtent – The maximum viewable extent, in map units, given in the same way as . This is the virtual area of the map, whether it’s in view currently or not. Tiles will not be retrieved if they lie outside of maxExtent. **Note that maxExtent should nearly always be set if you intend on putting multiple layers on the map, or odd things will happen, like missing tiles or zoom levels**. The default max extent is [-180,-90,180,90]
  • maxResolution – The maximum number of units-per-pixel on a single map tile. Will often be set by other parameters either to the constructor or as part of the base map. The default, barring any other setting, is 360.0/256.0.
  • minResolution – The minimum number of units-per-pixel on a single map tile.
  • restrictedExtent – Set this if you are not displaying the entire world. It should be set to a Bounds object (see min- and maxExtent) in the same units as the map projection.
  • numZoomLevels – The number of increments between the most-zoomed-in and most-zoomed-out a map can get.
  • projection – A string that refers to a projection code. Look for codes at SpatialReference.org. Projections are fundamental, and should always be set, even if just for your own reference. This property determines map units, valid values for map extent, and what projection is requested for layers that are layered on top of the base layer.
  • displayProjection – This is used by certain controls, like mouse position, to reproject the map coordinates used by OpenLayers to position things into human readable coordinates like longitude and latitude or state-plane coordinates.

You may note that the actual physical size of the map on the page is not part of the constructor options. This is generally controlled through CSS or in the HTML document, and cannot directly be set by the OpenLayers library. Size can be changed during runtime by manipulating the CSS that styles the map’s <div> element. When this size changes, you should call the map.updateSize() method to tell the map object about the change. This will allow it to recompute the viewing extent and such.

Once the map is created and the user is interacting with it, you may still find yourself wanting to snap to a particular feature, view, location, and zoom-level programmatically. These functions allow you to do this:

  • getCenter() – Returns a object (which has a .lon and .lat property). Counterintuitively, this object is in the map projection, whether or not the map projection uses lon and lat degrees.
  • zoom(z) – Returns an integer zoom level. 0 is the most zoomed out. Positive integers zoom in. Fires an zoomend event to listeners that the zoom has changed.
  • pan(xamt, yamt) – Pan the map so that its center point is adjusted by xamt and yamt, which are floats given in map projection units. Fires a moveend event to let listeners know that the location has changed.
  • panTo(lonlat) – Pan the map so that its center point is the same as a LonLat object. Once again LonLat objects contain map coordinate pairs, **not** longitudes and latitudes. Fires a moveend event to let listeners know that the location has changed.
  • setCenter(lonlat, zoom, dragging, zchange) – Set the center and zoom point. If dragging is set to true, let listeners know that the location has changed. If zchange is true, then let listeners know that the zoom level has changed.
  • getProjectionObject() – returns the projection object from the base layer. This can be used to transform LatLon objects or points into this coordinate system from another.

Let’s take a timeout for a moment to consider an example. Say you have a list of features whose locations are saved for the user to snap to when he/she selects them from a dropdown list on the page. We want to snap to these features upon the change in the dropdown list. Here’s how we’d do this using the methods contained in this section and a little extra JQuery magic:

var map = new OpenLayers.Map(‘map’);
map.addLayer(new OpenLayers.OSM(“OSM”));
var mySpecialFeatures = { ... }; 

$(“#specialfeatures”).change(function() {
	var selectedFeature = $(“#spatialfeatures”).val();
	if(mySpecialFeatures.hasOwnProperty(selectedFeature)) {
		map.panTo(mySpecialFeatures[selectedFeature].lonlat);
	}
});

$(“#recenter”).click(function() {
	map.setCenter(
		new OpenLayers.LonLat(0,0).transform(
			new OpenLayers.Projection(“EPSG:4326”),
			map.getProjectionObject())
		0,
		false, false // don’t notify listeners.
	);
});

where in the HTML there are fragments similar to this somewhere in the page:

<select id='specialfeatures'>...</select>
<input type='button' id='recenter' value='Recenter' />

Walking through the example, we define mySpecialFeatures, an object which contains properties whose names correspond to the values of options present in a <select> tag in the HTML document. The property values of the object contain LonLat objects in the map projection coordinates. We have a map object, and we add an OpenStreetMap layer to the map. The OpenStreetMap layer will set things for us like maxExtent, minExtent, zoom levels, and so on, so we can safely ignore them for this application. We then add two event handlers using JQuery (if you are not comfortable with JQuery, you can use a DOM manipulation library of your choosing or specify onchange or onclick handlers in the HTML code). The first event handler attaches to the select tag. It gets the value of our drop-down box upon selection, and recenters the map using the map.panTo method. The second event handler recenters our map to the origin (which is the prime meridian at the equator, longitude-latitude (0,0)) using the map.setCenter method, which sets zoom and pan at the same time.

This should get you started with the Map object.  In the next post we will talk about vector-based feature layers.

An introduction to OpenLayers

The most mature, portable, and stable API for browser-based map applications is OpenLayers.  OpenLayers is an API that renders a map in a <div> tag on your HTML page, and allows you to manipulate all the minutia of adding data to the map, interacting with it, getting data out of it, and so forth.  In this post, you will be introduced to OpenLayers, learn about how to develop interactive applications with it, and learn a little bit about adding data.

 Getting a base map on the page

I usually start by including the most up-to-date development snapshot of OpenLayers.  The OpenLayers development snapshots are stable within reason, and contain a few features that are well above and beyond those you can find in the stable branch.

<html>
  <head>
    <script src="http://dev.openlayers.org/nightly/OpenLayers.js">     </script>
<!-- the next line is needed if you intend to use Google Maps -->
<script src="http://maps.google.com/maps/api/js?v=3.2&sensor=false"></script>
  </head>
  <body>
    <div id=‘map’></div>
  </body>
</html>

This gives you a blank page to work with that includes everything you need to get started with OpenLayers.  To actually add a map to the page, you will need to add an OpenLayers.Map object and a base layer.  Practically any kind of layer can be used as a base layer, but the more common base layers are from widely used tile mapping services: Google, Bing, and Open Streetmaps.

When these are not used, it is common to use an OGC WMS or TMS (Tile Mapping Service) feed to populate the base map of OpenLayers. The advantage to a WMS or TMS service, especially if you deploy them yourself, is that you can get them on more than one map projection.  All the popular tiling map services use the same map projection and are locked into that projection, known as “Spherical Mercator”.  Spherical Mercator, despite its name, is a cylindrical projection, which gives increasing distortion of area near the poles and cannot display the poles themselves at all.  Additionally, you must be able to retrieve any layers added onto a Spherical Mercator layer in Spherical Mercator.  This is not normally a problem, as most WMS and WFS map servers are capable of reprojecting to the Spherical Mercator coordinate system.  Older services and static feeds may not, however.

Google

Google maps are the most popular maps on the web both because they were available to make mashup-apps early, and because they are highly readable.  Google’s provides separate tilesets for satellite imagery, terrain bumpmaps, roads and cities, and finally a road/satellite hybrid tileset.

Which one to use depends largely on the application you are writing.  The terrain map provides the most neutral background, however it shows the least detail.  The road map tells you nothing about what kind of terrain a feature is found on, but gives accurate information about roads, parks, military installations (in the US), cities, towns, and landmarks.  The satellite and hybrid maps have great variability in background, limiting the color palette and size of features that you can display legibly, but they also show whether or not the data you’re overlaying is in the middle of the forest or in the middle of the desert – an important distinction in some cases!

So let’s look at how to create a map and add a Google layer to it.  All this code would be enclosed in a <script> tag or be stored in a separate Javascript file to be loaded after OpenLayers:

var map = new OpenLayers.Map(‘map’ // the id of the <div> tag
   {maxExtent: new OpenLayers.Bounds(
       -20037508, -20037508,
        20037508, 20037508.34),
   }
);

// The terrain map
var terrain = new OpenLayers.Layer.Google(“Google Terrain”,
   {type: google.maps.MapTypeId.TERRAIN, sphericalMercator: true});

// The roads map
var roads = new OpenLayers.Layer.Google(“Google Maps”,
   {sphericalMercator: true}); // no maptype needed since this is default.

// The hybrid map
var hybrid = new OpenLayers.Layer.Google(“Google Maps”,
   {type: google.maps.MapTypeId.HYBRID, sphericalMercator: true });

//The satellite imagery map.
var satellite = new OpenLayers.Layer.Google(“Google Satellite”,
   {type: google.maps.MapTypeId.SATELLITE, sphericalMercator: true });

// This control is one way to raise or lower base layers.
map.addControl(new OpenLayers.Control.LayerSwitcher());

// Layers are stacked from bottom to top, with the leftmost at the bottom.
map.addLayers([terrain, roads, hybrid, satellite]);

Any one of these layers could have been added individually.  Here we add them all and a layer switcher control (shown as the little blue tab with a plus sign on it at the top right of the map.  The layer switcher lets us switch between them at will.  Note that sphericalMercator is set to “true”.  This is always a good practice, as layering other maps or drawing your own features on top of a Google (or any commercially provided) map will require that this is set to “true”.

The map object we created is very basic; we will come to more complex map objects later on, however note the maxExtent parameter.  This is often essential when adding multiple layers.  If this is omitted, some versions of OpenLayers can fail to display data at all zoom levels.  The maxExtent used here covers the entire world in the spherical Mercator projection. These values, although arcane, are safe to use and copy wholesale into your project.

Bing

Bing maps are perhaps the strongest commercial alternative to Google.  If you want to give your application a look that distinguishes it from other applications, Microsoft’s Bing might just be the way to go.  Bing’s maps are still fairly readable.  Their default map looks a bit like a combined view of Google’s road and terrain maps.  At its most detailed level it is still a little sparser than Google, which may appeal depending on your application.  Finally the licensing terms are different.  Licensing turns out to be a key issue when dealing with a commercial map provider, be it Google, Yahoo, or Microsoft, and each providers terms are enough different that if you find yourself unable to develop the application you want (for instance, Google doesn’t let you track vehicles as of this writing), you may look at another company’s map product.

To get the Bing map, you will need to add another script tag to the page in the area before your source code is included or written:

<script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&mkt=en-us"></script>

Then the map creation procedure is very similar to the procedure we followed for Google:

var map = new OpenLayers.Map(‘map’ // the id of the <div> tag.
  {maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508.34)
});

// API key for http://openlayers.org. Please get your own at
// http://bingmapsportal.com/ and use that instead.
var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
        
var roads = new OpenLayers.Layer.Bing(“Roads”,
   {type: "Road", apiKey:apiKey, sphericalMercator:true });

var hybrid = new OpenLayers.Layer.Bing("Hybrid",
   {type: "AerialWithLabels", apiKey:apiKey, sphericalMercator:true});

var aerial = new OpenLayers.Layer.Bing("Aerial",
   {type: "Aerial", apiKey:apiKey, sphericalMercator:true});

map.addControl(new OpenLayers.Control.LayerSwitcher());
map.addLayers([shaded, hybrid, aerial]);

OpenStreetMap

OpenStreetMap is a crowd-sourced mapping project similar in scope and intent to Wikipedia, but for navigation. Its strength lies in two areas: licensing and coverage.  OpenStreetMaps is released via a Creative Commons Share-alike license.  You can develop turn-by-turn directions, vehicle tracking, and all kinds of other applications that the commercial providers restrict you from.  You can download the data, which is vector formatted, and render your own map tiles to your liking. Additionally, in countries where US Trade restrictions abound, OpenStreetMaps commonly has whole towns that Google, Yahoo, and Bing just don’t have.  For instance, you might go to the Haitian port of Legoane in Google and OpenStreetMaps and compare notes.  Or try rural Cuba.  OpenStreetMap was used to great effect in the early days of the Haitian earthquake relief effort.

There is only one possible view to the openstreetmap.org’s OpenStreetMap, so creating an OpenStreetMap layer is perhaps the simplest of all.  No extra script tag to include APIs is required:

var map = new OpenLayers.Map(‘map’ // the string is the id of the <div> tag.
   {maxExtent: new OpenLayers.Bounds(
      -20037508, -20037508,20037508, 20037508.34)});

layer = new OpenLayers.Layer.OSM( "Simple OSM Map”, {sphericalMercator: true});
map.addLayer(layer);

WMS

WMS stands for Web Map Service, and is a standard produced by the OGC.  We will go into detail into WMS in a couple of posts, but for now, we’ll stick to showing how to add a WMS service to a base map, and why you would want to.  WMS is by far the most versatile mapping solution.  The open source Geoserver and MapServer projects are just two examples of servers you can setup for yourself to serve WMS tiles.  RENCI’s Geoanalytics Framework contains a highly flexible implementation of WMS for Python/Django, which operates on top of any of the supported data models.

WMS has its drawbacks. Many WMS servers render tiles on the fly, so many require an additional caching service to run on the server to keep up performance.  WMS servers may also be quite varied in the projections they support, so it would be good to do a little research on the service you intend to use before you actually go about using it.  We will go into how to do this programmatically later, but for now, let’s stay focused on getting the data into the map:

var map = new OpenLayers.Map(‘map’,{ // the string is the id of the <div> tag.
  maxExtent: new OpenLayers.Bounds(-180,-90,180,90),
  projection: “EPSG:4326”
});

var layer = new OpenLayers.Layer.WMS("OpenLayers WMS",
    "http://vmap0.tiles.osgeo.org/wms/vmap0", {
    layers: ‘basic’,
});

map.addLayer(layer);

Here we see a couple of new parameters: layers and srs.  Layers describes to the WMS what layers to composite together into a single image.  Compositing in WMS is done server side, so if you are sure you always want a few layers composited together, you can save client side performance and have the server do it.  This parameter is required.  The second parameter is optional, but without it you are at the mercy of whatever the projection the layer has set as default.  It should generally be the map projection.

To use a WMS layer as the base layer, be sure to set the projection and extents, and then add “baseLayer: true” to the options object.

The srs parameter stands for spatial reference system, and is usually but not always a code like that seen above.  “EPSG:4326” is a common, (but often misused) basic latitude-longitude projection.  “EPSG:3785” (or “EPSG:900913” on older systems) is the Google/Bing/Yahoo/OSM Spherical Mercator projection. There are a number of common ones, and not all may be supported by your particular map server.  We will cover in detail in a future post how to find out for your server which spatial reference systems are supported.  A full search engine and large database of spatial reference systems can be found online at SpatialReference.org.  This is a crowdsourced wiki for spatial reference systems, and not all spatial reference systems may be found there.