/* * Copyright (c) 2021. * Ramon Antonio Rodriges Zalipynis (Architect & Developer), rodriges AT gis DOT land */ import ConvolveWindow; import ConvolveWindows; import InputConvolveWindow; import OutputConvolveWindow; /** * @author (c) R.A. Rodriges Zalipynis (Architect & Developer), rodriges AT wikience DOT org */ public class TCA { public static final int MAX_VEHICLE_SPEED = 3; public static final int MAX_VEHICLE_LENGTH = 3; public static final int MAX_VEHICLE_VISIBILITY = 5; public static final int LANE_IMPASSIBLE = -1; public static final int LANE_WEST_EAST = 0; public static final int LANE_SOUTH_NORTH = 1; public static final int LANE_ROAD_CROSSING = 2; public static final int LANE_TRAFFIC_LIGHTS = 3; public static final int LIGHTS_RED = 0; public static final int LIGHTS_YELLOW = 1; public static final int LIGHTS_GREEN = 2; public static final int CROSSING_WIDTH = 3; public static final int TURN_LEFT = 1; public static final int TURN_RIGHT = -1; public static final int LIGHTS_TICKS = 5; public void putVehicle(ConvolveWindows w) { double laned = w.input(0).get(0, 0); int lane = (int) laned; boolean is = w.input(0).random().nextDouble() > 0.85; double result = Double.MAX_VALUE; if (lane == LANE_TRAFFIC_LIGHTS) result = 4.; if (lane == LANE_ROAD_CROSSING) result = 2.; if (lane == LANE_SOUTH_NORTH && is) result = 1.; if (lane == LANE_SOUTH_NORTH && !is) result = -1.; if (lane == LANE_WEST_EAST && is) result = 0.; if (lane == LANE_WEST_EAST && !is) result = -2.; w.output(0).set(result, 0, 0); } public void setLength(ConvolveWindows w) { InputConvolveWindow win = w.input(0); double val = win.get(0, 0); if (val != 1 && val != 0) { if (val == 4) { // traffic lights w.output(0).set((double) 0, 0, 0); } else { w.output(0).set(null, 0, 0); } return; // no vehicle } // a south-north moving vehicle, just rotate the window if (val == 1) win = win.rotate(ConvolveWindow.Degrees._90); int cellsToClosestVehicle = 1; for (int i = 1; i < MAX_VEHICLE_LENGTH; i++) { Double next = win.get(i, 0); if (next == null || next >= 0) break; cellsToClosestVehicle++; } int vehicleLength = win.random().nextInt(cellsToClosestVehicle) + 1; w.output(0).set((double) (vehicleLength), 0, 0); } public void setSpeed(ConvolveWindows w) { double val = w.input(0).get(0, 0); Double output = null; if (val == 1 || val == 0) { output = (double) w.input(0).random().nextInt(4); } else { if (val == 4) { // traffic lights output = (double) (200 + w.output(0).random().nextInt(2) + 1); } } w.output(0).set(output, 0, 0); } public boolean isLeftTurnNeeded(InputConvolveWindow speed, int myLength) { // != null: convolution is not run for NoData (0,0) cells int mySpeed = speed.get(0, 0).intValue(); int myFrontBumper = myLength - 1; for (int x = myFrontBumper; x <= MAX_VEHICLE_VISIBILITY; x++) { Double frontSpeed = speed.get(x, 0); if (frontSpeed != null) { if (frontSpeed < mySpeed) return speed.random().nextDouble() > 0.4; else return false; } } // we can still turn left with some probability even if there is no // slow-moving vehicle in front of us return speed.random().nextDouble() > 0.8; } public boolean isRightTurnNeeded(InputConvolveWindow speed) { return speed.random().nextDouble() > 0.8; } public boolean isLaneTurnPossible(InputConvolveWindow speed, InputConvolveWindow length, int turnType) { // border is on the left/right, we are at the leftmost/rightmost lane if (speed.get(0, turnType) == null) return false; for (int x = 0; x <= MAX_VEHICLE_VISIBILITY; x++) { Double backSpeed = speed.get(-x, turnType); Double backLength = length.get(-x, turnType); if (backSpeed != null) { // x = vehicle's rear bumper, lets calculate the front bumper int xFrontBumper_plus_one = x - backLength.intValue(); if (xFrontBumper_plus_one <= 0) return false; // a vehicle is just near us if (xFrontBumper_plus_one - backSpeed.intValue() <= 0) return false; // a too fast-moving vehicle } } return true; } public void turnLeftPhase(ConvolveWindows windows) { turnPhase(windows, TURN_LEFT); } public void turnRightPhase(ConvolveWindows windows) { turnPhase(windows, TURN_RIGHT); } private void turnPhase(ConvolveWindows windows, int turnType) { InputConvolveWindow lane = windows.input(0); InputConvolveWindow speed = windows.input(1); InputConvolveWindow length = windows.input(2); OutputConvolveWindow outSpeed = windows.output(0); OutputConvolveWindow outLength = windows.output(1); boolean notOnCrossing = true; // never == null int laneType = lane.get(0, 0).intValue(); int myLength = length.get(0, 0).intValue(); int mySpeed = speed.get(0, 0).intValue(); int codedSpeed = mySpeed; ConvolveWindow.Degrees degrees = ConvolveWindow.Degrees._0; boolean iAmSNVehicle = false; switch (laneType) { case LANE_TRAFFIC_LIGHTS: outSpeed.set((double) mySpeed, 0, 0); outLength.set((double) myLength, 0, 0); return; case LANE_ROAD_CROSSING: notOnCrossing = false; if (mySpeed >= 100) { iAmSNVehicle = true; degrees = ConvolveWindow.Degrees._90; mySpeed %= 10; } break; case LANE_WEST_EAST: break; case LANE_SOUTH_NORTH: degrees = ConvolveWindow.Degrees._90; iAmSNVehicle = true; break; } lane = lane.rotate(degrees); speed = speed.rotate(degrees); length = length.rotate(degrees); outSpeed = outSpeed.rotate(degrees); outLength = outLength.rotate(degrees); if (notOnCrossing) { int y = 0; boolean isTurnNeeded = false; boolean isTurnPossible = false; switch (turnType) { case TURN_LEFT: isTurnNeeded = isLeftTurnNeeded(speed, myLength); isTurnPossible = isLaneTurnPossible(speed, length, turnType); break; case TURN_RIGHT: isTurnNeeded = isRightTurnNeeded(speed); isTurnPossible = isLaneTurnPossible(speed, length, turnType); break; } if (isTurnNeeded && isTurnPossible) { y = turnType; } outSpeed.set((double) mySpeed, 0, y); outLength.set((double) myLength, 0, y); } else { boolean isTurnPossible = true; // it is not our turn to turn if ((turnType == TURN_LEFT && iAmSNVehicle) || (turnType == TURN_RIGHT && !iAmSNVehicle)) { isTurnPossible = false; } // a vehicle may not be allowed to turn as it is not on the outer lane Double v = lane.get(0, turnType); if (v == null) { isTurnPossible = false; } else { if (v.intValue() == LANE_ROAD_CROSSING) { isTurnPossible = false; } } if (!isTurnPossible) { outSpeed.set((double) codedSpeed, 0, 0); outLength.set((double) myLength, 0, 0); return; } boolean isTurnNeeded = windows.input(0).random().nextDouble() > 0.8; isTurnPossible = isCrossingTurnPossible(length, turnType); double theSpeed; int y; if (isTurnNeeded && isTurnPossible) { y = turnType; theSpeed = mySpeed; } else { y = 0; theSpeed = codedSpeed; } outSpeed.set(theSpeed, 0, y); outLength.set((double) myLength, 0, y); } } private boolean isCrossingTurnPossible(InputConvolveWindow length, int turnType) { // check whether there is sufficient space int myLength = length.get(0, 0).intValue(); int y = turnType; Double len = length.get(0, y); while (len == null && myLength > 0) { myLength--; y += turnType; len = length.get(0, y); } return myLength == 0; } public int[] findTrafficLights(InputConvolveWindow lane, boolean iAmSNVehicle) { int inc = iAmSNVehicle ? 1 : -1; int y = inc; int x = 0; Double up = lane.get(0, y); if (up != null && up == LANE_TRAFFIC_LIGHTS) { return new int[]{x, y}; } while (lane.get(x, y) != null) { y += inc; } while (x <= MAX_VEHICLE_VISIBILITY) { up = lane.get(x, y); if (up != null && up == LANE_TRAFFIC_LIGHTS) { return new int[]{x, y}; } x++; } return null; } public int getLightsState(int value, boolean iAmSNVehicle) { // xx < 100 ticks for green for WE // xx > 100 ticks for green for SE // x = 201 - yellow, next green is for WE // x = 202 - yellow, next green is for SN // initially 201 or 202 randomly in speed, length is always 0 if (value == 201 || value == 202) { return LIGHTS_YELLOW; } if (value < 100) { if (iAmSNVehicle) { return LIGHTS_RED; } else { return LIGHTS_GREEN; } } else { if (iAmSNVehicle) { return LIGHTS_GREEN; } else { return LIGHTS_RED; } } } public void lightsPhase(ConvolveWindows windows) { // xx < 100 ticks for green for WE // xx > 100 ticks for green for SE // x = 201 - yellow, next green is for WE // x = 202 - yellow, next green is for SN // initially 201 or 202 randomly in speed, length is always 0 InputConvolveWindow lane = windows.input(0); InputConvolveWindow speed = windows.input(1); InputConvolveWindow length = windows.input(2); int state = speed.get(0, 0).intValue(); int aLength = length.get(0, 0).intValue(); OutputConvolveWindow outSpeed = windows.output(0); OutputConvolveWindow outLength = windows.output(1); if (lane.get(0, 0).intValue() != LANE_TRAFFIC_LIGHTS) { outSpeed.set((double) state, 0, 0); outLength.set((double) aLength, 0, 0); return; } if (state == 201 || state == 202) { // check the road crossing boolean areVehiclesOnCrossing = false; for (int x = 1; x <= CROSSING_WIDTH; x++) { for (int y = 1; y <= CROSSING_WIDTH; y++) { if (speed.get(x, y) != null) { areVehiclesOnCrossing = true; break; } } if (areVehiclesOnCrossing) break; } if (!areVehiclesOnCrossing) { // switch the light state = state == 201 ? LIGHTS_TICKS : 100 + LIGHTS_TICKS; } outSpeed.set((double) state, 0, 0); outLength.set((double) 0, 0, 0); return; } boolean greenForSN = false; if (state >= 100) { greenForSN = true; state = state % 100; } if (state == 0) { // switch the lights state = greenForSN ? 201 : 202; } else { state--; if (greenForSN) state += 100; } outSpeed.set((double) state, 0, 0); outLength.set((double) 0, 0, 0); } public void moveForwardPhase(ConvolveWindows windows) { InputConvolveWindow lane = windows.input(0); InputConvolveWindow speed = windows.input(1); InputConvolveWindow length = windows.input(2); InputConvolveWindow temperature = windows.input(3); OutputConvolveWindow outSpeed = windows.output(0); OutputConvolveWindow outLength = windows.output(1); // never == null int laneType = lane.get(0, 0).intValue(); int myLength = length.get(0, 0).intValue(); int mySpeed = speed.get(0, 0).intValue(); double temp = temperature.get(0, 0); boolean iAmSNVehicle = false; boolean atARoadCrossing = false; ConvolveWindow.Degrees degrees = ConvolveWindow.Degrees._0; switch (laneType) { case LANE_TRAFFIC_LIGHTS: outSpeed.set((double) mySpeed, 0, 0); outLength.set((double) myLength, 0, 0); return; case LANE_ROAD_CROSSING: if (mySpeed >= 100) { iAmSNVehicle = true; degrees = ConvolveWindow.Degrees._90; mySpeed %= 10; } atARoadCrossing = true; break; case LANE_WEST_EAST: break; case LANE_SOUTH_NORTH: degrees = ConvolveWindow.Degrees._90; iAmSNVehicle = true; break; } lane = lane.rotate(degrees); speed = speed.rotate(degrees); outSpeed = outSpeed.rotate(degrees); outLength = outLength.rotate(degrees); // cannot be less than vehicle's length: (0, 0) is vehicle's rear bumper int distToNearestVehicle = myLength; while (distToNearestVehicle <= MAX_VEHICLE_VISIBILITY) { if (speed.get(distToNearestVehicle, 0) != null) break; distToNearestVehicle++; } distToNearestVehicle -= myLength; // 1, 2: acceleration & braking int newSpeed = Math.min(mySpeed + 1, MAX_VEHICLE_SPEED - myLength + 1); newSpeed = Math.min(newSpeed, distToNearestVehicle); // 3 randomization if (speed.random().nextDouble() < 0.1) newSpeed = Math.max(0, newSpeed - 1); // temperature if (temp < 10 && temperature.random().nextDouble() < 0.2) newSpeed = Math.max(0, newSpeed - 1); // 4 account for traffic lights if (!atARoadCrossing) { int[] lights = findTrafficLights(lane, iAmSNVehicle); if (lights != null) { int xL = lights[0]; int lState = getLightsState(speed.get(xL, lights[1]).intValue(), iAmSNVehicle); if (lState == LIGHTS_RED || lState == LIGHTS_YELLOW) { newSpeed = Math.min(Math.max(xL - myLength, 0), newSpeed); } } } // 5 vehicle movement int speedValue = newSpeed; int x = newSpeed; Double destinationLaneCellD = lane.get(newSpeed, 0); if (destinationLaneCellD != null) { int destinationLaneCell = destinationLaneCellD.intValue(); // vehicle is at the road crossing and we need to save its moving direction if (destinationLaneCell == LANE_ROAD_CROSSING) { if (iAmSNVehicle) { speedValue = 100 + newSpeed; } } } outSpeed.set((double) speedValue, x, 0); outLength.set((double) myLength, x, 0); } }