{"id":5566,"date":"2016-06-14T07:30:33","date_gmt":"2016-06-14T10:30:33","guid":{"rendered":"http:\/\/www.fernandoquadro.com.br\/html\/?p=5566"},"modified":"2016-08-12T15:04:44","modified_gmt":"2016-08-12T18:04:44","slug":"criando-um-aplicativo-de-rotas-com-pgrouting-parte-5","status":"publish","type":"post","link":"https:\/\/www.fernandoquadro.com.br\/html\/2016\/06\/14\/criando-um-aplicativo-de-rotas-com-pgrouting-parte-5\/","title":{"rendered":"Criando um aplicativo de rotas com pgRouting \u2013 Parte 5"},"content":{"rendered":"<p>Para interagir com o nosso algoritmo de roteamento vamos precisar de um cliente que possa realizar solicita\u00e7\u00f5es no padr\u00e3o OGR para nossas camadas <i>nearest_vertex<\/i> e <i>shortest_path<\/i> no GeoServer. <\/p>\n<p>Iremos implementar um cliente muito simples com este tutorial que vai deixar o usu\u00e1rio arrastar os marcadores para in\u00edcio 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\u00e1 escrito em OpenLayers 3 com JQuery.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-1024x633.png\" alt=\"route2\" width=\"676\" height=\"418\" class=\"aligncenter size-large wp-image-5627\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-1024x633.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-300x185.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-768x475.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-945x584.png 945w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2-600x371.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/route2.png 1053w\" sizes=\"auto, (max-width: 676px) 100vw, 676px\" \/><\/p>\n<p>N\u00f3s estaremos usando o SDK Su\u00edte para criar um modelo para a constru\u00e7\u00e3o de nossa aplica\u00e7\u00e3o. Na linha de comando vamos executar o seguinte.<\/p>\n<pre>suite-sdk create routing ol3view<\/pre>\n<p>Agora n\u00f3s teremos um diret\u00f3rio com a aplica\u00e7\u00e3o b\u00e1sica para visualizar as camadas. Existem v\u00e1rios arquivos neste diret\u00f3rio, mas s\u00f3 vamos nos preocupar com index.html e src\/app\/app.js.<\/p>\n<p>Vamos primeiro precisar editar o arquivo index.html que carrega as bibliotecas OpenLayers e jQuery, para adicionar um componente onde possamos exibir informa\u00e7\u00f5es sobre a rota. Encontre a linha que tem &lt;div id = &#8220;map&#8221;&gt; e adicione a seguinte linha antes: &lt;div id = &#8220;info&#8221;&gt;&lt;\/div&gt;. Esta parte do arquivo agora deve ter a seguinte apar\u00eancia:<\/p>\n<pre>&nbsp;&nbsp;&lt;\/div&gt;&lt;!--\/.navbar-collapse&nbsp;--&gt;\r\n&lt;\/div&gt;\r\n&lt;div&nbsp;id=\"info\"&gt;&lt;\/div&gt;\r\n&lt;div&nbsp;id=\"map\"&gt;\r\n&nbsp;&nbsp;&lt;div&nbsp;id=\"popup\"&nbsp;class=\"ol-popup\"&gt;\r\n&nbsp;&nbsp;&lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>Agora, vamos construir nossa aplica\u00e7\u00e3o javascript passo-a-passo, mas ao contr\u00e1rio do que fizemos com o arquivo index.html vamos remover os arquivos app.js e escrever um novo a partir do zero. N\u00e3o esque\u00e7a de colocar seus novos arquivos na pasta src\/app.<\/p>\n<p>Vamos come\u00e7ar declarando algumas vari\u00e1veis, que incluir\u00e3o o ponto inicial (center point) e o n\u00edvel de zoom para o nosso mapa.<\/p>\n<pre>var geoserverUrl = '\/geoserver';\r\nvar center = ol.proj.transform([-70.26, 43.67], 'EPSG:4326', 'EPSG:3857');\r\nvar zoom = 12;\r\nvar pointerDown = false;\r\nvar currentMarker = null;\r\nvar changed = false;\r\nvar routeLayer;\r\nvar routeSource;\r\nvar travelTime;\r\nvar travelDist;<\/pre>\n<p>Vamos precisar atualizar o texto em dois elementos no documento index.html como nossas mudan\u00e7as de rota.<\/p>\n<pre>\/\/ elements in HTML document\r\nvar info = document.getElementById('info');\r\nvar popup = document.getElementById('popup');<\/pre>\n<p>Quando apresentarmos as informa\u00e7\u00f5es sobre a nossa rota, teremos de formatar os dados para exibi\u00e7\u00e3o. Por exemplo, o tempo que leva para viajar ao longo de uma rota \u00e9 medido em horas, por isso vamos ter o n\u00famero de 0,25 e format\u00e1-lo para exibir 15 minutos. Fa\u00e7a alguma formata\u00e7\u00e3o de dist\u00e2ncias, nomes das estradas e cruzamentos.<\/p>\n<p>Nosso mapa ter\u00e1 dois marcadores para que o usu\u00e1rio possa arrast\u00e1-los para novas posi\u00e7\u00f5es e indicar o in\u00edcio e fim da rota.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/markers.png\" alt=\"markers\" width=\"573\" height=\"294\" class=\"aligncenter size-full wp-image-5640\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/markers.png 573w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/markers-300x154.png 300w\" sizes=\"auto, (max-width: 573px) 100vw, 573px\" \/><\/p>\n<p>N\u00f3s vamos adicionar uma fun\u00e7\u00e3o para as camadas de sobreposi\u00e7\u00e3o chamada <em>changeHandler<\/em> que ser\u00e1 acionada sempre que um dos marcadores for movido.<\/p>\n<pre>\/\/&nbsp;create&nbsp;a&nbsp;point&nbsp;with&nbsp;a&nbsp;colour&nbsp;and&nbsp;change&nbsp;handler\r\nfunction&nbsp;createMarker(point,&nbsp;colour)&nbsp;{\r\n&nbsp;&nbsp;var&nbsp;marker&nbsp;=&nbsp;new&nbsp;ol.Feature({\r\n&nbsp;&nbsp;&nbsp;&nbsp;geometry:&nbsp;new&nbsp;ol.geom.Point(ol.proj.transform(point,&nbsp;'EPSG:4326',&nbsp;'EPSG:3857'))\r\n&nbsp;&nbsp;});\r\n&nbsp;&nbsp;marker.setStyle(\r\n&nbsp;&nbsp;&nbsp;&nbsp;[new&nbsp;ol.style.Style({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;image:&nbsp;new&nbsp;ol.style.Circle({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;radius:&nbsp;6,\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fill:&nbsp;new&nbsp;ol.style.Fill({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;color:&nbsp;'rgba('&nbsp;+&nbsp;colour.join(',')&nbsp;+&nbsp;',&nbsp;1)'\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;&nbsp;&nbsp;})]\r\n&nbsp;&nbsp;);\r\n&nbsp;&nbsp;marker.on('change',&nbsp;changeHandler);\r\n&nbsp;&nbsp;return&nbsp;marker;\r\n}\r\nvar&nbsp;sourceMarker&nbsp;=&nbsp;createMarker([-70.26013,&nbsp;43.66515],&nbsp;[0,&nbsp;255,&nbsp;0]);\r\nvar&nbsp;targetMarker&nbsp;=&nbsp;createMarker([-70.24667,&nbsp;43.66996],&nbsp;[255,&nbsp;0,&nbsp;0]);\r\n\/\/&nbsp;create&nbsp;overlay&nbsp;to&nbsp;display&nbsp;the&nbsp;markers\r\nvar&nbsp;markerOverlay&nbsp;=&nbsp;new&nbsp;ol.FeatureOverlay({\r\n&nbsp;&nbsp;features:&nbsp;[sourceMarker,&nbsp;targetMarker],\r\n});<\/pre>\n<p>A fun\u00e7\u00e3o para o movimento do marcador \u00e9 muito simples: vamos manter um registro do marcador que quando se move indica que a rota foi alterada.<\/p>\n<pre>\/\/&nbsp;record&nbsp;when&nbsp;we&nbsp;move&nbsp;one&nbsp;of&nbsp;the&nbsp;source\/target&nbsp;markers&nbsp;on&nbsp;the&nbsp;map\r\nfunction&nbsp;changeHandler(e)&nbsp;{\r\n&nbsp;&nbsp;if&nbsp;(pointerDown)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;changed&nbsp;=&nbsp;true;\r\n&nbsp;&nbsp;&nbsp;&nbsp;currentMarker&nbsp;=&nbsp;e.target;\r\n&nbsp;&nbsp;}\r\n}<\/pre>\n<p>Agora que os marcadores foram criados, podemos dizer ao OpenLayers que eles podem ser modificados pela intera\u00e7\u00e3o do usu\u00e1rio:<\/p>\n<pre>var&nbsp;moveMarker&nbsp;=&nbsp;new&nbsp;ol.interaction.Modify({\r\n&nbsp;&nbsp;features:&nbsp;markerOverlay.getFeatures(),\r\n&nbsp;&nbsp;tolerance:&nbsp;20\r\n});<\/pre>\n<p>Vamos criar uma segunda camada, que ser\u00e1 usada para exibir um pop-up quando o usu\u00e1rio clicar em segmentos da rota, e vamos destacar os segmentos selecionados com um estilo diferente.<\/p>\n<pre>\/\/&nbsp;create&nbsp;overlay&nbsp;to&nbsp;show&nbsp;the&nbsp;popup&nbsp;box\r\nvar&nbsp;popupOverlay&nbsp;=&nbsp;new&nbsp;ol.Overlay({\r\n&nbsp;&nbsp;element:&nbsp;popup\r\n});\r\n\r\n\/\/&nbsp;style&nbsp;routes&nbsp;differently&nbsp;when&nbsp;clicked\r\nvar&nbsp;selectSegment&nbsp;=&nbsp;new&nbsp;ol.interaction.Select({\r\n&nbsp;&nbsp;condition:&nbsp;ol.events.condition.click,\r\n&nbsp;&nbsp;style:&nbsp;new&nbsp;ol.style.Style({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stroke:&nbsp;new&nbsp;ol.style.Stroke({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;color:&nbsp;'rgba(255,&nbsp;0,&nbsp;128,&nbsp;1)',\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;8\r\n&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;})\r\n});<\/pre>\n<p>A camada base para a nossa aplica\u00e7\u00e3o ser\u00e1 do OpenStreetMap, que \u00e9 suportada pelo Openlayers 3. O mapa ser\u00e1 criado com suporte para os marcadores e as diferentes intera\u00e7\u00f5es que criamos sobre ele.<\/p>\n<pre>\/\/&nbsp;set&nbsp;the&nbsp;starting&nbsp;view\r\nvar&nbsp;view&nbsp;=&nbsp;new&nbsp;ol.View({\r\n&nbsp;&nbsp;center:&nbsp;center,\r\n&nbsp;&nbsp;zoom:&nbsp;zoom\r\n});\r\n\/\/&nbsp;create&nbsp;the&nbsp;map&nbsp;with&nbsp;OSM&nbsp;data\r\nvar&nbsp;map&nbsp;=&nbsp;new&nbsp;ol.Map({\r\n&nbsp;&nbsp;target:&nbsp;'map',\r\n&nbsp;&nbsp;layers:&nbsp;[\r\n&nbsp;&nbsp;&nbsp;&nbsp;new&nbsp;ol.layer.Tile({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;source:&nbsp;new&nbsp;ol.source.OSM()\r\n&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;],\r\n&nbsp;&nbsp;view:&nbsp;view,\r\n&nbsp;&nbsp;overlays:&nbsp;[popupOverlay,&nbsp;markerOverlay]\r\n});\r\nmap.addInteraction(moveMarker);\r\nmap.addInteraction(selectSegment);\r\n});<\/pre>\n<p>Vamos apresentar o pop-up quando o usu\u00e1rio clicar em um segmento de rota, mostrando o nome da estrada, a dist\u00e2ncia e o tempo necess\u00e1rio para atravess\u00e1-la.<\/p>\n<pre>\/\/&nbsp;show&nbsp;pop&nbsp;up&nbsp;box&nbsp;when&nbsp;clicking&nbsp;on&nbsp;part&nbsp;of&nbsp;route\r\nvar&nbsp;getFeatureInfo&nbsp;=&nbsp;function(coordinate)&nbsp;{\r\n&nbsp;&nbsp;var&nbsp;pixel&nbsp;=&nbsp;map.getPixelFromCoordinate(coordinate);\r\n&nbsp;&nbsp;var&nbsp;feature&nbsp;=&nbsp;map.forEachFeatureAtPixel(pixel,&nbsp;function(feature,&nbsp;layer)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(layer&nbsp;==&nbsp;routeLayer)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;feature;\r\n&nbsp;&nbsp;&nbsp;&nbsp;}\r\n&nbsp;&nbsp;});\r\n&nbsp;&nbsp;var&nbsp;text&nbsp;=&nbsp;null;\r\n&nbsp;&nbsp;if&nbsp;(feature)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;=&nbsp;'&lt;strong&gt;'&nbsp;+&nbsp;feature.get('name')&nbsp;+&nbsp;'&lt;\/strong&gt;&lt;br\/&gt;';\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;+=&nbsp;'&lt;p&gt;Distance:&nbsp;&lt;code&gt;'&nbsp;+&nbsp;feature.get('distance')&nbsp;+&nbsp;'&lt;\/code&gt;&lt;\/p&gt;';\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;+=&nbsp;'&lt;p&gt;Estimated&nbsp;travel&nbsp;time:&nbsp;&lt;code&gt;'&nbsp;+&nbsp;feature.get('time')&nbsp;+&nbsp;'&lt;\/code&gt;&lt;\/p&gt;';\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;=&nbsp;text.replace(\/&nbsp;\/g,&nbsp;'&nbsp;');\r\n&nbsp;&nbsp;}\r\n&nbsp;&nbsp;return&nbsp;text;\r\n};\r\n\/\/&nbsp;display&nbsp;the&nbsp;popup&nbsp;when&nbsp;user&nbsp;clicks&nbsp;on&nbsp;a&nbsp;route&nbsp;segment\r\nmap.on('click',&nbsp;function(evt)&nbsp;{\r\n&nbsp;&nbsp;var&nbsp;coordinate&nbsp;=&nbsp;evt.coordinate;\r\n&nbsp;&nbsp;var&nbsp;text&nbsp;=&nbsp;getFeatureInfo(coordinate);\r\n&nbsp;&nbsp;if&nbsp;(text)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;popupOverlay.setPosition(coordinate);\r\n&nbsp;&nbsp;&nbsp;&nbsp;popup.innerHTML&nbsp;=&nbsp;text;\r\n&nbsp;&nbsp;&nbsp;&nbsp;popup.style.display&nbsp;=&nbsp;'block';\r\n&nbsp;&nbsp;}\r\n});<\/pre>\n<p>Precisamos registrar quando o usu\u00e1rio inicia ou para de arrastar um marcador para que possamos saber quando recalcular a rota. Faremos isso registrando o evento do clique do mouse.<\/p>\n<pre>\/\/&nbsp;record&nbsp;start&nbsp;of&nbsp;click\r\nmap.on('pointerdown',&nbsp;function(evt)&nbsp;{\r\n&nbsp;&nbsp;pointerDown&nbsp;=&nbsp;true;\r\n&nbsp;&nbsp;popup.style.display&nbsp;=&nbsp;'none';\r\n});\r\n\/\/&nbsp;record&nbsp;end&nbsp;of&nbsp;click\r\nmap.on('pointerup',&nbsp;function(evt)&nbsp;{\r\n&nbsp;&nbsp;pointerDown&nbsp;=&nbsp;false;\r\n&nbsp;&nbsp;\/\/&nbsp;if&nbsp;we&nbsp;were&nbsp;dragging&nbsp;a&nbsp;marker,&nbsp;recalculate&nbsp;the&nbsp;route\r\n&nbsp;&nbsp;if&nbsp;(currentMarker)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;getVertex(currentMarker);\r\n&nbsp;&nbsp;&nbsp;&nbsp;getRoute();\r\n&nbsp;&nbsp;&nbsp;&nbsp;currentMarker&nbsp;=&nbsp;null;\r\n&nbsp;}\r\n});<\/pre>\n<p>O \u00faltimo passo antes de trabalhar com a comunica\u00e7\u00e3o do cliente com o GeoServer \u00e9 criar um temporizador que ir\u00e1 acionar a cada quarto de segundo, o que nos permite atualizar a rota periodicamente ao mover um marcador para uma nova localiza\u00e7\u00e3o.<\/p>\n<pre>\/\/&nbsp;timer&nbsp;to&nbsp;update&nbsp;the&nbsp;route&nbsp;when&nbsp;dragging\r\nwindow.setInterval(function(){\r\n&nbsp;&nbsp;if&nbsp;(currentMarker&nbsp;&&&nbsp;changed)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;getVertex(currentMarker);\r\n&nbsp;&nbsp;&nbsp;&nbsp;getRoute();\r\n&nbsp;&nbsp;&nbsp;&nbsp;changed&nbsp;=&nbsp;false;\r\n&nbsp;&nbsp;}\r\n},&nbsp;250);<\/pre>\n<p>No c\u00f3digo acima, podemos ver as chamadas para duas fun\u00e7\u00f5es principais: <i>getVertex<\/i> e <i>getRoute<\/i>. Estes dois m\u00e9todos realizam requisi\u00e7\u00f5es WFS ao GeoServer para obter informa\u00e7\u00f5es do recurso. <i>getVertex<\/i> recupera o v\u00e9rtice mais pr\u00f3ximo na rede para a posi\u00e7\u00e3o do marcador atual, enquanto <i>getRoute<\/i> calcula o caminho mais curto entre os dois marcadores.<\/p>\n<p>O m\u00e9todo <i>getVertex<\/i> utiliza as coordenadas atuais de um marcador e os passa como par\u00e2metros x e y para o <i>nearest_vertex<\/i>  (SQL View) que criamos no GeoServer. O requisi\u00e7\u00e3o GetFeature do WFS ser\u00e1 capturada como JSON e passada para a fun\u00e7\u00e3o <i>loadVertex<\/i> para o processamento.<\/p>\n<pre>\/\/&nbsp;WFS&nbsp;to&nbsp;get&nbsp;the&nbsp;closest&nbsp;vertex&nbsp;to&nbsp;a&nbsp;point&nbsp;on&nbsp;the&nbsp;map\r\nfunction&nbsp;getVertex(marker)&nbsp;{\r\n&nbsp;&nbsp;var&nbsp;coordinates&nbsp;=&nbsp;marker.getGeometry().getCoordinates();\r\n&nbsp;&nbsp;var&nbsp;url&nbsp;=&nbsp;geoserverUrl&nbsp;+&nbsp;'\/wfs?service=WFS&version=1.0.0&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'request=GetFeature&typeName=tutorial:nearest_vertex&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'outputformat=application\/json&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'viewparams=x:'&nbsp;+&nbsp;coordinates[0]&nbsp;+&nbsp;';y:'&nbsp;+&nbsp;coordinates[1];\r\n&nbsp;&nbsp;$.ajax({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url:&nbsp;url,\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;async:&nbsp;false,\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataType:&nbsp;'json',\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;success:&nbsp;function(json)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loadVertex(json,&nbsp;marker&nbsp;==&nbsp;sourceMarker);\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\r\n&nbsp;&nbsp;});\r\n}<\/pre>\n<p>O <i>loadVertex<\/i> analisa a resposta do GeoServer e armazena o v\u00e9rtice mais pr\u00f3ximo como o ponto de in\u00edcio ou o fim do nosso percurso. Vamos precisar do id do v\u00e9rtice mais tarde para solicitar a rota ao pgRouting.<\/p>\n<pre>\/\/&nbsp;load&nbsp;the&nbsp;response&nbsp;to&nbsp;the&nbsp;nearest_vertex&nbsp;layer\r\nfunction&nbsp;loadVertex(response,&nbsp;isSource)&nbsp;{\r\n&nbsp;&nbsp;var&nbsp;geojson&nbsp;=&nbsp;new&nbsp;ol.format.GeoJSON();\r\n&nbsp;&nbsp;var&nbsp;features&nbsp;=&nbsp;geojson.readFeatures(response);\r\n&nbsp;&nbsp;if&nbsp;(isSource)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(features.length&nbsp;==&nbsp;0)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.removeLayer(routeLayer);\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;source&nbsp;=&nbsp;null;\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;\r\n&nbsp;&nbsp;&nbsp;&nbsp;}\r\n&nbsp;&nbsp;&nbsp;&nbsp;source&nbsp;=&nbsp;features[0];\r\n&nbsp;&nbsp;}&nbsp;else&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(features.length&nbsp;==&nbsp;0)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.removeLayer(routeLayer);\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;target&nbsp;=&nbsp;null;\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return;\r\n&nbsp;&nbsp;&nbsp;&nbsp;}\r\n&nbsp;&nbsp;&nbsp;&nbsp;target&nbsp;=&nbsp;features[0];\r\n&nbsp;&nbsp;}\r\n}<\/pre>\n<p>Tudo o que fizemos at\u00e9 agora foi construir a requisi\u00e7\u00e3o final (WFS GetFeature) que vai realmente solicitar e exibir a rota. O <i>shortest_path<\/i> (SQL View) tem tr\u00eas par\u00e2metros, o v\u00e9rtice de origem, o v\u00e9rtice destino e o custo (dist\u00e2ncia ou tempo).<\/p>\n<pre>function&nbsp;getRoute()&nbsp;{\r\n&nbsp;&nbsp;\/\/&nbsp;set&nbsp;up&nbsp;the&nbsp;source&nbsp;and&nbsp;target&nbsp;vertex&nbsp;numbers&nbsp;to&nbsp;pass&nbsp;as&nbsp;parameters\r\n&nbsp;&nbsp;var&nbsp;viewParams&nbsp;=&nbsp;[\r\n&nbsp;&nbsp;&nbsp;&nbsp;'source:'&nbsp;+&nbsp;source.getId().split('.')[1],\r\n&nbsp;&nbsp;&nbsp;&nbsp;'target:'&nbsp;+&nbsp;target.getId().split('.')[1],\r\n&nbsp;&nbsp;&nbsp;&nbsp;'cost:time'\r\n&nbsp;&nbsp;];\r\n&nbsp;&nbsp;var&nbsp;url&nbsp;=&nbsp;geoserverUrl&nbsp;+&nbsp;'\/wfs?service=WFS&version=1.0.0&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'request=GetFeature&typeName=tutorial:shortest_path&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'outputformat=application\/json&'&nbsp;+\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'&viewparams='&nbsp;+&nbsp;viewParams.join(';');\r\n&nbsp;&nbsp;\/\/&nbsp;create&nbsp;a&nbsp;new&nbsp;source&nbsp;for&nbsp;our&nbsp;layer\r\n&nbsp;&nbsp;routeSource&nbsp;=&nbsp;new&nbsp;ol.source.ServerVector({\r\n&nbsp;&nbsp;&nbsp;&nbsp;format:&nbsp;new&nbsp;ol.format.GeoJSON(),\r\n&nbsp;&nbsp;&nbsp;&nbsp;strategy:&nbsp;ol.loadingstrategy.all,\r\n&nbsp;&nbsp;&nbsp;&nbsp;loader:&nbsp;function(extent,&nbsp;resolution)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$.ajax({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;url:&nbsp;url,\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataType:&nbsp;'json',\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;success:&nbsp;loadRoute,\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;async:&nbsp;false\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});\r\n&nbsp;&nbsp;&nbsp;&nbsp;},\r\n&nbsp;&nbsp;});\r\n&nbsp;&nbsp;\/\/&nbsp;remove&nbsp;the&nbsp;previous&nbsp;layer&nbsp;and&nbsp;create&nbsp;a&nbsp;new&nbsp;one\r\n&nbsp;&nbsp;map.removeLayer(routeLayer);\r\n&nbsp;&nbsp;routeLayer&nbsp;=&nbsp;new&nbsp;ol.layer.Vector({\r\n&nbsp;&nbsp;&nbsp;&nbsp;source:&nbsp;routeSource,\r\n&nbsp;&nbsp;&nbsp;&nbsp;style:&nbsp;new&nbsp;ol.style.Style({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stroke:&nbsp;new&nbsp;ol.style.Stroke({\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;color:&nbsp;'rgba(0,&nbsp;0,&nbsp;255,&nbsp;0.5)',\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;width:&nbsp;8\r\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;&nbsp;&nbsp;})\r\n&nbsp;&nbsp;});\r\n&nbsp;&nbsp;\/\/&nbsp;add&nbsp;the&nbsp;new&nbsp;layer&nbsp;to&nbsp;the&nbsp;map\r\n&nbsp;&nbsp;map.addLayer(routeLayer);\r\n}<\/pre>\n<p>A rota gerada ser\u00e1 usado para criar uma nova camada e atualizar as informa\u00e7\u00f5es da pop-up com os detalhes da rota, incluindo os locais de in\u00edcio e fim, a dist\u00e2ncia e o tempo de viagem.<\/p>\n<pre>\/\/&nbsp;handle&nbsp;the&nbsp;response&nbsp;to&nbsp;shortest_path\r\nvar&nbsp;loadRoute&nbsp;=&nbsp;function(response)&nbsp;{\r\n&nbsp;&nbsp;selectSegment.getFeatures().clear();\r\n&nbsp;&nbsp;routeSource.clear();\r\n&nbsp;&nbsp;var&nbsp;features&nbsp;=&nbsp;routeSource.readFeatures(response)\r\n&nbsp;&nbsp;if&nbsp;(features.length&nbsp;==&nbsp;0)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;info.innerHTML&nbsp;=&nbsp;'';\r\n&nbsp;&nbsp;&nbsp;&nbsp;return;\r\n&nbsp;&nbsp;}\r\n&nbsp;&nbsp;routeSource.addFeatures(features);\r\n&nbsp;&nbsp;var&nbsp;time&nbsp;=&nbsp;0;\r\n&nbsp;&nbsp;var&nbsp;dist&nbsp;=&nbsp;0;\r\n&nbsp;&nbsp;features.forEach(function(feature)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;+=&nbsp;feature.get('time');\r\n&nbsp;&nbsp;&nbsp;&nbsp;dist&nbsp;+=&nbsp;feature.get('distance');\r\n&nbsp;&nbsp;});\r\n&nbsp;&nbsp;if&nbsp;(!pointerDown)&nbsp;{\r\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/&nbsp;set&nbsp;the&nbsp;route&nbsp;text\r\n&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;text&nbsp;=&nbsp;'Travelling&nbsp;from&nbsp;<strong>'&nbsp;+&nbsp;formatPlaces(source.get('name'))&nbsp;+&nbsp;'<\/strong>&nbsp;to&nbsp;<strong>'&nbsp;+&nbsp;formatPlaces(target.get('name'))&nbsp;+&nbsp;'<\/strong>.&nbsp;';\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;+=&nbsp;'Total&nbsp;distance&nbsp;'&nbsp;+&nbsp;formatDist(dist)&nbsp;+&nbsp;'.&nbsp;';\r\n&nbsp;&nbsp;&nbsp;&nbsp;text&nbsp;+=&nbsp;'Estimated&nbsp;travel&nbsp;time:&nbsp;'&nbsp;+&nbsp;formatTime(time)&nbsp;+&nbsp;'.';\r\n&nbsp;&nbsp;&nbsp;&nbsp;info.innerHTML&nbsp;=&nbsp;text;\r\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/&nbsp;snap&nbsp;the&nbsp;markers&nbsp;to&nbsp;the&nbsp;exact&nbsp;route&nbsp;source\/target\r\n&nbsp;&nbsp;&nbsp;&nbsp;markerOverlay.getFeatures().clear();\r\n&nbsp;&nbsp;&nbsp;&nbsp;sourceMarker.setGeometry(source.getGeometry());\r\n&nbsp;&nbsp;&nbsp;&nbsp;targetMarker.setGeometry(target.getGeometry());\r\n&nbsp;&nbsp;&nbsp;&nbsp;markerOverlay.getFeatures().push(sourceMarker);\r\n&nbsp;&nbsp;&nbsp;&nbsp;markerOverlay.getFeatures().push(targetMarker);\r\n&nbsp;&nbsp;}\r\n}<\/pre>\n<p>Nossa aplica\u00e7\u00e3o agora est\u00e1 completa! Voc\u00ea pode test\u00e1-lo, executando o SDK no modo de depura\u00e7\u00e3o:<\/p>\n<pre>suite-sdk debug routing<\/pre>\n<p>Veja como ficou o nosso mapa:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-1024x736.png\" alt=\"application\" width=\"676\" height=\"486\" class=\"aligncenter size-large wp-image-5652\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-1024x736.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-300x216.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-768x552.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-945x680.png 945w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application-600x431.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2016\/06\/application.png 1043w\" sizes=\"auto, (max-width: 676px) 100vw, 676px\" \/><\/p>\n<p>Este tutorial \u00e9 uma tradu\u00e7\u00e3o e adapta\u00e7\u00e3o livre do artigo \u201cBuilding a Routing Application\u201d publicado no site da Boundless.<\/p>\n<p>Fonte: <a href=\"http:\/\/workshops.boundlessgeo.com\/tutorial-routing\/\" target=\"_blank\">Boundless<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Para interagir com o nosso algoritmo de roteamento vamos precisar de um cliente que possa realizar solicita\u00e7\u00f5es no padr\u00e3o OGR para nossas camadas nearest_vertex e shortest_path no GeoServer. Iremos implementar um cliente muito simples com este tutorial que vai deixar&#8230; <a class=\"more-link\" href=\"https:\/\/www.fernandoquadro.com.br\/html\/2016\/06\/14\/criando-um-aplicativo-de-rotas-com-pgrouting-parte-5\/\">Continue Reading &rarr;<\/a><\/p>\n","protected":false},"author":275,"featured_media":5551,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,24,132,11],"tags":[208,223,250,266,212],"class_list":["post-5566","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-geoserver","category-gis","category-openlayers","category-postgis","tag-geoserver","tag-gis","tag-openlayers","tag-pgrouting","tag-postgis"],"_links":{"self":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/5566","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/users\/275"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/comments?post=5566"}],"version-history":[{"count":28,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/5566\/revisions"}],"predecessor-version":[{"id":6211,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/5566\/revisions\/6211"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media\/5551"}],"wp:attachment":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media?parent=5566"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/categories?post=5566"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/tags?post=5566"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}