Bring data together with OpenLayers

Using data from multiple divergent sources in web maps

OpenLayers enables you to seamlessly use data from divergent sources, including the Web Map Service (WMS), the Web Feature Service (WFS), and Google Maps. The library also provides tiling features that optimize map display and includes functionality invaluable for a usable map, such as pan controls, layer-switching controls, and even the ability to control zoom with a mouse wheel. In this article, learn how to create a hike-tracking website merging Microsoft® Virtual Earth satellite imagery with the NOAA Weather Radar WMS, using Google Gears to store hike data.

Share:

Christopher Michaelis, Developer, Consultant

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.



15 February 2011

Also available in Japanese

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."

Library architecture overview

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
Screenshot of the example application showing a menu of interactive options ot the left and a satelite map with colored route overlays to the right

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.


Building the website

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.


Implementing OpenLayers

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);

Fleshing out functionality

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
}));

Advanced functionality

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]);

Conclusion

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!


Download

DescriptionNameSize
Hike tracker and planner example projectos-openlayers-HikeExample.zip6.6KB

Resources

Learn

Get products and technologies

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.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=626189
ArticleTitle=Bring data together with OpenLayers
publish-date=02152011