]> gerrit.simantics Code Review - simantics/district.git/commitdiff
Optimization of district scene graph node rendering 49/3349/2
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 16 Oct 2019 22:46:26 +0000 (01:46 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Wed, 16 Oct 2019 22:47:42 +0000 (22:47 +0000)
* Removed as many repetitive Graphics2D.getTransform calls as possible
* Cleaned up map info nodes from lower right corner of the screen to
  support hi-dpi/display zoom
* Enabled r-tree based picking for district diagrams
* Cleaner looking and more properly working map scale indicator

gitlab #63

Change-Id: Ieebbfc659ef71ba4ff3dad00664fb7b6ee5019b2

18 files changed:
org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java
org.simantics.district.maps/src/org/simantics/maps/sg/Formatting.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java
org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoConstants.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoNode.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationInfoNode.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java [deleted file]
org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/NetworkDrawingParticipant.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/adapters/DistrictDiagramClassAdapter.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkEdgeNode.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkNodeUtils.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkVertexNode.java
org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingHints.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingPreparationNode.java [new file with mode: 0644]
org.simantics.district.region.ui/src/org/simantics/district/region/ui/handlers/ZoomToRegionHandler.java
org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/ElementSelectionTools.java

index ef88e1c55ea9fdf266506ffbcd82c41b6a75a954..5e989546723e2966866dcb5ea78d0683cc5d8efd 100644 (file)
@@ -23,7 +23,8 @@ import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
 import org.simantics.g2d.participant.MouseUtil;
 import org.simantics.maps.sg.MapAttributionNode;
-import org.simantics.maps.sg.MapLocationZoomInfoNode;
+import org.simantics.maps.sg.MapInfoNode;
+import org.simantics.maps.sg.MapLocationInfoNode;
 import org.simantics.maps.sg.MapNode;
 import org.simantics.maps.sg.MapScaleNode;
 import org.simantics.maps.sg.commands.MapCommands;
@@ -76,13 +77,13 @@ public class MapPainter extends AbstractCanvasParticipant {
         }
     };
 
-    protected MapNode node = null;
-    protected MapScaleNode scaleNode = null;
+    private MapNode mapNode;
+    private MapLocationInfoNode locationInfoNode;
+    private MapScaleNode scaleNode;
+    private MapInfoNode attributionNode;
 
     private AffineTransform transform;
 
-    private MapLocationZoomInfoNode locationZoomInfoNode;
-
     private ScheduledFuture<?> schedule;
 
     public MapPainter(AffineTransform transform) {
@@ -139,7 +140,7 @@ public class MapPainter extends AbstractCanvasParticipant {
     public boolean handleEvent(Event e) {
         if (e instanceof MouseMovedEvent) {
             // here we should somehow re-render ?
-            if (locationZoomInfoNode.isEnabled()) {
+            if (locationInfoNode != null && locationInfoNode.isEnabled()) {
                 if (schedule == null || schedule.isDone()) {
                     LOGGER.debug("current setDirty time" + System.currentTimeMillis());
                     schedule = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
@@ -155,38 +156,42 @@ public class MapPainter extends AbstractCanvasParticipant {
 
     @SGInit
     public void initSG(G2DParentNode parent) {
-        node = parent.addNode("map", MapNode.class);
-        node.setTransform(transform);
-        node.setEnabled(true);
-        node.setZIndex(Integer.MIN_VALUE + 999); // Just under the grid
-        
+        // Just under the grid
+        mapNode = parent.addNode("map", MapNode.class);
+        mapNode.setTransform(transform);
+        mapNode.setEnabled(true);
+        mapNode.setZIndex(Integer.MIN_VALUE + 999);
+
+        // On top of pretty much everything
+        attributionNode = parent.addNode("mapAttribution", MapAttributionNode.class);
+        attributionNode.setTransform(transform);
+        attributionNode.setZIndex(Integer.MAX_VALUE - 999);
+        attributionNode.setEnabled(true);
+
         scaleNode = parent.addNode("mapScale", MapScaleNode.class);
         scaleNode.setTransform(transform);
+        scaleNode.setZIndex(Integer.MAX_VALUE - 998);
         scaleNode.setEnabled(true);
-        scaleNode.setZIndex(Integer.MAX_VALUE - 999); // Just under the grid
-        
-        locationZoomInfoNode = parent.addNode("locationZoomInfo", MapLocationZoomInfoNode.class);
-        locationZoomInfoNode.setTransform(transform);
-        locationZoomInfoNode.setEnabled(true);
-        MouseUtil mouseUtil = getContext().getAtMostOneItemOfClass(MouseUtil.class);
-        locationZoomInfoNode.setMouseUtil(mouseUtil);
-        locationZoomInfoNode.setZIndex(Integer.MAX_VALUE - 999); // Just under the grid
-        
-        MapAttributionNode addNode = parent.addNode("mapAttribution", MapAttributionNode.class);
-        addNode.setTransform(transform);
-        addNode.setEnabled(true);
-        addNode.setZIndex(Integer.MAX_VALUE - 999);
+
+        locationInfoNode = parent.addNode("mapLocationInfo", MapLocationInfoNode.class);
+        locationInfoNode.setTransform(transform);
+        locationInfoNode.setZIndex(Integer.MAX_VALUE - 997);
+        locationInfoNode.setEnabled(true);
+        locationInfoNode.setMouseUtil(getContext().getAtMostOneItemOfClass(MouseUtil.class));
     }
 
     @SGCleanup
     public void cleanupSG() {
-        node.remove();
+        mapNode.remove();
+        attributionNode.remove();
+        scaleNode.remove();
+        locationInfoNode.remove();
     }
 
     protected void updateNode() {
-        node.setEnabled(isPaintingEnabled());
-        node.setEnabled(isMapEnabled());
-        node.setBackgroundColor(getBackgroundColor());
+        mapNode.setEnabled(isPaintingEnabled());
+        mapNode.setEnabled(isMapEnabled());
+        mapNode.setBackgroundColor(getBackgroundColor());
     }
 
     boolean isPaintingEnabled() {
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/Formatting.java b/org.simantics.district.maps/src/org/simantics/maps/sg/Formatting.java
new file mode 100644 (file)
index 0000000..67c803d
--- /dev/null
@@ -0,0 +1,92 @@
+package org.simantics.maps.sg;
+
+import java.util.Locale;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+class Formatting {
+
+    private static final transient double EPSILON = 0.01;
+
+    private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
+
+    private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
+        "k", "M", "G", "T", "P", "E", "Z", "Y"
+    };
+
+    public static enum FormatMode {
+        LIMIT_DIGITS,
+        LIMIT_DECIMALS,
+    }
+
+    public static String formatValue(double value, int max, boolean removeTrailingZeros, FormatMode formatMode) {
+        return formatValue(value, max, removeTrailingZeros, formatMode, TRIM_THRESHOLD_MAX_VALUE);
+    }
+
+    public static String formatValue(double value, int max, boolean removeTrailingZeros, FormatMode formatMode, double trimThresholdMaxValue) {
+        return formatValue(value, max, removeTrailingZeros, formatMode, trimThresholdMaxValue, true);
+    }
+
+    public static String formatValue(double value, int max, boolean removeTrailingZeros, FormatMode formatMode, double trimThresholdMaxValue, boolean trimLargeValues) {
+        //System.out.println("formatValue(" + value + ", " + max + ", " + removeTrailingZeros + ", " + formatMode + ", " + trimThresholdMaxValue + ", " + trimLargeValues + ")");
+        int allowedDecimals = max;
+        if (formatMode == FormatMode.LIMIT_DIGITS) {
+            int magnitude = (int) Math.ceil(Math.log10(Math.abs(value)));
+            //System.out.println("magnitude(" + value + "): " + magnitude);
+            allowedDecimals -= Math.abs(magnitude);
+            if (allowedDecimals < 0)
+                allowedDecimals = 0;
+            //System.out.println("allowedDecimals: " + allowedDecimals);
+        }
+
+        String valueStr = String.format(Locale.US, "%." + allowedDecimals + "f", value);
+        if (allowedDecimals > 0) {
+            if (removeTrailingZeros) {
+                for (int trunc = valueStr.length() - 1; trunc > 0; --trunc) {
+                    char ch = valueStr.charAt(trunc);
+                    if (ch == '.') {
+                        valueStr = valueStr.substring(0, trunc);
+                        break;
+                    }
+                    if (ch != '0') {
+                        valueStr = valueStr.substring(0, trunc + 1);
+                        break;
+                    }
+                }
+            }
+            if (Math.abs(value) + EPSILON > trimThresholdMaxValue) {
+                // 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 (trimLargeValues) {
+            double trimValue = value;
+            if (Math.abs(value) + EPSILON >= trimThresholdMaxValue) {
+                for (int i = 0; Math.abs(trimValue) + EPSILON >= trimThresholdMaxValue; ++i) {
+                    double trim = trimValue / 1000;
+                    if (Math.abs(trim) - EPSILON < trimThresholdMaxValue) {
+                        valueStr = valueStr.substring(0, valueStr.length() - (i + 1) * 3);
+                        valueStr += SI_UNIT_LARGE_PREFIXES[i];
+                        break;
+                    }
+                    trimValue = trim;
+                }
+            }
+        }
+
+        //System.out.println("formatted result: " + valueStr);
+
+        return valueStr;
+    }
+
+}
index b2d28c0b7575491bdd0e3d690c19fbf42fc30ccc..97706d23c6404ef88f00281c98e2e9f0915a0f83 100644 (file)
@@ -1,84 +1,56 @@
 package org.simantics.maps.sg;
 
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
 import java.awt.Color;
-import java.awt.Font;
+import java.awt.Composite;
 import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 
-import org.simantics.scenegraph.g2d.G2DNode;
-import org.simantics.scenegraph.utils.DPIUtil;
-
-public class MapAttributionNode extends G2DNode {
+public class MapAttributionNode extends MapInfoNode {
 
     private static final long serialVersionUID = 7994492218791569147L;
 
-    private static final Color GRAY              = new Color(100, 100, 100);
-
-    protected boolean enabled = true;
-
     @Override
     public void render(Graphics2D g2d) {
         if (!enabled)
             return;
-        
+
         AffineTransform ot = g2d.getTransform();
-        Color originalColor = g2d.getColor();
-        g2d.transform(transform);
-        
         g2d.setTransform(new AffineTransform());
-        // do the rendering magic
-        
-        Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));
-        
-        //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        g2d.setStroke(new BasicStroke(1));
-        g2d.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
-        
-        Rectangle2D bounds = g2d.getClipBounds();
-        if (bounds == null)
+
+        Rectangle2D controlBounds = g2d.getClipBounds();
+        if (controlBounds == null)
             return; // FIXME
 
-        String str = "Map data \u00A9 OpenStreetMap contributors";
-        
-        g2d.setFont(rulerFont);
+        g2d.setFont(MapInfoConstants.getInfoFont());
+
         FontMetrics fm = g2d.getFontMetrics();
-        Rectangle2D r = fm.getStringBounds(str, g2d);
+        Rectangle2D r = fm.getStringBounds(MapInfoConstants.ATTRIBUTION, g2d);
+
+        double yOffsetFromBottom = getMapInfoNextY(g2d);
+        double frameMaxX = controlBounds.getMaxX();
+        double frameMaxY = controlBounds.getMaxY() - yOffsetFromBottom;
+        int frameWidth = (int) Math.ceil(r.getWidth()) + MapInfoConstants.TEXT_HORIZONTAL_MARGIN * 2;
+        int frameHeight = (int) Math.ceil(r.getHeight()) + MapInfoConstants.TEXT_VERTICAL_MARGIN * 2;
+
+        Composite oc = g2d.getComposite();
+        g2d.setComposite(MapInfoConstants.INFO_COMPOSITE);
+        g2d.setStroke(MapInfoConstants.INFO_STROKE);
+
+        g2d.setColor(MapInfoConstants.TEXT_BG_COLOR);
+        rect.setFrameFromDiagonal(frameMaxX - frameWidth, frameMaxY - frameHeight, frameMaxX, frameMaxY);
+        g2d.fill(rect);
 
-        double pixels = r.getWidth();
-        double scaleRight = bounds.getMaxX();
-        double newScaleLeft = scaleRight - pixels;
-        double y = bounds.getMaxY();
-        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
-        
-        
-        Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft - 10, y - 15, pixels + 10, 15);
-        g2d.fill(vertical);
-        
-        g2d.setColor(GRAY);
-        g2d.setFont(rulerFont);
-        
-        
         g2d.setColor(Color.BLACK);
-        g2d.drawString(str, (int)newScaleLeft - 5, (int)y - 5);
-        
-        g2d.setColor(originalColor);
-        g2d.setTransform(ot);
-    }
+        g2d.drawString(MapInfoConstants.ATTRIBUTION,
+                (int) rect.getMinX() + MapInfoConstants.TEXT_HORIZONTAL_MARGIN,
+                (int) frameMaxY - fm.getMaxDescent() - MapInfoConstants.TEXT_VERTICAL_MARGIN);
 
-    @Override
-    public Rectangle2D getBoundsInLocal() {
-        return null;
-    }
+        g2d.setComposite(oc);
+        g2d.setTransform(ot);
 
-    public boolean isEnabled() {
-        return enabled;
+        setMapInfoNextY(g2d, yOffsetFromBottom + rect.getHeight() + MapInfoConstants.INFO_ROW_SPACING);
     }
 
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
 }
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoConstants.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoConstants.java
new file mode 100644 (file)
index 0000000..1014243
--- /dev/null
@@ -0,0 +1,48 @@
+package org.simantics.maps.sg;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.RenderingHints;
+
+import org.simantics.scenegraph.utils.DPIUtil;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+class MapInfoConstants {
+
+    public static final String ATTRIBUTION = "Map data \u00A9 OpenStreetMap contributors"; //$NON-NLS-1$
+
+    public static final AlphaComposite INFO_COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f);
+
+    public static final BasicStroke INFO_STROKE = new BasicStroke(1f);
+
+    public static final Color TEXT_BG_COLOR = new Color(0.9f, 0.9f, 0.9f, 0.75f);
+
+    public static final int FONT_SIZE = 9;
+
+    public static final int TEXT_VERTICAL_MARGIN = 2;
+
+    public static final int TEXT_HORIZONTAL_MARGIN = 10;
+
+    public static final int INFO_ROW_SPACING = 4;
+
+    public static int scaledFontSize() {
+        return DPIUtil.upscale(FONT_SIZE);
+    }
+
+    public static Font getInfoFont() {
+        int fontSize = MapInfoConstants.scaledFontSize();
+        return new Font("Tahoma", Font.PLAIN, fontSize);
+    }
+
+    public static final RenderingHints.Key KEY_MAP_INFO_Y_COORDINATE = new RenderingHints.Key(4000) {
+        @Override
+        public boolean isCompatibleValue(Object val) {
+            return val instanceof Double;
+        }
+    };
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoNode.java
new file mode 100644 (file)
index 0000000..16a7c50
--- /dev/null
@@ -0,0 +1,44 @@
+package org.simantics.maps.sg;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+
+import org.simantics.scenegraph.g2d.G2DNode;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public abstract class MapInfoNode extends G2DNode {
+
+    private static final long serialVersionUID = 5514354427232700777L;
+
+    protected boolean enabled = true;
+    protected Rectangle2D rect = new Rectangle2D.Double();
+
+    public MapInfoNode() {
+        super();
+    }
+
+    @Override
+    public Rectangle2D getBoundsInLocal() {
+        return null;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    protected void setMapInfoNextY(Graphics2D g, double y) {
+        g.setRenderingHint(MapInfoConstants.KEY_MAP_INFO_Y_COORDINATE, y);
+    }
+
+    protected double getMapInfoNextY(Graphics2D g) {
+        Double d = (Double) g.getRenderingHint(MapInfoConstants.KEY_MAP_INFO_Y_COORDINATE);
+        return d != null ? d : 0.0;
+    }
+
+}
\ No newline at end of file
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationInfoNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationInfoNode.java
new file mode 100644 (file)
index 0000000..5a8d445
--- /dev/null
@@ -0,0 +1,109 @@
+package org.simantics.maps.sg;
+
+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 org.simantics.g2d.participant.MouseUtil;
+import org.simantics.g2d.participant.MouseUtil.MouseInfo;
+import org.simantics.maps.elevation.server.SingletonTiffTileInterface;
+import org.simantics.maps.sg.Formatting.FormatMode;
+
+public class MapLocationInfoNode extends MapInfoNode {
+
+    private static final long serialVersionUID = 7994492218791569147L;
+
+    private static final transient int    MAX_DIGITS = 7;
+    private static final transient int    MAX_Z_DECIMALS = 2;
+    private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
+    private static final transient double TEXT_RIGHT_MARGIN = 20;
+
+    private MouseUtil mouse;
+
+    @Override
+    public void render(Graphics2D g2d) {
+        if (!enabled || mouse == null)
+            return;
+
+        AffineTransform ot = g2d.getTransform();
+        g2d.setTransform(new AffineTransform());
+
+        Rectangle2D bounds = g2d.getClipBounds();
+        if (bounds == null)
+            return; // FIXME
+
+        MouseInfo mouseInfo = mouse.getMouseInfo(0);
+
+        double startLat;
+        double startLon;
+        if (mouseInfo != null && mouseInfo.canvasPosition != null) {
+            Point2D canvasPosition = mouseInfo.canvasPosition;
+            double cx = canvasPosition.getX();
+            double cy = canvasPosition.getY();
+            startLat = yToLatitude(-cy / transform.getScaleY());
+            startLon = xToLongitude(cx / transform.getScaleX());
+        } else {
+            startLat = 0;
+            startLon = 0;
+        }
+
+        Number z = SingletonTiffTileInterface.lookup(startLat, startLon);
+
+        String str = "X: " + Formatting.formatValue(startLon, MAX_DIGITS, false, FormatMode.LIMIT_DIGITS, TRIM_THRESHOLD_MAX_VALUE) +
+                "   Y: " + Formatting.formatValue(startLat, MAX_DIGITS, false, FormatMode.LIMIT_DIGITS, TRIM_THRESHOLD_MAX_VALUE) +
+                "   Z: " + Formatting.formatValue(z.doubleValue(), MAX_Z_DECIMALS, false, FormatMode.LIMIT_DECIMALS, TRIM_THRESHOLD_MAX_VALUE);
+
+        Font font = MapInfoConstants.getInfoFont();
+        g2d.setFont(font);
+        FontMetrics fm = g2d.getFontMetrics();
+
+        Rectangle2D r = fm.getStringBounds(str, g2d);
+
+        double yOffsetFromBottom = getMapInfoNextY(g2d);
+        double frameMaxY = bounds.getMaxY() - yOffsetFromBottom;
+        double frameMinY = frameMaxY - font.getSize() - 2 * MapInfoConstants.TEXT_VERTICAL_MARGIN;
+        double frameWidth = r.getWidth() + MapInfoConstants.TEXT_HORIZONTAL_MARGIN * 2;
+        double frameMaxX = bounds.getMaxX() - TEXT_RIGHT_MARGIN;
+        double frameMinX = frameMaxX - frameWidth;
+        double textY = frameMinY + MapInfoConstants.TEXT_VERTICAL_MARGIN + fm.getMaxAscent();
+
+        Composite oc = g2d.getComposite();
+        g2d.setComposite(MapInfoConstants.INFO_COMPOSITE);
+        g2d.setStroke(MapInfoConstants.INFO_STROKE);
+
+        g2d.setColor(MapInfoConstants.TEXT_BG_COLOR);
+        rect.setFrame(frameMinX, frameMinY, frameWidth, frameMaxY - frameMinY);
+        g2d.fill(rect);
+
+        g2d.setColor(Color.BLACK);
+        g2d.drawString(str, (int) frameMinX + MapInfoConstants.TEXT_HORIZONTAL_MARGIN, (int) textY);
+
+        g2d.setComposite(oc);
+        g2d.setTransform(ot);
+
+        setMapInfoNextY(g2d, yOffsetFromBottom + rect.getHeight() + MapInfoConstants.INFO_ROW_SPACING);
+    }
+
+    public void setMouseUtil(MouseUtil util) {
+        this.mouse = util;
+    }
+
+    // TODO: these only work with Spherical Mercator
+    private static double xToLongitude(double x) {
+        return x;
+    }
+
+    private static double yToLatitude(double y) {
+        double rad = Math.toRadians(y);
+        double sinh = Math.sinh(rad);
+        double atan = Math.atan(sinh);
+        double finald = Math.toDegrees(atan);
+        return finald;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java
deleted file mode 100644 (file)
index 790ebbe..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-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.simantics.g2d.participant.MouseUtil;
-import org.simantics.g2d.participant.MouseUtil.MouseInfo;
-import org.simantics.maps.elevation.server.SingletonTiffTileInterface;
-import org.simantics.scenegraph.g2d.G2DNode;
-import org.simantics.scenegraph.utils.DPIUtil;
-
-public class MapLocationZoomInfoNode extends G2DNode {
-
-    private static final long serialVersionUID = 7994492218791569147L;
-
-    private static final Color GRAY              = new Color(100, 100, 100);
-
-    protected boolean enabled = true;
-
-    private MouseUtil util;
-    
-    @Override
-    public void render(Graphics2D g2d) {
-        if (!enabled)
-            return;
-        
-        AffineTransform ot = g2d.getTransform();
-        Color originalColor = g2d.getColor();
-        g2d.transform(transform);
-        
-        g2d.setTransform(new AffineTransform());
-        // do the rendering magic
-        
-        Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9));
-        
-        //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        g2d.setStroke(new BasicStroke(1));
-        g2d.setColor(new Color(0.9f, 0.9f, 0.9f, 0.75f));
-        
-        Rectangle2D bounds = g2d.getClipBounds();
-        if (bounds == null)
-            return; // FIXME
-
-        MouseInfo mouseInfo = util.getMouseInfo(0);
-        
-        double startLat;
-        double startLon;
-        if (mouseInfo != null && mouseInfo.canvasPosition != null) {
-            Point2D canvasPosition = mouseInfo.canvasPosition;
-            double cx = canvasPosition.getX();
-            double cy = canvasPosition.getY();
-            
-            startLat = yToLatitude(-cy / transform.getScaleY());
-            startLon = xToLongitude(cx / transform.getScaleX());
-        } else {
-            startLat = 0;
-            startLon = 0;
-        }
-        Number zoomLevel = SingletonTiffTileInterface.lookup(startLat, startLon);
-        String str = "X: " + formatValue(startLon, MAX_DIGITS) + ", Y: " + formatValue(startLat, MAX_DIGITS) + ", Z: " + zoomLevel;
-        g2d.setFont(rulerFont);
-        FontMetrics fm = g2d.getFontMetrics();
-        Rectangle2D r = fm.getStringBounds(str, g2d);
-
-        double pixels = r.getWidth() + 10;
-        double scaleRight = bounds.getMaxX() - 20;
-        double newScaleLeft = scaleRight - pixels;
-        double y = bounds.getMaxY() - 65;
-        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
-        
-        
-        Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft, y, pixels, 20);
-        g2d.fill(vertical);
-        
-        g2d.setColor(GRAY);
-        g2d.setFont(rulerFont);
-        
-        
-        g2d.setColor(Color.BLACK);
-        g2d.drawString(str, (int)newScaleLeft + 5, (int)y + 15);
-        
-        g2d.setColor(originalColor);
-        g2d.setTransform(ot);
-    }
-
-    @Override
-    public Rectangle2D getBoundsInLocal() {
-        return null;
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public void setMouseUtil(MouseUtil util) {
-        this.util = util;
-    }
-
-    private static final transient int    MAX_DIGITS = 7;
-    private static final transient double EPSILON    = 0.01;
-    private static final transient double TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 4);
-    private static final transient String[] SI_UNIT_LARGE_PREFIXES = {
-        "k", "M", "G", "T", "P", "E", "Z", "Y"
-    };
-
-    private static String formatValue(double value, int maxDigits) {
-        int magnitude = (int) Math.round(Math.log10(value));
-        //System.out.println("magnitude: " + magnitude + ", " + value);
-        int allowedDecimals = maxDigits;
-        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);
-                }
-            }
-        }
-
-        double trimValue = value;
-        if (Math.abs(value)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE) {
-            for (int i = 0; Math.abs(trimValue)+EPSILON >= TRIM_THRESHOLD_MAX_VALUE; ++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;
-            }
-        }
-
-        if (valueStr.equals("-0"))
-            valueStr = "0";
-
-        return valueStr;
-    }
-
-    // TODO: these only work with Spherical Mercator
-    private static double xToLongitude(double x) {
-        return x;
-    }
-
-    private static double yToLatitude(double y) {
-        double rad = Math.toRadians(y);
-        double sinh = Math.sinh(rad);
-        double atan = Math.atan(sinh);
-        double finald = Math.toDegrees(atan);
-        return finald;
-    }
-}
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;
+    }
+
 }
index c27b090db092f240f2bc3d0e43b0f4f5a3a0aaac..d0b1a8a1ac9d6a1e5dd73848815d74c17d72c523 100644 (file)
@@ -19,6 +19,7 @@ import org.simantics.db.procedure.Listener;
 import org.simantics.diagram.ui.DiagramModelHints;
 import org.simantics.district.network.DistrictNetworkUtil;
 import org.simantics.district.network.ontology.DistrictNetworkResource;
+import org.simantics.district.network.ui.nodes.DistrictRenderingPreparationNode;
 import org.simantics.district.network.ui.participants.DNPointerInteractor;
 import org.simantics.district.network.ui.participants.DynamicVisualisationContributionsParticipant;
 import org.simantics.district.network.ui.participants.ElevationServerParticipant;
@@ -41,10 +42,12 @@ import org.simantics.g2d.participant.PanZoomRotateHandler;
 import org.simantics.g2d.participant.RenderingQualityInteractor;
 import org.simantics.g2d.participant.TransformUtil;
 import org.simantics.g2d.participant.ZoomToAreaHandler;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
 import org.simantics.maps.MapScalingTransform;
 import org.simantics.maps.eclipse.MapPainter;
 import org.simantics.maps.sg.commands.MapCommands;
 import org.simantics.modeling.ui.diagramEditor.DiagramViewer;
+import org.simantics.scenegraph.g2d.G2DParentNode;
 import org.simantics.scenegraph.g2d.events.command.Command;
 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
 import org.simantics.scenegraph.g2d.events.command.Commands;
@@ -71,6 +74,12 @@ public class DistrictDiagramViewer extends DiagramViewer {
         ctx.add(new NetworkDrawingParticipant(tr));
         ctx.add(new ElevationServerParticipant(tr));
         ctx.add(new DynamicVisualisationContributionsParticipant(tr));
+        
+        // Optimize AffineTransform memory allocations during district diagram rendering
+        G2DParentNode spatialRoot = (G2DParentNode) ctx.getSceneGraph().lookupNode(SceneGraphConstants.SPATIAL_ROOT_NODE_ID);
+        DistrictRenderingPreparationNode prepNode = new DistrictRenderingPreparationNode();
+        prepNode.setZIndex(Integer.MIN_VALUE / 2);
+        spatialRoot.addNode("districtRenderingPrepareNode", prepNode);
     }
     
     protected String getPopupId() {
index bd81bd01aac6e0683c2ec904c0987143a2578322..da2bf32aec1a6ab3170e1386da2dddda8a2e9b7b 100644 (file)
@@ -67,49 +67,41 @@ public class NetworkDrawingParticipant extends AbstractDiagramParticipant {
 
     public boolean pickHoveredElement(Point2D currentMousePos, boolean isConnectionTool) {
         PickRequest req = new PickRequest(new Rectangle2D.Double(currentMousePos.getX(), currentMousePos.getY(), 1e-8, 1e-8)).context(getContext());
-        List<IElement> pickables = new ArrayList<IElement>();
+        List<IElement> pickables = new ArrayList<>();
         pick.pick(diagram, req, pickables);
-        
-        List<IElement> snap = new ArrayList<>(diagram.getSnapshot());
-        
-        // snap.removeAll(pickables);
-        
+
+        List<IElement> snap = diagram.getSnapshot();
+
         boolean changed = false;
-        changed = hoverVertexNodes(snap, false, isConnectionTool, changed, currentMousePos);
-        changed = hoverEdgeNodes(snap, false, isConnectionTool, changed, currentMousePos);
-        changed = hoverVertexNodes(pickables, true, isConnectionTool, changed, currentMousePos);
-        changed = hoverEdgeNodes(pickables, true, isConnectionTool, changed, currentMousePos);
+        changed |= hoverNodes(snap, false, isConnectionTool, currentMousePos);
+        changed |= hoverNodes(pickables, true, isConnectionTool, currentMousePos);
         return changed;
     }
 
-    private boolean hoverVertexNodes(List<IElement> elements, boolean hover, boolean isConnectionTool, boolean changed, Point2D p) {
+    private boolean hoverNodes(List<IElement> elements, boolean hover, boolean isConnectionTool, Point2D p) {
+        boolean changed = false;
         for (IElement elem : elements) {
             Node node = elem.getHint(DistrictNetworkVertexElement.KEY_DN_VERTEX_NODE);
             if (node instanceof DistrictNetworkVertexNode) {
-                changed = ((DistrictNetworkVertexNode) node).hover(hover, isConnectionTool) || changed;
+                changed |= ((DistrictNetworkVertexNode) node).hover(hover, isConnectionTool);
                 if (hover)
                     ((DistrictNetworkVertexNode) node).setMousePosition(p);
-            }
-        }
-        return changed;
-    }
-    
-    private boolean hoverEdgeNodes(List<IElement> elements, boolean hover, boolean isConnectionTool, boolean changed, Point2D p) {
-        for (IElement elem : elements) {
-            Node node = elem.getHint(DistrictNetworkEdgeElement.KEY_DN_EDGE_NODE);
-            if (node instanceof DistrictNetworkEdgeNode) {
-                for (IG2DNode n : ((DistrictNetworkEdgeNode) node).getNodes()) {
-                    if (n instanceof HoverSensitiveNode) {
-                        changed = ((HoverSensitiveNode)n).hover(hover, isConnectionTool) || changed;
-                        if (hover)
-                            ((HoverSensitiveNode)n).setMousePosition(p);
+            } else {
+                node = elem.getHint(DistrictNetworkEdgeElement.KEY_DN_EDGE_NODE);
+                if (node instanceof DistrictNetworkEdgeNode) {
+                    for (IG2DNode n : ((DistrictNetworkEdgeNode) node).getNodes()) {
+                        if (n instanceof HoverSensitiveNode) {
+                            changed |= ((HoverSensitiveNode)n).hover(hover, isConnectionTool);
+                            if (hover)
+                                ((HoverSensitiveNode)n).setMousePosition(p);
+                        }
                     }
                 }
             }
         }
         return changed;
     }
-    
+
     public boolean isHoveringOverNode(Point2D currentMousePos) {
         PickRequest req = new PickRequest(currentMousePos).context(getContext());
         List<IElement> pickables = new ArrayList<IElement>();
index 171d31e671c81640ff84ddb1c4364a820f827666..e83f467d11fef0a43ffaa2848b8156528a58aeba 100644 (file)
@@ -17,19 +17,27 @@ import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
 import org.simantics.g2d.diagram.DiagramClass;
 import org.simantics.g2d.diagram.IDiagram;
 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
+import org.simantics.g2d.diagram.handler.impl.LockingTransactionContext;
+import org.simantics.g2d.diagram.handler.impl.PickContextImpl;
 import org.simantics.g2d.element.ElementClass;
 import org.simantics.g2d.element.ElementHints;
 import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.element.handler.ElementLayerListener;
+import org.simantics.g2d.elementclass.connection.ConnectionValidator;
 import org.simantics.g2d.layers.ILayer;
 
 public class DistrictDiagramClassAdapter extends DiagramClassAdapter {
 
-    static final SubstituteElementClass DINSTANCE = new DistrictDiagramSubstituteElementClass();
+    static final DiagramClass INSTANCE = DiagramClass.compile(
+            new PickContextImpl(true),
+            LockingTransactionContext.INSTANCE,
+            ConnectionValidator.INSTANCE,
+            DistrictDiagramSubstituteElementClass.INSTANCE
+    );
 
     @Override
     public void adapt(AsyncReadGraph g, Resource source, Resource r, AsyncProcedure<DiagramClass> procedure) {
-        procedure.execute(g, INSTANCE.newClassWith(DINSTANCE));
+        procedure.execute(g, INSTANCE);
     }
 
     static class DistrictDiagramElementLayerListenerImpl implements ElementLayerListener {
@@ -78,7 +86,9 @@ public class DistrictDiagramClassAdapter extends DiagramClassAdapter {
 
     static class DistrictDiagramSubstituteElementClass implements SubstituteElementClass {
 
-        static final ElementLayerListener LAYER_LISTENER = new DistrictDiagramElementLayerListenerImpl();
+        static final SubstituteElementClass INSTANCE = new DistrictDiagramSubstituteElementClass();
+
+        final ElementLayerListener LAYER_LISTENER = new DistrictDiagramElementLayerListenerImpl();
 
         @Override
         public ElementClass substitute(IDiagram d, ElementClass ec) {
index a089ecaf149e61653a6849c6bf536a523c94aab3..00fe2184e49dcc53ba77fa922e426b1e4888562c 100644 (file)
@@ -13,12 +13,11 @@ import java.awt.geom.Rectangle2D;
 import org.simantics.district.network.ModelledCRS;
 import org.simantics.district.network.ui.DistrictNetworkEdge;
 import org.simantics.district.network.ui.adapters.DistrictNetworkEdgeElementFactory;
-import org.simantics.maps.MapScalingTransform;
 import org.simantics.scenegraph.INode;
 import org.simantics.scenegraph.ISelectionPainterNode;
-import org.simantics.scenegraph.INode.PropertySetter;
 import org.simantics.scenegraph.g2d.G2DNode;
 import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.G2DRenderingHints;
 import org.simantics.scenegraph.g2d.nodes.SVGNode;
 import org.simantics.scenegraph.utils.GeometryUtils;
 import org.simantics.scenegraph.utils.NodeUtil;
@@ -34,7 +33,7 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
     private Rectangle2D bounds;
     private transient Path2D path;
 
-    private boolean scaleStroke = true;
+    private static boolean scaleStroke = true;
     private Color color;
     private Double stroke;
     private transient Color dynamicColor = null;
@@ -68,17 +67,19 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
     public void render(Graphics2D g2d) {
         AffineTransform ot = null;
         AffineTransform t = getTransform();
+        double scale = scaleStroke ? (Double) g2d.getRenderingHint(DistrictRenderingHints.KEY_VIEW_SCALE_UNDER_SPATIAL_ROOT) : 1.0;
         if (t != null && !t.isIdentity()) {
-            ot = g2d.getTransform();
-            g2d.transform(getTransform());
+            //ot = g2d.getTransform();
+            ot = (AffineTransform) g2d.getRenderingHint(G2DRenderingHints.KEY_TRANSFORM_UNDER_SPATIAL_ROOT);
+            g2d.transform(t);
+            if (scaleStroke) {
+                AffineTransform work = DistrictNetworkNodeUtils.sharedTransform.get();
+                work.setTransform(ot);
+                work.concatenate(t);
+                scale = DistrictNetworkNodeUtils.getScale(work);
+            }
         }
 
-        double scale = 1.0;
-        if (scaleStroke) {
-            AffineTransform tr = g2d.getTransform();
-            scale = DistrictNetworkNodeUtils.getScale(tr);
-        }
-        
         if (!hidden) {
             Object aaHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
@@ -92,8 +93,8 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
             } else {
                 bs = STROKE;
             }
-            
-            int zoomLevel = MapScalingTransform.zoomLevel(ot);
+
+            int zoomLevel = (Integer) g2d.getRenderingHint(DistrictRenderingHints.KEY_VIEW_ZOOM_LEVEL);
             path = calculatePath(edge, path, zoomLevel > 15);
     
             if (isSelected()) {
@@ -143,7 +144,7 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
             g2d.setColor(oldColor);
             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
         }
-            
+
         for (INode nn : getNodes()) {
             G2DNode g2dNode = (G2DNode)nn;
             if (g2dNode instanceof SVGNode) {
@@ -152,16 +153,20 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
                 if (scaleStroke) {
                     viewScaleRecip /= scale;
                 }
-                
+
+                // If the node is hidden from the start, then the center point cannot be calculated yet.
                 Point2D p = getCenterPoint();
+                if (p == null)
+                    break;
+
                 symbolRect = DistrictNetworkNodeUtils.calculateDrawnGeometry(p, NORMAL, symbolRect, viewScaleRecip);
                 symbolTransform = DistrictNetworkNodeUtils.getTransformToRectangle(symbolRect, symbolTransform);
-                
+
                 g2dNode.setTransform(symbolTransform);
             }
             g2dNode.render(g2d);
         }
-        
+
         if (ot != null)
             g2d.setTransform(ot);
     }
@@ -189,9 +194,10 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
     }
     
     private Point2D getCenterPoint() {
+        if (path == null)
+            return null;
         if (centerPoint == null)
             centerPoint = new Point2D.Double();
-        
         Rectangle2D bounds = path.getBounds2D();
         centerPoint.setLocation(bounds.getCenterX(), bounds.getCenterY());
         return centerPoint;
@@ -282,7 +288,7 @@ public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelection
     }
     
     @PropertySetter(value = "arrowLength")
-    public void setArroLength(Double length) {
+    public void setArrowLength(Double length) {
         arrowLength = length;
     }
 
index 0d300d058bb1df991ae6cbca6726d9d4332fcfeb..0efab43173f112d4521667f4af00919434c3f7d2 100644 (file)
@@ -10,6 +10,12 @@ import org.simantics.scenegraph.utils.GeometryUtils;
 
 public class DistrictNetworkNodeUtils {
 
+    public static ThreadLocal<AffineTransform> sharedTransform = new ThreadLocal<AffineTransform>() {
+        protected AffineTransform initialValue() {
+            return new AffineTransform();
+        }
+    };
+
     public static Rectangle2D calculateDrawnGeometry(Point2D p, Rectangle2D margin, Rectangle2D result, double scaleRecip) {
         if (result == null)
             result = new Rectangle2D.Double();
index 2e2b4293871114144b53fb50e73d99065ac7c10e..51ae30e07f58113363020d4f34b248b10c4ad331 100644 (file)
@@ -14,6 +14,7 @@ import org.simantics.scenegraph.INode;
 import org.simantics.scenegraph.ISelectionPainterNode;
 import org.simantics.scenegraph.g2d.G2DNode;
 import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.G2DRenderingHints;
 import org.simantics.scenegraph.g2d.IG2DNode;
 import org.simantics.scenegraph.g2d.nodes.SVGNode;
 import org.simantics.scenegraph.utils.GeometryUtils;
@@ -38,7 +39,7 @@ public class DistrictNetworkVertexNode extends G2DParentNode implements ISelecti
 
     private DistrictNetworkVertex vertex;
 
-    private boolean scaleStroke = true;
+    private static boolean scaleStroke = true;
     private boolean hover;
 
     private Color color;
@@ -62,19 +63,23 @@ public class DistrictNetworkVertexNode extends G2DParentNode implements ISelecti
     public void render(Graphics2D g2d) {
         AffineTransform ot = null;
         AffineTransform t = getTransform();
+        double viewScaleRecip = scaleStroke ? (Double) g2d.getRenderingHint(DistrictRenderingHints.KEY_VIEW_SCALE_RECIPROCAL_UNDER_SPATIAL_ROOT) : 1.0;
         if (t != null && !t.isIdentity()) {
-            ot = g2d.getTransform();
+            //ot = g2d.getTransform();
+            ot = (AffineTransform) g2d.getRenderingHint(G2DRenderingHints.KEY_TRANSFORM_UNDER_SPATIAL_ROOT);
             g2d.transform(t);
+
+            if (scaleStroke) {
+                AffineTransform work = DistrictNetworkNodeUtils.sharedTransform.get();
+                work.setTransform(ot);
+                work.concatenate(t);
+                viewScaleRecip = DistrictNetworkNodeUtils.calculateScaleRecip(work);
+            }
         }
 
         // Translate lat and lon to X and Y
         Point2D p = point = DistrictNetworkNodeUtils.calculatePoint2D(vertex.getPoint(), point);
-        
-        double viewScaleRecip = 1;
-        if (scaleStroke) {
-            viewScaleRecip *= DistrictNetworkNodeUtils.calculateScaleRecip(g2d.getTransform());
-        }
-        
+
         if (!hidden && nodeSize > 0.0) {
             Object oaaHint = null;
             Object aaHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
@@ -152,11 +157,6 @@ public class DistrictNetworkVertexNode extends G2DParentNode implements ISelecti
         updateBounds();
     }
 
-    @Override
-    public AffineTransform getTransform() {
-        return super.getTransform();
-    }
-
     private Rectangle2D calculateBounds(Rectangle2D rect) {
         Point2D calcPoint = DistrictNetworkNodeUtils.calculatePoint2D(vertex.getPoint(), point);
         AffineTransform at = NodeUtil.getLocalToGlobalTransform(this);
diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingHints.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingHints.java
new file mode 100644 (file)
index 0000000..6d44035
--- /dev/null
@@ -0,0 +1,34 @@
+package org.simantics.district.network.ui.nodes;
+
+import java.awt.RenderingHints.Key;
+
+import org.simantics.scenegraph.g2d.G2DRenderingHints;
+
+/**
+ * @author Tuukka Lehtonen
+ * See {@link G2DRenderingHints}
+ */
+public final class DistrictRenderingHints {
+
+    public static final Key KEY_VIEW_SCALE_UNDER_SPATIAL_ROOT = new Key(3001) {
+        @Override
+        public boolean isCompatibleValue(Object val) {
+            return val instanceof Double;
+        }
+    };
+
+    public static final Key KEY_VIEW_SCALE_RECIPROCAL_UNDER_SPATIAL_ROOT = new Key(3002) {
+        @Override
+        public boolean isCompatibleValue(Object val) {
+            return val instanceof Double;
+        }
+    };
+
+    public static final Key KEY_VIEW_ZOOM_LEVEL = new Key(3003) {
+        @Override
+        public boolean isCompatibleValue(Object val) {
+            return val instanceof Integer;
+        }
+    };
+
+}
\ No newline at end of file
diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingPreparationNode.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingPreparationNode.java
new file mode 100644 (file)
index 0000000..4414258
--- /dev/null
@@ -0,0 +1,30 @@
+package org.simantics.district.network.ui.nodes;
+
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+
+import org.simantics.maps.MapScalingTransform;
+import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
+
+public class DistrictRenderingPreparationNode extends UnboundedNode {
+
+    private static final long serialVersionUID = 6891204351705486193L;
+
+    @Override
+    public boolean validate() {
+        // This allows render() to be invoked always.
+        return true;
+    }
+
+    @Override
+    public void render(Graphics2D g2d) {
+        AffineTransform ot = g2d.getTransform();
+        double scale = DistrictNetworkNodeUtils.getScale(ot);
+        double scaleRecip = DistrictNetworkNodeUtils.calculateScaleRecip(ot);
+        int zoomLevel = MapScalingTransform.zoomLevel(ot);
+        g2d.setRenderingHint(DistrictRenderingHints.KEY_VIEW_SCALE_UNDER_SPATIAL_ROOT, scale);
+        g2d.setRenderingHint(DistrictRenderingHints.KEY_VIEW_SCALE_RECIPROCAL_UNDER_SPATIAL_ROOT, scaleRecip);
+        g2d.setRenderingHint(DistrictRenderingHints.KEY_VIEW_ZOOM_LEVEL, zoomLevel);
+    }
+
+}
index 7a1e105bee7599f6f8483e0c99de45600ced8b6f..f0535d1fe5e0b98a5147061f78858ef0a32c1610 100644 (file)
@@ -71,7 +71,7 @@ public class ZoomToRegionHandler {
                 }
             });
             
-            // get currentyl active workbench part
+            // get currently active workbench part
             IWorkbenchPart beforePerformingDefaultAction = WorkbenchUtils.getActiveWorkbenchPart();
             IEditorPart activeEditor = WorkbenchUtils.getActiveEditor();
             LOGGER.info("activeWorkbenchPart {}", beforePerformingDefaultAction);
index 130e3f4f3550d8e98dc5b9795743dba56a6d867a..33a428f41e71fddb25a729aed13b5467aa8f2fed 100644 (file)
@@ -37,8 +37,9 @@ public class ElementSelectionTools {
                                return (T)element;
                        }
                        else if (contentType instanceof AnyVariable) {
+                               AnyVariable type = (AnyVariable) contentType;
                                try {
-                                       return (T) Simantics.getSession().syncRequest(new ResourceRead<Variable>(element) {
+                                       return (T) type.processor.syncRequest(new ResourceRead<Variable>(element) {
                                                public Variable perform(ReadGraph graph) throws DatabaseException {
                                                        return ElementSelector.getVariableForElement(graph, resource);
                                                }