]> gerrit.simantics Code Review - simantics/district.git/commitdiff
Add location & zoom node for network diagram - also OSM attribution 36/2736/2
authorjsimomaa <jani.simomaa@gmail.com>
Thu, 28 Feb 2019 09:15:13 +0000 (11:15 +0200)
committerJani Simomaa <jani.simomaa@semantum.fi>
Thu, 28 Feb 2019 10:05:55 +0000 (10:05 +0000)
gitlab #33

Change-Id: I170d79d1af403d0dde6fb5b9ec8feb3157a1c113

org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java
org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java

index c519b9a5f1a3d53cadb779ded4ce78e07929b249..ef88e1c55ea9fdf266506ffbcd82c41b6a75a954 100644 (file)
@@ -13,24 +13,35 @@ package org.simantics.maps.eclipse;
 
 import java.awt.Color;
 import java.awt.geom.AffineTransform;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import org.simantics.g2d.canvas.Hints;
 import org.simantics.g2d.canvas.ICanvasContext;
 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
 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.MapNode;
 import org.simantics.maps.sg.MapScaleNode;
 import org.simantics.maps.sg.commands.MapCommands;
 import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.Event;
 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
 import org.simantics.scenegraph.g2d.events.command.Commands;
 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
-import org.simantics.utils.datastructures.hints.IHintListener;
-import org.simantics.utils.datastructures.hints.IHintObservable;
 import org.simantics.utils.datastructures.hints.IHintContext.Key;
 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintListener;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * MapPainter is an ICanvasContext participant that uses the scene graph
@@ -42,6 +53,8 @@ import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
  */
 public class MapPainter extends AbstractCanvasParticipant {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(MapPainter.class);
+
     /**
      * Grid enabled status. Default value is True
      */
@@ -68,6 +81,10 @@ public class MapPainter extends AbstractCanvasParticipant {
 
     private AffineTransform transform;
 
+    private MapLocationZoomInfoNode locationZoomInfoNode;
+
+    private ScheduledFuture<?> schedule;
+
     public MapPainter(AffineTransform transform) {
         this.transform = transform;
     }
@@ -118,6 +135,24 @@ public class MapPainter extends AbstractCanvasParticipant {
         return false;
     }
 
+    @EventHandler(priority = 31)
+    public boolean handleEvent(Event e) {
+        if (e instanceof MouseMovedEvent) {
+            // here we should somehow re-render ?
+            if (locationZoomInfoNode.isEnabled()) {
+                if (schedule == null || schedule.isDone()) {
+                    LOGGER.debug("current setDirty time" + System.currentTimeMillis());
+                    schedule = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
+                        AWTThread.getThreadAccess().asyncExec(this::setDirty);
+                    }, 100, TimeUnit.MILLISECONDS);
+                } else {
+                    //LOGGER.debug("ingoring setDirty time" + System.currentTimeMillis());
+                }
+            }
+        }
+        return false;
+    }
+
     @SGInit
     public void initSG(G2DParentNode parent) {
         node = parent.addNode("map", MapNode.class);
@@ -129,6 +164,18 @@ public class MapPainter extends AbstractCanvasParticipant {
         scaleNode.setTransform(transform);
         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);
     }
 
     @SGCleanup
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java
new file mode 100644 (file)
index 0000000..66543aa
--- /dev/null
@@ -0,0 +1,83 @@
+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.Rectangle2D;
+
+import org.simantics.scenegraph.g2d.G2DNode;
+
+public class MapAttributionNode extends G2DNode {
+
+    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);
+        
+        AffineTransform tr = g2d.getTransform();
+        
+        g2d.setTransform(new AffineTransform());
+        // do the rendering magic
+        
+        Font rulerFont = new Font("Tahoma", Font.PLAIN, 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
+
+        String str = "Map data © OpenStreetMap contributors";
+        FontMetrics fm = g2d.getFontMetrics();
+        Rectangle2D r = fm.getStringBounds(str, g2d);
+
+        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);
+    }
+
+    @Override
+    public Rectangle2D getBoundsInLocal() {
+        return null;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+}
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
new file mode 100644 (file)
index 0000000..bad499e
--- /dev/null
@@ -0,0 +1,182 @@
+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.MapScalingTransform;
+import org.simantics.scenegraph.g2d.G2DNode;
+
+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);
+        
+        AffineTransform tr = g2d.getTransform();
+        
+        g2d.setTransform(new AffineTransform());
+        // do the rendering magic
+        
+        Font rulerFont = new Font("Tahoma", Font.PLAIN, 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
+
+        int zoomLevel = MapScalingTransform.zoomLevel(ot);
+        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;
+        }
+        
+        String str = "X: " + formatValue(startLon, MAX_DIGITS) + ", Y: " + formatValue(startLat, MAX_DIGITS) + ", Z: " + zoomLevel;
+        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 793550b6087b05b92694b36e0291ab85ae6ce30c..dc6b393d5a4ea2e8daf334d1095e3f7b46f0cb98 100644 (file)
@@ -58,9 +58,9 @@ public class MapScaleNode extends G2DNode {
 
         double previousText = -100;
         
-        double minY = bounds.getMaxY() - 30;
+        double minY = bounds.getMaxY() - 40;
         
-        double scaleRight = bounds.getMaxX() - 30;
+        double scaleRight = bounds.getMaxX() - 20;
         
         double meterPerPixel = getMeterPerPixel(scaleRight - offsetX, minY - offsetY, scaleX, scaleY);
         
@@ -77,7 +77,7 @@ public class MapScaleNode extends G2DNode {
         
         double newScaleLeft = scaleRight - pixels;
         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
-        Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft, bounds.getMaxY() - 30, pixels, 20);
+        Rectangle2D vertical = new Rectangle2D.Double(newScaleLeft, bounds.getMaxY() - 40, pixels, 20);
         g.fill(vertical);
         
         g.setColor(GRAY);
@@ -101,9 +101,9 @@ public class MapScaleNode extends G2DNode {
         
         // Horizontal ruler
         double label = 0;
+        FontMetrics fm = g.getFontMetrics();
         for(double x = newScaleLeft; x < scaleRight; x += stepX) {
             String str = formatValue(label * meterPerPixel);
-            FontMetrics fm = g.getFontMetrics();
             Rectangle2D r = fm.getStringBounds(str, g);
             if((x - r.getWidth() / 2) > previousText) {
                 g.setColor(Color.BLACK);