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