<< Back

Instructions for the Places Mobile App Project

In this project you are going to create a mobile app like this that reads your geolocation and displays nearby places.


We will be using the following Application Programmer Interfaces (APIs) which augment Google's Maps and Places APIs. The source code for these APIs is provided if you would like to implement the same or modified interface on your own server.

APIs



In this exercise we use Ionic Creator to create the screens. If you don't have an account yet you can sign up at Creator.Ionic.io. Then you can decide whether to upgrade to Creator Pro. With Creator Pro you will continue to use Creator to add the Angular directives and services necessary for the app to work. If you choose not to upgrade, then you can export your app to your desktop to complete.


Step 1. Add Ionic Components

Create Pages

Create the two pages used for this app.
  • Page 1
    • Title = Places
    • Routing URL = /page1
  • Page 2 - Click +Add Page above the left Pages panel
    • Click to create a Blank page
    • Set Title = Details
    • Routing URL = /page2

Page 1 Components

In the Pages panel in the upper left, click the Places page to edit the first page. Add the following components
  • Container - First row
    • Tag = div
    • Classes = row
    • Container
      • Tag = div
      • Classes = col-50 col-bottom
      • Paragraph
        • Content = Enter Address or tap **Where Am I?**
    • Container - "Where Am I?" button
      Hint - Duplicate the left container, then edit the right container.
      • Tag = div
      • Classes = col-50
      • Button
        • Link = Page (first icon)
        • Type = Not Linked
        • Text = Where Am I?

Page 1 - Places (continued)

Continue by adding these components on the first page under the row container containing the "Where Am I?" button.
  • Input
    • Title =
    • Type = Text
    • Placeholder = Enter Street Address
  • Paragraph
    • Align = Center
    • Content = **{{ data.latlng }}**
  • Select
    • Title = Place Type
  • Paragraph
    • Align = Center
    • Content = {{ data.num_places }}
  • Avatar List Item
    • Link = Page (first icon)
    • Type = Details - /page2
    • Style = Avatar
    • Switch to text input (not upload mode)
    • Avatar Image = {{ place.icon }}
    • Text Content = {{ place.name }}
    • Text Line #2 = {{ place.vicinity }}
    • Right Side Icon = ion-ios-arrow-right

Page 2 - Place Details

In the Pages panel in the upper left, click the Details page to edit the second page. Add the following components
  • Heading
    • Text = {{ post.result.name }}
    • Size = H3
    • Align = Center
  • Paragraph
    • Align = Center
    • Content = **{{ post.result.vicinity }}**
  • Container - Star Rating and Dollar Sign Price Level
    • Tag = div
    • Classes = row
    • Container
      • Tag = div
      • Classes = col-50
      • Paragraph
        • Content = **Rating**
      • Container - below paragraph
        • Tag = div
        • Classes = row
        • Container
          • Tag = div
          • Classes = col-25
          • Paragraph - inside col-25 container
            • Color = #E42112
            • Align = Center
            • Content = {{ post.result.rating }}
        • Container - Star Icons
          Hint - Duplicate the col-25 container containing the paragraph on the left, then edit the duplicated container right container.
          • Tag = div
          • Classes = col-75
          • Image - inside the col-75 container
            • Link = Page (first icon)
            • Type = Not Linked
            • Source = Switch to text input (not upload mode)
            • Image Source = {{ data.star_rating_icon }}
            • Style Size = 100% x auto
            • Style Position= Center
    • Container
      Hint - Duplicate the col-50 container containing the rating and rating icons, then edit the right container.
      • Tag = div
      • Classes = col-50
      • Paragraph
        • Content = **Price Level**
        • Container - Price Level
          • Tag = div
          • Classes = row
          • Container
            • Tag = div
            • Classes = col-25
            • Paragraph
              • Color = #E42112
              • Align = Center
              • Content = {{ post.result.price_level }}
          • Container - Dollar Sign Icons
            • Tag = div
            • Classes = col-75
            • Image
              • Link = Page (first icon)
              • Type = Not Linked
              • Source = Switch to text input (not upload mode)
              • Image Source = {{ data.price_level_icon }}
              • Style Size = 100% x auto
              • Style Position= Center
  • Spacer
    • Height = 50px - When adding the rest of the components, always keep this spacer component at the very bottom

Page 2 - Place Details (continued)

Continue on the second page. Add the following components below the Rating and Price Level icons and above the 50px spacer component. Keep the 50px spacer at the very bottom.
  • Container
    • Tag = div
    • Classes = row
    • We will make the More... button first, then duplicate it for the Phone button.
    • Container - More ... Button to Google Places Page
      • Tag = div
      • Classes = col-34
      • Button
        • Link = Link (second icon)
        • Type = {{ post.result.url }}
        • Text = More ...
        • Align = Center
        • Icon = ion-ios-arrow-right
        • Icon Position = align right
    • Container - Phone Number Button (left of More... button)
      Hint: Duplicate the col-34 container containing the button, then edit the left col-34 container.
      • Tag = div
      • Classes = col-66
      • Button
        • Link = Phone (last icon)
        • Type = {{ post.result.formatted_phone_number }}
        • Text = {{ post.result.formatted_phone_number }}
        • Align = Center
        • Icon = No Icon
  • Container - Hours, Open, Closed Row
    • Tag = div
    • Classes = row
    • Container
      • Tag = div
      • Classes = col-50
      • Paragraph
        • Content = **Hours**
    • Container
      • Tag = div
      • Classes = col-50
      • Paragraph
        • Color = #E42112 (red)
        • Align = Right
        • Content = **Open**
      • Paragraph
        • Color = #969292 (gray)
        • Align = Right
        • Content = **Closed**
  • Container - Open Hours Daily
    • Tag = div
    • Classes =
    • Html
      • <span ng-repeat="hour in post.result.opening_hours.weekday_text">
        {{ hour }}<br>
        </span>
  • Paragraph
    • Content = _____
  • Slider - Static Maps (4 at different zoom levels)
    • Style Size = 100% x 300px
    • Click Add New Slide (Slide 4)
      • Click on and delete the Slide 4 Heading (on the slide)
      • Switch to text input (not upload mode)
      • Enter {{ data.map4 }}
    • Edit Slide = Slide 1
      • Title = Slide 1
      • Switch to text input (not upload mode)
      • Enter {{ data.map1 }}
    • Edit Slide = Slide 2
      • Title = Slide 2
      • Switch to text input (not upload mode)
      • Enter {{ data.map2 }}
    • Edit Slide = Slide 3
      • Title = Slide 3
      • Switch to text input (not upload mode)
      • Enter {{ data.map3 }}
  • Paragraph
    • Align = Center
    • Content = Swipe to Zoom
  • Paragraph
    • Content = _____


Step 2. Export Your Mobile App from Ionic Creator to your Website

To export your app to your server,
  1. Click on the Cloud icon with the down arrow to export your application to your computer as a ZIP file.
  2. Open your cPanel on your web server.
  3. In the cPanel File Manager, create a new folder in the public_html directory named places.
  4. Open the places folder. Upload the zipped folder of your app into the places folder on your server.
  5. Select the zipped file, right click and unzip the file.
  6. Test your app by going to the /places/ directory of your website on your browser or mobile device.
  7. If your site is blank, the access permissions of the lib folder might not have been initialized correctly. To make sure,
    1. In your /places/ directory, create a new file named chmod.php
    2. Enter the following line: <?php exec('chmod -R 755 lib'); echo "done"; ?>
    3. Save the file. Execute by going to /places/chmod.php
  8. Use the Developer's Web Console of your browser to track down errors you may have.


Step 3. Add Angular Directives

Page 1 - Places

  • Button - "Where Am I"
    • ng-click="readGeolocation()"
  • Input - "Enter Street Address"
    • ng-model="data.address"
    • ng-blur="getLocation()"
  • Select - Place Type
    • ng-model="data.type"
    • ng-options="type.TypeID as type.TypeName for type in types"
    • ng-change="getPlaces()"
  • Avatar List - List of nearby places to geolocation
    • ng-repeat="place in places"
  • Avatar List Item
    • (Pro) place_id="{{ place.place_id }}"
    • (Pro) price_level="{{ place.price_level }}"
    • (Free) ui-sref="details({&quot;place_id&quot;:&quot;&quot;+ place.place_id +&quot;&quot;,&quot;price_level&quot;:&quot;&quot;+ place.price_level +&quot;&quot;})"

Page 2 - Place Details

  • Paragraph - "Rating"
    • ng-hide="hideRating"
  • Container class="row" containing the rating number and icons
    • ng-hide="hideRating"
  • Container class="col-50" containing the Price Level paragraph, price level number and icons
    • ng-hide="hidePriceLevel"
  • Container class="col-66" containing phone Button
    • style="padding-right:20px"
  • Button - Phone
    • ng-hide="hidePhone"
  • Paragraph - "Hours"
    • ng-hide="hideHours"
  • Paragraph - "Open"
    • ng-hide="hideOpen"
  • Paragraph - "Closed"
    • ng-hide="hideClosed"
  • Container - HTML Hours
    • style="padding-left:10px;"
    • ng-hide="hideHours"

With the Creator.Ionic.io development platform, follow the instructions to create a mobile app that uses this API.

Step 4. Add Page Controller Code

.controller('placesCtrl'
  1. Locate the line that begins with .controller('placesCtrl', ['$scope', '$stateParams', and
    add 'GetPlaces', 'GetAddress', 'GetLocation', 'GetTypes', 'Globals', (including the comma) just after "'$stateParams',"

  2. Replace line: function ($scope, $stateParams) { with :
    function ($scope, $stateParams, GetPlaces, GetAddress, GetLocation, GetTypes, GetPlace, Globals) {
        
        var key = Globals.key;
        
        $scope.data = { "type": "restaurant" };
    
        GetTypes.getPost()
        .then(function(response) {
            $scope.post = response;
        
            $scope.types = $scope.post.PlaceTypes;
        });
        
        var geo_options = { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 };
        
        function error_alert(err) {
          var msg='Geolocation ERROR(' + err.code + '): ' + err.message;
          alert(msg);
        }
        
        $scope.readGeolocation = function()
        {
            navigator.geolocation.getCurrentPosition(savePosition, error_alert, geo_options);
            setTimeout(readGeolocationAgain,1000);
        }
        function readGeolocationAgain()
        {
            navigator.geolocation.getCurrentPosition(savePosition, error_alert, geo_options);
            $scope.getAddress();
        }
        function savePosition(location)
        {
            var latitude = location.coords.latitude.toFixed(6);
            var longitude = location.coords.longitude.toFixed(6);
            $scope.data.latlng = latitude+','+longitude;
        }
        
        $scope.readGeolocation();
        
        $scope.getAddress = function()
        {
            GetAddress.getPost($scope.data.latlng, key)
            .then(function(response) {
                $scope.post = response;
                $scope.data.address = $scope.post.results[0].formatted_address;
            });
            $scope.getPlaces();
        }
    
        $scope.getLocation = function()
        {
            GetLocation.getPost($scope.data.address, key)
            .then(function(response) {
                $scope.post = response;
                $scope.data.address = $scope.post.results[0].formatted_address;
                var latitude = $scope.post.results[0].geometry.location.lat;
                var longitude = $scope.post.results[0].geometry.location.lng;
                $scope.data.latlng = latitude+','+longitude;
                $scope.getPlaces();
            })
        }
    
        $scope.getPlaces = function()
        {
            GetPlaces.getPost($scope.data.latlng, $scope.data.type, key)
            .then(function(response) {
                $scope.post = response;
                $scope.places = $scope.post.results;
                $scope.data.num_places = $scope.post.results.length + " places returned";
            })
        }
.controller('detailsCtrl'
  1. Locate the line that begins with .controller('detailsCtrl', ['$scope', '$stateParams', and
    add 'GetPlace', 'Globals', (including the comma) just after "'$stateParams',"

  2. Replace line: function ($scope, $stateParams) {
    with :
    function ($scope, $stateParams, GetPlace, Globals) {
        
        var key = Globals.key;
        
        $scope.data = {
    		"placeid" : $stateParams.place_id,
    		"price_level" : $stateParams.price_level,
    	};
    	
        GetPlace.getPost($scope.data.placeid, key)
        .then(function(response)
        {
            $scope.post = response;
    
            if(!$scope.post.result.formatted_phone_number) $scope.hidePhone = true;
    
            if($scope.post.result.opening_hours)
            {
                if($scope.post.result.opening_hours.open_now) { $scope.hideClosed = true; }
                else $scope.hideOpen = true;
            }
            else
            {
                $scope.hideClosed = true;
                $scope.hideOpen = true;
            }
    
            if($scope.post.result.rating)
            { 
                $scope.data.star_rating_icon = "https://secrdir.com/api/rating_icons?stars=" + $scope.post.result.rating;
            }
            else $scope.hideRating = true;
            
            if($scope.post.result.price_level)
            {
                $scope.data.price_level_icon = "https://secrdir.com/api/rating_icons?dollars=" + $scope.post.result.price_level;
            }
            else $scope.hidePriceLevel = true;
    
            var latlng = $scope.post.result.geometry.location.lat;
            latlng += ',' + $scope.post.result.geometry.location.lng;
            
            var map_url = "https://secrdir.com/api/staticmap?key=" + key;
            map_url += "&center="+latlng;
            
            $scope.data.map1 = map_url + "&zoom=12";
            $scope.data.map2 = map_url + "&zoom=15";
            $scope.data.map3 = map_url + "&zoom=18";
        });

Step 5. Add Parameter Passing to Page Routing

Locate and open the routes.js file in the js folder.

Just after "url: '/page2',", add a new line:
params: { place_id: "", price_level: "" },

Step 6. Add Services

Copy the following two services just above the ".service('BlankService', [function(){" line.
with :
.service('Globals',[function(){
    var ret = { "key": "MY_GOOGLE_API_KEY" }
    return ret;
}])

.service('GetPlaces', function($http){
    return {
        getPost: function(location, type, key) {
            var query = "?location="+location;
            query += "&key=" + key;
            query += "&type=" + type;
            query += "&rankby=distance";
            return $http.get("https://secrdir.com/api/place/nearbysearch/"+query)
            .then(function (response) {
                return response.data;
            })
        }
    }
})

.service('GetPlace', function($http){
    return {
        getPost: function(placeid, key) {
            var query = "?placeid="+placeid;
            query += "&key=" + key;
            return $http.get("https://secrdir.com/api/place/details/"+query)
            .then(function (response) {
                return response.data;
            })
        }
    }
})


.service('GetAddress', function($http){
    return {
        getPost: function(latlng, key) {
            var query = "?latlng="+latlng;
            query += "&key=" + key;
            return $http.get("https://secrdir.com/api/geocode/"+query)
            .then(function (response) {
                return response.data;
            })
        }
    }
})

.service('GetLocation', function($http){
    return {
        getPost: function(address, key) {
            var query = "?address="+address;
            query += "&key=" + key;
            return $http.get("https://secrdir.com/api/geocode/"+query)
            .then(function (response) {
                return response.data;
            })
        }
    }
})

.service('GetTypes', function($http){
    return {
        getPost: function() {
            return $http.get("https://secrdir.com/api/place/types/")
            .then(function (response) {
                return response.data;
            })
        }
    }
})


Richard Halverson, Jr., Ph.D. • University of Hawaii