/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.gwt.client.gfx.paintable.mapaddon;
import org.geomajas.configuration.client.UnitType;
import org.geomajas.geometry.Coordinate;
import org.geomajas.gwt.client.gfx.PainterVisitor;
import org.geomajas.gwt.client.gfx.paintable.Rectangle;
import org.geomajas.gwt.client.gfx.paintable.Text;
import org.geomajas.gwt.client.gfx.style.FontStyle;
import org.geomajas.gwt.client.gfx.style.ShapeStyle;
import org.geomajas.gwt.client.spatial.Bbox;
import org.geomajas.gwt.client.widget.MapWidget;
import com.google.gwt.i18n.client.NumberFormat;
/**
* <p>
* Map add-on that displays a scalebar.
* </p>
*
* @author Kristof Heirwegh
* @author Pieter De Graef
*/
public class ScaleBar extends MapAddon {
private static final ShapeStyle STYLE_BACKGROUND = new ShapeStyle("white", 0.60f, "white", 0.30f, 1);
private static final ShapeStyle STYLE_MARKER = new ShapeStyle("black", 1f, null, 0f, 0);
private static final FontStyle STYLE_FONT = new FontStyle("#000000", 10, "Arial, Verdana", "normal", "normal");
private static final double METERSINMILE = 1609.344d;
private static final double METERSINYARD = 0.9144d;
private static final double FEETINMETER = 3.2808399d;
private static final int HORIZONTALPADDING = 3; // px
private static final int VERTICALPADDING = 3; // px
private static final int MARKERHEIGHT = 13; // px
// max width this scalebar should be
private static final int MAXSIZEINPIXELS = 125;
private Rectangle backGround;
private Rectangle leftMarker;
private Rectangle rightMarker;
private Text distance;
private Rectangle bottomLine;
private Rectangle dummy;
private int[] lengths = new int[] { 1, 2, 5, 10, 25, 50, 100, 250, 500, 750, 1000, 2000, 5000, 10000, 25000, 50000,
75000, 100000, 250000, 500000, 750000, 1000000, 2000000, 5000000, 10000000 };
// position in lengths array up to where to test for yards (larger values is for miles)
private int yardStartingPoint = 11;
/**
* unitlength in meter
*/
private double unitLength;
/**
* how should units be shown to the user
*/
private UnitType unitType;
// -- for internal use, holds the last calculated best value
private int widthInUnits;
// -- for internal use, holds the last calculated best value
private int widthInPixels;
// -- for internal use, for UnitType.ENGLISH only
private boolean widthInUnitsIsMiles;
private MapWidget map;
public ScaleBar(String id, MapWidget map) {
super(id, MAXSIZEINPIXELS, MARKERHEIGHT + VERTICALPADDING * 2);
this.map = map;
}
public void accept(PainterVisitor visitor, Object group, Bbox bounds, boolean recursive) {
map.getVectorContext().drawGroup(group, this);
// Draw a dummy at 0,0 so that Internet Explorer knows where coordinate 0,0 is. If this is not drawn, the text
// will disappear, because the parent group will have coordinate 0,0 at the upper left corner of the union of
// all the rectangles that are drawn here.
map.getVectorContext().drawRectangle(this, dummy.getId(), dummy.getBounds(), (ShapeStyle) dummy.getStyle());
map.getVectorContext().drawRectangle(this, backGround.getId(), backGround.getBounds(),
(ShapeStyle) backGround.getStyle());
map.getVectorContext().drawRectangle(this, bottomLine.getId(), bottomLine.getBounds(),
(ShapeStyle) bottomLine.getStyle());
map.getVectorContext().drawRectangle(this, leftMarker.getId(), leftMarker.getBounds(),
(ShapeStyle) leftMarker.getStyle());
map.getVectorContext().drawRectangle(this, rightMarker.getId(), rightMarker.getBounds(),
(ShapeStyle) rightMarker.getStyle());
map.getVectorContext().drawText(this, distance.getId(), distance.getContent(), distance.getPosition(),
(FontStyle) distance.getStyle());
}
public void onDraw() {
}
public void onRemove() {
}
/**
*
* @param unitType
* how are units shown (english/metric/crs)
* @param unitLength
* length of a unit in meter
* @param position
* coordinate where scalebar should be drawn on the graphics context
*/
public void initialize(UnitType unitType, double unitLength, Coordinate position) {
if (null == unitType) {
throw new IllegalArgumentException("please provide a unitType");
}
this.unitType = unitType;
this.unitLength = unitLength;
backGround = new Rectangle((null == getId() ? "" : getId() + "-bg"));
backGround.setBounds(new Bbox(0, 0, 5, MARKERHEIGHT + 2 * VERTICALPADDING));
backGround.setStyle(STYLE_BACKGROUND);
leftMarker = new Rectangle((null == getId() ? "" : getId() + "-lm"));
leftMarker.setStyle(STYLE_MARKER);
leftMarker.setBounds(new Bbox(0, 0, 1, MARKERHEIGHT));
rightMarker = new Rectangle((null == getId() ? "" : getId() + "-rm"));
rightMarker.setStyle(STYLE_MARKER);
rightMarker.setBounds(new Bbox(0, 0, 1, MARKERHEIGHT));
bottomLine = new Rectangle((null == getId() ? "" : getId() + "-bm"));
bottomLine.setStyle(STYLE_MARKER);
bottomLine.setBounds(new Bbox(0, 0, 0, 1));
distance = new Text((null == getId() ? "" : getId() + "-text"));
distance.setStyle(STYLE_FONT);
dummy = new Rectangle(getId() + "-dummy");
dummy.setStyle(new ShapeStyle("#FFFFFF", 0, "#FFFFFF", 0, 0));
dummy.setBounds(new Bbox(0, 0, 1, 1));
}
// ----------------------------------------------------------
public double getUnitLength() {
return unitLength;
}
public UnitType getUnitType() {
return unitType;
}
/**
* Adjust the scalebar to reflect the given scale.
*
* @param scale
*/
public void adjustScale(double scale) {
if (null == unitType) {
throw new IllegalStateException("Please initialize scalebar before using");
}
calculateBestFit(scale);
updatePaintables();
}
public void setMapSize(int mapWidth, int mapHeight) {
super.setMapSize(mapWidth, mapHeight);
if (null == unitType) {
throw new IllegalStateException("Please initialize scalebar before using");
}
backGround.getBounds().setX(getUpperLeftCorner().getX());
backGround.getBounds().setY(getUpperLeftCorner().getY());
bottomLine.getBounds().setX(getUpperLeftCorner().getX() + HORIZONTALPADDING);
bottomLine.getBounds().setY(getUpperLeftCorner().getY() + VERTICALPADDING + MARKERHEIGHT);
leftMarker.getBounds().setX(getUpperLeftCorner().getX() + HORIZONTALPADDING);
leftMarker.getBounds().setY(getUpperLeftCorner().getY() + VERTICALPADDING);
rightMarker.getBounds().setY(getUpperLeftCorner().getY() + VERTICALPADDING);
distance.setPosition(new Coordinate(getUpperLeftCorner().getX() + HORIZONTALPADDING + 6, getUpperLeftCorner()
.getY()
+ VERTICALPADDING));
updatePaintables();
}
// ----------------------------------------------------------
private void updatePaintables() {
distance.setContent(formatUnits(widthInUnits));
backGround.getBounds().setWidth(widthInPixels + 2 * HORIZONTALPADDING);
rightMarker.getBounds().setX(leftMarker.getBounds().getX() + widthInPixels - 1);
bottomLine.getBounds().setWidth(widthInPixels);
}
/**
* Find the rounded value (from the lengths array) which fits the closest into the maxSizeInPixels for the given
* scale.
*
* @param scale
* @return closest fit in units (will be miles or yards for English, m for metric, unit for crs)
*/
public void calculateBestFit(double scale) {
int len = 0;
long px = 0;
if (UnitType.ENGLISH.equals(unitType)) {
// try miles
for (int i = lengths.length - 1; i > -1; i--) {
len = this.lengths[i];
px = Math.round((len * scale / unitLength) * METERSINMILE);
if (px < MAXSIZEINPIXELS) {
break;
}
}
// try yards
if (px > MAXSIZEINPIXELS) {
for (int i = yardStartingPoint; i > -1; i--) {
len = this.lengths[i];
px = Math.round((len * scale / unitLength) * METERSINYARD);
if (px < MAXSIZEINPIXELS) {
break;
}
}
widthInUnitsIsMiles = false;
} else {
widthInUnitsIsMiles = true;
}
} else if (UnitType.ENGLISH_FOOT.equals(unitType)) {
// try miles
for (int i = lengths.length - 1; i > -1; i--) {
len = this.lengths[i];
px = Math.round((len * scale / unitLength) * METERSINMILE);
if (px < MAXSIZEINPIXELS) {
break;
}
}
// try feet
if (px > MAXSIZEINPIXELS) {
for (int i = yardStartingPoint; i > -1; i--) {
len = this.lengths[i];
px = Math.round((len * scale / unitLength) / FEETINMETER);
if (px < MAXSIZEINPIXELS) {
break;
}
}
widthInUnitsIsMiles = false;
} else {
widthInUnitsIsMiles = true;
}
} else {
for (int i = lengths.length - 1; i > -1; i--) {
len = this.lengths[i];
px = Math.round(len * scale / unitLength);
if (px < MAXSIZEINPIXELS) {
break;
}
}
}
widthInUnits = len;
widthInPixels = (int) px;
}
public boolean getWidthInUnitsIsMiles() {
return widthInUnitsIsMiles;
}
public int getWidthInUnits() {
return widthInUnits;
}
public int getWidthInPixels() {
return widthInPixels;
}
/**
* format to humanreadable string converting to unittype
*
* @param units
* @return
*/
String formatUnits(int units) {
switch (unitType) {
case ENGLISH:
return NumberFormat.getDecimalFormat().format(units) + (widthInUnitsIsMiles ? " mi" : " yd");
case ENGLISH_FOOT:
return NumberFormat.getDecimalFormat().format(units) + (widthInUnitsIsMiles ? " mi" : " ft");
case METRIC:
if (units < 10000) {
return NumberFormat.getDecimalFormat().format(units) + " m";
} else {
return NumberFormat.getDecimalFormat().format((double) units / 1000) + " km";
}
case CRS:
return NumberFormat.getDecimalFormat().format(units) + " u";
default:
return "??";
}
}
}