Geocoding/reverse geocoding with Ionic Native

June 13, 2017, 5:50 pm Categories:

Categories

The great thing about the Ionic Native Geocoder plugin is that it doesn't require any external API's as the functionality is provided using the iOS CoreLocation service and Android Geocoder class.

This is quite handy as it means we can perform geocode/reverse geocode operations directly within the application without having to rely on a third-party service such as the Google Maps Geocoding API.

What we're going to be building over the course of this tutorial is the single page application displayed below which will allow us to perform the following actions:

  • Geocoding - converting a given latitude and longitude coordinate value into an address
  • Reverse geocoding - converting a given address into a set of latitude and longitude values

Geocoding and reverse geocoding examples displayed in an Ionic framework application running on iOS

This a fairly simple but fun (I think) project so let's get cracking...

Laying the foundation

As with all things Ionic related we begin by creating a project from our system command line tool using the Ionic CLI:

ionic start ionic-geocode blank

Answer any CLI prompts that might appear (such as requesting upgrades to existing local plugins or requests to add new local plugins) and, once the project has been successfully created, change into the project root directory and install the Ionic Native Geocoder plugin (and its associated node package) with the following commands:

ionic cordova plugin add cordova-plugin-nativegeocoder
npm install --save @ionic-native/native-geocoder

Next, run the following command to generate an Angular service named geocoder:

ionic g provider geocoder

With the project now created, a geocoder service generated and the Ionic Native Geocoder plugin/package installed let's progress onto configuring the application's root module.

Open the ionic-geocode/src/app/app.module.ts file and change the contents so that they match the following:

import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { NativeGeocoder } from '@ionic-native/native-geocoder';


import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { GeocoderProvider } from '../providers/geocoder/geocoder';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    NativeGeocoder,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    GeocoderProvider
  ]
})
export class AppModule {}

Crafting the geocoding logic

The ionic-geocode/src/providers/geocoder/geocoder.ts service will handle the geocoding and reverse geocoding for the application by implementing methods made available from the Ionic Native Geocoder plugin.

These methods are as follows:

  • reverseGeocode - Allow transformation of latitude/longitude coordinate values into an address
  • forwardGeocode - Allow transformation of address into latitude/longitude coordinate values

The provider starts by importing the necessary modules from the Ionic Native Geocoder plugin package:

import { NativeGeocoder, 
         NativeGeocoderReverseResult, 
         NativeGeocoderForwardResult } from '@ionic-native/native-geocoder';

We then reference the NativeGeocoder module through a private property of _GEOCODE which is initialised within the class constructor like so:

constructor(public http       : Http,
            private _GEOCODE  : NativeGeocoder) 

The reverseGeocode method accepts 2 parameters: a latitude and longitude value which is subsequently supplied to the reverseGeocode method of the NativeGeocoder module.

A promise is wrapped around the Native plugin method to asychronously handle the return of the reverse geocoded data back to the home screen:

reverseGeocode(lat : number, lng : number) : Promise<any>
{
   return new Promise((resolve, reject) =>
   {
      this._GEOCODE.reverseGeocode(lat, lng)
      .then((result : NativeGeocoderReverseResult) => 
      {
         let str : string   = `The reverseGeocode address is ${result.street} in ${result.countryCode}`;
         resolve(str);
      })
      .catch((error: any) => 
      {
         reject(error);
      });
   });
}

The forwardGeocode method follows a similar pattern although it accepts a single parameter: a keyword/phrase pertaining to a real world address.

This is then supplied to the forwardGeocode method of the NativeGeocoder module which will subsequently return the matching latitude/longitude coordinates (if any can be found).

Once again this uses a promise to manage the asychronous handling of data returned from the forwardGeocode method of the NativeGeocoder module:

forwardGeocode(keyword : string) : Promise<any>
{
   return new Promise((resolve, reject) =>
   {
      this._GEOCODE.forwardGeocode(keyword)
      .then((coordinates : NativeGeocoderForwardResult) => 
      {
         let str : string   = `The coordinates are latitude=${coordinates.latitude} and longitude=${coordinates.longitude}`;
         resolve(str);
      })
      .catch((error: any) => 
      {
         reject(error);
      });
   });
}

The ionic-geocode/src/providers/geocoder/geocoder.ts service is displayed in full below:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { NativeGeocoder, 
         NativeGeocoderReverseResult, 
         NativeGeocoderForwardResult } from '@ionic-native/native-geocoder';


@Injectable()
export class GeocoderProvider {

   constructor(public http       : Http,
               private _GEOCODE  : NativeGeocoder) 
   {
      
   }



   /**
     *
     * Perform reverseGeocoding operation and return address details
     *
     * @public
     * @method reverseGeocode
     * @return {Promise}
     *
     */ 
   reverseGeocode(lat : number, lng : number) : Promise<any>
   {
      return new Promise((resolve, reject) =>
      {
         this._GEOCODE.reverseGeocode(lat, lng)
         .then((result : NativeGeocoderReverseResult) => 
         {
            let str : string   = `The reverseGeocode address is ${result.street} in ${result.countryCode}`;
            resolve(str);
         })
         .catch((error: any) => 
         {
            console.log(error);
            reject(error);
         });
      });
   }




   /**
     *
     * Perform forwardGeocode operation and return latitude/longitude details
     *
     * @public
     * @method forwardGeocode
     * @return {Promise}
     *
     */ 
   forwardGeocode(keyword : string) : Promise<any>
   {
      return new Promise((resolve, reject) =>
      {
         this._GEOCODE.forwardGeocode(keyword)
         .then((coordinates : NativeGeocoderForwardResult) => 
         {
            let str : string   = `The coordinates are latitude=${coordinates.latitude} and longitude=${coordinates.longitude}`;
            resolve(str);
         })
         .catch((error: any) => 
         {
            console.log(error);
            reject(error);
         });
      });
   }

}

Home screen logic

With the geocoding logic in place, courtesy of the Geocoder service, we can now begin scripting the logic for the ionic-geocode/src/pages/home/home.ts file.

This component class will manage the following tasks:

  • Handle validation for the 2 separate forms in the HTML template
  • Manage switching between the forms based on the user's choices in the HTML template
  • Handle data retrieved from the reverseGeocoding form submission and parse/return results using the reverseGeocode method of the Geocoder service
  • Handle data retrieved from the forwardGeocoding form submission and parse/return results using the forwardGeocode method of the Geocoder service

Here's what the component class looks like in full (the method names and commenting should make the class and its individual elements fairly self-explanatory to understand):

import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import {
  FormBuilder,
  FormGroup,
  Validators
} from '@angular/forms';

import { GeocoderProvider } from '../../providers/geocoder/geocoder';


@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   /**
    * Define a FormGroup object for the forwardGeocoding form
    */
   public form                   : FormGroup;
   


   /**
    * Define a FormGroup object for the reverseGeocoding form
    */
   public geoForm                : FormGroup;



   /**
    * Define a boolean property to reference whether geocoding has been 
    * performed or not
    */
   public geocoded               : boolean;



   /**
    * Define a string value to handle returned geocoding results
    */   
   public results                : string;



   /**
    * Define the initial text value for the form switching button in the
    * HTML template
    */   
   public filter                 : string      = 'Search by Coordinates';
   


   /**
    * Define a boolean property to determine that the forwardGeocoding
    * form is displayed first
    */   
   public displayForward         : boolean     = true;
   


   /**
    * Define a boolean property to determine that the reverseGeocoding
    * form is not to be displayed first
    */   
   public displayReverse         : boolean     = false;



   constructor(public navCtrl    : NavController,
               public _GEOCODE   : GeocoderProvider,
               private _FB       : FormBuilder,
               private _PLATFORM : Platform) 
   {


      // Define the validation rules for handling the
      // address submission from the forward geocoding form
      this.form       = _FB.group({
         'keyword'        : ['', Validators.required]
      });

      
      // Define the validation rules for handling the 
      // latitude/longitude submissions from the reverse 
      // geocoding form
      this.geoForm    = _FB.group({
         'latitude'        : ['', Validators.required],
         'longitude'       : ['', Validators.required]
      });

   }



   /**
     *
     * Determine whether the forwardGeocoding or
     * reverseGeocoding form will be displayed
     *
     * @public
     * @method filterForm
     * @return {none}
     *
     */   
   filterForm()
   {
      if(this.displayForward)
      {
         this.filter      		 = 'Search by keyword';
         this.displayReverse     = true;
         this.displayForward     = false;
      }
      else
      {
         this.filter             = 'Search by Co-ordinates';
         this.displayReverse     = false;
         this.displayForward     = true;
      }
   }



   
   /**
     *
     * Retrieve latitude/longitude coordinate values from HTML form, 
     * pass these into the reverseGeocode method of the Geocoder service 
     * and handle the results accordingly
     *
     * @public
     * @method performReverseGeocoding
     * @return {none}
     *
     */   
   performReverseGeocoding(val)
   {
      this._PLATFORM.ready()
      .then((data : any) =>
      {
         let latitude     : any = parseFloat(this.geoForm.controls["latitude"].value),
             longitude    : any = parseFloat(this.geoForm.controls["longitude"].value);

         this._GEOCODE.reverseGeocode(latitude, longitude)
         .then((data : any) =>
         {
            this.geocoded      = true;
            this.results       = data; 
            
         })
         .catch((error : any)=>
         {
            this.geocoded      = true;
            this.results       = error.message;
         });
      });
   }




   /**
     *
     * Retrieve address location submitted from HTML form, 
     * pass these into the forwardGeocode method of the Geocoder service 
     * and handle returned latitude/longitude coordinate values accordingly
     *
     * @public
     * @method performForwardGeocoding
     * @return {none}
     *
     */   
   performForwardGeocoding(val)
   {
      this._PLATFORM.ready()
      .then((data : any) =>
      {
         let keyword : string = this.form.controls["keyword"].value;
         this._GEOCODE.forwardGeocode(keyword)
         .then((data : any) =>
         {
            this.geocoded      = true;
            this.results       = data; 
            
         })
         .catch((error : any)=>
         {
            this.geocoded      = true;
            this.results       = error.message;
         });
      });
   }




}

Application templating

Finally we have the ionic-geocode/src/pages/home/home.html file which will handle the following tasks:

  • Display a button allowing the type of form (geocoding or reverseGeocoding) to be changed and rendered into view
  • Conditionally display the forwardGeocoding form
  • Conditionally display the reverseGeocoding form
  • Conditionally display returned geocoded results
<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Geocoder
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>


   <!--
      Handle switching between the different form types (and 
      change the text displayed within the button) by calling 
      the filterForm method from the component class
   -->
   <button 
      ion-button 
      block 
      color="secondary" 
      (click)="filterForm()">{{ filter }}</button>



   <!--
      Conditionally display the address based Geocoding form 
      and implement the Angular FormBuilder form object (defined in the 
      component class) to handle validating the single form field
   -->
   <form 
      *ngIf="displayForward"
      [formGroup]="form" 
      (ngSubmit)="performForwardGeocoding(form.value)">
      <ion-list>
         <ion-item margin-bottom>
            <ion-label>Location</ion-label>
            <ion-input 
               type="text" 
               formControlName="keyword"></ion-input>
         </ion-item>
   
   
         <ion-item margin-bottom>
            <button 
               ion-button 
               color="primary"
               text-center
               block
               [disabled]="!form.valid">Geocode this location</button>
         </ion-item>
   
      </ion-list>
   </form>



   <!--
      Conditionally display the latitude/longitude based Geocoding form 
      and implement the Angular FormBuilder form object (defined in the 
      component class) to handle validating the 2 form fields
   -->
   <form 
      *ngIf="displayReverse"
      [formGroup]="geoForm" 
      (ngSubmit)="performReverseGeocoding(geoForm.value)">
      <ion-list>
         <ion-item margin-bottom>
            <ion-label>Latitude</ion-label>
            <ion-input 
               type="text" 
               formControlName="latitude"></ion-input>
         </ion-item>


         <ion-item margin-bottom>
            <ion-label>Longitude</ion-label>
            <ion-input 
               type="text" 
               formControlName="longitude"></ion-input>
         </ion-item>
   
   
         <ion-item margin-bottom>
            <button 
               ion-button 
               color="primary"
               text-center
               block
               [disabled]="!geoForm.valid">Geocode this location</button>
         </ion-item>
   
      </ion-list>
   </form>


   
   <!--
      Conditionally display the geocoding results
   -->
   <section *ngIf="geocoded">
      {{ results }}
   </section>

</ion-content>

With the coding now completed and all the necessary fields in place it's time to build and run the application on a connected handheld device (I'm running on iOS so the following code targets that platform) using the Ionic CLI commands shown below:

ionic cordova build ios--prod
ionic cordova run ios

Which, if all has gone well, should publish to and run the application on the device like so:

Geocoding and reverse geocoding examples displayed in an Ionic framework application running on iOS

In summary

As you can see managing geocoding/reverse geocoding within an Ionic application is made incredibly simple with the Ionic Native Geocoder plugin.

In the above tutorial we've created a very simple single page application to manage converting between latitude/longitude values to a physical address and in reverse.

This could, of course, be built upon by implementing online maps functionality to plot the converted value and render that on the screen accordingly.

I'll let you run with this possibility (and any others you might think of along the way).

Hopefully you've found the above tutorial useful and, if so, please feel free to share your comments, thoughts and suggestions in the comments area below.

I cover using the Ionic Native Google Maps plugin over different projects within my e-book featured below and if you're interested in learning more about my e-books please sign up to my FREE mailing list where you can receive updates on current/forthcoming e-books and blog articles.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top