I. Google Map v2 pour Android▲
Google Map v2 pour Android est une API fournie par Google pour pouvoir travailler sur des fonds de cartes. Son installation et utilisation restant très simple.
I-A. Certificat▲
Avant d'enregistrer votre application sur le Google Play Console, il vous faut récupérer/créer votre signature SHA-1, via la commande keytool.
Vous aurez besoin d'une clé lors du développement de votre application et d'une autre lors de la release quand vous aurez signé votre application.
I-A-1. En debug ▲
Linux / OS X
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Windows
keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
I-A-2. Release▲
keytool -list -v -keystore your_keystore_name -alias your_alias_name
Vous obtiendrez alors votre clé SHA-1, votre MD5, ainsi que les paramètres de votre certificat.
I-B. Installation▲
Avant de commencer à utiliser l'API Google Map v2 pour Android vous devrez savoir que cette API nécessite l'installation sur l'appareil de Google Play Service. De ce fait vous aurez également besoin depuis votre code d'ajouter la bibliothèque google play service à votre projet.
Ensuite vous devrez récupérer la clé de l'API depuis Google APIs Console :
- depuis la page Services, vérifier que vous avez sélectionné "Google Maps Android API v2" ;
- ensuite depuis le menu de navigation, cliquez sur API Access ;
- vous trouverez alors un bouton “Create New Android Key”, cliquez dessus ;
- une fenêtre de dialogue apparaîtra vous demandant d'entrer votre signature SHA-1, un point-virgule et le nom de paquet de votre application. Par exemple :
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX;com.android2ee.tileprovider
- vous obtiendrez ensuite une clé générée que vous devrez sauvegarder enfin de l'utiliser depuis votre application.
I-C. Déploiement▲
Pour utiliser Google Map v2 pour Android vous devez avoir certaines informations dans votre application. Nous allons les détailler par type.
I-C-1. Le fichier Manifest▲
Dans le fichier Manifest vous aurez besoin de rajouter ces permissions :
2.
3.
4.
<
uses-permission
android
:
name
=
"
android.permission.INTERNET
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.ACCESS_NETWORK_STATE
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.WRITE_EXTERNAL_STORAGE
"
/
>
<
uses-permission
android
:
name
=
"
com.google.android.providers.gsf.permission.READ_GSERVICES
"
/
>
Sans celles-ci, vous ne pourrez pas utiliser l' API Google Map.
Ensuite il vous sera nécessaire de rajouter également ces méta-données depuis votre application :
- la clé de l' API que vous avez générée préalablement,
- et de spécifier quelle version de la bibliothèque google play service vous utilisez.
2.
3.
4.
5.
6.
7.
<!--
Google
API
Key
-->
<
meta-data
android
:
name
=
"
com.google.android.maps.v2.API_KEY
"
android
:
value
=
"
your_key
"
/
>
<
meta-data
android
:
name
=
"
com.google.android.gms.version
"
android
:
value
=
"
@integer/google_play_services_version
"
/
>
I-C-2. XML▲
Vous pouvez déclarer le composant graphique de Google Map depuis le fichier XML, pour cela il suffit d'intégrer dans le layout souhaité le composant graphique MapFragment (com.google.android.gms.maps.MapFragment) ou SupportMapFragment (com.google.android.gms.maps.SupportMapFragment) si vous êtes pré-HoneyComb ou si vous souhaitez être compatibles toutes versions (bref inclure Gingerbread).
Ce composant graphique est basé sur un Fragment classique d'Android.
Dans cet exemple nous avons pris la SupportMapFragment :
<
fragment
android
:
id
=
"
@+id/map
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
class
=
"
com.google.android.gms.maps.SupportMapFragment
"
/
>
Vous pouvez également charger dynamiquement ce composant graphique.
I-C-3. JAVA▲
l ne vous reste plus qu'à récupérer ce composant graphique depuis votre code JAVA en vue de le configurer pour diverse utilisation. Cela se fait tout simplement :
GoogleMap map =
(
(
SupportMapFragment) getSupportFragmentManager
(
).findFragmentById
(
R.id.map)) .getMap
(
);
De même pour une MapFragment :
GoogleMap map =
(
(
MapFragment) getFragmentManager
(
).findFragmentById
(
R.id.map)) .getMap
(
);
I-C-4. Autre informations▲
Attention il vous sera aussi utile de tester, si le Service de Google Play est disponible :
2.
3.
4.
5.
6.
7.
//
Getting
Google
Play
availability
status
int
status =
GooglePlayServicesUtil.isGooglePlayServicesAvailable
(
getBaseContext
(
));
//
Showing
status
if
(
status!
=
ConnectionResult.SUCCESS){
//
Google
Play
Services
are
not
available
Dialog dialog =
GooglePlayServicesUtil.getErrorDialog
(
status, this
, requestCode);
dialog.show
(
);
}
Cette partie est à réaliser avant de charger la Map, si vous n'arrivez pas à accéder au Service GooglePlay, affichez une information de type Dialog, DialogFragment, bandeau, etc. De plus, il vous est indispensable de proposer à l'utilisateur la documentation de Google sur l'utilisation des cartes. Pour cela le Service GooglePlay vous donne accès à cette documentation via ce code :
GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo
(
context);
Vous pouvez par exemple proposer cette documentation via un menu depuis la barre d'action qui pourra se trouver dans l'overflow ( les trois petits points ). Le but étant que l'utilisateur ai accès à cette documentation depuis l'affichage de la carte sans être intrusif.
I-D. Support des cartes à intégrer▲
Maintenant que nous avons vu comment intégrer Google Map v2 pour Android quel type de support pour nos cartes pouvons nous trouver ?
I-D-1. MbTiles▲
Le format MbTiles est une base de donnée SQLite contenant les tuiles de la carte que nous voulons afficher. Cette base de donnée respecte un schéma suivant :
-
une table metadata
- name: son nom.
- type: overlay ou baselayer
- version: sa version.
- description: une description.
- format: le format des images: png ou jpg
- bounds : le contour en latitude et longitude de la carte étendue. Elle est définie dans le format gauche, bas, droite, haut. Par exemple pour la Terre : -180.0, -85.0, 180.0, 85.0.
- attribution : une chaîne de caractère qui détaillent les données présentent de la carte.
-
une table tiles
- zoom_level : le niveau de zoom demandée
- tile_column : la colonne demandée
- tile_row : la ligne demandée
- tile_data : l'image demandée
Vous pouvez aussi avoir deux tables optionnelles grid et grid data. La documentation ici.
I-D-2. Vector Tiles▲
C'est le support utilisé par Google Maps Android depuis Décembre 2010, cela permet de construire l'image avec des données vectorielles reliées au contour de chaque tuile au lieu d'avoir une image prédéfinie pour celle-ci.
Cela peut être intéressant, si par exemple vous fonctionnez avec un serveur de données, cela allégera les données envoyées par celui-ci, puisque c'est des images vectorielles qui seront envoyées et non le rendu de celles-ci.
I-D-3. Fichier▲
Vous pouvez tout simplement gérer vos tuiles par un système de fichier que vous aurez mis en place. Soit gérer par leur nom ou via un fichier (XML, JSON, etc ..) pour répertorier vos images.
Par exemple juste par le nom :
-
dossier-zoom1
- image-1-1
-
dossier-zoom2
- image-1-1
- image-1-2
- image-2-1
- image-2-2
- etc...
I-D-4. Autres▲
Il existe d'autres supports comme un serveur envoyant les images en fonction des paramètres, etc. Vous pouvez aussi créer vos propres moyens de communication !
II. Fonctionnement▲
II-A. TileOverlays▲
Le TilesOverlays permet de pouvoir intégrer une carte personnalisée dans GoogleMaps, Elle est utile quand nous voulons remplacer l'ensemble de la carte, soit une grande superficie. Si vous ne souhaitez modifier qu'une légère partie, la classe GroundOverlays sera plus intéressante.
TileOverlays : un ensemble d'images;
GroundOverlays : une image.
Comment fonctionne l'affichage de nos propres cartes au sein de Google Map v2 :
L'initialisation se fait par un TileOverlay sur lequel nous associerons une TileProvider qui fournira les images en fonction du zoom, de la colonne et de la ligne voulus. Pour créer ce TileProvider nous aurons besoin d'un TileOverlayOption.
Voici ce que cela donnera dans le code :
GoogleMap map; //
...
get
a
map.
TileProvider tileProvider; //
...
create
a
tile
provider.
TileOverlay tileOverlay =
map.addTileOverlay
(
new
TileOverlayOptions
(
).tileProvider
(
tileProvider));
Suite à l'initialisation, lorsque Google Maps affichera une tuile, celle-ci demandera au TileOverlay la tuile correspondante, si elle n'est pas crée alors une demande au TileProvider sera effectuée pour créer cette tuile. Cette demande s'effectuera par la fonction getTile fournie par le TileProvider.
Il existe deux classes pour gérer les tuiles , soit la TileProvider ou la UrlTileProvider.
La TileProvider fournie comme nous avons vu préalablement la fonction permettant de récupérer la tuile par la fonction getTile. ( la tuile se présentera sous le format Tile qui possède le contenu de l'image à afficher).
La taille : Les tuiles seront de taille de 256 * 256 dp comme indiqué dans la documentation et seront définies par leurs coordonnées ainsi que leur niveau de zoom. Voici un exemple de représentation de ces coordonnées :
Pour le niveau de zoom celui sera défini par défaut à 0 jusqu'à n, il faut voir l'ensemble des tuiles comme une représentation de couches empilées ou chaque couche représente un niveau de zoom supplémentaire. Donc si nous combinons les coordonnées ainsi que le niveau de zoom. Nous aurons le quadruple de tuiles sur le zoom z+1 par rapport au zoom. Les coordonnées redémarre à chaque fois à 0,0 en haut à droite de la carte. Par exemple pour le zoom 0 nous aurons une seule image (1x1), pour le zoom 1, quatre images (2x2), pour le zoom 2 , seize images (4x4), etc...
information :l'UrlTileProvider quant à elle fournit la possibilité de retourner une URL et non l'image depuis la fonction getTile. Cet URL correspondra à l'image à afficher en fonction des paramètres donnés. La requête au serveur et la récupération de l'image se fera automatiquement par la classe UrlTileProvider. Vous avez donc une classe simple vous permettant de récupérer des images sur un serveur.
II-B. Exemple par l'utilisation d'un MbTiles▲
Prenons un exemple pour illustrer ceci.
II-B-1. Le fichier Manifest▲
Tout d'abord le fichier manifest, ou nous allons déclarer les permissions nécessaire à Google Maps, ainsi que les metadatas.
Penser également à inclure le bibliothèque google play service à votre projet !
Tout d'abord le fichier manifest, ou nous allons déclarer les permissions nécessaire à Google Maps, ainsi que les metadatas.
Penser également à inclure le bibliothèque google play service à votre projet !
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
<?
xml
version="1.0"
encoding="utf-8"?
>
<
manifest
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
package
=
"
com.android2ee.tileprovider
"
android
:
versionCode
=
"
1
"
android
:
versionName
=
"
1.0
"
>
<
uses-sdk
android
:
minSdkVersion
=
"
8
"
android
:
targetSdkVersion
=
"
21
"
/
>
<!--
Permissions
for
operation
-->
<
uses-permission
android
:
name
=
"
android.permission.INTERNET
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.ACCESS_NETWORK_STATE
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.ACCESS_WIFI_STATE
"
/
>
<
uses-permission
android
:
name
=
"
android.permission.WRITE_EXTERNAL_STORAGE
"
/
>
<
uses-permission
android
:
name
=
"
com.google.android.providers.gsf.permission.READ_GSERVICES
"
/
>
<
application
android
:
allowBackup
=
"
true
"
android
:
icon
=
"
@drawable/ic_launcher
"
android
:
label
=
"
@string/app_name
"
android
:
theme
=
"
@style/AppTheme
"
>
<
activity
android
:
name
=
"
.MainActivity
"
android
:
label
=
"
@string/app_name
"
>
<
intent-filter
>
<
action
android
:
name
=
"
android.intent.action.MAIN
"
/
>
<
category
android
:
name
=
"
android.intent.category.LAUNCHER
"
/
>
<
/
intent-filter
>
<
/
activity
>
<!--
Google
API
Key
-->
<
meta-data
android
:
name
=
"
com.google.android.maps.v2.API_KEY
"
android
:
value
=
"
AIzaSyDX06H1NUH4gcqcoqPgoxHpw_eEEqZzOPA
"
/
>
<
meta-data
android
:
name
=
"
com.google.android.gms.version
"
android
:
value
=
"
@integer/google_play_services_version
"
/
>
<
/
application
>
<
/
manifest
>
II-B-2. XML▲
Ensuite nous aurons besoin de déclarer le fichier XML que nous allons appeler activity_main.xml. Depuis ce fichier nous devrons ajouter le fragment qui inclura la classe com.google.android.gms.maps.SupportMapFragment qui permettra d'afficher la carte.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
<
FrameLayout
xmlns
:
android
=
"
http://schemas.android.com/apk/res/android
"
xmlns
:
tools
=
"
http://schemas.android.com/tools
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
tools
:
context
=
"
com.android2ee.tileprovider.MainActivity
"
>
<
fragment
android
:
id
=
"
@+id/map
"
android
:
layout_width
=
"
match_parent
"
android
:
layout_height
=
"
match_parent
"
class
=
"
com.google.android.gms.maps.SupportMapFragment
"
/
>
<
/
FrameLayout
>
II-B-3. JAVA▲
II-B-3-a. Le fichier MainActivity▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
/**
*
Main
Activity
*
*/
public
class
MainActivity extends
ActionBarActivity implements
LocationListener, OnCameraChangeListener {
//
Declaration
private
GoogleMap map;
private
MyTileProvider tileProvider;
private
int
minZoom =
-
1
;
private
int
maxZoom =
-
1
;
@
Override
protected
void
onCreate
(
Bundle savedInstanceState) {
@
Override
protected
void
onCreate
(
Bundle savedInstanceState) {
super
.onCreate
(
savedInstanceState);
setContentView
(
R.layout.activity_main);
map =
(
(
SupportMapFragment) getSupportFragmentManager
(
).findFragmentById
(
R.id.map))
.getMap
(
);
//
Init
Map
if
(
map !
=
null
){
//
set
the
type
to
TYPE_NONE
map.setMapType
(
GoogleMap.MAP_TYPE_NONE);
//
create
the
tileProvider
tileProvider =
new
MyTileProvider
(
this
, "
test.mbtiles
"
);
//
Add
the
Provider
in
Map
map.addTileOverlay
(
new
TileOverlayOptions
(
).tileProvider
(
tileProvider));
//
center
Map
LatLngBounds bounds =
tileProvider.getBounds
(
);
CameraUpdate upd =
CameraUpdateFactory.newLatLngBounds
(
bounds,MyTileProvider.TILE_WIDTH , MyTileProvider.TILE_HEIGHT, 0
);
minZoom =
tileProvider.getMinZoom
(
);
maxZoom =
tileProvider.getMaxZoom
(
);
map.setOnCameraChangeListener
(
this
);
map.moveCamera
(
upd);
}
}
@
Override
public
void
onCameraChange
(
CameraPosition position) {
if
(
minZoom !
=
-
1
) {
if
(
position.zoom <
minZoom)
map.animateCamera
(
CameraUpdateFactory.zoomTo
(
minZoom));
}
if
(
maxZoom !
=
-
1
) {
if
(
position.zoom >
maxZoom)
map.animateCamera
(
CameraUpdateFactory.zoomTo
(
maxZoom));
}
}
@
Override
protected
void
onDestroy
(
) {
super
.onDestroy
(
);
//
we
close
the
database
if
(
tileProvider !
=
null
) {
tileProvider.close
(
);
}
}
Pour expliquer les différentes étapes :
- initialisation de la variable map en récupérant le fragment déclaré dans notre fichier XML
map =
(
(
SupportMapFragment) getSupportFragmentManager
(
).findFragmentById
(
R.id.map))
.getMap
(
);
- configuration de la GoogleMap en déclarant son type de carte à MAP_TYPE_NONE
map.setMapType
(
GoogleMap.MAP_TYPE_NONE);
- création de notre TileProvider en donnant le nom du MbTiles présent dans les assets.
tileProvider =
new
MyTileProvider
(
this
, "
test.mbtiles
"
);
Nous verrons par la suite que nous copierons la base de donnée présente dans les assets dans l'espace interne de l'application.
- ajout de ce TileProvider dans la GoogleMap
map.addTileOverlay
(
new
TileOverlayOptions
(
).tileProvider
(
tileProvider));
- positionnement de la Google Map en fonction des tuiles présentes dans le MbTiles
LatLngBounds bounds =
tileProvider.getBounds
(
);
CameraUpdate upd =
CameraUpdateFactory.newLatLngBounds
(
bounds,MyTileProvider.TILE_WIDTH , MyTileProvider.TILE_HEIGHT, 0
);
map.moveCamera
(
upd);
- limitation des zooms en fonction des tuiles présentes dans le MbTiles
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
@
Override
protected
void
onCreate
(
Bundle savedInstanceState) {
...
minZoom =
tileProvider.getMinZoom
(
)
maxZoom =
tileProvider.getMaxZoom
(
);
map.setOnCameraChangeListener
(
this
);
…
}
@
Override
public
void
onCameraChange
(
CameraPosition position) {
if
(
minZoom !
=
-
1
) {
if
(
position.zoom <
minZoom)
map.animateCamera
(
CameraUpdateFactory.zoomTo
(
minZoom));
}
if
(
maxZoom !
=
-
1
) {
if
(
position.zoom >
maxZoom)
map.animateCamera
(
CameraUpdateFactory.zoomTo
(
maxZoom));
}
}
II-B-3-b. Le fichier MyTileProvider▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
/**
*
Tile
Provider
*
*/
public
class
MyTileProvider implements
TileProvider, Closeable {
//
declaration
public
static
final
int
TILE_WIDTH =
256
;
public
static
final
int
TILE_HEIGHT =
256
;
private
Context mContext;
private
DatabaseHelper dbHelper;
public
MyTileProvider
(
Context context, String name) {
super
(
);
mContext =
context;
//
load
bd
openTiles
(
name);
}
/**
*
Load
Db
*
@param
name
*/
private
void
openTiles
(
String
name
)
{
dbHelper
=
new
DatabaseHelper
(
mContext
,
name
)
;
try
{
dbHelper
.
createDatabase
(
)
;
dbHelper
.
openDataBase
(
)
;
}
catch
(
IOException
e
)
{
Log
.
e
(
getClass
(
)
.
getName
(
)
,
e
.
getMessage
(
)
)
;
}
}
@Override
public
Tile
getTile
(
int
x
,
int
y
,
int
zoom
)
{
Tile
result
=
null
;
/
/
convert
column
due
to
te
mbtile
int
column
=
(
(
int
)
(
Math
.
pow
(
2
,
zoom
)
-
y
)
-
1
)
;
/
/
call
request
in
db
Cursor
data
=
dbHelper
.
getTile
(
x
,
column
,
zoom
)
;
if
(
data
!
=
null
)
{
if
(
data
.
moveToFirst
(
)
)
{
/
/
get
the
bitmap
int
clmnindex
=
data
.
getColumnIndex
(
"
tile_data
"
)
;
byte
[
]
img
=
data
.
getBlob
(
clmnindex
)
;
result
=
new
Tile
(
TILE_WIDTH
,
TILE_HEIGHT
,
img
)
;
}
data
.
close
(
)
;
}
if
(
result
=
=
null
)
{
/
/
load
an
empty
image
result
=
NO_TILE
;
}
return
result
;
}
/
*
*
*
Get
Bounds
of
map
present
in
db
*
@return
*/
public
LatLngBounds getBounds
(
) {
LatLngBounds result =
null
;
Cursor data =
dbHelper.getBoundsMap
(
);
if
(
data !
=
null
) {
if
(
data.moveToFirst
(
)) {
int
columnIndex =
data.getColumnIndex
(
"
value
"
);
String value =
data.getString
(
columnIndex);
String[] values =
value.split
(
"
,
"
);
LatLng southwest =
new
LatLng
(
Double.parseDouble
(
values[1
]), Double.parseDouble
(
values[0
]));
LatLng northeast =
new
LatLng
(
Double.parseDouble
(
values[3
]), Double.parseDouble
(
values[2
]));
result =
new
LatLngBounds
(
southwest, northeast);
}
data.close
(
);
}
return
result;
}
/**
*
Get
Min
Zoom
present
in
db
*
@return
*/
public
int
getMinZoom
(
) {
Integer result =
-
1
;
Cursor data =
dbHelper.getMinZoom
(
);
if
(
data !
=
null
) {
if
(
data.moveToFirst
(
)) {
int
columnIndex =
data.getColumnIndex
(
"
value
"
);
result =
data.getInt
(
columnIndex);
}
data.close
(
);
}
return
result;
}
/**
*
Get
Max
Zoom
present
in
db
*
@return
*/
public
int
getMaxZoom
(
) {
Integer result =
-
1
;
Cursor data =
dbHelper.getMaxZoom
(
);
if
(
data !
=
null
) {
if
(
data.moveToFirst
(
)) {
int
columnIndex =
data.getColumnIndex
(
"
value
"
);
result =
data.getInt
(
columnIndex);
}
data.close
(
);
}
return
result;
}
@
Override
public
void
close
(
) {
if
(
dbHelper !
=
null
) {
dbHelper.close
(
);
}
}
@
Override
protected
void
finalize
(
) throws
Throwable {
super
.finalize
(
);
//
try
it.
close
(
);
}
}
Pour expliquer les étapes :
- ouverture du MbTiles
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
*
Load
Db
*
@param
name
*/
private
void
openTiles
(
String
name
)
{
dbHelper
=
new
DatabaseHelper
(
mContext
,
name
)
;
try
{
dbHelper
.
createDatabase
(
)
;
dbHelper
.
openDataBase
(
)
;
}
catch
(
IOException
e
)
{
Log
.
e
(
getClass
(
)
.
getName
(
)
,
e
.
getMessage
(
)
)
;
}
}
- récupération des tuiles
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
@
Override
public
Tile getTile
(
int
x, int
y, int
zoom) {
Tile result =
null
;
//
convert
column
due
to
the
mbtile
int
column =
(
(
int
) (
Math.pow
(
2
, zoom) -
y) -
1
);
//
call
request
in
db
Cursor data =
dbHelper.getTile
(
x, column, zoom);
if
(
data !
=
null
) {
if
(
data.moveToFirst
(
)) {
//
get
the
bitmap
int
clmnindex =
data.getColumnIndex
(
"
tile_data
"
);
byte
[] img =
data.getBlob
(
clmnindex);
result =
new
Tile
(
TILE_WIDTH, TILE_HEIGHT, img);
}
data.close
(
);
}
if
(
result =
=
null
) {
//
load
an
empty
image
result =
NO_TILE;
}
return
result;
}
NO_TILE permet de préciser que vous n'avez pas de carte associée à la demande. Cela permettra à GoogleMap de ne pas vous redemander la carte si cette tuile est ré-affichée.
Pour associer les valeurs x et y demandées par GoogleMap avec les colonnes et lignes présentes dans le MbTiles il vous faudra appliquer cette formule de conversion int column = ((int) (Math.pow(2, zoom) - y) - 1); !
- création des requêtes pour accéder au MbTiles
Les fonctions associées au MbTiles comme getMaxZoom, getMinZoom, getBounds, etc...
- fermeture du MbTiles
2.
3.
4.
5.
6.
@
Override
public
void
close
(
) {
if
(
dbHelper !
=
null
) {
dbHelper.close
(
);
}
}
II-B-3-c. Le fichier DataBaseHelper▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
/**
*
The
DataBase
Helper
*
*/
public
class
DatabaseHelper extends
SQLiteOpenHelper {
//
TODO
change
it
private
static
final
String DB_SUBPATH =
"
/databases/
"
;
private
String dbPath;
private
String dbName;
private
static
final
Integer DB_VERSION =
1
;
private
SQLiteDatabase myDataBase;
private
final
Context myContext;
public
DatabaseHelper (
Context context, String name) {
super
(
context, name, null
, DB_VERSION);
this
.myContext =
context;
this
.dbName =
name;
this
.dbPath =
context.getFilesDir
(
).getAbsolutePath
(
) +
DB_SUBPATH;
File file =
new
File
(
dbPath);
if
(
!
file.exists
(
)) {
file.mkdirs
(
);
}
}
/**
*
Create
database
*
@param
name
*
@throws
IOException
*/
public
void
createDatabase
(
)
throws
IOException
{
boolean
isExist
=
isDatabaseExist
(
)
;
if
(
!
isExist
)
{
this
.
getReadableDatabase
(
)
;
try
{
copyDataBase
(
)
;
}
catch
(
IOException
e
)
{
throw
new
Error
(
"
Error
copying
database
"
)
;
}
}
}
/
*
*
*
CopyDataBase
from
Asset
to
the
internal
storage
*
@param
name
*
@throws
IOException
*/
private
void
copyDataBase
(
)
throws
IOException
{
/
/
Open
your
local
db
as
the
input
stream
InputStream
myInput
=
myContext
.
getAssets
(
)
.
open
(
dbName
)
;
/
/
Path
to
the
just
created
empty
db
String
outFileName
=
dbPath
+
dbName
;
/
/
Open
the
empty
db
as
the
output
stream
OutputStream
myOutput
=
new
FileOutputStream
(
outFileName
)
;
/
/
transfer
bytes
from
the
inputfile
to
the
outputfile
byte
[
]
buffer
=
new
byte
[
1024
]
;
int
length
;
while
(
(
length
=
myInput
.
read
(
buffer
)
)
>
0
)
{
myOutput
.
write
(
buffer
,
0
,
length
)
;
}
/
/
Close
the
streams
myOutput
.
flush
(
)
;
myOutput
.
close
(
)
;
myInput
.
close
(
)
;
}
/
*
*
*
Test
if
the
database
already
exist
*
@return
*/
private
boolean
isDatabaseExist
(
) {
SQLiteDatabase control =
null
;
try
{
String myPath =
dbPath +
dbName;
control =
SQLiteDatabase.openDatabase
(
myPath, null
, SQLiteDatabase.OPEN_READONLY);
}
catch
(
SQLiteException e) {
control =
null
;
}
if
(
control !
=
null
) {
control.close
(
);
}
return
control !
=
null
? true
: false
;
}
/**
*
Open
DataBase
*
@throws
SQLException
*/
public
void
openDataBase
(
)
throws
SQLException
{
/
/
Open
the
database
String
myPath
=
dbPath
+
dbName
;
myDataBase
=
SQLiteDatabase
.
openDatabase
(
myPath
,
null
,
SQLiteDatabase
.
OPEN_READWRITE
)
;
}
/
*
*
*
Get
Tile
in
Database
*
@param
row
:
the
row
*
@param
column
:
the
column
,
*
@param
zoom
:
the
zoom
*
@return
the
byte
[
]
*/
public
Cursor getTile
(
int
row, int
column, int
zoom) {
return
myDataBase.query
(
"
tiles
"
, new
String[] {
"
tile_data
"
}
, "
tile_row
=
?
AND
tile_column
=
?
AND
zoom_level
=
?
"
,
new
String[]{
Integer.toString
(
column), Integer.toString
(
row), Integer.toString
(
zoom)}
, null
, null
, null
);
}
/**
*
Get
Bounds
present
in
db
*
@return
*/
public
Cursor getBoundsMap
(
) {
return
myDataBase.query
(
"
metadata
"
, new
String[] {
"
value
"
}
, "
name
like
\"bounds\"
"
, null
, null
, null
, null
);
}
/**
*
get
Min
Zoom
present
in
db
*
@return
*/
public
Cursor getMinZoom
(
) {
return
myDataBase.query
(
"
metadata
"
, new
String[] {
"
value
"
}
, "
name
like
\"minzoom\"
"
, null
, null
, null
, null
);
}
/**
*
get
Max
Zoom
present
in
db
*
@return
*/
public
Cursor getMaxZoom
(
) {
return
myDataBase.query
(
"
metadata
"
, new
String[] {
"
value
"
}
, "
name
like
\"maxzoom\"
"
, null
, null
, null
, null
);
}
@
Override
public
synchronized
void
close
(
) {
if
(
myDataBase !
=
null
)
myDataBase.close
(
);
super
.close
(
);
}
@
Override
public
void
onCreate
(
SQLiteDatabase db) {
}
@
Override
public
void
onUpgrade
(
SQLiteDatabase db, int
oldVersion, int
newVersion) {
}
}
Cette classe reste une classe simple implémentant une base de donnée existante, et permettant d'effectuer des requêtes SQLite sur celle-ci.
Nous trouverons alors les requêtes sur la table metadata comme la récupération des contours, du zoom maximum, du zoom minimum , etc…, ainsi que la requêtes sur la récupération des tuiles sur la table tile_data.
La base de donnée présente dans les assets sera copiée en interne via cette fonction copyDataBase(); De ce fait cet exemple ne marche seulement qu'avec une base de donnée déjà existante depuis le dossier assets du projet, auquel nous aurons passé le nom du fichier lors de la création de cette classe.
II-B-3-d. Le rendu▲
II-C. GroundOverlays▲
Le GroundOverlays permet de modifier des aires très légère sur la carte de Google Maps normale ou personnalisée. Cela vous permettra de pouvoir ajouter une image de fond simple sur une petite surface pour avoir un rendu personnalisé. Attention seule une image de fond peut être ajoutée sur l'ensemble de la carte. Son utilisation est très simple. Il n'y a pas de gestion de ressource comme pour les TileOverlay, seule une initialisation avec les données de l'image personnalisée à afficher est nécessaire.
2.
3.
4.
5.
LatLng TOULOUSE =
new
LatLng
(
43
.614086
, 1
.428697
);
GroundOverlayOptions groundMap =
new
GroundOverlayOptions
(
)
.image
(
BitmapDescriptorFactory.fromResource
(
R.drawable.background))
.position
(
TOULOUSE, 8600f
, 6500f
);
map.addGroundOverlay
(
groundMap);
Ne pas oubliez de passer la carte en MAP_TYPE_NONE si vous êtes sur une carte personnalisée ou bien MAP_TYPE_NORMAL, si vous êtes sur une carte de google.
image : L'image qui se positionnera sur les tuiles de la carte.
position : La position de l'image , LatLng (latitude, longitude), largeur, hauteur.
Le GroundOverlay positionne une image sur la carte définie par sa position en Latitude, longitude ainsi que la hauteur et largeur, que nous avons défini lors de la construction de cet objet.
II-C-1. Le rendu▲
center ce que nous obtenons :
III. Conclusion▲
La possibilité de pouvoir insérer nos propres cartes ou une seule image, permet de pouvoir gérer nos propres images de carte de fond (possibilité d'afficher/cacher des informations spécifiques sur la carte) ainsi que l'affichage des données en mode hors-ligne . Tout en ayant les optimisations apportées par Google Map sur l'affichage des tuiles sous Android. L'affichage de la carte reste très fluide.
Sa mise en place est très simple, juste la gestion de la taille des données reste problématique sur la possibilité de stocker un ensemble important sur l'appareil, il vous faudra effectuer des choix sur le zoom maximum et minimum ainsi que sur la zone de couverture de votre carte.
Le projet sous github : https://github.com/ffournier/TileProvider/