/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.android.maps;
import java.util.HashMap;
import java.util.Map;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.MercatorProjection;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
/**
* A MapScaleBar displays the ratio of a distance on the map to the corresponding distance on the ground.
*/
public class MapScaleBar {
/**
* Enumeration of all text fields.
*/
public enum TextField {
/**
* Unit symbol for one foot.
*/
FOOT,
/**
* Unit symbol for one kilometer.
*/
KILOMETER,
/**
* Unit symbol for one meter.
*/
METER,
/**
* Unit symbol for one mile.
*/
MILE;
}
private static final int BITMAP_HEIGHT = 50;
private static final int BITMAP_WIDTH = 200;
private static final double LATITUDE_REDRAW_THRESHOLD = 0.2;
private static final int MARGIN_BOTTOM = 5;
private static final int MARGIN_LEFT = 5;
private static final double METER_FOOT_RATIO = 0.3048;
private static final int ONE_KILOMETER = 1000;
private static final int ONE_MILE = 5280;
private static final Paint SCALE_BAR = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final Paint SCALE_BAR_STROKE = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final int[] SCALE_BAR_VALUES_IMPERIAL = { 26400000, 10560000, 5280000, 2640000, 1056000, 528000,
264000, 105600, 52800, 26400, 10560, 5280, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1 };
private static final int[] SCALE_BAR_VALUES_METRIC = { 10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000,
50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1 };
private static final Paint SCALE_TEXT = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final Paint SCALE_TEXT_STROKE = new Paint(Paint.ANTI_ALIAS_FLAG);
private static void configurePaints() {
SCALE_BAR.setStrokeWidth(2);
SCALE_BAR.setStrokeCap(Paint.Cap.SQUARE);
SCALE_BAR.setColor(Color.BLACK);
SCALE_BAR_STROKE.setStrokeWidth(5);
SCALE_BAR_STROKE.setStrokeCap(Paint.Cap.SQUARE);
SCALE_BAR_STROKE.setColor(Color.WHITE);
SCALE_TEXT.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
SCALE_TEXT.setTextSize(17);
SCALE_TEXT.setColor(Color.BLACK);
SCALE_TEXT_STROKE.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
SCALE_TEXT_STROKE.setStyle(Paint.Style.STROKE);
SCALE_TEXT_STROKE.setColor(Color.WHITE);
SCALE_TEXT_STROKE.setStrokeWidth(2);
SCALE_TEXT_STROKE.setTextSize(17);
}
private boolean imperialUnits;
private MapPosition mapPosition;
private final Bitmap mapScaleBitmap;
private final Canvas mapScaleCanvas;
private final MapView mapView;
private boolean redrawNeeded;
private boolean showMapScaleBar;
private final Map<TextField, String> textFields;
MapScaleBar(MapView mapView) {
this.mapView = mapView;
this.mapScaleBitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_4444);
this.mapScaleCanvas = new Canvas(this.mapScaleBitmap);
this.textFields = new HashMap<TextField, String>();
setDefaultTexts();
configurePaints();
}
/**
* @return true if imperial units are used, false otherwise.
*/
public boolean isImperialUnits() {
return this.imperialUnits;
}
/**
* @return true if this map scale bar is visible, false otherwise.
*/
public boolean isShowMapScaleBar() {
return this.showMapScaleBar;
}
/**
* @param imperialUnits
* true if imperial units should be used rather than metric units.
*/
public void setImperialUnits(boolean imperialUnits) {
this.imperialUnits = imperialUnits;
this.redrawNeeded = true;
}
/**
* @param showMapScaleBar
* true if the map scale bar should be drawn, false otherwise.
*/
public void setShowMapScaleBar(boolean showMapScaleBar) {
this.showMapScaleBar = showMapScaleBar;
}
/**
* Overrides the specified text field with the given string.
*
* @param textField
* the text field to override.
* @param value
* the new value of the text field.
*/
public void setText(TextField textField, String value) {
this.textFields.put(textField, value);
this.redrawNeeded = true;
}
private void drawScaleBar(float scaleBarLength, Paint paint) {
this.mapScaleCanvas.drawLine(7, 25, scaleBarLength + 3, 25, paint);
this.mapScaleCanvas.drawLine(5, 10, 5, 40, paint);
this.mapScaleCanvas.drawLine(scaleBarLength + 5, 10, scaleBarLength + 5, 40, paint);
}
private void drawScaleText(int scaleValue, String unitSymbol, Paint paint) {
this.mapScaleCanvas.drawText(scaleValue + unitSymbol, 12, 18, paint);
}
private boolean isRedrawNecessary() {
if (this.redrawNeeded || this.mapPosition == null) {
return true;
}
MapPosition currentMapPosition = this.mapView.getMapViewPosition().getMapPosition();
if (currentMapPosition.zoomLevel != this.mapPosition.zoomLevel) {
return true;
}
double latitudeDiff = Math.abs(currentMapPosition.geoPoint.latitude - this.mapPosition.geoPoint.latitude);
if (latitudeDiff > LATITUDE_REDRAW_THRESHOLD) {
return true;
}
return false;
}
/**
* Redraws the map scale bitmap with the given parameters.
*
* @param scaleBarLength
* the length of the map scale bar in pixels.
* @param mapScaleValue
* the map scale value in meters.
*/
private void redrawMapScaleBitmap(float scaleBarLength, int mapScaleValue) {
this.mapScaleBitmap.eraseColor(Color.TRANSPARENT);
// draw the scale bar
drawScaleBar(scaleBarLength, SCALE_BAR_STROKE);
drawScaleBar(scaleBarLength, SCALE_BAR);
int scaleValue;
String unitSymbol;
if (this.imperialUnits) {
if (mapScaleValue < ONE_MILE) {
scaleValue = mapScaleValue;
unitSymbol = this.textFields.get(TextField.FOOT);
} else {
scaleValue = mapScaleValue / ONE_MILE;
unitSymbol = this.textFields.get(TextField.MILE);
}
} else {
if (mapScaleValue < ONE_KILOMETER) {
scaleValue = mapScaleValue;
unitSymbol = this.textFields.get(TextField.METER);
} else {
scaleValue = mapScaleValue / ONE_KILOMETER;
unitSymbol = this.textFields.get(TextField.KILOMETER);
}
}
// draw the scale text
drawScaleText(scaleValue, unitSymbol, SCALE_TEXT_STROKE);
drawScaleText(scaleValue, unitSymbol, SCALE_TEXT);
}
private void setDefaultTexts() {
this.textFields.put(TextField.FOOT, " ft");
this.textFields.put(TextField.MILE, " mi");
this.textFields.put(TextField.METER, " m");
this.textFields.put(TextField.KILOMETER, " km");
}
void destroy() {
this.mapScaleBitmap.recycle();
}
void draw(Canvas canvas) {
int top = this.mapView.getHeight() - BITMAP_HEIGHT - MARGIN_BOTTOM;
canvas.drawBitmap(this.mapScaleBitmap, MARGIN_LEFT, top, null);
}
void redrawScaleBar() {
if (!isRedrawNecessary()) {
return;
}
this.mapPosition = this.mapView.getMapViewPosition().getMapPosition();
double groundResolution = MercatorProjection.calculateGroundResolution(this.mapPosition.geoPoint.latitude,
this.mapPosition.zoomLevel);
int[] scaleBarValues;
if (this.imperialUnits) {
groundResolution = groundResolution / METER_FOOT_RATIO;
scaleBarValues = SCALE_BAR_VALUES_IMPERIAL;
} else {
scaleBarValues = SCALE_BAR_VALUES_METRIC;
}
float scaleBarLength = 0;
int mapScaleValue = 0;
for (int i = 0; i < scaleBarValues.length; ++i) {
mapScaleValue = scaleBarValues[i];
scaleBarLength = mapScaleValue / (float) groundResolution;
if (scaleBarLength < (BITMAP_WIDTH - 10)) {
break;
}
}
redrawMapScaleBitmap(scaleBarLength, mapScaleValue);
this.redrawNeeded = false;
}
}