TileProvider Graphics Kinks at Higher Scale Levels

I am currently playing with TileProver in the Android Maps API v2 and I remember the following problem: the graphics that I draw manually in Bitmap are significantly distorted at higher zoom levels:

graphics is skewed

Let me explain what I'm doing here. I have the number of LatLng points, and I draw a circle for each point on the map, so that you increase the breakpoint at the same geographical location. As you can see in the screenshot, the circles look great at lower zoom levels, but as you start zooming, the circles become distorted.

How is it implemented:

 package trickyandroid.com.locationtracking; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.util.Log; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Tile; import com.google.android.gms.maps.model.TileProvider; import com.google.maps.android.geometry.Point; import com.google.maps.android.projection.SphericalMercatorProjection; import java.io.ByteArrayOutputStream; /** * Created by paveld on 8/8/14. */ public class CustomTileProvider implements TileProvider { private final int TILE_SIZE = 256; private int density = 1; private int tileSizeScaled; private Paint circlePaint; private SphericalMercatorProjection projection; private Point[] points; public CustomTileProvider(Context context) { density = 3; //hardcoded for now, but should be driven by DisplayMetrics.density tileSizeScaled = TILE_SIZE * density; projection = new SphericalMercatorProjection(TILE_SIZE); points = generatePoints(); circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setColor(0xFF000000); circlePaint.setStyle(Paint.Style.FILL); } private Point[] generatePoints() { Point[] points = new Point[6]; points[0] = projection.toPoint(new LatLng(47.603861, -122.333393)); points[1] = projection.toPoint(new LatLng(47.600389, -122.326741)); points[2] = projection.toPoint(new LatLng(47.598942, -122.318973)); points[3] = projection.toPoint(new LatLng(47.599000, -122.311549)); points[4] = projection.toPoint(new LatLng(47.601373, -122.301721)); points[5] = projection.toPoint(new LatLng(47.609764, -122.311850)); return points; } @Override public Tile getTile(int x, int y, int zoom) { Bitmap bitmap = Bitmap.createBitmap(tileSizeScaled, tileSizeScaled, Bitmap.Config.ARGB_8888); float scale = (float) (Math.pow(2, zoom) * density); Matrix m = new Matrix(); m.setScale(scale, scale); m.postTranslate(-x * tileSizeScaled, -y * tileSizeScaled); Canvas c = new Canvas(bitmap); c.setMatrix(m); for (Point p : points) { c.drawCircle((float) px, (float) py, 20 / scale, circlePaint); } return bitmapToTile(bitmap); } private Tile bitmapToTile(Bitmap bmp) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] bitmapdata = stream.toByteArray(); return new Tile(tileSizeScaled, tileSizeScaled, bitmapdata); } } 

The logic tells me that this happens because I put LatLng in the screen position for only 1 tile (256x256, which is the zoom level 0), and then to translate this screen point to other zoom levels I need to scale the bitmap and translate it to appropriate position. At the same time, since the bitmap is scaled, I need to compensate for the radius of the circle, so I divide the radius by the scale factor. Thus, at zoom level 19, my scale factor is already 1572864, which is huge. It is like viewing this circle with a huge magnifying glass. That's why I have this effect.

So, I assume that the solution will be to avoid scaling the bitmap and scaling / translating only the screen coordinates. In this case, the radius of the circle will always be the same and will not decrease.

Unfortunately, mathematical mathematics is not my strongest skill, so my question is: how do I scale / translate a lot of points for any zoom level with a set of points calculated for the zoom level "0"?

The easiest way to do this is to have different instances of Projection for each zoom level, but since GeoPoint β†’ ScreenPoint translation is quite an expensive operation, I would save this approach as a backup and use some simple math to translate existing screen points.

Note Please note that I need to specifically configure TileProvider , since in the application I will draw much more complex tiles than just circles. Therefore simple Marker class will not work for me here

UPDATE Although I figured out how to translate individual points and avoid scaling the bitmap:

 c.drawCircle((float) px * scale - (x * tileSizeScaled), (float) py * scale - (y * tileSizeScaled), 20, circlePaint); 

I still don't know how to do this with Path objects. I can’t translate / scale the path, as you would do with separate points, so I still need to scale the bitmap, which again draws artifacts (the stroke width is distorted at higher zoom levels):

enter image description here

And here is the code snippet:

 package trickyandroid.com.locationtracking; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Tile; import com.google.android.gms.maps.model.TileProvider; import com.google.maps.android.geometry.Point; import com.google.maps.android.projection.SphericalMercatorProjection; import java.io.ByteArrayOutputStream; /** * Created by paveld on 8/8/14. */ public class CustomTileProvider implements TileProvider { private final int TILE_SIZE = 256; private int density = 1; private int tileSizeScaled; private SphericalMercatorProjection projection; private Point[] points; private Path path; private Paint pathPaint; public CustomTileProvider(Context context) { density = 3; //hardcoded for now, but should be driven by DisplayMetrics.density tileSizeScaled = TILE_SIZE * density; projection = new SphericalMercatorProjection(TILE_SIZE); points = generatePoints(); path = generatePath(points); pathPaint = new Paint(); pathPaint.setAntiAlias(true); pathPaint.setColor(0xFF000000); pathPaint.setStyle(Paint.Style.STROKE); pathPaint.setStrokeCap(Paint.Cap.ROUND); pathPaint.setStrokeJoin(Paint.Join.ROUND); } private Path generatePath(Point[] points) { Path path = new Path(); path.moveTo((float) points[0].x, (float) points[0].y); for (int i = 1; i < points.length; i++) { path.lineTo((float) points[i].x, (float) points[i].y); } return path; } private Point[] generatePoints() { Point[] points = new Point[10]; points[0] = projection.toPoint(new LatLng(47.603861, -122.333393)); points[1] = projection.toPoint(new LatLng(47.600389, -122.326741)); points[2] = projection.toPoint(new LatLng(47.598942, -122.318973)); points[3] = projection.toPoint(new LatLng(47.599000, -122.311549)); points[4] = projection.toPoint(new LatLng(47.601373, -122.301721)); points[5] = projection.toPoint(new LatLng(47.609764, -122.311850)); points[6] = projection.toPoint(new LatLng(47.599221, -122.311531)); points[7] = projection.toPoint(new LatLng(47.599663, -122.312410)); points[8] = projection.toPoint(new LatLng(47.598823, -122.312614)); points[9] = projection.toPoint(new LatLng(47.599959, -122.310651)); return points; } @Override public Tile getTile(int x, int y, int zoom) { Bitmap bitmap = Bitmap.createBitmap(tileSizeScaled, tileSizeScaled, Bitmap.Config.ARGB_8888); float scale = (float) (Math.pow(2, zoom) * density); Canvas c = new Canvas(bitmap); Matrix m = new Matrix(); m.setScale(scale, scale); m.postTranslate(-x * tileSizeScaled, -y * tileSizeScaled); c.setMatrix(m); pathPaint.setStrokeWidth(6 * density / scale); c.drawPath(path, pathPaint); return bitmapToTile(bitmap); } private Tile bitmapToTile(Bitmap bmp) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] bitmapdata = stream.toByteArray(); return new Tile(tileSizeScaled, tileSizeScaled, bitmapdata); } } 
+7
android google-maps-android-api-2 graphics tile
source share
1 answer

I see that you are using google tileview, you can try and consider the mogarius library, which is open source on github

I have never tried it before, but it supports most of the functions you need (markers \ points and dynamic path drawing) out of the box, so you save time spent creating matrix calculations for scaling and scaling. There is also a video demonstration for some use that he made, and a large javadoc he posted.

+2
source share

All Articles