I am developing application with monodroid andI would like to insert custom pointer in my maps from Resource>Drawable.
I am able to draw text but i don't know how to insert my custom pointer in my map.
Currently it shows me small rectangle with red color in the current location of user.
Here is the code for Draw method.
public override void Draw(Android.Graphics.Canvas canvas, MapView mapView, bool shadow)
{
base.Draw(canvas, mapView, shadow);
var paint = new Paint();
paint.AntiAlias = true;
paint.Color = Color.Red;
var pt = mapView.Projection.ToPixels(_latlng, null);
float distance = mapView.Projection.MetersToEquatorPixels(10);
canvas.DrawText("Your Taxi is here", pt.X + distance, pt.Y + distance, paint);
canvas.DrawRect( pt.X, pt.Y, pt.X + distance, pt.Y + distance, paint);
}
Download full working demo with custom pointer
http://www.filedropper.com/androidopenstreetmapview120516a
If map is not display in emulator or mobile device then change Map API key .
Related
I'm using an Overlay to mark areas on Google Maps by drawing a shape of ten thousands of GeoPoints I get from any source. This works and looks like this:
#Override
public void draw(android.graphics.Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, false);
Projection projection = mapView.getProjection();
List<Zone> zones = ApplicationContext.getZones();
path.rewind();
for (Zone zone : zones) {
paint.setDither(true);
paint.setStyle(Style.FILL);
paint.setAlpha(40);
MultiPolygon multiPolygon = zone.getMultiPolygon();
List<Polygon> polygons = multiPolygon.getPolygons();
for (Polygon polygon : polygons) {
for (List<Coordinate> coordinates : polygon.getCoordinates()) {
for (int i = 0; i < coordinates.size(); i++) {
Point p = new Point();
projection.toPixels(new GeoPoint((int)(coordinates.get(i).getLatitude() * 1E6), (int)(coordinates.get(i).getLongitude() * 1E6)), p);
if (i == 0) {
path.moveTo(p.x, p.y);
}
else {
path.lineTo(p.x, p.y);
}
}
}
}
}
canvas.drawPath(path, paint);
}
The problem is that this is very resource consuming. Every time one scrolls or moves the map on MapView, the path has to be calculated over and over again, because the pixel coordinates have been changed. The drawn area could become so big that the scrolling on the MapView is so slow that it is functional unusable.
My ideas are
to somehow cache the "shape" the path generates and just redraw it
when the zoom level changes on the MapView.
to somehow draw the painting on an "on the fly"-Bitmap to use it as Overlay (maybe as ItemizedOverlay), listen for MapView scrolling and move the bitmap by the scrolled distance.
I'm not sure if there are better methods.
Any ideas how I could solve this problem?
(I'm using Google Maps API 1 and can't change).
Before resorting to trying to figure out how to match the map's movement, there are some optimizations to your current code that will probably yield significant savings. In particular, these two lines inside your inner loop is executed the most times, but fairly expensive to execute (two memory allocations, floating point multiplies, and four method calls).
Point p = new Point();
projection.toPixels(new GeoPoint((int)(coordinates.get(i).getLatitude() * 1E6), (int)(coordinates.get(i).getLongitude() * 1E6)), p);
First, you only ever need one Point object, so avoid allocating it in your loop. Move it to just below your path.rewind();
Second, if you pre-computed your coordinates as GeoPoints instead of computing them each time, you would save a lot of processing in your draw routine. You can also get rid of that if statement with a little work. Assuming you preconvert your list of coordinate to a list of GeoPoint, and make it available through polygon.getGeoCoordinates(), you could end up with your inner loops looking like -
for (List<GeoPoint> geoCoordinates : polygon.getGeoCoordinates()) {
projection.toPixels(geoCoordinates.get(0),p);
path.moveTo(p.x, p.y); // move to first spot
final List<GeoPoint> lineToList = geoCoordinates.sublist(1,geoCoordinates.size()); // A list of all the other points
for(GeoPoint gp : lineToList) {
projection.toPixels(gp, p);
path.lineTo(p.x, p.y);
}
}
And that will run a lot faster than what you were doing before.
After tinkering around in the last days I found a possible solution (and I don't think there is a better one) to not draw the path over and over again but move it to the current position.
The difficult part was to figure out how to cache the drawn shape to not calculate it over and over again. This can be done by using a Matrix. With this Matrix (I imagine this as some kind of "template") you can manipulate the points coordinates inside the path. The first time (when someone starts moving the Map) I draw the area as usual. When it tries to calculate it the second time or more, I don't redraw the shape but I manipulate the path by calculating the "delta" from the current point to the last point. I know what the current point is, because I always map the original GeoPoint (which always stays the same) to the point which results from the current projection. The "delta" needs to be set as Matrix. After that I transform the path by using this new Matrix. The result is really very fast. The scrolling of the Map is as fast as without using an Overlay.
This looks like this (this is no production code, and it cannot deal with zooming yet, but it shows the principle I use as basis for my optimizations):
public class DistrictOverlay extends Overlay {
// private final static String TAG = DistrictOverlay.class.getSimpleName();
private Paint paint = new Paint();
private Path path = new Path();
private boolean alreadyDrawn = false;
private GeoPoint origGeoPoint;
Point p = new Point();
Point lastPoint = new Point();
#Override
public void draw(android.graphics.Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, false);
Projection projection = mapView.getProjection();
List<Zone> zones = ApplicationContext.getZones();
if (!alreadyDrawn) {
path.rewind();
for (Zone zone : zones) {
if (!zone.getZoneId().equals(MenuContext.getChosenZoneId())) {
continue;
}
String dateString = zone.getEffectiveFrom().trim().replace("CEST", "").replace("GMT", "").replace("CET", "").replace("MESZ", "");
if (DateUtil.isBeforeCurrentDate(dateString)) {
paint.setColor(Color.RED);
} else {
paint.setColor(Color.GREEN);
}
paint.setDither(true);
paint.setStyle(Style.FILL);
paint.setAlpha(40);
MultiPolygon multiPolygon = zone.getMultiPolygon();
List<Polygon> polygons = multiPolygon.getPolygons();
for (Polygon polygon : polygons) {
for (List<GeoPoint> geoPoints : polygon.getGeoPoints()) {
projection.toPixels(geoPoints.get(0), p);
path.moveTo(p.x, p.y);
origGeoPoint = new GeoPoint(geoPoints.get(0).getLatitudeE6(), geoPoints.get(0).getLongitudeE6());
lastPoint = new Point(p.x, p.y);
final List<GeoPoint> pathAsList = geoPoints.subList(1, geoPoints.size());
for (GeoPoint geoPoint : pathAsList) {
projection.toPixels(geoPoint, p);
path.lineTo(p.x, p.y);
}
}
}
}
}
else {
projection.toPixels(origGeoPoint, p);
Matrix translateMatrix = new Matrix();
translateMatrix.setTranslate(p.x - lastPoint.x, p.y - lastPoint.y);
path.transform(translateMatrix);
lastPoint = new Point(p.x, p.y);
}
canvas.drawPath(path, paint);
if (!path.isEmpty()) {
alreadyDrawn = true;
}
}
#Override
public boolean onTap(GeoPoint p, MapView mapView) {
return true;
}
}
I'm developing an Android aplication that has to show some "places of interest" on a mapview, along with the device current location. This works well.
Also, in my app, the user could "tap" a "place of interest" marker and the aplication would have to draw a route to that marker.
I used Google Directions api to get the route, along with a polyline decoder to get the GeoPoints between the user and the place. For my testing route, google gives me about 200 different GeoPoints.
So, I have a class like this to add those GeoPoints:
public class RouteOverlay extends Overlay {
private GeoPoint gp1;
private GeoPoint gp2;
private int color;
public RouteOverlay(GeoPoint gp1, GeoPoint gp2, int color) {
this.gp1 = gp1;
this.gp2 = gp2;
this.color = color;
}
#Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
Projection projection = mapView.getProjection();
Paint paint = new Paint();
Point point = new Point();
projection.toPixels(gp1, point);
paint.setColor(color);
Point point2 = new Point();
projection.toPixels(gp2, point2);
paint.setStrokeWidth(5);
paint.setAlpha(120);
canvas.drawLine(point.x, point.y, point2.x, point2.y, paint);
super.draw(canvas, mapView, shadow);
}
}
what I do to draw the route is the following:
1) Detect the onClick event to a marker in the map.
2) From that event, I create a new thread, where I make the call to the Google API.
3) Once I have the result, I parse/convert it in a GeoPoint list.
4) Then I call my drawPath method:
private void drawPath(List<GeoPoint> geoPoints, int color) {
mapOverlays.clear();
mapOverlays.add(myLocationOverlay);
mapOverlays.add(itemizedoverlay);
for (int i = 1; i < geoPoints.size(); i++) {
mapOverlays.add(new RouteOverlay(geoPoints.get(i - 1), geoPoints.get(i), color));
}
mapView.postInvalidate();
5) Finally, I return to the UI thread.
This method clears the map overlay list (mapOverlays). Then, adds to the list the current location and the "places of interest" overlays. And, finally, adds the route overlays.
The problem is that, suddenly, works veeery slow and finally crashes. But there is no message in the LogCat. So, I thought that 30 overlays + 1 + more than 200 for the route are too much for the phone to handle. But the tutorials I've seen do it this way so...
Can someone tell me if I do anything wrong?
Thanks in advance.
I figured out what I was doing wrong.
When I called the drawPath function, after obtaining the List of GeoPoints, I made a loop to check the coordinates of each point. Something like
for (int i = 0; i < geoList.size(); i++){
Log.i("GEOPOINT " + i, geoList.get(i).toString());
drawpath(geoList, Color.BLUE);
}
The drawPath function got called N times. So the device crashed. My fault.
Programming at 2:00 am is not good for the code!
So I have an custom overlay item that I have written to fill in a transparent blue overlay based around an array of geo points
#Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
Projection projection = mapView.getProjection();
Paint fill = new Paint();
fill.setColor(Color.BLUE);
fill.setAlpha(50);
fill.setStyle(Paint.Style.FILL_AND_STROKE);
Path path = new Path();
Point firstPoint = new Point();
projection.toPixels(geoPoints.get(0), firstPoint);
path.moveTo(firstPoint.x, firstPoint.y);
for (int i = 1; i < geoPoints.size(); ++i) {
Point nextPoint = new Point();
projection.toPixels(geoPoints.get(i), nextPoint);
path.lineTo(nextPoint.x, nextPoint.y);
}
path.lineTo(firstPoint.x, firstPoint.y);
path.setLastPoint(firstPoint.x, firstPoint.y);
canvas.drawPath(path, fill);
super.draw(canvas, mapView, shadow);
}
What I need is a way to get the center point of this overlay so I can place a marker on it,
anyone have any ideas?
although i am not familiar with android framework, i assume you writing in java and using some kind of google maps api. But i do familiar with graphics and geo development. My suggestion to you firsst of all to check whether the standard api has some kind of
getBounds(path) that returns to you RectangularBounds object or similar. Then from rectangular bounds you can ask for bounds.getCenter() which returns the center of bounds as geo point or other metric. If you use pixels just convert the geopoint like you did...
If getBounds doesn't exists in api (what is hard to believe), just implement a simple interface , you can find a lot of examples on the net.
simple pseudo code for finding the bounds of a geo shape for geo points, if you need pixels use x,y respectively:
bounds = { topLeft: new GeoPoint(path[0]), bottomRight: new GeoPoint(path[0])};
for( point in path ){
bounds.topLeft.lat = max( bounds.topLeft.lat,point.lat );
bounds.topLeft.lng = min( bounds.topLeft.lng,point.lng );
bounds.bottomRight.lat = min( bounds.bottomRight.lat,point.lat );
bounds.bottomRight.lng = max( bounds.bottomRight.lng,point.lng );
}
bounds.getCenter(){
return new GeoPoint(rectangle center point);
// i am sure you will able to manage the code here )))
}
hope this will help
I need to draw text on a custom marker which are quite a lot in number. But the problem is that when I draw the text in the Overridden on draw, all the overlay items text appears to be in one layer above the markers and does not seem synched when the map is zoomed in or zoomed out. Previously I was calling the populate when each item was added and this was working fine but it was too slow. Any help?
In my custom class that extends from ItemizedOverlay, the constructor is as follows, where I set the custom marker:
public HotelMapFilterOverlay(Drawable defaultMarker, final Context con) {
super(boundCenterBottom(defaultMarker));
this.marker = defaultMarker;
this.con = con;
}
and the overridden draw method is as follows:
#Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, shadow);
Bitmap bmp = ((BitmapDrawable) marker).getBitmap();
Paint textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(16f);
textPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, 1));
textPaint.setStyle(Paint.Style.FILL);
Point screenPts = new Point();
float bmpWidth = bmp.getWidth();
float bmpHeight = bmp.getHeight();
float left;
float top;
int total = mOverlayItems.size();
for (int index = 0; index < total; index++) {
gp = mOverlayItems.get(index).getPoint();
mapView.getProjection().toPixels(gp, screenPts);
left = screenPts.x - (bmpWidth / 2);
top = screenPts.y - bmpHeight;
// draw text
this.title = mOverlayItems.get(index).getTitle();
canvas.drawText(this.title, left + 8, top + 30,textPaint);
}
}
and I calling the populate only once to make this efficient like
public void callPopulate(){
populate();
}
To get your marker text to appear separately over each marker without all of it layering on top of the markers, you need to draw the markers yourself and remove super.draw(canvas, mapView, shadow).
To draw the markers you have most of the code already! Just create a new Paint markerPaint = new Paint() object and add the following in your for-loop before you draw your text:
canvas.drawBitmap(bmp, screenPts.x-(bmp.getWidth()/2), screenPts.y-(bmp.getHeight()), markerPaint);
This way the marker is drawn then its text is drawn on top.
With respect to 'populate' you should call this just after you add all the items to your ItemizedOverlay.
I'm just trying to make sense of a basic Android navigation-related question, namely "How can I display the current location". I've used a bit of code from articles and tutorials from my long googling sessions. The display of a simple overlay (circle + text message) is works, yet at a wrong on-screen position (on the Equator apparently).
My code setup includes a small inner class that implements LocationListener, and its onLocationChanged event handler calls this method :
protected void createAndShowCustomOverlay(Location newLocation)
{
double lat = newLocation.getLatitude();
double lng = newLocation.getLongitude();
// geoPointFromLatLng is an E6 converter :
// return new GeoPoint((int) (pLat * 1E6), (int) (pLng * 1E6));
GeoPoint geopoint = GeoFunctions.geoPointFromLatLng(lat, lng);
CustomOverlay overlay = new CustomOverlay(geopoint);
mapView.getOverlays().add(overlay);
mapView.getController().animateTo(geopoint);
mapView.postInvalidate();
}
Up to this point, it all looks ok, I've debugged around and the non-transformed lat/lng pair is ok, the E6 variant thereof ok as well. Here is the CustomOverlay class :
public class CustomOverlay extends Overlay
{
private static final int CIRCLERADIUS = 2;
private GeoPoint geopoint;
public CustomOverlay(GeoPoint point)
{
geopoint = point;
}
#Override
public void draw(Canvas canvas, MapView mapView, boolean shadow)
{
// Transfrom geoposition to Point on canvas
Projection projection = mapView.getProjection();
Point point = new Point();
projection.toPixels(geopoint, point);
// background
Paint background = new Paint();
background.setColor(Color.WHITE);
RectF rect = new RectF();
rect.set(point.x + 2 * CIRCLERADIUS, point.y - 4 * CIRCLERADIUS,
point.x + 90, point.y + 12);
// text "My Location"
Paint text = new Paint();
text.setAntiAlias(true);
text.setColor(Color.BLUE);
text.setTextSize(12);
text.setTypeface(Typeface.MONOSPACE);
// the circle to mark the spot
Paint circle = new Paint();
circle.setColor(Color.BLUE);
circle.setAntiAlias(true);
canvas.drawRoundRect(rect, 2, 2, background);
canvas.drawCircle(point.x, point.y, CIRCLERADIUS, circle);
canvas.drawText("My Location", point.x + 3 * CIRCLERADIUS, point.y + 3
* CIRCLERADIUS, text);
}
}
It's most definitely a projection error of sorts, since all my geo fix'd coords end up on the Equator, so there are some 0.0 values getting thrown around.
I can provide other details if needed, I'm running the code on the emulator, Maps API Level 8 (Android 2.2) and I get tiles and the CustomOverlay (circle + text) gets displayed, only at a really false position (coords such as 54.foo and 8.bar are way off).
The codebase could be a bit older, then perhaps a projection such as toPixels isn't required anymore ? No idea.
Thanks in advance !
Solved, turns out the "geo fix" command sent via telnet wants a (Lng, Lat) pair, not a (Lat, Lng) pair. Inconsistency at its best ! Thanks for the input.
If you only want to show current location and keep it updating with a small blinking circule on the screen like Google Maps for Android does, then Android has a default implementation of this feature.
MyLocationOverlay myLocationOverlay = new MyLocationOverlay(context, mapView);
myLocationOverlay.enableMyLocation();
mapView.getOverlays().add(myLocationOverlay);
That's it Android will handle everything.