]> gerrit.simantics Code Review - simantics/district.git/blob
4d527c1a1c5fe038af161fafe706f668a133f267
[simantics/district.git] /
1 package org.simantics.district.network.ui.nodes;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
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;
15
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;
47
48 public class NetworkDrawingNode extends G2DNode {
49
50     private static final Logger LOGGER = LoggerFactory.getLogger(NetworkDrawingNode.class);
51
52     static class DrawingNode {
53
54         private List<Point2D> routeNodes = new ArrayList<>();
55     }
56
57     private static final long serialVersionUID = -3475301184009620573L;
58     
59     private Point2D currentMousePos = null;
60
61     private List<DrawingNode> nodes = new ArrayList<>();
62     private DrawingNode currentRouteNode = null;
63
64     private Resource diagramResource;
65
66     private NetworkDrawingParticipant participant;
67
68     private IDiagram diagram;
69
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);
74
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);
77
78     private boolean scaleStroke = true;
79     
80     @Override
81     public void init() {
82         super.init();
83         addEventHandler(this);
84     }
85     
86     public void setNetworkDrawingParticipant(NetworkDrawingParticipant participant) {
87         this.participant = participant;
88     }
89     
90     public void setDiagram(IDiagram diagram) {
91         if (diagram != null) {
92             this.diagram = diagram;
93             this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
94         }
95     }
96
97     @Override
98     public void render(Graphics2D g2d) {
99         if (nodes.isEmpty())
100             return;
101
102         Color old = g2d.getColor();
103         Stroke oldStroke = g2d.getStroke();
104
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());
113             }
114             while (nodeIter.hasNext()) {
115                 Point2D node = nodeIter.next();
116                 path.lineTo(node.getX(), node.getY());
117             }
118             if (!dnodeIterator.hasNext()) {
119                 if (currentMousePos != null)
120                     path.lineTo(currentMousePos.getX(), currentMousePos.getY());
121             }
122             
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())));
126                     g2d.setStroke(bs);
127                 } else {
128                     g2d.setStroke(DASHED_STROKE);
129                 }
130             }
131             
132             g2d.setColor(BLUE_ALPHA);
133             g2d.draw(path);
134             
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));
140         }
141         
142         g2d.setStroke(oldStroke);
143         g2d.setColor(old);
144     }
145
146     @Override
147     public Rectangle2D getBoundsInLocal() {
148         return null;
149     }
150     
151     @Override
152     public int getEventMask() {
153         return EventTypes.AnyMask;
154     }
155     
156     @Override
157     protected boolean mouseDoubleClicked(MouseDoubleClickedEvent e) {
158         // nodes to path2d
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);
169             } else {
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);
174                 
175                 double elevation = 0;
176                 if (MapsElevationServerPreferences.useElevationServer()) {
177                     // ok! we use new elevation API to resolve possible elevations for the starting points
178                     try {
179                         elevation = SingletonTiffTileInterface.lookup(x, y).doubleValue();
180                     } catch (Exception ee) {
181                         LOGGER.error("Could not get elevation from tiff interface", ee);
182                     }
183                 }
184                 final double felevation = elevation;
185                 
186                 boolean leftButton = e.button == MouseEvent.LEFT_BUTTON;
187                 
188                 ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
189                     Simantics.getSession().asyncRequest(new Write() {
190                         
191                         @Override
192                         public void perform(WriteGraph graph) throws DatabaseException {
193                             graph.markUndoPoint();
194                             Resource mapping = null;
195                             if (leftButton) {
196                                 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).LeftClickDefaultMapping);
197                             } else {
198                                 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).RightClickDefaultMapping);
199                             }
200                             if (mapping == null) {
201                                 mapping = graph.getSingleObject(diagramResource, DistrictNetworkResource.getInstance(graph).VertexDefaultMapping);
202                             }
203                             DistrictNetworkUtil.createVertex(graph, diagramResource, new double[] { x, y }, felevation, mapping);
204                         }
205                     });
206                 }, 100, TimeUnit.MILLISECONDS);
207             }
208             repaint();
209             return true;
210         }
211         return super.mouseDoubleClicked(e);
212     }
213
214     private void createEdge(DrawingNode node) {
215         
216         Point2D start = node.routeNodes.get(0);
217         Point2D end = node.routeNodes.get(node.routeNodes.size() - 1);
218         
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;
227         /*
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);
232          */
233         // TODO: fix scale
234         double scaleY = getTransform().getScaleY();
235         double scaleX = getTransform().getScaleX();
236         
237         double startLat = ModelledCRS.yToLatitude(-start.getY() / scaleY);
238         double startLon = ModelledCRS.xToLongitude(start.getX() / scaleX);
239         
240         double endLat = ModelledCRS.yToLatitude(-end.getY() / scaleY);
241         double endLon = ModelledCRS.xToLongitude(end.getX() / scaleX);
242         
243         double[] startCoords = new double[] { startLon, startLat };
244         double[] endCoords = new double[] { endLon, endLat };
245         
246         double[] detailedGeometryCoords = new double[node.routeNodes.size() * 2];
247         int i = 0;
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;
253         }
254         
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
259             try {
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);
264             }
265         }
266         final double se = startElevation;
267         final double ee = endElevation;
268         DNEdgeBuilder builder = new DNEdgeBuilder(diagramResource, diagram);
269         Simantics.getSession().asyncRequest(new WriteRequest() {
270
271             @Override
272             public void perform(WriteGraph graph) throws DatabaseException {
273                 builder.create(graph, startCoords, se, endCoords, ee, detailedGeometryCoords, padding);
274             }
275         });
276
277     }
278
279     @Override
280     protected boolean mouseClicked(MouseClickEvent e) {
281         // check ToolMode
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());
299                     }
300                     currentRouteNode = null;
301                     nodes.clear();
302                 } else if (currentRouteNode != null) {
303                     currentRouteNode.routeNodes.add(new Point2D.Double(localPos.getX(), localPos.getY()));
304                 }
305             }
306             repaint();
307             return true;
308         }
309         return super.mouseClicked(e);
310     }
311
312     private boolean canStartEdge(Point2D currentPos) {
313         return participant.isHoveringOverNode(currentPos);
314     }
315
316     private IToolMode getToolMode() {
317         return participant.getHint(Hints.KEY_TOOL);
318     }
319
320     @Override
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)) {
327             repaint = true;
328         }
329         if (!nodes.isEmpty()) {
330             currentMousePos = p;
331             
332             repaint();
333             return true;
334         }
335         currentMousePos = null;
336         if (repaint == true)
337             repaint();
338         return super.mouseMoved(e);
339     }
340     
341     @Override
342     protected boolean keyPressed(KeyPressedEvent e) {
343         if (e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {
344             currentRouteNode = null;
345             nodes.clear();
346             repaint();
347             return true;
348         }
349         return super.keyPressed(e);
350             
351     }
352 }