1 package org.opentrafficsim.editor.extensions.map;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.IOException;
10 import java.rmi.RemoteException;
11 import java.util.Iterator;
12 import java.util.LinkedHashMap;
13 import java.util.LinkedHashSet;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.WeakHashMap;
17 import java.util.function.Function;
18
19 import javax.naming.NamingException;
20 import javax.swing.Box;
21 import javax.swing.Box.Filler;
22 import javax.swing.BoxLayout;
23 import javax.swing.ButtonGroup;
24 import javax.swing.ButtonModel;
25 import javax.swing.DefaultComboBoxModel;
26 import javax.swing.Icon;
27 import javax.swing.JButton;
28 import javax.swing.JCheckBox;
29 import javax.swing.JComboBox;
30 import javax.swing.JLabel;
31 import javax.swing.JPanel;
32 import javax.swing.JToggleButton;
33 import javax.swing.SwingUtilities;
34
35 import org.djutils.draw.bounds.Bounds2d;
36 import org.djutils.event.Event;
37 import org.djutils.event.EventListener;
38 import org.djutils.event.reference.ReferenceType;
39 import org.djutils.exceptions.Try;
40 import org.opentrafficsim.core.geometry.Flattener;
41 import org.opentrafficsim.core.geometry.Flattener.NumSegments;
42 import org.opentrafficsim.draw.network.LinkAnimation;
43 import org.opentrafficsim.draw.network.LinkAnimation.LinkData;
44 import org.opentrafficsim.draw.network.NodeAnimation;
45 import org.opentrafficsim.draw.network.NodeAnimation.NodeData;
46 import org.opentrafficsim.draw.road.BusStopAnimation;
47 import org.opentrafficsim.draw.road.BusStopAnimation.BusStopData;
48 import org.opentrafficsim.draw.road.CrossSectionElementAnimation.ShoulderData;
49 import org.opentrafficsim.draw.road.GtuGeneratorPositionAnimation;
50 import org.opentrafficsim.draw.road.GtuGeneratorPositionAnimation.GtuGeneratorPositionData;
51 import org.opentrafficsim.draw.road.LaneAnimation;
52 import org.opentrafficsim.draw.road.LaneAnimation.CenterLine;
53 import org.opentrafficsim.draw.road.LaneAnimation.LaneData;
54 import org.opentrafficsim.draw.road.LaneDetectorAnimation;
55 import org.opentrafficsim.draw.road.LaneDetectorAnimation.LoopDetectorData;
56 import org.opentrafficsim.draw.road.LaneDetectorAnimation.SinkData;
57 import org.opentrafficsim.draw.road.LaneDetectorAnimation.SinkData.SinkText;
58 import org.opentrafficsim.draw.road.PriorityAnimation.PriorityData;
59 import org.opentrafficsim.draw.road.StripeAnimation.StripeData;
60 import org.opentrafficsim.draw.road.TrafficLightAnimation;
61 import org.opentrafficsim.draw.road.TrafficLightAnimation.TrafficLightData;
62 import org.opentrafficsim.editor.OtsEditor;
63 import org.opentrafficsim.editor.XsdPaths;
64 import org.opentrafficsim.editor.XsdTreeNode;
65 import org.opentrafficsim.editor.XsdTreeNodeRoot;
66 import org.opentrafficsim.swing.gui.AppearanceControlComboBox;
67 import org.opentrafficsim.swing.gui.OtsControlPanel;
68
69 import nl.tudelft.simulation.dsol.animation.Locatable;
70 import nl.tudelft.simulation.dsol.animation.d2.Renderable2d;
71 import nl.tudelft.simulation.dsol.animation.d2.RenderableScale;
72 import nl.tudelft.simulation.dsol.swing.animation.d2.AnimationUpdaterThread;
73 import nl.tudelft.simulation.dsol.swing.animation.d2.VisualizationPanel;
74 import nl.tudelft.simulation.naming.context.ContextInterface;
75 import nl.tudelft.simulation.naming.context.Contextualized;
76 import nl.tudelft.simulation.naming.context.JvmContext;
77
78
79
80
81
82
83
84
85
86 public class EditorMap extends JPanel implements EventListener
87 {
88
89
90 private static final long serialVersionUID = 20231010L;
91
92
93 private static final Color BAR_COLOR = Color.LIGHT_GRAY;
94
95
96 private static final Set<String> TYPES = Set.of(XsdPaths.NODE, XsdPaths.LINK, XsdPaths.TRAFFIC_LIGHT, XsdPaths.SINK,
97 XsdPaths.GENERATOR, XsdPaths.LIST_GENERATOR);
98
99
100 private final Contextualized contextualized;
101
102
103 private final OtsEditor editor;
104
105
106 private final VisualizationPanel animationPanel;
107
108
109 private final JPanel toolPanel;
110
111
112 private final JPanel togglePanel;
113
114
115 private final LinkedHashMap<XsdTreeNode, MapData> datas = new LinkedHashMap<>();
116
117
118 private final WeakHashMap<MapLinkData, Object> links = new WeakHashMap<>();
119
120
121 private final LinkedHashMap<XsdTreeNode, RoadLayoutListener> roadLayoutListeners = new LinkedHashMap<>();
122
123
124 private FlattenerListener networkFlattenerListener;
125
126
127 private final LinkedHashMap<XsdTreeNode, Renderable2d<?>> animations = new LinkedHashMap<>();
128
129
130 private boolean ignoreKeepScale = false;
131
132
133 private Double lastXScale = null;
134
135
136 private Double lastYScale = null;
137
138
139 private Dimension lastScreen = null;
140
141
142 private Map<String, Class<? extends Locatable>> toggleLocatableMap = new LinkedHashMap<>();
143
144
145 private Map<MapStripeData, SynchronizableMapStripe> synStripes = new LinkedHashMap<>();
146
147
148 private final Map<XsdTreeNode, ChangeListener<Object>> overrideListeners = new LinkedHashMap<>();
149
150
151
152
153
154
155
156
157
158 private EditorMap(final AnimationUpdaterThread animator, final Contextualized contextualized, final OtsEditor editor)
159 throws RemoteException, NamingException
160 {
161 super(new BorderLayout());
162 this.contextualized = contextualized;
163 this.editor = editor;
164 this.animationPanel = new VisualizationPanel(new Bounds2d(500, 500), animator, contextualized.getContext())
165 {
166
167 private static final long serialVersionUID = 20231016L;
168
169 @Override
170 public void setExtent(final Bounds2d extent)
171 {
172 if (EditorMap.this.lastScreen != null)
173 {
174
175 EditorMap.this.lastXScale = this.getRenderableScale().getXScale(extent, EditorMap.this.lastScreen);
176 EditorMap.this.lastYScale = this.getRenderableScale().getYScale(extent, EditorMap.this.lastScreen);
177 }
178 super.setExtent(extent);
179 }
180
181 @Override
182 public synchronized void zoomAll()
183 {
184 EditorMap.this.ignoreKeepScale = true;
185 Bounds2d extent = EditorMap.this.animationPanel.fullExtent();
186 if (Double.isFinite(extent.getMaxX()))
187 {
188 super.zoomAll();
189 }
190 else if (getSize().height != 0)
191 {
192
193 super.home();
194 }
195 EditorMap.this.ignoreKeepScale = false;
196 }
197
198 @Override
199 public synchronized void home()
200 {
201 EditorMap.this.ignoreKeepScale = true;
202 super.home();
203 EditorMap.this.ignoreKeepScale = false;
204 }
205 };
206 this.animationPanel.setBackground(Color.GRAY);
207 this.animationPanel.setShowToolTip(false);
208 editor.addListener(this, OtsEditor.NEW_FILE);
209 this.animationPanel.setRenderableScale(new RenderableScale()
210 {
211 @Override
212 public Bounds2d computeVisibleExtent(final Bounds2d extent, final Dimension screen)
213 {
214 if (EditorMap.this.ignoreKeepScale)
215 {
216 return super.computeVisibleExtent(extent, screen);
217 }
218
219 double xScale = getXScale(extent, screen);
220 double yScale = getYScale(extent, screen);
221 Bounds2d result;
222 if (EditorMap.this.lastYScale != null && yScale == EditorMap.this.lastYScale)
223 {
224 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * yScale,
225 extent.midPoint().getX() + 0.5 * screen.getWidth() * yScale, extent.getMinY(), extent.getMaxY());
226 xScale = yScale;
227 }
228 else if (EditorMap.this.lastXScale != null && xScale == EditorMap.this.lastXScale)
229 {
230 result = new Bounds2d(extent.getMinX(), extent.getMaxX(),
231 extent.midPoint().getY() - 0.5 * screen.getHeight() * xScale * getYScaleRatio(),
232 extent.midPoint().getY() + 0.5 * screen.getHeight() * xScale * getYScaleRatio());
233 yScale = xScale;
234 }
235 else
236 {
237 double scale = EditorMap.this.lastXScale == null ? Math.min(xScale, yScale)
238 : EditorMap.this.lastXScale * EditorMap.this.lastScreen.getWidth() / screen.getWidth();
239 result = new Bounds2d(extent.midPoint().getX() - 0.5 * screen.getWidth() * scale,
240 extent.midPoint().getX() + 0.5 * screen.getWidth() * scale,
241 extent.midPoint().getY() - 0.5 * screen.getHeight() * scale * getYScaleRatio(),
242 extent.midPoint().getY() + 0.5 * screen.getHeight() * scale * getYScaleRatio());
243 yScale = scale;
244 xScale = scale;
245 }
246 EditorMap.this.lastXScale = xScale;
247 EditorMap.this.lastYScale = yScale;
248 EditorMap.this.lastScreen = screen;
249 return result;
250 }
251 });
252 add(this.animationPanel, BorderLayout.CENTER);
253
254 this.toolPanel = new JPanel();
255 setupTools();
256
257 this.togglePanel = new JPanel();
258 this.togglePanel.setBackground(BAR_COLOR);
259 setAnimationToggles();
260 this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS));
261 add(this.togglePanel, BorderLayout.WEST);
262 }
263
264
265
266
267 private void setupTools()
268 {
269 this.toolPanel.setBackground(BAR_COLOR);
270 this.toolPanel.setMinimumSize(new Dimension(350, 28));
271 this.toolPanel.setPreferredSize(new Dimension(350, 28));
272 this.toolPanel.setLayout(new BoxLayout(this.toolPanel, BoxLayout.X_AXIS));
273
274 this.toolPanel.add(Box.createHorizontalStrut(5));
275 this.toolPanel.add(new JLabel("Add tools:"));
276
277 this.toolPanel.add(Box.createHorizontalStrut(5));
278
279 ButtonGroup group = new ButtonGroup()
280 {
281
282 private static final long serialVersionUID = 20240227L;
283
284 @Override
285 public void setSelected(final ButtonModel model, final boolean selected)
286 {
287 if (selected)
288 {
289 super.setSelected(model, selected);
290 }
291 else
292 {
293 clearSelection();
294 }
295 }
296 };
297
298 JToggleButton nodeButton = new JToggleButton(loadIcon("./OTS_node.png"));
299 nodeButton.setPreferredSize(new Dimension(24, 24));
300 nodeButton.setMinimumSize(new Dimension(24, 24));
301 nodeButton.setMaximumSize(new Dimension(24, 24));
302 nodeButton.setToolTipText("Add node");
303 group.add(nodeButton);
304 this.toolPanel.add(nodeButton);
305
306 JToggleButton linkButton = new JToggleButton(loadIcon("./OTS_link.png"));
307 linkButton.setPreferredSize(new Dimension(24, 24));
308 linkButton.setMinimumSize(new Dimension(24, 24));
309 linkButton.setMaximumSize(new Dimension(24, 24));
310 linkButton.setToolTipText("Add link");
311 group.add(linkButton);
312 this.toolPanel.add(linkButton);
313
314 JToggleButton centroidButton = new JToggleButton(loadIcon("./OTS_centroid.png"));
315 centroidButton.setPreferredSize(new Dimension(24, 24));
316 centroidButton.setMinimumSize(new Dimension(24, 24));
317 centroidButton.setMaximumSize(new Dimension(24, 24));
318 centroidButton.setToolTipText("Add centroid");
319 group.add(centroidButton);
320 this.toolPanel.add(centroidButton);
321
322 JToggleButton connectorButton = new JToggleButton(loadIcon("./OTS_connector.png"));
323 connectorButton.setPreferredSize(new Dimension(24, 24));
324 connectorButton.setMinimumSize(new Dimension(24, 24));
325 connectorButton.setMaximumSize(new Dimension(24, 24));
326 connectorButton.setToolTipText("Add connector");
327 group.add(connectorButton);
328 this.toolPanel.add(connectorButton);
329
330 this.toolPanel.add(Box.createHorizontalStrut(5));
331 JComboBox<String> shape = new AppearanceControlComboBox<>();
332 shape.setModel(new DefaultComboBoxModel<>(new String[] {"Straight", "Bezier", "Clothoid", "Arc", "PolyLine"}));
333 shape.setMinimumSize(new Dimension(50, 22));
334 shape.setMaximumSize(new Dimension(90, 22));
335 shape.setPreferredSize(new Dimension(90, 22));
336 shape.setToolTipText("Standard shape for new links");
337 this.toolPanel.add(shape);
338
339 this.toolPanel.add(Box.createHorizontalStrut(5));
340 JComboBox<String> roadLayout = new AppearanceControlComboBox<>();
341 roadLayout.setModel(new DefaultComboBoxModel<>(new String[] {}));
342 roadLayout.setMinimumSize(new Dimension(50, 22));
343 roadLayout.setMaximumSize(new Dimension(125, 22));
344 roadLayout.setPreferredSize(new Dimension(125, 22));
345 roadLayout.setToolTipText("Standard defined road layout for new links");
346 roadLayout.setEnabled(false);
347 this.toolPanel.add(roadLayout);
348
349 this.toolPanel.add(Box.createHorizontalStrut(5));
350 JComboBox<String> linkType = new AppearanceControlComboBox<>();
351 linkType.setModel(new DefaultComboBoxModel<>(new String[] {}));
352 linkType.setMinimumSize(new Dimension(50, 22));
353 linkType.setMaximumSize(new Dimension(125, 22));
354 linkType.setPreferredSize(new Dimension(125, 22));
355 linkType.setToolTipText("Standard link type for new links and connectors");
356 linkType.setEnabled(false);
357 this.toolPanel.add(linkType);
358
359 this.toolPanel.add(Box.createHorizontalStrut(5));
360 Dimension minDim = new Dimension(0, 1);
361 Dimension prefDim = new Dimension(0, 1);
362 Dimension maxDim = new Dimension(5000, 1);
363 this.toolPanel.add(new Filler(minDim, prefDim, maxDim));
364
365 this.toolPanel.add(new JLabel("Show:"));
366
367 this.toolPanel.add(Box.createHorizontalStrut(5));
368 JButton extent = new JButton(loadIcon("./Expand.png"));
369 extent.setMinimumSize(new Dimension(24, 24));
370 extent.setMaximumSize(new Dimension(24, 24));
371 extent.setPreferredSize(new Dimension(24, 24));
372 extent.setToolTipText("Zoom whole network");
373 extent.addActionListener((e) -> this.animationPanel.zoomAll());
374 this.toolPanel.add(extent);
375
376 JButton grid = new JButton(loadIcon("./Grid.png"));
377 grid.setMinimumSize(new Dimension(24, 24));
378 grid.setMaximumSize(new Dimension(24, 24));
379 grid.setPreferredSize(new Dimension(24, 24));
380 grid.setToolTipText("Toggle grid on/off");
381 grid.addActionListener((e) -> this.animationPanel.setShowGrid(!this.animationPanel.isShowGrid()));
382 this.toolPanel.add(grid);
383
384 this.toolPanel.add(Box.createHorizontalStrut(5));
385
386 add(this.toolPanel, BorderLayout.NORTH);
387 }
388
389
390
391
392
393
394 private Icon loadIcon(final String file)
395 {
396 try
397 {
398 return OtsEditor.loadIcon(file, 16, 16, -1, -1);
399 }
400 catch (IOException ioe)
401 {
402
403 return null;
404 }
405 }
406
407
408
409
410 private void setAnimationToggles()
411 {
412 addToggle("Node", NodeData.class, "/icons/Node24.png", "Show/hide nodes", true, false);
413 addToggle("NodeId", NodeAnimation.Text.class, "/icons/Id24.png", "Show/hide node ids", false, true);
414 addToggle("Link", LinkData.class, "/icons/Link24.png", "Show/hide links", true, false);
415 addToggle("LinkId", LinkAnimation.Text.class, "/icons/Id24.png", "Show/hide link ids", false, true);
416 addToggle("Priority", PriorityData.class, "/icons/Priority24.png", "Show/hide link priority", true, false);
417 addToggle("Lane", LaneData.class, "/icons/Lane24.png", "Show/hide lanes", true, false);
418 addToggle("LaneId", LaneAnimation.Text.class, "/icons/Id24.png", "Show/hide lane ids", false, true);
419 addToggle("LaneCenter", CenterLine.class, "/icons/CenterLine24.png", "Show/hide lane center lines", false, false);
420 addToggle("Stripe", StripeData.class, "/icons/Stripe24.png", "Show/hide stripes", true, false);
421 addToggle("Shoulder", ShoulderData.class, "/icons/Shoulder24.png", "Show/hide shoulders", true, false);
422
423 addToggle("Generator", GtuGeneratorPositionData.class, "/icons/Generator24.png", "Show/hide generators", true, false);
424 addToggle("Sink", SinkData.class, "/icons/Sink24.png", "Show/hide sinks", true, true);
425 addToggle("Detector", LoopDetectorData.class, "/icons/Detector24.png", "Show/hide loop detectors", true, false);
426 addToggle("DetectorId", LoopDetectorData.Text.class, "/icons/Id24.png", "Show/hide loop detector ids", false, true);
427 addToggle("Light", TrafficLightData.class, "/icons/TrafficLight24.png", "Show/hide traffic lights", true, false);
428 addToggle("LightId", TrafficLightAnimation.Text.class, "/icons/Id24.png", "Show/hide traffic light ids", false, true);
429 addToggle("Bus", BusStopData.class, "/icons/BusStop24.png", "Show/hide bus stops", true, false);
430 addToggle("BusId", BusStopAnimation.Text.class, "/icons/Id24.png", "Show/hide bus stop ids", false, true);
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444 public final void addToggle(final String name, final Class<? extends Locatable> locatableClass, final String iconPath,
445 final String toolTipText, final boolean initiallyVisible, final boolean idButton)
446 {
447 JToggleButton button;
448 Icon icon = OtsControlPanel.loadIcon(iconPath);
449 Icon unIcon = OtsControlPanel.loadGrayscaleIcon(iconPath);
450 button = new JCheckBox();
451 button.setSelectedIcon(icon);
452 button.setIcon(unIcon);
453 button.setPreferredSize(new Dimension(32, 28));
454 button.setName(name);
455 button.setEnabled(true);
456 button.setSelected(initiallyVisible);
457 button.setActionCommand(name);
458 button.setToolTipText(toolTipText);
459 button.addActionListener(new ActionListener()
460 {
461 @Override
462 public void actionPerformed(final ActionEvent e)
463 {
464 String actionCommand = e.getActionCommand();
465 if (EditorMap.this.toggleLocatableMap.containsKey(actionCommand))
466 {
467 Class<? extends Locatable> locatableClass = EditorMap.this.toggleLocatableMap.get(actionCommand);
468 EditorMap.this.animationPanel.toggleClass(locatableClass);
469 EditorMap.this.togglePanel.repaint();
470 }
471 }
472 });
473
474
475 if (idButton && this.togglePanel.getComponentCount() > 0)
476 {
477 JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1);
478 lastToggleBox.add(button);
479 }
480 else
481 {
482 JPanel toggleBox = new JPanel();
483 toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS));
484 toggleBox.add(button);
485 this.togglePanel.add(toggleBox);
486 toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
487 }
488
489 if (initiallyVisible)
490 {
491 this.animationPanel.showClass(locatableClass);
492 }
493 else
494 {
495 this.animationPanel.hideClass(locatableClass);
496 }
497 this.toggleLocatableMap.put(name, locatableClass);
498 }
499
500
501
502
503
504
505
506
507 public static EditorMap build(final OtsEditor editor) throws RemoteException, NamingException
508 {
509 AnimationUpdaterThread animator = new AnimationUpdaterThread();
510 animator.start();
511 ContextInterface context = new JvmContext("ots-context");
512 Contextualized contextualized = new Contextualized()
513 {
514 @Override
515 public ContextInterface getContext()
516 {
517 return context;
518 }
519 };
520 return new EditorMap(animator, contextualized, editor);
521 }
522
523 @Override
524 public void notify(final Event event) throws RemoteException
525 {
526 if (event.getType().equals(OtsEditor.NEW_FILE))
527 {
528 for (XsdTreeNode node : new LinkedHashSet<>(this.datas.keySet()))
529 {
530 remove(node);
531 }
532 this.datas.clear();
533 this.links.clear();
534 for (Renderable2d<?> animation : this.animations.values())
535 {
536 animation.destroy(this.contextualized);
537 removeAnimation(animation);
538 }
539 this.animations.clear();
540 for (RoadLayoutListener roadLayoutListener : this.roadLayoutListeners.values())
541 {
542 roadLayoutListener.destroy();
543 }
544 this.roadLayoutListeners.clear();
545 if (this.networkFlattenerListener != null)
546 {
547 this.networkFlattenerListener.destroy();
548 this.networkFlattenerListener = null;
549 }
550 XsdTreeNodeRoot root = (XsdTreeNodeRoot) event.getContent();
551 root.addListener(this, XsdTreeNodeRoot.NODE_CREATED);
552 root.addListener(this, XsdTreeNodeRoot.NODE_REMOVED);
553 SwingUtilities.invokeLater(() -> this.animationPanel.zoomAll());
554 }
555 else if (event.getType().equals(XsdTreeNodeRoot.NODE_CREATED))
556 {
557 Object[] content = (Object[]) event.getContent();
558 XsdTreeNode node = (XsdTreeNode) content[0];
559 if (isType(node))
560 {
561 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED);
562 node.addListener(this, XsdTreeNode.OPTION_CHANGED);
563 if (node.isActive())
564 {
565 add(node);
566 }
567 }
568 else if (node.getPathString().equals(XsdPaths.POLYLINE_COORDINATE))
569 {
570 for (MapLinkData linkData : this.links.keySet())
571 {
572 linkData.addCoordinate(node);
573 }
574 }
575 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
576 {
577 addRoadLayout(node);
578 }
579 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
580 {
581 setNetworkFlattener(node);
582 }
583 else if (node.getPathString().endsWith("LaneOverride") || node.getPathString().endsWith("StripeOverride"))
584 {
585 ChangeListener<Object> listener = new ChangeListener<>(node, () -> this.editor.getEval())
586 {
587 private static final long serialVersionUID = 20241206L;
588
589 @Override
590 public void notify(final Event event) throws RemoteException
591 {
592 if (event.getType().equals(ChangeListener.CHANGE_EVENT))
593 {
594 MapData data = EditorMap.this.datas.get(getNode().getParent().getParent());
595 if (data instanceof MapLinkData linkData)
596 {
597 linkData.evalChanged();
598 }
599 }
600 else
601 {
602 super.notify(event);
603 }
604 }
605
606 @Override
607 Object calculateData()
608 {
609 return null;
610 }
611 };
612 this.overrideListeners.put(node, listener);
613 listener.addListener(listener, ChangeListener.CHANGE_EVENT);
614 }
615 }
616 else if (event.getType().equals(XsdTreeNodeRoot.NODE_REMOVED))
617 {
618 Object[] content = (Object[]) event.getContent();
619 XsdTreeNode node = (XsdTreeNode) content[0];
620 if (this.datas.containsKey(node))
621 {
622 remove(node);
623 }
624 else if (node.getPathString().equals(XsdPaths.POLYLINE_COORDINATE))
625 {
626 for (MapLinkData linkData : this.links.keySet())
627 {
628 linkData.removeCoordinate(node);
629 }
630 }
631 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
632 {
633 removeRoadLayout(node);
634 }
635 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
636 {
637 removeNetworkFlattener();
638 }
639 else if (node.getPathString().endsWith("LaneOverride") || node.getPathString().endsWith("StripeOverride"))
640 {
641 ChangeListener<Object> listener = this.overrideListeners.remove(node);
642 if (listener != null)
643 {
644 listener.removeListener(listener, ChangeListener.CHANGE_EVENT);
645 listener.destroy();
646 }
647 }
648 }
649 else if (event.getType().equals(XsdTreeNode.ACTIVATION_CHANGED))
650 {
651 Object[] content = (Object[]) event.getContent();
652 XsdTreeNode node = (XsdTreeNode) content[0];
653 if (isType(node))
654 {
655 if ((boolean) content[1])
656 {
657 add(node);
658 }
659 else
660 {
661 remove(node);
662 }
663 }
664 else if (node.getPathString().equals(XsdPaths.DEFINED_ROADLAYOUT))
665 {
666 if ((boolean) content[1])
667 {
668 addRoadLayout(node);
669 }
670 else
671 {
672 removeRoadLayout(node);
673 }
674 }
675 else if (node.getPathString().equals(XsdPaths.NETWORK + ".Flattener"))
676 {
677 if ((boolean) content[1])
678 {
679 setNetworkFlattener(node);
680 }
681 else
682 {
683 removeNetworkFlattener();
684 }
685 }
686 }
687 else if (event.getType().equals(XsdTreeNode.OPTION_CHANGED))
688 {
689 Object[] content = (Object[]) event.getContent();
690 XsdTreeNode node = (XsdTreeNode) content[0];
691 XsdTreeNode selected = (XsdTreeNode) content[1];
692 XsdTreeNode previous = (XsdTreeNode) content[2];
693 if (node.equals(selected))
694 {
695 if (isType(previous))
696 {
697 remove(previous);
698 }
699 if (isType(selected) && selected.isActive())
700 {
701 add(selected);
702 }
703 }
704 }
705 else if (event.getType().equals(XsdTreeNode.ATTRIBUTE_CHANGED))
706 {
707 for (MapLinkData linkData : this.links.keySet())
708 {
709 linkData.notifyNodeIdChanged(linkData.getNode());
710 }
711 }
712 }
713
714
715
716
717
718
719 private boolean isType(final XsdTreeNode node)
720 {
721 for (String type : TYPES)
722 {
723 if (node.isType(type))
724 {
725 return true;
726 }
727 }
728 return false;
729 }
730
731
732
733
734
735 public void setValid(final MapData data)
736 {
737 XsdTreeNode node = data.getNode();
738 if (this.animations.containsKey(node))
739 {
740 return;
741 }
742 Renderable2d<?> animation;
743 if (node.getPathString().equals(XsdPaths.NODE))
744 {
745 animation = new NodeAnimation((MapNodeData) data, this.contextualized);
746 }
747 else if (node.getPathString().equals(XsdPaths.LINK))
748 {
749 animation = Try.assign(() -> new LinkAnimation((MapLinkData) data, this.contextualized, 0.5f).setDynamic(true), "");
750 }
751 else if (node.getPathString().equals(XsdPaths.TRAFFIC_LIGHT))
752 {
753 animation = Try.assign(() -> new TrafficLightAnimation((MapTrafficLightData) data, this.contextualized), "");
754 }
755 else if (node.getPathString().equals(XsdPaths.SINK))
756 {
757 Function<LaneDetectorAnimation<SinkData, SinkText>, SinkText> textSupplier =
758 (s) -> Try.assign(() -> new SinkText(s.getSource(),
759 (float) (s.getSource().getLine().getLength() / 2.0 + 0.2), this.contextualized), "");
760 animation = Try.assign(() -> new LaneDetectorAnimation<SinkData, SinkText>((SinkData) data, this.contextualized,
761 Color.ORANGE, textSupplier), "");
762 }
763 else if (node.getPathString().equals(XsdPaths.GENERATOR) || node.getPathString().equals(XsdPaths.LIST_GENERATOR))
764 {
765 animation = new GtuGeneratorPositionAnimation((GtuGeneratorPositionData) data, this.contextualized);
766 }
767 else
768 {
769 throw new UnsupportedOperationException("Data cannot be added by the map editor.");
770 }
771 this.animations.put(node, animation);
772 }
773
774
775
776
777
778
779 public void setInvalid(final MapData data)
780 {
781
782 }
783
784
785
786
787
788
789
790 private void add(final XsdTreeNode node) throws RemoteException
791 {
792 MapData data;
793 if (this.datas.containsKey(node))
794 {
795 return;
796 }
797 if (node.getPathString().equals(XsdPaths.NODE))
798 {
799 data = new MapNodeData(this, node, this.editor);
800 node.addListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
801 }
802 else if (node.getPathString().equals(XsdPaths.LINK))
803 {
804 MapLinkData linkData = new MapLinkData(this, node, this.editor);
805 data = linkData;
806 if (this.networkFlattenerListener != null)
807 {
808 this.networkFlattenerListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
809 }
810 this.links.put(linkData, null);
811 }
812 else if (node.getPathString().equals(XsdPaths.TRAFFIC_LIGHT))
813 {
814 MapTrafficLightData trafficLightData = new MapTrafficLightData(this, node, this.editor);
815 data = trafficLightData;
816 }
817 else if (node.getPathString().equals(XsdPaths.SINK))
818 {
819 MapSinkData sinkData = new MapSinkData(this, node, this.editor);
820 data = sinkData;
821 }
822 else if (node.getPathString().equals(XsdPaths.GENERATOR) || node.getPathString().equals(XsdPaths.LIST_GENERATOR))
823 {
824 MapGeneratorData generatorData = new MapGeneratorData(this, node, this.editor);
825 data = generatorData;
826 }
827 else
828 {
829 throw new UnsupportedOperationException("Node cannot be added by the map editor.");
830 }
831 this.datas.put(node, data);
832 }
833
834
835
836
837
838 private void remove(final XsdTreeNode node)
839 {
840 if (node.getPathString().equals(XsdPaths.NODE))
841 {
842 node.removeListener(this, XsdTreeNode.ATTRIBUTE_CHANGED);
843 }
844 if (node.getPathString().equals(XsdPaths.LINK))
845 {
846 Iterator<MapLinkData> it = this.links.keySet().iterator();
847 while (it.hasNext())
848 {
849 MapLinkData link = it.next();
850 if (link.getNode().equals(node))
851 {
852 for (RoadLayoutListener roadLayoutListener : this.roadLayoutListeners.values())
853 {
854 roadLayoutListener.removeListener(link, ChangeListener.CHANGE_EVENT);
855 }
856 if (this.networkFlattenerListener != null)
857 {
858 this.networkFlattenerListener.removeListener(link, ChangeListener.CHANGE_EVENT);
859 }
860 it.remove();
861 break;
862 }
863 }
864 }
865 MapData data = this.datas.remove(node);
866 if (data != null)
867 {
868 data.destroy();
869 }
870 removeAnimation(this.animations.remove(node));
871 }
872
873
874
875
876
877
878
879 public void reinitialize(final XsdTreeNode node)
880 {
881 remove(node);
882 Try.execute(() -> add(node), RuntimeException.class, "Unable to bind to context.");
883 }
884
885
886
887
888
889
890 public MapData getData(final XsdTreeNode node)
891 {
892 return this.datas.get(node);
893 }
894
895
896
897
898
899 private void addRoadLayout(final XsdTreeNode node)
900 {
901 RoadLayoutListener roadLayoutListener = new RoadLayoutListener(node, () -> this.editor.getEval());
902 for (MapLinkData linkData : this.links.keySet())
903 {
904 roadLayoutListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
905 }
906 this.roadLayoutListeners.put(node, roadLayoutListener);
907 }
908
909
910
911
912
913 private void removeRoadLayout(final XsdTreeNode node)
914 {
915 RoadLayoutListener roadLayoutListener = this.roadLayoutListeners.remove(node);
916 roadLayoutListener.destroy();
917 }
918
919
920
921
922
923 private void setNetworkFlattener(final XsdTreeNode node)
924 {
925 this.networkFlattenerListener = new FlattenerListener(node, () -> this.editor.getEval());
926 for (MapLinkData linkData : this.links.keySet())
927 {
928 this.networkFlattenerListener.addListener(linkData, ChangeListener.CHANGE_EVENT, ReferenceType.WEAK);
929 this.editor.addEvalListener(this.networkFlattenerListener);
930 }
931 node.addListener(this, XsdTreeNode.ACTIVATION_CHANGED, ReferenceType.WEAK);
932 }
933
934
935
936
937 private void removeNetworkFlattener()
938 {
939 if (this.networkFlattenerListener != null)
940 {
941 this.editor.removeEvalListener(this.networkFlattenerListener);
942 }
943 this.networkFlattenerListener.destroy();
944 for (MapLinkData linkData : this.links.keySet())
945 {
946 Try.execute(() -> linkData.notify(new Event(ChangeListener.CHANGE_EVENT, this.networkFlattenerListener.getNode())),
947 "Remove event exception.");
948 }
949 this.networkFlattenerListener = null;
950 }
951
952
953
954
955
956
957 RoadLayoutListener getRoadLayoutListener(final XsdTreeNode node)
958 {
959 return this.roadLayoutListeners.get(node);
960 }
961
962
963
964
965
966 void removeAnimation(final Renderable2d<?> animation)
967 {
968 if (animation != null)
969 {
970 this.animationPanel.objectRemoved(animation);
971 animation.destroy(this.contextualized);
972 }
973 }
974
975
976
977
978
979 Contextualized getContextualized()
980 {
981 return this.contextualized;
982 }
983
984
985
986
987
988 public Flattener getNetworkFlattener()
989 {
990 if (this.networkFlattenerListener != null)
991 {
992 Flattener flattener = this.networkFlattenerListener.getData();
993 if (flattener != null)
994 {
995 return flattener;
996 }
997 }
998 return new NumSegments(64);
999 }
1000
1001
1002
1003
1004
1005 public Map<MapStripeData, SynchronizableMapStripe> getSynchronizableStripes()
1006 {
1007 return this.synStripes;
1008 }
1009
1010 }