Rota/Trajeto editável usando o Google Maps

Visão Geral

Neste tópico vou apresentar uma solução de Trajeto/Rota editável usando Google Maps no Formulário.

Serão utilizados os seguintes recursos:

  • Biblioteca e Chave de API do Google Maps
  • Uma tabela no banco de dados
  • Formulário
  • Códigos JavaScript.

Este exemplo funcionará da seguinte maneira:

  • O usuário informa o local de origem e de destino e clica no primeiro botão para calcular a rota usando a API do Google Maps.

  • Depois, o usuário arrasta partes da rota para pontos específicos do mapa para gerar desvios (mesmo comportamento do App Google Maps).

  • Cada vez que um ponto é arrastado para gerar um desvio, o mapa cria um novo “Waypoint”.

  • Ao clicar no segundo botão, a origem, destino e os Waypoints são gravados no banco de dados.

  • Para carregar a rota previamente configurarada, quando a página for aberta novamente, a rota é calculada novamente, mas dessa vez especificando os Waypoints. Os Waypoints serão os responsáveis por carregar o formato customizado da rota.

Você pode conferir o exemplo em funcionamento clicando neste link.

Neste exemplo, o campo "Trajeto", que contém todas as coordenadas que formam o caminho (polyline) não está sendo usado para nada. Mas se for necessário, essas coordenadas podem ser salvas no banco de dados para serem carregadas em mapa do LATROMI.

Chave de API

Para obter ou configurar uma chave de API do Google Maps, é necessário acessar a página Credenciais - APIs e Servidor do Google Cloud Platform.

Neste exemplo, vamos precisar das seguintes APIs habilitadas para a chave:

  • Directions API
  • Geocoding API
  • Maps JavaScript API

Banco de Dados

Para gravar os dados do percurso, vamor criar uma tabela bem simples, e inicializa-la com o registro que vamos apresentar na tela:

CREATE TABLE routes
(
    id               INT NOT NULL,
    name             TEXT NOT NULL,
    origin           TEXT,
    destination      TEXT,
    waypoints        TEXT,
    CONSTRAINT pk_routes_id PRIMARY KEY (id)
);
-- Insere registro para demonstração
INSERT INTO routes (id, name, origin, destination)
VALUES (1, 'Rota de Teste', 'Latromi', 'Eco Bike Park Vento Negro')

Formulário

Crie um Formulário do LATROMI com a seguinte estrutura para hospedar o mapa:

Variáveis

Nome Comentários
apiKey Inicialize essa variável com a chave de API no evento Load do Formulário.

Campos

Nome Comentários
origin Critério de pesquisa do local de origem
destination Critério de pesquisa do local de destino
applyButton Botão para realizar o cálculo da rota
saveButton Botão para salvar a rota calculada
waypoints Recebe os desvios de rota, gerados para cada ponto editado no trajeto
path Recebe o caminho completo do trajeto, formato por uma lista de coordenadas

JavaScript

Adicione um novo arquivo JavaScript no Formulário, e nele informe o seguinte código:

const myForm = (function () {

    let map;
    let directionsService;
    let directionsRenderer;

    // Adiciona Callbacks no Formulário
    latromi.formManager.setOnFormCreatedCallback(onFormCreated);
    latromi.formManager.setOnEventFiringCallback(onEventFiring);
    latromi.formManager.setOnFieldValueChangedCallback(onFieldValueChanged);

    // Ocorre logo após o form ser inicializado
    function onFormCreated(ev) {

        const apiKey = ev.form.getVariableValue('apiKey');
        latromi.extensions.loadScripts(['https://maps.googleapis.com/maps/api/js?libraries=places&v=weekly&key=' + apiKey], initMap);
    }

    // Ocorre quando um evento é disparado no Form
    function onEventFiring(ev) { }

    // Ocorre quando o valor de um campo é alterado no Form
    function onFieldValueChanged(ev) { }

    // Função de callback para inicialização do mapa.
    // Essa função é chamada logo após a conclusão do carregamento das bibliotecas do Google Maps.
    function initMap() {
        // Initializa o mapa, passando o elemento "div" onde ele será renderizado
        map = new google.maps.Map(document.getElementById("map"));

        // Inicializa os serviços que fazem calculo e a apresentação das rotas
        directionsService = new google.maps.DirectionsService();
        directionsRenderer = new google.maps.DirectionsRenderer({
            draggable: true, // Essa propriedade habilita a criação de desvios na rota
            map
        });

        calculateRoute();

        // Adiciona callback no "directions_changed" para interceptar as mudanças na rota
        directionsRenderer.addListener("directions_changed", () => {
            const directions = directionsRenderer.getDirections();
            if (directions) {
                updateOuput(directions);
            }
        });
    }

    // Atualiza campos com "waypoints" (Desvios) e "path" (Rota)
    function updateOuput(directionResult) {
        const form = latromi.formManager.getFormInstance();
        form.setFieldValue('waypoints', formatCoordinates(getWaypointsCoordinates(directionResult)));
        form.setFieldValue('path', formatCoordinates(getPathCoordinates(directionResult)));
        form.setFieldValue('origin', getStartAddress(directionResult));
        form.setFieldValue('destination', getEndAddress(directionResult));
    }

    // Função que calcula a rota
    function calculateRoute() {
        if (!map) throw 'O mapa ainda não foi carregado.';

        const form = latromi.formManager.getFormInstance();
        const origin = form.getFieldValue('origin');            // Pega valor do TextBox "origin"
        const destination = form.getFieldValue('destination');  // Pega valor do TextBox "destination"
        const waypoints = parseStoredCoordinates(form.getFieldValue('waypoints')); // Pega valor do TextBox "waypoints"

        // Se origem e destino não foram informados, não faz nada
        if (!origin || !destination) return;

        const request = {
            origin: origin,
            destination: destination,
            waypoints: waypoints,
            travelMode: google.maps.TravelMode.DRIVING,
            avoidTolls: true,
        };

        directionsService
            .route(request)
            .then((result) => {
                directionsRenderer.setDirections(result);
            })
            .catch((e) => {
                alert("Could not display directions due to: " + e);
            });
    }

    // Transforma o valor fornecido no parâmetro "text" em um array de LatLng:
    //   Formato fornecido: 
    //      (lat,lng),(lat,lng)
    //
    //   Formato de resposta: 
    //      [
    //          {lat: number, lng: number}, 
    //          {lat: number, lng: number}
    //      ]
    function parseStoredCoordinates(text) {
        const result = [];

        if (text) {
            const reg = /[-+]?\d+(\.\d+)?,\s?[-+]?\d+(\.\d+)?\b/g;
            while ((match = reg.exec(text)) !== null) {
                const coordArray = match[0].split(',');
                result.push({
                    location: {
                        lat: parseFloat(coordArray[0]),
                        lng: parseFloat(coordArray[1])
                    }, stopover: false
                });
            }
        }
        return result;
    }

    // Formata o array de coordenadas fornecido no parâmetro "coordinates" para o formato (lat,lng),(lat,lng)
    function formatCoordinates(coordinates) {
        const arr = []
        if (coordinates) {
            for (let index = 0; index < coordinates.length; index++) {
                arr.push("(" + coordinates[index].lat() + "," + coordinates[index].lng() + ")");
            }
        }
        return arr.join(",");
    }

    // Retorna todas as coordenadas que compõe o trajeto (path) do resultado
    // do cálculo de rota fornecido no parâmetro "directionResult".
    function getPathCoordinates(directionResult) {
        const myroute = directionResult.routes[0];
        if (!myroute) return;
        return myroute.overview_path;
    }

    // Retorna as coordenadas dos waypoints (desvios) do trajeto  do resultado
    // do cálculo de rota fornecido no parâmetro "directionResult".
    function getWaypointsCoordinates(directionResult) {
        const myroute = directionResult.routes[0];
        if (!myroute) return;

        const coordinates = [];

        for (let legIndex = 0; legIndex < myroute.legs.length; legIndex++) {
            const leg = myroute.legs[legIndex];
            // Armazena waipoints
            for (let wpIndex = 0; wpIndex < leg.via_waypoint.length; wpIndex++) {
                coordinates.push(leg.via_waypoint[wpIndex].location);
            }
        }
        return coordinates;
    }

    // Retorna o endereço de origem do trajeto do resultado
    // do cálculo de rota fornecido no parâmetro "directionResult".
    function getEndAddress(directionResult, legIndex) {
        const myroute = directionResult.routes[0];
        if (!myroute) return;

        const myLeg = myroute.legs[legIndex || 0];
        if (!myLeg) return;

        return myLeg.end_address;
    }

    // Retorna o endereço de destino do trajeto do resultado
    // do cálculo de rota fornecido no parâmetro "directionResult".
    function getStartAddress(directionResult, legIndex) {
        const myroute = directionResult.routes[0];
        if (!myroute) return;

        const myLeg = myroute.legs[legIndex || 0];
        if (!myLeg) return;

        return myLeg.start_address;
    }

    // Todas as funções acima são "privadas", e não pode ser chamadas externamente.
    // A única função que pode ser chamada de externamente são as que compões o resultado a seguir:
    return {
        // Faz o calculo de rota uilizando os campos 
        // do formulário como critérios de pesquisa.
        route: calculateRoute
    }
})();
Esse script utiliza recursos "modernos" de JavaScript, como "arrow functions" e declaração de variáveis usando "const" e "let", portanto pode não funcionar em dispositivos obsoletos, que não recebem atualizações do navegador a algum tempo.

Eventos

Form: Load

Adicione uma instruação do tipo Criar Record para carregar o registro que inserimos na nossa tabela:

SELECT * FROM routes WHERE id = 1

Em seguida adicione uma instrução Popular Campos e Variáveis. Preencha os campos
“origin”, “destination” e “waypoints” com os suas respectivas colunas.

Preencha também a variável “apiKey” com o valor da sua chave de API.

applyButton: Click

No evento click do botão, utilize uma instrução do tipo Popular Campos e Variáveis para limpar o campo “waypoints”.

Em seguida, adicione uma instrução do tipo Executar código JavaScript, e informe o código a seguir:

myForm.route();

saveButton: Click

Por fim, adicionei uma instrução Executar comando SQL para salvar a rota no banco de dados:

UPDATE routes
SET origin = '{?INPUT origin}'
, destination = '{?INPUT destination}'
, waypoints = NULLIF('{?INPUT waypoints}', '')
WHERE id = 1

Referências

Você encontra a documentação completa da API “Directions” do Google Maps na página a seguir:

3 curtidas