In my previous post you have already seen how to add a local storage persistence layer for your photos with Ionic 2 framework.

In this post, I show you how to use location plugins to add coordinates for every photo taken and place your images on Google Maps.

This is the next part of our series where we integrate the Geolocation API and the native Google Maps support. I hope this part will be straightforward and helps everyone implementing these features.

So, let's do it!

Check out the other parts here:

Part 1Setup Ionic 2 Application and Take Photos with a Native Camera

Part 2Add a Local Storage Persistence Layer to your Photos

Part 3: Use Location Plugins to Add Coordinates for Every Photo Taken and Place Your Images on Google Maps (This post!)

Part 4: Change the Persistence Layer with Firebase, and Make a Real-time Application

Receive geolocation for every image

Ionic-native supports geolocation API through a cordova plugin. Firstly, you need to add this plugin to your project.

ionic plugin add cordova-plugin-geolocation

Ionic native provides an API to use the module easily. We need to extend the photo page’s ts-file, the geolocation service, and the coordinates’ interface to the result.

import { Geolocation, Coordinates } from 'ionic-native';
...
private coords: Coordinates = null;
...
Geolocation.getCurrentPosition().then((resp) => {
  this.coords = resp.coords;
}).catch((error) => {
  this.coords = null;
});

This simple change helps us to set our location to the coordinates’ variable. We can use this value at the addPhoto method and store the coordinates in the localstorage. We need to extend the PhotoRecord interface with a coord: coordinates property.

This is how the addPhoto method has changed.

photo.ts:
this.photoStorage.addPhoto(this.base64Image, this.coords)
 
storage.ts:
addPhoto(photoData: string, coordinates: Coordinates) {
  return this.getPhotos().then(photoList => {
    const coords = this.coordsToJSON(coordinates);
    photoList.push({ data: photoData, coords });
    return localforage.setItem(PHOTO_COLLECTION, JSON.stringify(photoList));
  });
}

Hint: The coordinates’ value is always an empty object after JSON.stringify; we need to add a mapping method to make a plain js object:

coordsToJSON(coordinates: Coordinates) : Coordinates {
  return {
    accuracy: coordinates.accuracy,
    altitude: coordinates.altitude,
    altitudeAccuracy: coordinates.altitudeAccuracy,
    heading: coordinates.heading,
    latitude: coordinates.latitude,
    longitude: coordinates.longitude,
    speed: coordinates.speed,
  };
}

We are done with the location part. Let’s continue with the harder part, which is the Google Maps integration.

Get Google Maps credentials from Google Developer Console

We are trying to integrate the GMaps plugin to this page and place markers to the saved photo locations.

The next step is to add the maps cordova plugin to the project, but before that, we need to generate API keys from the Google Developer Console.

1. Login with a Google account and go to the dashboard.

2. Create a new project.

3. Name it whatever you want and click on “Create.” The project will set up in a few seconds.

4. On the project’s page, click on the enable API button.


You have to query for two APIs named:

5. Click on each API and push the enable button.

When they are enabled, you need some credentials, and a warning message will notify you about this:


The UI will help you create two API keys. One for the Android and one for the iOS app.

I made these API keys without restrictions. Later, you can setup the keys to work only with this specific application. It’s a must-have step for production release, but for now, I will not setup these.

Integrate the Maps plugin

After these steps, you can add the cordova plugin to your project using the generated keys.

ionic plugin add cordova-plugin-googlemaps \
--variable API_KEY_FOR_ANDROID="YOUR_ANDROID_API_KEY_IS_HERE" \
--variable API_KEY_FOR_IOS="YOUR_IOS_API_KEY_IS_HERE"

We’re making a new page called Maps with the CLI:

ionic generate page Maps

The maps.html’s ion-content will only have one div containing the maps. Besides it, we’re removing the padding attribute from ion-content to make the map fullscreen without any borders.

<ion-content>
  <div map id="map" class="map"></div>
</ion-content>

We need to add some style to the map because as default, it has no height. To do so we should add some lines to the maps.scss:

page-maps {
  & .map {
    height:100%;
  }
}

Then we can add the initialization code to maps.ts. For me, it looks like this:

import { Component } from '@angular/core';
import {
GoogleMap,
GoogleMapsEvent,
} from 'ionic-native';

@Component({
  selector: 'page-maps',
  templateUrl: 'maps.html'
})
export class MapsPage {
  private map: GoogleMap = null;
  constructor() {}

  ngAfterViewInit() {
    this.initMap();
  }

 initMap() {
    const element: HTMLElement = document.getElementById('map');

    this.map = new GoogleMap(element);
    this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
      console.log('Map is ready!')
    });
  }
}

Now we need to add the new page to the app.module.ts declarations and entryComponents array as we did in the previous part with the Photo page.

Then, we only need to add another navigation card to the home.html:

<ion-card (click)="goMapsPage()">
  <img src="assets/images/photo-menu-bg.jpg"/>
  <div class="card-title">Open maps</div>
</ion-card>

Now we must make the navigation function like the Photo navigation at the home.ts:

goMapsPage() {
  this.navCtrl.push(MapsPage);
}

This is the most basic GMaps integration to an ionic project. We just need to run our application to see the map working as expected.

Note: With this configuration, the maps work properly on Android, but on iOS build, I got a black screen instead of the maps. We need to add some additional css to the maps.scss file to resolve this issue:

ion-app._gmaps_cdv_ .nav-decor{
  background-color: transparent !important;
}

After adding these lines, the maps plugin will work on iOS devices like a charm. :)

Iosdev

Source: Meme.am

Let’s put markers on the map

After these steps, the only thing I want to do is to get the photos array from the PhotoStorage and make a pin for the location (if the image has one).

We need to add PhotoStorage in the constructor:

constructor(private photoStorage: PhotoStorage) {}

When the map inited, we switch the promise callback from console log to a setup function:

this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
 this.loadImagePlaces(this.map);
});
 
loadImagePlaces(map: GoogleMap) {
  this.photoStorage.getPhotos().then(photos => {
    photos.forEach(photo => {
      const coords = photo.coords as Coordinates;
      if (coords) {
        if (coords.latitude && coords.longitude) {
          this.createMarker(coords, map);
        }
      }
    });
  });
}
 
createMarker(coords: Coordinates, map: GoogleMap) {
  let place: GoogleMapsLatLng = new GoogleMapsLatLng(coords.latitude, coords.longitude);
 
  let markerOptions: GoogleMapsMarkerOptions = {
    position: place,
    title: 'Picture'
  };
 
  map.addMarker(markerOptions);
}

We also add some lifecycle event to clear the map when we leave the page:

ionViewWillLeave() {
  this.clearMap(this.map);
}
 
clearMap(map) {
  map.clear();
}
 
ngOnDestroy() {
  this.map.remove();
  this.map = null;
}

To show the image related to the marker, we will render the image to HTML canvas and put it to the marker’s title as base64 string. For this, we will modify the createMarker function a bit.

This is what we want to achieve (before - after):

                                     

We put the picture’s base64 string into an HTMLImage element, and when it’s ready, the onload callback function will invoke. In this callback, we’re initailizing an HTML canvas where we render this image.

createMarker(coords: Coordinates, data: string, map: GoogleMap) {
  const place: GoogleMapsLatLng = new GoogleMapsLatLng(coords.latitude, coords.longitude);
 
  const img = new Image();
  img.onload = () => this.createMarkerWithImage(img, place, map);
  img.src = data;
}
 
createMarkerWithImage(img: HTMLImageElement, place: GoogleMapsLatLng, map: GoogleMap) {
  const canvas = this.createCanvas(img, window);
 
  let markerOptions: GoogleMapsMarkerOptions = {
    position: place,
    title: canvas.toDataURL(),
  };
 
  map.addMarker(markerOptions);
}
 
createCanvas(img: HTMLImageElement, window: Window) {
  const ratio = this.getRatio(img, window);
  const width = img.width * ratio;
  const height = img.height * ratio;
 
  return this.drawImageToCanvas(width, height, img);
}

Next, the image’s width and height is calculated to scale the image because of the camera’s resolution.

getRatio(img: HTMLImageElement, window: Window) {
  let ratio = 1;
  if (img.width > img.height) {
    ratio = (window.innerWidth / img.width) * 0.8;
  } else {
    ratio = (window.innerWidth / img.height) * 0.8;
  }
  return ratio;
}

We should display the image on the canvas, and we need to give back the canvas’ reference.

drawImageToCanvas(width: number, height: number, img: HTMLImageElement) {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
 
  const context = canvas.getContext('2d');
  context.drawImage(img, 0, 0, width, height);
  return canvas;
}

We can get the full base64 representation with toDataURL() function of the created canvas and attach it to the title.

This is a very basic example for using markers on the map with canvas images. Those can be customized in several ways. More detailed info can be found on the plugin’s wiki marker section.

For the canvas part, the full feature set can be seen here. You can check out the HTML canvas reference here.

With a little investigation, we can easily use the Google Maps Plugin in our ionic application. The geolocation plugin was pretty straightforward to use, and we didn’t have any issues with that.

I hope this article was detailed enough to help everyone create their own application with these integrations.

In the next part, I will show you how to switch the localstorage part and use Google’s realtime database (Firebase) instead.


State of Software Develoopment Report