I am using the Projection class of Google maps to obtain the screen location for points in a polygon with the aim of creating a GroundOverlay and drawing a custom line style as a Path. The problem is when the camera is rotated the toScreenLocation method is returning incorrect results.
GroundOverlayOptions overlayOptions = new GroundOverlay();
Projection projection = map.getProjection();
LatLngBounds screenBounds = projection.getVisibleRegion().latLngBounds;
imageWidth = projection.toScreenLocation(screenBounds.northeast).x;
imageHeight = projection.toScreenLocation(screenBounds.southwest).y;
_overlayOptions.positionFromBounds(screenBounds);
Bitmap bmp = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
Path path = new Path();
List<LatLng> points = polygon.getPoints();
Point startPos = projection.toScreenLocation(points.get(0));
path.moveTo(startPos.x, startPos.y);
for (int i = 1; i < points.size(); i++) {
Point nextPos = projection.toScreenLocation(points.get(i));
path.lineTo(nextPos.x, nextPos.y);
}
path.lineTo(startX, startY);
canvas.drawPath(path, paint);
BitmapDescriptor bmpDesc = BitmapDescriptorFactory.fromBitmap(bmp);
overlayOptions.image(bmpDesc);
map.addGroundOverlay(overlayOptions);
The image below illustrates the problem. When the map is orientated North the blue dashed line renders where it is supposed to but when the bearing is changed the result of toScreenLocation gives me warped coordinates.
I've attempted to apply a matrix rotate transformation on the bitmap
Matrix rotationMatrix = new Matrix();
rotationMatrix.postRotate(map.getCameraPosition().bearing, imageWidth / 2, imageHeight / 2);
and I have also tried using trigonometry to rotate the points. Neither approach was successful.
So my question is how do you get the screen location of a LatLng that is independent of the camera orientation?
I think the screen locations are correct. But you do not use them to paint directly into the screen, but into a bitmap, which is by default oriented north up. When you apply this bitmap as GroundOverlay it is rotated together with the map and thus the locations do no longer fit.
See also the Documentation:
Bearing
The amount that the image should be rotated in a clockwise direction. The center of the rotation will be the image's anchor. This is optional and the default bearing is 0, i.e., the image is aligned so that up is north.
I am not sure, but I think you have to use the current rotation of the map as negative bearing for your GroundOverlay to compensate the effect. And I am also not sure, how a tilt of the map can or must be compensated. I would just try. The anchor point should most probably be the center of the image.
But in any case, I think it's important to realize that the screen locations are not really wrong. They just have the real screen as reference system, which is different from the bitmaps system after it is rotated.
Related
I want to make a custom image cropper for Android that supports rotation. Cropper works fine. The problem that i am facing while rotating the image (bitmap of the using using rotation matrix) the crop overlay area should also be rotated accordingly. For that I rotated the points of the crop shape by applying another rotation matrix. It map the points of the crop shape (specifically the bounding box pointed by different color circle in the image). The rotation matrix is applied for crop shape rotating is applied with the center of the crop shape as origin. But after rotating the crop shape the points of the shape should be translated to maintain the same position and aspect ratio. Can any one help me please to achieve this? [ I am sharing some code snippet to rotate the bitmap and crop shape points
private Bitmap rotateBitmap(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
Bitmap rotatedBitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
rotateShapePoints(angle);
return rotatedBitmap;
}
Rotate Shape Points
private void rotateShapePoints(float angle)
{
PointF centerPoint = getCropShapeView().getCenterPoint(); // return the center of the shape
Matrix matrix = new Matrix();
matrix.postRotate(angle,centerPoint.x,centerPoint.y);
HashMap<Integer,PointF> points = (HashMap<Integer, PointF>) getCropShapeView().getPoints(); // map is used to track the point at different position
for(Map.Entry<Integer,PointF> entry : points.entrySet())
{
int key = entry.getKey();
PointF value = entry.getValue();
float[] pointArray = new float[] { value.x,value.y};
matrix.mapPoints(pointArray);
PointF rotatedPoint = new PointF(Math.abs(pointArray[0]),Math.abs(pointArray[1]));
points.put(key,rotatedPoint);
}
getCropShapeView().setPoints(points);
getCropShapeView().invalidate();
}
I'm trying to overlay a bitmap onto another, placing it at the location the user touches. Here's the code:
public static Bitmap mergeImage(Bitmap base, Bitmap overlay, float x, float y)
{
Bitmap mBitmap = Bitmap.createBitmap(base.getWidth(), base.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmap);
canvas.drawBitmap(base, 0, 0, null);
canvas.drawBitmap(overlay, x, y, null);
return mBitmap;
}
The issue here is, even though the x & y coordinates are obtained correctly (I checked), the overlay bitmap does not place correctly.
When around the top left portion of the image, placement is correct. However, as I move right and down, the location seems to scale differently (i.e. if I touch the bottom right corner of the screen, the overlay places somewhere near the middle of the image, if I touch bottom left, it places near the middle left of the image and so on)
Both images have the same density (320).
Edit: New issue, i reduced the sizes of both images and now placement is roughly accurate. But saving the image to SD card skews the overlay image to a different (and quite random) location
I found the solution using Matrix for set location and scale x,y
public static Bitmap mergeImage(Bitmap base, Bitmap overlay, float x, float y)
{
Bitmap mBitmap = Bitmap.createBitmap(base.getWidth(), base.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmap);
Matrix matrix = new Matrix ();
matrix.postTranslate( x,y);
canvas.drawBitmap(base, 0, 0, null);
canvas.drawBitmap(overlay, matrix, null);
return mBitmap;
}
I spend something similar and I found the solution, you can see my post in the siguiete link
canvas.drawBitmap bad Location and size (not placing image correctly)
I'm building a "navigation type" app for android.
For the navigation part I'm building an Activity where the user can move and zoom the map (which is a bitmap) using touch events, and also the map rotate around the center of the screen using the compass.
I'm using Matrix to scale, transpose and rotate the image, and than I draw it to the canvas.
Here is the code called on loading of the view, to center the image in the screen:
image = new Matrix();
image.setScale(zoom, zoom);
image_center = new PointF(bmp.getWidth() / 2, bmp.getHeight() / 2);
float centerScaledWidth = image_center.x * zoom;
float centerScaledHeigth = image_center.y * zoom;
image.postTranslate(
screen_center.x - centerScaledWidth,
screen_center.y - centerScaledHeigth);
The rotation of the image is doing using the postRotate method.
Then in the onDraw() method I only call
canvas.drawBitmap(bmp, image, drawPaint);
The problem is that, when the user touch the screen, I want to get the point touched on the image, but apparently I can't get the correct position.
I tried to invert the image matrix and translate the touched points, it isn't working.
Do somebody know how to translate the point coordinates?
EDIT
I'm using this code for traslation.
dx and dy are translation values get from the onTouch listener.
*new_center* is an array of float values in this form {x0, y0, x1, y1...}
Matrix translated = new Matrix();
Matrix inverted = new Matrix();
translated.set(image);
translated.postTranslate(dx, dy);
translated.invert(inverted);
inverted.mapPoints(new_center);
translated.mapPoints(new_center);
Log.i("new_center", new_center[0]+" "+new_center[1]);
Actually I tried using as *new_center = {0,0}*:
appling only the translated matrix, I get as espected the distance between the (0,0) point of the bmp and the (0,0) point of the screen, but it seems to not take account of the rotation.
Appling the inverted matrix to the points I get those results, moving the image in every possible way.
12-26 13:26:08.481: I/new_center(11537): 1.9073486E-6 -1.4901161E-7
12-26 13:26:08.581: I/new_center(11537): 0.0 -3.874302E-7
12-26 13:26:08.631: I/new_center(11537): 1.9073486E-6 1.2516975E-6
12-26 13:26:08.781: I/new_center(11537): -1.9073486E-6 -5.364418E-7
12-26 13:26:08.951: I/new_center(11537): 0.0 2.682209E-7
12-26 13:26:09.093: I/new_center(11537): 0.0 7.003546E-7
Instead I was especting the coordinates translated on the image.
Is it correct my line of thoughts?
Ok, I get it.
First I separated the rotation from the translation and zooming of image.
Because I created a custom ImageView, this was simple. I apply the rotation to the canvas of the ImageView, and the other transformations to the matrix of the image.
I get trace of the canva's matrix throught a global matrix variable.
Some code:
To set the correct movement for the corresponding onTouch event, first I "rotate back" the points passed from onTouch (start and stop points) using the inverse of the matrix of the canvas
Then I calculate the difference between x and y, and apply that to the image matrix.
float[] movement = {start.x, start.y, stop.x, stop.y};
Matrix c_t = new Matrix();
canvas.invert(c_t);
c_t.mapPoints(movement);
float dx = movement[2] - movement[0];
float dy = movement[3] - movement[1];
image.postTranslate(dx, dy);
If instead you want to check that the image movement don't exceed its size, before the image.postTranslate(dx, dy); you put this code:
float[] new_center = {screen_center.x, screen_center.y};
Matrix copy = new Matrix();
copy.set(image);
copy.postTranslate(dx, dy);
Matrix translated = new Matrix();
copy.invert(translated);
translated.mapPoints(new_center);
if ((new_center[0] > 0) && (new_center[0] < bmp.getWidth()) &&
(new_center[1] > 0) && (new_center[1] < bmp.getHeight())) {
// you can remove the image.postTranslate and copy the "copy" matrix instead
image.set(copy);
...
It's important to note that:
A) The center rotation of the image is the center of the screen, so it will not change coordinates during the canvas' rotation
B) You can use the coordinates of the center of the screen to get the rotation center of the image.
With this method you can also convert every touch event to image coordinates.
I am trying to implement my own map overlay for osmdroid (but I assume it is fairly similar to Google map overlays).
What I am trying to do is draw a plane, rotate it according to bearing and draw a speed vector (line ahead of the plane in the flight direction that shows where it will soon be).
The idea is that I draw the plane (et all) on a canvas "facing North", then rotate it according to flight direction and "merge" it with the overlay canvas (I tried drawing directly to the overlay canvas, but on rotate, it was rotating the map as well).
I have created a subclass of Overlay and overiden the onDraw method as follows:
#Override
protected void draw(Canvas c, MapView mapView, boolean shadow) {
if (location != null) {
Point locPoint = new Point();
GeoPoint locGeoPoint = new GeoPoint(location);
final Projection pj = mapView.getProjection();
pj.toMapPixels(locGeoPoint, locPoint);
this.drawPlane(c, locPoint, location.getBearing());
}
}
private void drawPlane(Canvas cs, Point ctr, float bearing) {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
Bitmap planeBM = Bitmap.createBitmap(cs.getWidth(), cs.getHeight(), Bitmap.Config.ARGB_8888);
planeBM.setDensity(cs.getDensity());
Canvas c = new Canvas(planeBM);
Rect r = new Rect();
//Point center = new Point(cs.getWidth() / 2, cs.getHeight() /2);
Point center = new Point(0, 0);
// Draw fuselage
r.left = center.x - PLANE_WIDTH / 2;
r.right = r.left + PLANE_WIDTH;
r.top = center.y - PLANE_SIZE / 3;
r.bottom = r.top + PLANE_SIZE;
c.drawRect(r, paint);
// Draw wing (REMOVED)
// Draw stabilizer (REMOVED)
// TODO Draw Speed vector
// "Merging" canvas
Matrix merge = new Matrix(cs.getMatrix());
//merge.setTranslate(0, 0);
//merge.setRotate(bearing, center.x, center.y);
cs.drawBitmap(planeBM, merge, paint);
cs.save();
}
Basically my plane never shows.
I assume this has to do with the matrix in the initial canvas which has large values (I assume these are sort of geographical coordinates).
It all seems to be consistent though (the plane location has large values as well consistent with the matrix).
I have tried a number of things :
drawing from the actual plane location (large values) : did not help;
setting the matrix of my new canvas with the overlay canvas matrix : did not help;
merging with a new "empty" matrix : did not help;
-...
I know that my image contains the plane (at least if I draw from 0,0 or the new canvas center as I saved it to the SD to check...
In case this is usefull to someone, I finally found a solution.
Basicaly I was trying to do too much at the same time.
Using setPostRotate instead of setRotate on the merge matrix did solve the issue (that and goig bac to the drawing board for the correct translation parameters).
Using the below as a merge matrix worked:
// "Merging" canvas
Matrix merge = new Matrix();
merge.setTranslate(loc.x - pCenter.x, loc.y - pCenter.y);
merge.postRotate(bearing, loc.x, loc.y);
I use that Google Maps component MapView in an Android application. I can use the GPS location to show my location with a dot. But I would like to show an arrow instead, that points out the driving direction (bearing). I think that I can use the bearing value to get the angle of the arrow.
How can I do that?
Assuming you've got the Location then obtain the bearing by doing:
float myBearing = location.getBearing();
To implement the overlay you'll be using ItemizedOverlay and OverlayItem. You'll need to subclass OverlayItem to add the functionality to rotate the Drawable. Something like:
public BitmapDrawable rotateDrawable(float angle)
{
Bitmap arrowBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.map_pin);
// Create blank bitmap of equal size
Bitmap canvasBitmap = arrowBitmap.copy(Bitmap.Config.ARGB_8888, true);
canvasBitmap.eraseColor(0x00000000);
// Create canvas
Canvas canvas = new Canvas(canvasBitmap);
// Create rotation matrix
Matrix rotateMatrix = new Matrix();
rotateMatrix.setRotate(angle, canvas.getWidth()/2, canvas.getHeight()/2);
// Draw bitmap onto canvas using matrix
canvas.drawBitmap(arrowBitmap, rotateMatrix, null);
return new BitmapDrawable(canvasBitmap);
}
Then all that remains to be done is to apply this new Drawable to the OverlayItem. This is done using the setMarker() method.