]> gerrit.simantics Code Review - simantics/district.git/blobdiff - org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java
Optimization of district scene graph node rendering
[simantics/district.git] / org.simantics.district.maps / src / org / simantics / maps / sg / MapScaleNode.java
index e506e11d4a14ab71e2f4dacb7ed5c8797d4f502f..9b4c9ba56c0a19667d9c86dc780634a2a88b7fc4 100644 (file)
@@ -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;
+    }
+
 }