3. Interfaz de Ruteo Basada en OpenLayers

El objetivo de este capítulo es crear una interfaz de usuario sencilla basada en web a pgRouting basada en OpenLayers. El usuario podrá elegir ubicaciones de inicio y destino y obtener la ruta desde el punto de inicio hasta el punto de destino.

Los puntos de inicio y destino son creados por el usuario, con simples clics en el mapa. A continuación, las coordenadas de inicio y destino se envían al servidor WMS (GeoServer), como parámetros a una solicitud WMS GetMap. La imagen resultante se agrega como una capa image al mapa.

3.1. Introducción a OpenLayers

OpenLayers utiliza tecnologías JavaScript y HTML5 modernas, como Canvas y WebGL, para la representación de imágenes/iconos y vectores.

Crear un mapa openlayers en una página web implica crear un objeto map, que es una instancia de la clase de mapa ol.Map. Posteriormente, se pueden agregar capas y controles de datos a ese objeto de mapa.

The center and resolution (zoom level) of the map are controlled through the view object. Unless other mapping libraries, the view is separated from the map; one advantage is to allow multiple maps to share the same view.

OpenLayers cuenta con tres renderizadores: el representador Canvas, el representador WebGL y el representador DOM. Actualmente, el renderizador más capaz es Canvas. En particular, el renderizador Canvas admite capas vectoriales, mientras que las otras dos no. Canvas es el representador predeterminado y el renderizador utilizado en este taller.

3.2. Crear un mapa mínimo

Vamos a crear nuestro primer mapa OpenLayers: abramos un editor de texto y copiemos este código en un archivo denominado ol.html. Puede guardar este archivo en Desktop y abrirlo con un navegador web.

<!DOCTYPE html>
<html>
  <head>
  <title>ol pgRouting client</title>
  <meta charset="utf-8">
  <link href="http://localhost/openlayers/dist/ol.css" rel="stylesheet">
  <style>
  #map {
    width: 100%;
    height: 400px;
  }
  </style>
  </head>
  <body>
  <div id="map"></div>
  <script src="http://localhost/openlayers/dist/ol.js"></script>
  <script type="text/javascript">
  var map = new ol.Map({
    target: 'map',
    layers: [
      new ol.layer.Tile({
        source: new ol.source.OSM()
      })
    ],
    view: new ol.View({
      center: ol.proj.transform([39.2765,-6.80975], 'EPSG:4326', 'EPSG:3857'),
      zoom: 13
    }),
    controls: ol.control.defaults({
      attributionOptions: {
        collapsible: false
      }
    })
  });
  </script>
  </body>
</html>

Nota

En este taller se asume que utiliza OSGeo Live, que incluye la biblioteca Javascript de OpenLayers accesible bajo la siguiente URL: http://localhost/openlayers/dist/ Si no utiliza OSGeo Live para este taller, debe ajustar la dirección URL al archivo Javascript y CSS de OpenLayers.

Esta página web incluye un mapa simple con una capa OpenStreetMap y un centro a una ubicación predifinida. No hay código relacionado con el ruteo por ahora; sólo un mapa simple con herramientas de navegación stantard.

Línea por línea tenemos:

  • Línea 6: incluir archivo CSS de OpenLayers predeterminado.

  • Línea 7 a Línea 12: reglas CSS para dar dimensiones al elemento DOM del mapa.

  • Línea 15: agregar un elemento div para el mapa. El identificador del elemento es map.

  • Línea 16: cargar el código de la biblioteca OpenLayers. Las funciones y clases del espacio de nombres ol provienen de allí.

  • Línea 18 a Línea 29: el código JavaScript específico de ese ejemplo.

Echemos un vistazo más de cerca al código que crea el código OpenLayers:

var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    })
  ],
  view: new ol.View({
    center: ol.proj.transform([39.2765,-6.80975], 'EPSG:4326', 'EPSG:3857'),
    zoom: 13
  }),
  controls: ol.control.defaults({
    attributionOptions: {
      collapsible: false
    }
  })
});

Este código crea un ol.Map instancia cuyo target es el elemento DOM map en la página HTML. El mapa se configura con una capa de teselas, configurada con una fuente OpenStreetMap . El mapa también se configura con una instancia de vista (de la clase ol.View ) con valores predefinidos para el centro y el nivel de zoom.

3.3. Parámetros WMS GET

Agregue este código después de la creación del mapa:

var params = {
  LAYERS: 'pgrouting:pgrouting',
  FORMAT: 'image/png'
};

El objeto params contiene los parámetros WMS GET que se enviarán a GeoServer. Aquí establecemos los valores que nunca cambiarán: el nombre de la capa y el formato de salida.

3.4. Seleccione «inicio» y «destino»

Ahora queremos permitir que el usuario agregue las posiciones de inicio y destino haciendo clic en el mapa. Agregue el código siguiente para ello:

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

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

Ese código crea dos características vectoriales, una para la posición «start» y otra para la posición «destination». Estas operaciones están vacías por ahora: no incluyen geometría.

El código también crea una capa vectorial, con las entidades «inicio» y «destino» añadidas a ella. También agrega la capa vectorial al mapa, utilizando el método``addLayer`` del mapa.

Agregar ahora el siguiente código:

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

// 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]
    ];
    params.viewparams = viewparams.join(';');
    result = new ol.layer.Image({
      source: new ol.source.ImageWMS({
        url: 'http://localhost:8082/geoserver/pgrouting/wms',
        params: params
      })
    });
    map.addLayer(result);
  }
});

Este código registra una función de escucha para el evento clic del mapa. Esto significa que OpenLayers llamará a esa función cada vez que se detecte un clic en el mapa.

El objeto del evento pasado a la función de escucha incluye una propiedad de “”coordinar”” que indica la ubicación geográfica del clic. Esa coordenada se utiliza para crear una geometría de punto para las entidades «inicio» y «destino».

Una vez que tengamos los puntos de inicio y destino (después de dos clics); los dos pares de coordenadas se transforman de la proyección del mapa (EPSG:3857) en la proyección del servidor (EPSG:4326) utilizando la función transform.

A continuación, la propiedad viewparams se establece en el objeto de parámetros WMS GET. El valor de esta propiedad tiene un significado especial: GeoServer sustituirá el valor antes de ejecutar la consulta SQL para la capa. Por ejemplo, si tenemos:

SELECT * FROM ways WHERE maxspeed_forward BETWEEN %min% AND %max%

Y viewparams es viewparams=min:20;max:120 entonces la consulta SQL enviada a PostGIS será:

SELECT * FROM ways WHERE maxspeed_forward BETWEEN 20 AND 120

Por último, se crea una nueva capa WMS OpenLayers y se agrega al mapa, se le pasa el objeto param.

3.5. Borrar los resultados

Añádalo después del mapa div en la página HTML:

<button id="clear">clear</button>

Esto crea un botón que usaremos para permitir al usuario borrar los puntos de ruteo e iniciar una nueva consulta de enrutamiento.

Ahora agregue lo siguiente al código JavaScript:

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

Cuando se hace clic en el botón, se ejecuta esta función pasada a addEventListener'. Esa función restablece las entidades «inicio» y «destino» y elimina la capa de resultados de ruteo del mapa.

3.6. Resumen (ejemplo completo)

Ahora copie el siguiente código de aplicación final en un archivo, al que puede acceder un servidor web, como Apache o Nginx, por ejemplo:

<!DOCTYPE html>
<html>
  <head>
  <title>ol pgRouting client</title>
  <meta charset="utf-8">
  <link href="http://localhost/openlayers/dist/ol.css" rel="stylesheet">
  <style>
  #map {
    width: 100%;
    height: 400px;
  }
  </style>
  </head>
  <body>
  <div id="map"></div>
  <button id="clear">clear</button>
  <script src="http://localhost/openlayers/dist/ol.js"></script>
  <script type="text/javascript">
  var map = new ol.Map({
    target: 'map',
    layers: [
      new ol.layer.Tile({
        source: new ol.source.OSM()
      })
    ],
    view: new ol.View({
      center: ol.proj.transform([39.2765,-6.80975], 'EPSG:4326', 'EPSG:3857'),
      zoom: 13
    }),
    controls: ol.control.defaults({
      attributionOptions: {
        collapsible: false
      }
    })
  });

  var params = {
    LAYERS: 'pgrouting:pgrouting',
    FORMAT: 'image/png'
  }

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

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

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

  // 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]
      ];
      params.viewparams = viewparams.join(';');
      result = new ol.layer.Image({
        source: new ol.source.ImageWMS({
          url: 'http://localhost:8082/geoserver/pgrouting/wms',
          params: params
        })
      });
      map.addLayer(result);
    }
  });

  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);
  });
  </script>
  </body>
</html>