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

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

package org.opentripplanner.visualizer;

import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

import org.opentripplanner.graph_builder.annotation.GraphBuilderAnnotation;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.PathwayEdge;
import org.opentripplanner.routing.edgetype.PatternEdge;
import org.opentripplanner.routing.edgetype.SimpleTransfer;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.StreetTransitLink;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.edgetype.TransitBoardAlight;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
import org.opentripplanner.routing.vertextype.TransitStationStop;

import processing.core.PApplet;
import processing.core.PFont;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.index.strtree.STRtree;

/**
 * Processing applet to show a map of the graph. The user can: - Use mouse wheel to zoom (or right drag, or ctrl-drag) - Left drag to pan around the
 * map - Left click to send a list of nearby vertices to the associated VertexSelectionListener.
 */
public class ShowGraph extends PApplet implements MouseWheelListener {
	
	// how many edges to draw before checking whether we need to move on to the next frame
	private final int BLOCK_SIZE = 1000; 
	// how many edges to skip over (to ensure a sampling of edges throughout the visible area)
	private final long DECIMATE = 40;
	// 800 instead of 1000 msec, leaving 20% of the time for work other than drawing.
	private final int FRAME_TIME = 800 / FRAME_RATE;

    private static final int FRAME_RATE = 30;

    private static final long serialVersionUID = -8336165356756970127L;

    private static final boolean VIDEO = false;

    private static final String VIDEO_PATH = "/home/syncopate/pathimage/";

    private int videoFrameNumber = 0;

    Graph graph;

    STRtree vertexIndex;

    STRtree edgeIndex;

    Envelope modelOuterBounds;

    Envelope modelBounds = new Envelope();

    VertexSelectionListener selector;

    private ArrayList selectors;

    private List visibleVertices;

    private List visibleStreetEdges = new ArrayList(1000);

    private List visibleLinkEdges = new ArrayList(1000);

    private List visibleTransitEdges = new ArrayList(1000);

    private List highlightedVertices = new ArrayList(1000);

    private List highlightedEdges = new ArrayList(1000);

    // these queues are filled by a search in another thread, so must be threadsafe
    private Queue newHighlightedVertices = new LinkedBlockingQueue();

    private Queue newHighlightedEdges = new LinkedBlockingQueue();

    private Coordinate highlightedCoordinate;

    private Edge highlightedEdge;

    private GraphPath highlightedGraphPath;

    protected double mouseModelX;

    protected double mouseModelY;

    private Point startDrag = null;

    private int dragX, dragY;

    private boolean ctrlPressed = false;

    boolean drawFast = false;

    boolean drawStreetEdges = true;

    boolean drawTransitEdges = true;

    boolean drawLinkEdges = true;

    boolean drawStreetVertices = true;

    boolean drawTransitStopVertices = true;

    private static double lastLabelY;

    private static final DecimalFormat latFormatter = new DecimalFormat("00.0000°N ; 00.0000°S");

    private static final DecimalFormat lonFormatter = new DecimalFormat("000.0000°E ; 000.0000°W");

    private final SimpleDateFormat shortDateFormat = new SimpleDateFormat("HH:mm:ss z");

    /* Layer constants */
    static final int DRAW_MINIMAL = 0; // XY coordinates

    static final int DRAW_HIGHLIGHTED = 1;
    static final int DRAW_SPT = 2;
    static final int DRAW_VERTICES = 3;
    static final int DRAW_TRANSIT = 4;
    static final int DRAW_LINKS = 5;
    static final int DRAW_STREETS = 6;
    static final int DRAW_ALL = 7;
    static final int DRAW_PARTIAL = 8;

    private int drawLevel = DRAW_ALL;

    private int drawOffset = 0;
    private boolean drawHighlighted = true;
    public SimpleSPT simpleSPT = new SimpleSPT();
	private LinkedBlockingQueue newSPTEdges = new LinkedBlockingQueue();
	private boolean drawEdges = true;
	private LinkedBlockingQueue sptEdgeQueue;
	private boolean sptVisible = true;
	private float sptFlattening = 0.3f;
	private float sptThickness = 0.1f;
	private boolean drawMultistateVertices=true;
	private ShortestPathTree spt;
	
	class Trunk{
		public Edge edge;
		public Double trunkiness;

		Trunk(Edge edge, Double trunkiness){
			this.edge = edge;
			this.trunkiness = trunkiness;
		}
	}
	
	class SimpleSPT{
		private HashMap nodes;
		SPTNode root;

		SimpleSPT(){
			nodes = new HashMap();
		}

		public void add(State state) {
			// create simpleSPT entry
			SPTNode curNode = new SPTNode(state);
			SPTNode parentNode = this.nodes.get(state.getBackState());
			if(parentNode!=null){
				parentNode.children.add(curNode);
			} else {
				root = curNode;
			}
			curNode.parent = parentNode;
			this.nodes.put(state, curNode);
		}
		
		void setWeights(){
			if(root==null){
				return;
			}
			root.setWeight();
		}
		
		public void draw() {
			if(root==null){
				return;
			}
			
			HashMap vertexHeight = new HashMap();
			
			root.drawRecursive(0, vertexHeight);
		}
		
		public LinkedBlockingQueue getEdgeQueue() {
			LinkedBlockingQueue ret = new LinkedBlockingQueue();
			if(root!=null){
				root.addToEdgeQueue(ret);
			}
			return ret;
		}
	}

	class SPTNode{
		// this is a tool for the traverse visitor to build a very simple
		// shortest path tree, which we can use to come up with the trunkiness
		// of every SPT edge.

		State state;
		SPTNode parent;
		List children;
		double weight=0.0;
		public Integer height;

		SPTNode(State state){
			this.state = state;
			this.height = null;
			this.children = new ArrayList();
		}
		
		public void addToEdgeQueue(LinkedBlockingQueue ret) {
			ret.add(this);
			for( SPTNode child : children ){
				child.addToEdgeQueue(ret);
			}
		}

		public void drawRecursive(int height, HashMap vertexStatesEncountered) {
			colorMode(HSB);
			
			// get the number of states we've already drawn from this vertex
			Integer vertexHeight = vertexStatesEncountered.get(this.state.getVertex());
			if( vertexHeight == null ){
				vertexHeight = 0;
			}
			
			// if it's larger than the 'height' of the state we're about to draw, bump the state's visual height
			// up to the number of states it has to climb over
			if(vertexHeight>height){
				height = vertexHeight;
			}
			
			// increment the counter of the number of times we've encountered this vertex
			vertexStatesEncountered.put(this.state.getVertex(), vertexHeight+1);
			
			if(state.getBackEdge() != null){
				//stroke( colorRamp( (int)(state.getWeight()/10.0) ) );
				stroke( color((height*10)%255, 255, 255) );
				
				strokeWeight( (float) (sptThickness*Math.pow(weight,sptFlattening)) );
				drawEdge( state.getBackEdge() );
			}
	
			for( SPTNode child : children ){
				child.drawRecursive(height, vertexStatesEncountered);
			}

			colorMode(RGB);
		}

		public void draw(List colors) {
			colorMode(HSB);

			if(state.getBackEdge() != null){
				//stroke( colorRamp( (int)(state.getWeight()/10.0) ) );
				strokeWeight( (float) (sptThickness*Math.pow(weight,sptFlattening)) );
				
				stroke( colors.get(this.height) );
				
				drawEdge( state.getBackEdge() );
			}
			
			colorMode(RGB);
		}
		
		private int colorRamp(int aa) {
			int NHUES = 6;
			int HUELEN = 256;
			int RAMPLEN = NHUES*HUELEN;
			int BRIGHTNESS = 220;

			aa = aa%RAMPLEN; //make sure aa fits within the color ramp
			int hueIndex = aa/HUELEN; //establish the hue
			int hue = hueIndex*(HUELEN/NHUES); //convert that to a hue value
			int saturation = HUELEN-aa%HUELEN;

			return color(hue,saturation,BRIGHTNESS);
		}
		
		public void setWeight() {
			weight = state.getWeight();
			for( SPTNode child : children ){
				child.setWeight();
				weight += child.weight;
			}
		}

		void addChild(SPTNode child){
			this.children.add( child );
		}

		public void setHeight(Integer height) {
			this.height = height;
		}
	}

    /*
     * Constructor. Call processing constructor, and register the listener to notify when the user selects vertices.
     */
    public ShowGraph(VertexSelectionListener selector, Graph graph) {
        super();
        this.graph = graph;
        this.spt = null;
        this.selector = selector;
        this.selectors = new ArrayList();        
    }

    /*
     * Setup Processing applet
     */
    public void setup() {
        size(getSize().width, getSize().height, P2D);

        /* Build spatial index of vertices and edges */
        buildSpatialIndex();

        /* Set model bounds to encompass all vertices in the index, and then some */
        modelBounds = (Envelope) (vertexIndex.getRoot().getBounds());
        modelBounds.expandBy(0.02);
        matchAspect();
        /* save this zoom level to allow returning to default later */
        modelOuterBounds = new Envelope(modelBounds);

        /* find and set up the appropriate font */
        String[] fonts = PFont.list();
        String[] preferredFonts = { "Mono", "Courier" };
        PFont font = null;
        for (String preferredFontName : preferredFonts) {
            for (String fontName : fonts) {
                if (fontName.contains(preferredFontName)) {
                    font = createFont(fontName, 16);
                    break;
                }
            }
            if (font != null) {
                break;
            }
        }
        textFont(font);
        textMode(SCREEN);
        addMouseWheelListener(this);
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                super.mouseMoved(e);
                Point p = e.getPoint();
                mouseModelX = toModelX(p.x);
                mouseModelY = toModelY(p.y);
            }
        });
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                matchAspect();
                drawLevel = DRAW_PARTIAL;
            }
        });
        frameRate(FRAME_RATE);
    }

    /*
     * Zoom in/out proportional to the number of clicks of the mouse wheel.
     */
    public void mouseWheelMoved(MouseWheelEvent e) {
        double f = e.getWheelRotation() * 0.2;
        zoom(f, e.getPoint());
    }

    /*
     * Zoom in/out. Translate the viewing window such that the place under the mouse pointer is a fixed point. If p is null, zoom around the center of
     * the viewport.
     */
    void zoom(double f, Point p) {
        double ex = modelBounds.getWidth() * f;
        double ey = modelBounds.getHeight() * f;
        modelBounds.expandBy(ex / 2, ey / 2);
        if (p != null) {
            // Note: Graphics Y coordinates increase down the screen, hence the opposite signs.
            double tx = ex * -((p.getX() / this.width) - 0.5);
            double ty = ey * +((p.getY() / this.height) - 0.5);
            modelBounds.translate(tx, ty);
        }
        // update the display
        drawLevel = DRAW_PARTIAL;
    }

    public void zoomToDefault() {
        modelBounds = new Envelope(modelOuterBounds);
        drawLevel = DRAW_ALL;
    }

    public void zoomOut() {
        modelBounds.expandBy(modelBounds.getWidth(), modelBounds.getHeight());
        drawLevel = DRAW_ALL;
    }

    public void zoomToLocation(Coordinate c) {
        Envelope e = new Envelope();
        e.expandToInclude(c);
        e.expandBy(0.002);
        modelBounds = e;
        matchAspect();
        drawLevel = DRAW_ALL;
    }

    public void zoomToVertex(Vertex v) {
        Envelope e = new Envelope();
        e.expandToInclude(v.getCoordinate());
        e.expandBy(0.002);
        modelBounds = e;
        drawLevel = DRAW_ALL;
    }

    /**
     * Zoom to an envelope. Used for annotation zoom.
     * 
     * @author mattwigway
     */
    public void zoomToEnvelope(Envelope e) {
        modelBounds = e;
        matchAspect();
        drawLevel = DRAW_ALL;
    }

    void matchAspect() {
        /* Basic sinusoidal projection of lat/lon data to square pixels */
        double yCenter = modelBounds.centre().y;
        float xScale = cos(radians((float) yCenter));
        double newX = modelBounds.getHeight() * (1 / xScale)
                * ((float) this.getWidth() / this.getHeight());
        modelBounds.expandBy((newX - modelBounds.getWidth()) / 2f, 0);
    }

    /*
     * Iterate through all vertices and their (outgoing) edges. If they are of 'interesting' types, 
     * add them to the corresponding spatial index.
     */
    public synchronized void buildSpatialIndex() {
        vertexIndex = new STRtree();
        edgeIndex = new STRtree();
        Envelope env;
        
        // int xminx, xmax, ymin, ymax;
        for (Vertex v : graph.getVertices()) {
            Coordinate c = v.getCoordinate();
            env = new Envelope(c);
            vertexIndex.insert(env, v);
            for (Edge e : v.getOutgoing()) {
                if (e.getGeometry() == null)
                    continue;
                if (e instanceof PatternEdge || e instanceof StreetTransitLink
                        || e instanceof StreetEdge || e instanceof PathwayEdge
                        || e instanceof SimpleTransfer) {
                    env = e.getGeometry().getEnvelopeInternal();
                    edgeIndex.insert(env, e);
                }
            }
        }
        vertexIndex.build();
        edgeIndex.build();
    }

    @SuppressWarnings("unchecked")
    private synchronized void findVisibleElements() {
        visibleVertices = (List) vertexIndex.query(modelBounds);
        visibleStreetEdges.clear();
        visibleLinkEdges.clear();
        visibleTransitEdges.clear();
        for (Edge de : (Iterable) edgeIndex.query(modelBounds)) {
            if (de instanceof PatternEdge) {
                visibleTransitEdges.add(de);
            }
            else if (de instanceof PathwayEdge || de instanceof StreetTransitLink || de instanceof SimpleTransfer) {
                visibleLinkEdges.add(de);
            }
            else if (de instanceof StreetEdge) {
                visibleStreetEdges.add(de);
            }
        }
    }

    private int drawEdge(Edge e) {
        if (e.getGeometry() == null)
            return 0; // do not attempt to draw geometry-less edges
        Coordinate[] coords = e.getGeometry().getCoordinates();
        beginShape();
        for (int i = 0; i < coords.length; i++)
            vertex((float) toScreenX(coords[i].x), (float) toScreenY(coords[i].y));
        endShape();
        return coords.length; // should be used to count segments, not edges drawn
    }

    /* use endpoints instead of geometry for quick updating */
    private void drawEdgeFast(Edge e) {
        Coordinate[] coords = e.getGeometry().getCoordinates();
        Coordinate c0 = coords[0];
        Coordinate c1 = coords[coords.length - 1];
        line((float) toScreenX(c0.x), (float) toScreenY(c0.y), (float) toScreenX(c1.x),
                (float) toScreenY(c1.y));
    }

    private void drawGraphPath(GraphPath gp) {
        // draw edges in different colors according to mode
        for (State s : gp.states) {
            TraverseMode mode = s.getBackMode();

            Edge e = s.getBackEdge();
            if (e == null)
                continue;

            if (mode != null && mode.isTransit()) {
                stroke(200, 050, 000);
                strokeWeight(6);
                drawEdge(e);
            }
            if (e instanceof StreetEdge) {
                StreetTraversalPermission stp = ((StreetEdge) e).getPermission();
                if (stp == StreetTraversalPermission.PEDESTRIAN) {
                    stroke(000, 200, 000);
                    strokeWeight(6);
                    drawEdge(e);
                } else if (stp == StreetTraversalPermission.BICYCLE) {
                    stroke(000, 000, 200);
                    strokeWeight(6);
                    drawEdge(e);
                } else if (stp == StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE) {
                    stroke(000, 200, 200);
                    strokeWeight(6);
                    drawEdge(e);
                } else if (stp == StreetTraversalPermission.ALL) {
                    stroke(200, 200, 200);
                    strokeWeight(6);
                    drawEdge(e);
                } else {
                    stroke(64, 64, 64);
                    strokeWeight(6);
                    drawEdge(e);
                }
            }
        }
        // mark key vertices
        lastLabelY = -999;
        labelState(gp.states.getFirst(), "begin");
        for (State s : gp.states) {
            Edge e = s.getBackEdge();
            if (e instanceof TransitBoardAlight) {
                if (((TransitBoardAlight) e).boarding) {
                    labelState(s, "board");
                } else {
                    labelState(s, "alight");
                }
            }
        }
        labelState(gp.states.getLast(), "end");

        if (VIDEO) {
            // freeze on final path for a few frames
            for (int i = 0; i < 10; i++)
                saveVideoFrame();
            resetVideoFrameNumber();
        }
    }

    private void labelState(State s, String str) {
        fill(240, 240, 240);
        Vertex v = s.getVertex();
        drawVertex(v, 8);
        str += " " + shortDateFormat.format(new Date(s.getTimeSeconds() * 1000));
        str += " [" + (int) s.getWeight() + "]";
        double x = toScreenX(v.getX()) + 10;
        double y = toScreenY(v.getY());
        double dy = y - lastLabelY;
        if (dy == 0) {
            y = lastLabelY + 20;
        } else if (Math.abs(dy) < 20) {
            y = lastLabelY + Math.signum(dy) * 20;
        }
        text(str, (float) x, (float) y);
        lastLabelY = y;
    }

    private void drawCoordinate(Coordinate c, double r) {
        noStroke();
        ellipse(toScreenX(c.x), toScreenY(c.y), r, r);
    }

    private void drawVertex(Vertex v, double r) {
        drawCoordinate(v.getCoordinate(), r);
    }

    public synchronized void draw() {
    	smooth();
        int startMillis = millis();
        if (drawLevel == DRAW_PARTIAL) {
            drawPartial(startMillis);
        } else if (drawLevel == DRAW_ALL) {
            boolean finished = drawAll(startMillis);
            if(!finished){
            	return;
            }
        } else if (drawLevel == DRAW_LINKS) {
            boolean finished = drawLinks(startMillis);
            if(!finished){
            	return;
            }
        } else if (drawLevel == DRAW_TRANSIT) {
            boolean finished = drawTransit(startMillis);
            if(!finished){
            	return;
            }
        } else if (drawLevel == DRAW_VERTICES) {
            drawVertices();
        } else if (drawLevel == DRAW_SPT){
        	boolean finished = drawSPT(startMillis);
        	if(!finished){
        		return;
        	}
        } else if (drawLevel == DRAW_HIGHLIGHTED){
        	drawHighlighted();
        } else if (drawLevel == DRAW_MINIMAL) {
        	if (!newHighlightedEdges.isEmpty())
        		handleNewHighlights();
        	drawNewEdges();
        	drawCoords();
        }
        drawOffset = 0;
        if (drawLevel > DRAW_MINIMAL)
            drawLevel -= 1; // move to next layer
    }
    
	private boolean drawSPT(int startMillis) {
		if(!sptVisible){
			return true;
		}
		
		noFill();
//		if(sptEdgeQueue==null){
//			sptEdgeQueue = simpleSPT.getEdgeQueue();
//		}
		
//		colorOverlappingBranches(sptEdgeQueue);
//		
//		int i=0;
//		while(!sptEdgeQueue.isEmpty()){
//			SPTNode node = sptEdgeQueue.poll();
//			i++;
//			node.draw(sptBranchColors);
//    		if ((i%BLOCK_SIZE==0) && (millis() - startMillis > FRAME_TIME))
//    			return false;
//    	}
//    	sptEdgeQueue=null;
		
		simpleSPT.draw();
		
    	return true;
	}

	private void colorOverlappingBranches(LinkedBlockingQueue queue) {
    	HashMap stateHeight = new HashMap();
    	
		Iterator nodes = queue.iterator();
		while(nodes.hasNext()){
			SPTNode node = nodes.next();
			
			Integer height = stateHeight.get(node.state.getVertex());
			if(height==null){
				height = 0;
			} else{
				height += 1;
			}
			stateHeight.put(node.state.getVertex(),height);
			
			node.setHeight(height);
		}
	}

	private void drawNewEdges() {
    	if( drawEdges  ){
    		strokeWeight(1);
			stroke(255,255,255); //white	
			noFill();
			while (!newSPTEdges.isEmpty()) {
				State leaf = newSPTEdges.poll();
  	
				if(leaf != null){
					if( leaf.getBackEdge() != null ){
						drawEdge(leaf.getBackEdge());
					}
				}
			}

		}
	}

    private void drawCoords() {
		// Black background box
		fill(0, 0, 0);
		stroke(30, 128, 30);
		// noStroke();
		strokeWeight(1);
		rect(3, 3, 303, textAscent() + textDescent() + 6);
		// Print lat & lon coordinates
		fill(128, 128, 256);
		// noStroke();
		String output = lonFormatter.format(mouseModelX) + " "
		        + latFormatter.format(mouseModelY);
		textAlign(LEFT, TOP);
		text(output, 6, 6);
	}

	private void drawVertices() {
		/* turn off vertex display when zoomed out */
		final double METERS_PER_DEGREE_LAT = 111111.111111;
		boolean closeEnough = (modelBounds.getHeight() * METERS_PER_DEGREE_LAT / this.width < 5);
		/* Draw selected visible vertices */
		for (Vertex v : visibleVertices) {
            if (drawTransitStopVertices && closeEnough && v instanceof TransitStationStop) {
                fill(60, 60, 200); // Make transit stops blue dots
		        drawVertex(v, 7);
			}
			if (drawStreetVertices && v instanceof IntersectionVertex) {
		        IntersectionVertex iv = (IntersectionVertex) v;
		        if (iv.trafficLight) {
                    fill(120, 60, 60); // Make traffic lights red dots
                    drawVertex(v, 5);
                }
		    }
			if (drawMultistateVertices && spt!=null){
				List states = spt.getStates(v);
				if(states != null){
                    fill(100, 60, 100);
					drawVertex( v, states.size()*2 );
				}
			}
		}
	}

	private void drawHighlighted() {
		/* Draw highlighted edges in another color */
		noFill();
		stroke(200, 200, 000, 16); // yellow transparent edge highlight
		strokeWeight(8);
		if (drawHighlighted  && highlightedEdges != null) {
            try {
                for (Edge e : highlightedEdges) {
                    drawEdge(e);
                }
            } catch (ConcurrentModificationException cme) {
                // The edge list was cleared or added to while it was being drawn, no harm done.
            }
		}
		/* Draw highlighted graph path in another color */
		if (highlightedGraphPath != null) {
		    drawGraphPath(highlightedGraphPath);
		}
		/* Draw (single) highlighted edge in highlight color */
		if (highlightedEdge != null && highlightedEdge.getGeometry() != null) {
			stroke(10, 200, 10, 128);
			strokeWeight(12);
		    drawEdge(highlightedEdge);
		}
		/* Draw highlighted vertices */
		fill(255, 127, 0); // orange fill
		noStroke();
		if (highlightedVertices != null) {
		    for (Vertex v : highlightedVertices) {
		        drawVertex(v, 8);
		    }
		}
		/* Draw (single) highlighed coordinate in a different color */
		if (highlightedCoordinate != null) {
		    fill(255, 255, 30);
		    drawCoordinate(highlightedCoordinate, 7);
		}
		noFill();
	}

	private boolean drawTransit(int startMillis) {
		if (drawTransitEdges) {
		    stroke(40, 40, 128, 30); // transparent blue
		    strokeWeight(4);
		    noFill();
		    // for (Edge e : visibleTransitEdges) {
		    while (drawOffset < visibleTransitEdges.size()) {
		        Edge e = visibleTransitEdges.get(drawOffset);
		        drawEdge(e);
		        drawOffset += 1;
		        if (drawOffset % BLOCK_SIZE == 0) {
		            if (millis() - startMillis > FRAME_TIME)
		                return false;
		        }
		    }
		}
		return true;
	}

	private boolean drawLinks(int startMillis) {
		if (drawLinkEdges) {
		    stroke(256, 165, 0, 30); // transparent blue
		    strokeWeight(3);
		    noFill();
		    // for (Edge e : visibleTransitEdges) {
		    while (drawOffset < visibleLinkEdges.size()) {
		        Edge e = visibleLinkEdges.get(drawOffset);
		        drawEdge(e);
		        drawOffset += 1;
		        if (drawOffset % BLOCK_SIZE == 0) {
		            if (millis() - startMillis > FRAME_TIME)
		                return false;
		        }
		    }
		}
		return true;
	}

	private boolean drawAll(int startMillis) {
		if (drawOffset == 0) {
		    findVisibleElements();
		    background(15);
		}
		if (drawStreetEdges) {
		    stroke(30, 128, 30); // dark green
		    strokeWeight(1);
		    noFill();
		    while (drawOffset < visibleStreetEdges.size()) {
		        drawEdge(visibleStreetEdges.get(drawOffset));
		        drawOffset += 1;
		        if (drawOffset % BLOCK_SIZE == 0) {
		            if (millis() - startMillis > FRAME_TIME)
		                return false;
		        }
		    }
		}
		return true;
	}

	private void drawPartial(int startMillis) {
		background(15);
		stroke(30, 128, 30);
		strokeWeight(1);
		noFill();
		// noSmooth();
		int drawIndex = 0;
		int drawStart = 0;
		int drawCount = 0;
		while (drawStart < DECIMATE && drawStart < visibleStreetEdges.size()) {
		    if (drawFast)
		        drawEdgeFast(visibleStreetEdges.get(drawIndex));
		    else
		        drawEdge(visibleStreetEdges.get(drawIndex));
		    drawIndex += DECIMATE;
		    drawCount += 1;
		    if (drawCount % BLOCK_SIZE == 0 && millis() - startMillis > FRAME_TIME) {
		        // ran out of time to draw this frame.
		        // enable fast-drawing when too few edges were drawn:
		        // drawFast = drawCount < visibleStreetEdges.size() / 10;
		        // leave edge drawing loop to let other work happen.
		        break;
		    }
		    if (drawIndex >= visibleStreetEdges.size()) {
		        // start over drawing every DECIMATEth edge, offset by 1
		        drawStart += 1;
		        drawIndex = drawStart;
		    }
		}
	}

    private void handleNewHighlights() {
        // fill(0, 0, 0, 1);
        // rect(0,0,this.width, this.height);
        desaturate();
        noFill();
        stroke(256, 0, 0, 128); // , 8);
        strokeWeight(6);
        while (!newHighlightedEdges.isEmpty()) {
            Edge de = newHighlightedEdges.poll();
            if (de != null) {
                drawEdge(de);
                highlightedEdges.add(de);
            }
        }
        if (VIDEO)
            saveVideoFrame();
    }

    private void saveVideoFrame() {
        save(VIDEO_PATH + "/" + videoFrameNumber++ + ".bmp");
    }

    private void resetVideoFrameNumber() {
        videoFrameNumber = 0;
    }

    private void desaturate() {
        final float f = 8;
        loadPixels();
        for (int i = 0; i < width * height; i++) {
            int c = pixels[i];
            float r = red(c);
            float g = green(c);
            float b = blue(c);
            float avg = (r + g + b) / 3;
            r += (avg - r) / f;
            g += (avg - g) / f;
            b += (avg - b) / f;
            pixels[i] = color(r, g, b);
        }
        updatePixels();
    }

    private double toScreenY(double y) {
        return map(y, modelBounds.getMinY(), modelBounds.getMaxY(), getSize().height, 0);
    }

    private double toScreenX(double x) {
        return map(x, modelBounds.getMinX(), modelBounds.getMaxX(), 0, getSize().width);
    }

    public void keyPressed() {
        if (key == CODED && keyCode == CONTROL)
            ctrlPressed = true;
    }

    public void keyReleased() {
        if (key == CODED && keyCode == CONTROL)
            ctrlPressed = false;
    }

    @SuppressWarnings("unchecked")
    public void mouseClicked() {
        Envelope screenEnv = new Envelope(new Coordinate(mouseX, mouseY));
        screenEnv.expandBy(4, 4);
        Envelope env = new Envelope(toModelX(screenEnv.getMinX()), toModelX(screenEnv.getMaxX()),
                toModelY(screenEnv.getMinY()), toModelY(screenEnv.getMaxY()));

        List nearby = (List) vertexIndex.query(env);
        selector.verticesSelected(nearby);
        drawLevel = DRAW_ALL;
    }

    public void mouseReleased(MouseEvent e) {
        startDrag = null;
    }

    public void mouseDragged(MouseEvent e) {
        Point c = e.getPoint();
        if (startDrag == null) {
            startDrag = c;
            dragX = c.x;
            dragY = c.y;
        }
        double dx = dragX - c.x;
        double dy = c.y - dragY;
        if (ctrlPressed || mouseButton == RIGHT) {
            zoom(dy * 0.01, startDrag);
        } else {
            double tx = modelBounds.getWidth() * dx / getWidth();
            double ty = modelBounds.getHeight() * dy / getHeight();
            modelBounds.translate(tx, ty);
        }
        dragX = c.x;
        dragY = c.y;
        drawLevel = DRAW_PARTIAL;
    }

    private double toModelY(double y) {
        return map(y, 0, getSize().height, modelBounds.getMaxY(), modelBounds.getMinY());
    }

    private double toModelX(double x) {
        return map(x, 0, getSize().width, modelBounds.getMinX(), modelBounds.getMaxX());
    }

    /**
     * A version of ellipse that takes double args, because apparently Java is too stupid to downgrade automatically.
     * 
     * @param d
     * @param e
     * @param f
     * @param g
     */
    private void ellipse(double d, double e, double f, double g) {
        ellipse((float) d, (float) e, (float) f, (float) g);
    }

    /**
     * Set the Vertex selector to newSelector, and store the old selector on the stack of selectors
     * 
     * @param newSelector
     */
    public void pushSelector(VertexSelectionListener newSelector) {
        selectors.add(selector);
        selector = newSelector;
    }

    /**
     * Restore the previous vertexSelector
     */
    public void popSelector() {
        selector = selectors.get(selectors.size() - 1);
        selectors.remove(selectors.size() - 1);
    }

    public void highlightCoordinate(Coordinate c) {
        double xd = 0, yd = 0;
        while (!modelBounds.contains(c)) {
            xd = modelBounds.getWidth() / 100;
            yd = modelBounds.getHeight() / 100;
            modelBounds.expandBy(xd, yd);
        }
        modelBounds.expandBy(xd, yd);
        highlightedCoordinate = c;
        drawLevel = DRAW_ALL;
    }

    public void highlightVertex(Vertex v) {
        highlightCoordinate(v.getCoordinate());
    }

    public void enqueueHighlightedEdge(Edge de) {
        newHighlightedEdges.add(de);
    }

    public void clearHighlights() {
        highlightedEdges.clear();
        highlightedVertices.clear();
        drawLevel = DRAW_ALL;
    }

    public void highlightEdge(Edge selected) {
        highlightedEdge = selected;
        drawLevel = DRAW_ALL;
    }

    public void highlightGraphPath(GraphPath gp) {
        highlightedGraphPath = gp;
        // drawLevel = DRAW_ALL;
        drawLevel = DRAW_TRANSIT; // leave streets in grey
    }

    public void setHighlightedVertices(Set vertices) {
        highlightedVertices = new ArrayList(vertices);
        drawLevel = DRAW_ALL;
    }

    public void setHighlightedVertices(List vertices) {
        highlightedVertices = vertices;
        drawLevel = DRAW_ALL;
    }

    public void setHighlightedEdges(List edges) {
        highlightedEdges = edges;
        drawLevel = DRAW_ALL;
    }

    public void drawAnotation(GraphBuilderAnnotation anno) {
        Envelope env = new Envelope();

        Edge e = anno.getReferencedEdge();
        if (e != null) {
            this.enqueueHighlightedEdge(e);
            env.expandToInclude(e.getFromVertex().getCoordinate());
            env.expandToInclude(e.getToVertex().getCoordinate());
        }

        ArrayList vertices = new ArrayList();
        Vertex v = anno.getReferencedVertex();
        if (v != null) {
            env.expandToInclude(v.getCoordinate());
            vertices.add(v);
        }

        if (e == null && v == null)
            return;

        // make it a little bigger, especially needed for STOP_UNLINKED
        env.expandBy(0.02);

        // highlight relevant things
        this.clearHighlights();
        this.setHighlightedVertices(vertices);

        // zoom the graph display
        this.zoomToEnvelope(env);

        // and draw
        this.draw();
    }

	public void setShowTransit(boolean selected) {
		drawTransitEdges = selected;
		drawTransitStopVertices = selected;
	}

	public void setShowStreets(boolean selected) {
		drawStreetEdges = selected;
		drawStreetVertices = selected;
	}

	public void setShowHightlights(boolean selected) {
		drawHighlighted = selected;
	}

	public void redraw(){
		drawLevel = DRAW_ALL;
	}
    
	public void addNewSPTEdge(State state) {
		this.newSPTEdges.add( state );
		this.simpleSPT.add( state );
	}

	public void resetSPT() {
		this.simpleSPT = new SimpleSPT();
	}
	
	public void setShowSPT(boolean selected) {
		sptVisible = selected;
	}
	
	public void setSPTFlattening(float sptFlattening) {
		this.sptFlattening  = sptFlattening;
	}

	public void setSPTThickness(float sptThickness) {
		this.sptThickness  = sptThickness;
	}

	public void setShowMultistateVertices(boolean selected) {
		this.drawMultistateVertices = selected;
	}

	public void setSPT(ShortestPathTree spt) {
		this.spt = spt;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy