1 package org.simantics.district.network.ui.nodes;
3 import java.awt.BasicStroke;
5 import java.awt.Graphics2D;
6 import java.awt.Stroke;
7 import java.awt.geom.AffineTransform;
8 import java.awt.geom.Path2D;
9 import java.awt.geom.Point2D;
10 import java.awt.geom.Rectangle2D;
11 import java.util.ArrayList;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.concurrent.TimeUnit;
16 import org.simantics.Simantics;
17 import org.simantics.db.Resource;
18 import org.simantics.db.WriteGraph;
19 import org.simantics.db.common.request.WriteRequest;
20 import org.simantics.db.exception.DatabaseException;
21 import org.simantics.db.request.Write;
22 import org.simantics.diagram.elements.DiagramNodeUtil;
23 import org.simantics.diagram.ui.DiagramModelHints;
24 import org.simantics.district.network.DNEdgeBuilder;
25 import org.simantics.district.network.DistrictNetworkUtil;
26 import org.simantics.district.network.ModelledCRS;
27 import org.simantics.district.network.ontology.DistrictNetworkResource;
28 import org.simantics.district.network.ui.NetworkDrawingParticipant;
29 import org.simantics.g2d.canvas.Hints;
30 import org.simantics.g2d.canvas.ICanvasContext;
31 import org.simantics.g2d.canvas.IToolMode;
32 import org.simantics.g2d.diagram.IDiagram;
33 import org.simantics.maps.elevation.server.SingletonTiffTileInterface;
34 import org.simantics.maps.elevation.server.prefs.MapsElevationServerPreferences;
35 import org.simantics.scenegraph.g2d.G2DNode;
36 import org.simantics.scenegraph.g2d.events.EventTypes;
37 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
38 import org.simantics.scenegraph.g2d.events.MouseEvent;
39 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
40 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
41 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
42 import org.simantics.scenegraph.utils.GeometryUtils;
43 import org.simantics.scenegraph.utils.NodeUtil;
44 import org.simantics.utils.threads.ThreadUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 public class NetworkDrawingNode extends G2DNode {
50 private static final Logger LOGGER = LoggerFactory.getLogger(NetworkDrawingNode.class);
52 static class DrawingNode {
54 private List<Point2D> routeNodes = new ArrayList<>();
57 private static final long serialVersionUID = -3475301184009620573L;
59 private Point2D currentMousePos = null;
61 private List<DrawingNode> nodes = new ArrayList<>();
62 private DrawingNode currentRouteNode = null;
64 private Resource diagramResource;
66 private NetworkDrawingParticipant participant;
68 private IDiagram diagram;
70 private static final Stroke DASHED_STROKE = new BasicStroke(2.0f,
71 BasicStroke.CAP_ROUND,
72 BasicStroke.JOIN_ROUND,
73 4.0f, new float[]{4.0f}, 0.0f);
75 private static final Color BLUE_ALPHA = new Color(0, 0, 255, 100);
76 private static final Color RED_ALPHA = new Color(255, 0, 0, 100);
78 private boolean scaleStroke = true;
83 addEventHandler(this);
86 public void setNetworkDrawingParticipant(NetworkDrawingParticipant participant) {
87 this.participant = participant;
90 public void setDiagram(IDiagram diagram) {
91 if (diagram != null) {
92 this.diagram = diagram;
93 this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
98 public void render(Graphics2D g2d) {
102 Color old = g2d.getColor();
103 Stroke oldStroke = g2d.getStroke();
105 Iterator<DrawingNode> dnodeIterator = nodes.iterator();
106 while (dnodeIterator.hasNext()) {
107 Path2D path = new Path2D.Double();
108 DrawingNode dnode = dnodeIterator.next();
109 Iterator<Point2D> nodeIter = dnode.routeNodes.iterator();
110 if (nodeIter.hasNext()) {
111 Point2D node = nodeIter.next();
112 path.moveTo(node.getX(), node.getY());
114 while (nodeIter.hasNext()) {
115 Point2D node = nodeIter.next();
116 path.lineTo(node.getX(), node.getY());
118 if (!dnodeIterator.hasNext()) {
119 if (currentMousePos != null)
120 path.lineTo(currentMousePos.getX(), currentMousePos.getY());
123 if (DASHED_STROKE != null) {
124 if (scaleStroke && DASHED_STROKE instanceof BasicStroke) {
125 BasicStroke bs = GeometryUtils.scaleStroke(DASHED_STROKE, (float) (1.0 / GeometryUtils.getScale(g2d.getTransform())));
128 g2d.setStroke(DASHED_STROKE);
132 g2d.setColor(BLUE_ALPHA);
135 g2d.setColor(RED_ALPHA);
136 BasicStroke stroke = GeometryUtils.scaleStroke(DASHED_STROKE, (float) (1.0 / GeometryUtils.getScale(g2d.getTransform())));
137 g2d.setStroke(stroke);
138 Point2D currentPoint = path.getCurrentPoint();
139 g2d.draw(new Rectangle2D.Double(currentPoint.getX() - 0.0001 / 2, currentPoint.getY() - 0.0001 / 2, 0.0001, 0.0001));
142 g2d.setStroke(oldStroke);
147 public Rectangle2D getBoundsInLocal() {
152 public int getEventMask() {
153 return EventTypes.AnyMask;
157 protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
159 IToolMode mode = getToolMode();
160 if (mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {
161 // ok, new routenode starts from here
162 Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());
163 Point2D.Double pos = new Point2D.Double(localPos.getX(), localPos.getY());
164 if (currentRouteNode != null) {
165 //currentRouteNode.routeNodes.add(pos);
166 currentRouteNode = new DrawingNode();
167 currentRouteNode.routeNodes.add(pos);
168 nodes.add(currentRouteNode);
170 // ok, this must be creation of dh_point
171 double scale = getTransform().getScaleY();
172 double x = ModelledCRS.xToLongitude(pos.getX() / scale);
173 double y = ModelledCRS.yToLatitude(-pos.getY() / scale);
175 double elevation = 0;
176 if (MapsElevationServerPreferences.useElevationServer()) {
177 // ok! we use new elevation API to resolve possible elevations for the starting points
179 elevation = SingletonTiffTileInterface.lookup(x, y).doubleValue();
180 } catch (Exception ee) {
181 LOGGER.error("Could not get elevation from tiff interface", ee);
184 final double felevation = elevation;
186 boolean leftButton = e.button == MouseEvent.LEFT_BUTTON;
188 ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
189 Simantics.getSession().asyncRequest(new Write() {
192 public void perform(WriteGraph graph) throws DatabaseException {
193 graph.markUndoPoint();
194 Resource mapping = null;
196 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).LeftClickDefaultMapping);
198 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).RightClickDefaultMapping);
200 if (mapping == null) {
201 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).VertexDefaultMapping);
203 DistrictNetworkUtil.createVertex(graph, diagramResource, new double[] { x, y }, felevation, mapping);
206 }, 100, TimeUnit.MILLISECONDS);
211 return super.mouseDoubleClicked(e);
214 private void createEdge(DrawingNode node) {
216 Point2D start = node.routeNodes.get(0);
217 Point2D end = node.routeNodes.get(node.routeNodes.size() - 1);
219 double currentPadding = DistrictNetworkVertexNode.width;
220 AffineTransform test = getTransform();
221 ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this);
222 AffineTransform tr = ctx.getHintStack().getHint(Hints.KEY_CANVAS_TRANSFORM);
223 AffineTransform testing = new AffineTransform(tr);
224 testing.concatenate(test);
225 double calculateScaleRecip = DistrictNetworkNodeUtils.calculateScaleRecip(testing);
226 double padding = currentPadding * calculateScaleRecip;
228 * To convert y-coordinates to map coordinates in ruler, use:
229 * double val = (y-offsetY)/scaleY;
230 * val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
231 * String str = formatValue(val);
234 double scaleY = getTransform().getScaleY();
235 double scaleX = getTransform().getScaleX();
237 double startLat = ModelledCRS.yToLatitude(-start.getY() / scaleY);
238 double startLon = ModelledCRS.xToLongitude(start.getX() / scaleX);
240 double endLat = ModelledCRS.yToLatitude(-end.getY() / scaleY);
241 double endLon = ModelledCRS.xToLongitude(end.getX() / scaleX);
243 double[] startCoords = new double[] { startLon, startLat };
244 double[] endCoords = new double[] { endLon, endLat };
246 double[] detailedGeometryCoords = new double[node.routeNodes.size() * 2];
248 for (Point2D p : node.routeNodes) {
249 double lat = ModelledCRS.yToLatitude(-p.getY() / scaleY);
250 double lon = ModelledCRS.xToLongitude(p.getX() / scaleX);
251 detailedGeometryCoords[i++] = lon;
252 detailedGeometryCoords[i++] = lat;
255 double startElevation = 0;
256 double endElevation = 0;
257 if (MapsElevationServerPreferences.useElevationServer()) {
258 // ok! we use new elevation API to resolve possible elevations for the starting points
260 startElevation = SingletonTiffTileInterface.lookup(startLat, startLon).doubleValue();
261 endElevation = SingletonTiffTileInterface.lookup(endLat, endLon).doubleValue();
262 } catch (Exception e) {
263 LOGGER.error("Could not get elevation from tiff interface", e);
266 final double se = startElevation;
267 final double ee = endElevation;
268 DNEdgeBuilder builder = new DNEdgeBuilder(diagramResource, diagram);
269 Simantics.getSession().asyncRequest(new WriteRequest() {
272 public void perform(WriteGraph graph) throws DatabaseException {
273 builder.create(graph, startCoords, se, endCoords, ee, detailedGeometryCoords, padding);
280 protected boolean mouseClicked(MouseClickEvent e) {
282 IToolMode mode = getToolMode();
283 if (mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {
284 if (e.button == MouseEvent.RIGHT_BUTTON && !nodes.isEmpty()) {
285 nodes.remove(nodes.size() - 1);
286 } else if (e.button == MouseEvent.LEFT_BUTTON) {
287 Point2D localPos = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());
288 if (currentRouteNode == null && canStartEdge(localPos)) {
289 // ok, we can start from here
290 currentRouteNode = new DrawingNode();
291 currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY()));
292 nodes.add(currentRouteNode);
293 } else if (currentRouteNode != null && canStartEdge(localPos)) {
294 // let's commit our new routenode
295 currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY()));
296 Iterator<DrawingNode> nodeIter = nodes.iterator();
297 while (nodeIter.hasNext()) {
298 createEdge(nodeIter.next());
300 currentRouteNode = null;
302 } else if (currentRouteNode != null) {
303 currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY()));
309 return super.mouseClicked(e);
312 private boolean canStartEdge(Point2D currentPos) {
313 return participant.isHoveringOverNode(currentPos);
316 private IToolMode getToolMode() {
317 return participant.getHint(Hints.KEY_TOOL);
321 protected boolean mouseMoved(MouseMovedEvent e) {
322 IToolMode mode = getToolMode();
323 boolean repaint = false;
324 Point2D p = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());
325 boolean isConnectionTool = mode == Hints.CONNECTTOOL || e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK);
326 if (participant.pickHoveredElement(p, isConnectionTool)) {
329 if (!nodes.isEmpty()) {
335 currentMousePos = null;
338 return super.mouseMoved(e);
342 protected boolean keyPressed(KeyPressedEvent e) {
343 if (e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {
344 currentRouteNode = null;
349 return super.keyPressed(e);