From: Tuukka Lehtonen Date: Wed, 16 Oct 2019 22:46:26 +0000 (+0300) Subject: Optimization of district scene graph node rendering X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=b018055e3c5809d33161154aebdc47f733721cdb;p=simantics%2Fdistrict.git Optimization of district scene graph node rendering * 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 --- 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 ef88e1c5..5e989546 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 @@ -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 index 00000000..67c803db --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/Formatting.java @@ -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; + } + +} 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 index b2d28c0b..97706d23 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapAttributionNode.java @@ -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 index 00000000..1014243a --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoConstants.java @@ -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 index 00000000..16a7c501 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapInfoNode.java @@ -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 index 00000000..5a8d4453 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationInfoNode.java @@ -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 index 790ebbe0..00000000 --- a/org.simantics.district.maps/src/org/simantics/maps/sg/MapLocationZoomInfoNode.java +++ /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; - } -} 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 e506e11d..9b4c9ba5 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 @@ -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; + } + } diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java index c27b090d..d0b1a8a1 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java @@ -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() { diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/NetworkDrawingParticipant.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/NetworkDrawingParticipant.java index bd81bd01..da2bf32a 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/NetworkDrawingParticipant.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/NetworkDrawingParticipant.java @@ -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 pickables = new ArrayList(); + List pickables = new ArrayList<>(); pick.pick(diagram, req, pickables); - - List snap = new ArrayList<>(diagram.getSnapshot()); - - // snap.removeAll(pickables); - + + List 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 elements, boolean hover, boolean isConnectionTool, boolean changed, Point2D p) { + private boolean hoverNodes(List 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 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 pickables = new ArrayList(); diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/adapters/DistrictDiagramClassAdapter.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/adapters/DistrictDiagramClassAdapter.java index 171d31e6..e83f467d 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/adapters/DistrictDiagramClassAdapter.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/adapters/DistrictDiagramClassAdapter.java @@ -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 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) { diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkEdgeNode.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkEdgeNode.java index a089ecaf..00fe2184 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkEdgeNode.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkEdgeNode.java @@ -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; } diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkNodeUtils.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkNodeUtils.java index 0d300d05..0efab431 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkNodeUtils.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkNodeUtils.java @@ -10,6 +10,12 @@ import org.simantics.scenegraph.utils.GeometryUtils; public class DistrictNetworkNodeUtils { + public static ThreadLocal sharedTransform = new ThreadLocal() { + 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(); diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkVertexNode.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkVertexNode.java index 2e2b4293..51ae30e0 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkVertexNode.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictNetworkVertexNode.java @@ -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 index 00000000..6d440356 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingHints.java @@ -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 index 00000000..44142580 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/nodes/DistrictRenderingPreparationNode.java @@ -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); + } + +} diff --git a/org.simantics.district.region.ui/src/org/simantics/district/region/ui/handlers/ZoomToRegionHandler.java b/org.simantics.district.region.ui/src/org/simantics/district/region/ui/handlers/ZoomToRegionHandler.java index 7a1e105b..f0535d1f 100644 --- a/org.simantics.district.region.ui/src/org/simantics/district/region/ui/handlers/ZoomToRegionHandler.java +++ b/org.simantics.district.region.ui/src/org/simantics/district/region/ui/handlers/ZoomToRegionHandler.java @@ -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); diff --git a/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/ElementSelectionTools.java b/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/ElementSelectionTools.java index 130e3f4f..33a428f4 100644 --- a/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/ElementSelectionTools.java +++ b/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/ElementSelectionTools.java @@ -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(element) { + return (T) type.processor.syncRequest(new ResourceRead(element) { public Variable perform(ReadGraph graph) throws DatabaseException { return ElementSelector.getVariableForElement(graph, resource); }