7 de març del 2018

Ionic: aplicació per saber l'hora de sortida/posta del sol (V)

Part V: Versió amb Google Maps

Aquest article ens mostrarà com utilitzar Google Maps per escollir el lloc en l'aplicació per saber l'hora de sortida i posta de sol.

Fins ara hem vist:

Ara utilitzarem Google Maps per poder escollir el lloc. Tot i que hi ha molta informació sobre com utilitzar Google Maps, la informació sol estar incompleta o no acabar-se d'adaptar a les nostres necessitats concretes.

En general, una de les meves primeres fonts d'informació són els articles de Josh Morony. Són acurats i força actualitzats. Tota una referència. I just podem trobar-hi el que busquem: "com fer una pàgina de selecció de lloc amb Google Maps i Ionic". Seguirem el tutorial i fem els canvis necessaris per afegir la funcionalitat a la nostra aplicació.

Afegim el mòdul de xarxa, que permetrà verificar quan tenim connexió de dades, i les definicions de tipus per Google Maps:
 $ ionic cordova plugin add cordova-plugin-network-information
 $ npm install --save @ionic-native/network
 $ npm install @types/google-maps --save

Seguint el tutorial, afegim la pàgina nova (LocationSelect) i els nous proveïdors a app.module.ts:
 ...
 import { CalendarModule } from "ion2-calendar";
 import { LocationSelect } from '../pages/location-select/location-select';
 import { Connectivity } from '../providers/connectivity-service';
 import { GoogleMapsService } from '../providers/google-maps-service';
 import { Network } from '@ionic-native/network';
 ...
  declarations: [
    ...
    HomePage,
    LocationSelect,
    TabsPage
  ],
  ...
  entryComponents: [
    ...
    HomePage,
    LocationSelect,
    TabsPage
  ],
  providers: [
    ...
    Geolocation,
    Connectivity,
    GoogleMapsService,
    Network
  ]
 ...

Ara afegim les pàgines dels proveïdors de servei, segons el tutorial. En primer lloc, providers/connectivity-service.ts:
  1. import { Injectable } from '@angular/core';
  2. import { Network } from '@ionic-native/network';
  3. import { Platform } from 'ionic-angular';
  4. @Injectable()
  5. export class Connectivity {
  6. onDevice: boolean;
  7. constructor(public platform: Platform, public network: Network) {
  8. this.onDevice = this.platform.is('cordova');
  9. }
  10. isOnline(): boolean {
  11. if(this.onDevice && this.network.type){
  12. return this.network.type != 'none';
  13. } else {
  14. return navigator.onLine;
  15. }
  16. }
  17. isOffline(): boolean {
  18. if(this.onDevice && this.network.type){
  19. return this.network.type == 'none';
  20. } else {
  21. return !navigator.onLine;
  22. }
  23. }
  24. watchOnline(): any { return this.network.onConnect(); }
  25. watchOffline(): any { return this.network.onDisconnect(); }
  26. }

I tot seguit, providers/google-maps-service.ts:
  1. import { Injectable } from '@angular/core';
  2. import { Connectivity } from './connectivity-service';
  3. import { Geolocation } from '@ionic-native/geolocation';
  4. @Injectable()
  5. export class GoogleMapsService {
  6. mapElement: any;
  7. pleaseConnect: any;
  8. map: any;
  9. mapInitialised: boolean = false;
  10. mapLoaded: any;
  11. mapLoadedObserver: any;
  12. currentMarker: any;
  13. apiKey: string; // = "YOUR_API_KEY";
  14. lat: number = null;
  15. lon: number = null;
  16. constructor( public connectivityService: Connectivity,
  17. public geolocation: Geolocation )
  18. {
  19. }
  20. init( mapElement: any, pleaseConnect: any,
  21. lat: number, lon: number
  22. ): Promise
  23. {
  24. this.mapElement = mapElement;
  25. this.pleaseConnect = pleaseConnect;
  26. this.lat = lat;
  27. this.lon = lon;
  28. return this.loadGoogleMaps();
  29. }
  30. loadGoogleMaps(): Promise {
  31. return new Promise((resolve) => {
  32. if(typeof google=="undefined" || typeof google.maps=="undefined") {
  33. console.log("Google maps JavaScript needs to be loaded.");
  34. this.disableMap();
  35. if(this.connectivityService.isOnline()){
  36. window['mapInit'] = () => {
  37. this.initMap().then(() => {
  38. resolve(true);
  39. });
  40. this.enableMap();
  41. }
  42. let script = document.createElement("script");
  43. script.id = "googleMaps";
  44. if(this.apiKey){
  45. script.src = 'http://maps.google.com/maps/api/js?key=' +
  46. this.apiKey + '&callback=mapInit&libraries=places';
  47. } else {
  48. script.src = 'http://maps.google.com/maps/api/js' +
  49. '?callback=mapInit&libraries=places';
  50. }
  51. document.body.appendChild(script);
  52. }
  53. } else {
  54. if(this.connectivityService.isOnline()){
  55. this.initMap();
  56. this.enableMap();
  57. } else {
  58. this.disableMap();
  59. }
  60. resolve(true);
  61. }
  62. this.addConnectivityListeners();
  63. });
  64. }
  65. initMap(): Promise {
  66. this.mapInitialised = true;
  67. return new Promise((resolve) => {
  68. if(this.lat && this.lon) {
  69. this.initMapPos(this.lat, this.lon);
  70. resolve(true);
  71. } else {
  72. this.geolocation.getCurrentPosition().then((pos) => {
  73. this.initMapPos(pos.coords.latitude, pos.coords.longitude);
  74. resolve(true);
  75. }).catch((error) => {
  76. console.log('Error getting location'+JSON.stringify(error));
  77. return Promise.reject("Unable to get location");
  78. });
  79. }
  80. });
  81. }
  82. initMapPos(lat: number, lon: number) {
  83. let latLng = new google.maps.LatLng(lat, lon);
  84. let mapOptions = {
  85. center: latLng,
  86. zoom: 15,
  87. mapTypeId: google.maps.MapTypeId.ROADMAP
  88. }
  89. this.map = new google.maps.Map(this.mapElement, mapOptions);
  90. }
  91. disableMap(): void {
  92. if(this.pleaseConnect){
  93. this.pleaseConnect.style.display = "block";
  94. }
  95. }
  96. enableMap(): void {
  97. if(this.pleaseConnect){
  98. this.pleaseConnect.style.display = "none";
  99. }
  100. }
  101. addConnectivityListeners(): void {
  102. this.connectivityService.watchOnline().subscribe(() => {
  103. setTimeout(() => {
  104. if(typeof google=="undefined" || typeof google.maps=="undefined") {
  105. this.loadGoogleMaps();
  106. } else {
  107. if(!this.mapInitialised) {
  108. this.initMap();
  109. }
  110. this.enableMap();
  111. }
  112. }, 2000);
  113. });
  114. this.connectivityService.watchOffline().subscribe(() => {
  115. this.disableMap();
  116. });
  117. }
  118. }

En aquest fitxer hem fet alguns canvis:
  • [6] Hem canviat el nom de la classe a GoogleMapsService.
  • [14] Hem eliminat l'apiKey. Se n'hauria de generar una per poder-la utilitzar en l'aplicació però, per fer proves, podem treballar sense.
  • [15-16] Declarem les variables per poder inicialitzar el mapa a partir d'una posició concreta, enlloc de la posició del nostre dispositiu.
  • [24-31] Afegim paràmetres per la posició desitjada a la funció init i els traslladem als corresponents camps de la classe. 
  • [59] Hem afegit el paràmetre que faltava a la petició: '&libraries=places'.
  • [79-109] En la funció initMap, utilitzem la posició obtinguda durant la inicialització, o si està buida, usem el servei de geolocalització. També hem separat el codi d'inicialització de Google Maps en una nova funció initMapPos per no repetir codi.

Pel que fa a la pàgina LocationSelect, el fitxer d'estils location-select/location-select.scss no s'ha modificat, en el fitxer location-select/location-select.html només s'han traduït els textos al català, i el fitxer controlador location-select/location-select.ts s'ha modificat per poder passar la localització actual al servei GoogleMapsService:
  1. ...
  2. import { NavParams } from 'ionic-angular';
  3. import { GoogleMapsService } from '../../providers/google-maps-service';
  4. ...
  5. export class LocationSelect {
  6. ...
  7. location: any;
  8. lat: number = 42.582104;
  9. lon: number = 1.0008419;
  10. constructor( public navCtrl: NavController,
  11. public params: NavParams,
  12. public maps: GoogleMapsService,
  13. public platform: Platform,
  14. public geolocation: Geolocation,
  15. public viewCtrl: ViewController)
  16. {
  17. this.searchDisabled = true;
  18. this.saveDisabled = true;
  19. this.lat = params.get('lat');
  20. this.lon = params.get('lon');
  21. }
  22. ionViewDidLoad(): void {
  23. this.maps.init( this.mapElement.nativeElement,
  24. this.pleaseConnect.nativeElement,
  25. this.lat, this.lon).then(() => {
  26. this.autocompleteService =
  27. new google.maps.places.AutocompleteService();
  28. this.placesService =
  29. new google.maps.places.PlacesService(this.maps.map);
  30. this.searchDisabled = false;
  31. });
  32. }
  33. ...

On:
  • [2] S'ha afegit la classe NavParams per poder passar paràmetres a la pàgina.
  • [8-9] S'han afegit membres a la classe per a guardar la posició inicial.
  • [12] S'utilitza el controlador NavParams en la classe, per a rebre els paràmetres.
  • [21-22] Es llegeixen i es guarden els paràmetres relacionats amb la posició.
  • [28] Els paràmetres de la posició s'utilitzen en la crida init del servei GoogleMapsService.

I finalment només ens queda modificar la pàgina HomePage per afegir la funcionalitat. En el controlador home.ts:
  1. ...
  2. import { LocationSelect } from '../location-select/location-select';
  3. ...
  4. export class HomePage {
  5. ...
  6. launchLocationPage() {
  7. let modal = this.modalCtrl.create( LocationSelect,
  8. {lat: this.lat, lon: this.lon});
  9. modal.onDidDismiss((location) => {
  10. if(location) {
  11. console.log("Nou lloc: ", location);
  12. this.lat = location.lat;
  13. this.lon = location.lng;
  14. this.posMsg = location.name;
  15. this.posError = false;
  16. this.getSunriseSunsetFromApi();
  17. }
  18. });
  19. modal.present();
  20. }
  21. }

Hem afegit:
  • [2] La importació de la nova pàgina per a seleccionar el lloc.
  • [6-22] La funció launchLocationPage per mostrar la finestra de selecció de lloc. Observem com passem els paràmetres de la posició actual a la nova pàgina [8], i com s'actualitzen les dades i missatges en cas d'haver escollit un lloc, en el retorn de la pàgina [13-17].

En la part de visualització home.html, simplement activem la crida a la funció anterior:
  1. ...
  2. <ion-item color="light" *ngIf="posError" (click)="launchLocationPage()">
  3. <h2 text-wrap>No s'ha pogut obtenir la posició actual.</h2>
  4. <p text-wrap>Verifica el GPS i/o els permisos.</p>
  5. <ion-icon name="warning" color="danger" item-left></ion-icon>
  6. </ion-item>
  7. <ion-item (click)="launchLocationPage()">
  8. <ion-label>Latitud</ion-label>
  9. <ion-note item-right>{{ lat | number: "1.1-7" }}</ion-note>
  10. </ion-item>
  11. <ion-item (click)="launchLocationPage()">
  12. <ion-label>Longitud</ion-label>
  13. <ion-note item-right>{{ lon | number: "1.1-7" }}</ion-note>
  14. </ion-item>
  15. ...

I ja ho tenim tot a punt ! Provem de seguida la nova funcionalitat:

Podem veure com el mapa s'obre en una nova finestra superposada, si té prou espai, i com permet escollir un lloc, auto-completant les opcions. En escollir-ne una, el mapa es posició en ella i, en guardar-la, s'utilitza per calcular les hores de sortida/posta del sol en la nostra aplicació:

Podem veure en la consola l'objecte retornat pel servei de Google Maps. Les seves propietats s'utilitzen per actualitzar les dades en la pàgina principal.
Com sempre, teniu tot el codi a la vostre disposició en el Github: v0.4 (amb Google Maps).

Ara ja tenim una aplicació funcional, però encara la podem completar en alguns aspectes. Per exemple, podríem afegir-hi traducció a diversos idiomes; també es poden fer alguns canvis estètics, amb icones personalitzades i tipus de lletra diferents; i finalment, completar el projecte donant les dades de contacte i afegint una pàgina per donar crèdit als projectes i persones que l'han fet possible.

Seguiu l'evolució de l'aplicació:

Cap comentari:

Publica un comentari a l'entrada