/ openlayers3

Combining pgRouting functions in OpenLayers3

In my previous post about getting the Turn Restricted Shortest Path (TRSP) function working in the Geoserver/OpenLayers3 web application I outlined the two functions used to calculate the shortest and quickest paths. In this post I will show the web parts that let me combine both functions to calculate both routing options on one map.

TRSP using two different costs

So my PostGIS database has both functions installed. My Geoserver has two new SQL views set to hold the route geometry returned by the functions. My HTML file to hold the map has been updated to link in an updated script which generates the start and end points and then calls the two functions to calculate the routes.

Right then, the HTML file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>pgRouting in OL3</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
    <link rel="stylesheet" href="lib/ol3/ol.css" />
    <link rel="stylesheet" href="lib/ol3-popup/src/ol3-popup.css" />
    <link rel="stylesheet" href="ol3-sbs.css" />
  </head>
  <body>
    <div id="map"></div>
    <div class="overlay title">
    <h4>Turn Restricted Shortest Path (TRSP)</h4>
    <p>Click twice to add start and end points. Click the RESET button to clear the map.</p>
    <p><font color="green">Green</font> route is shortest, <font color="blue">blue</font> route is quickest.</p>
    </div>
    <div class="overlay button"><button id="clear">reset</button></div>
    <script src="../common/lib/reqwest.min.js"></script>
    <script src="lib/proj4.js"></script>
    <script src="lib/ol3/ol.js"></script>
    <script src="lib/ol3-popup/src/ol3-popup.js"></script>
    <script src="ol3-trsp-both.js"></script>
  </body>
</html>

The bit we are interested in here is the last script referenced in the HTML above:

<script src="ol3-trsp-both.js"></script>

The file sets up the map to use the British National Grid (EPSG:27700), sets the centre and the zoom levels and adds a "reset" button to clear the map.

// 1. Extent of the map in units of the projection (these match our base map)
var extent = [-3276800, -3276800, 3276800, 3276800];

// 2. Fixed resolutions to display the map at (pixels per ground unit (meters when
// the projection is British National Grid))
var resolutions = [1600,800,400,200,100,50,25,10,5,2.5,1,0.5,0.25,0.125,0.0625];

// 3. Define British National Grid Proj4js projection (copied from http://epsg.io/27700.js)
proj4.defs("EPSG:27700","+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs");

// 4. Define an OL3 projection based on the included Proj4js projection
// definition and set it's extent.
var bng = ol.proj.get('EPSG:27700');
bng.setExtent(extent);

// 5. Define a TileGrid to ensure that WMS requests are made for
// tiles at the correct resolutions and tile boundaries
var tileGrid = new ol.tilegrid.TileGrid({
    origin: extent.slice(0, 2),
    resolutions: resolutions
});

// 6. Create the map
var map = new ol.Map({
    target: 'map',
    layers: [
        new ol.layer.Tile({
            source: new ol.source.TileWMS({
                url: 'http://yourserver/geoserver/wms?',
                attributions: [
                    new ol.Attribution({html: 'Angus Council 100023404 OS data &copy; Crown copyright and database right 2015'})
                ],
                params: {
                    'LAYERS': 'yourlayers',
                    'FORMAT': 'image/png',
                    'TILED': true
                },
                tileGrid: tileGrid
            })
        })
    ],
    view: new ol.View({
        projection: bng,
        resolutions: resolutions,
        center: [343965, 759502],
        zoom: 5
    }),
    controls: ol.control.defaults({
      attributionOptions: {
        collapsible: true
      }
    })
});

// 7. pgRouting layer values
var params = {
    'LAYERS': 'routing:itn_trsp_time',
    'FORMAT': 'image/png'
};
var params2 = {
    'LAYERS': 'routing:itn_trsp_length',
    'FORMAT': 'image/png'
};

// 8. The "start" and "destination" features.
var startPoint = new ol.Feature();
var destPoint = new ol.Feature();

// 9. The vector layer used to display the "start" and "destination" features.
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector({
    features: [startPoint, destPoint]
  })
});
map.addLayer(vectorLayer);

// 10. A transform function to convert coordinates from EPSG:27700
// to EPSG:27700.
var transform = ol.proj.getTransform('EPSG:27700', 'EPSG:27700');

// 11. Register a map click listener.
map.on('click', function(event) {
  if (startPoint.getGeometry() == null) {
    // First click.
    startPoint.setGeometry(new ol.geom.Point(event.coordinate));
  } else if (destPoint.getGeometry() == null) {
    // Second click.
    destPoint.setGeometry(new ol.geom.Point(event.coordinate));
    // Transform the coordinates from the map projection (EPSG:3857)
    // to the server projection (EPSG:4326).
    var startCoord = transform(startPoint.getGeometry().getCoordinates());
    var destCoord = transform(destPoint.getGeometry().getCoordinates());
    var viewparams = [
      'x1:' + startCoord[0], 'y1:' + startCoord[1],
      'x2:' + destCoord[0], 'y2:' + destCoord[1]
    ];
    // 12. Quickest Path
    params.viewparams = viewparams.join(';');
    result = new ol.layer.Image({
      source: new ol.source.ImageWMS({
        url: 'http://yourserver/geoserver/wms?',
        params: params
      })
    });
    map.addLayer(result);
    // 13. Shortest Path
    params2.viewparams = viewparams.join(';');
    result2 = new ol.layer.Image({
      source: new ol.source.ImageWMS({
        url: 'http://yourserver/geoserver/wms?',
        params: params2
      })
    });
    map.addLayer(result2);
  }
});

// 14. Create a RESET button to clear the map
var clearButton = document.getElementById('clear');
clearButton.addEventListener('click', function(event) {
  // Reset the "start" and "destination" features.
  startPoint.setGeometry(null);
  destPoint.setGeometry(null);
  // Remove the result layer.
  map.removeLayer(result);
  map.removeLayer(result2);
});

In the code above sections 1 through 6 set up the map. Section 7 sets up the two layers that hold the TRSP routing results. Sections 8 and 9 set up the vector layer to hold the start and end points of your route. Sections 11, 12 and 13 build the routing results and add them to the map. I have Geoserver set up to render the two routes in different colours. The last section adds the reset button to the map so you can calculate another route.

I'll see if I can get a working example up on the web shortly.