From: jsimomaa Date: Thu, 28 Feb 2019 09:15:13 +0000 (+0200) Subject: Add location & zoom node for network diagram - also OSM attribution X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=17c52752fcc15ee30a7790b96e3976214077761b;p=simantics%2Fdistrict.git Add location & zoom node for network diagram - also OSM attribution gitlab #33 Change-Id: I170d79d1af403d0dde6fb5b9ec8feb3157a1c113 --- diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java index c519b9a5..ef88e1c5 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java @@ -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 index 00000000..66543aac --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java @@ -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 index 00000000..bad499e6 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java @@ -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; + } +} diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java index 793550b6..dc6b393d 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java @@ -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);