Google Maps offline Mode on Android Application - android

Is there any posibility to show Google Maps if you are offline in your own App?
What about if I download an Area FROM Google Maps application for offline mode, could i visualize the map on the app that i develop if i don't have internet connection?
if not, What options do i have to make this possible? I just want to visualize the map when my app is offline...
The following its the code that this post provided TileProvider using local tiles
#Override
public Tile getTile(int x, int y, int zoom) {
byte[] image = readTileImage(x, y, zoom);
return image == null ? null : new Tile(TILE_WIDTH, TILE_HEIGHT,image);
}
private byte[] readTileImage(int x,int y, int zoom){
InputStream is= null;
ByteArrayOutputStream buffer= null;
try{
is= mAssets.open(getTileFileName(x,y,zoom));
buffer= new ByteArrayOutputStream();
int nRead;
byte[] data= new byte[BUFFER_SIZE];
while ((nRead= is.read(data,0,BUFFER_SIZE)) !=-1){
buffer.write(data,0,nRead);
}
buffer.flush();
return buffer.toByteArray();
}
catch(IOException ex){
Log.e("LINE 60 CustomMap", ex.getMessage());
return null;
}catch(OutOfMemoryError e){
Log.e("LINE 64 CustomMap", e.getMessage());
return null;
}finally{
if(is!=null){
try{
is.close();
} catch (IOException e) {}
}
if(buffer !=null){
try{
buffer.close();
}catch (Exception e){}
}
}
}
private String getTileFileName(int x, int y, int zoom){
return "map/"+ zoom +'/' +x+ '/'+y+".png";
}
I was looking for information, and My questions is, how can i download the tiles?

I was facing the same challenge, and none of the examples I found included a complete implementation of downloading the tiles, writing them to file and reading them from file.
This is my code, which reads the tile from file when it's available locally and downloads/saves the tile when not. This uses the OpenStreetMap.org tile server, but you could use any server you like by changing the URL.
private class OfflineTileProvider implements TileProvider {
private static final String TILES_DIR = "your_tiles_directory/";
private static final int TILE_WIDTH = 256;
private static final int TILE_HEIGHT = 256;
private static final int BUFFER_SIZE_FILE = 16384;
private static final int BUFFER_SIZE_NETWORK = 8192;
private ConnectivityManager connectivityManager;
#Override
public Tile getTile(int x, int y, int z) {
Log.d(TAG, "OfflineTileProvider.getTile(" + x + ", " + y + ", " + z + ")");
try {
byte[] data;
File file = new File(TILES_DIR + z, x + "_" + y + ".png");
if (file.exists()) {
data = readTile(new FileInputStream(file), BUFFER_SIZE_FILE);
} else {
if (connectivityManager == null) {
connectivityManager = (ConnectivityManager) getSystemService(
Context.CONNECTIVITY_SERVICE);
}
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo == null || !activeNetworkInfo.isConnected()) {
Log.w(TAG, "No network");
return NO_TILE;
}
Log.d(TAG, "Downloading tile");
data = readTile(new URL("https://a.tile.openstreetmap.org/" +
z + "/" + x + "/" + y + ".png").openStream(),
BUFFER_SIZE_NETWORK);
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
out.write(data);
}
}
return new Tile(TILE_WIDTH, TILE_HEIGHT, data);
} catch (Exception ex) {
Log.e(TAG, "Error loading tile", ex);
return NO_TILE;
}
}
private byte[] readTile(InputStream in, int bufferSize) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
int i;
byte[] data = new byte[bufferSize];
while ((i = in.read(data, 0, bufferSize)) != -1) {
buffer.write(data, 0, i);
}
buffer.flush();
return buffer.toByteArray();
} finally {
in.close();
buffer.close();
}
}
}
Replace "your_tiles_directory" with the path to the directory where you want to store your tiles.
To use the TileProvider:
map.setMapType(GoogleMap.MAP_TYPE_NONE);
offlineTileOverlay = map.addTileOverlay(new TileOverlayOptions()
.tileProvider(new OfflineTileProvider()));
Edit: You may want to set the max zoom level, the default is 21 but OpenStreetMap for example seems to have a maximum of 19.
map.setMaxZoomPreference(19);

You can download tile image from a tile server and cache on your app. Check some server on this link. Or you can build a tile as this demo, then download tile image from it.
Good luck

Related

android image not appearing when getting bytes from socket and using bitmap creation

I have a program in which I'm trying to download the content of an image file from a server. I'm using java socket to download it. After downloading, I use BitmapFactory.decodeByteArray() to create a bitmap.
At the server side, the file is a .jpg file and it's only about 180 KBytes, so I don't need to try scaling it. I can see through logs that the exact number of bytes in the file is received by my image download code. I store all the bytes in a byte[] array and then convert it into a bitmap.
The imageView is initially hidden and then supposed to be made visible after populating the image. But using BitmapFactory.decodeByteArray() is returning null always. I did see some other posts about null bitmap, but nothing seems to have an answer for this problem.
I don't want to use any external library just for this, so please do not give me suggestions to try out some other libraries. Can someone spot any problem with the code? The server side program is also mine and I know that part is correct because using that, browsers are able to download the same image file. I have copy-pasted it below.
public class ImageDownloader {
private Socket sockToSrvr;
private PrintWriter strmToSrvr;
private BufferedInputStream strmFromSrvr;
private String srvrAddr;
private int port;
private String remoteFile;
private Context ctxt;
private Bitmap imgBmap;
private View parkSpotImgVwHldr;
private View mngAndFndVwHldr;
private View parkSpotImgVw;
public ImageDownloader(Context c) {
srvrAddr = KloudSrvr.srvrIp();
port = KloudSrvr.port();
sockToSrvr = null;
strmFromSrvr = null;
strmToSrvr = null;
remoteFile = null;
ctxt = c;
imgBmap = null;
parkSpotImgVwHldr = null;
mngAndFndVwHldr = null;
parkSpotImgVw = null;
}
public void downloadFile(String remf, View parkSpotImgVwHldrVal,
View mngAndFndVwHldrVal, View parkSpotImgVwVal) {
remoteFile = remf;
parkSpotImgVwHldr = parkSpotImgVwHldrVal;
mngAndFndVwHldr = mngAndFndVwHldrVal;
parkSpotImgVw = parkSpotImgVwVal;
Thread dwnThrd = new Thread() {
#Override
public void run() {
imgBmap = null;
openServerConnection(); sendReq(); doDownload(); closeServerConnection();
((Activity)ctxt).runOnUiThread(new Runnable() {
public void run() {
((Activity)ctxt).runOnUiThread(new Runnable() {
public void run() {
mngAndFndVwHldr.setVisibility(View.GONE);
parkSpotImgVwHldr.setVisibility(View.VISIBLE);
Toast.makeText(ctxt, "completed", Toast.LENGTH_LONG).show();
}
});
}
});
}
};
dwnThrd.start();
}
private void sendReq() {
if(strmToSrvr == null) return;
String req = "GET /downloadFile " + remoteFile + " HTTP/1.1\r\n\r\n";
Log.d("IMG-DWNL-LOG: ", "writing req msg to socket " + req);
strmToSrvr.write(req); strmToSrvr.flush();
}
private void doDownload() {
boolean gotContLen = false;
int contLen = 0;
while(true) {
String inLine = getLine(strmFromSrvr); if(inLine == null) break;
if((gotContLen == true) &&
(inLine.replace("\r", "").replace("\n", "").isEmpty() == true)) break;
if(inLine.trim().startsWith("Content-Length:") == true) {
// an empty line after this signifies start of content
String s = inLine.replace("Content-Length:", "").trim();
try {contLen = Integer.valueOf(s); gotContLen = true; continue;}
catch(NumberFormatException nfe) {contLen = 0;}
}
}
if((gotContLen == false) || (contLen <= 0)) return;
byte[] imgByts = new byte[contLen];
int totRdByts = 0, rdByts, chnk = 1024, avlByts;
while(true) {
try {
avlByts = strmFromSrvr.available(); if(avlByts < 0) break;
if(avlByts == 0) {try {Thread.sleep(1000);} catch(InterruptedException ie) {} continue;}
rdByts = (avlByts < chnk) ? avlByts : chnk;
rdByts = strmFromSrvr.read(imgByts, totRdByts, rdByts); if(rdByts < 0) break;
if(rdByts == 0) {try {Thread.sleep(1000);} catch(InterruptedException ie) {} continue;}
totRdByts += rdByts;
if(totRdByts >= contLen) break;
} catch(IOException ioe) {return;}
}
if(totRdByts < contLen) {
Log.d("IMG-DWNL-LOG: ", "error - bytes read " + totRdByts
+ " less than content length " + contLen);
return;
}
if(totRdByts <= 0) return;
Log.d("IMG-DWNL-LOG: ", "read all image bytes successfully, setting image into view");
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeByteArray(imgByts, 0, contLen, options);
if(bitmap == null) {Log.d("IMG-DWNL-LOG: ", "got a null bitmap");}
((ImageView)parkSpotImgVw).setImageBitmap(bitmap);
}
private void closeServerConnection() {
if(sockToSrvr == null) return;
if(strmFromSrvr != null) {
try {strmFromSrvr.close();}
catch(IOException e) {Log.d("IMG-DWNL-LOG: ", "Inp strm close exception");}
}
if(strmToSrvr != null) strmToSrvr.close();
try {sockToSrvr.close();}
catch(IOException e) {Log.d("IMG-DWNL-LOG: ", "Conn close exception");}
strmFromSrvr = null; strmToSrvr = null; sockToSrvr = null;
}
private void openServerConnection() {
try {sockToSrvr = new Socket(InetAddress.getByName(srvrAddr), port);}
catch(UnknownHostException e) {
Log.d("IMG-DWNL-LOG: ", "Unknown host exception"); sockToSrvr = null; return;
} catch(IOException e) {
Log.d("IMG-DWNL-LOG: ", "Server connect exception"); sockToSrvr = null; return;
}
Log.d("IMG-DWNL-LOG: ", "Connected to server");
try {
strmFromSrvr = new BufferedInputStream(sockToSrvr.getInputStream());
strmToSrvr = new PrintWriter(new BufferedWriter(new OutputStreamWriter
(sockToSrvr.getOutputStream())), true);
} catch(IOException e) {
closeServerConnection();
Log.d("IMG-DWNL-LOG: ", "Failed to open reader / writer. Closed the connection."); return;
}
}
private String getLine(BufferedInputStream dis) {
String outLine = "";
while(true) {
try {
int c = dis.read(); if((c == -1) && (outLine.length() <= 0)) return(null);
outLine += Character.toString((char)c);
if(c == '\n') return(outLine);
} catch(IOException e) {if(outLine.length() <= 0) return(null); return(outLine);}
}
}
}
I was making a mistake, assuming that a .jpg file's bytes can be directly decode with the android bitmap decoder. Apparently this is not the case. So, I wrote the received bytes into a temporary file in the phone storage and then called BitmapFactory.decodeFile() which is able to return a good bitmap and ends up showing the image.
So, have a working solution now.
Still - if anyone has a better suggestion how to decode directly from the received bytes (which are from a .jpg file), I would be very interested to try it out since that would be more efficient. Thanks.

How to prevent buildings from moving on Google Maps Android API

I've set custom overlays on Google Maps to get indoor maps of my building. But when I move the map on the screen, buildings are moving and overlays are fixed. So it gets a bit ugly. You can see how ugly it is on the image below.
ugly indoor map
I'd like to get the map like this : pretty indoor map
How to prevent buildings from moving on Android Google Maps API ?
Below the part of code I use to set overlays on the map :
private GoogleMap mMap;
private MapView mapView;
#Override
public void onMapReady(GoogleMap googleMap) {
this.mMap = googleMap;
refreshGoogleMap();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
}
private void refreshGoogleMap() {
mMap.clear();
mMap.addTileOverlay(new TileOverlayOptions()
.tileProvider(new AssetsTileProvider())
.zIndex(-1));
}
private class AssetsTileProvider implements TileProvider {
private static final int TILE_WIDTH = 256;
private static final int TILE_HEIGHT = 256;
private static final int BUFFER_SIZE = 16 * 1024;
#Override
public Tile getTile(int x, int y, int zoom) {
byte[] bytes = readFile(x, y, zoom);
return bytes == null ? NO_TILE : new Tile(TILE_WIDTH, TILE_HEIGHT, bytes);
}
private byte[] readFile(int x, int y, int zoom) {
InputStream in = null;
ByteArrayOutputStream buffer = null;
try {
in = getAssets().open(getFilename(x, y, zoom));
buffer = new ByteArrayOutputStream();
int read;
byte[] data = new byte[BUFFER_SIZE];
while ((read = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, read);
}
buffer.flush();
return buffer.toByteArray();
} catch (Exception e) {
// NO OP
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
return null;
}
private String getFilename(int x, int y, int zoom) {
String level = currentLevel == LEVEL_1 ? "level-1" : "level-2";
return level + "/" + zoom + "/" + x + "/" + y + ".png";
}
}

Adding an Authorization header in getTileUrl for Maps Tile Android

I would like to access some Custom Map Tiles when creating a TileOverlay for Google Maps API.
So this is my current code:
TileProvider tileProvider = new UrlTileProvider(256, 256) {
#Override
public URL getTileUrl(int x, int y, int z) {
String url = String.format("https://api.mycustommaps.com/v1/%d/%d/%d.jpg", z, x, y);
if (!checkTileExists(x, y, z)) {
return null;
}
try {
URL tileUrl = new URL(url);
tileUrl.openConnection().addRequestProperty("Authorization", LOGIN_TOKEN);
return tileUrl;
} catch (MalformedURLException e) {
e.printStackTrance();
} catch (IOException e) {
e.printStackTrance();
}
return null;
}
};
Since the connection returns 401 Anauthorized, I can't access the tiles. How could I pass Authorization header to let the url know I am authorized to access those tiles?
you have to implement the "TileProvider" interface, not URLTileProvider (because you have to retrieve the tile on your own, an URL is not enough.
https://developers.google.com/android/reference/com/google/android/gms/maps/model/TileProvider
as you can see, there is a note to keep attention:
Calls to methods in this interface might be made from multiple threads so implementations of this interface must be threadsafe.
and you have to implement a single method:
abstract Tile
getTile(int x, int y, int zoom)
It is now your work download the tile, I've done it for local files, so I'm just writing here some code that might need some more refinement and testing:
#Override
public Tile getTile(int x, int y, int zoom) {
String url = String.format("https://api.mycustommaps.com/v1/%d/%d/%d.jpg", z, x, y);
if (!checkTileExists(x, y, z)) {
return null;
}
try {
URL tileUrl = new URL(url);
//Download the PNG as byte[], I suggest using OkHTTP library or see next code!
final byte[] data = downloadData(tileUrl);
final int height = tileheight;
final int width = tilewidth;
if (data != null) {
if (BuildConfig.DEBUG)Log.d(TAG, "Cache hit for tile " + key);
return new Tile(width, height, data);
}
//In this case error, maybe return a placeholder tile or TileProvider.NO_TILE
} catch (MalformedURLException e) {
e.printStackTrance();
} catch (IOException e) {
e.printStackTrance();
}
}
to download:
byte[] downloadData(URL url){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = null;
try {
tileUrl.openConnection().addRequestProperty("Authorization", LOGIN_TOKEN);
is = url.openStream();
byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time.
int n;
while ( (n = is.read(byteChunk)) > 0 ) {
baos.write(byteChunk, 0, n);
}
}
catch (IOException e) {
System.err.printf ("Failed while reading bytes from %s: %s", url.toExternalForm(), e.getMessage());
e.printStackTrace ();
// Perform any other exception handling that's appropriate.
}
finally {
if (is != null) { is.close(); }
}
return baos.toByteArray():

Google Maps API Android - Tile provider's tiles resolution

I'm using custom TileProviders in my Android app to display offline maps and OpenStreetMap maps. It works, but there is a problem with the tiles resolution, which is quite bad. The files have a size of 256x256, and setting the width/height of my TileProvider to 128 doesn't change anything.
Here is some piece of code :
public class GenericUrlTileProvider extends UrlTileProvider {
// ---------------------------------------------------------------------------------------
// Private attributes :
private String _baseUrl;
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// Constructor :
public GenericUrlTileProvider(int width, int height, String url) {
super(width, height);
this._baseUrl = url;
}
#Override
public URL getTileUrl(int x, int y, int zoom) {
try {
return new URL(_baseUrl.replace("{z}", "" + zoom).replace("{x}", "" + x).replace("{y}", "" + y));
}
catch (MalformedURLException e) { e.printStackTrace(); }
return null;
}
// ---------------------------------------------------------------------------------------
}
Does anyone know how to fix this to support high resolution devices ?
Thanks
On #grub request, here is what I did for getting the 4 tiles of the next zoom level :
public Tile getTileFromNextZoomLevel(int x, int y, int zoom) {
final String topLeftTileUrl = _source.getUrlSchema().replace("{z}", "" + (zoom + 1)).replace("{x}", "" + (x * 2)).replace("{y}", "" + (y * 2));
final String topRightTileUrl = _source.getUrlSchema().replace("{z}", "" + (zoom + 1)).replace("{x}", "" + (x * 2 + 1)).replace("{y}", "" + (y * 2));
final String bottomLeftTileUrl = _source.getUrlSchema().replace("{z}", "" + (zoom + 1)).replace("{x}", "" + (x * 2)).replace("{y}", "" + (y * 2 + 1));
final String bottomRightTileUrl = _source.getUrlSchema().replace("{z}", "" + (zoom + 1)).replace("{x}", "" + (x * 2 + 1)).replace("{y}", "" + (y * 2 + 1));
final Bitmap[] tiles = new Bitmap[4];
Thread t1 = new Thread() {
#Override
public void run() { tiles[0] = Utils.getBitmapFromURL(topLeftTileUrl); }
};
t1.start();
Thread t2 = new Thread() {
#Override
public void run() { tiles[1] = Utils.getBitmapFromURL(topRightTileUrl); }
};
t2.start();
Thread t3 = new Thread() {
#Override
public void run() { tiles[2] = Utils.getBitmapFromURL(bottomLeftTileUrl); }
};
t3.start();
Thread t4 = new Thread() {
#Override
public void run() { tiles[3] = Utils.getBitmapFromURL(bottomRightTileUrl); }
};
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
}
catch (InterruptedException e) { e.printStackTrace(); }
byte[] tile = Utils.mergeBitmaps(tiles, Bitmap.CompressFormat.JPEG); // PNG is a lot slower, use it only if you really need to
return tile == null ? TileProvider.NO_TILE : new Tile( (int) _source.getTileSize().getWidth(), (int) _source.getTileSize().getHeight(), tile);
}
And the Utils methods :
public static byte[] mergeBitmaps(Bitmap[] parts, Bitmap.CompressFormat format) {
// Check if all the bitmap are null (if so return null) :
boolean allNulls = true;
for (int i = 0; i < parts.length; i++) {
if(parts[i] != null) {
allNulls = false;
break;
}
}
if(allNulls) return null;
Bitmap tileBitmap = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tileBitmap);
Paint paint = new Paint();
for (int i = 0; i < parts.length; i++) {
if(parts[i] == null) {
parts[i] = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
}
canvas.drawBitmap(parts[i], parts[i].getWidth() * (i % 2), parts[i].getHeight() * (i / 2), paint);
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
tileBitmap.compress(format, 100, stream);
byte[] bytes = stream.toByteArray();
return bytes;
}
public static Bitmap getBitmapFromURL(String urlString) {
try {
// Ensure the file exists :
if(Utils.getResponseCode(urlString) != 200) return null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
Bitmap bitmap = BitmapFactory.decodeStream(connection.getInputStream());
return bitmap;
}
catch (IOException e) { return null; }
}
You may have to adapt it for your needs. Please note that my app is still under development, and this code may need some tests / improvements.

Solving StringIndexOutOfBoundsException

I received a crash report, which is about java.lang.StringIndexOutOfBoundsException in ZhuangDictActivity$SearchDicAsyncTask.doInBackground
Here is the ZhuangDictActivity$SearchDicAsyncTask.doInBackground:
private class SearchDicAsyncTask extends AsyncTask<String, Integer, String> {
private byte searchStatus;
#Override
protected String doInBackground(String... params) {
if (params[0].length() > 0) {
word = params[0].trim();
long[] index = null;
FileAccessor in = null;
DictZipInputStream din = null;
try {
char key = GB2Alpha.Char2Alpha(word.charAt(0));
tableName = DatabaseHelper.transTableName(key);
index = databaseHelper.queryTable(tableName, word);
if (index != null) {
in = new FileAccessor(new File(dictFileName), "r");
byte[] bytes = new byte[(int) index[1]];
if (isDZDict) {
din = new DictZipInputStream(in);
DictZipHeader h = din.readHeader();
int idx = (int) index[0] / h.getChunkLength();
int off = (int) index[0] % h.getChunkLength();
long pos = h.getOffsets()[idx];
in.seek(pos);
byte[] b = new byte[off + (int) index[1]];
din.readFully(b);
System.arraycopy(b, off, bytes, 0, (int) index[1]);
} else {
in.seek(index[0]);
in.read(bytes);
}
wordDefinition = new String(bytes, "UTF-8");
} else {
searchStatus = 0;
return null;
}
} catch (FileNotFoundException ffe) {
searchStatus = 1;
return null;
} catch (IOException ex) {
ex.printStackTrace();
searchStatus = 2;
return null;
} finally {
try {
if (din != null)
din.close();
if (in != null)
in.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return wordDefinition;
}
}
The complete code is available here.
I have limited knowledge in Java and Android development. How should I solve this? I intended to post the complete stack traces but stackoverflow do not allow me to do so because it stated my question has too many code. Anyway, the line which is causing the problem is char key = GB2Alpha.Char2Alpha(word.charAt(0));.
It is possible that your string contains only white spaces. meaning it passed the condition:
if (params[0].length() > 0)
But when you call trim(), these are removed, resulting in an empty stream and an "IndexOutOfBoundsException" exception being thrown when you execute:
word.charAt(0)
EDIT
This is not the reason. After a test, when trim is called on a String with only whitespaces, the String remains unchanged.

Categories

Resources