Skip to content Skip to sidebar Skip to footer

How To Add A Map Scale In Mapview On Android?

I'm struggling with adding a map scale that displays current length on screen depending on current zoom level. I'm having a feeling that it might exist some predefined class to use

Solution 1:

Alright, I got it now! Luis answer helped me a lot and also OpenStreetMap. Here's what I came up with:

<your.own.package.path>;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Picture;
import android.graphics.Rect;
import android.location.Location;
import android.util.Log;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;
import com.iqqn.uppgift5.GameMapActivity;

publicclassScaleBarOverlayextendsOverlay{

    // ===========================================================// Fields// ===========================================================// Defaultsbooleanenabled=true;

    floatxOffset=10;
    floatyOffset=10;
    floatlineWidth=2;
    inttextSize=12;

    booleanimperial=false;
    booleannautical=false;

    booleanlatitudeBar=true;
    booleanlongitudeBar=false;

    // Internalprotectedfinal MapView mapView;
    protectedfinal GameMapActivity master;

    private Context context;

    protectedfinalPicturescaleBarPicture=newPicture();
    privatefinalMatrixscaleBarMatrix=newMatrix();

    privateintlastZoomLevel= -1;

    float xdpi;
    float ydpi;
    int screenWidth;
    int screenHeight;

    // ===========================================================// Constructors// ===========================================================publicScaleBarOverlay(Context _context, GameMapActivity master, MapView mapView) {
        super();

        this.master = master;
        this.context = _context;
        this.mapView = mapView;

        xdpi = this.context.getResources().getDisplayMetrics().xdpi;
        ydpi = this.context.getResources().getDisplayMetrics().ydpi;

        screenWidth = this.context.getResources().getDisplayMetrics().widthPixels;
        screenHeight = this.context.getResources().getDisplayMetrics().heightPixels;

    }

    // ===========================================================// Getter & Setter// ===========================================================/**
     * @return the enabled
     */publicbooleanisEnabled() {
        return enabled;
    }

    /**
     * @param enabled the enabled to set
     */publicvoidsetEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * @return the lineWidth
     */publicfloatgetLineWidth() {
        return lineWidth;
    }

    /**
     * @param lineWidth the lineWidth to set
     */publicvoidsetLineWidth(float lineWidth) {
        this.lineWidth = lineWidth;
    }

    /**
     * @return the imperial
     */publicbooleanisImperial() {
        return imperial;
    }

    /**
     * @param imperial the imperial to set
     */publicvoidsetImperial() {
        this.imperial = true;
        this.nautical = false;
        createScaleBarPicture();
    }

    /**
     * @return the nautical
     */publicbooleanisNautical() {
        return nautical;
    }

    /**
     * @param nautical the nautical to set
     */publicvoidsetNautical() {
        this.nautical = true;
        this.imperial = false;
        createScaleBarPicture();
    }

    publicvoidsetMetric() {
        this.nautical = false;
        this.imperial = false;
        createScaleBarPicture();
    }

    publicvoiddrawLatitudeScale(boolean latitude) {
        this.latitudeBar = latitude;
    }

    publicvoiddrawLongitudeScale(boolean longitude) {
        this.longitudeBar = longitude;
    }

    @Overridepublicvoiddraw(Canvas canvas, MapView localMapView, boolean shadow) {
        if (this.enabled) {
            // Draw the overlayif (shadow == false) {
                finalintzoomLevel= localMapView.getZoomLevel();

                if (zoomLevel != lastZoomLevel) {
                    lastZoomLevel = zoomLevel;
                    createScaleBarPicture();
                }

                this.scaleBarMatrix.setTranslate(-1 * (scaleBarPicture.getWidth() / 2 - 0.5f), -1 * (scaleBarPicture.getHeight() / 2 - 0.5f));
                this.scaleBarMatrix.postTranslate(xdpi/2, ydpi/2 + canvas.getHeight()-50);

                canvas.save();
                canvas.setMatrix(scaleBarMatrix);
                canvas.drawPicture(scaleBarPicture);
                canvas.restore();
            }
        }
    }

    // ===========================================================// Methods// ===========================================================publicvoiddisableScaleBar() {
        this.enabled = false;
    }

    publicbooleanenableScaleBar() {
        returnthis.enabled = true;
    }

    privatevoidcreateScaleBarPicture() {
        // We want the scale bar to be as long as the closest round-number miles/kilometers// to 1-inch at the latitude at the current center of the screen.Projectionprojection= mapView.getProjection();

        if (projection == null) {
            return;
        }

        LocationlocationP1=newLocation("ScaleBar location p1");
        LocationlocationP2=newLocation("ScaleBar location p2");

        // Two points, 1-inch apart in x/latitude, centered on screenGeoPointp1= projection.fromPixels((int) ((screenWidth / 2) - (xdpi / 2)), screenHeight/2);
        GeoPointp2= projection.fromPixels((int) ((screenWidth / 2) + (xdpi / 2)), screenHeight/2);

        locationP1.setLatitude(p1.getLatitudeE6()/1E6);
        locationP2.setLatitude(p2.getLatitudeE6()/1E6);
        locationP1.setLongitude(p1.getLongitudeE6()/1E6);
        locationP2.setLongitude(p2.getLongitudeE6()/1E6);

        floatxMetersPerInch= locationP1.distanceTo(locationP2);

        p1 = projection.fromPixels(screenWidth/2, (int) ((screenHeight / 2) - (ydpi / 2)));
        p2 = projection.fromPixels(screenWidth/2, (int) ((screenHeight / 2) + (ydpi / 2)));

        locationP1.setLatitude(p1.getLatitudeE6()/1E6);
        locationP2.setLatitude(p2.getLatitudeE6()/1E6);
        locationP1.setLongitude(p1.getLongitudeE6()/1E6);
        locationP2.setLongitude(p2.getLongitudeE6()/1E6);

        floatyMetersPerInch=  locationP1.distanceTo(locationP2);

        finalPaintbarPaint=newPaint();
        barPaint.setColor(Color.BLACK);
        barPaint.setAntiAlias(true);
        barPaint.setStyle(Style.FILL);
        barPaint.setAlpha(255);

        finalPainttextPaint=newPaint();
        textPaint.setColor(Color.BLACK);
        textPaint.setAntiAlias(true);
        textPaint.setStyle(Style.FILL);
        textPaint.setAlpha(255);
        textPaint.setTextSize(textSize);

        finalCanvascanvas= scaleBarPicture.beginRecording((int)xdpi, (int)ydpi);

        if (latitudeBar) {
            StringxMsg= scaleBarLengthText(xMetersPerInch, imperial, nautical);
            RectxTextRect=newRect();
            textPaint.getTextBounds(xMsg, 0, xMsg.length(), xTextRect);

            inttextSpacing= (int)(xTextRect.height() / 5.0);

            canvas.drawRect(xOffset, yOffset, xOffset + xdpi, yOffset + lineWidth, barPaint);
            canvas.drawRect(xOffset + xdpi, yOffset, xOffset + xdpi + lineWidth, yOffset + xTextRect.height() + lineWidth + textSpacing, barPaint);

            if (!longitudeBar) {
                canvas.drawRect(xOffset, yOffset, xOffset + lineWidth, yOffset + xTextRect.height() + lineWidth + textSpacing, barPaint);
            }
            canvas.drawText(xMsg, (xOffset + xdpi/2 - xTextRect.width()/2), (yOffset + xTextRect.height() + lineWidth + textSpacing), textPaint);
        }

        if (longitudeBar) {
            StringyMsg= scaleBarLengthText(yMetersPerInch, imperial, nautical);
            RectyTextRect=newRect();
            textPaint.getTextBounds(yMsg, 0, yMsg.length(), yTextRect);

            inttextSpacing= (int)(yTextRect.height() / 5.0);

            canvas.drawRect(xOffset, yOffset, xOffset + lineWidth, yOffset + ydpi, barPaint);
            canvas.drawRect(xOffset, yOffset + ydpi, xOffset + yTextRect.height() + lineWidth + textSpacing, yOffset + ydpi + lineWidth, barPaint);

            if (! latitudeBar) {
                canvas.drawRect(xOffset, yOffset, xOffset + yTextRect.height() + lineWidth + textSpacing, yOffset + lineWidth, barPaint);
            }                       

            floatx= xOffset + yTextRect.height() + lineWidth + textSpacing;
            floaty= yOffset + ydpi/2 + yTextRect.width()/2;

            canvas.rotate(-90, x, y);
            canvas.drawText(yMsg, x, y + textSpacing, textPaint);

        }

        scaleBarPicture.endRecording();
    }

    private String scaleBarLengthText(float meters, boolean imperial, boolean nautical) {
        if (this.imperial) {
            if (meters >= 1609.344) {
                return (meters / 1609.344) + "mi";
            } elseif (meters >= 1609.344/10) {
                return ((meters / 160.9344) / 10.0) + "mi";
            } else {
                return (meters * 3.2808399) + "ft";
            }
        } elseif (this.nautical) {
            if (meters >= 1852) {
                return ((meters / 1852)) + "nm";
            } elseif (meters >= 1852/10) {
                return (((meters / 185.2)) / 10.0) + "nm";
            } else {
                return ((meters * 3.2808399)) + "ft";
            }
        } else {
            if (meters >= 1000) {
                return ((meters / 1000)) + "km";
            } elseif (meters > 100) {
                return ((meters / 100.0) / 10.0) + "km";
            } else {
                return meters + "m";
            }
        }
    }

    @OverridepublicbooleanonTap(GeoPoint point, MapView mapView) {
        // Do not react to screen taps.returnfalse;
    }
}

Use it the following way in your onCreate():

...
scaleBarOverlay = new ScaleBarOverlay(this.getBaseContext(), this, myMapView);
List<Overlay> overlays = myMapView.getOverlays();
// Add scale bar overlay
scaleBarOverlay.setMetric();
overlays.add(scaleBarOverlay);
...

Hope this will help anyone =) This will work from API level 7+. I haven't tested it on API level 14+ though, and i know that some hardware accelerated stuff "don't" work there like drawing a picture with canvas. But i think it'll work with a recording.

Thanks again Luis!

// Alexander

Solution 2:

As far as I know there isn't a predifined class to do that.

One possibility is to create an overlay that checks current longitude span, as it changes accordingly to latitude, and then draw the scale at the correct size.

Bellow you can find an example on how to do that:

ScaleBar Overlay

publicclassCopyOfScaleBarOverlayextendsOverlay {    
privatestaticfinalStringSTR_M="m"; 
privatestaticfinalStringSTR_KM="km"; 

//ConstantsprivatestaticfloatscaleBarProportion=0.25f;
privatefloat cMarginLeft=4;
privatefloat cLineTopSize=8;
privatefloat cMarginTop=6;
privatefloat cMarginBottom=2;
privatefloat cTextSize=12;
privatefloat distanceFromBottom=100;


//instantiationprivate Context context;

private Paint paintLine, paintText, paintRectangle;
private Location l0;
private Location l1;
privatefloat ds;
privateint width, height, pi;
privatefloat marginLeft, marginTop, marginBottom, lineTopSize;
private String unit;



publicCopyOfScaleBarOverlay(Context context){
    super();
    this.context=context;

    paintText= newTextPaint();
    paintText.setARGB(180, 0, 0, 0);
    paintText.setAntiAlias(true);
    paintText.setTextAlign(Align.CENTER);

    paintRectangle = newPaint();
    paintRectangle.setARGB(80,255,255,255);
    paintRectangle.setAntiAlias(true);

    paintLine = newPaint();
    paintLine.setARGB(180, 0, 0, 0);
    paintLine.setAntiAlias(true);

    l0 = newLocation("none");
    l1 = newLocation("none");

    ds=this.context.getApplicationContext().getResources().getDisplayMetrics().density;
    width=this.context.getApplicationContext().getResources().getDisplayMetrics().widthPixels;
    height=this.context.getApplicationContext().getResources().getDisplayMetrics().heightPixels;
    pi = (int) (height - distanceFromBottom *ds);

    marginLeft=cMarginLeft*ds;
    lineTopSize=cLineTopSize*ds;
    marginTop=cMarginTop*ds;
    marginBottom=cMarginBottom*ds;


}

@Overridepublicvoiddraw(Canvas canvas, MapView mapview, boolean shadow) {
    super.draw(canvas, mapview, shadow);
    if(mapview.getZoomLevel() > 1){

        //Calculate scale bar size and unitsGeoPointg0= mapview.getProjection().fromPixels(0, height/2);
        GeoPointg1= mapview.getProjection().fromPixels(width, height/2);
        l0.setLatitude(g0.getLatitudeE6()/1E6);
        l0.setLongitude(g0.getLongitudeE6()/1E6);
        l1.setLatitude(g1.getLatitudeE6()/1E6);
        l1.setLongitude(g1.getLongitudeE6()/1E6);
        float d01=l0.distanceTo(l1);
        float d02=d01*scaleBarProportion;
        // multiply d02 by a unit conversion factor if neededfloat cd02;
        if(d02 > 1000){
            unit = STR_KM;
            cd02 = d02 / 1000;
        } else{
            unit = STR_M;
            cd02 = d02;
        }
        int i=1;
        do{
            i *=10;
        }while (i <= cd02);
        i/=10;
        float dcd02=(int)(cd02/i)*i;
        float bs=dcd02*width/d01*d02/cd02;

        String text=String.format("%.0f %s", dcd02, unit);
        paintText.setTextSize(cTextSize * ds);
        float text_x_size=paintText.measureText(text);
        floatx_size= bs + text_x_size/2 + 2*marginLeft;

        //Draw rectangle
        canvas.drawRect(0,pi,x_size,pi+marginTop+paintText.getFontSpacing()+marginBottom, paintRectangle);

        //Draw line
        canvas.drawLine(marginLeft, pi+marginTop, marginLeft + bs, pi+marginTop, paintLine);
        //Draw line tops
        canvas.drawLine(marginLeft, pi+marginTop - lineTopSize/2, marginLeft, pi+marginTop + lineTopSize/2, paintLine);
        canvas.drawLine(marginLeft +bs, pi+marginTop - lineTopSize/2, marginLeft+bs, pi+marginTop + lineTopSize/2, paintLine);
        //Draw line midle
        canvas.drawLine(marginLeft + bs/2, pi+marginTop - lineTopSize/3, marginLeft + bs/2, pi+marginTop + lineTopSize/3, paintLine);
        //Draw line quarters
        canvas.drawLine(marginLeft + bs/4, pi+marginTop - lineTopSize/4, marginLeft + bs/4, pi+marginTop + lineTopSize/4, paintLine);
        canvas.drawLine(marginLeft + 3*bs/4, pi+marginTop - lineTopSize/4, marginLeft + 3*bs/4, pi+marginTop + lineTopSize/4, paintLine);

        //Draw text
        canvas.drawText(text, marginLeft +bs, pi+marginTop+paintText.getFontSpacing(), paintText);
    }
}

}

To use

On your activity that extendes MapActivity, add the following:

mapView.getOverlays().add(new CopyOfScaleBarOverlay(this));

Note

The example is using metric units. To use a different unit system, multiply d02 in the code above by a unit conversion factor and adjust strings with the unit name.

Enjoy it.

Post a Comment for "How To Add A Map Scale In Mapview On Android?"