X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=org.simantics.district.maps%2Fsrc%2Forg%2Fsimantics%2Fmaps%2Fsg%2FMapScaleNode.java;fp=org.simantics.district.maps%2Fsrc%2Forg%2Fsimantics%2Fmaps%2Fsg%2FMapScaleNode.java;h=a0242c11d502781fac0a69e7b3511b6178e09a3c;hb=55f42e7fcc2f6733082ab8c150efe3a2b54ff22b;hp=0000000000000000000000000000000000000000;hpb=5750b5736279abe0d3e310b2da4fc1be3ffa0004;p=simantics%2Fdistrict.git diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java new file mode 100644 index 00000000..a0242c11 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java @@ -0,0 +1,242 @@ +package org.simantics.maps.sg; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +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.scenegraph.utils.GridUtils; + +public class MapScaleNode extends G2DNode { + + private static final long serialVersionUID = -2738682328944298290L; + + private static final Color GRAY = new Color(100, 100, 100); + + protected Boolean enabled = true; + + protected double gridSize = 1.0; + + private double scale; + + @Override + public void render(Graphics2D g) { + if (!enabled) + return; + + AffineTransform tr = g.getTransform(); + double scaleX = Math.abs(tr.getScaleX()); + double scaleY = Math.abs(tr.getScaleY()); + if (scaleX <= 0 || scaleY <= 0) { + // Make sure that we don't end up in an eternal loop below. + return; + } + double offsetX = tr.getTranslateX(); + double offsetY = tr.getTranslateY(); + g.setTransform(new AffineTransform()); + + Font rulerFont = new Font("Tahoma", Font.PLAIN, 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() - 30; + + double scaleRight = bounds.getMaxX() - 30; + + 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() - 30, 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; + for(double x = newScaleLeft; x < scaleRight; x += stepX) { + String str = formatValue(label * meterPerPixel); + FontMetrics fm = g.getFontMetrics(); + 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; + } + + g.setTransform(tr); + } + + @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; + } + } + 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); + } + } + } + + 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; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setScale(double scale) { + this.scale = scale; + } + + @Override + public void init() { + try { + EPSG4326 = CRS.decode("EPSG:4326"); + calculator = new GeodeticCalculator(EPSG4326); + } catch (FactoryException e) { + e.printStackTrace(); + } + 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) { + double startLon = (screenX / scaleX) / scale; + double val = (screenY / scaleY) / scale; + val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val)))); + double startLat = val; + double endLon = ((screenX + 1) / scaleX) / scale; + double endLat = val; + + calculator.setStartingGeographicPoint(startLon, startLat); + calculator.setDestinationGeographicPoint(endLon, endLat); + double distance = calculator.getOrthodromicDistance(); + + return distance; + } + +}