package org.simantics.maps.sg;
import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
import java.awt.Color;
+import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
-import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
-import java.util.Locale;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.maps.sg.Formatting.FormatMode;
import org.simantics.scenegraph.utils.DPIUtil;
-import org.simantics.scenegraph.utils.GridUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class MapScaleNode extends G2DNode {
+public class MapScaleNode extends MapInfoNode {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MapScaleNode.class);
private static final long serialVersionUID = -2738682328944298290L;
-
- private static final Color GRAY = new Color(100, 100, 100);
- protected Boolean enabled = true;
+ private static final Color GRAY = new Color(50, 50, 50);
+
+ private static final transient int SCALE_VERTICAL_MARGIN = 4;
+ private static final transient int SCALE_MAJOR_TICK_HEIGHT = 8;
+ private static final transient int SCALE_MINOR_TICK_HEIGHT = 5;
+ private static final transient int SCALE_MICRO_TICK_HEIGHT = 3;
+ private static final transient double SCALE_RIGHT_MARGIN = 20;
+
+ private static final transient int MAX_DIGITS = 0;
+ private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 2);
+ private static final transient String[] SI_LENGTH_UNIT = { "m", "km" };
- protected double gridSize = 1.0;
+ 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 };
+
+ private GeodeticCalculator calculator;
@Override
public void render(Graphics2D g) {
- if (!enabled)
+ if (!enabled || calculator == null)
return;
AffineTransform ot = g.getTransform();
g.transform(transform);
-
+
AffineTransform tr = g.getTransform();
double scaleX = Math.abs(tr.getScaleX());
double scaleY = Math.abs(tr.getScaleY());
double offsetY = tr.getTranslateY();
g.setTransform(new AffineTransform());
- Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));;
-
- //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setStroke(new BasicStroke(1));
- g.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
-
- Rectangle2D bounds = g.getClipBounds();
- if(bounds == null) return; // FIXME
-
- double previousText = -100;
-
- double minY = bounds.getMaxY() - 40;
-
- double scaleRight = bounds.getMaxX() - 20;
-
- double meterPerPixel = getMeterPerPixel(scaleRight - offsetX, minY - offsetY, scaleX, scaleY);
-
- double pixels = 0;
- double value = 0;
- for (int i = 0; i < SCALE_VALUES.length; i++) {
- value = SCALE_VALUES[i];
- pixels = value / meterPerPixel;
- if (pixels > 100) {
- break;
- }
- }
-
-
- double newScaleLeft = scaleRight - pixels;
- g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
- Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft, bounds.getMaxY() - 40, pixels, 20);
- g.fill(vertical);
-
- g.setColor(GRAY);
- g.setFont(rulerFont);
-
-
- // stepX and stepY should be something between 50 and 100
- double stepX = 50;
-
- stepX = GridUtils.limitedEvenGridSpacing(stepX, scaleX, 100, gridSize, true);
- //while(stepX * scaleX > 100) stepX /= 2;
- //while(stepY * scaleY > 100) stepY /= 2;
-
- while(stepX * scaleX < 50) stepX *= 2;
-
- stepX *= scaleX;
-
- double gap = scaleRight -newScaleLeft;
-
- stepX = gap / 2 - 0.00001;
-
- // Horizontal ruler
- double label = 0;
+ Rectangle2D controlBounds = g.getClipBounds();
+ if (controlBounds == null)
+ return; // FIXME
+
+ Font font = MapInfoConstants.getInfoFont();
+ g.setFont(font);
FontMetrics fm = g.getFontMetrics();
- for(double x = newScaleLeft; x < scaleRight; x += stepX) {
- String str = formatValue(label * meterPerPixel);
- Rectangle2D r = fm.getStringBounds(str, g);
- if((x - r.getWidth() / 2) > previousText) {
- g.setColor(Color.BLACK);
- g.drawString(str, (int)(x-r.getWidth()/2), (int)(minY+1+r.getHeight()));
- previousText = x+r.getWidth()/2+stepX/4;
- }
- g.setColor(GRAY);
- g.drawLine((int)x, (int)minY+12, (int)x, (int)minY+19);
- if (x + 0.1 < scaleRight) {
- if(stepX/5 > 2) {
- for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) {
- if(x2 > 20) {
- g.drawLine((int)x2, (int)minY+15, (int)x2, (int)minY+19);
- }
- }
- for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) {
- if(x2 > 20) {
- g.drawLine((int)x2, (int)minY+17, (int)x2, (int)minY+19);
- }
- }
- }
- }
- label += stepX;
- }
+ int majorTickHeight = DPIUtil.upscale(SCALE_MAJOR_TICK_HEIGHT);
+ int minorTickHeight = DPIUtil.upscale(SCALE_MINOR_TICK_HEIGHT);
+ int microTickHeight = DPIUtil.upscale(SCALE_MICRO_TICK_HEIGHT);
+ double yOffsetFromBottom = getMapInfoNextY(g);
- g.setTransform(ot);
- }
+ double scaleTotalHeight = Math.max(majorTickHeight + SCALE_VERTICAL_MARGIN * 2 , font.getSize() + MapInfoConstants.TEXT_VERTICAL_MARGIN * 2);
+ double scaleMaxX = controlBounds.getMaxX() - SCALE_RIGHT_MARGIN;
+ double scaleMaxY = controlBounds.getMaxY() - yOffsetFromBottom;
+ double scaleMinY = scaleMaxY - scaleTotalHeight;
+ int textY = (int) scaleMinY + MapInfoConstants.TEXT_VERTICAL_MARGIN + fm.getMaxAscent();
+ double meterPerPixel = getMeterPerPixel(scaleMaxX - offsetX, scaleMinY - offsetY, scaleX, scaleY);
- @Override
- public Rectangle2D getBoundsInLocal() {
- return null;
- }
-
- private static final transient int MAX_DIGITS = 0;
- private static final transient double EPSILON = 0.01;
- private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 2);
- //private static final transient String[] SI_UNIT_LARGE_PREFIXES = { "m", "km" };
-
- 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 };
-
- public static String formatValue(double value) {
- int magnitude = (int) Math.round(Math.log10(value));
- //System.out.println("magnitude: " + magnitude + ", " + value);
- int allowedDecimals = MAX_DIGITS;
- allowedDecimals -= Math.abs(magnitude);
- if (allowedDecimals < 0)
- allowedDecimals = 0;
-
- String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
- if (allowedDecimals > 0) {
- for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
- char ch = valueStr.charAt(trunc);
- if (ch == '.') {
- valueStr = valueStr.substring(0, trunc);
- break;
- }
- if (valueStr.charAt(trunc) != '0') {
- valueStr = valueStr.substring(0, trunc + 1);
- break;
+ // Prevent the scale from getting too long in the X-direction
+ if (meterPerPixel < 0.005)
+ return;
+
+ double scaleWidth = findScaleWidth(meterPerPixel);
+ double scaleMinX = scaleMaxX - scaleWidth;
+ double value = scaleWidth * meterPerPixel;
+ String formattedValue = formatValue(value);
+ Rectangle2D textBounds = fm.getStringBounds(formattedValue, g);
+ double addedTextWidth = textBounds.getWidth() + MapInfoConstants.TEXT_HORIZONTAL_MARGIN * 2;
+ scaleMinX -= addedTextWidth;
+ scaleMaxX -= addedTextWidth;
+ rect.setFrame(scaleMinX, scaleMinY, scaleWidth + addedTextWidth, scaleTotalHeight);
+
+// System.out.println("----");
+// System.out.println("scale: " + scaleX + ", " + scaleY);
+// System.out.println("scaleMaxX: " + scaleMaxX + ", offsetX: " + offsetX);
+// System.out.println("scaleMinY: " + scaleMinY + ", offsetY: " + offsetY);
+// System.out.println("meterPerPixel: " + meterPerPixel);
+
+ Composite oc = g.getComposite();
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
+ g.setStroke(MapInfoConstants.INFO_STROKE);
+
+ g.setColor(MapInfoConstants.TEXT_BG_COLOR);
+ g.fill(rect);
+
+ g.setColor(Color.BLACK);
+ g.drawString(formattedValue, (int) (scaleMinX + scaleWidth) + MapInfoConstants.TEXT_HORIZONTAL_MARGIN, textY);
+
+ double stepX = scaleWidth;
+
+ g.setColor(GRAY);
+ int yOffset = -SCALE_VERTICAL_MARGIN;
+ g.drawLine((int) scaleMinX, (int) scaleMaxY + yOffset + 1, (int) scaleMaxX, (int) scaleMaxY + yOffset + 1);
+ g.drawLine((int) scaleMinX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMinX, (int) scaleMaxY + yOffset);
+ g.drawLine((int) scaleMaxX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMaxX, (int) scaleMaxY + yOffset);
+ double x = scaleMinX;
+ if (stepX / 5 > 2) {
+ for (double x2 = x + stepX / 5; x2 < x + stepX; x2 += stepX / 5) {
+ if (x2 > 20) {
+ g.drawLine((int) x2, (int) scaleMaxY - minorTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
}
}
- if (Math.abs(value) + EPSILON > TRIM_THRESHOLD_MAX_VALUE) {
- // Cut anything beyond a possible decimal dot out since they
- // should not show anyway. This is a complete hack that tries to
- // circumvent floating-point inaccuracy problems.
- int dotIndex = valueStr.lastIndexOf('.');
- if (dotIndex > -1) {
- valueStr = valueStr.substring(0, dotIndex);
+ for (double x2 = x + stepX / 10; x2 < x + stepX; x2 += stepX / 5) {
+ if (x2 > 20) {
+ g.drawLine((int) x2, (int) scaleMaxY - microTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
}
}
}
- if (valueStr.equals("-0"))
- valueStr = "0";
-
-// if (!valueStr.equals("0")) {
-// double trimValue = value;
-// for (int i = 0; i < SI_UNIT_LARGE_PREFIXES.length; i++) {
-// double trim = trimValue / 1000;
-// if (Math.abs(trim)-EPSILON < TRIM_THRESHOLD_MAX_VALUE) {
-// valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
-// valueStr += SI_UNIT_LARGE_PREFIXES[i];
-// break;
-// }
-// trimValue = trim;
-// }
-// }
-
- return valueStr;
+ g.setComposite(oc);
+ g.setTransform(ot);
+
+ setMapInfoNextY(g, yOffsetFromBottom + scaleTotalHeight + MapInfoConstants.INFO_ROW_SPACING);
}
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
+ private String formatValue(double value) {
+ int lengthUnit = 0;
+ if (value > 1000.0) {
+ value /= 1000.0;
+ lengthUnit = 1;
+ }
+ return Formatting.formatValue(value, MAX_DIGITS, true, FormatMode.LIMIT_DECIMALS, TRIM_THRESHOLD_MAX_VALUE, false)
+ + " " + SI_LENGTH_UNIT[lengthUnit];
}
@Override
public void init() {
try {
- EPSG4326 = CRS.decode("EPSG:4326");
+ CoordinateReferenceSystem EPSG4326 = CRS.decode("EPSG:4326");
calculator = new GeodeticCalculator(EPSG4326);
} catch (FactoryException e) {
- e.printStackTrace();
+ LOGGER.error("Failed to initialize GeodeticCalculator with coordinate reference system EPSG:4326", e);
}
super.init();
}
-
- GeodeticCalculator calculator;
- CoordinateReferenceSystem EPSG4326;
-
- public Point2D scaleLeftmostPoint(double startLon, double startLat, double distance, double azimuth) {
- GeodeticCalculator calculator = new GeodeticCalculator();
- calculator.setStartingGeographicPoint(startLon, startLat);
- calculator.setDirection(azimuth, distance);
- return calculator.getDestinationGeographicPoint();
- }
-
- public double getMeterPerPixel(double screenX, double screenY, double scaleX, double scaleY) {
+
+ private double getMeterPerPixel(double screenX, double screenY, double scaleX, double scaleY) {
double startLon = (screenX / scaleX);
double val = (screenY / scaleY);
val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
double startLat = val;
double endLon = ((screenX + 1) / scaleX);
double endLat = val;
-
+
calculator.setStartingGeographicPoint(startLon, startLat);
calculator.setDestinationGeographicPoint(endLon, endLat);
double distance = calculator.getOrthodromicDistance();
-
+
return distance;
}
+ private double findScaleWidth(double meterPerPixel) {
+ double scaleWidth = 0;
+ for (int i = 0; i < SCALE_VALUES.length; i++) {
+ scaleWidth = SCALE_VALUES[i] / meterPerPixel;
+ if (scaleWidth > 100)
+ break;
+ }
+ return scaleWidth;
+ }
+
}