I want to show a CustomOverlay on a Google Maps V2. The loading of the tiles works as expected, the only thing is, that the shown tiles are not appearing to be sharp at all, kind of blurry. Google did not get me to an usable answer.
I took the approach of adding classical 256px x 256px tiles for the TileProvider. I managed to get a tile source where the images were #2x (retina), which made the whole visual experience much sharper, but I would rather not use this source, as the data transfer rate is four times higher, hence not usable on mobile devices and slow internet speeds.
I included two different examples (screenshots) of the rendered map, both with the same configuration (OSM - 256x256 tiles) and with the provided TileProviderOsm. One is on a Galaxy Nexus and the other on a Nexus 5 phone. Both do not look like they were rendered correctly.
Any idea what I could do to prevent the blurriness or increase the sharpness?
My TileProvider looks like following:
public class TileProviderOsm extends UrlTileProvider {
private static final String MAP_URL = "http://tile.openstreetmap.org/%d/%d/%d.png";
private static int TILE_WIDTH = 256;
private static int TILE_HEIGHT = 256;
public static int MIN_ZOOM = 7;
public static int MAX_ZOOM = 15;
public TileProviderOsm() {
super(TILE_WIDTH, TILE_HEIGHT);
}
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
String s = String.format(Locale.US, MAP_URL, zoom, x, y);
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return url;
}
}
Here's how I add the overlay to the map:
map.addTileOverlay(new TileOverlayOptions().tileProvider(new TileProviderOsm()));
Here are some examples of the rendered Google Map:
I am solving this issue by drawing four tiles into one tile.
I wrote this TileProvider which is using another tile provider to create higher resolution tiles.
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileProvider;
import java.io.ByteArrayOutputStream;
public class CanvasTileProvider implements TileProvider {
static final int TILE_SIZE = 512;
private TileProvider mTileProvider;
public CanvasTileProvider(TileProvider tileProvider) {
mTileProvider = tileProvider;
}
#Override
public Tile getTile(int x, int y, int zoom) {
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
boolean isOk = onDraw(canvas, zoom, x, y);
data = bitmapToByteArray(image);
image.recycle();
if (isOk) {
Tile tile = new Tile(TILE_SIZE, TILE_SIZE, data);
return tile;
} else {
return mTileProvider.getTile(x, y, zoom);
}
}
Paint paint = new Paint();
private boolean onDraw(Canvas canvas, int zoom, int x, int y) {
x = x * 2;
y = y * 2;
Tile leftTop = mTileProvider.getTile(x, y, zoom + 1);
Tile leftBottom = mTileProvider.getTile(x, y + 1, zoom + 1);
Tile rightTop = mTileProvider.getTile(x + 1, y, zoom + 1);
Tile rightBottom = mTileProvider.getTile(x + 1, y + 1, zoom + 1);
if (leftTop == NO_TILE && leftBottom == NO_TILE && rightTop == NO_TILE && rightBottom == NO_TILE) {
return false;
}
Bitmap bitmap;
if (leftTop != NO_TILE) {
bitmap = BitmapFactory.decodeByteArray(leftTop.data, 0, leftTop.data.length);
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmap.recycle();
}
if (leftBottom != NO_TILE) {
bitmap = BitmapFactory.decodeByteArray(leftBottom.data, 0, leftBottom.data.length);
canvas.drawBitmap(bitmap, 0, 256, paint);
bitmap.recycle();
}
if (rightTop != NO_TILE) {
bitmap = BitmapFactory.decodeByteArray(rightTop.data, 0, rightTop.data.length);
canvas.drawBitmap(bitmap, 256, 0, paint);
bitmap.recycle();
}
if (rightBottom != NO_TILE) {
bitmap = BitmapFactory.decodeByteArray(rightBottom.data, 0, rightBottom.data.length);
canvas.drawBitmap(bitmap, 256, 256, paint);
bitmap.recycle();
}
return true;
}
private Bitmap getNewBitmap() {
Bitmap image = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE,
Bitmap.Config.ARGB_8888);
image.eraseColor(Color.TRANSPARENT);
return image;
}
private static byte[] bitmapToByteArray(Bitmap bm) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] data = bos.toByteArray();
try {
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
}
It's a problem with the zoom level and scaling of the map tiles. This seems to be a problem with the Maps API V2 for Android. The behavior is supposedly intended as stated here: gmaps-api-issues #4840
I solved it by requesting larger tiles from the WMS - just replace 256x256 with 512x512.
Related
i am developing a custom tile provider to show traffic data on Google Maps. In high zoom levels it is good for me. But polylines are overlapping at low level zoom.
My custom tile provider class is
public class PolylineTileProvider implements TileProvider {
private static final String TAG = "TileOverlay";
private final int mTileSize = 256;
private final SphericalMercatorProjection mProjection = new SphericalMercatorProjection(mTileSize);
private final int mScale = 2;
private final int mDimension = mScale * mTileSize;
private final List<PolylineOptions> polylines;
public PolylineTileProvider(List<PolylineOptions> polylines) {
this.polylines = polylines;
}
#Override
public Tile getTile(int x, int y, int zoom) {
Matrix matrix = new Matrix();
float scale = ((float) Math.pow(2, zoom) * mScale);
matrix.postScale(scale, scale);
matrix.postTranslate(-x * mDimension, -y * mDimension);
Bitmap bitmap = Bitmap.createBitmap(mDimension, mDimension, Bitmap.Config.ARGB_8888); //save memory on old phones
Canvas c = new Canvas(bitmap);
c.setMatrix(matrix);
drawCanvasFromArray(c, scale);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return new Tile(mDimension, mDimension, baos.toByteArray());
}
private void drawCanvasFromArray(Canvas c, float scale) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setShadowLayer(0, 0, 0, 0);
paint.setAntiAlias(true);
if (polylines != null) {
for (int i = 0; i < polylines.size(); i++) {
List<LatLng> route = polylines.get(i).getPoints();
paint.setColor(polylines.get(i).getColor());
paint.setStrokeWidth(getLineWidth(polylines.get(i).getWidth(), scale));
Path path = new Path();
if (route != null && route.size() > 1) {
Point screenPt1 = mProjection.toPoint(route.get(0)); //first point
MarkerOptions m = new MarkerOptions();
m.position(route.get(0));
path.moveTo((float) screenPt1.x, (float) screenPt1.y);
for (int j = 1; j < route.size(); j++) {
Point screenPt2 = mProjection.toPoint(route.get(j));
path.lineTo((float) screenPt2.x, (float) screenPt2.y);
}
}
c.drawPath(path, paint);
}
}
}
private float getLineWidth(float width, float scale) {
return width / (scale);
}
}
Trafic layer is shown at Google Maps android application so good.
How can i make a similar layer. Thanks in advance.
the reason why it gets blured, or is maybe no more seen on the screen is because you create one image that is then scaled using the matrix you provided.
instead you shoudn't use a Matrix and generate the images in the right size.
Todo so, remove you your setMatrix call on the Canvas
and
add the Points to the Path with the right scaled coordinates.
x = screenPt1.x * scale - x * mDimension;
y = screenPt1.y * scale - y * mDimension;
then you get the exact line as specified, in every zoom level.
I am using the framework that is described in the (great) book Beginning Android Games by Mario Zechner and Robert Green. So far all has been going great, where I can utilize the framework to draw my bitmaps for the main menu. With the framework, I am also able to clear the background, which also works great. However, when it comes to drawing pixels, lines, or shapes in general, I am completely stuck as they are not drawn. The methods execute in a similar fashion to that of the two working methods and my debugging with logging has told me that they do execute, however nothing new appears on the screen.
One source( canvas.drawLine() not appearing on bitmap ) said to draw onto a bitmap and then draw the bitmap onto the canvas, but I already made sure to do this. My paint is not null either. When drawing a rectangle, I made sure that its height and width were greater than 0. When drawing a line, I tried setting a width to 10 and the fill the STROKE. However, all of this was in vain.
Here is the code for the graphics class
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.RectF;
import com.learnplex.framework.Graphics;
import com.learnplex.framework.Pixmap;
import java.io.IOException;
import java.io.InputStream;
public class AndroidGraphics implements Graphics {
AssetManager assets;
Bitmap frameBuffer;
Canvas canvas;
Paint paint;
Rect srcRect = new Rect();
Rect dstRect = new Rect();
public AndroidGraphics(AssetManager assets, Bitmap frameBuffer) {
this.assets = assets;
this.frameBuffer = frameBuffer;
this.canvas = new Canvas(frameBuffer);
this.paint = new Paint();
}
public Pixmap newPixmap(String fileName, PixmapFormat format) {
Config config = null;
if (format == PixmapFormat.RGB565)
config = Config.RGB_565;
else if (format == PixmapFormat.ARGB4444)
config = Config.ARGB_4444;
else
config = Config.ARGB_8888;
Options options = new Options();
options.inPreferredConfig = config;
InputStream in = null;
Bitmap bitmap = null;
try {
in = assets.open(fileName);
bitmap = BitmapFactory.decodeStream(in);
if (bitmap == null)
throw new RuntimeException("Couldn't load bitmap from asset '"
+ fileName + "'");
} catch (IOException e) {
throw new RuntimeException("Couldn't load bitmap from asset '"
+ fileName + "'");
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
if (bitmap.getConfig() == Config.RGB_565)
format = PixmapFormat.RGB565;
else if (bitmap.getConfig() == Config.ARGB_4444)
format = PixmapFormat.ARGB4444;
else
format = PixmapFormat.ARGB8888;
return new AndroidPixmap(bitmap, format);
}
public void clear(int color) {
canvas.drawRGB((color & 0xff0000) >> 16, (color & 0xff00) >> 8,
(color & 0xff));
}
public void drawPixel(int x, int y, int color) {
paint.setColor(color);
canvas.drawPoint(x, y, paint);
}
public void drawLine(int x, int y, int x2, int y2, int color) {
paint.setColor(color);
canvas.drawLine(x, y, x2, y2, paint);
}
public void drawRect(int x, int y, int width, int height, int color) {
paint.setColor(color);
paint.setStyle(Style.FILL);
canvas.drawRect(x, y, x + width - 1, y + width - 1, paint);
}
public void drawArc(int x, int y, int width, int height, int startAngle, int sweepAngle, int color) {
paint.setColor(color);
RectF oval = new RectF(x, y, x + width - 1, y + height -1);
canvas.drawArc(oval, startAngle, sweepAngle, true, paint); // true is usecenter, idk what it does yet
}
public void drawPixmap(Pixmap pixmap, int x, int y, int srcX, int srcY,
int srcWidth, int srcHeight) {
srcRect.left = srcX;
srcRect.top = srcY;
srcRect.right = srcX + srcWidth - 1;
srcRect.bottom = srcY + srcHeight - 1;
dstRect.left = x;
dstRect.top = y;
dstRect.right = x + srcWidth - 1;
dstRect.bottom = y + srcHeight - 1;
canvas.drawBitmap(((AndroidPixmap) pixmap).bitmap, srcRect, dstRect, null);
}
public void drawPixmap(Pixmap pixmap, int x, int y) {
canvas.drawBitmap(((AndroidPixmap)pixmap).bitmap, x, y, null);
}
public int getWidth() {
return frameBuffer.getWidth();
}
public int getHeight() {
return frameBuffer.getHeight();
}
}
As I said, the drawPixmap class works, as well as the clear class
And here is the relevant part of the implementation
public void present(float deltaTime){
Graphics g = game.getGraphics();
g.clear(0xffffff); //From here down works - clears background and makes white
g.drawPixmap(Assets.getTitle(), 240, 20 ); // Display title
g.drawPixmap(Assets.getStart(), 540, 300); // Display start button
g.drawPixmap(Assets.getScore(), 590, 550); // Display score button / connect with google play
g.drawPixmap(Assets.getThemeScreen(),740, 550); // Display theme screen
if(Settings.soundEnabled) // Display sound icon
g.drawPixmap(Assets.getSoundOn(), 440, 550);
else
g.drawPixmap(Assets.getSoundOff(), 440, 550);
g.drawLine(100, 200, 300, 400, 0x000000); //This does not work - draws black line
}
Here is the code where i want to draw rectangle on the surface view and crop image inside that
I have captured image from the camera then trying to crop the part visible inside the rectangle but no luck , i am able to crop but that is not appropriate , please help ! Thanks in advance
package com.desmond.demo.camera;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.desmond.demo.R;
public class CameraFragment extends Fragment implements SurfaceHolder.Callback, Camera.PictureCallback {
int left , top, right, bottom;
Rect rect;
public static Fragment newInstance() {
return new CameraFragment();
}
public CameraFragment() {}
private class DrawOnTop extends View
{
public DrawOnTop(Context context)
{
super(context);
}
#Override
public void onDraw(Canvas canvas)
{
if (bitmap != null)
{
overlayX = (canvas.getWidth()/2)-(bitmap.getWidth()/2);
overlayY = canvas.getHeight()/4;
// canvas.drawBitmap(bitmap, overlayX, overlayY, null);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
left = (int)overlayX;
top = (int)overlayY;
right = (int)overlayX+bitmap.getWidth();
bottom = (int)overlayY+bitmap.getHeight();
rect = new Rect(left, top, right, bottom);
canvas.drawRect(rect,paint);
}
super.onDraw(canvas);
}
}
/**
* Take a picture
*/
private void takePicture() {
if (mIsSafeToTakePhoto) {
setSafeToTakePhoto(false);
mOrientationListener.rememberOrientation();
// Shutter callback occurs after the image is captured. This can
// be used to trigger a sound to let the user know that image is taken
Camera.ShutterCallback shutterCallback = null;
// Raw callback occurs when the raw image data is available
Camera.PictureCallback raw = null;
// postView callback occurs when a scaled, fully processed
// postView image is available.
Camera.PictureCallback postView = null;
// jpeg callback occurs when the compressed image is available
mCamera.takePicture(shutterCallback, raw, postView, this);
}
}
/**
* A picture has been taken
* #param data
* #param camera
*/
#Override
public void onPictureTaken(byte[] data, Camera camera)
{
int rotation = getPhotoRotation();
// TODO Auto-generated method stub
Bitmap cameraBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
int wid = cameraBitmap.getWidth();
int hgt = cameraBitmap.getHeight();
// Toast.makeText(getApplicationContext(), wid+""+hgt, Toast.LENGTH_SHORT).show();
Bitmap newImage = Bitmap.createBitmap(wid, hgt, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newImage);
canvas.drawBitmap(cameraBitmap, 0f, 0f, null);
Drawable drawable = getResources().getDrawable(R.drawable.ring_image);
drawable.setBounds((canvas.getWidth()/2)-(drawable.getIntrinsicWidth()/2), canvas.getHeight()/4, drawable.getIntrinsicWidth()+((canvas.getWidth()/2)-(drawable.getIntrinsicWidth()/2)), drawable.getIntrinsicHeight()+canvas.getHeight()/4);
drawable.draw(canvas);
File storagePath = new File(Environment.getExternalStorageDirectory() + "/DiamondValuer/");
storagePath.mkdirs();
try
{
Matrix matrix = new Matrix();
File originalImage = new File(storagePath,"original.jpg");
FileOutputStream out = new FileOutputStream(originalImage);
cameraBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
out.flush();
out.close();
// Somewhere in your code
// 2.1 Load bitmap from your .jpg file
Bitmap bitmap = BitmapFactory.decodeFile(originalImage.getPath());
// 2.2 Rotate the bitmap to be the same as display, if need.
// ... Add some bitmap rotate code
// bitmap = RotateBitmap(bitmap, 90);
// 2.3 Size of rotated bitmap
int bitWidth = bitmap.getWidth();
int bitHeight = bitmap.getHeight();
// 3. Size of camera preview on screen
int preWidth = cameraBitmap.getWidth();
int preHeight = cameraBitmap.getHeight();
int startx = left * bitWidth / preWidth;
int starty = top * bitHeight / preHeight;
int endx = (preWidth - right) * (bitWidth / preWidth);
int endy = (preHeight - bottom) * (bitHeight / preHeight);
Log.e("TAG", "START X = "+startx+" \nSTART Y ="+starty+"\nEND X="+endx+" \nEND Y="+endy);
Log.e("TAG", "LEFT = "+left+" \nTOP ="+top+"\nRIGHT="+right+" \nBOTTOM="+bottom);
Log.e("TAG", "BITMAP WIDTH = "+bitmap.getWidth());
// 5. Crop image
Bitmap blueArea = Bitmap.createBitmap(bitmap, startx, starty, endx, endy);
File cropped1 = new File(storagePath, Long.toString(System.currentTimeMillis()) + ".jpg");
FileOutputStream outCrop1 = new FileOutputStream(cropped1);
blueArea.compress(Bitmap.CompressFormat.JPEG, 80, outCrop1);
outCrop1.flush();
outCrop1.close();
}
catch(FileNotFoundException e)
{
Log.d("In Saving File", e + "");
}
catch(IOException e)
{
Log.d("In Saving File", e + "");
}
camera.startPreview();
newImage.recycle();
newImage = null;
setSafeToTakePhoto(true);
}
}
is there a way to make a pixel transformation in Android by program code, like you see in the pictures below? Source should be the original picture and the result the transformed picture.
It would be very nice if someone could send me the code for it.
I have no idea if it's possible with Android and how I have to programme it.
Thanks for your help
Best regards
Tom
Original picture
Picture after pixel transformation
I've found a solution....
package com.devcom_networks.graphicslibrary;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
public class Pixelize {
Bitmap originalBmp;
Bitmap obscuredBmp;
Canvas obscuredCanvas ;
Paint obscuredPaint;
private final static int PIXEL_BLOCK = 120;
public Bitmap createPixelizedBitmap(Bitmap bitmap, int pixelsize)
{
if (bitmap == null)
return null;
// Create the bitmap that we'll output from this method
obscuredBmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),bitmap.getConfig());
// Create the canvas to draw on
obscuredCanvas = new Canvas(obscuredBmp);
// Create the paint used to draw with
obscuredPaint = new Paint();
// Create a default matrix
Matrix obscuredMatrix = new Matrix();
// Draw the scaled image on the new bitmap
obscuredCanvas.drawBitmap(bitmap, obscuredMatrix, obscuredPaint);
pixelizeImage(obscuredBmp, obscuredCanvas, pixelsize);
return obscuredBmp;
}
private Bitmap pixelizeImage(Bitmap bitmap, Canvas canvas, int pixelSize)
{
originalBmp = bitmap;
if (pixelSize == 0)
pixelSize = (int)(bitmap.getWidth())/PIXEL_BLOCK;
if (pixelSize <= 0) //1 is the smallest it can be
pixelSize = 1;
int px, py;
for (int x = 0; x < originalBmp.getWidth() - 1; x+=pixelSize) {
for (int y = 0; y < originalBmp.getHeight() - 1; y+=pixelSize) {
px = (x/pixelSize)*pixelSize;
py = (y/pixelSize)*pixelSize;
try
{
int pixels[] = new int[pixelSize*pixelSize];
int newPixel = originalBmp.getPixel(px, py);
for (int i = 0; i < pixels.length; i++)
pixels[i] = newPixel;
originalBmp.setPixels(pixels, 0, pixelSize, px, py, pixelSize, pixelSize);
}
catch (IllegalArgumentException iae)
{
//something is wrong with our pixel math
break; //stop the filter
}
}
}
return originalBmp;
}
}
I would like to use the new TileProvider functionality of the latest Android Maps API (v2) to overlay some custom tiles on the GoogleMap. However as my users will not have internet a lot of the time, I want to keep the tiles stored in a zipfile/folder structure on the device. I will be generating my tiles using Maptiler with geotiffs. My questions are:
What would be the best way to store the tiles on the device?
How would I go about creating a TileProvider that returns local tiles?
You can put tiles into assets folder (if it is acceptable for the app size) or download them all on first start and put them into device storage (SD card).
You can implement TileProvider like this:
public class CustomMapTileProvider implements TileProvider {
private static final int TILE_WIDTH = 256;
private static final int TILE_HEIGHT = 256;
private static final int BUFFER_SIZE = 16 * 1024;
private AssetManager mAssets;
public CustomMapTileProvider(AssetManager assets) {
mAssets = assets;
}
#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 in = null;
ByteArrayOutputStream buffer = null;
try {
in = mAssets.open(getTileFilename(x, y, zoom));
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
private String getTileFilename(int x, int y, int zoom) {
return "map/" + zoom + '/' + x + '/' + y + ".png";
}
}
And now you can use it with your GoogleMap instance:
private void setUpMap() {
mMap.setMapType(GoogleMap.MAP_TYPE_NONE);
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(new CustomMapTileProvider(getResources().getAssets())));
CameraUpdate upd = CameraUpdateFactory.newLatLngZoom(new LatLng(LAT, LON), ZOOM);
mMap.moveCamera(upd);
}
In my case I also had a problem with y coordinate of tiles generated by MapTiler, but I managed it by adding this method into CustomMapTileProvider:
/**
* Fixing tile's y index (reversing order)
*/
private int fixYCoordinate(int y, int zoom) {
int size = 1 << zoom; // size = 2^zoom
return size - 1 - y;
}
and callig it from getTile() method like this:
#Override
public Tile getTile(int x, int y, int zoom) {
y = fixYCoordinate(y, zoom);
...
}
[Upd]
If you know exac area of your custom map, you should return NO_TILE for missing tiles from getTile(...) method.
This is how I did it:
private static final SparseArray<Rect> TILE_ZOOMS = new SparseArray<Rect>() {{
put(8, new Rect(135, 180, 135, 181 ));
put(9, new Rect(270, 361, 271, 363 ));
put(10, new Rect(541, 723, 543, 726 ));
put(11, new Rect(1082, 1447, 1086, 1452));
put(12, new Rect(2165, 2894, 2172, 2905));
put(13, new Rect(4330, 5789, 4345, 5810));
put(14, new Rect(8661, 11578, 8691, 11621));
}};
#Override
public Tile getTile(int x, int y, int zoom) {
y = fixYCoordinate(y, zoom);
if (hasTile(x, y, zoom)) {
byte[] image = readTileImage(x, y, zoom);
return image == null ? null : new Tile(TILE_WIDTH, TILE_HEIGHT, image);
} else {
return NO_TILE;
}
}
private boolean hasTile(int x, int y, int zoom) {
Rect b = TILE_ZOOMS.get(zoom);
return b == null ? false : (b.left <= x && x <= b.right && b.top <= y && y <= b.bottom);
}
The possibility of adding custom tileproviders in the new API (v2) is great, however you mention that your users are mostly offline. If a user is offline when first launching the application you cannot use the new API as it requires the user to be online (at least once to build a cache it seems) - otherwise it will only display a black screen.
EDIT 2/22-14:
I recently came across the same issue again - having custom tiles for an app which had to work offline. Solved it by adding an invisible (w/h 0/0) mapview to an initial view where the client had to download some content. This seems to work, and allows me to use a mapview in offline mode later on.
This is how I implemented this in Kotlin:
class LocalTileProvider : TileProvider
{
override fun getTile(x: Int, y: Int, zoom: Int): Tile?
{
// This is for my case only
if (zoom > 11)
return TileProvider.NO_TILE
val path = "${getImagesFolder()}/tiles/$zoom/$x/$y/filled.png"
val file = File(path)
if (!file.exists())
return TileProvider.NO_TILE
return try {
Tile(TILE_SIZE, TILE_SIZE, file.readBytes())
}
catch (e: Exception)
{
TileProvider.NO_TILE
}
}
companion object {
const val TILE_SIZE = 512
}
}