All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.visualizer.GraphVisualizer Maven / Gradle / Ivy

package org.opentripplanner.visualizer;

import com.google.common.collect.Sets;
import org.locationtech.jts.geom.Coordinate;
import javassist.Modifier;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.graph_builder.annotation.GraphBuilderAnnotation;
import org.opentripplanner.graph_builder.annotation.StopUnlinked;
import org.opentripplanner.routing.algorithm.TraverseVisitor;
import org.opentripplanner.routing.core.OptimizeType;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.impl.GraphPathFinder;
import org.opentripplanner.routing.spt.DominanceFunction;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
import org.opentripplanner.standalone.Router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.Queue;

/**
 * Exit on window close.
 * 
 */
class ExitListener extends WindowAdapter {
    public void windowClosing(WindowEvent event) {
        System.exit(0);
    }
}

/**
 * DisplayVertex holds a vertex, but has a toString value that's a little more useful.
 */
class DisplayVertex {
    public Vertex vertex;

    public DisplayVertex(Vertex v) {
        vertex = v;
    }

    public String toString() {
        String label = vertex.getLabel();
        if (label.contains("osm node")) {
            label = vertex.getName();
        }
        return label;
    }
}

/**
 * This is a ListModel that holds Edges. It gets its edges from a PatternBoard/PatternAlight, hence the iterable.
 */
class EdgeListModel extends AbstractListModel {

    private static final long serialVersionUID = 1L;

    private ArrayList edges;

    EdgeListModel(Iterable edges) {
        this.edges = new ArrayList();
        for (Edge e : edges) {
            this.edges.add(e);
        }
    }

    public int getSize() {
        return edges.size();
    }

    public Edge getElementAt(int index) {
        return edges.get(index);
    }
}

/**
 * A list of vertices where the internal container is exposed.
 */
class VertexList extends AbstractListModel {

    private static final long serialVersionUID = 1L;

    public List selected;

    VertexList(List selected) {
        this.selected = selected;
    }

    public int getSize() {
        return selected.size();
    }

    public DisplayVertex getElementAt(int index) {
        return new DisplayVertex(selected.get(index));
    }
};

/**
 * A simple visualizer for graphs. It shows (using ShowGraph) a map of the graph, intersections and 
 * TransitStops only, and allows a user to select stops, examine incoming and outgoing edges, and 
 * examine trip patterns. It's meant mainly for debugging, so it's totally OK if it develops (say) 
 * a bunch of weird buttons designed to debug specific cases.
 */
public class GraphVisualizer extends JFrame implements VertexSelectionListener {
	
	private final class ComparePathStatesClickListener implements ListSelectionListener {
		private JList outputList;

		ComparePathStatesClickListener(JList outputList){
			this.outputList = outputList;
		}
		
		@Override
		public void valueChanged(ListSelectionEvent e) {
			@SuppressWarnings("unchecked")
			JList theList = (JList)e.getSource();
			State st = (State)theList.getSelectedValue();
			if(st==null){
				return;
			}
			
			DefaultListModel stateListModel = new DefaultListModel();
			stateListModel.addElement( "weight:"+st.getWeight() );
			stateListModel.addElement( "weightdelta:"+st.getWeightDelta() );
			stateListModel.addElement( "bikeRenting:"+st.isBikeRenting() );
			stateListModel.addElement( "carParked:"+st.isCarParked() );
			stateListModel.addElement( "walkDistance:"+st.getWalkDistance() );
			stateListModel.addElement( "elapsedTime:"+st.getElapsedTimeSeconds() );
			stateListModel.addElement( "numBoardings:"+st.getNumBoardings() );
			outputList.setModel( stateListModel );
			
			lastStateClicked = st;
		}
	}

	private final class OnPopupMenuClickListener implements ActionListener {
		private final class DiffListCellRenderer extends DefaultListCellRenderer {
			private final int diverge;
			private final int converge;

			private DiffListCellRenderer(int diverge, int converge) {
				this.diverge = diverge;
				this.converge = converge;
			}

			@Override
			public Component getListCellRendererComponent(JList list, Object value, int index,
					boolean isSelected, boolean cellHasFocus) {
				Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
				if(isSelected){
					return c;
				}
				
				if(index <= diverge){
					c.setBackground(new Color(196,201,255));
				}
				if(index >= converge){
					c.setBackground(new Color(255,196,196));
				}
				
				return c;
			}
		}
		
		private int[] diffPaths() {
			if(firstComparePath == null || secondComparePath == null) {
				int[] failboat = {-2,-2};
				return failboat;
			}
			
			int l1 = firstComparePath.states.size();
			int l2 = secondComparePath.states.size();
			int minlen = l1 < l2 ? l1 : l2;
			
			int divergence=-1;
			int convergence=-1;
			
			// find divergence
			for(int i=0; i pathModel = new DefaultListModel();
				for( State st : firstComparePath.states ){
					pathModel.addElement( st );
				}
				firstComparePathStates.setModel( pathModel );
			}
			if(secondComparePath != null){
				DefaultListModel pathModel = new DefaultListModel();
				for( State st : secondComparePath.states ){
					pathModel.addElement( st );
				}
				secondComparePathStates.setModel( pathModel );
			}
			
			int[] diff = diffPaths();
			final int diverge = diff[0];
			final int converge = diff[1];
			if(diff[0]>=0){
				firstComparePathStates.setCellRenderer(new DiffListCellRenderer(diverge,firstComparePath.states.size()-converge-1));
				secondComparePathStates.setCellRenderer(new DiffListCellRenderer(diverge,secondComparePath.states.size()-converge-1));
			}
		}
	}

	class PathPrinter{
		GraphPath gp;
		PathPrinter(GraphPath gp){
			this.gp=gp;
		}
		public String toString(){
			SimpleDateFormat shortDateFormat = new SimpleDateFormat("HH:mm:ss z");
			String startTime = shortDateFormat.format(new Date(gp.getStartTime()*1000));
			String endTime = shortDateFormat.format(new Date(gp.getEndTime()*1000));
			return "Path ("+startTime+"-"+endTime+") weight:"+gp.getWeight()+" dur:"+(gp.getDuration()/60.0)+" walk:"+gp.getWalkDistance()+" nTrips:"+gp.getTrips().size();
		}
	}

    private static final long serialVersionUID = 1L;

    private static final Logger LOG = LoggerFactory.getLogger(GraphVisualizer.class);

    private JPanel leftPanel;

    /* The Processing applet that actually displays the graph. */
    private ShowGraph showGraph;

    /* The set of callbacks that display search progress on the showGraph Processing applet. */
    public TraverseVisitor traverseVisitor;

    public JList nearbyVertices;

    private JList outgoingEdges;

    private JList incomingEdges;

    private JTextField sourceVertex;

    private JTextField sinkVertex;

    private JCheckBox walkCheckBox;

    private JCheckBox bikeCheckBox;

    private JCheckBox trainCheckBox;

    private JCheckBox busCheckBox;

    private JCheckBox ferryCheckBox;

    private JCheckBox transitCheckBox;

    private JCheckBox carCheckBox;

    private JTextField searchDate;

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");

    private JTextField boardingPenaltyField;

    private DefaultListModel annotationMatchesModel;

    private JList annotationMatches;
    
    private DefaultListModel metadataModel;

    private HashSet closed;

    private Vertex tracingVertex;

    private HashSet open;

    private HashSet seen;

    private JList metadataList;

    /* The router we are visualizing. */
    private final Router router;

    /* The graph from the router we are visualizing, note that it will not be updated if the router reloads. */
    private final Graph graph;

	private JRadioButton opQuick;

	private JRadioButton opSafe;

	private JRadioButton opFlat;

	private JRadioButton opGreenways;

	private ButtonGroup optimizeTypeGrp;

	private JTextField maxWalkField;

	private JTextField walkSpeed;

	private JTextField bikeSpeed;

	private JTextField heuristicWeight;

	private JCheckBox softWalkLimiting;

	private JTextField softWalkPenalty;

	private JTextField softWalkOverageRate;

	private JCheckBox arriveByCheckBox;

	private JLabel searchTimeElapsedLabel;

	private JCheckBox dontUseGraphicalCallbackCheckBox;
	
	private JTextField nPaths;
	
	private JList pathsList;
	
	private JList pathStates;
	
	private JCheckBox showTransitCheckbox;

	private JCheckBox showStreetsCheckbox;
	
	private JCheckBox showMultistateVerticesCheckbox;

	private JCheckBox showHighlightedCheckbox;
	
	private JCheckBox showSPTCheckbox;
	
	private ShortestPathTree spt;
	
	private JTextField sptFlattening;
	
	private JTextField sptThickness;
	
	private JPopupMenu popup;

	private GraphPath firstComparePath;
	private GraphPath secondComparePath;

	private JList firstComparePathStates;
	private JList secondComparePathStates;

	private JList secondStateData;

	private JList firstStateData;

	protected State lastStateClicked=null;

	private JCheckBox longDistanceModeCheckbox;

    public GraphVisualizer(Router router) {
        super();
        LOG.info("Starting up graph visualizer...");
        setTitle("GraphVisualizer");
        this.router = router;
        this.graph = router.graph;
        init();
    }

    public void run () {
        this.setVisible(true);
    }
    
    public void init() {
    	final JTabbedPane tabbedPane = new JTabbedPane();
    	
    	final Container mainTab = makeMainTab();
    	Container prefsPanel = makePrefsPanel();
    	Container diffTab = makeDiffTab();
         
    	tabbedPane.addTab("Main", null, mainTab,
                "Pretty much everything");
         
    	tabbedPane.addTab("Prefs", null, prefsPanel,
                "Routing preferences");
    	
    	tabbedPane.addTab("Diff", null, diffTab, "multistate path diffs");
         
        //Add the tabbed pane to this panel.
        add(tabbedPane);
         
        //The following line enables to use scrolling tabs.
        tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
        
        // startup the graphical pane; ensure closing works; draw the window
        showGraph.init();
        addWindowListener(new ExitListener());
        pack();
        
        // make sure the showGraph quits drawing when we switch tabs
        tabbedPane.addChangeListener(new ChangeListener(){
        	@Override
        	public void stateChanged(ChangeEvent e) {
        		if( tabbedPane.getSelectedComponent().equals(mainTab) ){
        			showGraph.loop();	
        		} else{
        			showGraph.noLoop();
        		}
        	}
        });
    }

	private Container makeDiffTab() {
        JPanel pane = new JPanel();
        pane.setLayout(new GridLayout(0, 2));
        
        firstStateData = new JList();
        secondStateData = new JList();
        
        // a place to list the states of the first path
        firstComparePathStates = new JList();        
        JScrollPane stScrollPane = new JScrollPane(firstComparePathStates);
        stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        pane.add(stScrollPane);
        firstComparePathStates.addListSelectionListener(new ComparePathStatesClickListener(firstStateData));
        
        // a place to list the states of the second path
        secondComparePathStates = new JList();
        stScrollPane = new JScrollPane(secondComparePathStates);
        stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        pane.add(stScrollPane);
        secondComparePathStates.addListSelectionListener(new ComparePathStatesClickListener(secondStateData));
        
        // a place to list details of a state selected from the first path
        stScrollPane = new JScrollPane(firstStateData);
        stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        pane.add(stScrollPane);
        
        // a place to list details of a state selected from the second path
        stScrollPane = new JScrollPane(secondStateData);
        stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        pane.add(stScrollPane);
        
        // A button that executes the 'dominates' function between the two states
        // this is useful only if you have a breakpoint set up
        JButton dominateButton = new JButton();
        dominateButton.setText("dominates");
        dominateButton.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e) {
                State s1 = firstComparePathStates.getSelectedValue();
				State s2 = secondComparePathStates.getSelectedValue();
                DominanceFunction pareto = new DominanceFunction.Pareto();
				System.out.println("s1 dominates s2:" + pareto.betterOrEqualAndComparable(s1, s2));
				System.out.println("s2 dominates s1:" + pareto.betterOrEqualAndComparable(s2, s1));
			}
        });
        pane.add(dominateButton);
        
        // A button that executes the 'traverse' function leading to the last clicked state
        // in either window. Also only useful if you set a breakpoint.
        JButton traverseButton = new JButton();
        traverseButton.setText("traverse");
        traverseButton.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent e) {
				if(lastStateClicked==null){
					return;
				}
				
				Edge backEdge = lastStateClicked.getBackEdge();
				State backState = lastStateClicked.getBackState();
				
				backEdge.traverse(backState);
			}
        });
        pane.add(traverseButton);
        
        return pane;
	}

	private Container makeMainTab() {
		Container pane = new JPanel();
    	pane.setLayout(new BorderLayout());
    	
        // init center graphical panel
        showGraph = new ShowGraph(this, getGraph());
        pane.add(showGraph, BorderLayout.CENTER);
        traverseVisitor = new VisualTraverseVisitor(showGraph);

        // init left panel
        leftPanel = new JPanel();
        leftPanel.setLayout(new BorderLayout());

        pane.add(leftPanel, BorderLayout.LINE_START);

        initRoutingSubpanel();
        initVertexInfoSubpanel();
        initControlButtons();

        // init right panel
        initRightPanel(pane);
		return pane;
	}
	
    private JComponent makePrefsPanel() {
        JPanel pane = new JPanel();
        pane.setLayout(new GridLayout(0, 2));

        // 4 rows (7 elements): transport mode options
        walkCheckBox = new JCheckBox("walk");
        walkCheckBox.setSelected(true);
        pane.add(walkCheckBox);
        bikeCheckBox = new JCheckBox("bike");
        pane.add(bikeCheckBox);
        trainCheckBox = new JCheckBox("trainish");
        pane.add(trainCheckBox);
        busCheckBox = new JCheckBox("busish");
        pane.add(busCheckBox);
        ferryCheckBox = new JCheckBox("ferry");
        pane.add(ferryCheckBox);
        transitCheckBox = new JCheckBox("transit");
        transitCheckBox.setSelected(true);
        pane.add(transitCheckBox);
        carCheckBox = new JCheckBox("car");
        pane.add(carCheckBox);

        // GridLayout does not support empty cells, so a dummy label is used to fix the layout.
        JLabel dummyLabel = new JLabel("");
        pane.add(dummyLabel);

        // row: arrive by?
        JLabel arriveByLabel = new JLabel("Arrive by?:");
        pane.add(arriveByLabel);
        arriveByCheckBox = new JCheckBox("arrive by");
        pane.add(arriveByCheckBox);

        // row: boarding penalty
        JLabel boardPenaltyLabel = new JLabel("Boarding penalty (min):");
        pane.add(boardPenaltyLabel);
        boardingPenaltyField = new JTextField("5");
        pane.add(boardingPenaltyField);

        // row: max walk
        JLabel maxWalkLabel = new JLabel("Maximum walk (meters):");
        pane.add(maxWalkLabel);
        maxWalkField = new JTextField("5000");
        pane.add(maxWalkField);

        // row: walk speed
        JLabel walkSpeedLabel = new JLabel("Walk speed (m/s):");
        pane.add(walkSpeedLabel);
        walkSpeed = new JTextField("1.33");
        pane.add(walkSpeed);

        // row: bike speed
        JLabel bikeSpeedLabel = new JLabel("Bike speed (m/s):");
        pane.add(bikeSpeedLabel);
        bikeSpeed = new JTextField("5.0");
        pane.add(bikeSpeed);

        // row: heuristic weight
        JLabel heuristicWeightLabel = new JLabel("Heuristic weight:");
        pane.add(heuristicWeightLabel);
        heuristicWeight = new JTextField("1.0");
        pane.add(heuristicWeight);

        // row: soft walk?
        JLabel softWalkLimitLabel = new JLabel("Soft walk-limit?:");
        pane.add(softWalkLimitLabel);
        softWalkLimiting = new JCheckBox("soft walk-limiting");
        pane.add(softWalkLimiting);

        // row: soft walk-limit penalty
        JLabel softWalkLimitPenaltyLabel = new JLabel("Soft walk-limiting penalty:");
        pane.add(softWalkLimitPenaltyLabel);
        softWalkPenalty = new JTextField("60.0");
        pane.add(softWalkPenalty);

        // row: soft walk-limit overage
        JLabel softWalkLimitOverageLabel = new JLabel("Soft walk-limiting overage:");
        pane.add(softWalkLimitOverageLabel);
        softWalkOverageRate = new JTextField("5.0");
        pane.add(softWalkOverageRate);

        // row: nPaths
        JLabel nPathsLabel = new JLabel("nPaths:");
        pane.add(nPathsLabel);
        nPaths = new JTextField("1");
        pane.add(nPaths);

        // viz preferences
        ItemListener onChangeVizPrefs = new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                showGraph.setShowTransit(showTransitCheckbox.isSelected());
                showGraph.setShowStreets(showStreetsCheckbox.isSelected());
                showGraph.setShowMultistateVertices(showMultistateVerticesCheckbox.isSelected());
                showGraph.setShowHightlights(showHighlightedCheckbox.isSelected());
                showGraph.setShowSPT(showSPTCheckbox.isSelected());
                showGraph.redraw();
            }
        };
        showTransitCheckbox = new JCheckBox("show transit");
        showTransitCheckbox.setSelected(true);
        showTransitCheckbox.addItemListener(onChangeVizPrefs);
        pane.add(showTransitCheckbox);
        showStreetsCheckbox = new JCheckBox("show streets");
        showStreetsCheckbox.setSelected(true);
        showStreetsCheckbox.addItemListener(onChangeVizPrefs);
        pane.add(showStreetsCheckbox);
        showHighlightedCheckbox = new JCheckBox("show highlighted");
        showHighlightedCheckbox.setSelected(true);
        showHighlightedCheckbox.addItemListener(onChangeVizPrefs);
        pane.add(showHighlightedCheckbox);
        showSPTCheckbox = new JCheckBox("show SPT");
        showSPTCheckbox.setSelected(true);
        showSPTCheckbox.addItemListener(onChangeVizPrefs);
        pane.add(showSPTCheckbox);
        showMultistateVerticesCheckbox = new JCheckBox("show multistate vertices");
        showMultistateVerticesCheckbox.setSelected(true);
        showMultistateVerticesCheckbox.addItemListener(onChangeVizPrefs);
        pane.add(showMultistateVerticesCheckbox);

        // GridLayout does not support empty cells, so a dummy label is used to fix the layout.
        JLabel dummyLabel2 = new JLabel("");
        pane.add(dummyLabel2);

        // row: SPT flattening
        JLabel sptFlatteningLabel = new JLabel("SPT flattening:");
        pane.add(sptFlatteningLabel);
        sptFlattening = new JTextField("0.3");
        pane.add(sptFlattening);

        // row: SPT thickness
        JLabel sptThicknessLabel = new JLabel("SPT thickness:");
        pane.add(sptThicknessLabel);
        sptThickness = new JTextField("0.1");
        pane.add(sptThickness);

        // radio buttons: optimize type
        JLabel optimizeTypeLabel = new JLabel("Optimize type:");
        pane.add(optimizeTypeLabel);

        opQuick = new JRadioButton("Quick");
        opQuick.setSelected(true);
        opSafe = new JRadioButton("Safe");
        opFlat = new JRadioButton("Flat");
        opGreenways = new JRadioButton("Greenways");

        optimizeTypeGrp = new ButtonGroup();
        optimizeTypeGrp.add(opQuick);
        optimizeTypeGrp.add(opSafe);
        optimizeTypeGrp.add(opFlat);
        optimizeTypeGrp.add(opGreenways);

        JPanel optimizeTypePane = new JPanel();
        optimizeTypePane.add(opQuick);
        optimizeTypePane.add(opSafe);
        optimizeTypePane.add(opFlat);
        optimizeTypePane.add(opGreenways);

        pane.add(optimizeTypePane);

        return pane;
    }
	
	OptimizeType getSelectedOptimizeType(){
		if(opQuick.isSelected()){
			return OptimizeType.QUICK;
		}
		if(opSafe.isSelected()){
			return OptimizeType.SAFE;
		}
		if(opFlat.isSelected()){
			return OptimizeType.FLAT;
		}
		if(opGreenways.isSelected()){
			return OptimizeType.GREENWAYS;
		}
		return OptimizeType.QUICK;
	}
	
    
    protected JComponent makeTextPanel(String text) {
        JPanel panel = new JPanel(false);
        JLabel filler = new JLabel(text);
        filler.setHorizontalAlignment(JLabel.CENTER);
        panel.setLayout(new GridLayout(1, 1));
        panel.add(filler);
        return panel;
    }

	private void initRightPanel(Container pane) {
		/* right panel holds trip pattern and stop metadata */
        JPanel rightPanel = new JPanel();
        rightPanel.setLayout(new BorderLayout());
        pane.add(rightPanel, BorderLayout.LINE_END);

        JTabbedPane rightPanelTabs = new JTabbedPane();

        rightPanel.add(rightPanelTabs, BorderLayout.LINE_END);
        
        // a place to print out the details of a path
        pathStates = new JList();
        JScrollPane stScrollPane = new JScrollPane(pathStates);
        stScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        rightPanelTabs.addTab("path states", stScrollPane);
        
        // when you select a path component state, it prints the backedge's metadata
        pathStates.addListSelectionListener(new ListSelectionListener(){
	        @Override
	        public void valueChanged(ListSelectionEvent e) {
	        	outgoingEdges.clearSelection();
	        	incomingEdges.clearSelection();
		
	        	@SuppressWarnings("unchecked")
				JList theList = (JList)e.getSource();
	        	State st = (State)theList.getSelectedValue();
	        	Edge edge = st.getBackEdge();
	        	reactToEdgeSelection( edge, false );
	        }
        });
         

        metadataList = new JList();
        metadataModel = new DefaultListModel();
        metadataList.setModel(metadataModel);
        JScrollPane mdScrollPane = new JScrollPane(metadataList);
        mdScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        rightPanelTabs.addTab("metadata", mdScrollPane);

        // This is where matched annotations from an annotation search go
        annotationMatches = new JList();
        annotationMatches.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                @SuppressWarnings("unchecked")
				JList theList = (JList) e.getSource();
                
                GraphBuilderAnnotation anno = theList.getSelectedValue();
                if (anno == null)
                    return;
                showGraph.drawAnotation(anno);
            }
        });

        annotationMatchesModel = new DefaultListModel();
        annotationMatches.setModel(annotationMatchesModel);
        JScrollPane amScrollPane = new JScrollPane(annotationMatches);
        amScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        rightPanelTabs.addTab("annotations", amScrollPane);

        Dimension size = new Dimension(200, 1600);

        amScrollPane.setMaximumSize(size);
        amScrollPane.setPreferredSize(size);
        stScrollPane.setMaximumSize(size);
        stScrollPane.setPreferredSize(size);
        mdScrollPane.setMaximumSize(size);
        mdScrollPane.setPreferredSize(size);
        rightPanelTabs.setMaximumSize(size);
        rightPanel.setMaximumSize(size);
	}

	private void initControlButtons() {
		/* buttons at bottom */
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(0, 3));
        leftPanel.add(buttonPanel, BorderLayout.PAGE_END);

        JButton zoomDefaultButton = new JButton("Zoom to default");
        zoomDefaultButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showGraph.zoomToDefault();
            }
        });
        buttonPanel.add(zoomDefaultButton);

        final JFrame frame = this;

        JButton zoomToNodeButton = new JButton("Zoom to node");
        zoomToNodeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String nodeName = (String) JOptionPane.showInputDialog(frame, "Node id",
                        JOptionPane.PLAIN_MESSAGE);
                Vertex v = getGraph().getVertex(nodeName);
                if (v == null) {
                    System.out.println("no such node " + nodeName);
                } else {
                    showGraph.zoomToVertex(v);
                }
            }
        });
        buttonPanel.add(zoomToNodeButton);

        JButton zoomToLocationButton = new JButton("Zoom to location");
        zoomToLocationButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String result = JOptionPane.showInputDialog("Enter the location (lat lon)");
                if (result == null || result.length() == 0)
                    return;
                String[] tokens = result.split("[\\s,]+");
                double lat = Double.parseDouble(tokens[0]);
                double lon = Double.parseDouble(tokens[1]);
                Coordinate c = new Coordinate(lon, lat);
                showGraph.zoomToLocation(c);
            }
        });
        buttonPanel.add(zoomToLocationButton);

        JButton zoomOutButton = new JButton("Zoom out");
        zoomOutButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showGraph.zoomOut();
            }
        });
        buttonPanel.add(zoomOutButton);

        JButton routeButton2 = new JButton("Route");
        routeButton2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // String initialFrom = "";
                // Object selected = nearbyVertices.getSelectedValue();
                // if (selected != null) {
                // initialFrom = selected.toString();
                // }
                // RouteDialog dlg = new RouteDialog(frame, initialFrom); // modal
                String from = sourceVertex.getText();
                String to = sinkVertex.getText();
                route(from, to);
            }
        });
        buttonPanel.add(routeButton2);

        JButton findButton = new JButton("Find node");
        findButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String nodeName = (String) JOptionPane.showInputDialog(frame, "Node id",
                        JOptionPane.PLAIN_MESSAGE);
                Vertex v = getGraph().getVertex(nodeName);
                if (v == null) {
                    System.out.println("no such node " + nodeName);
                } else {
                    showGraph.highlightVertex(v);
                    ArrayList l = new ArrayList();
                    l.add(v);
                    verticesSelected(l);
                }
            }
        });
        buttonPanel.add(findButton);

        JButton findEdgeButton = new JButton("Find edge");
        findEdgeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String edgeName = (String) JOptionPane.showInputDialog(frame, "Edge name like",
                        JOptionPane.PLAIN_MESSAGE);
                for (Vertex gv : getGraph().getVertices()) {
                    for (Edge edge : gv.getOutgoing()) {
                        if (edge.getName() != null && edge.getName().contains(edgeName)) {
                            showGraph.highlightVertex(gv);
                            ArrayList l = new ArrayList();
                            l.add(gv);
                            verticesSelected(l);
                        }
                    }
                }
            }
        });
        buttonPanel.add(findEdgeButton);

        JButton checkButton = new JButton("Check graph");
        checkButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                checkGraph();
            }
        });
        buttonPanel.add(checkButton);

        JButton traceButton = new JButton("Trace");
        traceButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                trace();
            }
        });
        buttonPanel.add(traceButton);

        // annotation search button
        JButton annotationButton = new JButton("Find annotations");
        annotationButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                findAnnotation();
            }
        });
        buttonPanel.add(annotationButton);

        JButton findEdgeByIdButton = new JButton("Find edge ID");
        findEdgeByIdButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String edgeIdStr = (String) JOptionPane.showInputDialog(frame, "Edge ID",
                        JOptionPane.PLAIN_MESSAGE);
                Integer edgeId = Integer.parseInt(edgeIdStr);
                Edge edge = getGraph().getEdgeById(edgeId);
                if (edge != null) {
                    showGraph.highlightEdge(edge);
                    showGraph.highlightVertex(edge.getFromVertex());
                } else {
                    System.out.println("Found no edge with ID " + edgeIdStr);
                }
            }
        });
        buttonPanel.add(findEdgeByIdButton);
        
        JButton snapButton = new JButton("Snap location");
        snapButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                LOG.error("StreetIndex.getClosestPointOnStreet no longer exists.");
            }
        });
        buttonPanel.add(snapButton);
	}
	
		
	    private void getMetadata(Object selected) {
	        Class c = selected.getClass();
	        Field[] fields;
	        while (c != null && c != Object.class) {
	            metadataModel.addElement("Class:" + c);
	            fields = c.getDeclaredFields();
	            for (int i = 0; i < fields.length; i++) {
	                Field field = fields[i];
	                int modifiers = field.getModifiers();
	                if ((modifiers & Modifier.STATIC) != 0) {
	                    continue;
	                }
	                field.setAccessible(true);
	                String name = field.getName();
	
	                String value = "(unknown -- see console for stack trace)";
	                try {
	                    value = "" + field.get(selected);
	                } catch (IllegalArgumentException e1) {
						LOG.error("IllegalArgumentException", e1);
	                } catch (IllegalAccessException e1) {
	                    LOG.error("IllegalAccessException", e1);
	                }
	                metadataModel.addElement(name + ": " + value);
	            }
	            c = c.getSuperclass();
	        }
	    }
		
		private void reactToEdgeSelection(Edge selected, boolean outgoing){
	        if (selected == null) {
	            return;
	        }
	        showGraph.highlightEdge(selected);
	
	        /* for turns, highlight the outgoing street's ends */
	        if (selected instanceof StreetEdge) {
	            List vertices = new ArrayList();
	            List edges = new ArrayList();
	            Vertex tov = selected.getToVertex();
	            for (Edge og : tov.getOutgoing()) {
	                if (og instanceof StreetEdge) {
	                    edges.add(og);
	                    vertices.add(og.getToVertex());
	                    break;
	                }
	            }
	            Vertex fromv = selected.getFromVertex();
	            for (Edge ic : fromv.getIncoming()) {
	                if (ic instanceof StreetEdge) {
	                    edges.add(ic);
	                    vertices.add(ic.getFromVertex());
	                    break;
	                }
	            }
	            // showGraph.setHighlightedVertices(vertices);
	            showGraph.setHighlightedEdges(edges);
	        }
	
	        /* add the connected vertices to the list of vertices */
	        VertexList nearbyModel = (VertexList) nearbyVertices.getModel();
	        List vertices = nearbyModel.selected;
	
	        Vertex v;
	        if (outgoing) {
	            v = selected.getToVertex();
	        } else {
	            v = selected.getFromVertex();
	        }
	        if (!vertices.contains(v)) {
	            vertices.add(v);
	            nearbyModel = new VertexList(vertices);
	            nearbyVertices.setModel(nearbyModel); // this should just be an event, but for
	                                                  // some reason, JList doesn't implement
	                                                  // the right event.
	        }
	
	        /* set up metadata tab */
	        metadataModel.clear();
	        getMetadata(selected);
	        // fromv
	        Vertex fromv = selected.getFromVertex();
	        getMetadata(fromv);
	        if (selected instanceof StreetEdge) {
	            //TODO ElevationProfileSegment do not exist anymore
	            //getMetadata(((StreetEdge) selected).getElevationProfileSegment());
	        }
	        metadataList.revalidate();
	
		}

	private void initVertexInfoSubpanel() {
        JPanel vertexDataPanel = new JPanel();
        vertexDataPanel.setLayout(new BoxLayout(vertexDataPanel, BoxLayout.PAGE_AXIS));
        vertexDataPanel.setPreferredSize(new Dimension(300, 600));
        leftPanel.add(vertexDataPanel, BorderLayout.CENTER);

        // nearby vertices
        JLabel nvLabel = new JLabel("Vertices");
        vertexDataPanel.add(nvLabel);
        nearbyVertices = new JList();
        nearbyVertices.setVisibleRowCount(4);
        JScrollPane nvScrollPane = new JScrollPane(nearbyVertices);
        vertexDataPanel.add(nvScrollPane);
        nearbyVertices.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                outgoingEdges.removeAll();
                incomingEdges.removeAll();
                DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue();
                if (selected != null) {
                    Vertex nowSelected = selected.vertex;
                    showGraph.highlightVertex(nowSelected);
                    outgoingEdges.setModel(new EdgeListModel(nowSelected.getOutgoing()));
                    incomingEdges.setModel(new EdgeListModel(nowSelected.getIncoming()));
                }
            }
        });
        
        // listener useful for both incoming and outgoing edge list panes
        // when a different edge is selected, change up the pattern pane and list of nearby nodes
        ListSelectionListener edgeChanged = new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {

                @SuppressWarnings("unchecked")
				JList edgeList = (JList) e.getSource();
                
                Edge selected = (Edge) edgeList.getSelectedValue();
                
                boolean outgoing = (edgeList==outgoingEdges);
                reactToEdgeSelection( selected, outgoing );
            }

        };

        // outgoing edges
        JLabel ogeLabel = new JLabel("Outgoing edges");
        vertexDataPanel.add(ogeLabel);
        outgoingEdges = new JList();
        outgoingEdges.setVisibleRowCount(4);
        JScrollPane ogeScrollPane = new JScrollPane(outgoingEdges);
        vertexDataPanel.add(ogeScrollPane);
        outgoingEdges.addListSelectionListener(edgeChanged);

        // incoming edges
        JLabel iceLabel = new JLabel("Incoming edges");
        vertexDataPanel.add(iceLabel);
        incomingEdges = new JList();
        JScrollPane iceScrollPane = new JScrollPane(incomingEdges);
        vertexDataPanel.add(iceScrollPane);
        incomingEdges.addListSelectionListener(edgeChanged);

        // paths list
        JLabel pathsLabel = new JLabel("Paths");
        vertexDataPanel.add(pathsLabel);
        pathsList = new JList();
        
        popup = new JPopupMenu();
        JMenuItem compareMenuItem = new JMenuItem("compare");
        compareMenuItem.addActionListener(new OnPopupMenuClickListener());
        popup.add(compareMenuItem);
        
        // make paths list right-clickable
        pathsList.addMouseListener(new MouseListener(){
			@Override
			public void mouseClicked(MouseEvent e) {
				if( SwingUtilities.isRightMouseButton(e) ){
					@SuppressWarnings("unchecked")
					JList list = (JList)e.getSource();
		            int row = list.locationToIndex(e.getPoint());
		            list.setSelectedIndex(row);
		            
		            popup.show(list, e.getX(), e.getY());
				}
			}

			@Override
			public void mousePressed(MouseEvent e) {}

			@Override
			public void mouseReleased(MouseEvent e) {}

			@Override
			public void mouseEntered(MouseEvent e) {}

			@Override
			public void mouseExited(MouseEvent e) {}
        });
        pathsList.addListSelectionListener(new ListSelectionListener(){
			@Override
			public void valueChanged(ListSelectionEvent ev) {
				
				PathPrinter pp = ((PathPrinter) pathsList.getSelectedValue());
				if(pp==null){
					return;
				}
				GraphPath path = pp.gp;
				
				DefaultListModel pathModel = new DefaultListModel();
				for( State st : path.states ){
					pathModel.addElement( st );
				}
				pathStates.setModel( pathModel );
				
				showGraph.highlightGraphPath(path);		
			}
	
        });
        JScrollPane pathsScrollPane = new JScrollPane(pathsList);
        vertexDataPanel.add(pathsScrollPane);
	}

	private void initRoutingSubpanel() {
		/* ROUTING SUBPANEL */
        JPanel routingPanel = new JPanel();
        routingPanel.setLayout(new GridLayout(0, 2));
        leftPanel.add(routingPanel, BorderLayout.NORTH);

        // row: source vertex
        JButton setSourceVertexButton = new JButton("set source");
        setSourceVertexButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Object selected = nearbyVertices.getSelectedValue();
                if (selected != null) {
                    sourceVertex.setText(selected.toString());
                }
            }
        });
        routingPanel.add(setSourceVertexButton);
        sourceVertex = new JTextField();
        routingPanel.add(sourceVertex);

        // row: sink vertex
        JButton setSinkVertexButton = new JButton("set sink");
        setSinkVertexButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Object selected = nearbyVertices.getSelectedValue();
                if (selected != null) {
                    sinkVertex.setText(selected.toString());
                }
            }
        });
        routingPanel.add(setSinkVertexButton);
        sinkVertex = new JTextField();
        routingPanel.add(sinkVertex);

        // row: set date
        JButton resetSearchDateButton = new JButton("now ->");
        resetSearchDateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                searchDate.setText(dateFormat.format(new Date()));
            }
        });
        routingPanel.add(resetSearchDateButton);
        searchDate = new JTextField();
        searchDate.setText(dateFormat.format(new Date()));
        routingPanel.add(searchDate);



        // row: launch, continue, and clear path search
        JButton routeButton = new JButton("path search");
        routeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String from = sourceVertex.getText();
                String to = sinkVertex.getText();
                route(from, to);
            }
        });
        routingPanel.add(routeButton);
        JButton continueButton = new JButton("continue");
        continueButton.addActionListener(new ActionListener() {
	        public void actionPerformed(ActionEvent e) {
	        	//TODO continue search
	        }
        });
        routingPanel.add(continueButton);
        JButton clearRouteButton = new JButton("clear path");
        clearRouteButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showGraph.highlightGraphPath(null);
                showGraph.clearHighlights();
                showGraph.resetSPT();
            }
        });
        routingPanel.add(clearRouteButton);
        
        //label: search time elapsed
        searchTimeElapsedLabel = new JLabel("search time elapsed:");
        routingPanel.add(searchTimeElapsedLabel);
        
        //option: don't use graphical callback. useful for doing a quick profile
        dontUseGraphicalCallbackCheckBox = new JCheckBox("no graphics");
        routingPanel.add(dontUseGraphicalCallbackCheckBox);
	}

    protected void trace() {
        DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue();
        if (selected == null) {
            return;
        }
        Vertex v = selected.vertex;

        if (tracingVertex != v) {
            tracingVertex = v;
            closed = new HashSet();
            open = new HashSet();
            open.add(v);
            seen = new HashSet();
        }
        HashSet newOpen = new HashSet();
        for (Vertex v2 : open) {
            closed.add(v2);
            for (Edge e : v2.getOutgoing()) {
                Vertex target = e.getToVertex();
                if (closed.contains(target)) {
                    continue;
                }
                newOpen.add(target);
            }
        }
        seen.addAll(newOpen);
        open = newOpen;
        showGraph.setHighlightedVertices(seen);
    }

    protected void traceOld() {
        HashSet seenVertices = new HashSet();
        DisplayVertex selected = (DisplayVertex) nearbyVertices.getSelectedValue();
        if (selected == null) {
            System.out.println("no vertex selected");
            return;
        }
        Vertex v = selected.vertex;
        System.out.println("initial vertex: " + v);
        Queue toExplore = new LinkedList();
        toExplore.add(v);
        seenVertices.add(v);
        while (!toExplore.isEmpty()) {
            Vertex src = toExplore.poll();
            for (Edge e : src.getOutgoing()) {
                Vertex tov = e.getToVertex();
                if (!seenVertices.contains(tov)) {
                    seenVertices.add(tov);
                    toExplore.add(tov);
                }
            }
        }
        showGraph.setHighlightedVertices(seenVertices);
    }

    protected void checkGraph() {

        HashSet seenVertices = new HashSet();
        Collection allVertices = getGraph().getVertices();
        Vertex v = allVertices.iterator().next();
        System.out.println("initial vertex: " + v);
        Queue toExplore = new LinkedList();
        toExplore.add(v);
        seenVertices.add(v);
        while (!toExplore.isEmpty()) {
            Vertex src = toExplore.poll();
            for (Edge e : src.getOutgoing()) {
                Vertex tov = e.getToVertex();
                if (!seenVertices.contains(tov)) {
                    seenVertices.add(tov);
                    toExplore.add(tov);
                }
            }
        }

        System.out.println("After investigation, visited " + seenVertices.size() + " of "
                + allVertices.size());

        /* now, let's find an unvisited vertex */
        for (Vertex u : allVertices) {
            if (!seenVertices.contains(u)) {
                System.out.println("unvisited vertex" + u);
                break;
            }
        }
    }

    protected void route(String from, String to) {
        Date when;
        // Year + 1900
        try {
            when = dateFormat.parse(searchDate.getText());
        } catch (ParseException e) {
            searchDate.setText("Format: " + dateFormat.toPattern());
            return;
        }
        TraverseModeSet modeSet = new TraverseModeSet();
        modeSet.setWalk(walkCheckBox.isSelected());
        modeSet.setBicycle(bikeCheckBox.isSelected());
        modeSet.setFerry(ferryCheckBox.isSelected());
        modeSet.setRail(trainCheckBox.isSelected());
        modeSet.setTram(trainCheckBox.isSelected());
        modeSet.setSubway(trainCheckBox.isSelected());
        modeSet.setFunicular(trainCheckBox.isSelected());
        modeSet.setGondola(trainCheckBox.isSelected());
        modeSet.setBus(busCheckBox.isSelected());
        modeSet.setCableCar(busCheckBox.isSelected());
        modeSet.setCar(carCheckBox.isSelected());
        // must set generic transit mode last, and only when it is checked
        // otherwise 'false' will clear trainish and busish
        if (transitCheckBox.isSelected())
            modeSet.setTransit(true);
        RoutingRequest options = new RoutingRequest(modeSet);
        options.setArriveBy(arriveByCheckBox.isSelected());
        options.setWalkBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60); // override low 2-4 minute values
        // TODO LG Add ui element for bike board cost (for now bike = 2 * walk)
        options.setBikeBoardCost(Integer.parseInt(boardingPenaltyField.getText()) * 60 * 2);
        // there should be a ui element for walk distance and optimize type
        options.setOptimize( getSelectedOptimizeType() );
        options.setMaxWalkDistance(Integer.parseInt(maxWalkField.getText()));
        options.setDateTime(when);
        options.setFromString(from);
        options.setToString(to);
        options.walkSpeed = Float.parseFloat(walkSpeed.getText());
        options.bikeSpeed = Float.parseFloat(bikeSpeed.getText());
        options.softWalkLimiting = ( softWalkLimiting.isSelected() );
        options.softWalkPenalty = (Float.parseFloat(softWalkPenalty.getText()));
        options.softWalkOverageRate = (Float.parseFloat(this.softWalkOverageRate.getText()));
        options.numItineraries = 1;
        System.out.println("--------");
        System.out.println("Path from " + from + " to " + to + " at " + when);
        System.out.println("\tModes: " + modeSet);
        System.out.println("\tOptions: " + options);
        
        options.numItineraries = ( Integer.parseInt( this.nPaths.getText() ) );
        
        // apply callback if the options call for it
        // if( dontUseGraphicalCallbackCheckBox.isSelected() ){
        // TODO perhaps avoid using a GraphPathFinder and go one level down the call chain directly to a GenericAStar
        // TODO perhaps instead of giving the pathservice a callback, we can just put the visitor in the routing request
        GraphPathFinder finder = new GraphPathFinder(router);

        long t0 = System.currentTimeMillis();
        // TODO: check options properly intialized (AMB)
        List paths = finder.graphPathFinderEntryPoint(options);
        long dt = System.currentTimeMillis() - t0;
        searchTimeElapsedLabel.setText( "search time elapsed: "+dt+"ms" );
        
        // grab the spt from the visitor
        // TODO somehow yank the SPT out of the depths of the call stack... but there multiple SPTs here.
        // This is why we should probably just use AStar directly.
        /*
        spt = vis.spt;
        showGraph.setSPT(spt);
        System.out.println( "got spt:"+spt );
        */

        if (paths == null) {
            System.out.println("no path");
            showGraph.highlightGraphPath(null);
            return;
        }
        
        // now's a convenient time to set graphical SPT weights
        showGraph.simpleSPT.setWeights();
                        
        showPathsInPanel(paths);
        
        // now's a good time to set showGraph's SPT drawing weights
        showGraph.setSPTFlattening( Float.parseFloat(sptFlattening.getText()) );
        showGraph.setSPTThickness( Float.parseFloat(sptThickness.getText()) );
        showGraph.redraw();
        
        options.cleanup();
    }
    
	private void showPathsInPanel(List paths) {
		// show paths in a list panel
		DefaultListModel data = new DefaultListModel();
		for(GraphPath gp : paths ){
			data.addElement( new PathPrinter(gp) );
		}
		pathsList.setModel(data);
	}

    protected void findAnnotation() {
        Set> gbaClasses = Sets.newHashSet();
        for (GraphBuilderAnnotation gba : graph.getBuilderAnnotations()) {
            gbaClasses.add(gba.getClass());
        }

        @SuppressWarnings("unchecked")
        Class variety = (Class) JOptionPane
                .showInputDialog(null, // parentComponent; TODO: set correctly
                        "Select the type of annotation to find", // question
                        "Select annotation", // title
                        JOptionPane.QUESTION_MESSAGE, // message type
                        null, // no icon
                        gbaClasses.toArray(), // options (built above)
                        StopUnlinked.class // default value
                );

        // User clicked cancel
        if (variety == null)
            return;

        // loop over the annotations and save the ones of the requested type
        annotationMatchesModel.clear();
        for (GraphBuilderAnnotation anno : graph.getBuilderAnnotations()) {
            if (variety.isInstance(anno)) {
                annotationMatchesModel.addElement(anno);
            }
        }

        System.out.println("Found " + annotationMatchesModel.getSize() + " annotations of type "
                + variety);

    }

    public void verticesSelected(final List selected) {
        // sort vertices by name
        Collections.sort(selected, new Comparator() {
            @Override
            public int compare(Vertex arg0, Vertex arg1) {
                return arg0.getLabel().compareTo(arg1.getLabel());
            }

        });
        ListModel data = new VertexList(selected);
        nearbyVertices.setModel(data);

        // pick out an intersection vertex and find the path
        // if the spt is already available
        Vertex target=null;
        for(Vertex vv : selected){
        	if( vv instanceof IntersectionVertex ){
        		target = vv;
        		break;
        	}
        }
        if(target!=null && spt!=null){
        	List paths = spt.getPaths(target,true);
        	showPathsInPanel( paths );
        }
    }

    public Graph getGraph() {
        return graph;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy