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
*/
public class MapPainter extends AbstractCanvasParticipant {
+ private static final Logger LOGGER = LoggerFactory.getLogger(MapPainter.class);
+
/**
* Grid enabled status. Default value is True
*/
private AffineTransform transform;
+ private MapLocationZoomInfoNode locationZoomInfoNode;
+
+ private ScheduledFuture<?> schedule;
+
public MapPainter(AffineTransform transform) {
this.transform = transform;
}
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);
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
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
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);
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);
// 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);