Conteúdo


Desenvolver e implementar um aplicativo localizador de caixas eletrônicos no IBM Cloud

As APIs Google Places e Google Maps simplificam a localização e o mapeamento de caixas eletrônicos perto de você

Comments

Um dos aplicativos usados com mais frequência no meu smartphone é, sem dúvida, o Google Maps. É excelente para verificar rapidamente o tráfego antes de sair de casa; também é muito útil quando você está tentando encontrar o caminho para um novo destino. Além disso, é uma ótima maneira de descobrir novos restaurantes e lojas interessantes quando estiver em um novo bairro ou cidade.

Contudo, o Google Maps também é interessante pela perspectiva do desenvolvimento. Como muitos outros produtos Google, expõe várias APIs que permitem que os desenvolvedores se conectem a ele e desenvolvam aplicativos customizados usando seus dados. Por exemplo, a API Google Places permite recuperar informações sobre lojas, empresas e outros estabelecimentos por local, enquanto a API Google Maps permite integrar um mapa estático ou interativo em uma página da web. Essas duas APIs, assim como algumas outras, podem ser usadas diretamente por meio de linguagens de programação comuns, como PHP e JavaScript.

Este tutorial apresenta a API Google Places ao mostrar como desenvolver um aplicativo simples que usa sua localização atual para identificar os caixas eletrônicos mais próximos e integrar essas informações em um aplicativo da web baseado em PHP. Depois, quando isso estiver funcionando, ele explica como é possível usar a API Google Maps para criar rapidamente um mapa estático ou interativo que represente graficamente tais caixas eletrônicos com relação à sua localização atual. Evidentemente, tudo é adequado para dispositivos móveis. É possível, portanto, implementar seu aplicativo no IBM® Cloud™ e começar a usá-lo imediatamente em um tablet ou smartphone.

Do que você precisa para desenvolver seu aplicativo

  • Conhecimento de HTML, CSS e jQuery, além de um ambiente de desenvolvimento Apache/PHP que esteja funcionando.
  • Conhecimento dos princípios básicos de trabalhar com REST e com classes e objetos em PHP, pois o código de exemplo de PHP neste tutorial usa esses conceitos.
  • Conta Google: O aplicativo de exemplo demonstrado neste tutorial depende da API Google Places produzir uma lista de caixas eletrônicos nas proximidades e da API Google Maps produzir mapas estáticos e interativos. Para acessar essas APIs, você precisará de uma Conta Google, que pode ser utilizada para gerar uma chave de API.

    Observação: Qualquer aplicativo que utiliza a API Google Places ou qualquer outra API Google Maps precisa cumprir os Termos de Serviço e a Política de Privacidade da Google, bem como observar suas Políticas da API Places. Antes de começar seu projeto, dedique alguns minutos à leitura destes requisitos e certifique-se de que são cumpridos pelo seu aplicativo.

  • Bootstrap: Para criar uma experiência móvel utilizável, eu utilizo o Bootstrap, uma estrutura de UI baseada em HTML5 criada especificamente para dispositivos de toque, como tablets e smartphones. O Bootstrap ajudará a colocar a interface com o usuário do aplicativo para funcionar rapidamente, com problemas mínimos de compatibilidade de plataforma e navegador.
  • IBM Cloud e ferramenta de linha de comando CloudFoundry para fazer o upload do seu aplicativo para o IBM Cloud.

Execute o aplicativoObtenha o código

Etapa 1. Criar o stub do aplicativo

Supondo que você tem todas as ferramentas necessárias, a primeira etapa é criar um stub do aplicativo desprotegido. Altere para o diretório-raiz de documentos do servidor da web (normalmente, /usr/local/apache/htdocs no Linux ou C:\Program Files\Apache\htdocs no Windows) e crie um novo subdiretório para o aplicativo. Nomeie esse diretório como atm.

shell> cd /usr/local/apache/htdocs
shell> mkdir atm

O diretório atm é mencionado em todo este artigo como $APP_ROOT. Dentro do diretório $APP_ROOT, crie um novo arquivo denominado index.php e preencha-o com o conteúdo a seguir:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Find an ATM</title>
    <style>
    html, body, .tab-content, .tab-pane, #map {
      height: 100%;
    }
    #footer {
      text-align: center
    }
    </style>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap-theme.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <div id="content">

      <ul class="nav nav-tabs" role="tablist">
        <li class="active"><a href="#list" data-toggle="tab">List</a></li>
        <li><a href="#map" data-toggle="tab">Map</a></li>
      </ul>
      
      <div class="tab-content">
        <!-- list tab -->
        <div role="tabpanel" class="tab-pane active" id="list">
        </div>
        
        <!-- map tab -->
        <div role="tabpanel" class="tab-pane" id="map">
        </div>
      </div>
      
      <div id="footer">
        <img src="powered-by-google-on-white.png" /> <br />
        <a href="terms.html">Legal Notices</a>
      </div>  
      
    </div>
    
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>    
  </body>
</html>

Essa página de stub configura componentes importantes como Bootstrap e jQuery a partir dos seus CDNs, além de configurar a interface com o usuário básica para o aplicativo. Como é possível ver, essa interface consiste em duas guias: Uma para a lista de caixas eletrônicos e outra para o mapa que exibe o local visualmente. Se você tentar acessar essa página por meio do seu navegador neste momento, verá uma interface com guias sem conteúdo:

A tabbed interface with no content
A tabbed interface with no content

Não se preocupe! À medida que passar pelas próximas etapas, a página será preenchida rapidamente com conteúdo.

Etapa 2: Obter uma chave de API

Antes de poder utilizar a API Google Places, você precisa registrar seu aplicativo da web com a Google.

  1. Efetue login na Google usando suas credenciais de Conta Google e acesse o Google Developers Console.
  2. Crie um novo projeto, atribua-lhe um nome e ative o acesso à API Google Places, à API Google Static Maps e à API Google Maps JavaScript, tal como mostrado abaixo. Enquanto estiver fazendo isso, familiarize-se com os limites de uso para cada uma das APIs. Enabled APIs
    Enabled APIs
  3. Na tela de credenciais, anote a chave de API para o acesso a uma API pública (Consultea imagem abaixo). Isso será usado para autorizar todas as solicitações de API feitas pelo aplicativo. Credentials screen to note the API key
    Credentials screen to note the API key

Pronto? Agora é possível efetuar logout da sua conta Google.

Etapa 3: Entender a API Google Places

Antes de poder desenvolver aplicativos com a API Google Places, você precisa entender como ela funciona. Como muitas outras APIs da web, ela funciona por HTTP e espera uma solicitação de HTTP para um terminal designado. Ao receber essa solicitação, o servidor da API responde à consulta com um feed JSON contendo os dados solicitados. Em seguida, é possível analisar esses dados utilizando uma linguagem de programação no lado do servidor (como PHP ou Perl) ou um kit de ferramentas no lado do cliente (como jQuery ou AngularJS) e extrair conteúdo para integração em uma página da web.

Para entender como a API Google Places funciona, leve-a para fazer um test drive procurando restaurantes na região de Covent Garden em Londres (as coordenadas do mapa são 51.5117316 N,-0.1232697 W): insira a URL abaixo no seu navegador, lembrando-se de atualizá-la para refletir a chave de API obtida na etapa anterior.

https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=YOUR_API_KEY&location=51.5117316,-0.1232697&rankby=distance&types=restaurant

Este é um exemplo do que pode ser visto:

Code example
Code example

Como a imagem mostra, a API Places responde à solicitação com um documento JSON que lista os restaurantes perto das coordenadas especificadas. Para cada resultado, a resposta inclui o nome do local, tipo(s) de local, coordenadas, endereço e um ID do local exclusivo. Vários outros campos também estão incluídos; consulte a documentação da API Places para obter detalhes.

Etapa 4: Ativar a interface de procura

Agora que você já sabe como a API funciona, é hora de começar a desenvolver o aplicativo. A primeira etapa é usar a API de localização geográfica HTML5 que está incluída nos navegadores mais modernos para identificar a localização atual do usuário. Atualize o arquivo index.php para incluir este código:

<!DOCTYPE html>
<html lang="en">
  <!-- snip -->
  <body>
    <div id="content">
  
    <?php
    // if no latitude or longitude
    // try to detect using browser geolocation
    if (empty($latitude) || empty($longitude)) {
    ?>
    <script>
    $(document).ready(function() {

      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(handle_geo_query, handle_error);
      }

      function handle_geo_query(location) {
        window.location = '?latitude=' + location.coords.latitude + 
          '&longitude=' + location.coords.longitude;
      }
      
      function handle_error(e) {
        alert('An error occurred during geo-location.');
      }
    });
    </script>
    <?php
    exit;
    }
    ?>
    
    </div>
  </body>
</html>

Normalmente, quando esse código é executado em um navegador, os usuários veem uma janela de mensagem que pede permissão para divulgar a localização atual. É importante observar que o usuário precisa autorizar explicitamente tal divulgação para que o aplicativo receba a localização atual. A localização costuma ser identificada usando a torre de celular mais próxima no sistema GPS do dispositivo.

Supondo que o usuário concorde em divulgar sua localização atual, o script chama a função handle_geo_query , que lê a latitude e a longitude e recarrega a URL da página; desta vez, esses valores são anexados à URL como parâmetros GET para que possam ser usados como PHP.

Caso haja um erro -- por exemplo, se o usuário negar a permissão para compartilhar sua localização ou se a localização não puder ser identificada -- a função handle_error é chamada para exibir uma caixa de alerta. Embora esse código não mostre, vale a pena destacar que é possível ajustar adicionalmente a mensagem de erro com base no tipo de erro.

Depois que a página é recarregada, a latitude e a longitude detectadas no código anterior são utilizadas para desenvolver uma solicitação para a API Google Places e, em seguida, transformam a saída em uma lista formatada. Esta é a seção de código relevante:

<!DOCTYPE html>
<html lang="en">
  <!-- snip -->
  <body>
    <div id="content">
  
      <?php
      // Google Maps/Places API key
      $apiKey = 'YOUR_API_KEY';
      
      // variables from URL request string
      $latitude = isset($_GET['latitude']) ? trim($_GET['latitude']) : null;
      $longitude = isset($_GET['longitude']) ? trim($_GET['longitude']) : null;
      
      // create API request URLs with required parameters
      $placesApiUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=' . 
         $apiKey . '&location=' . sprintf('%f,%f', $latitude, $longitude) . 
         '&rankby=distance&types=atm';   
      
      // send request to Places API (for collection of ATMs or single selected ATM)    
      $ch = curl_init();
      curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_SSL_VERIFYPEER => 1,  
        CURLOPT_SSL_VERIFYHOST => 2,  
        CURLOPT_URL => $placesApiUrl,
      ));
      $places = json_decode(curl_exec($ch));
      curl_close($ch);
      $results = isset($places->result) ? $places->result : $places->results;
      ?>

      <?php if (count($results)): ?>
      <div class="alert alert-success" role="alert">
        <?php echo count($results); ?> ATM(s) found.
      </div>
      <?php else: ?>
      <div class="alert alert-warning" role="alert">No ATM(s) found.</div>    
      <?php endif; ?>
      
      <ul class="nav nav-tabs" role="tablist">
        <li class="active"><a href="#list" data-toggle="tab">List</a></li>
        <li><a href="#map" data-toggle="tab">Map</a></li>
        <a href="index.php" class="btn btn-success">Refresh</a>
      </ul>
      
      <div class="tab-content">
        <!-- list tab -->
        <div role="tabpanel" class="tab-pane active" id="list">
          <ul class="list-group">
          <?php
          // iterate over list of ATMs returned
          // generate data for map markers
          // display name, label, and location for each ATM
          $c = 65;
          foreach ($results as $place) {
            $label = chr($c);
            $markers[] = "markers=color:red|label:" . $label . "|" . 
              $place->geometry->location->lat . "," . $place->geometry->location->lng;
            $c++;
          ?>
            <li class="list-group-item">
              <h3>
                <span class="label label-danger"><?php echo $label; ?></span>
                <span class="label label-default"><?php echo $place->name; ?></span>
              </h3>
              <p><?php echo $place->vicinity; ?></p>
            </li>
          <?php
          }
          ?>
            <li class="list-group-item">
              <?php echo implode(',', $places->html_attributions); ?>
            </li>
          </ul>
        </div>
        
        <!-- map tab -->
        <div role="tabpanel" class="tab-pane" id="map">
        </div>
      </div>
    </div>
  </body>
</html>

As primeiras linhas de código criam uma nova solicitação de API usando o formato explicado na seção anterior. A chave de API e as coordenadas da localização atual do usuário são interpoladas na URL da solicitação; o conjunto de resultados é filtrado adicionalmente para exibir apenas caixas eletrônicos com o parâmetro types . Por fim, os resultados são classificados por distância, de modo que o caixa eletrônico mais próximo da localização atual aparece em primeiro lugar na lista.

As funções curl do PHP são utilizadas para transmitir a solicitação; em seguida, a saída codificada por JSON é decodificada em um objeto PHP com o json_decode() . Agora, é uma tarefa simples iterar a coleção de resultados e apresentá-la como uma lista HTML dentro de uma das duas guias preparadas anteriormente. A lista inclui, para cada item, o nome do caixa eletrônico, sua localização e uma etiqueta alfabética gerada automaticamente. Esta é uma amostra da aparência da saída:

ATM list sample output
ATM list sample output

Você perceberá que o código também cria uma matriz chamada $markers, que contém uma cadeia de caracteres em um formato específico. A cadeia de caracteres inclui uma cor de marcador, a etiqueta alfabética gerada automaticamente para o caixa eletrônico e a latitude e a longitude do caixa eletrônico. Mais tarde, essa matriz é usada para criar marcadores de mapa correspondentes às diferentes localizações dos caixas eletrônicos.

Etapa 5: Incluir um mapa estático

Na etapa anterior, você obteve uma lista de caixas eletrônicos. A próxima etapa é representar esses caixas eletrônicos em um mapa para que o usuário possa identificá-los facilmente com relação à sua localização atual. A maneira mais fácil de fazer isso é com a API Google Static Maps, que produz uma imagem em mapa de um local especificado, incluindo marcadores em coordenadas especificadas, em resposta a uma solicitação da API.

Para entender como a API Google Static Maps, utilize-a para produzir um mapa de Covent Garden, em Londres, usando as mesmas coordenadas usadas anteriormente. Insira a URL abaixo no seu navegador, lembrando-se de atualizá-la para refletir sua chave de API:

https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&size=640x480&maptype=roadmap&zoom=15&scale=2&markers=51.5117316,-0.1232697

Este é um exemplo do que pode ser visto:

Static Maps API map image
Static Maps API map image

Como a imagem mostra, a API Static Maps responde à solicitação com uma imagem que contém um mapa das coordenadas especificadas. O tamanho da imagem, a proporção do zoom e a escala são especificados na URL da solicitação; também é possível adicionar marcadores à imagem ao incluir um parâmetro markers na URL da solicitação com coordenadas para os locais com marcadores. Vários outros parâmetros também estão disponíveis para controlar a exibição do mapa; consulte a documentação da API Static Maps para obter detalhes.

Agora que você já sabe como esse processo funciona, fica muito fácil aprimorar o código PHP acima para incluir um mapa estático. A maior parte do trabalho pesado já foi feita; é preciso apenas fazer uma segunda solicitação para o terminal da API Static Maps e incluir a imagem resultante na segunda guia. Este é o código revisado:

<!DOCTYPE html>
<html lang="en">
  <!-- snip -->
  <body>
    <div id="content">
    
      <?php
      // Google Maps/Places API key
      $apiKey = 'YOUR_API_KEY';
      
      // variables from URL request string
      $latitude = isset($_GET['latitude']) ? trim($_GET['latitude']) : null;
      $longitude = isset($_GET['longitude']) ? trim($_GET['longitude']) : null;
      
      // create API request URLs with required parameters
      $placesApiUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=' . 
        $apiKey . '&location=' . sprintf('%f,%f', $latitude, $longitude) .
        '&rankby=distance&types=atm';   
      $mapsApiUrl = 'https://maps.googleapis.com/maps/api/staticmap?key=' . $apiKey . 
        '&size=640x480&maptype=roadmap&scale=2&markers=color:green|' . 
        sprintf('%f,%f', $latitude, $longitude);
      
      // send request to Places API (for collection of ATMs or single selected ATM)    
      $ch = curl_init();
      curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_SSL_VERIFYPEER => 1,  
        CURLOPT_SSL_VERIFYHOST => 2,  
        CURLOPT_URL => $placesApiUrl,
      ));
      $places = json_decode(curl_exec($ch));
      curl_close($ch);
      $results = isset($places->result) ? $places->result : $places->results;
      ?>

      <?php if (count($results)): ?>
      <div class="alert alert-success" role="alert">
        <?php echo count($results); ?> ATM(s) found.
      </div>
      <?php else: ?>
      <div class="alert alert-warning" role="alert">No ATM(s) found.</div>    
      <?php endif; ?>
      
      <ul class="nav nav-tabs" role="tablist">
        <li class="active"><a href="#list" data-toggle="tab">List</a></li>
        <li><a href="#map" data-toggle="tab">Map</a></li>
        <a href="index.php" class="btn btn-success">Refresh</a>
      </ul>
      
      <div class="tab-content">
        <!-- list tab -->
        <div role="tabpanel" class="tab-pane active" id="list">
          <ul class="list-group">
          <?php
          // iterate over list of ATMs returned
          // generate data for map markers
          // display name, label, and location for each ATM
          $c = 65;
          foreach ($results as $place) {
            $label = chr($c);
            $markers[] = "markers=color:red|label:" . $label . "|" . 
              $place->geometry->location->lat . "," . $place->geometry->location->lng;
            $c++;
          ?>
            <li class="list-group-item">
              <h3>
                <span class="label label-danger"><?php echo $label; ?></span>
                <span class="label label-default"><?php echo $place->name; ?></span>
              </h3>
              <p><?php echo $place->vicinity; ?></p>
            </li>
          <?php
          }
          ?>
            <li class="list-group-item">
              <?php echo implode(',', $places->html_attributions); ?>
            </li>
          </ul>
        </div>
        
        <!-- map tab -->
        <div role="tabpanel" class="tab-pane" id="map">
          <img class="img-responsive" id="mapImage"
            src="<?php echo $mapsApiUrl; ?>&<?php echo implode('&', $markers); ?>" />
        </div>
      </div>
      
    </div>
    
  </body>
</html>

Você verá que essa revisão inclui uma solicitação adicional para a API Static Maps. Além dos parâmetros size, zoome scale padrão, a URL da solicitação inclui muitos valores para o parâmetro markers . O primeiro é um marcador verde, exibido na localização atual do usuário. É seguido pelos conteúdos da matriz $markers , da qual cada elemento cria um marcador vermelho exibido na localização do caixa eletrônico correspondente. A etiqueta para cada marcador de caixa eletrônico corresponde à sua etiqueta alfabética gerada automaticamente, ajudando o usuário a fazer referência a um caixa eletrônico da lista com sua localização correspondente no mapa.

A imagem resultante é colocada dentro da segunda guia da página. Este é um exemplo da aparência:

Second tab example
Second tab example

Um problema com essa abordagem é que, quando há um grande grupo de marcadores muito próximos, alguns deles são obscurecidos por outros. Assim, fica difícil identificar a localização de um caixa eletrônico específico no grupo. Uma forma de resolver isso é incluir uma opção para o usuário visualizar apenas um único caixa eletrônico selecionado no mapa e, consequentemente, identificar sua localização com facilidade.

A revisão a seguir do código anterior implementa essa mudança:

<!DOCTYPE html>
<html lang="en">
  <!-- snip -->
  <body>
    <div id="content">
    
      <?php
      // Google Maps/Places API key
      $apiKey = 'YOUR_API_KEY';
      
      // variables from URL request string
      $latitude = isset($_GET['latitude']) ? trim($_GET['latitude']) : null;
      $longitude = isset($_GET['longitude']) ? trim($_GET['longitude']) : null;
      $selected = isset($_GET['selected']) ? trim($_GET['selected']) : null;
      
      // create API request URLs with required parameters
      $placesApiUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=' . 
        $apiKey . '&location=' . sprintf('%f,%f', $latitude, $longitude) .
        '&rankby=distance&types=atm';   
      $mapsApiUrl = 'https://maps.googleapis.com/maps/api/staticmap?key=' . $apiKey .
       '&size=640x480&maptype=roadmap&scale=2&markers=color:green|' . 
        sprintf('%f,%f', $latitude, $longitude);
      
      // send request to Places API (for collection of ATMs or single selected ATM)    
      $ch = curl_init();
      curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_SSL_VERIFYPEER => 1,  
        CURLOPT_SSL_VERIFYHOST => 2,  
        CURLOPT_URL => $placesApiUrl,
      ));
      $places = json_decode(curl_exec($ch));
      curl_close($ch);
      $results = isset($places->result) ? $places->result : $places->results;
      ?>

      <?php if (count($results) && empty($selected)): ?>
      <div class="alert alert-success" role="alert">
        <?php echo count($results); ?> ATM(s) found.
      </div>
      <?php elseif (count($results) && !empty($selected)): ?>
      <div class="alert alert-success" role="alert">Selected ATM found.</div>
      <?php else: ?>
      <div class="alert alert-warning" role="alert">No ATM(s) found.</div>    
      <?php endif; ?>
      
      <ul class="nav nav-tabs" role="tablist">
        <li class="active"><a href="#list" data-toggle="tab">List</a></li>
        <li><a href="#map" data-toggle="tab">Map</a></li>
        <a href="index.php" class="btn btn-success">Refresh</a>
      </ul>
      
      <div class="tab-content">
        <!-- list tab -->
        <div role="tabpanel" class="tab-pane active" id="list">
          <ul class="list-group">
          <?php
          // iterate over list of ATMs returned
          // generate data for map markers
          // display name, label, and location for each ATM
          $c = 65;
          foreach ($results as $place) {
            $label = chr($c);
            if (!empty($selected)) {
              $selArr = explode(':', $selected);
              $label = $selArr[0];
              $selectedId = $selArr[1];
              if ($place->place_id != $selectedId) {
                continue;
              }
            }
            $markers[] = "markers=color:red|label:" . $label . "|" . 
              $place->geometry->location->lat . "," . $place->geometry->location->lng;
            $c++;
          ?>
            <li class="list-group-item">
              <h3>
                <span class="label label-danger"><?php echo $label; ?></span>
                <span class="label label-default"><?php echo $place->name; ?></span>
              </h3>
              <p><?php echo $place->vicinity; ?></p>
              <a href="?latitude=<?php echo $latitude; ?>&longitude=<?php echo $longitude; ?>
                &selected=<?php echo $label; ?>:<?php echo $place->place_id; ?>#map">
                Pinpoint on map</a>
            </li>
          <?php
          }
          ?>
            <li class="list-group-item">
              <?php echo implode(',', $places->html_attributions); ?>
            </li>
          </ul>
        </div>
        
        <!-- map tab -->
        <div role="tabpanel" class="tab-pane" id="map">
          <img class="img-responsive" id="mapImage"
            src="<?php echo $mapsApiUrl; ?>&<?php echo implode('&', $markers); ?>" />
        </div>
      </div>
      
    </div>
  </body>
</html>

Observe que cada item da lista passou a incluir um hiperlink rotulado "Identificar no mapa". O hiperlink recarrega a página com as mesmas informações de antes, mas inclui um parâmetro selected adicional na URL da solicitação que contém o ID de local exclusivo do caixa eletrônico. Quando esse parâmetro selected existe na URL, o código filtra adicionalmente o conjunto de resultados produzido para incluir somente o caixa eletrônico selecionado e, de modo correspondente, gera apenas um marcador de mapa exclusivo para o caixa eletrônico em questão. Com isso, a imagem do mapa é refinada para exibir apenas a localização atual do usuário e a do caixa eletrônico selecionado.

Este é um exemplo da aparência do resultado:

O toque final é um botão Refresh que permite que o usuário obtenha uma lista revisada dos caixas eletrônicos caso se mova para um novo local. Não é mágica: o botão simplesmente recarrega o aplicativo sem informações de localização, levando o navegador a obter e gerar um novo conjunto de coordenadas.

Etapa 6: Incluir um mapa interativo

O problema com um mapa estático é exatamente este: ele é estático, o que significa que não é possível aumentar ou diminuir o zoom, girar a imagem ou clicar em elementos do mapa para obter informações adicionais. Como alternativa, é possível usar a API Google Maps JavaScript, que permite criar mapas interativos que respondem ao toque ou clicar em eventos usando JavaScript.

Para utilizar a API Google Maps JavaScript, a primeira etapa é incluir o arquivo de origem JavaScript, usando um código como o da linha abaixo:

<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=[YOUR_API_KEY]"></script>

É possível criar um novo objeto de mapa como este:

<script>
  var mapOptions = {
    zoom: 18,
    center: new google.maps.LatLng(51.5117316,-0.1232697),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };

  var map = new google.maps.Map(document.getElementById('map'), mapOptions);  
</script>

E é possível incluir um marcador ao objeto de mapa como a seguir:

<script>
var marker = new google.maps.Marker({
  position: new google.maps.LatLng(51.5117316,-0.1232697),
  map: map,
  title: 'My Location',
});    
marker.setMap(map);   
</script>

Isso cria um novo mapa centralizado na região de Covent Garden, com um nível de zoom de 18. O mapa é anexado automaticamente ao elemento DOM chamado map, que deve existir na página HTML.

Evidentemente, esta é apenas a ponta do iceberg: a API Google Maps JavaScript é muito mais complexa e não pode ser explicada aqui na íntegra. Consulte a documentação de referência e você verá que é muito fácil modificar o script anterior e substituir o mapa estático por um dinâmico usando a API JavaScript. Esta é a aparência do código revisado:

<!DOCTYPE html>
<html lang="en">
  <!-- snip -->
  <body>
    <div id="content">
    
      <?php
      // Google Maps/Places API key
      $apiKey = 'YOUR_API_KEY';
      
      // variables from URL request string
      $latitude = isset($_GET['latitude']) ? trim($_GET['latitude']) : null;
      $longitude = isset($_GET['longitude']) ? trim($_GET['longitude']) : null;
      $selected = isset($_GET['selected']) ? trim($_GET['selected']) : null;
      
      // create API request URL with required parameters
      $placesApiUrl = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=' . 
        $apiKey . '&location=' . sprintf('%f,%f', $latitude, $longitude) .
        '&rankby=distance&types=atm';   
      
      // send request to Places API (for collection of ATMs or single selected ATM)    
      $ch = curl_init();
      curl_setopt_array($ch, array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_SSL_VERIFYPEER => 1,  
        CURLOPT_SSL_VERIFYHOST => 2,  
        CURLOPT_URL => $placesApiUrl,
      ));
      $places = json_decode(curl_exec($ch));
      curl_close($ch);
      $results = isset($places->result) ? $places->result : $places->results;
      ?>

      <?php if (count($results) && empty($selected)): ?>
      <div class="alert alert-success" role="alert">
        <?php echo count($results); ?> ATM(s) found.
      </div>
      <?php elseif (count($results) && !empty($selected)): ?>
      <div class="alert alert-success" role="alert">Selected ATM found.</div>
      <?php else: ?>
      <div class="alert alert-warning" role="alert">No ATM(s) found.</div>    
      <?php endif; ?>
      
      <ul class="nav nav-tabs" role="tablist">
        <li class="active"><a href="#list" data-toggle="tab">List</a></li>
        <li><a href="#map" data-toggle="tab">Map</a></li>
        <a href="index.php" class="btn btn-success">Refresh</a>
      </ul>
      
      <div class="tab-content">
        <!-- ATMs as list -->
        <div role="tabpanel" class="tab-pane active" id="list">
          <ul class="list-group">

          <?php
          // iterate over list of ATMs returned
          // generate data for map markers
          // display name, label, and location for each ATM
          $c = 65;
          foreach ($results as $place) {
            $label = chr($c);
            if (!empty($selected)) {
              $selArr = explode(':', $selected);
              $label = $selArr[0];
              $selectedId = $selArr[1];
              if ($place->place_id != $selectedId) {
                continue;
              }
            }
            
            $markers[] = array(
              'title' => $label . ': ' . $place->name,
              'lat' => $place->geometry->location->lat,
              'lng' => $place->geometry->location->lng,
            );
            $c++;
          ?>
            <li class="list-group-item">
              <h3>
                <span class="label label-danger"><?php echo $label; ?></span>
                <span class="label label-default"><?php echo $place->name; ?></span>
              </h3>
              <p><?php echo $place->vicinity; ?></p>
              <a href="?latitude=<?php echo $latitude; ?>&longitude=<?php echo $longitude; ?>
                &selected=<?php echo $label; ?>:<?php echo $place->place_id; ?>#map">
                Pinpoint on map</a>
            </li>

          <?php
          }
          ?>
            <li class="list-group-item">
              <?php echo implode(',', $places->html_attributions); ?>
            </li>
          </ul>
        </div>
        
        <!-- ATMs on map -->
        <div role="tabpanel" class="tab-pane" id="map">
           
        </div>
      </div>
      
    </div>
    
    <script type="text/javascript" 
      src="https://maps.googleapis.com/maps/api/js?key=<?php echo $apiKey; ?>"></script>
    <script>
    $(document).ready(function() {
    
      var mapOptions = {
        zoom: 18,
        center: new google.maps.LatLng(<?php printf('%f,%f', $latitude, $longitude); ?>),
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };

      var map = new google.maps.Map(document.getElementById('map'), mapOptions);  
      
      var marker = new google.maps.Marker({
        position: new google.maps.LatLng(<?php printf('%f,%f', $latitude, $longitude); ?>),
        map: map,
        title: 'Current Location',
        icon: {
          path: google.maps.SymbolPath.CIRCLE,
          scale: 10,
          strokeColor: 'green',
        }

      });    
      marker.setMap(map);    
      var infowindow = new google.maps.InfoWindow({
          content: 'None'
      });

      
      <?php foreach ($markers as $marker): ?>
      var marker = new google.maps.Marker({
          position: new google.maps.LatLng(
            <?php echo $marker['lat']; ?>, <?php echo $marker['lng']; ?>
          ),
          map: map,
          title: '<?php echo $marker['title']; ?>'
      });
      marker.setMap(map);    
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.setContent('<strong>' + this.getTitle() + '</strong>');
        infowindow.open(map,this);      
      });     
      <?php endforeach; ?>


      $("a[href='#map']").on('shown.bs.tab', function(){
        google.maps.event.trigger(map, 'resize');
      });  
  
    });
    </script>
  </body>
</html>

Você estará familiarizado com grande parte disso em função das versões estáticas anteriores do código. A principal diferença está em como o mapa é criado: em vez de uma solicitação para a API Google Static Maps que produz uma imagem, esta versão do código carrega a API Google Maps JavaScript e a utiliza para criar e renderizar um mapa com os marcadores solicitados. Estas são as principais mudanças:

  • Em primeiro lugar, a matriz $markers é preenchida de forma diferente, com cada elemento representando um dos caixas eletrônicos produzidos pela API Google Places. Cada elemento da matriz contém informações sobre o nome do caixa eletrônico, sua latitude e longitude.
  • Depois, o código JavaScript no fim da página cria um novo objeto Map usando a latitude e a longitude passadas para a URL da solicitação e as coloca dentro da segunda guia na página. Também cria um objeto InfoWindow que conterá informações sobre o caixa eletrônico selecionado em qualquer ponto específico.
  • Por fim, um loop foreach() PHP é iterado na matriz $markers , processando as localizações dos caixas eletrônicos e criando um objeto Marker JavaScript para cada localização de caixa eletrônico. A seguir, esses objetos Marker são anexados ao Map, sendo que cada um recebe um listener para eventos de clique. Quando o Marker recebe um clique, o objeto de listener atualiza o conteúdo do InfoWindow com o nome do caixa eletrônico selecionado e o exibe.

Este é um exemplo da aparência do resultado:

Etapa 7: Implementar no IBM Cloud

Depois de codificar o aplicativo, a etapa final é implementá-lo. Evidentemente, se estiver implementando localmente, já está tudo pronto -- você deve ser capaz de usar o aplicativo normalmente. No entanto, se estiver implementando no IBM Cloud, precisa de um IBM Cloud e também precisa fazer o download e instalar o cliente da linha de comando Cloud Foundry. Siga as etapas abaixo para concluir o processo de implementação.

  1. Crie seu manifesto de aplicativo. O arquivo de manifesto do aplicativo diz ao IBM Cloud como implementar seu aplicativo. Em particular, ele especifica o ambiente de tempo de execução do PHP ("construir pacote") que deve ser usado. Crie este arquivo em $APP_ROOT/manifest.yml, preenchendo-o com as informações abaixo.
    ---
    applications:
    - name: atm-finder-[random-number]
    memory: 256M
    instances: 1
    host: atm-finder-[random-number]
    buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

    Lembre-se de atualizar os nomes do host e do aplicativo para torná-lo exclusivo, seja alterando-o ou anexando um número aleatório a ele. Estou usando o pacote de compilação PHP do CloudFoundry, embora outras alternativas também estejam disponíveis.

  2. Conecte-se com o IBM Cloud e implemente o aplicativo. Use a interface da linha de comandos cf para efetuar login no IBM Cloud utilizando seu nome do usuário e senha IBM:
    shell> cf api https://api.ng.bluemix.net
    shell> cf login

    Altere para o diretório $APP_ROOT e envie o aplicativo por push para o IBM Cloud:

    shell> cf
    push

    Esta é uma amostra daquilo que você vê durante o processo.

Neste ponto, seu aplicativo deve estar implementado no IBM Cloud. Agora, deve ser capaz de começar a usá-lo navegando até o host que especificou no seu manifest do aplicativo -- por exemplo, http://atm-finder-[random-number].mybluemix.net. Ou clique em Execute o aplicativo na parte superior deste artigo para testar uma demonstração em tempo real do aplicativo.

Conclusão

Como este artigo mostrou, o IBM Cloud fornece uma base sólida para criar e implementar aplicativos fáceis de usar em uma plataforma baseada em nuvem. Inclua PHP, Bootstrap e jQuery para ter um kit de ferramentas sólido para desenvolver aplicativos móveis da web interativos e dinâmicos.

É possível fazer o download de todos os códigos implementados neste artigo no repositório JazzHub, juntamente com os arquivos de configuração para o buildpack do PHP que foram usados. Recomendo que você obtenha o código, comece a brincar com ele e teste incluir novos recursos. Garanto que você não quebrará nada e, sem dúvidas, aprenderá com isso.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Cloud computing, Desenvolvimento móvel
ArticleID=1004754
ArticleTitle=Desenvolver e implementar um aplicativo localizador de caixas eletrônicos no IBM Cloud
publish-date=01222016