Contents


Build and deploy an ATM finder app on Bluemix

Google Places and Google Maps APIs make it simple to locate and map ATMs close to you

Comments

One of the most frequently-used apps on my smartphone has to be Google Maps. It's great for a quick traffic check before heading out, and it's also very useful when you're trying to find your way to a new destination. Plus, it's a good way to discover interesting new restaurants and stores when you're in a new neighborhood or city.

But Google Maps is also interesting from a development perspective. Like many other Google products, it exposes a number of APIs that allow developers to connect to it and build custom applications using its data. For example, the Google Places API lets you retrieve information about shops, businesses, and other establishments by location, and the Google Maps API lets you embed a static or interactive map into a web page. Both of these APIs, and a few others, are directly usable through common programming languages such as PHP and JavaScript.

This tutorial introduces you to the Google Places API by showing you how to build a simple application that uses your current location to identify the closest ATMs and integrate that information into a PHP-based web application. Then, after you have that working, it explains how you can use the Google Maps API to quickly create a static or interactive map that graphically plots those ATMs with respect to your current location. Needless to say, it's all mobile friendly, so you can deploy your application to IBM® Bluemix™ and immediately start using it on a tablet or smartphone.

What you need to build your application

  • Familiarity with HTML, CSS, and jQuery, and a working Apache/PHP development environment.
  • Familiarity with the basics of working with REST and with classes and objects in PHP, as the PHP example code in this tutorial makes use of these concepts.
  • Google account: The example application demonstrated in this tutorial depends on the Google Places API to produce a list of nearby ATMs, and on the Google Maps API to produce static and interactive maps. To access these APIs, you'll need a Google Account, which you can use to generate an API key.

    Note: Any application that uses the Google Places API or any of the other Google Maps APIs must comply with Google's Terms of Service and Privacy Policy, as well as satisfy Google's Places API Policies. Before beginning your project, spend a few minutes reading these requirements and ensuring that your application complies with them.

  • Bootstrap: To create a usable mobile experience, I use Bootstrap, an HTML5-based UI framework that's specifically designed for touch devices like tablets and smartphones. Bootstrap will help get the application's user interface up and running quickly, with minimal platform and browser compatibility issues.
  • Bluemix account and CloudFoundry command-line tool to upload your application to Bluemix.

Run the appGet the code

Step 1. Create the application stub

Assuming that you have all the necessary tools, the first step is to create a bare application stub. Change to the web server's document root directory (typically /usr/local/apache/htdocs on Linux or C:\Program Files\Apache\htdocs on Windows) and create a new subdirectory for the application. Name this directory atm.

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

The atm directory is referenced throughout this article as $APP_ROOT. Within the $APP_ROOT directory, create a new file named index.php and populate it with the following content:

<!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>

This stub page sets up key components such as Bootstrap and jQuery from their CDNs, and sets up the basic user interface for the application. As you can see, this interface consists of two tabs: One for the list of ATMs and the other for the map that displays their location visually. If you try accessing this page through your browser right now, you'll see a tabbed interface with no content:

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

Don't worry! As you proceed through the remaining steps, this page will rapidly fill up with content.

Step 2: Obtain an API key

Before you can use the Google Places API, you need to register your web application with Google.

  1. Log in to Google using your Google Account credentials and visit the Google Developers Console.
  2. Create a new project, assign it a name, and then turn on access to the Google Places API, the Google Static Maps API, and the Google Maps JavaScript API, as shown below. While you're at it, familiarize yourself with the usage limits for each of these APIs. Enabled APIs
    Enabled APIs
  3. In the credentials screen, make a note of the API key for public API access (see the image below). You'll use this to authorize all API requests made by the application. Credentials screen to note the API key
    Credentials screen to note the API key

Done? You can now log out of your Google account.

Step 3: Understand the Google Places API

Before you can start developing applications with the Google Places API, you need to understand how it works. Like many other web APIs, this one works over HTTP and expects an HTTP request to a designated endpoint. On receipt of this request, the API server replies to the query with a JSON feed containing the requested data. It's then possible to parse this data using either a server-side programming language (such as PHP or Perl) or a client-side toolkit (such as jQuery or AngularJS) and extract content from it for integration into a web page.

To understand how the Google Places API works, take the API for a test drive by searching for restaurants around Covent Garden in London (map coordinates 51.5117316 N,-0.1232697 W): Enter the URL below into your browser, remembering to update it to reflect the API key you obtained in the previous step.

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

Here's an example of what you might see:

Code example
Code example

As the image illustrates, the Places API responds to the request with a JSON document listing restaurants near the specified coordinates. For each result, the response includes the place name, place type(s), coordinates, address, and a unique place ID. A number of other fields are also included; check the Places API documentation for details.

Step 4: Enable the search interface

Now that you know how the API works, it's time to get started building the application. The first step is to use the HTML5 geolocation API that's included in most modern browsers to identify the user's current location. Update the index.php file to include this code:

<!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>

Typically, when this code runs in a browser, users see a message window asking for permission to disclose the current location. It is important to note that the user must explicitly allow such disclosure in order for the application to receive the current location. The location is usually identified using the nearest cellular tower or the GPS system on the device.

Assuming the user agrees to disclose his or her current location, the script calls the handle_geo_query function, which reads in the latitude and longitude and reloads the page URL; this time, it appends these values to the URL as GET parameters so that they can be used by PHP.

If there's an error -- for example, if the user denies permission to share his or her location or the location cannot be identified -- the handle_error function is invoked to display an alert box. Although this code doesn't show it, it's worth noting that you can further fine-tune the error message based on the error type.

Once the page reloads, the latitude and longitude detected in the previous code is used to construct a request to the Google Places API, then turns the output into a formatted list. Here's the relevant section of code:

<!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>

The first few lines of code create a new API request using the format explained in the previous section. The user's API key and current location coordinates are interpolated into the request URL, and the result set is further filtered to display only ATMs with the types parameter. Finally, the results are ranked by distance, such that the ATM nearest the current location appears first in the list.

PHP's curl functions are used to transmit the request and the JSON-encoded output is then decoded into a PHP object with the json_decode() function. It's now a simple task to iterate over the result collection and present it as an HTML list within one of the two tabs prepared earlier. The list includes, for each item, the ATM name, its location and an auto-generated alphabetic label. Here's a sample of what the output looks like:

ATM list sample output
ATM list sample output

You'll notice that the code also creates an array named $markers, which contains a string in a specific format. The string includes a marker color, the auto-generated alphabetic label for the ATM, and the latitude and longitude of the ATM. Later, this array is used to create map markers corresponding to the different ATM locations.

Step 5: Add a static map

In the previous step, you obtained a list of ATMs. The next step is to plot those ATMs on a map, so that the user can easily identify them with respect to his or her current location. The easiest way to do this is with the Google Static Maps API, which produces a map image of a specified location, complete with markers at specified coordinates, in response to an API request.

To understand how the Google Static Maps API works, use it to produce a map of Covent Garden in London using the same coordinates used earlier. Enter the URL below into your browser, remembering to update it to reflect your API key:

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

Here's an example of what you might see:

Static Maps API map image
Static Maps API map image

As the image illustrates, the Static Maps API responds to the request with an image containing a map of the specified coordinates. The image size, zoom ratio, and scale are all specified in the request URL, and it's also possible to add markers to the image by including a markers parameter in the request URL with coordinates for the marker locations. A number of other parameters are also available to control the map display; look in the Static Maps API documentation for details.

Now that you know how this process works, it's quite easy to enhance the PHP code above to include a static map. Most of the heavy lifting is already done; all that's needed is to make a second request to the Static Maps API endpoint and include the resulting image in the second tab. Here's the revised code:

<!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>

You'll see that this revision includes an additional request to the Static Maps API. In addition to the standard size, zoom, and scale parameters, the request URL includes a number of values for the markers parameter. The first of these is a green marker, displayed at the user's current location. This is followed by the contents of the $markers array, each element of which creates a red marker displayed at the corresponding ATM location. The label for each ATM marker corresponds to its auto-generated alphabetic label, making it easy for the user to reference an ATM from the list with its corresponding location on the map.

The resulting image is placed within the second tab of the page. Here's an example of what it looks like:

Second tab example
Second tab example

One problem with this approach is that when there's a large cluster of ATMs in close proximity to each other, some markers are obscured by others. This makes it hard to identify the location of a specific ATM in the cluster. One way to resolve this is to include an option for the user to view just a single, selected ATM on the map and thereby easily pinpoint its location.

The following revision of the previous code implements this change:

<!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>

Notice that each list item now includes a hyperlink labeled "Pinpoint on map." The hyperlink reloads the page with all the same information as before, but includes an additional selected parameter in the request URL containing the unique place ID of the ATM. When this selected parameter exists in the URL, the code further filters the returned result set to include only the selected ATM, and correspondingly, generates only a single map marker for that ATM. With this, the map image is refined to display only the user's current location and that of the selected ATM.

Here's an example of what the result looks like:

The final touch is a Refresh button that lets the user obtain a revised list of ATMs if he or she moves to a new location. There's no magic here: The button simply reloads the application without any location information, prompting the browser to obtain and generate a new set of coordinates.

Step 6: Add an interactive map

The problem with a static map is just that: It's static, which means you can't zoom in or out, rotate the image, or click on map elements for additional information. As an alternative, you can use the Google Maps JavaScript API, which lets you create interactive maps that respond to touch or click events using JavaScript.

To use the Google Maps JavaScript API, the first step is to include the JavaScript source file, using code like the line below:

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

You can then create a new map object like this:

<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>

And you can add a marker to the map object like this:

<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>

This creates a new map centered on Covent Garden, with a zoom level of 18. The map is automatically attached to the DOM element named map, which should exist on the HTML page.

Of course, this is just the tip of the iceberg: There's a lot more to the Google Maps JavaScript API and it can't all be explained here. Take a look at the reference documentation and you'll see that it's quite easy to modify the previous script and replace the static map with a dynamic one using the JavaScript API. Here's what the revised code looks like:

<!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>

Much of this will be familiar to you from the previous, static version of the code. The primary difference arises from how the map is created: Instead of a request to the Google Static Maps API that returns an image, this version of the code loads the Google Maps JavaScript API and uses it to create and render a map with required markers. Here are the main changes:

  • First, the $markers array is populated differently, with each element representing one of the ATMs returned by the Google Places API. Each element of the array holds information on the ATM name, its latitude, and longitude.
  • Then, the JavaScript code at the end of the page creates a new Map object using the latitude and longitude passed on the request URL, and places this within the second tab on the page. It also creates an InfoWindow object that will hold information on the currently selected ATM at any given point.
  • Finally, a PHP foreach() loop iterates over the $markers array, processing the ATM locations and creating a JavaScript Marker object for each ATM location. These Marker objects are then attached to the Map, with each assigned a listener for click events. When the Marker receives a click, the listener object updates the content of the InfoWindow with the name of the selected ATM and displays it.

Here's an example of what the result looks like:

Step 7: Deploy to IBM Bluemix

Now that you've got the application coded, the final step is to deploy it. Of course, if you're deploying locally, you're all done -- you should be able to use the application normally. However, if you're deploying on Bluemix, you need a Bluemix account and you also need to download and install the Cloud Foundry command-line client. Follow the steps below to complete the deployment process.

  1. Create your application manifest. The application manifest file tells Bluemix how to deploy your application. In particular, it specifies the PHP run-time environment ("build pack") that should be used. Create this file at $APP_ROOT/manifest.yml, filling it with the information below.
    ---
    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

    Remember to update the host and application names to make it unique, either by changing it or by appending a random number to it. I'm using the CloudFoundry PHP build pack, although other alternatives are also available.

  2. Connect to Bluemix and deploy the application. Use the cf command line interface to log in to Bluemix using your IBM username and password:
    shell> cf api https://api.ng.bluemix.net
    shell> cf login

    Change to the $APP_ROOT directory and push the application to Bluemix:

    shell> cf push

    Here's a sample of what you see during this process.

At this point, your application should be deployed to Bluemix. You should now be able to start using it by browsing to the host you specified in your application manifest -- for example, http://atm-finder-[random-number].mybluemix.net. Or click Run the app at the top of this article to try out a live demo of the application.

Conclusion

As this article has illustrated, Bluemix provides a solid foundation for creating and deploying mobile-friendly applications on a cloud-based platform. Add in PHP, Bootstrap, and jQuery and you've got a solid toolkit for building dynamic, interactive mobile web applications.

You can download all the code implemented in this article from its JazzHub repository, together with the configuration files for the PHP buildpack used in this article. I recommend that you get the code, start playing with it, and try adding new features to it. I guarantee you won't break anything, and it will definitely add to your learning.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Mobile development
ArticleID=998514
ArticleTitle=Build and deploy an ATM finder app on Bluemix
publish-date=02252015