X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=org.simantics.district.maps%2Fsrc%2Forg%2Fsimantics%2Fmaps%2Fsg%2FMapScaleNode.java;h=9b4c9ba56c0a19667d9c86dc780634a2a88b7fc4;hb=refs%2Fchanges%2F55%2F3355%2F2;hp=e506e11d4a14ab71e2f4dacb7ed5c8797d4f502f;hpb=74600320e9ef95377a2e0acee5d445334e1e55e5;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 index e506e11d..9b4c9ba5 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java @@ -1,42 +1,53 @@ 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()); @@ -48,193 +59,127 @@ public class MapScaleNode extends G2DNode { 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; + } + }