// 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.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
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.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils;
public final class Turn {
static Set<Turn> load(ModelContainer c, String role, OsmPrimitive primitive) {
final Set<Turn> result = new HashSet<>();
for (Relation r : OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) {
if (!r.isUsable() || !r.get("type").equals(Constants.TYPE_TURNS)) {
continue;
}
for (RelationMember m : r.getMembers()) {
if (m.getRole().equals(role) && m.getMember().equals(primitive)) {
result.addAll(load(c, r));
}
}
}
return result;
}
static Set<Turn> load(ModelContainer c, Relation r) {
for (RelationMember m : r.getMembers()) {
if (m.getRole().equals(Constants.TURN_ROLE_VIA)) {
if (m.isNode()) {
return loadWithViaNode(c, r);
} else if (m.isWay()) {
return loadWithViaWays(c, r);
}
}
}
throw new IllegalArgumentException("No via node or way(s).");
}
private static Set<Turn> loadWithViaWays(ModelContainer c, Relation r) {
final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
if (!c.hasRoad(from) || !c.hasRoad(to)) {
return Collections.emptySet();
}
final List<Way> tmp = Utils.getMemberWays(r, Constants.TURN_ROLE_VIA);
final LinkedList<Road> via = new LinkedList<>();
final Road.End fromRoadEnd = c.getJunction(Utils.lineUp(from, tmp.get(0))).getRoadEnd(from);
Node n = fromRoadEnd.getJunction().getNode();
final Iterator<Way> it = tmp.iterator();
while (it.hasNext()) {
final Way w = it.next();
if (!c.hasRoad(w)) {
return Collections.emptySet();
}
final Road v = c.getRoad(w);
via.add(v);
n = Utils.getOppositeEnd(w, n);
if (!v.isPrimary()) {
throw new IllegalStateException("The road is not part of the junction.");
}
final Iterator<Route.Segment> it2 = (v.getRoute().getFirstSegment().getWay().equals(w) ? v.getRoute()
.getSegments() : CollectionUtils.reverse(v.getRoute().getSegments())).iterator();
it2.next(); // first is done
while (it2.hasNext()) {
final Way w2 = it2.next().getWay();
n = Utils.getOppositeEnd(w2, n);
if (!it.hasNext() || !w2.equals(it.next())) {
throw new IllegalStateException("The via ways of the relation do not form a road.");
}
}
}
final Road.End toRoadEnd = c.getJunction(n).getRoadEnd(to);
n = Utils.getOppositeEnd(to, n);
final Set<Turn> result = new HashSet<>();
for (int i : indices(r, Constants.TURN_KEY_LANES)) {
result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), via, toRoadEnd));
}
for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
result.add(new Turn(r, fromRoadEnd.getExtraLane(i), via, toRoadEnd));
}
return result;
}
static List<Integer> indices(Relation r, String key) {
final String joined = r.get(key);
if (joined == null) {
return new ArrayList<>(1);
}
final List<Integer> result = new ArrayList<>();
for (String lane : Constants.SPLIT_PATTERN.split(joined)) {
result.add(Integer.parseInt(lane));
}
return result;
}
private static Set<Turn> loadWithViaNode(ModelContainer c, Relation r) {
final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM);
final Node via = Utils.getMemberNode(r, Constants.TURN_ROLE_VIA);
final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO);
if (!c.hasRoad(from) || !c.hasJunction(via) || !c.hasRoad(to)) {
return Collections.emptySet();
}
final Junction j = c.getJunction(via);
final Road.End fromRoadEnd = j.getRoadEnd(from);
final Road.End toRoadEnd = j.getRoadEnd(to);
final Set<Turn> result = new HashSet<>();
for (int i : indices(r, Constants.TURN_KEY_LANES)) {
result.add(new Turn(r, fromRoadEnd.getLane(Lane.Kind.REGULAR, i), Collections.<Road>emptyList(), toRoadEnd));
}
for (int i : indices(r, Constants.TURN_KEY_EXTRA_LANES)) {
result.add(new Turn(r, fromRoadEnd.getExtraLane(i), Collections.<Road>emptyList(), toRoadEnd));
}
return result;
}
static String join(List<Integer> list) {
if (list.isEmpty()) {
return null;
}
final StringBuilder builder = new StringBuilder(list.size() * (2 + Constants.SEPARATOR.length()));
for (int e : list) {
builder.append(e).append(Constants.SEPARATOR);
}
builder.setLength(builder.length() - Constants.SEPARATOR.length());
return builder.toString();
}
private final Relation relation;
private final Lane from;
private final List<Road> via;
private final Road.End to;
public Turn(Relation relation, Lane from, List<Road> via, Road.End to) {
this.relation = relation;
this.from = from;
this.via = via;
this.to = to;
}
public Lane getFrom() {
return from;
}
public List<Road> getVia() {
return via;
}
public Road.End getTo() {
return to;
}
Relation getRelation() {
return relation;
}
public void remove() {
final GenericCommand cmd = new GenericCommand(relation.getDataSet(), tr("Delete turn."));
remove(cmd);
Main.main.undoRedo.add(cmd);
}
void remove(GenericCommand cmd) {
final List<Integer> lanes = indices(relation, Constants.TURN_KEY_LANES);
final List<Integer> extraLanes = indices(relation, Constants.TURN_KEY_EXTRA_LANES);
// TODO understand & document
if (lanes.size() + extraLanes.size() == 1 && (from.isExtra() ^ !lanes.isEmpty())) {
cmd.backup(relation).setDeleted(true);
// relation.getDataSet().removePrimitive(relation.getPrimitiveId());
} else if (from.isExtra()) {
extraLanes.remove(Integer.valueOf(from.getIndex()));
} else {
lanes.remove(Integer.valueOf(from.getIndex()));
}
cmd.backup(relation).put(Constants.TURN_KEY_LANES, lanes.isEmpty() ? null : join(lanes));
cmd.backup(relation).put(Constants.TURN_KEY_EXTRA_LANES, extraLanes.isEmpty() ? null : join(extraLanes));
}
void fixReferences(GenericCommand cmd, boolean left, int index) {
final List<Integer> fixed = new ArrayList<>();
for (int i : indices(relation, Constants.TURN_KEY_EXTRA_LANES)) {
if (left ? i < index : i > index) {
fixed.add(left ? i + 1 : i - 1);
} else {
fixed.add(i);
}
}
cmd.backup(relation).put(Constants.TURN_KEY_EXTRA_LANES, join(fixed));
}
}