Package org.movsim.viewer.graphics

Source Code of org.movsim.viewer.graphics.TrafficCanvas

/*
* Copyright (C) 2010, 2011, 2012 by Arne Kesting, Martin Treiber, Ralph Germ, Martin Budden
* <movsim.org@gmail.com>
* -----------------------------------------------------------------------------------------
*
* This file is part of
*
* MovSim - the multi-model open-source vehicular-traffic simulator.
*
* MovSim is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MovSim is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MovSim. If not, see <http://www.gnu.org/licenses/>
* or <http://www.movsim.org>.
*
* -----------------------------------------------------------------------------------------
*/

package org.movsim.viewer.graphics;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.xml.bind.JAXBException;

import org.movsim.autogen.TrafficLightStatus;
import org.movsim.roadmappings.RoadMapping;
import org.movsim.simulator.SimulationRunnable;
import org.movsim.simulator.Simulator;
import org.movsim.simulator.roadnetwork.AbstractTrafficSource;
import org.movsim.simulator.roadnetwork.Lanes;
import org.movsim.simulator.roadnetwork.RoadNetwork;
import org.movsim.simulator.roadnetwork.RoadSegment;
import org.movsim.simulator.roadnetwork.Slope;
import org.movsim.simulator.roadnetwork.SpeedLimit;
import org.movsim.simulator.roadnetwork.TrafficSink;
import org.movsim.simulator.trafficlights.TrafficLight;
import org.movsim.simulator.trafficlights.TrafficLightLocation;
import org.movsim.simulator.vehicles.Vehicle;
import org.movsim.utilities.Colors;
import org.movsim.utilities.Units;
import org.movsim.viewer.roadmapping.PaintRoadMapping;
import org.movsim.viewer.ui.ViewProperties;
import org.movsim.viewer.util.SwingHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

/**
* <p>
* TrafficCanvas class.
* </p>
*
* <p>
* Handles:
* <ul>
* <li>Drawing the road network and the vehicles upon it.</li>
* <li>Standard mouse events.</li>
* <li>Mouse-over and clicks on vehicles (when simulation paused).</li>
* <li>Key events.</li>
* </ul>
* </p>
* <p>
* The vehicles are redrawn in their new positions in the drawForeground() method, which is indirectly invoked from repaint(). The
* drawForeground() method has a synchronization lock so that vehicles are not updated or removed while they are being drawn.
* </p>
* <p>
* Actual road networks and traffic scenarios should be set up in a subclass.
* </p>
*
*/
public class TrafficCanvas extends SimulationCanvasBase implements SimulationRunnable.UpdateDrawingCallback,
        SimulationRunnable.HandleExceptionCallback {

    final static Logger logger = LoggerFactory.getLogger(TrafficCanvas.class);
    static final long serialVersionUID = 1L;

    protected final Simulator simulator;
    protected final RoadNetwork roadNetwork;
    private Properties properties;

    /**
     * Callbacks from this TrafficCanvas to the application UI.
     *
     */
    public interface StatusControlCallbacks {
        /**
         * Callback to get the UI to display a status message.
         *
         * @param message
         *            the status message
         */
        public void showStatusMessage(String message);

        public void stateChanged();
    }

    protected StatusControlCallbacks statusControlCallbacks;

    // pre-allocate vehicle drawing path
    private final GeneralPath vehiclePath = new GeneralPath();

    // pre-allocate clipping path for road mappings
    private final GeneralPath clipPath = new GeneralPath(Path2D.WIND_EVEN_ODD);

    // colors
    protected Color roadColor;
    protected Color roadEdgeColor;
    protected Color roadLineColor;
    protected Color sourceColor;
    protected Color sinkColor;

    private double vmaxForColorSpectrum;

    protected boolean drawRoadId;
    protected boolean drawSources;
    protected boolean drawSinks;
    protected boolean drawSpeedLimits;
    protected boolean drawSlopes;

    // brake light handling
    protected Color brakeLightColor = Color.RED;

    float lineWidth;
    float lineLength;
    float gapLength;
    float gapLengthExit;

    /**
     * Vehicle color support only the first four are used by the button. commandCyclevehicleColors()
     */
    public enum VehicleColorMode {
        VELOCITY_COLOR, LANE_CHANGE, ACCELERATION_COLOR, VEHICLE_LABEL_COLOR, VEHICLE_COLOR, EXIT_COLOR, HIGHLIGHT_VEHICLE
    }

    /** Color mode displayed on startup */
    protected VehicleColorMode vehicleColorMode = VehicleColorMode.VELOCITY_COLOR;
    protected VehicleColorMode vehicleColorModeSave;

    double[] velocities;

    Color[] accelerationColors;
    protected final Map<String, Color> labelColors = new HashMap<String, Color>();

    private final double[] accelerations = new double[] { -7.5, -0.1, 0.2 };

    /** vehicle mouse-over support */
    String popupString;
    String popupStringExitEndRoad;
    protected Vehicle vehiclePopup;
    protected VehicleTipWindow vehicleTipWindow;
    final TrafficCanvasMouseListener mouseListener;
    final TrafficCanvasKeyListener controller;

    protected long lastVehicleViewed = -1;
    protected long vehicleToHighlightId = -1;

    public TrafficCanvas(Simulator simulator, Properties properties) {
        super(simulator.getSimulationRunnable());
        this.simulator = simulator;
        this.roadNetwork = simulator.getRoadNetwork();
        this.properties = properties;

        initGraphicConfigFieldsFromProperties();

        simulationRunnable.setUpdateDrawingCallback(this);
        simulationRunnable.setHandleExceptionCallback(this);

        setStatusControlCallbacks(statusControlCallbacks);

        controller = new TrafficCanvasKeyListener(this, roadNetwork);
        addKeyListener(controller);

        mouseListener = new TrafficCanvasMouseListener(this, controller, roadNetwork);
        addMouseListener(mouseListener);
        addMouseMotionListener(mouseListener);
        addMouseWheelListener(mouseListener);
    }

    /**
     * Returns the traffic canvas controller.
     *
     * @return the traffic canvas controller
     */
    public TrafficCanvasController controller() {
        return controller;
    }

    protected void initGraphicConfigFieldsFromProperties() {
        setDrawRoadId(Boolean.parseBoolean(properties.getProperty("drawRoadId")));
        setDrawSinks(Boolean.parseBoolean(properties.getProperty("drawSinks")));
        setDrawSources(Boolean.parseBoolean(properties.getProperty("drawSources")));
        setDrawSlopes(Boolean.parseBoolean(properties.getProperty("drawSlopes")));
        setDrawSpeedLimits(Boolean.parseBoolean(properties.getProperty("drawSpeedLimits")));

        final int hexRadix = 16;
        setBackgroundColor(new Color(Integer.parseInt(properties.getProperty("backgroundColor"), hexRadix)));
        roadColor = new Color(Integer.parseInt(properties.getProperty("roadColor"), hexRadix));
        roadEdgeColor = new Color(Integer.parseInt(properties.getProperty("roadEdgeColor"), hexRadix));
        roadLineColor = new Color(Integer.parseInt(properties.getProperty("roadLineColor"), hexRadix));
        sourceColor = new Color(Integer.parseInt(properties.getProperty("sourceColor"), hexRadix));
        sinkColor = new Color(Integer.parseInt(properties.getProperty("sinkColor"), hexRadix));
        setVehicleColorMode(vehicleColorMode.valueOf(properties.getProperty("vehicleColorMode")));

        setVmaxForColorSpectrum(Double.parseDouble(properties.getProperty("vmaxForColorSpectrum")));

        lineWidth = Float.parseFloat(properties.getProperty("lineWidth"));
        lineLength = Float.parseFloat(properties.getProperty("lineLength"));
        gapLength = Float.parseFloat(properties.getProperty("gapLength"));
        gapLengthExit = Float.parseFloat(properties.getProperty("gapLengthExit"));

        scale = Double.parseDouble(properties.getProperty("initialScale"));
        setSleepTime(Integer.parseInt(properties.getProperty("initial_sleep_time")));
    }

    @Override
    void stateChanged() {
        if (statusControlCallbacks != null) {
            statusControlCallbacks.stateChanged();
        }
    }

    @Override
    public void reset() {
        super.reset();
        try {
            simulator.initialize();
        } catch (JAXBException | SAXException e) {
            throw new RuntimeException("Jaxb exception:" + e.toString());
        }
        simulator.reset();
        mouseListener.reset();
        vehicleToHighlightId = -1;
        initGraphicSettings();
        forceRepaintBackground();
    }

    @Override
    public void resetScaleAndOffset() {
        scale = Double.parseDouble(properties.getProperty("initialScale"));
        xOffset = Integer.parseInt(properties.getProperty("xOffset"));
        yOffset = Integer.parseInt(properties.getProperty("yOffset"));
        setTransform();
    }

    /**
     * Sets up the given traffic scenario.
     *
     * @param scenario
     * @throws SAXException
     * @throws JAXBException
     */
    public void setupTrafficScenario(String scenario, String path) {
        reset();
        try {
            simulator.loadScenarioFromXml(scenario, path);
        } catch (JAXBException | SAXException e) {
            throw new IllegalArgumentException(e.toString());
        }
        properties = ViewProperties.loadProperties(scenario, path);
        initGraphicSettings();
        forceRepaintBackground();
    }

    private void initGraphicSettings() {
        initGraphicConfigFieldsFromProperties();
        resetScaleAndOffset();
        for (final RoadSegment roadSegment : roadNetwork) {
            roadSegment.roadMapping().setRoadColor(roadColor.getRGB());
        }
        for (String vehicleTypeLabel : simulator.getVehiclePrototypeLabels()) {
            final Color color = new Color(Colors.randomColor());
            logger.info("set color for vehicle label={}", vehicleTypeLabel);
            labelColors.put(vehicleTypeLabel, color);
        }
    }

    /**
     * Sets the status callback functions.
     *
     * @param statusCallbacks
     */
    public void setStatusControlCallbacks(StatusControlCallbacks statusCallbacks) {
        this.statusControlCallbacks = statusCallbacks;
    }

    /**
     * Sets the (locale dependent) message strings.
     *
     * @param popupString
     *            popup window format string for vehicle that leaves road segment at a specific exit
     * @param popupStringExitEndRoad
     *            popup window format string for vehicle that leaves road segment at end
     */
    public void setMessageStrings(String popupString, String popupStringExitEndRoad) {
        this.popupString = popupString;
        this.popupStringExitEndRoad = popupStringExitEndRoad;
    }

    public void setMessageStrings(String popupString, String popupStringExitEndRoad, String trafficInflowString,
            String perturbationRampingFinishedString, String perturbationAppliedString, String simulationFinished) {
        setMessageStrings(popupString, popupStringExitEndRoad);
    }

    void setAccelerationColors() {
        accelerationColors = new Color[] { Color.WHITE, Color.RED, Color.BLACK, Color.GREEN };
        assert accelerations.length == accelerationColors.length - 1;
    }

    public double getVmaxForColorSpectrum() {
        return vmaxForColorSpectrum;
    }

    public void setVmaxForColorSpectrum(double vmaxForColorSpectrum) {
        this.vmaxForColorSpectrum = vmaxForColorSpectrum;
    }

    public boolean isDrawRoadId() {
        return drawRoadId;
    }

    public void setDrawRoadId(boolean drawRoadId) {
        this.drawRoadId = drawRoadId;
        repaint();
    }

    /**
     * @return the drawSouces
     */
    public boolean isDrawSources() {
        return drawSources;
    }

    /**
     * @return the drawSinks
     */
    public boolean isDrawSinks() {
        return drawSinks;
    }

    /**
     * @return the drawSpeedLimits
     */
    public boolean isDrawSpeedLimits() {
        return drawSpeedLimits;
    }

    /**
     * @return the drawSlopes
     */
    public boolean isDrawSlopes() {
        return drawSlopes;
    }

    public void setDrawSources(boolean b) {
        this.drawSources = b;
        repaint();
    }

    public void setDrawSinks(boolean b) {
        this.drawSinks = b;
        repaint();
    }

    public void setDrawSpeedLimits(boolean b) {
        this.drawSpeedLimits = b;
        repaint();
    }

    public void setDrawSlopes(boolean b) {
        this.drawSlopes = b;
        repaint();
    }

    /**
     * Returns the color of the vehicle. The color may depend on the vehicle's properties, such as its velocity.
     *
     * @param vehicle
     * @param simulationTime
     */
    protected Color vehicleColor(Vehicle vehicle, double simulationTime) {
        Color color;

        switch (vehicleColorMode) {
        case ACCELERATION_COLOR:
            final double a = vehicle.physicalQuantities().getAcc();
            final int count = accelerations.length;
            for (int i = 0; i < count; ++i) {
                if (a < accelerations[i])
                    return accelerationColors[i];
            }
            return accelerationColors[accelerationColors.length - 1];
        case EXIT_COLOR:
            color = Color.BLACK;
            if (vehicle.exitRoadSegmentId() != Vehicle.ROAD_SEGMENT_ID_NOT_SET) {
                color = Color.WHITE;
            }
            break;
        case HIGHLIGHT_VEHICLE:
            color = vehicle.getId() == vehicleToHighlightId ? Color.BLUE : Color.BLACK;
            break;
        case LANE_CHANGE:
            color = Color.BLACK;
            if (vehicle.inProcessOfLaneChange()) {
                color = Color.ORANGE;
            }
            break;
        case VEHICLE_COLOR:
            // use vehicle's cache for AWT color object
            color = (Color) vehicle.colorObject();
            if (color == null) {
                int vehColorInt = vehicle.color();
                color = new Color(Colors.red(vehColorInt), Colors.green(vehColorInt), Colors.blue(vehColorInt));
                vehicle.setColorObject(color);
            }
            break;
        case VEHICLE_LABEL_COLOR:
            String label = vehicle.getLabel();
            color = labelColors.containsKey(label) ? labelColors.get(label) : Color.WHITE;
            break;
        default:
            final double v = vehicle.physicalQuantities().getSpeed() * 3.6;
            color = SwingHelper.getColorAccordingToSpectrum(0, getVmaxForColorSpectrum(), v);
        }
        return color;
    }

    public void setVehicleColorMode(VehicleColorMode vehicleColorMode) {
        this.vehicleColorMode = vehicleColorMode;
    }

    /**
     * Callback to allow the application to make any further required drawing after the vehicles have been moved.
     */
    protected void drawAfterVehiclesMoved(Graphics2D g, double simulationTime, long iterationCount) {
    }

    /**
     * <p>
     * Draws the foreground: everything that moves each timestep. For the traffic simulation that means draw all the vehicles:<br />
     * For each roadSection, draw all the vehicles in the roadSection, positioning them using the roadMapping for that roadSection.
     * </p>
     *
     * <p>
     * This method is synchronized with the <code>SimulationRunnable.run()</code> method, so that vehicles are not updated, added or removed
     * while they are being drawn.
     * </p>
     * <p>
     * tm The abstract method paintAfterVehiclesMoved is called after the vehicles have been moved, to allow any further required drawing on
     * the canvas.
     * </p>
     *
     * @param g
     */
    @Override
    protected void drawForeground(Graphics2D g) {
        // moveVehicles occurs in the UI thread, so must synchronize with the
        // update of the road network in the calculation thread.

        final long timeBeforePaint_ms = System.currentTimeMillis();

        synchronized (simulationRunnable.dataLock) {

            drawTrafficLights(g);

            final double simulationTime = this.simulationTime();

            for (final RoadSegment roadSegment : roadNetwork) {
                final RoadMapping roadMapping = roadSegment.roadMapping();
                assert roadMapping != null;

                PaintRoadMapping.setClipPath(g, roadMapping, clipPath);
                for (final Vehicle vehicle : roadSegment) {
                    drawVehicle(g, simulationTime, roadMapping, vehicle);
                }
            }
            totalAnimationTime += System.currentTimeMillis() - timeBeforePaint_ms;
            drawAfterVehiclesMoved(g, simulationRunnable.simulationTime(), simulationRunnable.iterationCount());
        }
    }

    private void drawVehicle(Graphics2D g, double simulationTime, RoadMapping roadMapping, Vehicle vehicle) {
        // draw vehicle polygon at new position
        final RoadMapping.PolygonFloat polygon = roadMapping.mapFloat(vehicle, simulationTime);
        vehiclePath.reset();
        vehiclePath.moveTo(polygon.xPoints[0], polygon.yPoints[0]);
        vehiclePath.lineTo(polygon.xPoints[1], polygon.yPoints[1]);
        vehiclePath.lineTo(polygon.xPoints[2], polygon.yPoints[2]);
        vehiclePath.lineTo(polygon.xPoints[3], polygon.yPoints[3]);
        vehiclePath.closePath();
        g.setPaint(vehicleColor(vehicle, simulationTime));
        g.fill(vehiclePath);
        if (vehicle.isBrakeLightOn()) {
            // if the vehicle is decelerating then display the
            vehiclePath.reset();
            // points 2 & 3 are at the rear of vehicle
            vehiclePath.moveTo(polygon.xPoints[2], polygon.yPoints[2]);
            vehiclePath.lineTo(polygon.xPoints[3], polygon.yPoints[3]);
            vehiclePath.closePath();
            g.setPaint(brakeLightColor);
            g.draw(vehiclePath);
        }
    }

    /**
     * Draws the background: everything that does not move each timestep. The background consists of the road segments and the sources and
     * sinks, if they are visible.
     *
     * @param g
     */
    @Override
    protected void drawBackground(Graphics2D g) {
        if (drawSources) {
            drawSources(g);
        }
        if (drawSinks) {
            drawSinks(g);
        }
        drawRoadSegments(g);

        if (drawSpeedLimits) {
            drawSpeedLimits(g);
        }

        if (drawSlopes) {
            drawSlopes(g);
        }

        if (drawRoadId) {
            drawRoadSectionIds(g);
        }
    }

    /**
     * Draws each road segment in the road network.
     *
     * @param g
     */
    private void drawRoadSegments(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            final RoadMapping roadMapping = roadSegment.roadMapping();
            assert roadMapping != null;
            drawRoadSegment(g, roadMapping);
            drawRoadSegmentLines(g, roadMapping); // in one step (parallel or sequential update)?!
        }
    }

    private static void drawRoadSegment(Graphics2D g, RoadMapping roadMapping) {
        final BasicStroke roadStroke = new BasicStroke((float) roadMapping.roadWidth(), BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER);
        g.setStroke(roadStroke);
        g.setColor(new Color(roadMapping.roadColor()));
        PaintRoadMapping.paintRoadMapping(g, roadMapping);
    }

    /**
     * Draws the road lines and road edges.
     *
     * @param g
     */
    private void drawRoadSegmentLines(Graphics2D g, RoadMapping roadMapping) {
        final float dashPhase = (float) (roadMapping.roadLength() % (lineLength + gapLength));

        final Stroke lineStroke = new BasicStroke(lineWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
                new float[] { lineLength, gapLength }, dashPhase);
        g.setStroke(lineStroke);
        g.setColor(roadLineColor);

        // draw the road lines
        final int laneCount = roadMapping.laneCount();
        for (int lane = 1; lane < laneCount; ++lane) {
            final double offset = roadMapping.laneInsideEdgeOffset(lane);
            if (lane == roadMapping.trafficLaneMin() || lane == roadMapping.trafficLaneMax()) {
                // use exit stroke pattern for on-ramps, off-ramps etc
                final Stroke exitStroke = new BasicStroke(lineWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
                        10.0f, new float[] { 5.0f, gapLengthExit }, 5.0f);
                g.setStroke(exitStroke);
            } else {
                g.setStroke(lineStroke);
            }
            PaintRoadMapping.paintRoadMapping(g, roadMapping, offset);
        }

        // draw the road edges
        g.setStroke(new BasicStroke());
        g.setColor(roadEdgeColor);
        // FIXME BUGGY HERE, offset not calculated correctly
        // edge of most inner lane: hack here, lane does not exist
        double offset = roadMapping.laneInsideEdgeOffset(Lanes.MOST_INNER_LANE - 1);
        PaintRoadMapping.paintRoadMapping(g, roadMapping, offset);
        // edge of most outer edge
        offset = roadMapping.laneInsideEdgeOffset(roadMapping.laneCount());
        PaintRoadMapping.paintRoadMapping(g, roadMapping, offset);

    }

    private void drawTrafficLights(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            drawTrafficLightsOnRoad(g, roadSegment);
        }
    }

    public static Rectangle2D trafficLightRect(RoadMapping roadMapping, TrafficLightLocation trafficLightLocation) {
        final double offset = (roadMapping.laneCount() / 2.0 + 1.5) * roadMapping.laneWidth();
        final double size = 2 * roadMapping.laneWidth();
        final RoadMapping.PosTheta posTheta = roadMapping.map(trafficLightLocation.position(), offset);
        final Rectangle2D rect = new Rectangle2D.Double(posTheta.x - size / 2, posTheta.y - size / 2, size, size
                * trafficLightLocation.getTrafficLight().lightCount());
        return rect;
    }

    /**
     * Draw a traffic light that has only one light
     *
     * @param g
     * @param trafficLight
     */
    private static void drawTrafficLight1(Graphics2D g, TrafficLight trafficLight, Rectangle2D trafficLightRect,
            double radius) {
        g.setColor(Color.DARK_GRAY);
        g.fill(trafficLightRect);
        switch (trafficLight.status()) {
        case GREEN:
            g.setColor(Color.GREEN);
            break;
        case GREEN_RED:
            g.setColor(Color.YELLOW);
            break;
        case RED:
            g.setColor(Color.RED);
            break;
        case RED_GREEN:
            g.setColor(Color.ORANGE);
            break;
        }
        final double x = trafficLightRect.getCenterX();
        final double y = trafficLightRect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
    }

    /**
     * Draw a traffic light that has two lights
     *
     * @param g
     * @param trafficLight
     */
    private static void drawTrafficLight2(Graphics2D g, TrafficLight trafficLight, Rectangle2D trafficLightRect,
            double radius) {
        g.setColor(Color.DARK_GRAY);
        g.fill(trafficLightRect);
        final Double width = trafficLightRect.getWidth();
        final Double height = trafficLightRect.getHeight();

        // draw the top light
        g.setColor(trafficLight.status() == TrafficLightStatus.RED ? Color.RED : Color.LIGHT_GRAY);
        Rectangle2D rect = new Rectangle2D.Double(trafficLightRect.getX(), trafficLightRect.getY(), width, height / 2.0);
        double x = rect.getCenterX();
        double y = rect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

        // draw the bottom light
        g.setColor(trafficLight.status() == TrafficLightStatus.GREEN ? Color.GREEN : Color.LIGHT_GRAY);
        rect = new Rectangle2D.Double(trafficLightRect.getX(), trafficLightRect.getY() + height / 2.0, width,
                height / 2.0);
        x = rect.getCenterX();
        y = rect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
    }

    /**
     * Draw a traffic light that has three lights
     *
     * @param g
     * @param trafficLight
     */
    private static void drawTrafficLight3(Graphics2D g, TrafficLight trafficLight, Rectangle2D trafficLightRect,
            double radius) {
        g.setColor(Color.DARK_GRAY);
        g.fill(trafficLightRect);
        final Double width = trafficLightRect.getWidth();
        final Double height = trafficLightRect.getHeight();

        // draw the top light
        g.setColor(trafficLight.status() == TrafficLightStatus.RED ? Color.RED : Color.LIGHT_GRAY);
        Rectangle2D rect = new Rectangle2D.Double(trafficLightRect.getX(), trafficLightRect.getY(), width, height / 3.0);
        double x = rect.getCenterX();
        double y = rect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

        // draw the middle light
        if (trafficLight.status() == TrafficLightStatus.GREEN_RED) {
            g.setColor(Color.YELLOW);
        } else if (trafficLight.status() == TrafficLightStatus.RED_GREEN) {
            g.setColor(Color.ORANGE);
        } else {
            g.setColor(Color.LIGHT_GRAY);
        }
        rect = new Rectangle2D.Double(trafficLightRect.getX(), trafficLightRect.getY() + height / 3.0, width,
                height / 3.0);
        x = rect.getCenterX();
        y = rect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));

        // draw the bottom light
        g.setColor(trafficLight.status() == TrafficLightStatus.GREEN ? Color.GREEN : Color.LIGHT_GRAY);
        rect = new Rectangle2D.Double(trafficLightRect.getX(), trafficLightRect.getY() + 2.0 * height / 3.0, width,
                height / 3.0);
        x = rect.getCenterX();
        y = rect.getCenterY();
        g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
    }

    private static void drawTrafficLightsOnRoad(Graphics2D g, RoadSegment roadSegment) {
        if (roadSegment.trafficLightLocations() == null) {
            return;
        }
        final RoadMapping roadMapping = roadSegment.roadMapping();
        assert roadMapping != null;

        // final double offset = -(roadMapping.laneCount() / 2.0 + 1.5) * roadMapping.laneWidth();
        // final int size = (int) (2 * roadMapping.laneWidth());
        final double radius = 0.8 * roadMapping.laneWidth();
        for (TrafficLightLocation trafficLightLocation : roadSegment.trafficLightLocations()) {
            Rectangle2D trafficLightRect = trafficLightRect(roadMapping, trafficLightLocation);
            switch (trafficLightLocation.getTrafficLight().lightCount()) {
            case 1:
                drawTrafficLight1(g, trafficLightLocation.getTrafficLight(), trafficLightRect, radius);
                break;
            case 2:
                drawTrafficLight2(g, trafficLightLocation.getTrafficLight(), trafficLightRect, radius);
                break;
            default:
                drawTrafficLight3(g, trafficLightLocation.getTrafficLight(), trafficLightRect, radius);
                break;
            }
        }
    }

    private void drawSpeedLimits(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            drawSpeedLimitsOnRoad(g, roadSegment);
        }
    }

    private void drawSpeedLimitsOnRoad(Graphics2D g, RoadSegment roadSegment) {
        if (roadSegment.speedLimits() == null) {
            return;
        }

        final RoadMapping roadMapping = roadSegment.roadMapping();
        assert roadMapping != null;
        final double offset = -(roadMapping.laneCount() / 2.0 + 1.5) * roadMapping.laneWidth();
        final int redRadius2 = (int) (2.5 * roadMapping.laneWidth()) / 2;
        final int whiteRadius2 = (int) (2.0 * roadMapping.laneWidth()) / 2;
        final int fontHeight = whiteRadius2;
        final int offsetY = (int) (0.4 * fontHeight);
        final Font font = new Font("SansSerif", Font.BOLD, fontHeight); //$NON-NLS-1$
        final FontMetrics fontMetrics = getFontMetrics(font);

        for (final SpeedLimit speedLimit : roadSegment.speedLimits()) {

            g.setFont(font);
            final RoadMapping.PosTheta posTheta = roadMapping.map(speedLimit.getPosition(), offset);

            final double speedLimitValueKmh = speedLimit.getSpeedLimitKmh();
            if (speedLimitValueKmh < 150) {
                g.setColor(Color.RED);
                g.fillOval((int) posTheta.x - redRadius2, (int) posTheta.y - redRadius2, 2 * redRadius2, 2 * redRadius2);
                g.setColor(Color.WHITE);
                g.fillOval((int) posTheta.x - whiteRadius2, (int) posTheta.y - whiteRadius2, 2 * whiteRadius2,
                        2 * whiteRadius2);
                g.setColor(Color.BLACK);
                final String text = String.valueOf((int) (speedLimit.getSpeedLimitKmh()));
                final int textWidth = fontMetrics.stringWidth(text);
                g.drawString(text, (int) (posTheta.x - textWidth / 2.0), (int) (posTheta.y + offsetY));
            } else {
                // Draw a line between points (x1,y1) and (x2,y2)
                // draw speed limit clearing
                g.setColor(Color.BLACK);
                g.fillOval((int) posTheta.x - redRadius2, (int) posTheta.y - redRadius2, 2 * redRadius2, 2 * redRadius2);
                g.setColor(Color.WHITE);
                g.fillOval((int) posTheta.x - whiteRadius2, (int) posTheta.y - whiteRadius2, 2 * whiteRadius2,
                        2 * whiteRadius2);
                g.setColor(Color.BLACK);
                final int xOnCircle = (int) (whiteRadius2 * Math.cos(Math.toRadians(45.)));
                final int yOnCircle = (int) (whiteRadius2 * Math.sin(Math.toRadians(45.)));
                final Graphics2D g2 = g;
                final Line2D line = new Line2D.Double((int) posTheta.x - xOnCircle, (int) posTheta.y + yOnCircle,
                        (int) posTheta.x + xOnCircle, (int) posTheta.y - yOnCircle);
                g2.setStroke(new BasicStroke(2)); // thicker than just one pixel when calling g.drawLine
                g2.draw(line);
            }
        }
    }

    private void drawSlopes(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            drawSlopesOnRoad(g, roadSegment);
        }
    }

    private void drawSlopesOnRoad(Graphics2D g, RoadSegment roadSegment) {
        if (roadSegment.slopes() == null) {
            return;
        }

        final RoadMapping roadMapping = roadSegment.roadMapping();
        assert roadMapping != null;
        final double laneWidth = 10; // ;
        final double offset = -(roadMapping.laneCount() / 2.0 + 1.5) * (roadMapping.laneWidth() + 1);
        final int redRadius2 = (int) (2.5 * laneWidth) / 2;
        final int whiteRadius2 = (int) (2.0 * laneWidth) / 2;
        final int fontHeight = whiteRadius2;
        final int offsetY = (int) (0.4 * fontHeight);
        final Font font = new Font("SansSerif", Font.BOLD, fontHeight); //$NON-NLS-1$
        final FontMetrics fontMetrics = getFontMetrics(font);

        for (final Slope slope : roadSegment.slopes()) {
            g.setFont(font);
            final RoadMapping.PosTheta posTheta = roadMapping.map(slope.getPosition(), offset);

            final double gradient = slope.getGradient() * 100;
            // if (gradient != 0) {
            g.setColor(Color.BLACK);
            final String text = String.valueOf((int) (gradient)) + " %";
            final int textWidth = fontMetrics.stringWidth(text);
            g.drawString(text, (int) (posTheta.x - textWidth / 2.0), (int) (posTheta.y + offsetY));

            // } else {
            // // Draw a line between points (x1,y1) and (x2,y2)
            // // draw speed limit clearing
            // g.setColor(Color.BLACK);
            // g.fillOval((int) posTheta.x - redRadius2, (int) posTheta.y - redRadius2, 2 * redRadius2, 2 * redRadius2);
            // g.setColor(Color.WHITE);
            // g.fillOval((int) posTheta.x - whiteRadius2, (int) posTheta.y - whiteRadius2, 2 * whiteRadius2,
            // 2 * whiteRadius2);
            // g.setColor(Color.BLACK);
            // final int xOnCircle = (int) (whiteRadius2 * Math.cos(Math.toRadians(45.)));
            // final int yOnCircle = (int) (whiteRadius2 * Math.sin(Math.toRadians(45.)));
            // final Graphics2D g2 = g;
            // final Line2D line = new Line2D.Double((int) posTheta.x - xOnCircle, (int) posTheta.y + yOnCircle,
            // (int) posTheta.x + xOnCircle, (int) posTheta.y - yOnCircle);
            // g2.setStroke(new BasicStroke(2)); // thicker than just one pixel when calling g.drawLine
            // g2.draw(line);
            // }
        }
    }

    /**
     * Draws the ids for the road sections, sources and sinks.
     *
     * @param g
     */
    private void drawRoadSectionIds(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            final RoadMapping roadMapping = roadSegment.roadMapping();
            // final int radius = (int) ((roadMapping.laneCount() + 2) * roadMapping.laneWidth());
            final RoadMapping.PosTheta posTheta = roadMapping.map(0.0);

            // draw the road segment's id
            final int fontHeight = 12;
            final Font font = new Font("SansSerif", Font.PLAIN, fontHeight); //$NON-NLS-1$
            g.setFont(font);
            g.setColor(Color.BLACK);
            g.drawString("R" + roadSegment.userId(), (int) (posTheta.x), (int) (posTheta.y)); //$NON-NLS-1$
        }
    }

    /**
     * Draws the sources.
     *
     * @param g
     */
    private void drawSources(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            final RoadMapping roadMapping = roadSegment.roadMapping();
            assert roadMapping != null;
            final int radius = (int) ((roadMapping.laneCount() + 2) * roadMapping.laneWidth());
            final RoadMapping.PosTheta posTheta;

            // draw the road segment source, if there is one
            final AbstractTrafficSource trafficSource = roadSegment.trafficSource();
            if (trafficSource != null) {
                g.setColor(sourceColor);
                posTheta = roadMapping.startPos();
                g.fillOval((int) posTheta.x - radius / 2, (int) posTheta.y - radius / 2, radius, radius);
                g.setColor(Color.BLACK);
                StringBuilder inflowStringBuilder = new StringBuilder();
                inflowStringBuilder.append("set/target inflow: ");
                inflowStringBuilder.append((int) (Units.INVS_TO_INVH * trafficSource.getTotalInflow(simulationTime())));
                inflowStringBuilder.append("/");
                inflowStringBuilder.append((int) (Units.INVS_TO_INVH * trafficSource.measuredInflow()));
                inflowStringBuilder.append(" veh/h");
                inflowStringBuilder.append(" (");
                inflowStringBuilder.append(trafficSource.getQueueLength());
                inflowStringBuilder.append(")");
                g.drawString(inflowStringBuilder.toString(), (int) (posTheta.x) + radius / 2, (int) (posTheta.y)
                        + radius / 2);
            }
        }
    }

    /**
     * Draws the sinks.
     *
     * @param g
     */
    private void drawSinks(Graphics2D g) {
        for (final RoadSegment roadSegment : roadNetwork) {
            final RoadMapping roadMapping = roadSegment.roadMapping();
            assert roadMapping != null;
            final int radius = (int) ((roadMapping.laneCount() + 2) * roadMapping.laneWidth());
            final RoadMapping.PosTheta posTheta;

            // draw the road segment sink, if there is one
            final TrafficSink sink = roadSegment.sink();
            if (sink != null) {
                g.setColor(sinkColor);
                posTheta = roadMapping.endPos();
                g.fillOval((int) posTheta.x - radius / 2, (int) posTheta.y - radius / 2, radius, radius);
                String outflowString = "outflow: " + (int) (Units.INVS_TO_INVH * sink.measuredOutflow()) + " veh/h";
                g.drawString(outflowString, (int) (posTheta.x) + radius / 2, (int) (posTheta.y) + radius / 2);
            }
        }
    }

    // ============================================================================================
    // SimulationRunnable callbacks
    // ============================================================================================

    /**
     * <p>
     * Implements SimulationRunnable.UpdateDrawingCallback.updateDrawing().
     * </p>
     * <p>
     * Calls repaint() which causes UI framework to asynchronously call update(g).
     * </p>
     */
    @Override
    public void updateDrawing(double simulationTime) {
        repaint();
    }

    /**
     * <p>
     * Implements SimulationRunnable.HandleExceptionCallback.handleException().
     * </p>
     * <p>
     * Called back from the TrafficRunnable thread, in the synchronization block, if an exception occurs.
     * </p>
     */
    @Override
    public void handleException(Exception e) {
        // if (e instanceof Vehicle.VehicleException) {
        // // if (e.getClass() == Vehicle.VehicleException.class) {
        // // something went wrong with the integration
        // final Vehicle.VehicleException v = (Vehicle.VehicleException) e;
        // final Vehicle vehicle = v.vehicle;
        // vehicleToHighlightId = vehicle.getId();
        // if (vehicleColorMode != VehicleColorMode.HIGHLIGHT_VEHICLE) {
        // vehicleColorModeSave = vehicleColorMode;
        // vehicleColorMode = VehicleColorMode.HIGHLIGHT_VEHICLE;
        // }
        // repaint();
        // if (DEBUG) {
        //                System.out.println("VehicleException id:" + vehicle.getId()); //$NON-NLS-1$
        //                System.out.println("  pos:" + vehicle.getPosition()); //$NON-NLS-1$
        //                System.out.println("  vel:" + vehicle.getSpeed()); //$NON-NLS-1$
        //                System.out.println("  roadSectionId:"); //$NON-NLS-1$
        // }
        // }
    }

}
TOP

Related Classes of org.movsim.viewer.graphics.TrafficCanvas

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.