Maps are an interesting way to present data, whether you're showing customers how to get to your business, displaying water-quality sampling sites, or geotagging vacation pictures. Using maps on websites is becoming increasingly common, and people frequently use free services like Google Maps, Microsoft Virtual Earth, and Yahoo! Maps. The tools these major players offer are useful and can enable you to get a web page with a map out very quickly. However, they're not very customizable beyond basic functionality like adding markers or way points.
Suppose you're a cartographer or geographic information system (GIS) professional, working on behalf of one, or an advanced hobbyist and want to bring in multiple datasets from a variety of sources. A traditional approach would be to use a web mapping solution like the commercial ESRI ArcIMS or the open source MapServer tool. Both tools excel at allowing you to merge differing data sources and display them. However, they do not allow you to use data sources like Google Maps and Virtual Earth by themselves. In addition, setting up a website with a fully functional map using MapServer templates or self-written JavaScript can be time-consuming to accomplish a good level of functionality.
OpenLayers solves these problems. Using OpenLayers, you can put together a full-featured map on your website quickly and easily. The resulting map can consume data from a wide variety of sources, including public servers, MapServer, ArcIMS services, ESRI ArcGIS, and even NASA World Wind. By having your map consume MapServer-driven Web Map Service (WMS) services, you can also include your own datasets, such as shapefiles and raster data.
Note: If you are unfamiliar with GIS terms such as projection and shapefile, please see Resources for a link to the developerWorks article "Using geospatial data in applications on Linux using GDAL."
OpenLayers is a stand-alone JavaScript library requiring no server-side
components or specific web server requirements and composed of a set of
well-organized JavaScript classes. The starting place is OpenLayers.Map, the actual map object itself.
Individual layer classes, such as Layer.ArcIMS
or Layer.Google, are instantiated and added to
the map. Classes representing map controls, such as Control.Pan, are used to interact with the map. Finally, a
variety of utility classes for styling, data formats, coordinate storage,
and more are available. See the link to OpenLayers API documentation in
Resources for the full documentation.
This article explores these classes as you build the example website shown in Figure 1, which combines data from Google Earth, Virtual Earth, a local shapefile, and the National Oceanic and Atmospheric Administration North Radar Integrated Display with Geospatial Elements (NOAA RIDGE) radar coverage WMS.
Figure 1. The completed example at hike.chrismichaelis.com
The example website also allows users to draw hiking routes on the map. Users can save these routes between browser sessions using Google Gears or can export them to Keyhole Markup Language (KML). Google Gears is a browser plug-in that extends JavaScript so it can store data on behalf of pages with persistence between sessions. It also provides background JavaScript execution and other benefits. In this example, you use it solely for storage. See the link to the Google Gears developer site in Resources for more information.
The first step is to build the website that will contain the map. The example in this article uses a separate file for the HTML, CSS, and JavaScript content to keep things organized. This structure also aids in future maintenance and makes it easy to extend the site later. Because OpenLayers is purely JavaScript, your page is pure HTML and uses no server-side language. As a result, you can use fast and slim web servers like Nginx to host it instead of servers like Apache. Because all data is stored in Google Gears or pulled from Internet sources, no database server is needed.
The jQuery JavaScript library is also included in your new site. You use
the tools that jQuery provides to speed up JavaScript development, such as
selectors allowing quick access to document
objects — for example, using
$('.btnActive').removeClass('btnActive'); to
remove the btnActive CSS class from all
instances where it appears without needing to specifically loop through
objects retrieved with the
getElementsByClassName function. Similarly,
$('#someID') selects an element by its HTML
id attribute.
Start by downloading the OpenLayers package from the OpenLayers site (see Resources). Extract it into a subdirectory in your website's location and include it with the following line in your HTML file:
<script type="text/javascript"
src="OpenLayers/OpenLayers.js"></script>'
|
In your JavaScript, use the jQuery "document ready" function to
set up your map. This function fires later than the onLoad event; not all images and DOM elements can be loaded
and ready when the onLoad event fires, but they
are guaranteed to be loaded and ready when the jQuery "ready" function
fires. Create a map and assign it to a variable, as Listing 1 shows. The
second argument to the constructor allows you to pass options, such as
controls to add to the map, the data projection and the display projection
of the map, and the maximum extents that should be viewable. The maxExtents parameter restricts how far the viewer
can pan, and it must be specified to avoid strange behavior, particularly
when using multiple data layers. Leaving it out can cause problems such as
being unable to pan at all or having additional layers badly misaligned on
top of the base layer.
Listing 1. Creating an OpenLayers map in the div
mapContainer
$(document).ready(function() {
var olMap = new OpenLayers.Map('mapContainer', {
controls: [
new OpenLayers.Control.MouseDefaults(),
],
projection: new OpenLayers.Projection('EPSG:900913'),
displayProjection: new OpenLayers.Projection('EPSG:4326'),
maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34)
});
});
|
A map in OpenLayers consists of a base layer and (optionally) several overlay (or non-base) layers. A base layer is usually an image layer like Google Earth or Virtual Earth; an overlay is generally a user-drawn vector layer or imagery from MapServer. Only one base layer can be active at a time.
Bringing in data from multiple sources
Typically, Google Earth or Virtual Earth are used as base layers. These and many other commercial free data sources use the spherical Mercator data projection (identified by European Petroleum Survey Group [EPSG] code 900913). This projection provides data measured in meters across the globe, basing the shape of the earth on a sphere model. It appears visually accurate on large scales but is inaccurate for measurements at the local level. OpenLayers does not have the ability to re-project data itself, so all data included in a map must also be provided in Spherical Mercator projection. See the links to Spherical Mercator information in Resources for details.
Now add your base layers to the map. Because only one base layer can be active at a time, you can add as many base layers as you like, but the first one will be active by default. Later, you'll create a way to toggle between them. Listing 2 shows the code for adding Virtual Earth and Google Earth layers.
Listing 2. Adding Virtual Earth and Google Earth base layers
// Create the Virtual Earth layer
var veSatLayer = new OpenLayers.Layer.VirtualEarth('Virt. Earth', {
sphericalMercator: true,
'type': VEMapStyle.Aerial,
});
olMap.addLayer(veSatLayer);
// Create a Google Earth layer (because it is not first, it is not selected by default)
var googleEarth = new OpenLayers.Layer.Google('Google', {
sphericalMercator: true,
});
olMap.addLayer(googleEarth);
|
The next layer to add is the NOAA RIDGE WMS. This service provides reflectivity values over the United States and is typically used for weather prediction and cloud cover analysis, providing a handy general indicator of weather for your hike planning tool. Data from this service is provided in NAD83 latitude/longitude coordinates (EPSG code 4269), which is incompatible with your requirement of using Spherical Mercator projection. As a result, you can't add it directly to OpenLayers.
Instead, use MapServer to perform the re-projection for you. Sometimes, you will be able to request data in the Spherical Mercator projection, and the data provider will project it for you — but don't rely on this. Regardless, it is more polite to perform the re-projection on your own server to reduce your load and demand on third-party servers. The first step is to install MapServer on your web server (most Linux® software repositories have it available). Next, add the line:
<900913> +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0
+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null
+no_defs
|
to the bottom of the file /usr/share/proj/epsg on your web server. This line defines the Spherical Mercator projection so that MapServer (and its projection library, PROJ4) will know how to convert data into this projection.
Next, create a MapServer map file defining the data that you want to
include (shown in Listing 3). Key points include setting the projection of
the map file to EPSG:4326 and defining the WMS layer you want to pull in
along with its appropriate metadata (the LAYER
section). Finally, the map file must provide a WEB section where the wms_srs value
includes the projection you want to request — in this case,
900913. This setting essentially lists the projections that users are
allowed to request. Queries for other projections result in an error
message asking the visitor to "request a supported SRS" (that is, a
spatial reference system — essentially, the projection).
Listing 3. MapServer map file pulling in the NOAA RIDGE radar layer for re-projection to Spherical Mercator
MAP
IMAGEQUALITY 95
IMAGETYPE png
OUTPUTFORMAT
NAME png
DRIVER 'GD/PNG'
MIMETYPE 'image/png'
IMAGEMODE RGBA
EXTENSION 'png'
END
PROJECTION
"init=epsg:4326"
END
WEB
IMAGEPATH '/tmp/'
IMAGEURL '/tmp/'
METADATA
"wms_srs" "EPSG:4326 EPSG:4326 EPSG:3857 EPSG:900913"
END
END
LAYER
NAME noaaradar
TYPE RASTER
CONNECTIONTYPE WMS
CONNECTION "http://gis.srh.noaa.gov/arcgis/services/RIDGERadar/MapServer/WMSServer?"
METADATA
"wms_srs" "EPSG:4326"
"wms_name" "0"
"wms_server_version" "1.1.1"
"wms_format" "image/png"
END
PROJECTION
"init=epsg:4629"
END
END
END
|
Finally, you can add the NOAA RIDGE radar layer to your map by referencing your newly created map file, as shown in Listing 4.
Listing 4. Adding the NOAA RIDGE radar WMS via your own local MapServer instance
// Add the NOAA RIDGE radar WMS by routing it through our local MapServer instance
// so that it can be re-projected on the fly to Spherical Mercator
var ridgeRadar = new OpenLayers.Layer.WMS('NOAA RidgeRadar',
'http://hike.chrismichaelis.com/cgi-bin/mapserv.cgi', {
layers: 'noaaradar',
map: '/var/www/hike.chrismichaelis.com/mapserver/NOAARidgeRadar.map',
transparent: true,
singleTile: true,
visibility: false,
srs: 'EPSG:900913'
});
olMap.addLayer(ridgeRadar);
|
It's often useful to be able to pull in your own local data, whether it's
data you've downloaded or data you've created. You can do so via MapServer
similar to how you included the NOAA RIDGE radar layer. A MapServer map
file is created exactly as in Listing 3, but with the
LAYER section describing a shapefile
instead of a WMS, as Listing 5 illustrates.
Listing 5. MapServer map file
LAYER section to
re-project and serve a local shapefile
LAYER
NAME usaroads
TYPE LINE
DATA '/var/www/hike.chrismichaelis.com/mapserver/usaroads/roadtrl020.shp'
PROJECTION
"init=epsg:4326"
END
CLASS
NAME 'roadtrl020'
STYLE
WIDTH 2
COLOR 255 0 0
END
END
|
You add the roads layer to the map using the same method as in Listing 4, substituting the layers and map options with
appropriate values. At this point, you have a fully functional map pulling
data in from multiple sources, including default functionality to pan with
the mouse and zoom in and out with the mouse wheel. You're still
lacking any ability to control visible layers or draw hiking routes,
however. Now that the map is functional, you can zoom the map to your area
of interest, rather than leaving it zoomed out to the entire United States.
You do this by zooming to an integer zoom level (an arbitrary fixed-scale
experiment to see the effect of different values) and defining a point by
latitude and longitude. You then project (or transform) the
coordinate from WGS84 latitude and longitude (EPSG:4326) to the map's
current projection (Spherical Mercator) and zoom to it. This process is
illustrated in Listing 6.
Listing 6. Zooming to an arbitrary latitude and longitude location
// Move to your default location - Pocatello, ID
olMap.zoomTo(10);
var newCenter = new OpenLayers.LonLat(-112.4447222, 42.8713889);
newCenter.transform(new OpenLayers.Projection('EPSG:4326'), olMap.getProjectionObject());
olMap.setCenter(newCenter);
|
No map is complete without navigation controls. By default, OpenLayers provides mouse wheel-based zoom in and out and mouse-based panning. You can also add on-map controls — for example:
olMap.addControls(new OpenLayers.Control.NavToolbar); |
However, using your own HTML layout to place and render navigation controls provides greater flexibility and avoids problems like severely clashing colors between underlying map data and navigation controls. This is particularly a problem with scale bars and textual information overlaid on a map (such as light white snow imagery and white scale text).
In this example, you use simple HTML <img>
tags to pull in icon images and jQuery to attach an anonymous function to
that image. The functions manipulate the map appropriately, calling zoom
functions or setting the center of the map, as shown in Listing 7. You can
also add controls for showing mouse position and map scale and providing
the ability to change which layers are visible (including choosing base
layer) — also shown in Listing 7. For these controls,
you provide a <div> argument that
instructs OpenLayers to render these controls into the specified HTML
<div> element instead of the default
(drawing it on the map directly).
Listing 7. Adding zoom, pan, and layer selection controls as well as mouse position and scale indicators
// Set up your map GUI controls (vs. using OpenLayers built-in controls)
$('#zoomIn').click(function() {
olMap.zoomIn();
});
$('#zoomOut').click(function() {
olMap.zoomOut();
});
$('#moveUp').click(function() {
// toArray on bounds are in order of left, bottom, right, top
olMap.setCenter(
new OpenLayers.LonLat(olMap.getCenter().lon, olMap.getExtent().toArray()[3]));
});
$('#moveDown').click(function() {
olMap.setCenter(
new OpenLayers.LonLat(olMap.getCenter().lon, olMap.getExtent().toArray()[1]));
});
$('#moveLeft').click(function() {
olMap.setCenter(
new OpenLayers.LonLat(olMap.getExtent().toArray()[0], olMap.getCenter().lat));
});
$('#moveRight').click(function() {
olMap.setCenter(
new OpenLayers.LonLat(olMap.getExtent().toArray()[2], olMap.getCenter().lat));
});
// Add the mouse position to your Controls area
olMap.addControl(new OpenLayers.Control.MousePosition({
'div': OpenLayers.Util.getElement('mousePosition'),
numDigits: 4
}));
// Add a scale indicator to the Controls area
olMap.addControl(new OpenLayers.Control.ScaleLine({
'div': OpenLayers.Util.getElement('scaleBar'),
geoDesic: true
}));
// Add a layer switcher to your Controls area
olMap.addControl(new OpenLayers.Control.LayerSwitcher({
'div': OpenLayers.Util.getElement('layerSwitcher'),
roundedCorner: false
}));
|
You want your visitors to be able to draw hiking routes on the screen. You
provide this functionality by adding a few buttons to your HTML file with
image tags: pan mode, draw mode,
delete mode, and Save to KML. In the
JavaScript, you first set up a vector layer that will contain the
hiking routes and add it to the map. Next, create two controls:
SelectFeature and
DrawFeature. On the
SelectFeature control, add a function to the
featurehighlighted event that deletes the
selected feature, effectively turning this into a "delete control."
Setting box to True and
hover to False makes the control require
drawing a box intersecting the line to delete it versus just hovering over
it. The draw control is more straightforward and just needs to be added to
the map.
Next, you use jQuery to add a function to each of your new image "buttons."
The main purpose of these functions is to call deactivate() on the control not in use and activate() on the control in use. If both the SelectFeature and DrawFeature controls are deactivated, the map switches to pan
mode. You also highlight the active button in these functions by adding
and removing the btnActive CSS class, which
changes the border color to white. All of these steps are shown in Listing
8, which brings your map to the desired functionality level.
Listing 8. Adding a vector layer and setting up functions to interact with it
// Create a vector layer that will represent your hiking routes
var hikePaths = new OpenLayers.Layer.Vector("Hike Routes", {
strategies: [new OpenLayers.Strategy.Fixed()],
style: {
strokeWidth: 3,
strokeColor: "#00eeee"
}
});
olMap.addLayer(hikePaths);
// Set up your pan and draw mode toggling and edit controls
featureSelect = new OpenLayers.Control.SelectFeature(hikePaths, {
box: true,
multiple: true,
hover: false,
eventListeners: {
featurehighlighted: function(feat) {
// Remove a feature from the layer
hikePaths.destroyFeatures(hikePaths.selectedFeatures);
}
}
});
featureDraw = new OpenLayers.Control.DrawFeature(hikePaths, OpenLayers.Handler.Path);
olMap.addControls([featureSelect, featureDraw]);
$('#drawMode').click(function() {
featureSelect.deactivate();
featureDraw.activate();
$('.btnActive').removeClass('btnActive');
$('#drawMode').addClass('btnActive');
});
$('#panMode').click(function() {
featureSelect.deactivate();
featureDraw.deactivate();
$('.btnActive').removeClass('btnActive');
$('#panMode').addClass('btnActive');
});
// Set up your delete button -
// just turns on selection mode, and on select the listener will destroy the feature
$('#deleteMode').click(function() {
featureDraw.deactivate();
featureSelect.activate();
$('.btnActive').removeClass('btnActive');
$('#deleteMode').addClass('btnActive');
});
|
Because many people use Google Earth and Google Maps, a handy feature of
the Layer object is the ability to export to
KML format. You do this with the OpenLayers.Format.KML class, as shown in Listing 9.
Listing 9. Exporting vector layer features to KML format
// Set up the Save to KML button
$('#saveKML').click(function() {
var kmlFormat = new OpenLayers.Format.KML();
var newWindow = window.open('',
'KML Export ' + (new Date()).getTime(), "width=300,height=300");
newWindow.document.write('<textarea id="kml" style="width: 100%; height: 100%">' +
kmlFormat.write(hikePaths.features) + '</textarea>');
});
|
Using Google Gears to store data
Using Google Gears allows your site to save hiking data between browser
sessions, making it more convenient and seamless for casual use. First,
the OpenLayers.Protocol.SQL.Gears class must be
instantiated and given a database and table name. Afterward, just set the
protocol property of the vector layer to
your Gears instance. During initial setup, the read() function can be used to pull already-stored features
from the database, which can then be passed to addFeatures() on the vector layer, as in Listing 10.
Listing 10. Adding a vector layer with Google Gears (compare to Listing 8 — vector without Gears)
// Create a vector layer that will represent your hiking routes
var hikePaths = new OpenLayers.Layer.Vector("Hike Routes", {
strategies: [new OpenLayers.Strategy.Fixed()],
style: {
strokeWidth: 3,
strokeColor: "#00eeee"
}
});
// Because you'll use Google Gears to store hiking routes, set up that protocol:
var gears = new OpenLayers.Protocol.SQL.Gears({
saveFeatureState: false,
databaseName: 'hikingPaths',
tableName: 'hikingPaths'
});
if (!gears.supported()) {
alert('Warning: Google Gears is not available. You will not be able to use the hike
path drawing tools. Visit http://gears.google.com to install Gears.');
}
else {
hikePaths.protocol = gears;
var existingGearsData = gears.read();
if (existingGearsData.features) hikePaths.addFeatures(existingGearsData.features);
}
// Finally, add the layer
olMap.addLayer(hikePaths);
|
When features are deleted, they must be removed from the Gears database as
well as from the layer, so add this to the featurehighlighted event of the SelectFeature control (see Listing 11). Similarly, when you
add features, use the featureAdded event of the
DrawFeature control.
Listing 11. Adding and deleting features in the Gears database (compare to Listing 8 — managing features without Gears)
// Set up your pan and draw mode toggling and edit controls
featureSelect = new OpenLayers.Control.SelectFeature(hikePaths, {
box: true,
multiple: true,
hover: false,
eventListeners: {
featurehighlighted: function(feat) {
// Remove it from the Gears database
if (gears.supported()) gears.delete(hikePaths.selectedFeatures);
// Remove the actual feature from the layer
hikePaths.destroyFeatures(hikePaths.selectedFeatures);
}
}
});
featureDraw = new OpenLayers.Control.DrawFeature(hikePaths, OpenLayers.Handler.Path, {
featureAdded: function(feat) {
// Send to the Gears database
if (gears.supported()) gears.create(feat);
}
});
olMap.addControls([featureSelect, featureDraw]);
|
Although the website you built here is fully functional, a few enhancements can still be made: a locator lap to better show position, a legend or key for the NOAA RIDGE radar and other layers, a social-networking tie-in for sharing hikes, and other possibilities. See Resources for links to the live example or Download for complete source code. As you write your own code, refer to the OpenLayers examples in the Resources section. Happy mapping!
| Description | Name | Size | Download method |
|---|---|---|---|
| Hike tracker and planner example project | os-openlayers-HikeExample.zip | 6.6KB | HTTP |
Information about download methods
Learn
- For additional background on concepts and
terms used in this article, see the developerWorks article "Using geospatial data in applications on
Linux using GDAL."
- The OpenLayers
library documentation provides a complete guide to OpenLayers.
- To learn more about Google Gears, see the
Google Gears developer
documentation.
- The OpenLayers downloads page
provides the latest OpenLayers library.
- The Spherical Mercator information on the OpenLayers site provides
handy tips for working with public data sources.
- If you're curious, look at the mathematical
definition of spherical Mercator and other mercator projections.
-
OpenLayers Examples
provides a fantastic resource for demonstrations of any OpenLayers
feature.
- The completed example shows all
the concepts discussed in this article.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
MapServer is a server-side mapping
solution that can re-project data on the fly.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Get involved in the developerWorks community.
Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.
Christopher Michaelis is a developer specializing in web hosting and web applications. He attended Utah State University and Idaho State University, studying computer science and geographic information science. His experience covers a wide range of development, including applications for environmental modeling, education, diabetes data analysis, systems administration tools, web hosting, platform integration, and location-based services.



