001: /*
002: * uDig - User Friendly Desktop Internet GIS client
003: * http://udig.refractions.net
004: * (C) 2004, Refractions Research Inc.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package net.refractions.udig.printing.ui;
018:
019: import java.awt.Color;
020: import java.awt.Point;
021: import java.awt.Rectangle;
022: import java.awt.geom.GeneralPath;
023: import java.awt.geom.Rectangle2D;
024: import java.awt.geom.RoundRectangle2D;
025: import java.text.MessageFormat;
026: import java.util.Locale;
027:
028: import net.refractions.udig.mapgraphic.MapGraphic;
029: import net.refractions.udig.mapgraphic.MapGraphicContext;
030: import net.refractions.udig.printing.ui.internal.Messages;
031: import net.refractions.udig.printing.ui.internal.PrintingPlugin;
032: import net.refractions.udig.ui.graphics.ViewportGraphics;
033:
034: import org.geotools.geometry.DirectPosition2D;
035: import org.geotools.referencing.GeodeticCalculator;
036: import org.opengis.referencing.FactoryException;
037: import org.opengis.referencing.crs.CoordinateReferenceSystem;
038: import org.opengis.referencing.operation.TransformException;
039: import org.opengis.spatialschema.geometry.DirectPosition;
040:
041: import com.vividsolutions.jts.geom.Coordinate;
042:
043: /**
044: * Provides a decorator object that represents a typical Scalebar
045: *
046: * @author Richard Gould
047: * @since 0.6.0
048: */
049: public class ScalebarMapGraphic implements MapGraphic {
050:
051: public static final int KILOMETRE = 1000;
052: public static final int METRE = 1;
053:
054: static int[] niceIntegers = new int[] { 0, 1, 2, 5, //zero is a trick - we don't want to display "0km", but this way we can easily downgrade to metres
055: 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000 };
056:
057: static int closestInt(int value, final int[] range) {
058: if (range == null) {
059: throw new NullPointerException("Range cannot be null"); //$NON-NLS-1$
060: }
061:
062: if (range.length == 0) {
063: throw new IllegalArgumentException(
064: "Range cannot be a zero length array"); //$NON-NLS-1$
065: }
066:
067: for (int i = 0; i < range.length; i++) {
068: if (value < range[i]) {
069: if (i == 0) {
070: return range[0];
071: }
072:
073: int dist1 = Math.abs(value - range[i - 1]);
074: int dist2 = Math.abs(range[i] - value);
075:
076: if (dist1 == dist2) {
077: return range[i - 1];
078: }
079:
080: if (dist1 < dist2) {
081: return range[i - 1];
082: } else {
083: return range[i];
084: }
085: }
086: }
087: return range[range.length - 1];
088: }
089:
090: /*
091: * @see net.refractions.udig.project.render.decorator.Decorator#draw(java.awt.Graphics2D, net.refractions.udig.project.render.RenderContext)
092: */
093: public void draw(MapGraphicContext context) {
094: int measurement = KILOMETRE;
095: ViewportGraphics graphics = context.getGraphics();
096:
097: Rectangle location = (Rectangle) context.getLayer()
098: .getStyleBlackboard().get(LocationStyleContent.ID);
099: if (location == null) {
100: location = LocationStyleContent.createDefaultStyle();
101: context.getLayer().getStyleBlackboard().put(
102: LocationStyleContent.ID, location);
103: }
104:
105: /*
106: * 1. find out the width of the map [display] in pixels
107: */
108:
109: int displayWidth = context.getRenderManagerInternal()
110: .getMapDisplay().getWidth();
111: int displayHeight = context.getRenderManagerInternal()
112: .getMapDisplay().getHeight();
113:
114: /*
115: * 2. take a coordinate from the left side and the right side of
116: * the map, in the map projection (whatever that may be).
117: */
118: Coordinate min = context.getViewportModelInternal()
119: .pixelToWorld(0, displayHeight / 2);
120: Coordinate max = context.getViewportModelInternal()
121: .pixelToWorld(displayWidth, displayHeight / 2);
122:
123: /*
124: * 3. back-project the coordinates from 2. to latlong (or, if
125: * they are already in latlong, leave them be)
126: *
127: * GeodeticCalculator can handle this for us -rg
128: */
129:
130: /*
131: * 4. calculate the distance between the two point, using the
132: * distance on a spheroid functions from the CRS packages
133: *
134: * 5. now you have the width of the screen in pixels, and the
135: * width of the screen in meters, so you know the number of
136: * ground units per pixel
137: */
138: double distance;
139: double unitsPerPixel = 0.0;
140: int trueBarLength = 0;
141: int nice = 0;
142:
143: if (min == null || max == null) {
144: distance = 0.0;
145: } else {
146: try {
147: CoordinateReferenceSystem mapCRS = context
148: .getViewportModel().getCRS();
149: GeodeticCalculator calc = new GeodeticCalculator(mapCRS);
150: calc.setAnchorPosition(new DirectPosition2D(min.x,
151: min.y));
152: calc.setDestinationPosition(new DirectPosition2D(max.x,
153: max.y));
154: distance = calc.getOrthodromicDistance();
155:
156: unitsPerPixel = distance / displayWidth;
157: // System.out.println("Units per pixel: " + unitsPerPixel);
158:
159: int idealBarLength = location.width;
160: // System.out.println("Ideal bar length: " + idealBarLength);
161:
162: double mIdealBarDistance = (unitsPerPixel * idealBarLength);
163: // System.out.println("Ideal bar distance: " + mIdealBarDistance);
164: nice = closestInt((int) mIdealBarDistance / 1000,
165: niceIntegers) * 1000;
166:
167: if (nice == 0) {
168: nice = closestInt((int) mIdealBarDistance,
169: niceIntegers);
170: measurement = METRE;
171: }
172:
173: // System.out.println("Nice distance: " + nice);
174:
175: calc.setAnchorPosition(new DirectPosition2D(min.x,
176: min.y));
177: // System.out.println("Anchor: " + new DirectPosition2D(min.x, min.y));
178: calc.setDirection(90, nice); //90 is east
179: DirectPosition nicePoint = calc
180: .getDestinationPosition();
181: // System.out.println("Nice point: " + nicePoint);
182:
183: Point nicePixels = context.worldToPixel(new Coordinate(
184: nicePoint.getOrdinate(0), nicePoint
185: .getOrdinate(1)));
186:
187: // System.out.println("Nice pixels: " + nicePixels);
188:
189: trueBarLength = nicePixels.x;
190:
191: // System.out.println("True bar length: " + trueBarLength);
192: } catch (IllegalArgumentException outOfRange) {
193: distance = 0.0;
194: } catch (FactoryException e) {
195: PrintingPlugin
196: .log(
197: "Factory Exception while calculating distance using Geodetic Calculator", e); //$NON-NLS-1$
198: distance = 0.0;
199: } catch (TransformException e) {
200: PrintingPlugin
201: .log(
202: "Transform Exception while transforming positions", e); //$NON-NLS-1$
203: distance = 0.0;
204: }
205: }
206: if (distance == 0.0) {
207: graphics.setColor(Color.GRAY);
208: graphics.fillRect(location.x, location.y, location.width,
209: location.height);
210: graphics.setColor(Color.BLACK);
211: //graphics.drawRect( location.x, location.y, location.width, location.height );
212: graphics.draw(new Rectangle(location.x, location.y,
213: location.width, location.height));
214:
215: int yText = (location.y < (displayHeight / 2)) ? location.y
216: + location.height + 15 : location.y - 15;
217: graphics.drawString(
218: Messages.ScalebarMapGraphic_zoomInRequiredMessage,
219: location.x, yText, -1, -1);
220:
221: return;
222: }
223:
224: /*
225: * Draw the scale bar
226: */
227: int x = location.x;
228: int y = location.y;
229:
230: int width = trueBarLength;
231: int height = location.height;
232:
233: int barWidth = 4;
234:
235: int barStartX = x + barWidth / 2;
236: int barStartY = y;
237:
238: GeneralPath path = new GeneralPath();
239: path.moveTo(barStartX, barStartY);
240: path.lineTo(barStartX, barStartY + height);
241: path.lineTo(barStartX + width, barStartY + height);
242: path.lineTo(barStartX + width, barStartY);
243:
244: graphics.setColor(Color.WHITE);
245: graphics.setStroke(ViewportGraphics.LINE_SOLID, barWidth + 2);
246:
247: graphics.draw(path);
248:
249: graphics.setColor(Color.BLACK);
250: graphics.setStroke(ViewportGraphics.LINE_SOLID, barWidth);
251:
252: graphics.draw(path);
253:
254: MessageFormat formatter = new MessageFormat(
255: "", Locale.getDefault()); //$NON-NLS-1$
256: if (measurement == KILOMETRE) {
257: formatter
258: .applyPattern(Messages.ScalebarMapGraphic_numberOfKilometres);
259: } else {
260: formatter
261: .applyPattern(Messages.ScalebarMapGraphic_numberOfMetres);
262: }
263:
264: Object[] arguments = { nice / measurement };
265: String msg = formatter.format(arguments);
266:
267: int yText = y + (height + barWidth) / 2 - 2;
268: int xText = x + barWidth + 2;
269:
270: //Draw a little alpha box underneath the text, so it can be viewed in dark areas.
271: graphics.setColor(new Color(255, 255, 255, 103)); // 103/255 = ~40% transparency
272:
273: Rectangle2D msgBounds = graphics.getStringBounds(msg);
274: RoundRectangle2D roundBounds = new RoundRectangle2D.Double();
275: roundBounds.setRoundRect(xText - 1, yText
276: - (int) msgBounds.getHeight() + 3,
277: msgBounds.getWidth() + 4, msgBounds.getHeight() - 1,
278: 10, 10);
279: graphics.fill(roundBounds);
280:
281: //Draw the string denoting kilometres (or metres)
282: graphics.setColor(Color.BLACK);
283: graphics.drawString(msg, xText, yText,
284: ViewportGraphics.ALIGN_LEFT,
285: ViewportGraphics.ALIGN_BOTTOM);
286: }
287:
288: }
|