I want to use my image to replace the Compass image of MyLocationOverlay, how can i implement?
Subclass MyLocationOverlay, override the method drawCompass(Canvas canvas, float bearing), and draw your own bitmap(s) onto the canvas object:
#Override
protected void drawCompass(Canvas canvas, float bearing) {
Resources res = _context.getResources();
Bitmap myCompassPointer = BitmapFactory.decodeResource(res, R.drawable.compass_pointer);
float rotationAngle = -bearing + 360f;
Matrix rotation = new Matrix();
rotation.preRotate(rotationAngle, myCompassPointer.getWidth()/2.0f, myCompassPointer.getHeight()/2.0f);
canvas.drawBitmap(myCompassPointer, rotation, null);
// don't call super if you don't want the default compass image:
//super.drawCompass(canvas, bearing);
}
I think you can only replace the Marker (the blue, pulsating dot) which displays the location, please see a related StackOverflow question. Hope this helps, even if the issue seems a bit old.
Related
I have searched all internet but I don't find answer to my problem .I'm using osmdroid and I want to add grid over polygon as shown in image. I found one similar question in stackoverflow but this question doesn't have answer. So please tell me is that possible?
#Mker gave a good point to start: BitmapShader.
Here is a sample code:
public class GridPolygon extends Polygon {
private BitmapShader bitmapShader;
public GridPolygon(Context ctx) {
super(ctx);
}
public void setPatternBMP(#NonNull final Bitmap patternBMP) {
bitmapShader = new BitmapShader(patternBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mFillPaint.setShader(bitmapShader);
}
}
Usage:
final GridPolygon polygon = new GridPolygon(context);
polygon.setPoints(geoData);
polygon.setFillColor(fillColor);
polygon.setStrokeColor(strokeColor);
polygon.setStrokeWidth(strokeWidth);
polygon.setPatternBMP(BitmapFactory.decodeResource(getResources(), R.drawable.pattern));
map.getOverlays().add(polygon);
map.invalidate();
But you might be confused if you tried to move the polygon - the bitmap doesn't want to move:
To avoid this you should calculate the offset for your shader:
public class GridPolygon extends Polygon {
private BitmapShader bitmapShader;
private IGeoPoint lastCenterGeoPoint;
private int xOffset = 0;
private int yOffset = 0;
public GridPolygon(Context ctx) {
super(ctx);
}
public void setPatternBMP(#NonNull final Bitmap patternBMP) {
bitmapShader = new BitmapShader(patternBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mFillPaint.setShader(bitmapShader);
}
protected void recalculateMatrix(#NonNull final MapView mapView) {
//final int mapSize = TileSystem.MapSize(mapView.getZoomLevel());
final Projection projection = mapView.getProjection();
final IGeoPoint geoPoint = mapView.getMapCenter();
if (lastCenterGeoPoint == null) lastCenterGeoPoint = geoPoint;
final Point point = projection.toPixels(geoPoint, null);
final Point lastCenterPoint = projection.toPixels(lastCenterGeoPoint, null);
xOffset += lastCenterPoint.x - point.x;
yOffset += lastCenterPoint.y - point.y;
xOffset %= 100; // 100 is pixel size of shader image
yOffset %= 100;
final Matrix matrix = new Matrix();
matrix.reset();
matrix.setScale(1,1);
matrix.preTranslate(xOffset, yOffset);
//matrix.setTranslate(xOffset, yOffset);
bitmapShader.setLocalMatrix(matrix);
mFillPaint.setShader(bitmapShader);
lastCenterGeoPoint = geoPoint;
}
#Override
protected void draw(Canvas canvas, MapView mapView, boolean shadow) {
recalculateMatrix(mapView);
super.draw(canvas, mapView, shadow);
}
}
Result:
Full source code.
Yes it's possible.
There's a few potential solutions.
1) Assuming someone was nice enough to make a kml file that meets your needs, the kml file can be directly imported using osmbonuspack.
2) Make it yourself programatically. So you have a few tasks.
a) Make the polygon as an overlay
b) Make the grid as an overlay
c) Add them to the map view in that order. This should make the grid be on top of the polygon.
Now on to the details. Making the polygon is trivial so won't cover this here.
Making the grid isn't too hard either. You need to know the bounds of the grid, then place lines from the east, west bounds at some interval from the north bounds to the south bounds. Then do the opposite for north south lines. There's special cases at the date line, equator, and poles so keep that in mind.
Calculating the line interval in this case is somewhat simple and you can tackle it two ways. Use a fixed interval in degrees decimal or calculate based on zoom level. The later part is harder but generally gives a better visualization (when you zoom in, the grid redraws and looks more appropriate at that zoom level).
Important note, with osmbonuspack and osmdroid, you may run into out of memory errors if you give the overlay lines that are way outside of the bounds of the view (if hardware acceleration is off). If hardware acceleration is on, then lines may not show at all if both the start and end points are off screen by a certain margin. Long story short, for relatively small distances, you should be fine, otherwise, you have to clip at the view bounds on map panning and zooming.
I've done similar things with osmbonuspack for displaying lat/lon grid lines that adjust as you zoom in and pan (meaning the interval adjusts based on on zoom level). If that's a requirement, then you might be able to just reuse the code, which essentially calculates about how far away and where to draw each line of the grid.
Now, if you just want to draw the grid as a pattern (no constraint about grid lines positions), there should be a simple alternative by using a "shader":
fillPaint.setShader(patternBMPshader);
Full example: http://code.tutsplus.com/tutorials/android-sdk-drawing-with-pattern-fills--mobile-19527
Bad news, there is no getter of the Polygon fill paint. Good news, the attribute is protected, not private.
So you can subclass Polygon, and add the getter:
Paint getFillPaint(){
return mFillPaint;
}
I'm trying to rotate my Bitmap using a readymade solution I found somewhere. The code is below:
public void onDraw(Canvas canvas) {
float x = ship.Position.left;
float y = ship.Position.top;
canvas.drawBitmap(ship.ship, x,y,null);
invalidate();
}
However, when I do it, the X and Y axii change their direction - if I increase the Y the image goes towards the top of the screen, not towards the bottom. Same happens to X if I rotate by 90 degrees.
I need to rotate it but without changing the Y and X axii directions.
Even rotated, I still want the Bitmap to go towards the bottom if I increase Y and to the right if I increase the X.
public void update()
{
if(!moving)
{
fall();
}
else //moving
{
move();
faceDirection();
}
Position.top += Speed;
}
private void move() {
if(Speed < MAXSPEED)
Speed -= 0.5f;
}
private void fall() {
if(Speed > MAXSPEED*-1)
Speed += 0.2f;
}
private void faceDirection() {
double OldDiretion = Direction;
Direction = DirectionHelper.FaceObject(Position, ClickedDiretion);
if (Direction != OldDiretion)
{
Matrix matrix = new Matrix();
matrix.postRotate((float)Direction);
ship = Bitmap.createBitmap(ship, 0, 0, ship.getWidth(),ship.getHeight(), matrix, false);
}
I tried the code above, but it's still changing the Y direction, It's going to bottom of the BitMap, not bottom of the screen.
Here is the project: https://docs.google.com/file/d/0B8V9oTk0eiOKOUZJMWtsSmUtV3M/edit?usp=sharing
You should first rotate, than translate:
matrix.postTranslate(x, y);
matrix.postRotate(degree);
alternative would be to try to use preRotate() instead of postRotate().
I also strongly recommend to translate/rotate the original while drawing. So your createBitmap() call shouldn't modify the orientation. At least not when you change it dynamically on user interaction. Otherwise you would create a lot of bitmaps to represent rotations over and over again which would impact the performance.
The problem is that you don't actually rotate the bitmap - you just draw it rotated. So, the next time you redraw it, you first push it towards the bottom or right by incrementing x/y and then rotate it.
You have to actually rotate the bitmap itself. You could use the following code:
ship.ship = Bitmap.createBitmap(ship.ship, 0, 0, ship.ship.getWidth(), ship.ship.getHeight(), matrix, false);
Here you create a new rotated bitmap and set your reference to point to it.
Note! You must do this only once! So you can't do it in the onDraw method, since then it will get rotated every time it's redrawn. You have to do it somewhere else and then draw it as usual in onDraw (without the matrix rotations).
I am trying to gain some more familiarity with the Android SurfaceView class, and in doing so am attempting to create a simple application that allows a user to move a Bitmap around the screen. The troublesome part of this implementation is that I am also including the functionality that the user may drag the image again after it has been placed. In order to do this, I am mapping the bitmap to a simple set of coordinates that define the Bitmap's current location. The region I am mapping the image to, however, does not match up with the image.
The Problem
After placing an image on the SurfaceView using canvas.drawBitmap(), and recording the coordinates of the placed image, the mapping system that I have set up misinterprets the Bitmap's coordinates somehow and does not display correctly. As you can see in this image, I have simply used canvas.drawLine() to draw lines representing the space of my touch region, and the image is always off and to the right:
The Code
Here, I shall provide the relevant code excerpts to help answer my question.
CustomSurface.java
This method encapsulates the drawing of the objects onto the canvas. The comments clarify each element:
public void onDraw(Canvas c){
//Simple black paint
Paint paint = new Paint();
//Draw a white background
c.drawColor(Color.WHITE);
//Draw the bitmap at the coordinates
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
//Draws the actual surface that is receiving touch input
c.drawLine(g.left, g.top, g.right, g.top, paint);
c.drawLine(g.right, g.top, g.right, g.bottom, paint);
c.drawLine(g.right, g.bottom, g.left, g.bottom, paint);
c.drawLine(g.left, g.bottom, g.left, g.top, paint);
}
This method encapsulates how I capture touch events:
public boolean onTouchEvent(MotionEvent e){
switch(e.getAction()){
case MotionEvent.ACTION_DOWN:{
if(g.contains((int) e.getX(), (int) e.getY()))
item_selected = true;
break;
}
case MotionEvent.ACTION_MOVE:{
if(item_selected)
g.move((int) e.getX(), (int) e.getY());
break;
}
case MotionEvent.ACTION_UP:{
item_selected = false;
break;
}
default:{
//Do nothing
break;
}
}
return true;
}
Graphic.java
This method is used to construct the Graphic:
//Initializes the graphic assuming the coordinate is in the upper left corner
public Graphic(Bitmap image, int start_x, int start_y){
resource = image;
left = start_x;
top = start_y;
right = start_x + image.getWidth();
bottom = start_y + image.getHeight();
}
This method detects if a user is clicking inside the image:
public boolean contains(int x, int y){
if(x >= left && x <= right){
if(y >= top && y <= bottom){
return true;
}
}
return false;
}
This method is used to move the graphic:
public void move(int x, int y){
left = x;
top = y;
right = x + resource.getWidth();
bottom = y + resource.getHeight();
}
I also have 2 methods that determine the center of the region (used for redrawing):
public int getCenterX(){
return (right - left) / 2 + left;
}
public int getCenterY(){
return (bottom - top) / 2 + top;
}
Any help would be greatly appreciated, I feel as though many other StackOverflow users could really benefit from a solution to this issue.
There's a very nice and thorough explanation of touch/multitouch/gestures on Android Developers blog, that includes free and open source code example at google code.
Please, take a look. If you don't need gestures -- just skip that part, read about touch events only.
This issue ended up being much simpler than I had thought, and after some tweaking I realized that this was an issue of image width compensation.
This line in the above code is where the error stems from:
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
As you can tell, I manipulated the coordinates from within the Graphic class to produce the center of the bitmap, and then called canvas.drawBitmap() assuming that it would draw from the center outward.
Obviously, this would not work because the canvas always drops from the top left of an image downwards and to the right, so the solution was simple.
The Solution
Create the touch region with regards to the touch location, but draw it relative to a distance equal to the image width subtracted from the center location in the x and y directions. I basically changed the architecture of the Graphic class to implement a getDrawX() and getDrawY() method that would return the modified x and y coordinates of where it should be drawn in order to have the center_x and center_y values (determined in the constructor) actually appear to be at the center of the region.
It all comes down to the fact that in an attempt to compensate for the way the canvas draws bitmaps, I unfortunately incorporated some bad behaviors and in the end had to handle the offset in a completely different way.
I'm trying to use something like a compass, passing it longitude/latitude values to let it point to a specific location, my code can draw an arrow while moving the phone (using GPS) to determine the location.
I want to use an image instead of the drawing thing
public void draw(Canvas canvas) {
double angle = calculateAngle(currentLongitude, currentLatitude, targetLongitude, targetLatitude);
//Correction;
angle-=90;
//Correction for azimuth
angle-=azimuth;
if((getContext() instanceof Activity) && ((Activity)getContext()).getWindowManager().getDefaultDisplay().getOrientation()==Configuration.ORIENTATION_PORTRAIT)angle-=90;
while(angle<0)angle=angle+360;
Rect rect = canvas.getClipBounds();
int height = rect.bottom-rect.top;
int width = rect.right-rect.left;
int left = rect.left;
int top = rect.top;
}
You need to do two things:
Rotate the pointer image
Draw the resulting bitmap to screen.
A few tricks, it might be faster to pre-rotate the image when your program starts up rather than rotating it every time you draw; however it would also take more memory.
I've developed a GPS app in which i record the user roots and show it
on the map.......but
Panning around on the map when reviewing my route is painfully slow,
it takes at least 4 or 5 seconds for the map to respond the finger
swipes......
I've overridden the onDraw() method and drawing the lines to show the
routes......is there any better way to do this so that panning becomes
faster as in "MyTracks"...........
Thank you all.....
Pratap S.
I've had to do something similar. My attempt currently does the following in onDraw (simplified for readability - error-handling etc. stripped out):
if ((bmap == null) || (lastZoom != mapv.getLatitudeSpan()))
{
// bitmap is null - so we haven't previously drawn the path, OR
// the map has been zoomed in/out, so we're gonna re-draw it anyway
// (alternatively, I could have tried scaling the bitmap... might
// be worth investigating if that is more efficient)
Projection proj = mapv.getProjection();
// store zoom level for comparing in the next onDraw
lastZoom = mapv.getLatitudeSpan();
// draw a path of all of the points in my route
GeoPoint start = routePoints.get(0);
Point startPt = new Point();
proj.toPixels(start, startPt);
Path path = new Path();
path.moveTo(startPt.x, startPt.y);
Point nxtPt;
for (GeoPoint nextPoint : routePoints)
{
nxtPt = new Point();
proj.toPixels(nextPoint, nxtPt);
path.lineTo(nxtPt.x, nxtPt.y);
}
// create a new bitmap, the size of the map view
bmap = Bitmap.createBitmap(mapv.getWidth(), mapv.getHeight(), Bitmap.Config.ARGB_8888);
// create an off-screen canvas to prepare new bitmap, and draw path on to it
Canvas offscreencanvas = new Canvas(bmap);
offscreencanvas.drawPath(path, mPaint);
// draw the bitmap of the path onto my map view's canvas
canvas.drawBitmap(bmap, 0, 0, null);
// make a note of where we put the bitmap, so we know how much we
// we need to move it by if the user pans the map
mapStartPosition = proj.fromPixels(0, 0);
}
else
{
// as we're in onDraw, we think the user has panned/moved the map
// if we're in here, the zoom level hasn't changed, and
// we've already got a bitmap with a drawing of the route path
Projection proj = mapv.getProjection();
// where has the mapview been panned to?
Point offsetPt = new Point();
proj.toPixels(mapStartPosition, offsetPt);
// draw the bitmap in the new correct location
canvas.drawBitmap(bmap, offsetPt.x, offsetPt.y, null);
}
It's not perfect yet.... for example, the path ends up in the wrong place immediately after zooming - being moved to the correct place once the user starts panning.
But it's a start - and hugely more efficient than redrawing the path on every onDraw call
Hope this helps!
Comment to dalelane's answer from May,7th:
I used your solution for reducing the load of drawing, but modified it a bit:
a new bitmap is created, if the map center, the zoom levelhave cjanged or no old bitmap exists.
After zooming the route is placed on the correct position. It seems that zooming has not finished completely, when a changed zoom level is detected.
I used a timer, which modifies the map center by 10 after a delay of 600 msecs after the zoom level changed.
By changing the map center the draw method is called and creates a new bitmap. The route then is placed correctly.
This is an ugly work around. Has anyone a better solution?
private void panAfterZoom(MapView mv, long delay){
timer = new java.util.Timer("drawtimer", true);
mapView=mv;
task = new java.util.TimerTask() {
public void run() {
GeoPoint center=mapView.getMapCenter();
GeoPoint point=new GeoPoint(center.getLatitudeE6()+10, center.getLongitudeE6());
MapController contr=mapView.getController();
contr.setCenter(point);
timer.cancel();
}
};
timer.schedule(task, delay);
}
This is called in the draw method as: pabAfterZoom(mapView, 600);
Bost
My thanks to dalelane, who's proposal above helped me improving my route overlay.
I would like to share an improvement that solves the problem with path ending in the wrong place after a zoom change.
Problem root cause:
The mapview.getLatitudeSpan() as well as the mapview.getZoomLevel() methods return the values not taking into consideration the progressive map scale variation (animation) between to zoom values.
Solution:
The method mapview.getProjection().fromPixels(x,y) take this progressive variation into account, so you can build your getLatitudeSpan() or getLongitudeSpan() from it, and the route will always display correctly.
Below is the dalelane proposed code with the changes made:
**int lonSpanNew = mapv.getProjection().fromPixels(0,mapv.getHeight()/2).getLongitudeE6() - mapv.getProjection().fromPixels(mapv.getWidth(),mapview.getHeight()/2).getLongitudeE6();**
if ((bmap == null) || (lastZoom != **lonSpanNew** ))
{
// bitmap is null - so we haven't previously drawn the path, OR
// the map has been zoomed in/out, so we're gonna re-draw it anyway
// (alternatively, I could have tried scaling the bitmap... might
// be worth investigating if that is more efficient)
Projection proj = mapv.getProjection();
// store zoom level for comparing in the next onDraw
lastZoom = **lonSpanNew**;
// draw a path of all of the points in my route
GeoPoint start = routePoints.get(0);
Point startPt = new Point();
proj.toPixels(start, startPt);
Path path = new Path();
path.moveTo(startPt.x, startPt.y);
Point nxtPt;
for (GeoPoint nextPoint : routePoints)
{
nxtPt = new Point();
proj.toPixels(nextPoint, nxtPt);
path.lineTo(nxtPt.x, nxtPt.y);
}
// create a new bitmap, the size of the map view
bmap = Bitmap.createBitmap(mapv.getWidth(), mapv.getHeight(), Bitmap.Config.ARGB_8888);
// create an off-screen canvas to prepare new bitmap, and draw path on to it
Canvas offscreencanvas = new Canvas(bmap);
offscreencanvas.drawPath(path, mPaint);
// draw the bitmap of the path onto my map view's canvas
canvas.drawBitmap(bmap, 0, 0, null);
// make a note of where we put the bitmap, so we know how much we
// we need to move it by if the user pans the map
mapStartPosition = proj.fromPixels(0, 0);
}
else
{
// as we're in onDraw, we think the user has panned/moved the map
// if we're in here, the zoom level hasn't changed, and
// we've already got a bitmap with a drawing of the route path
Projection proj = mapv.getProjection();
// where has the mapview been panned to?
Point offsetPt = new Point();
proj.toPixels(mapStartPosition, offsetPt);
// draw the bitmap in the new correct location
canvas.drawBitmap(bmap, offsetPt.x, offsetPt.y, null);
}
Hope this help.
Regards,
Luis
Overriding onDraw would be the only way. How are you drawing the tracks, maybe that can be made more efficient?