Para interagir com o nosso algoritmo de roteamento vamos precisar de um cliente que possa realizar solicitações no padrão OGR para nossas camadas nearest_vertex e shortest_path no GeoServer.
Iremos implementar um cliente muito simples com este tutorial que vai deixar o usuário arrastar os marcadores para início e destino da rota e, em seguida, atualizar o mapa com uma linha indicando a rota mais curta entre os dois pontos. O cliente será escrito em OpenLayers 3 com JQuery.

Nós estaremos usando o SDK Suíte para criar um modelo para a construção de nossa aplicação. Na linha de comando vamos executar o seguinte.
suite-sdk create routing ol3view
Agora nós teremos um diretório com a aplicação básica para visualizar as camadas. Existem vários arquivos neste diretório, mas só vamos nos preocupar com index.html e src/app/app.js.
Vamos primeiro precisar editar o arquivo index.html que carrega as bibliotecas OpenLayers e jQuery, para adicionar um componente onde possamos exibir informações sobre a rota. Encontre a linha que tem <div id = “map”> e adicione a seguinte linha antes: <div id = “info”></div>. Esta parte do arquivo agora deve ter a seguinte aparência:
</div><!--/.navbar-collapse --> </div> <div id="info"></div> <div id="map"> <div id="popup" class="ol-popup"> </div> </div>
Agora, vamos construir nossa aplicação javascript passo-a-passo, mas ao contrário do que fizemos com o arquivo index.html vamos remover os arquivos app.js e escrever um novo a partir do zero. Não esqueça de colocar seus novos arquivos na pasta src/app.
Vamos começar declarando algumas variáveis, que incluirão o ponto inicial (center point) e o nível de zoom para o nosso mapa.
var geoserverUrl = '/geoserver'; var center = ol.proj.transform([-70.26, 43.67], 'EPSG:4326', 'EPSG:3857'); var zoom = 12; var pointerDown = false; var currentMarker = null; var changed = false; var routeLayer; var routeSource; var travelTime; var travelDist;
Vamos precisar atualizar o texto em dois elementos no documento index.html como nossas mudanças de rota.
// elements in HTML document
var info = document.getElementById('info');
var popup = document.getElementById('popup');
Quando apresentarmos as informações sobre a nossa rota, teremos de formatar os dados para exibição. Por exemplo, o tempo que leva para viajar ao longo de uma rota é medido em horas, por isso vamos ter o número de 0,25 e formatá-lo para exibir 15 minutos. Faça alguma formatação de distâncias, nomes das estradas e cruzamentos.
Nosso mapa terá dois marcadores para que o usuário possa arrastá-los para novas posições e indicar o início e fim da rota.

Nós vamos adicionar uma função para as camadas de sobreposição chamada changeHandler que será acionada sempre que um dos marcadores for movido.
// create a point with a colour and change handler
function createMarker(point, colour) {
var marker = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.transform(point, 'EPSG:4326', 'EPSG:3857'))
});
marker.setStyle(
[new ol.style.Style({
image: new ol.style.Circle({
radius: 6,
fill: new ol.style.Fill({
color: 'rgba(' + colour.join(',') + ', 1)'
})
})
})]
);
marker.on('change', changeHandler);
return marker;
}
var sourceMarker = createMarker([-70.26013, 43.66515], [0, 255, 0]);
var targetMarker = createMarker([-70.24667, 43.66996], [255, 0, 0]);
// create overlay to display the markers
var markerOverlay = new ol.FeatureOverlay({
features: [sourceMarker, targetMarker],
});
A função para o movimento do marcador é muito simples: vamos manter um registro do marcador que quando se move indica que a rota foi alterada.
// record when we move one of the source/target markers on the map
function changeHandler(e) {
if (pointerDown) {
changed = true;
currentMarker = e.target;
}
}
Agora que os marcadores foram criados, podemos dizer ao OpenLayers que eles podem ser modificados pela interação do usuário:
var moveMarker = new ol.interaction.Modify({
features: markerOverlay.getFeatures(),
tolerance: 20
});
Vamos criar uma segunda camada, que será usada para exibir um pop-up quando o usuário clicar em segmentos da rota, e vamos destacar os segmentos selecionados com um estilo diferente.
// create overlay to show the popup box
var popupOverlay = new ol.Overlay({
element: popup
});
// style routes differently when clicked
var selectSegment = new ol.interaction.Select({
condition: ol.events.condition.click,
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(255, 0, 128, 1)',
width: 8
})
})
});
A camada base para a nossa aplicação será do OpenStreetMap, que é suportada pelo Openlayers 3. O mapa será criado com suporte para os marcadores e as diferentes interações que criamos sobre ele.
// set the starting view
var view = new ol.View({
center: center,
zoom: zoom
});
// create the map with OSM data
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: view,
overlays: [popupOverlay, markerOverlay]
});
map.addInteraction(moveMarker);
map.addInteraction(selectSegment);
});
Vamos apresentar o pop-up quando o usuário clicar em um segmento de rota, mostrando o nome da estrada, a distância e o tempo necessário para atravessá-la.
// show pop up box when clicking on part of route
var getFeatureInfo = function(coordinate) {
var pixel = map.getPixelFromCoordinate(coordinate);
var feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if (layer == routeLayer) {
return feature;
}
});
var text = null;
if (feature) {
text = '<strong>' + feature.get('name') + '</strong><br/>';
text += '<p>Distance: <code>' + feature.get('distance') + '</code></p>';
text += '<p>Estimated travel time: <code>' + feature.get('time') + '</code></p>';
text = text.replace(/ /g, ' ');
}
return text;
};
// display the popup when user clicks on a route segment
map.on('click', function(evt) {
var coordinate = evt.coordinate;
var text = getFeatureInfo(coordinate);
if (text) {
popupOverlay.setPosition(coordinate);
popup.innerHTML = text;
popup.style.display = 'block';
}
});
Precisamos registrar quando o usuário inicia ou para de arrastar um marcador para que possamos saber quando recalcular a rota. Faremos isso registrando o evento do clique do mouse.
// record start of click
map.on('pointerdown', function(evt) {
pointerDown = true;
popup.style.display = 'none';
});
// record end of click
map.on('pointerup', function(evt) {
pointerDown = false;
// if we were dragging a marker, recalculate the route
if (currentMarker) {
getVertex(currentMarker);
getRoute();
currentMarker = null;
}
});
O último passo antes de trabalhar com a comunicação do cliente com o GeoServer é criar um temporizador que irá acionar a cada quarto de segundo, o que nos permite atualizar a rota periodicamente ao mover um marcador para uma nova localização.
// timer to update the route when dragging
window.setInterval(function(){
if (currentMarker && changed) {
getVertex(currentMarker);
getRoute();
changed = false;
}
}, 250);
No código acima, podemos ver as chamadas para duas funções principais: getVertex e getRoute. Estes dois métodos realizam requisições WFS ao GeoServer para obter informações do recurso. getVertex recupera o vértice mais próximo na rede para a posição do marcador atual, enquanto getRoute calcula o caminho mais curto entre os dois marcadores.
O método getVertex utiliza as coordenadas atuais de um marcador e os passa como parâmetros x e y para o nearest_vertex (SQL View) que criamos no GeoServer. O requisição GetFeature do WFS será capturada como JSON e passada para a função loadVertex para o processamento.
// WFS to get the closest vertex to a point on the map
function getVertex(marker) {
var coordinates = marker.getGeometry().getCoordinates();
var url = geoserverUrl + '/wfs?service=WFS&version=1.0.0&' +
'request=GetFeature&typeName=tutorial:nearest_vertex&' +
'outputformat=application/json&' +
'viewparams=x:' + coordinates[0] + ';y:' + coordinates[1];
$.ajax({
url: url,
async: false,
dataType: 'json',
success: function(json) {
loadVertex(json, marker == sourceMarker);
}
});
}
O loadVertex analisa a resposta do GeoServer e armazena o vértice mais próximo como o ponto de início ou o fim do nosso percurso. Vamos precisar do id do vértice mais tarde para solicitar a rota ao pgRouting.
// load the response to the nearest_vertex layer
function loadVertex(response, isSource) {
var geojson = new ol.format.GeoJSON();
var features = geojson.readFeatures(response);
if (isSource) {
if (features.length == 0) {
map.removeLayer(routeLayer);
source = null;
return;
}
source = features[0];
} else {
if (features.length == 0) {
map.removeLayer(routeLayer);
target = null;
return;
}
target = features[0];
}
}
Tudo o que fizemos até agora foi construir a requisição final (WFS GetFeature) que vai realmente solicitar e exibir a rota. O shortest_path (SQL View) tem três parâmetros, o vértice de origem, o vértice destino e o custo (distância ou tempo).
function getRoute() {
// set up the source and target vertex numbers to pass as parameters
var viewParams = [
'source:' + source.getId().split('.')[1],
'target:' + target.getId().split('.')[1],
'cost:time'
];
var url = geoserverUrl + '/wfs?service=WFS&version=1.0.0&' +
'request=GetFeature&typeName=tutorial:shortest_path&' +
'outputformat=application/json&' +
'&viewparams=' + viewParams.join(';');
// create a new source for our layer
routeSource = new ol.source.ServerVector({
format: new ol.format.GeoJSON(),
strategy: ol.loadingstrategy.all,
loader: function(extent, resolution) {
$.ajax({
url: url,
dataType: 'json',
success: loadRoute,
async: false
});
},
});
// remove the previous layer and create a new one
map.removeLayer(routeLayer);
routeLayer = new ol.layer.Vector({
source: routeSource,
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 255, 0.5)',
width: 8
})
})
});
// add the new layer to the map
map.addLayer(routeLayer);
}
A rota gerada será usado para criar uma nova camada e atualizar as informações da pop-up com os detalhes da rota, incluindo os locais de início e fim, a distância e o tempo de viagem.
// handle the response to shortest_path
var loadRoute = function(response) {
selectSegment.getFeatures().clear();
routeSource.clear();
var features = routeSource.readFeatures(response)
if (features.length == 0) {
info.innerHTML = '';
return;
}
routeSource.addFeatures(features);
var time = 0;
var dist = 0;
features.forEach(function(feature) {
time += feature.get('time');
dist += feature.get('distance');
});
if (!pointerDown) {
// set the route text
var text = 'Travelling from ' + formatPlaces(source.get('name')) + ' to ' + formatPlaces(target.get('name')) + '. ';
text += 'Total distance ' + formatDist(dist) + '. ';
text += 'Estimated travel time: ' + formatTime(time) + '.';
info.innerHTML = text;
// snap the markers to the exact route source/target
markerOverlay.getFeatures().clear();
sourceMarker.setGeometry(source.getGeometry());
targetMarker.setGeometry(target.getGeometry());
markerOverlay.getFeatures().push(sourceMarker);
markerOverlay.getFeatures().push(targetMarker);
}
}
Nossa aplicação agora está completa! Você pode testá-lo, executando o SDK no modo de depuração:
suite-sdk debug routing
Veja como ficou o nosso mapa:

Este tutorial é uma tradução e adaptação livre do artigo “Building a Routing Application” publicado no site da Boundless.
Fonte: Boundless