Right, that's enough theory; time for some practical examples.
We'll start off with a basic page template:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://openlayers.org/api/OpenLayers.js"></script>
<style type="text/css">
#map {
width: 600px;
height: 450px;
border: 1px solid black;
}
</style>
<script>
window.onload = function() {
var map;
}
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>
This is much the same as you'll find in the OpenLayers examples, except that it uses the online OpenLayers API rather than a local copy. In most of my maps, the map itself takes up 100% of the html body; for these examples, I'll use a fixed-size div instead. The onload function is a global variable, and all the other functions are included in that. If your apps get bigger, you may want to separate the functions into different namespaces (i.e. JS objects), or even use OL's class structure. I've put the 'map' variable in the onload function, so it's not global; for debugging purposes you may prefer to move this into the global context.
Adding a map with baselayer
Now, let's put an actual map in the map div. The only thing to change in the template above is to fill in the onload function. We first create a map object but, as the Map class docs say, a map by itself isn't much use. The actual vectors and raster images go in layers which are added to the map, and this is what is actually displayed. All my maps use rasterised map tiles as the baselayer, with vectors added on top. So we'll start off with some raster tiles, and add vectors in a later page.
The imaginatively named example.html in the OpenLayers examples shows adding 3 separate layers from WMS servers. Most of the baselayers I use are from WMS servers too. Here's an example using the SRTM rendering from University College London (unfortunately, this server seems to have been down a lot recently; in this case, you will by default see pink tiles):
var map = new OpenLayers.Map('map');
var iceds = '<a target="_blank" href="http://iceds.ge.ucl.ac.uk/">ICEDS</a>';
map.addLayer(new OpenLayers.Layer.WMS( "SRTM relief",
"http://iceds.ge.ucl.ac.uk/cgi-bin/icedswms?",
{layers: 'srtm30'},
{ attribution: 'SRTM relief maps from '+iceds }
));
map.setCenter(new OpenLayers.LonLat(7, 48.9), 5);
So, the three steps are: instantiate the Map object, add one or more layers, display the map by setting the start position, in this case using the setCenter() method (example.html uses zoomToMaxExtent() instead).
Look at the Map object docs and you will see it can be instantiated with or without an options object parameter; in this case, we are not using an options parameter, so the map is created with default options. WMS Layer object instantiation has 4 parameters: name (a simple string identifier), the base url of the server, any parameters needed by the server in addition to those defined in the class as default values, and an optional options object. In the example above, I give the layers parameter for the WMS server, and also add an attribution option for the layer. By default, WMS layers are assumed to be baselayers, but you can change this by setting "isBaseLayer: false" in the options object - as with the third layer in example.html.
Changing the resolutions
As stated in the theory section, by default, the map is created with a fixed set of resolutions, and a maxExtent of worldwide. As I'm only dealing with Europe, and in any case do not need so many resolutions for SRTM data, I set resolutions when adding the layer:
var resolutions = [0.06,0.04,0.02,0.01,0.005];
and then add this to the layer options object:
{ attribution: 'SRTM relief maps from '+iceds,
resolutions: resolutions }
This then gives five zoomlevels.
Setting server parameters dynamically
As the ICEDS server seems to be down so frequently, let's try a different example, with the Catalonian topo maps served by the ICC. Examine the WMS capabilities and you will see two things. Firstly, as the maps only cover Catalonia, the default worldwide coverage of the WGS84 projection no longer applies and we need to specify the geographical bounds of the area covered by this server. We do this by setting the maxExtent property:
map.maxExtent = new OpenLayers.Bounds(0.041150,40.485583,3.484697,42.940881);
Note that you can set maxExtent on both map and layer depending on circumstances: changing it on the map means the whole map is limited to that bounds, whereas changing it on the layer means the map continues to cover the worldwide default, but this layer is limited to the bounds (you may have other layers which are worldwide). In our case, we are setting it on the map, so are limiting the whole map to Catalonia. And, as we're doing this, we might as well set the resolutions on the map as well rather than on the layer.
The other difference here is that the server provides the maps with all the different scales, but these are given different layer names (we're interested in the topo maps which start 'mtc...'). In the previous example, the layer name was constant, so we provided it with the layers parameter in the instantiation of the layer object. In this case, every time there is a change in zoom level, we need to check that the layers parameter is set appropriately on the WMS request.
To do this, we first set up a cross-reference of layer names to zoomlevels; let's set this on the map object:
map.mapTypes = ['mtc250m','mtc250m','mtc250m','mtc250m','mtc50m','mtc50m','mtc25m','mtc10m'];
We then need to override the layer's moveTo function with our own, which sets the params.LAYERS on the layer depending on this cross-reference. We first define this function:
var moveTo = function(bounds, zoomChanged, dragging) {
if (zoomChanged) {
this.params.LAYERS = this.map.mapTypes[this.map.getZoom()];
}
OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
};
and we then set this on the options passed to the layer instantiation:
{ attribution: 'Topo maps from <a target="_blank" href="http://www.icc.cat/">ICC</a>',
moveTo: moveTo }
Look in Layer/WMS.js and you will see it has no moveTo function; it inherits one from Layer/Grid.js. So, by setting this on our layer instance, whenever the map is panned or zoomed our function is executed. This is restricted to zoomChanged, as we don't need to check when the map is only panned. It sets params.LAYERS as appropriate ('this' here refers of course to the layer) and then calls the moveTo function on the parent class, so effectively we are inserting this bit of code at the front of Grid.moveTo().
This is quite a simple case, as we only have one layer. If we had several layers which each needed their own logic, we could set up a different moveTo function for each layer and pass each one its own in the options object. If, on the other hand, we had several layers which each needed the same logic, rather than pass it in the options object for each layer, we could set it on the layer prototype so it applied to all instances. For example, putting something like the following at the top of the script:
OpenLayers.Layer.WMS.prototype.moveTo = function(bounds, zoomChanged, dragging) {
if (zoomChanged) {
this.params.LAYERS = this.map.mapTypes[this.map.getZoom()];
}
OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
}
There's a further complication with this server, as it doesn't seem to like the "application/vnd.ogc.se_inimage" exceptions parameter that OL uses by default. Instead it expects "application/vnd.ogc.se_xml". We set this on the WMS parameters object passed to the layer:
exceptions: "application/vnd.ogc.se_xml"
Controls
The example above uses default options, which means that the Map instantiation adds 4 default controls to the map:
- Navigation, which enables the basic panning and zooming of the map via mouse clicks and drags
- PanZoom, which provides a simple panel which also enables panning and zooming
- ArgParser, which adds functionality for parsing long/lat and zoom parameters in the querystring
- Attribution, which displays the content of the attribution property for each layer, so you can give credit or any copyright info for each of your sources
On my maps, I want somewhat different controls, so I define my map as follows:
var options = { controls: [
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.KeyboardDefaults(),
new OpenLayers.Control.PanZoomBar(),
new OpenLayers.Control.Scale(),
new OpenLayers.Control.Attribution()
]};
map = new OpenLayers.Map('map', options);
So I use PanZoomBar instead of PanZoom (a bigger brother), use a scale display, and also add KeyboardDefaults, which enables pan/zoom with arrow and +/- keys. I do not by default use the ArgParser, but only include it when I need it. And there are a couple of others that I include as needed, such as a LayerSwitcher when there is more than one layer. I do always include MousePosition, but this is a bit more complicated; I'll come back to this in the next page on projections.
A further control I always have is a panel for buttons, though which buttons appear in it depends on the application/page. A panel is a special type of control, a sort of container for buttons, some of which are simple push buttons, like the help button, others are grouped, like radio buttons, so only one can be active at a time. To create this, you first have to create a panel control, add controls to it, then add the panel to the map:
var panel = new OpenLayers.Control.Panel();
panel.addControls([
new OpenLayers.Control.Button({
displayClass: "helpButton", trigger: function() {alert('help')}, title: 'Help'
})
]);
map.addControl(panel);
The button has 3 options: title, displayClass - the CSS class used for styling - and trigger, which is the function called when the user clicks on the button. Of course, you also have to set up the stylesheet establishing how/where the panel and the button should be displayed; in this case, we're using an external image called 'help.png'.
div.olControlPanel {
top: 0px;
left: 50px;
position: absolute;
}
.olControlPanel div {
display: block;
width: 22px;
height: 22px;
border: thin solid black;
margin-top: 10px;
background-color: white
}
.helpButtonItemInactive {
background-image: url("help.png");
}
Add all this to your page - I'll leave you to create help.png - and you should now see the help button on the left; click on it and it should display 'help' - of course, in the real world, it would do something more useful.
I'll come back to the grouped buttons when I discuss editing vectors.

