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