Formato MBTiles
El formato MBTiles es una base de datos SQLite, que se utiliza para guardar mapas. El mapa puede ser de tipo raster o vectorial. En esta sección explicaré la estructura de datos (tablas) y valores para obtener los mapas de tipo raster.
El mapa puede tener varios zooms, y cada zoom está compuesto de imágenes cuadradas de 256 pixeles (256 pixeles de alto X 256 pixeles de ancho). Estas imágenes se les denominan tiles (azulejos).
No es necesario tener un access para lo que se va a contar a continuación, pero las capturas de pantalla son obtenidas del SQLiteStudio (https://sqlitestudio.pl/).
Tablas
MBTiles puede tener varias tablas, para obtener las imágenes raster sólo nos interesan 2 tablas: METADATA y TILES.
Tabla METADATA
Contiene información básica del mapa.
Esta tabla consta de 2 campos. NAME y VALUES.
- NAME
- Es el nombre de una propiedad
- VALUES
- Es el valor de dicha propiedad.
Algunas propiedades
Obligatorias:
- name
- Nombre del mapa.
- format
- Formato de las imágenes (tiles). (pbf, jpg, png,…)
Recomendadas (por lo que algunos ficheros pueden no tener dichas propiedades):
- bounds
Define el perímetro del mapa. Las coordenadas LONGITUD, LATITUD están representadas en WGS84 y separadas por comas. Las coordenadas son izquierda, abajo, derecha, arriba.
Por ejemplo la tierra completa sería -180.0,-85,180,85.
Las coordenadas oeste y sur son negativas y las coordenadas este y norte son positivas.
- minzoom
- Zoom mínimo del mapa.
- maxzoom
- Zoom máximo del mapa.
Tabla TILES
Contiene las imágenes para formar el mapa.
Esta tabla consta de 4 campos:
- zoom_level
- Zoom al que pertenece el tile.
- tile_column
- Columna del tile.
- tiel_row
- Fila del tile
- tile_data
- Imagen en el formato definido la propiedad FORMAT de la tabla METADATA.
Partiendo del fichero countries-raster.mbtiles. Podemos ver las 2 tablas comentadas anteriormente.
Haciendo la siguiente SQL sobre la tabla metadata.
SELECT * FROM metadata
Obtenemos los siguientes valores.
attribution center 0,0,2 format png description bounds -180,-85.738076382392,180,84.79842793857 minzoom 0 maxzoom 6 name Countries
Vemos que el mapa tiene definidos 7 zooms (minzoom=0 maxzoom=6). El zoom mínimo no tiene porque empezar en 0.
Debido a que minzoom y maxzoom no son unas propiedades obligatorias, es mejor obtener los zooms que contiene un fichero a través de la tabla TILES con la siguiente SQL.
SELECT DISTINCT zoom_level FROM tiles;
O
SELECT MIN(zoom_level) FROM tiles; SELECT MAX(zoom_level) FROM tiles;
Las imágenes (tiles) son del formato png.
Los límites del mapa van desde la longitud 180 O, latitud 85.738076382392 S, a la longitud 180 E, latitud 85. 79842793857 N.
Si hacemos una SQL para obtener los tiles del zoom 1.
SELECT * FROM tiles WHERE zoom_level=1
Vemos que el zoom 1 está formado por 4 tiles.
Podemos ver el tile_data haciendo doble clic.
Por ejemplo, el tile de la columna 0 fila 0.
Si vamos a la pestaña imagen. Vemos la imagen del tile.
El zoom 1 del mapa estaría formado por los siguientes tiles.
Vemos como el primer tile 0,0 está en la parte inferior izquierda de la ventana y el último tile 1,1 está en la parte superior derecha, es decir el origen de coordenadas está en la parte inferior izquierda, al revés de cómo se dibuja en las pantallas de los ordenadores donde el origen de coordenadas está en la parte superior izquierda.
No siempre los mapas están tan bien definidos, podemos encontrarnos cosas tan curiosas como estas.
Donde se puede apreciar que no hay tiles en las filas 316,315, de la columna 254
Referencias
Especificación del formato MBTiles
https://github.com/mapbox/mbtiles-specDibujar/Ver MBTiles
En esta sección partiendo de los ficheros (CMarco.cpp y CFicheroMBTiles.cpp), voy a intentar explicar, cómo utilizar la clase CFicheroMBTiles (CFicheroMBTiles.h, CFicheroMBTiles.cpp) para visualizar un fichero MBTiles.
No va a ser una disección sobre la implementación de dicha clase y de cómo está implementada ni una explicación pormenorizadas de sus funciones y algoritmos.
El entorno de desarrollo en que se han desarrollado estos fuentes ha sido en eclipse para el framework wxWidgest.
Quien quiera utilizar el proyecto, deberá indicar donde están el compilador, los includes, las librerías de wxWidgets y de SQLite. Esto depende de la instalación que de cada uno.
Primero indicaremos donde está el compilador de C/C++.
En propiedades del proyecto(Project->Properties) en C/C++Build->Enviorement.
La variable MINGW_HOME deberá apuntar a la ubicación de nuestro compilador.
En mi caso utilizo mingw.
El siguiente paso, en indicar donde están las librerías, includes, etc.
En C/C++Build->Settings -> GCC C++ Compiler.
Includes.
Indicaremos dónde están los includes (include paths e includes files) para wxWidgets, que debe de apuntar en nuestra instalación de wxWidgest.
En mi caso es:
D:\Lenguajes\wxWidgets-3.1.0\lib\gcc_lib32_4_8_1\mswu
D:\Lenguajes\wxWidgets-3.1.0\include
Para SQLite.
D:\herramientas\db\sqlite\v3.55.5\fuentes
Include files
D:\Lenguajes\wxWidgets-3.1.0\include\wx\bitmap.h
Por último habrá que definir donde están las librerías (C/C++Build->Settings -> MinGW C++ Linker).
En mi caso las librerías están en en D:\Lenguajes\wxWidgets-3.1.0\lib\gcc_lib32_4_8_1 para wxWidges y en D:\herramientas\db\sqlite\v3.55.5\lib\lib32bits, para SQLite.
Una vez hechos estos pasos ya podemos compilar el proyecto.
Los fuentes (app.h,app.cpp, CMarco.h y CMarco.cpp) son los fuentes que implementa la aplicación (app) y la ventana (CMarco).
Los fuentes CFicheroMBTiles.h y CFicheroMBTiles.cpp implementan la clase CFicheroMBTiles y no son dependientes del framewok wxWidgest, por lo que se podría usar Windows o las MFCs de Windows o cualquier otro framework.
Lo primero será instanciar la clase CFicheroMBtiles.
CMarco.h
CFicheroMBTiles mFileTiles;
Después habrá que cargar el fichero MBTiles (CFicheroMBTiles.leerFichero). Esta función no carga los tiles, esta función básicamente lee la tabla METADATA para obtener información del fichero.
CMarco.cpp
CMarco::onAbrirFichero
lchEstado=mFileTiles.leerFichero((const char*)lstrPath.c_str());
Si todo ha ido correctamente la variable lchEstado tendrá el valor 0, un valor inferior a 0 será un error.
Obtenemos el número de zooms (CFicheroMBTiles.obtNumZooms) que tiene el fichero y generar un menú por cada zoom del fichero.
CMarco.cpp
CMarco::crearMenuItemZooms
luiNumZooms=mFileTiles.obtNumZooms();
En la clase CFicheroMBTiles, guardamos en un vector los zooms que forma el mapa.
Para obtener un determinado zoom accederemos a través el índice del vector (CFicheroMBTiles.obtInfoPosZoom). Dicho índice va desde 0 (min zoom) hasta Número de Zooms-1 (max zoom).
liZoom=mFileTiles.obtInfoPosZoom(luiIndi);
Una vez seleccionado un zoom (desde el menú de zooms), podremos cargar los tiles que forma dicho zoom en memoria (CFicheroMBTiles.cargarTiles).
CMarco.cpp
CMarco:: onCargarTile
mFileTiles.cargarTiles(miZoom,&luiNumTilesZoom)
En la variable luiNumTilesZoom devuelve el número de tiles que forma un determinado zoom.
Ahora en la clase CFicheroMBTiles, tenemos guardados los tiles del zoom que hemos cargado.
Estos tiles debemos de pasarlos a un bitmap, que será lo que dibujará nuestra aplicación.
Para hacer esto, primero definimos la estructura de datos (typdef(imagen) y un vector) que está definida por una estructura imagen. Donde guardaremos la posición x,y donde se dibujara el bitmap (tile), y la imagen del tile.
CMarco.h
typedef struct { // guardamos la posicion x, y, donde se dibujara del bitmap wxPoint posBitmap; wxBitmap *graf; } imagen;
Los tiles pasados a bitmap, los guardamos un vector (mVecBitmap).
std::vectormVecBitmap;
Una vez hemos cargado los tiles para un determinado zoom (CFicheroMBTiles.cargarTiles) debemos de pasar esos tiles a bitmaps, por lo que llamaremos a la función CMarco.tilesToAzulejos.
CMarco.cpp
CMarco::onCargarTile
if (luiNumItems>0) tilesToAzulejos();
Básicamente esta función se encargará de obtener los tiles cargados a través de la función CFicheroMBTiles.obtTileByIndice.
CMarco.cpp
CMarco::tilesToAzulejos
Tile=mFileTiles.obtTileByIndice(luiIndi,&lchEstado);
Y “traducirlo” a un bitmap a través de la función CMarco.creaBitmap
CMarco.cpp
CMarco::tilesToAzulejos
lpBitmap=creaBitmap(lTile.pRawImg,lTile.sizeBytes,liFila,liColumna);
Pasamos los datos de la imagen (tile) a un bitmap.
CMarco.cpp
CMarco::creaBitmap
Utilizando la clase wxMemoryInputStream, donde se le pasara los datos de la imagen(ppRaw) y el tamaño de la misma en bytes (piSize).
wxMemoryInputStream lMemory(ppRaw,piSize);
Después utilizaremos una clase wxImage, para cargar esos datos.
wxImage lImagen; lImagen.LoadFile(lMemory,wxBITMAP_TYPE_JPEG);
Para finalizar pasamos dicha wxImage a un wxBitmap.
wxBitmap *lpBitmap; lpBitmap=new wxBitmap(lImagen);
Este bitmap, será el que devuelva la función creaBitmap (CMarco.creaBitmap).
¿Cómo hacer esto con windows?
Primero creamos un HGLOBAL
HGLOBAL lhMem
Asignamos memoria.
lhMem=GlobalAlloc(GMEM_MOVEABLE, tamañoBytesImagen);
Ahora debemos de pasar(copiar) los datos de la imagen a la memoria HGLOBAL
lhMem=(HGLOBAL)GlobalLock(lhMem); CopyMemory(lhMem, datosImagen, tamañoBytesImagen); GlobalUnlock(lhMem);
Esta información debemos de pasarla a un ISTREAM.
IStream *lpStream; CreateStreamOnHGlobal(lhMem,FALSE,&lpStream);
Y el último paso, pasar el IStream a un CImagen.
CImage* lCImagen; lCImagen=new CImage(); lCImagen->Load(lpStream);
Una vez hecho esto, liberamos memoria.
lpStream->Release(); GlobalFree(lhMem);
La función sería algo como esto.
CImage* createImagen(unsigned char *ppuchBytes, unsigned int puiSize, char *pchEstado) { CImage* lResult; HGLOBAL lhMem; IStream *lpStream; HRESULT lEstado; lResult=new CImage(); //creamos la memoria lhMem=GlobalAlloc(GMEM_MOVEABLE,puiSize); if (lhMem==NULL) { TRACE("CRaster::cargaArray -> Error al ejecutar GlobalAlloc\n"); *pchEstado=-1; return lResult; } //pasamos los bytes de la imagen a la memoria creada lhMem=(HGLOBAL)GlobalLock(lhMem); if (lhMem==NULL) { TRACE("Error al ejecutar GlobalLock\n"); *pchEstado=-2; return lResult; } CopyMemory(lhMem,ppuchBytes,puiSize); GlobalUnlock(lhMem); //creamos el ISTrema lEstado=CreateStreamOnHGlobal(lhMem,FALSE,&lpStream); switch (lEstado) { case E_INVALIDARG: { TRACE("Error al ejecutar CreateStreamOnHGlobal:E_INVALIDARG \n"); GlobalFree(lhMem); *pchEstado=-3; return lResult;; } case E_OUTOFMEMORY: { TRACE("Error al ejecutar CreateStreamOnHGlobal:E_OUTOFMEMORY \n"); GlobalFree(lhMem); *pchEstado=-4; return lResult; } } //cargamos los bytes en CImagen lEstado=lResult->Load(lpStream); if (lEstado!=S_OK) { lpStream->Release(); GlobalFree(lhMem); TRACE("Error al ejecutar m_ImagenRaster.Load\n"); *pchEstado=-5; return lResult; } lpStream->Release(); GlobalFree(lhMem); *pchEstado=0; return lResult; }
Volvemos a wxWidgets.
Una vez creado el bitmap, lo guardamos en el vector de azulejos (mVecBitmap).
Recordar que ahora la aplicación tiene los tiles en memoria (bitmaps) y el objeto CFicheroMBTiles también tiene los mismos tiles. Debemos de liberar memoria, borrando los tiles del objeto CFicheroMBTiles. Por lo que utilizaremos la función CFicheroMBTiles.borrarTiles.
CMarco.h
CMarco::tilesToAzulejos
mFileTiles.borrarTiles();
Por último los tiles se dibujaran, en la función CMarco.onPaint.
CMarco.h
CMarco::onPaint
wxPaintDC lDC(this);
Obtenemos el número de tiles a dibujar.
luiNumTiles=mVecBitmap.size();
Por cada tile.
lImg=mVecBitmap[luiIndi]; lDc.DrawBitmap(*lImg.graf,lImg.posBitmap,false);
Aplicación visorMBTiles:
Es un visor muy simple y solo se ha hecho para mostrar los conceptos básicos de cómo utilizar la clase CFicheroMBTiles.
Tiene muchas carencias como por ejemplo dibujará todos los tiles aunque no se vayan a mostrar en la ventana (los mapas suelen tener un tamaño bastante grande).
lDc.DrawBitmap(*lImg.graf,lImg.posBitmap,false);
También se podría mejorar la clase CFicheroMBTiles, para que solo cargue los tiles que vamos a visualizar y no cargar TODOS los tiles de un determinado zoom.
En el visor tenemos 2 opciones de menú:
Abrir MBTile:
EVT_MENU(ID_MENU_OPEN_FILE, CMarco::onAbrirFichero)
Abrir MBTile Perímetro:
EVT_MENU(ID_MENU_OPEN_FILE_PERIMETRO, CMarco::onAbrirFicheroPerimetro)
Las dos opciones hacen lo mismo, cargar el fichero. Pero si utilizamos Abrir MBTile Perímetro, podremos ver el perímetro de los tiles que forman un determinado zoom, junto con su columna y fila.
Descarga de ficheros
Fuentes del programa visor:
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
visorMBTiles32 | visorMBTiles32.zip | 2024-01-28 | 17433 bytes | 366 |
Mapas de ejemplo (countries-raster):
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Mapas | countries-raster.zip | 2024-01-28 | 7038978 bytes | 298 |
Referencias.
https://github.com/pedro-vicente/render_mbtiles.
https://github.com/pedro-vicente/lib_mbtiles.
Mapas en formato MBTiles.
http://centrodedescargas.cnig.es/CentroDescargas/loadMapMov.do.