// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.turnlanes.model;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
public class Lane {
public enum Kind {
EXTRA_LEFT,
EXTRA_RIGHT,
REGULAR;
public boolean isExtra() {
return this == EXTRA_LEFT || this == EXTRA_RIGHT;
}
}
static List<Lane> load(Road.End roadEnd) {
final List<Lane> result = new ArrayList<>();
int i;
i = 0;
for (double l : roadEnd.getLengths(Kind.EXTRA_LEFT)) {
result.add(new Lane(roadEnd, --i, Kind.EXTRA_LEFT, l));
}
Collections.reverse(result);
final int regulars = getRegularCount(roadEnd.getWay(), roadEnd.getJunction().getNode());
for (i = 1; i <= regulars; ++i) {
result.add(new Lane(roadEnd, i));
}
i = 0;
for (double l : roadEnd.getLengths(Kind.EXTRA_RIGHT)) {
result.add(new Lane(roadEnd, ++i, Kind.EXTRA_RIGHT, l));
}
return result;
}
static List<Double> loadLengths(Relation r, String key, double lengthBound) {
final List<Double> result = new ArrayList<>();
if (r != null && r.get(key) != null) {
for (String s : Constants.SPLIT_PATTERN.split(r.get(key))) {
// TODO what should the exact input be (there should probably be
// a unit (m))
final Double length = Double.parseDouble(s.trim());
if (length >= lengthBound) {
result.add(length);
}
}
}
return result;
}
static int getRegularCount(Way w, Node end) {
final int count = Utils.parseIntTag(w, "lanes");
final boolean forward = w.lastNode().equals(end);
if (w.hasDirectionKeys()) {
return getRegularCountOneWay(w, forward, count);
} else {
return getRegularCountTwoWay(w, forward, count);
}
}
private static int getRegularCountOneWay(Way w, boolean forward, final int count) {
if (forward ^ "-1".equals(w.get("oneway"))) {
return count;
} else {
return 0;
}
}
private static int getRegularCountTwoWay(Way w, boolean forward, final int count) {
if (w.get("lanes:backward") != null) {
final int backwardCount = Utils.parseIntTag(w, "lanes:backward");
return forward ? count - backwardCount : backwardCount;
}
if (w.get("lanes:forward") != null) {
final int forwardCount = Utils.parseIntTag(w, "lanes:forward");
return forward ? forwardCount : count - forwardCount;
}
// default: round up in forward direction...
return forward ? (count + 1) / 2 : count / 2;
}
private final Road.End roadEnd;
private final int index;
private final Kind kind;
private Set<Turn> turns;
private double length = -1;
public Lane(Road.End roadEnd, int index) {
this.roadEnd = roadEnd;
this.index = index;
this.kind = Kind.REGULAR;
}
public Lane(Road.End roadEnd, int index, Kind kind, double length) {
assert kind == Kind.EXTRA_LEFT || kind == Kind.EXTRA_RIGHT;
this.roadEnd = roadEnd;
this.index = index;
this.kind = kind;
this.length = length;
if (length <= 0) {
throw new IllegalArgumentException("Length must be positive");
}
}
public Road getRoad() {
return roadEnd.getRoad();
}
public Kind getKind() {
return kind;
}
public double getLength() {
return isExtra() ? length : getRoad().getLength();
}
public void setLength(double length) {
if (!isExtra()) {
throw new UnsupportedOperationException("Length can only be set for extra lanes.");
} else if (length <= 0) {
throw new IllegalArgumentException("Length must positive.");
}
this.length = length;
// TODO if needed, increase length of other lanes
getOutgoingRoadEnd().updateLengths();
}
public boolean isExtra() {
return getKind() != Kind.REGULAR;
}
public int getIndex() {
return index;
}
public Junction getOutgoingJunction() {
return getOutgoingRoadEnd().getJunction();
}
public Junction getIncomingJunction() {
return getIncomingRoadEnd().getJunction();
}
public Road.End getOutgoingRoadEnd() {
return roadEnd;
}
public Road.End getIncomingRoadEnd() {
return roadEnd.getOppositeEnd();
}
public ModelContainer getContainer() {
return getRoad().getContainer();
}
public void addTurn(List<Road> via, Road.End to) {
final GenericCommand cmd = new GenericCommand(getOutgoingJunction().getNode().getDataSet(), tr("Add turn"));
Relation existing = null;
for (Turn t : to.getTurns()) {
if (t.getFrom().getOutgoingRoadEnd().equals(getOutgoingRoadEnd()) && t.getVia().equals(via)) {
if (t.getFrom().equals(this)) {
// was already added
return;
}
existing = t.getRelation();
}
}
final Relation r;
if (existing == null) {
r = new Relation();
r.put("type", Constants.TYPE_TURNS);
r.addMember(new RelationMember(Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay()));
if (via.isEmpty()) {
r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, getOutgoingJunction().getNode()));
} else {
for (Way w : Utils.flattenVia(getOutgoingJunction().getNode(), via, to.getJunction().getNode())) {
r.addMember(new RelationMember(Constants.TURN_ROLE_VIA, w));
}
}
r.addMember(new RelationMember(Constants.TURN_ROLE_TO, to.getWay()));
cmd.add(r);
} else {
r = existing;
}
final String key = isExtra() ? Constants.TURN_KEY_EXTRA_LANES : Constants.TURN_KEY_LANES;
final List<Integer> lanes = Turn.indices(r, key);
lanes.add(getIndex());
cmd.backup(r).put(key, Turn.join(lanes));
Main.main.undoRedo.add(cmd);
}
public Set<Turn> getTurns() {
return turns;
}
public void remove() {
if (!isExtra()) {
throw new UnsupportedOperationException();
}
final GenericCommand cmd = new GenericCommand(getOutgoingJunction().getNode().getDataSet(), tr("Delete lane."));
for (Turn t : getTurns()) {
t.remove(cmd);
}
getOutgoingRoadEnd().removeLane(cmd, this);
Main.main.undoRedo.add(cmd);
}
void initialize() {
final Set<Turn> turns = Turn.load(getContainer(), Constants.TURN_ROLE_FROM, getOutgoingRoadEnd().getWay());
final Iterator<Turn> it = turns.iterator();
while (it.hasNext()) {
final Turn t = it.next();
if (!t.getFrom().equals(this)) {
it.remove();
}
}
this.turns = Collections.unmodifiableSet(turns);
}
}