package org.simantics.district.network.ui.nodes; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Optional; import org.simantics.district.network.ModelledCRS; import org.simantics.district.network.ui.DistrictNetworkEdge; import org.simantics.district.network.ui.adapters.DistrictNetworkEdgeElementFactory; 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.nodes.SVGNode; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scenegraph.utils.NodeUtil; public class DistrictNetworkEdgeNode extends G2DParentNode implements ISelectionPainterNode { private static final long serialVersionUID = 8049769475036519806L; public static final BasicStroke STROKE = new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); private static final Color SELECTION_COLOR = new Color(255, 0, 255, 96); private DistrictNetworkEdge edge; private Rectangle2D bounds; private transient Path2D path; private transient Path2D detailedPath; private transient int zoomLevel = 0; static final boolean scaleStroke = true; private Color color; private Double stroke; private transient Color dynamicColor = null; private transient Color eventColor = null; // Dimensions for shut-off valve symbol private static final double left = -0.25; private static final double top = -0.25; private static final double width = 0.5; private static final double height = 0.5; private static final Rectangle2D NORMAL = new Rectangle2D.Double(left, top, width, height); private transient Point2D centerPoint; private transient Point2D detailedCenterPoint; private transient Point2D direction; private transient Point2D detailedDirection; private transient Rectangle2D symbolRect; private transient AffineTransform symbolTransform; private boolean hidden = false; private static double startX; private static double startY; private static double endX; private static double endY; @Override public void init() { } @Override 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(); ot = (AffineTransform) g2d.getRenderingHint(G2DRenderingHints.KEY_TRANSFORM_UNDER_SPATIAL_ROOT); if (ot == null) ot = g2d.getTransform(); g2d.transform(t); if (scaleStroke) { AffineTransform work = DistrictNetworkNodeUtils.sharedTransform.get(); work.setTransform(ot); work.concatenate(t); scale = DistrictNetworkNodeUtils.getScale(work); } } zoomLevel = (Integer) g2d.getRenderingHint(DistrictRenderingHints.KEY_VIEW_ZOOM_LEVEL); if (!hidden) { Object aaHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color oldColor = g2d.getColor(); BasicStroke oldStroke = (BasicStroke) g2d.getStroke(); BasicStroke bs = null; if (scaleStroke) { bs = GeometryUtils.scaleStroke(STROKE, getStrokeWidth(scale)); } else { bs = STROKE; } Path2D path = renderDetailed(zoomLevel) ? this.detailedPath : this.path; if (isSelected()) { g2d.setColor(SELECTION_COLOR); g2d.setStroke(GeometryUtils.scaleAndOffsetStrokeWidth(bs, 1.f, (float)(2 * STROKE.getLineWidth() / scale))); g2d.draw(path); } g2d.setColor(dynamicColor != null ? dynamicColor : color); g2d.setColor(eventColor != null ? eventColor : g2d.getColor()); g2d.setStroke(bs); g2d.draw(path); // Reset g2d.setStroke(oldStroke); g2d.setColor(oldColor); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint); } Point2D centerP = getCenterPoint(zoomLevel); for (INode nn : getNodes()) { G2DNode g2dNode = (G2DNode) nn; if (g2dNode instanceof SVGNode) { // If the node is hidden from the start, then the center point cannot be calculated yet. if (!isDefined(centerP)) break; // Render SVG symbol double viewScaleRecip = scaleStroke ? 10 / scale : 10; symbolRect = DistrictNetworkNodeUtils.calculateDrawnGeometry(centerP, NORMAL, symbolRect, viewScaleRecip); symbolTransform = DistrictNetworkNodeUtils.getTransformToRectangle(symbolRect, symbolTransform); g2dNode.setTransform(symbolTransform); } g2dNode.render(g2d); } if (ot != null) g2d.setTransform(ot); } static boolean isDefined(Point2D p) { return p != null && !(Double.isNaN(p.getX()) || Double.isNaN(p.getY())); } boolean renderDetailed(int zoomLevel) { return zoomLevel > 13; } public float getStrokeWidth(AffineTransform tr, boolean selection) { double scale = DistrictNetworkNodeUtils.getScale(tr); float width = STROKE.getLineWidth() * getStrokeWidth(scale); if (selection) width = width + (float) (2 * STROKE.getLineWidth() / scale); return width; } private float getStrokeWidth(double scale) { if (scaleStroke) { double str = stroke != null ? Math.abs(stroke) : 1.0; float strokeWidth = (float) (str / scale); return strokeWidth; } else { return 1.f; } } public Path2D getPath() { return renderDetailed(zoomLevel) ? detailedPath : path; } public Point2D getCenterPoint(int zoomLevel) { return renderDetailed(zoomLevel) ? detailedCenterPoint : centerPoint; } public Point2D getDirection(int zoomLevel) { return renderDetailed(zoomLevel) ? detailedDirection : direction; } public static Path2D calculatePath(DistrictNetworkEdge edge, Path2D result, boolean detailed) { startX = ModelledCRS.longitudeToX(edge.getStartPoint().getX()); startY = ModelledCRS.latitudeToY(-edge.getStartPoint().getY()); endX = ModelledCRS.longitudeToX(edge.getEndPoint().getX()); endY = ModelledCRS.latitudeToY(-edge.getEndPoint().getY()); if (result == null) { result = new Path2D.Double(); } else { result.reset(); } result.moveTo(startX, startY); if (detailed) { double[] detailedGeometry = edge.getGeometry(); if (detailedGeometry != null && !DistrictNetworkEdgeElementFactory.EMPTY.equals(detailedGeometry)) { // ok, lets do this for (int i = 0; i < detailedGeometry.length; i += 2) { double x = ModelledCRS.longitudeToX(detailedGeometry[i]); double y = ModelledCRS.latitudeToY(-detailedGeometry[i+1]);// Invert for Simantics result.lineTo(x, y); } } } result.lineTo(endX, endY); return result; } private boolean isSelected() { return NodeUtil.isSelected(this, 1); } @Override public Rectangle2D getBoundsInLocal() { return bounds; } private void updateBounds() { Rectangle2D oldBounds = bounds; if (oldBounds == null) oldBounds = new Rectangle2D.Double(); bounds = calculateBounds(oldBounds); } /** * Calculates conservative bounds of the node provided by * detailedPath. These might be larger than the simplified bounds * of path but the conservative bounds ensure that spatial * optimizations work properly. */ private Rectangle2D calculateBounds(Rectangle2D rect) { if (detailedPath == null) detailedPath = calculatePath(edge, detailedPath, true); return detailedPath.getBounds2D(); } public void setDNEdge(DistrictNetworkEdge edge) { this.edge = edge; path = calculatePath(edge, path, false); detailedPath = calculatePath(edge, detailedPath, true); centerPoint = new Point2D.Double(); detailedCenterPoint = new Point2D.Double(); direction = new Point2D.Double(); detailedDirection = new Point2D.Double(); DistrictNetworkNodeUtils.calculateCenterPointAndDirection(path, centerPoint, direction); DistrictNetworkNodeUtils.calculateCenterPointAndDirection(detailedPath, detailedCenterPoint, detailedDirection); updateBounds(); } public void setColor(Color color) { this.color = color; } public Color getColor() { return color; } @PropertySetter(value = "stroke") public void setStroke(Double stroke) { this.stroke = stroke; } @PropertySetter(value = "dynamicColor") public void setDynamicColor(Color color) { this.dynamicColor = color; } @PropertySetter(value = "eventColor") public void setEventColor(Color color) { this.eventColor = color; } @PropertySetter(value = "arrowLength") public void setArrowLength(Double length) { // find if there is a child deferred arrow node DistrictNetworkEdgeArrayNode child = getOrCreateNode(DistrictNetworkEdgeArrayNode.NODE_KEY, DistrictNetworkEdgeArrayNode.class); child.setEdgeNode(this); child.setArrowLength(length); //arrowLength = length; } @PropertySetter(value = "SVG") public void setSVG(String value) { for (INode nn : this.getNodes()) if (nn instanceof SVGNode) ((SVGNode)nn).setData(value); } @PropertySetter(value = "hidden") public void setHidden(Boolean value) { this.hidden = value; } public void setStaticInformation(Optional staticInformation) { DistrictNetworkStaticInfoNode child = getOrCreateNode(DistrictNetworkStaticInfoNode.NODE_KEY, DistrictNetworkStaticInfoNode.class); child.setEdgeNode(this); if (staticInformation.isPresent()) { child.setInfo(staticInformation.get()); } else { child.setInfo(null); } } public void setInSimulation(Optional isInSimulation) { if (!isInSimulation.isPresent()) { removeNode(NotInSimulationNode.NODE_NAME); } else { NotInSimulationNode child = getOrCreateNode(NotInSimulationNode.NODE_NAME, NotInSimulationNode.class); child.setZIndex(1000); child.setIsInSimulation(isInSimulation.get()); } } }