Avaliação das APIs de Mapas para o Android

On 8 de agosto de 2012 by Johnnatan Messias

Para este estudo foi escolhido os seguintes mapas:

  • Google Maps
  • OpenStreetMaps
  • Bing via Javascript
  • Bing com o OpenStreetMaps

Para exemplificar a visualização das funcionalidades dispostas em cada uma das APIs, foi elaborado a tabela abaixo:

RecursosGoogle Maps OpenStreetMapBingOSMapsBing
BússolaNãoSimSimNão
CacheNãoSimSimNão
GPSSimSimSimNão
Mapa AtualizadoSimNãoSimSim
Necessidade de KeySimNãoSimSim
OverlaysSimSimSim-----
RotasSimÀ PesquisarÀ Pesquisar-----
SatéliteSimPoucos LevelsSimSim
ZoomSimSimSimSim

Como principais características, temos:

  • Google Maps: Como verificamos na tabela acima podemos notar que o Google Maps não permite armazenamento dos dados de mapas em cache uma vez que vai de desacordo com a licença de uso do Google. Através disso, utilizar o Google Maps para mapas no laboratório não seria viável uma vez que teremos que diminuir o máximo possível do tráfego de dados da rede para economia de bateria. Assim, como não há o armazenamento em cache não poderemos reaproveitar os dados que já foram baixados anteriormente pelo usuário.
  • OpenStreetMaps: Uma solução que permite armazenamento em cache, o que é realmente útil para o laboratório. Ainda, possui total abstrações de implementações que, no Google Maps, demandaria muito tempo de implementação. Dentre as opções de abstrações, podemos considerar:
    • GPS: Bastando, apenas, chamar o método enableMyLocation();
    • Bússola: Bastando, apenas, chamar o método enableCompass();
    • Modo OffLine
  • Bing via Javascript: Roda mediante um WebView (HTML e Javascript), causando um delay e desconforto para o usuário.
  • Bing com o OpenStreetMaps: Através de uma biblioteca pode-se utilizar o mapa do Microsoft Bing na biblioteca do OpenStreetMaps de modo que o usuário possa utilizar todos os conceitos e abstrações que o OpenStreetMaps permite, porém com um diferencial, exibindo, como mapa, o Bing.
Implementações:
Abaixo segue as implementações resultante do BingOSMaps:
MapView
Para sobrescrever alguns métodos da Classe org.osmdroid.views.MapView foi preciso criar o código MapView. Logo, essa será o a classe utilizada por todo o desenvolvimento.

[sourcecode language=”java”]
public class MapView extends org.osmdroid.views.MapView {
public MapView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public int getMinZoomLevel() {
return 3;
}

@Override
public int getMaxZoomLevel() {
return 19;
}
}
[/sourcecode]

MyPoint

A classe MyPoint serve para conversão de dados em localização.

[sourcecode language=”java”]
public class MyPoint extends GeoPoint {
private static final long serialVersionUID = -4564125501085882978L;

// Valores em graus * 1E6
public MyPoint(int latitudeE6, int longitudeE6) {
super(latitudeE6, longitudeE6);
}

// Converte para "graus * 1E6"
public MyPoint(double latitude, double longitude) {
this((int) (latitude * 1E6), (int) (longitude * 1E6));
}

// Cria baseado no objeto ‘Location’ diretamente recebido do GPS
public MyPoint(Location location) {
this(location.getLatitude(), location.getLongitude());
}

public MyPoint(GeoPoint p) {
this(p.getLatitudeE6(), p.getLongitudeE6());
}

// Cria baseado em um endereço
public MyPoint(Address endereco) {
this(endereco.getLatitude(), endereco.getLongitude());
}

// Cria baseado em um endereço
public MyPoint(MyAddress address) {
this(address.getLatitude(), address.getLongitude());
}

@Override
public int getLatitudeE6() {
return super.getLatitudeE6();
}

@Override
public int getLongitudeE6() {
return super.getLongitudeE6();
}

public double getLatitude() {
return super.getLatitudeE6() / 1000000;
}

public double getLongitude() {
return super.getLongitudeE6() / 1000000;
}

public static void get(Context context, double latitude, double longitude) {
Geocoder gc = new Geocoder(context, Locale.US);
List< Address > addresses = null;
try {
addresses = gc.getFromLocation(latitude, longitude, 10);
for (Address address : addresses) {
System.out.println(address.getFeatureName());
}

addresses = gc.getFromLocationName(
"Rua Carlos Benato, 5 , Curtiiba", 10);
for (Address address : addresses) {
System.out.println(address.getFeatureName());
}
} catch (IOException e) {
System.out.println("Deu erro o geo coder – " + e.getMessage());
}
}

public static List< Address > getA(Context context, double latitude,
double longitude) {
Geocoder gc = new Geocoder(context, Locale.US);
List< Address > addresses = null;
try {
addresses = gc.getFromLocation(latitude, longitude, 10);
} catch (IOException e) {
System.out.println("Deu erro o geo coder – " + e.getMessage());
}
return addresses;
}

public static List< Address > getA(Context context, String rua) {
Geocoder gc = new Geocoder(context, Locale.US);
List< Address > addresses = null;
try {
addresses = gc.getFromLocationName(rua, 10);
return addresses;
} catch (IOException e) {
System.out.println("Deu erro o geo coder – " + e.getMessage());
}
return addresses;
}

public List< Address > getEndereco(Context context) {
return getA(context, getLatitude(), getLongitude());
}

public static MyPoint getFromRua(Context context, String rua) {
List< Address > endereco = MyPoint.getA(context, rua);
Address eOrigem = endereco.get(0);
MyPoint c = new MyPoint(eOrigem);
return c;
}
}
[/sourcecode]

ResourceProxyImpl

Classe responsável por alterar o Drawable dos recursos do Mapa. Os apontadores e overlays.

[sourcecode language=”java”]
public class ResourceProxyImpl extends DefaultResourceProxyImpl {
private final Context mContext;

public ResourceProxyImpl(final Context pContext) {
super(pContext);
mContext = pContext;
}

@Override
public String getString(final string pResId) {
try {
final int res = R.string.class.getDeclaredField(pResId.name())
.getInt(null);
return mContext.getString(res);
} catch (final Exception e) {
return super.getString(pResId);
}
}

@Override
public Bitmap getBitmap(final bitmap pResId) {
try {
final int res = R.drawable.class.getDeclaredField(pResId.name())
.getInt(null);
return BitmapFactory.decodeResource(mContext.getResources(), res);
} catch (final Exception e) {
return super.getBitmap(pResId);
}
}

@Override
public Drawable getDrawable(final bitmap pResId) {
try {
final int res = R.drawable.class.getDeclaredField(pResId.name())
.getInt(null);
return mContext.getResources().getDrawable(res);
} catch (final Exception e) {
return super.getDrawable(pResId);
}
}
}
[/sourcecode]

PlacesMapOsmDroidActivity

No código PlacesMapOsmDroidActivity verificamos um exemplo de implementação utilizando o recurso adotado. Note que ele utiliza a classe MapView presente no código MapView.

[sourcecode language=”java”]
public class PlacesMapOsmDroidActivity extends Activity {
private static String TAG = "PlacesMapOsmDroidActivity";
private MapView mapView;
private MapController mapController;
private static double latitude = -20.39711, longitude = -43.50906;
private MyLocationOverlay myLocationOverlay;
private BingMapTileSource bms;
private ItemizedOverlay itemizedOverlay;
private ResourceProxy resourceProxy;
private boolean isAerial = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ui_main_map_osmdroid);

resourceProxy = new ResourceProxyImpl(getApplicationContext());
bms = new BingMapTileSource(null);
// bms.setStyle(BingMapTileSource.IMAGERYSET_AERIAL);
if (BingMapTileSource.getBingKey().length() == 0) {
BingMapTileSource.retrieveBingKey(getApplicationContext());
}

if (!TileSourceFactory.containsTileSource(bms.name())) {
TileSourceFactory.addTileSource(bms);
}

MyPoint point = new MyPoint(latitude, longitude);

mapView = (MapView) findViewById(R.id.map_view);
mapView.setBuiltInZoomControls(true);
mapView.setClickable(true);
mapView.setMultiTouchControls(true);
mapView.setDrawingCacheEnabled(true);

mapView.setTileSource(TileSourceFactory.getTileSource(bms.name()));

mapController = mapView.getController();
mapController.setZoom(17);
mapController.setCenter(point);

myLocationOverlay = new MyLocationOverlay(this, mapView, resourceProxy);

myLocationOverlay.enableCompass();
myLocationOverlay.enableMyLocation();

ArrayList items = new ArrayList();
items.add(new OverlayItem("Johnnatan", "Está na UFOP", point));

itemizedOverlay = new ItemizedIconOverlay(items,
new ItemizedIconOverlay.OnItemGestureListener() {
@Override
public boolean onItemLongPress(int index, OverlayItem item) {
// TODO Auto-generated method stub
Toast.makeText(
PlacesMapOsmDroidActivity.this,
"onItemLongPress Title: " + item.mTitle + " "
+ "Desc: " + item.mDescription
+ " index: " + index,
Toast.LENGTH_SHORT).show();
return true;
}

@Override
public boolean onItemSingleTapUp(int index, OverlayItem item) {
// TODO Auto-generated method stub
Toast.makeText(
PlacesMapOsmDroidActivity.this,
"ItemSingleTapUp Title: " + item.mTitle + " "
+ "Desc: " + item.mDescription
+ " index: " + index,
Toast.LENGTH_SHORT).show();
return true;
}

}, resourceProxy);

mapView.getOverlays().add(myLocationOverlay);
mapView.getOverlays().add(itemizedOverlay);
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy()");
myLocationOverlay.disableCompass();
myLocationOverlay.disableMyLocation();

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.ui_main_menu, menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

case R.id.main_menu_map:
if (isAerial) {
bms.setStyle(BingMapTileSource.IMAGERYSET_ROAD);
isAerial = false;
Toast.makeText(this, "Mapa normal", Toast.LENGTH_SHORT).show();
} else {
bms.setStyle(BingMapTileSource.IMAGERYSET_AERIAL);
isAerial = true;
Toast.makeText(this, "Mapa Satélite", Toast.LENGTH_SHORT)
.show();
}

break;
}
return super.onOptionsItemSelected(item);
}
}
[/sourcecode]

ui_main_map_osmdroid

O código ui_main_map_osmdroid apresenta o Layout necessário para exibição do mapa. Note que ele utiliza a classe MapView presente no código MapView.

[sourcecode language=”xml”]
<!–?xml version="1.0" encoding="utf-8"?–>
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >

<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >

<br.com.johnnatan.places.util.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true" >

[/sourcecode]

AndroidManifest

O código AndroidManifest contém o código de configuração da aplicação do Android. Além disso, deve-se incluir a chave em BING_KEY obtida no site da Microsoft para a utilização do Bing.

[sourcecode language=”xml”]
<!–?xml version="1.0" encoding="UTF-8"?–>
package="com.bingopenstreetmaps"
android:versionCode="1"
android:versionName="1.0" >

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<meta-data
android:name="BING_KEY"
android:value="Your Bing_Key here" />
<activity
android:name="PlacesMapOsmDroidActivity"
android:label="@string/app_name" >

[/sourcecode]

Telas da Aplicação:

Conclusão

Obs.: Está em anexo o código desenvolvido por mim propondo o Bing como solução de mapas para o iMobilis.
Vale ressaltar que sugiro a utilização do OpenStreetMap com TileSources do Bing.
Devido a essa sugestão podemos utilizar todas as abstrações do OpenStreetMaps aplicadas ao mapa Bing da Microsoft.

Trabalhos Futuros

Como trabalho futuro, visando uma maior cobertura, devemos ter em mente:

  • Pré-armazenamento de todo o Mapa do Brasil para utilização em veículos auto-motores de modo a diminuir o tráfego e custo de dados na rede sem fio (3G, EDGE, HSDPA, WiFi)

Logo, o resultado final para o projeto seria obtermos todo o mapa do Brasil de modo que possamos incluí-lo em nosso sistema sem que haja a necessidade do mapa ser baixado pelo usuário, deixando-o totalmente off-line para mapas.

Download:
  • Aplicativo (link)  
  • SourceCode (link

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *