1 package org.simantics.maps.sg;
3 import java.awt.AlphaComposite;
4 import java.awt.BasicStroke;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics2D;
9 import java.awt.geom.AffineTransform;
10 import java.awt.geom.Point2D;
11 import java.awt.geom.Rectangle2D;
12 import java.util.Locale;
14 import org.geotools.referencing.CRS;
15 import org.geotools.referencing.GeodeticCalculator;
16 import org.opengis.referencing.FactoryException;
17 import org.opengis.referencing.crs.CoordinateReferenceSystem;
18 import org.simantics.scenegraph.g2d.G2DNode;
19 import org.simantics.scenegraph.utils.DPIUtil;
20 import org.simantics.scenegraph.utils.GridUtils;
22 public class MapScaleNode extends G2DNode {
24 private static final long serialVersionUID = -2738682328944298290L;
26 private static final Color GRAY = new Color(100, 100, 100);
28 protected Boolean enabled = true;
30 protected double gridSize = 1.0;
33 public void render(Graphics2D g) {
37 AffineTransform ot = g.getTransform();
38 g.transform(transform);
40 AffineTransform tr = g.getTransform();
41 double scaleX = Math.abs(tr.getScaleX());
42 double scaleY = Math.abs(tr.getScaleY());
43 if (scaleX <= 0 || scaleY <= 0) {
44 // Make sure that we don't end up in an eternal loop below.
47 double offsetX = tr.getTranslateX();
48 double offsetY = tr.getTranslateY();
49 g.setTransform(new AffineTransform());
51 Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));;
53 //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
54 g.setStroke(new BasicStroke(1));
55 g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
57 Rectangle2D bounds = g.getClipBounds();
58 if(bounds == null) return; // FIXME
60 double previousText = -100;
62 double minY = bounds.getMaxY() - 40;
64 double scaleRight = bounds.getMaxX() - 20;
66 double meterPerPixel = getMeterPerPixel(scaleRight - offsetX, minY - offsetY, scaleX, scaleY);
70 for (int i = 0; i < SCALE_VALUES.length; i++) {
71 value = SCALE_VALUES[i];
72 pixels = value / meterPerPixel;
79 double newScaleLeft = scaleRight - pixels;
80 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
81 Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft, bounds.getMaxY() - 40, pixels, 20);
88 // stepX and stepY should be something between 50 and 100
91 stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
92 //while(stepX * scaleX > 100) stepX /= 2;
93 //while(stepY * scaleY > 100) stepY /= 2;
95 while(stepX * scaleX < 50) stepX *= 2;
99 double gap = scaleRight -newScaleLeft;
101 stepX = gap / 2 - 0.00001;
105 FontMetrics fm = g.getFontMetrics();
106 for(double x = newScaleLeft; x < scaleRight; x += stepX) {
107 String str = formatValue(label * meterPerPixel);
108 Rectangle2D r = fm.getStringBounds(str, g);
109 if((x - r.getWidth() / 2) > previousText) {
110 g.setColor(Color.BLACK);
111 g.drawString(str, (int)(x-r.getWidth()/2), (int)(minY+1+r.getHeight()));
112 previousText = x+r.getWidth()/2+stepX/4;
116 g.drawLine((int)x, (int)minY+12, (int)x, (int)minY+19);
117 if (x + 0.1 < scaleRight) {
119 for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
121 g.drawLine((int)x2, (int)minY+15, (int)x2, (int)minY+19);
124 for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
126 g.drawLine((int)x2, (int)minY+17, (int)x2, (int)minY+19);
138 public Rectangle2D getBoundsInLocal() {
142 private static final transient int MAX_DIGITS = 0;
143 private static final transient double EPSILON = 0.01;
144 private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 2);
145 //private static final transient String[] SI_UNIT_LARGE_PREFIXES = { "m", "km" };
147 private static final transient double[] SCALE_VALUES = { 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000, 50000000 };
149 public static String formatValue(double value) {
150 int magnitude = (int) Math.round(Math.log10(value));
151 //System.out.println("magnitude: " + magnitude + ", " + value);
152 int allowedDecimals = MAX_DIGITS;
153 allowedDecimals -= Math.abs(magnitude);
154 if (allowedDecimals < 0)
157 String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
158 if (allowedDecimals > 0) {
159 for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
160 char ch = valueStr.charAt(trunc);
162 valueStr = valueStr.substring(0, trunc);
165 if (valueStr.charAt(trunc) != '0') {
166 valueStr = valueStr.substring(0, trunc + 1);
170 if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
171 // Cut anything beyond a possible decimal dot out since they
172 // should not show anyway. This is a complete hack that tries to
173 // circumvent floating-point inaccuracy problems.
174 int dotIndex = valueStr.lastIndexOf('.');
176 valueStr = valueStr.substring(0, dotIndex);
181 if (valueStr.equals("-0"))
184 // if (!valueStr.equals("0")) {
185 // double trimValue = value;
186 // for (int i = 0; i < SI_UNIT_LARGE_PREFIXES.length; i++) {
187 // double trim = trimValue / 1000;
188 // if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
189 // valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
190 // valueStr += SI_UNIT_LARGE_PREFIXES[i];
200 public void setEnabled(boolean enabled) {
201 this.enabled = enabled;
207 EPSG4326 = CRS.decode("EPSG:4326");
208 calculator = new GeodeticCalculator(EPSG4326);
209 } catch (FactoryException e) {
215 GeodeticCalculator calculator;
216 CoordinateReferenceSystem EPSG4326;
218 public Point2D scaleLeftmostPoint(double startLon, double startLat, double distance, double azimuth) {
219 GeodeticCalculator calculator = new GeodeticCalculator();
220 calculator.setStartingGeographicPoint(startLon, startLat);
221 calculator.setDirection(azimuth, distance);
222 return calculator.getDestinationGeographicPoint();
225 public double getMeterPerPixel(double screenX, double screenY, double scaleX, double scaleY) {
226 double startLon = (screenX / scaleX);
227 double val = (screenY / scaleY);
228 val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
229 double startLat = val;
230 double endLon = ((screenX + 1) / scaleX);
233 calculator.setStartingGeographicPoint(startLon, startLat);
234 calculator.setDestinationGeographicPoint(endLon, endLat);
235 double distance = calculator.getOrthodromicDistance();