1 package org.simantics.maps.sg;
3 import java.awt.AlphaComposite;
5 import java.awt.Composite;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics2D;
9 import java.awt.geom.AffineTransform;
10 import java.awt.geom.Rectangle2D;
12 import org.geotools.referencing.CRS;
13 import org.geotools.referencing.GeodeticCalculator;
14 import org.opengis.referencing.FactoryException;
15 import org.opengis.referencing.crs.CoordinateReferenceSystem;
16 import org.simantics.maps.sg.Formatting.FormatMode;
17 import org.simantics.scenegraph.utils.DPIUtil;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
21 public class MapScaleNode extends MapInfoNode {
23 private static final Logger LOGGER = LoggerFactory.getLogger(MapScaleNode.class);
25 private static final long serialVersionUID = -2738682328944298290L;
27 private static final Color GRAY = new Color(50, 50, 50);
29 private static final transient int SCALE_VERTICAL_MARGIN = 4;
30 private static final transient int SCALE_MAJOR_TICK_HEIGHT = 8;
31 private static final transient int SCALE_MINOR_TICK_HEIGHT = 5;
32 private static final transient int SCALE_MICRO_TICK_HEIGHT = 3;
33 private static final transient double SCALE_RIGHT_MARGIN = 20;
35 private static final transient int MAX_DIGITS = 0;
36 private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 2);
37 private static final transient String[] SI_LENGTH_UNIT = { "m", "km" };
39 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, 5000000, 10000000, 20000000, 50000000 };
41 private GeodeticCalculator calculator;
44 public void render(Graphics2D g) {
45 if (!enabled || calculator == null)
48 AffineTransform ot = g.getTransform();
49 g.transform(transform);
51 AffineTransform tr = g.getTransform();
52 double scaleX = Math.abs(tr.getScaleX());
53 double scaleY = Math.abs(tr.getScaleY());
54 if (scaleX <= 0 || scaleY <= 0) {
55 // Make sure that we don't end up in an eternal loop below.
58 double offsetX = tr.getTranslateX();
59 double offsetY = tr.getTranslateY();
60 g.setTransform(new AffineTransform());
62 Rectangle2D controlBounds = g.getClipBounds();
63 if (controlBounds == null)
66 Font font = MapInfoConstants.getInfoFont();
68 FontMetrics fm = g.getFontMetrics();
70 int majorTickHeight = DPIUtil.upscale(SCALE_MAJOR_TICK_HEIGHT);
71 int minorTickHeight = DPIUtil.upscale(SCALE_MINOR_TICK_HEIGHT);
72 int microTickHeight = DPIUtil.upscale(SCALE_MICRO_TICK_HEIGHT);
73 double yOffsetFromBottom = getMapInfoNextY(g);
75 double scaleTotalHeight = Math.max(majorTickHeight + SCALE_VERTICAL_MARGIN * 2 , font.getSize() + MapInfoConstants.TEXT_VERTICAL_MARGIN * 2);
76 double scaleMaxX = controlBounds.getMaxX() - SCALE_RIGHT_MARGIN;
77 double scaleMaxY = controlBounds.getMaxY() - yOffsetFromBottom;
78 double scaleMinY = scaleMaxY - scaleTotalHeight;
79 int textY = (int) scaleMinY + MapInfoConstants.TEXT_VERTICAL_MARGIN + fm.getMaxAscent();
80 double meterPerPixel = getMeterPerPixel(scaleMaxX - offsetX, scaleMinY - offsetY, scaleX, scaleY);
82 // Prevent the scale from getting too long in the X-direction
83 if (meterPerPixel < 0.005)
86 double scaleWidth = findScaleWidth(meterPerPixel);
87 double scaleMinX = scaleMaxX - scaleWidth;
88 double value = scaleWidth * meterPerPixel;
89 String formattedValue = formatValue(value);
90 Rectangle2D textBounds = fm.getStringBounds(formattedValue, g);
91 double addedTextWidth = textBounds.getWidth() + MapInfoConstants.TEXT_HORIZONTAL_MARGIN * 2;
92 scaleMinX -= addedTextWidth;
93 scaleMaxX -= addedTextWidth;
94 rect.setFrame(scaleMinX, scaleMinY, scaleWidth + addedTextWidth, scaleTotalHeight);
96 // System.out.println("----");
97 // System.out.println("scale: " + scaleX + ", " + scaleY);
98 // System.out.println("scaleMaxX: " + scaleMaxX + ", offsetX: " + offsetX);
99 // System.out.println("scaleMinY: " + scaleMinY + ", offsetY: " + offsetY);
100 // System.out.println("meterPerPixel: " + meterPerPixel);
102 Composite oc = g.getComposite();
103 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
104 g.setStroke(MapInfoConstants.INFO_STROKE);
106 g.setColor(MapInfoConstants.TEXT_BG_COLOR);
109 g.setColor(Color.BLACK);
110 g.drawString(formattedValue, (int) (scaleMinX + scaleWidth) + MapInfoConstants.TEXT_HORIZONTAL_MARGIN, textY);
112 double stepX = scaleWidth;
115 int yOffset = -SCALE_VERTICAL_MARGIN;
116 g.drawLine((int) scaleMinX, (int) scaleMaxY + yOffset + 1, (int) scaleMaxX, (int) scaleMaxY + yOffset + 1);
117 g.drawLine((int) scaleMinX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMinX, (int) scaleMaxY + yOffset);
118 g.drawLine((int) scaleMaxX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMaxX, (int) scaleMaxY + yOffset);
119 double x = scaleMinX;
121 for (double x2 = x + stepX / 5; x2 < x + stepX; x2 += stepX / 5) {
123 g.drawLine((int) x2, (int) scaleMaxY - minorTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
126 for (double x2 = x + stepX / 10; x2 < x + stepX; x2 += stepX / 5) {
128 g.drawLine((int) x2, (int) scaleMaxY - microTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
136 setMapInfoNextY(g, yOffsetFromBottom + scaleTotalHeight + MapInfoConstants.INFO_ROW_SPACING);
139 private String formatValue(double value) {
141 if (value > 1000.0) {
145 return Formatting.formatValue(value, MAX_DIGITS, true, FormatMode.LIMIT_DECIMALS, TRIM_THRESHOLD_MAX_VALUE, false)
146 + " " + SI_LENGTH_UNIT[lengthUnit];
152 CoordinateReferenceSystem EPSG4326 = CRS.decode("EPSG:4326");
153 calculator = new GeodeticCalculator(EPSG4326);
154 } catch (FactoryException e) {
155 LOGGER.error("Failed to initialize GeodeticCalculator with coordinate reference system EPSG:4326", e);
160 private double getMeterPerPixel(double screenX, double screenY, double scaleX, double scaleY) {
161 double startLon = (screenX / scaleX);
162 double val = (screenY / scaleY);
163 val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
164 double startLat = val;
165 double endLon = ((screenX + 1) / scaleX);
168 calculator.setStartingGeographicPoint(startLon, startLat);
169 calculator.setDestinationGeographicPoint(endLon, endLat);
170 double distance = calculator.getOrthodromicDistance();
175 private double findScaleWidth(double meterPerPixel) {
176 double scaleWidth = 0;
177 for (int i = 0; i < SCALE_VALUES.length; i++) {
178 scaleWidth = SCALE_VALUES[i] / meterPerPixel;
179 if (scaleWidth > 100)