Mostrar un mapa offline en Ionic con Leaflet

En el siguiente enlace tienes el 铆ndice para acceder al resto de entradas de este tutorial:

 

隆隆Atenci贸n!! este tutorial se basa en ionic 3 y est谩 desactualizado por lo que es posible que los ejemplos no funcionen en la 煤ltima versi贸n de ionic, haz click aqu铆 para acceder a un tutorial mas actual de Ionic.

Hola a todos:

En posts anteriores vimos como mostrar un mapa de Google maps en ionic, sin embargo 驴que pasa si queremos mostrar informaci贸n en un mapa sin conexi贸n?, google maps no nos podr谩 mostrar el mapa.

En lugar de utlizar Google maps en esta ocasi贸n vamos a utilizar Leaflet para mostrar un mapa.

Leaflet聽 es una biblioteca javascript de c贸digo abierto que nos permite crear mapas.

Lanzado por primera vez en 2011,聽es compatible con la mayor铆a de las plataformas m贸viles y de escritorio, y es compatible con聽HTML5聽y聽CSS3聽.

Es una de las bibliotecas de mapas de JavaScript m谩s populares y es utilizada por los sitios web como聽FourSquare聽,聽Pinterest聽y聽Flickr聽.

Para poder mostrar un mapa offline es necesario tener alojados loc谩lmente los titles que forman el mapa, si quieres mostrar un mapa de todo el mundo offline a d铆a de hoy podemos afirmar聽 que es inviable ya que eso supondr铆a much铆simos gigas de informaci贸n, sin embargo si que puede ser viable mostrar un mapa offline de una ciudad.

Si por ejemplo est谩s desarrollando una aplicaci贸n de turismo de una ciudad concreta donde vas a mostrar en un mapa聽 los monumentos que merece la pena visitar en dicha ciudad, o cualquier otra informaci贸n geolocalizada en un mapa, puede ser interesante el poder mostrar un mapa sin necesidad de tener conexi贸n a Internet en ese preciso momento.

Vamos a ver como podemos solucionar esto en ionic 3, y como siempre la mejor manera de verlo es con un ejemplo.

Lo primero que vamos ha hacer es crear un nuevo proyecto en ionic que vamos a llamar mapaoffline:

ionic start mapaoffline blank

Una vez creado el en proyecto entramos en la carpeta con:

cd mapaoffline

Ahora vamos a descargar Leaflet, para ello descargamos la 煤ltima versi贸n desde聽 la secci贸n de descargas de su p谩gina oficial en el siguiente enlace:

http://leafletjs.com/download.html

Se habr谩 descargado un archivo .zip, antes de nada debemos descomprimirlo para extraer su contenido:

 

Ahora vamos a crear una carpeta llamada leaflet en src/assets y dentro vamos a copiar la carpeta images y el archivo leaflet.css.

Una vez hecho esto vamos a cargar el archivo leaflet.css en index.html:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="UTF-8">
  <title>Ionic App</title>
  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">

  <link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
  <link rel="manifest" href="manifest.json">
  <meta name="theme-color" content="#4e8ef7">

  <!-- add to homescreen for ios -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">

  <!-- cordova.js required for cordova apps -->
  <script src="cordova.js"></script>

  <!-- un-comment this code to enable service worker
聽聽
聽聽聽聽if ('serviceWorker' in navigator) {
聽聽聽聽聽聽navigator.serviceWorker.register('service-worker.js')
聽聽聽聽聽聽聽聽.then(() => console.log('service worker installed'))
聽聽聽聽聽聽聽聽.catch(err => console.error('Error', err));
聽聽聽聽}
聽聽-->

  <link href="build/main.css" rel="stylesheet">
  <link href="assets/leaflet/leaflet.css" rel="stylesheet">


</head>
<body>

  <!-- Ionic's root component and where the app will load -->
  <ion-app></ion-app>

  <!-- The polyfills js is generated during the build process -->
  <script src="build/polyfills.js"></script>

  <!-- The vendor js is generated during the build process
聽聽聽聽聽聽聽It contains all of the dependencies in node_modules -->
  <script src="build/vendor.js"></script>


  <!-- The main bundle js is generated during the build process -->
  <script src="build/main.js"></script>

</body>
</html>

Ahora vamos a instalar leaflet desde consola con los siguiente comandos:

npm install leaflet --save
npm install @types/leaflet --save

Ahora que ya tenemos instalado leaflet necesitamos descargar los tiles del mapa que vamos a mostrar, para ello vamos a echar mano de una herramienta llamada Mobile Atlas Creator (MOBAC), para descargarla accedemos al siguiente enlace:聽http://mobac.sourceforge.net/聽y bajamos en la p谩gina hasta encontrar Download y pulsamos en latest stable version聽para descargar la aplicaci贸n.

Una vez descargada tenemos que descomprimir el archivo zip y dentro encontraremos el archivo ejecutable.

Al ejecutar Mobile Atlas Creator nos pide introducir un nombre para el atlas (en este caso le llamamos simplemente mapa) y el formato, tenemos que seleccionar Osmdroid ZIP.

 

Pulsamos Aceptar y nos mostrar谩 un mapa, podemos mover zoom para ver mejor la zona que queremos seleccionar. Si hacemos doble click en un punto el mapa har谩 zoom y se centrar谩 en ese punto, por ello para localizar la zona que queremos mostrar es mejor partir de un zoom que nos permita ver donde estamos e ir haciendo doble click hasta obtener la localizaci贸n y zoom que nos interesa.

En el panel de la izquierda en la secci贸n map source tenemos los mapas de los que podemos obtener los tiles, muchos son solo de una zona en concreto, yo para este ejemplo voy a utilizar OpenStreetMap4UMaps.eu聽que nos ofrece un mapa de todo el mundo.

Debajo del panel Map Source tenemos聽 el panel Zoom Levels, aqui debemos seleccionar los niveles de zoom que queremos que tenga el mapa, cabe mencionar que cuantos m谩s niveles de zoom mas tiles necesitaremos y por ende mas espacio ocupar谩n las im谩genes.

Marcamos por ejemplo 11,12,13,14, y 15.

Ahora tenemos que seleccionar la porci贸n del mapa que queremos descargar:

En la parte de arriba del mapa tenemos una barra de zoom que podemos mover para ver mejor la parte del mapa que queremos seleccionar. Con el rat贸n sobre el mapa seleccionamos un 谩rea.

Yo por ejemplo he seleccionado la zona de Bilbao, de est谩 manera podr茅 mostrar un mapa de Bilbao sin conexi贸n.

Ahora debajo del panel Zoom Levels tenemos el panel Atlas content, aqu铆 vamos a pulsar en el bot贸n聽Add Selection聽lo que nos crear谩 un Layer聽 que si lo desplegamos vemos que contiene dentro los niveles de zoom que hemos seleccionado.

Bien, una vez que hemos seleccionado el 谩rea que queremos descargar tenemos que seleccionar Create Atlas聽y聽acto seguido comenzar谩 a generar el atlas:

El atlas que se ha generado ser谩 un archico .zip con el nombre que le hemos dado al atlas en este caso mapa seguido de la fecha y hora en la que ha sido generado y lo encontrar谩s en la carpeta atlases聽dentro de la carpeta聽 donde tengas Mobile Atlas Creator.

Bien, ahora vamos descomprimir el archivo zip que nos ha generado, al descomprimir vemos que dentro de la carpeta que nos ha creado hay otra carpeta llamada 4uMaps, vamos a cambiarle el nombre a la carpeta por simplemente聽mapa聽y a copiar esta carpeta dentro de la carpeta assets de nuestro proyecto:

 

Una vez que tenemos Los tiles copiados en la carpeta assets de nuestro proyecto ya podemos mostrarlos en el mapa.

Vamos a editar home.html聽e igual que hac铆amos cuando creamos un mapa con google maps vamos a crear un div con id=”map” donde se renderizar谩 el mapa:

<ion-header>
<ion-navbar>
  <ion-title>
    Ionic Blank
  </ion-title>
</ion-navbar>
</ion-header>

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

Ahora le vamos a definir el tama帽o para que ocupe toda la pantalla por lo tanto editamos home.scss y a帽adimos lo siguiente:

page-home {
聽聽聽聽#map{
聽聽聽聽聽聽聽聽width:100%;
聽聽聽聽聽聽聽聽height: 100%;
    }
}

Ahora en home.ts vamos a ver como se crea un mapa con leaflet:

Lo primero que debemos hacer es importar leaflet:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import * as L from 'leaflet';

Ahora vamos a crear una variable de clase llamada map donde guardaremos la instancia del mapa que vamos a crear:

...

export class HomePage {

  map : any;

  constructor(public navCtrl: NavController) {

  }

...

Ahora cuando se haya cargado la p谩gina en el m茅todo onViewDidLoad vamos a crear el mapa de la siguiente manera:

ionViewDidLoad() {

   this.map = L.map('map').
     setView([ 43.2603479, -2.9334110],
     12);

   L.tileLayer('assets/mapa/{z}/{x}/{y}.png', {    maxZoom: 15  }).addTo(this.map);

   L.marker([ 43.2603479,-2.9334110],{draggable: true}).addTo(this.map);

 }

Para crear un mapa con Leaflet utilizamos L.map(‘map’), ‘map’ es el id del div que hemos creado en la vista home.html y que es donde se va a renderizar el mapa.

Despu茅s con el metodo .setView le indicamos las coordenadas donde se tiene que posicionar el mapa, en este caso son las del centro de Bilbao.

Con L.tileLayer()聽a帽adimos al mapa una capa de tiles que va a contener los tiles del mapa que hemos descargado, como primer par谩metro le pasamos la ruta donde se encuentran los tiles que puede ser una url si estamos utilizando un mapa online o la ruta donde se encuentrar alojados los archivos para mostrar el mapa offline como es el caso.

Si nos fijamos en la ruta vemos que es ‘assets/mapa/{z}/{x}/{y}.png’,聽聽聽z, x e y hacen referencia respectivamente al nivel de Zoom, tiles en el eje X y tiles en el eje Y.

Obtendr谩n el valor correspondiente autom谩ticamente en funci贸n de la posici贸n del mapa y al tener la carpeta de los tiles ordenada por estos par谩metros no te tienes que preocupar.

Despues con聽L.marker聽 creamos un marcador en en las coordenadas indicadas y le decimos que sea聽 arratrable (draggable) y con .addTo(this.map) lo a帽adimos a nuestro mapa.

El c贸digo completo de home.ts quedar铆a as铆:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import * as L from 'leaflet';

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

  map;

  constructor(public navCtrl: NavController) {

  }

  ionViewDidLoad() {

    this.map = L.map('map').
      setView([ 43.2603479, -2.9334110],
      12);

    L.tileLayer('assets/mapa/{z}/{x}/{y}.jpg', {    maxZoom: 15  }).addTo(this.map);

    L.marker([ 43.2603479,-2.9334110],{draggable: true}).addTo(this.map);

  }

}

Ahora si ejecutamos nuestra app aunque no tengamos conexi贸n a internet podemos mostrar un mapa:

 

Eso es todo por hoy, como siempre espero que os sea de utilidad.

 

Si necesitas desarrollar una aplicaci贸n m贸vil no dudes en solicitarme un presupuesto sin compromiso:

Un saludo, y si a煤n no lo has hecho no olvides suscribirte a mi blog para no perderte los pr贸ximos posts 聽:-),

Tambi茅n puedes seguirme en Twitter en 鈥嶡revigames y no olvides que me ayudas mucho聽si compartes este post en las redes sociales.

10 comentarios en “Mostrar un mapa offline en Ionic con Leaflet

  1. buen d铆a eduardo.

    tengo una cuesti贸n.. necesito convertir coordenadas al formato de direcci贸n, las coordenadas la tengo en una tabla de firebase, segu铆 tu tutorial de mis sitios pero hay genera las coordenadas con el GPS y ahora que tengo las coordenadas ya no logro transformarlo al formato de direcci贸n.

    me podr铆as decir como hacerle o donde puedo leer algo acerca de eso, ya le铆 la documentaci贸n de google pero no logre implementarlo a ionic.

    espero tengas un espacio en tu tiempo para contestarme, excelentes tutoriales, los sigo desde el juego de adivina el numero.

    saludos y bendiciones

    1. Hola gamster12, no entiendo muy bi茅n cual es el problema, es decir, da igual si las coordenadas las obtienes del gps o de firebase, una vez que obtienes las coordenadas puedes usar la funci贸n getAdress que vimos en este post:
      https://reviblog.net/2017/03/23/tutorial-de-ionic-2-crear-una-aplicacion-para-guardar-nuestros-sitios-geolocalizados-parte-4-mostrando-la-direccion-a-partir-de-las-coordenadas/

      getAddress(coords):any {
      var geocoder = new google.maps.Geocoder();

      return new Promise(function(resolve, reject) {
      geocoder.geocode({‘location’: coords} , function (results, status) { // llamado asincronamente
      if (status == google.maps.GeocoderStatus.OK) {
      resolve(results);
      } else {
      reject(status);
      }
      });
      });
      }

      Luego para obtener la direcci贸n solo tienes que llamar a la funci贸n getAddress p谩sandole como par谩metro un objeto con las coordenadas que has obtenido por ejemplo:
      // suponiendo que tienes coords declarado
      this.coords.lat = // Aqu铆 la latitud que has obtenido de firebase //
      this.coords.lng = // Aqu铆 la llongitud que has obtenido de firebase //
      this.getAddress(this.coords).then(results=> {
      this.address = results[0][‘formatted_address’];
      }, errStatus => {
      // Aqu铆 ir铆a el c贸digo para manejar el error
      });
      }

      Un saludo

  2. hola eduardo,

    as铆 mismo lo tengo, con una diferencia.. yo quiero mostrar la direcci贸n de n lugares, tengo mi funci贸n getAddress y luego llamo la funci贸n de esta forma

    this.afDB.list(‘local/’).subscribe( local => {
    this.locales=local;
    local.forEach((loc) => {
    this.coords.lat=loc.latitud
    this.coords.lng=loc.longitud

    this.idLocal=loc.key;

    this.getAddress(this.coords).then(results=> {
    this.address = results[0][‘formatted_address’];
    }, errStatus => {
    // Aqu铆 ir铆a el c贸digo para manejar el error
    });
    console.log(this.coords,this.address);
    });

    pero el valor de address me dice unidefined, no entiendo lo que pasa, que estoy haciendo mal

    Saludos

    1. Hola Gamster12, Ya lo siento pero por ese trozo de c贸digo yo tampoco se por que te falla, comprueba que this.coords.lat y this.coords.lng realmente tiene las coordenadas correctamente, si encuentras el error puedes dejarlo en los comentarios para ayudar a otras personas que les pase lo mismo.

      Muchas gracias por comentar

      Un saludo

  3. Hola Eduardo, no eh podido resolverlo, te muestro el c贸digo de mi funci贸n getAddres.

    getAddress(coords):any {

    return new Promise(function(resolve, reject) {
    geocoder.geocode({‘location’: coords} , function (results, status) { // llamado asincronamente
    if (status == google.maps.GeocoderStatus.OK) {
    resolve(results);
    } else {
    reject(status);
    }
    });
    });
    }

    Lo que intento realizar en el c贸digo siguiente jalar informaci贸n de mi base de datos de firebase de la tabla “local” y llamo la funci贸n con el par谩metro this.coords.

    en la tabla de local tengo varios valores, por esa raz贸n uso forEach, y mi intenci贸n es que me genere la direcci贸n de varios lugares en una misma pagina.

    this.afDB.list(鈥榣ocal/鈥).subscribe( local => {
    this.locales=local;
    local.forEach((loc) => {
    this.coords.lat=loc.latitud
    this.coords.lng=loc.longitud

    this.idLocal=loc.key;

    this.getAddress(this.coords).then(results=> {
    this.address = results[0][鈥榝ormatted_address鈥橾;
    }, errStatus => {
    // Aqu铆 ir铆a el c贸digo para manejar el error
    });
    console.log(this.coords,this.address);
    });

    this.coords.lat y this.coords.lng est谩n correctos, imprimo en la imagen en lista las coordenadas.

    disc煤lpame, no tengo mucha habilidad para explicarme, espero puedas ayudarme.

    eso es todo el c贸digo que estoy utilizando.

    1. Hola Gamster12,

      sinceramente no se lo que te puede estar fallando, a priori el c贸digo que muestras parece correcto, siento no poder ayudarte.
      Si das con la soluci贸n puedes comentarlo aqu铆 para que sirva de ayuda a otros.

      Gracias por comentar.

      Un saludo

  4. Buenas tardes

    驴Quisiera saber si las coordenadas que se obtienen con leaflet son iguales a las obtenidas en Google Maps, y adicional como podr铆a obtener la altitud de un punto?

  5. Hola! Muy bueno tu blog. Hace un tiempo estaba siguiendo los tutoriales de ionic y est谩n muy bien explicados! aunque por motivos de trabajo no he tenido tanto tiempo para seguir por ahora. Ahora paso a dejar una pregunta por si me puedes ayudar por favor. Lo que ocurre es que quer铆a hacer una app con videos integrados. Ya hab铆a hecho hace 1 a帽o una hibrida y comprimido bien los videos pero igual segu铆a siendo bien pesada. Mi pregunta es la siguiente. Cu谩l es la funci贸n que se necesita para poder descargar archivos cuando est茅 la aplicaci贸n ya instalada. Me explico, que en apk est茅 solo la parte informativa y si despu茅s quiere el usuario ver un video que lo descargue al celular s贸lo cuando lo necesite en vez de que traiga todo incluido.

    Gracias de antemano

    Saludos!

Deja una respuesta

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *

Este sitio usa Akismet para reducir el spam. Aprende c贸mo se procesan los datos de tus comentarios.