Android mapping with Hebrew or, using the osmDroid API with ease - Part 2: fixed stations (adding non GPS locations to the map).


Israel Office



Europe office


Android mapping with Hebrew or, using the osmDroid API with ease - Part 2: fixed stations (adding non GPS locations to the map).
submitted by: royi

today we will add a new feature to our hebrew enabled mapping Android application that we started last time, this feature will be the ability to set fixed locations (ie stations) on the map.

I would like to start with 2 clarifications:
1. this post is a simplified tutorial for plusminus's amazing OSMDroid project so if my post isn't clear enough you can go directly to the source.
2. As before the source code for all that we will do today is available via gitHub at

stage 1: implementing the newItemizedOverlay class

-Add a new class to your package and call it newItemizedOverlay (or anything else you would like to call it for that matter).
-Have it extend the "OpenStreetMapViewItemizedOverlay" class.
-Implement the constructors.
-Implement the "protected void onDrawFinished(Canvas c, OpenStreetMapView osmv)" method.
-Implement the "protected void onDrawItem(final Canvas c, final int index, final Point screenCoords)" method.

At the end it should look kind of like that:

// Created by plusminus on 20:50:06 - 03.10.2008
package com.linnovate.osmap;

import java.util.List;
import org.andnav.osm.DefaultResourceProxyImpl;
import org.andnav.osm.ResourceProxy;
import org.andnav.osm.views.OpenStreetMapView;
import org.andnav.osm.views.overlay.OpenStreetMapViewItemizedOverlay;
import org.andnav.osm.views.overlay.OpenStreetMapViewOverlayItem;
import android.content.Context;

public class newItemizedOverlay extends OpenStreetMapViewItemizedOverlay {

// =========================================================== // Constants // =========================================================== public static final int DESCRIPTION_BOX_PADDING = 3; public static final int DESCRIPTION_BOX_CORNERWIDTH = 3; public static final int DESCRIPTION_LINE_HEIGHT = 12; public static final int DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT = 2; protected static final Point DEFAULTMARKER_FOCUSED_HOTSPOT = new Point(10, 19); protected static final int DEFAULTMARKER_BACKGROUNDCOLOR = Color.rgb(101, 185, 74); protected static final int DESCRIPTION_MAXWIDTH = 200; // =========================================================== // Fields // =========================================================== protected final Point mMarkerFocusedHotSpot; protected final Drawable mMarkerFocusedBase; protected final int mMarkerFocusedBackgroundColor; protected final int mMarkerFocusedWidth, mMarkerFocusedHeight; protected final Paint mMarkerBackgroundPaint, mDescriptionPaint, mTitlePaint; protected int mFocusedItemIndex; protected boolean mFocusItemsOnTap; private Point mFocusedScreenCoords = new Point(); private final String UNKNOWN; // =========================================================== // Constructors // =========================================================== public newItemizedOverlay( final Context ctx, final List<T> aList, final OnItemTapListener<T> aOnItemTapListener) { this(ctx, aList, aOnItemTapListener, new DefaultResourceProxyImpl(ctx)); } public newItemizedOverlay( final Context ctx, final List<T> aList, final OnItemTapListener<T> aOnItemTapListener, final ResourceProxy pResourceProxy) { this(ctx, aList, null, null, null, null, NOT_SET, aOnItemTapListener, pResourceProxy); } public newItemizedOverlay( final Context ctx, final List<T> aList, final Drawable pMarker, final Point pMarkerHotspot, final Drawable pMarkerFocusedBase, final Point pMarkerFocusedHotSpot, final int pFocusedBackgroundColor, final OnItemTapListener<T> aOnItemTapListener, final ResourceProxy pResourceProxy) { super(ctx, aList, pMarker, pMarkerHotspot, aOnItemTapListener, pResourceProxy); UNKNOWN = mResourceProxy.getString(ResourceProxy.string.unknown); this.mMarkerFocusedBase = (pMarkerFocusedBase != null) ? pMarkerFocusedBase : mResourceProxy.getDrawable(ResourceProxy.bitmap.marker_default_focused_base); this.mMarkerFocusedHotSpot = (pMarkerFocusedHotSpot != null) ? pMarkerFocusedHotSpot : DEFAULTMARKER_FOCUSED_HOTSPOT; if(pFocusedBackgroundColor != NOT_SET) this.mMarkerFocusedBackgroundColor = pFocusedBackgroundColor; else this.mMarkerFocusedBackgroundColor = DEFAULTMARKER_BACKGROUNDCOLOR; this.mMarkerBackgroundPaint = new Paint(); // Color is set in onDraw(...) this.mDescriptionPaint = new Paint(); this.mDescriptionPaint.setAntiAlias(true); this.mTitlePaint = new Paint(); this.mTitlePaint.setFakeBoldText(true); this.mTitlePaint.setAntiAlias(true); this.mMarkerFocusedWidth = this.mMarkerFocusedBase.getIntrinsicWidth(); this.mMarkerFocusedHeight = this.mMarkerFocusedBase.getIntrinsicHeight(); } // =========================================================== // Getter & Setter // =========================================================== public void setFocusedItem(final int pIndex){ this.mFocusedItemIndex = pIndex; } public void unSetFocusedItem(){ this.mFocusedItemIndex = NOT_SET; } public void setFocusedItem(final T pItem){ final int indexFound = super.mItemList.indexOf(pItem); if(indexFound < 0) throw new IllegalArgumentException(); this.setFocusedItem(indexFound); } public void setFocusItemsOnTap(final boolean doit) { this.mFocusItemsOnTap = doit; } // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override protected boolean onTap(int pIndex) { if(this.mFocusItemsOnTap) this.mFocusedItemIndex = pIndex; return super.onTap(pIndex); } @Override protected void onDrawFinished(Canvas c, OpenStreetMapView osmv) { if(this.mFocusedItemIndex != NOT_SET){ /* Calculate and set the bounds of the marker. */ final int left = this.mFocusedScreenCoords.x - this.mMarkerFocusedHotSpot.x; final int right = left + this.mMarkerFocusedWidth; final int top = this.mFocusedScreenCoords.y - this.mMarkerFocusedHotSpot.y; final int bottom = top + this.mMarkerFocusedHeight; this.mMarkerFocusedBase.setBounds(left, top, right, bottom); /* Strings of the OverlayItem, we need. */ final T focusedItem = super.mItemList.get(this.mFocusedItemIndex); final String itemTitle = (focusedItem.mTitle == null) ? UNKNOWN : focusedItem.mTitle; final String itemDescription = (focusedItem.mDescription == null) ? UNKNOWN : focusedItem.mDescription; /* Store the width needed for each char in the description to a float array. This is pretty efficient. */ final float[] widths = new float[itemDescription.length()]; this.mDescriptionPaint.getTextWidths(itemDescription, widths); final StringBuilder sb = new StringBuilder(); int maxWidth = 0; int curLineWidth = 0; int lastStop = 0; int i; int lastwhitespace = 0; /* Loop through the charwidth array and harshly insert a linebreak, * when the width gets bigger than DESCRIPTION_MAXWIDTH. */ for (i = 0; i < widths.length; i++) { if(!Character.isLetter(itemDescription.charAt(i))) lastwhitespace = i; float charwidth = widths[i]; if(curLineWidth + charwidth> DESCRIPTION_MAXWIDTH){ if(lastStop == lastwhitespace) i--; else i = lastwhitespace; sb.append(itemDescription.subSequence(lastStop, i)); sb.append('\n'); lastStop = i; maxWidth = Math.max(maxWidth, curLineWidth); curLineWidth = 0; } curLineWidth += charwidth; } /* Add the last line to the rest to the buffer. */ if(i != lastStop){ final String rest = itemDescription.substring(lastStop, i); maxWidth = Math.max(maxWidth, (int)this.mDescriptionPaint.measureText(rest)); sb.append(rest); } final String[] lines = sb.toString().split("\n"); /* The title also needs to be taken into consideration for the width calculation. */ final int titleWidth = (int)this.mDescriptionPaint.measureText(itemTitle); maxWidth = Math.max(maxWidth, titleWidth); final int descWidth = Math.min(maxWidth, DESCRIPTION_MAXWIDTH); /* Calculate the bounds of the Description box that needs to be drawn. */ final int descBoxLeft = left - descWidth / 2 - DESCRIPTION_BOX_PADDING + this.mMarkerFocusedWidth / 2; final int descBoxRight = descBoxLeft + descWidth + 2 * DESCRIPTION_BOX_PADDING; final int descBoxBottom = top; final int descBoxTop = descBoxBottom - DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT - (lines.length + 1) * DESCRIPTION_LINE_HEIGHT /* +1 because of the title. */ - 2 * DESCRIPTION_BOX_PADDING; /* Twice draw a RoundRect, once in black with 1px as a small border. */ this.mMarkerBackgroundPaint.setColor(this.mMarkerFocusedBackgroundColor); c.drawRoundRect(new RectF(descBoxLeft-3, descBoxTop-3, descBoxRight+3, descBoxBottom+3), DESCRIPTION_BOX_CORNERWIDTH, DESCRIPTION_BOX_CORNERWIDTH, this.mDescriptionPaint);//this.mMarkerBackgroundPaint); this.mMarkerBackgroundPaint.setColor(Color.YELLOW);//XXX - the background color for the sign! c.drawRoundRect(new RectF(descBoxLeft - 1, descBoxTop - 1, descBoxRight + 1, descBoxBottom + 1), DESCRIPTION_BOX_CORNERWIDTH, DESCRIPTION_BOX_CORNERWIDTH, this.mMarkerBackgroundPaint);//this.mDescriptionPaint); final int descLeft = descBoxLeft + DESCRIPTION_BOX_PADDING; int descTextLineBottom = descBoxBottom - DESCRIPTION_BOX_PADDING; /* Draw all the lines of the description. */ for(int j = lines.length - 1; j >= 0; j--){ c.drawText(lines[j].trim(), descLeft, descTextLineBottom, this.mDescriptionPaint); descTextLineBottom -= DESCRIPTION_LINE_HEIGHT; } /* Draw the title. */ c.drawText(itemTitle, descLeft, descTextLineBottom - DESCRIPTION_TITLE_EXTRA_LINE_HEIGHT, this.mTitlePaint); c.drawLine(descBoxLeft, descTextLineBottom, descBoxRight, descTextLineBottom, mDescriptionPaint); /* Finally draw the marker base. This is done in the end to make it look better. */ this.mMarkerFocusedBase.draw(c); } } @Override protected void onDrawItem(final Canvas c, final int index, final Point screenCoords) { if(this.mFocusedItemIndex != NOT_SET && index == this.mFocusedItemIndex){ /* Actual draw will take place in onDrawFinished. */ /* Because we are reusing the screencoords apssed here, we cannot simply store the reference. */ this.mFocusedScreenCoords.set(screenCoords.x, screenCoords.y); }else{ super.onDrawItem(c, index, screenCoords); } } // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // ===========================================================


As you can see it's mostly copied from plusminus and his amazing OSMDroid project so if my post isn't clear enough you can go directly to the source.

stage 2: initialization of the overlay in the map activity

Just open the activity and add this block, (i hope it is documented enough):

/XXX - new portion of code relating to non GPS data stations - START/
//grab your application's resources to use your drawables (in this case).
res = this.getResources();
//create an ArrayList of stations.
stations = new ArrayList();
//create station(s) (sorry for the 80's refference :)
stations.add(new OpenStreetMapViewOverlayItem("Here i am!", "rocking like a hurricane!!!", new GeoPoint(32.000000,35.000000)));
//create the overlay with the stations you created earlier and a marker base for the stations (i chose the android icon - very original, i know :)
mStationOverlay = new newItemizedOverlay
//add the stations overlay to the map

/**XXX - new portion of code relating to non GPS data stations - END!**/

As you can see i added commenting lines to the source code so you could see exactly where you should add this block.

Now, when you start the app you'll get something like this:

try and figure out where's the marker (it's a tradition now :).
That's it, pretty easy, isn't it?

p.s. - if you want the application to stop following your location (GPS location) just comment out the following line (line 136 at


have fun.

Add new comment


Welcome popup block