// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.turnlanes.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
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 Utils {
private static final Set<String> ROAD_HIGHWAY_VALUES = Collections.unmodifiableSet(new HashSet<>(Arrays
.asList("motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary",
"secondary_link", "tertiary", "tertiary_link", "residential", "unclassified", "road", "living_street", "service",
"track", "pedestrian", "raceway", "services")));
private Utils() {
// Hide default constructor for utilities classes
}
public static boolean isRoad(Way w) {
return ROAD_HIGHWAY_VALUES.contains(w.get("highway"));
}
public static List<Way> filterRoads(List<OsmPrimitive> of) {
final List<Way> result = new ArrayList<>();
for (OsmPrimitive p : of) {
if (p.getType() == OsmPrimitiveType.WAY && Utils.isRoad((Way) p)) {
result.add((Way) p);
}
}
return result;
}
public static Node getMemberNode(Relation r, String role) {
return getMember(r, role, OsmPrimitiveType.NODE).getNode();
}
public static Way getMemberWay(Relation r, String role) {
return getMember(r, role, OsmPrimitiveType.WAY).getWay();
}
public static RelationMember getMember(Relation r, String role, OsmPrimitiveType type) {
final List<RelationMember> candidates = getMembers(r, role, type);
if (candidates.isEmpty()) {
throw UnexpectedDataException.Kind.NO_MEMBER.chuck(role);
} else if (candidates.size() > 1) {
throw UnexpectedDataException.Kind.MULTIPLE_MEMBERS.chuck(role);
}
return candidates.get(0);
}
public static List<RelationMember> getMembers(Relation r, String role, OsmPrimitiveType type) {
final List<RelationMember> result = getMembers(r, role);
for (RelationMember m : getMembers(r, role)) {
if (m.getType() != type) {
throw UnexpectedDataException.Kind.WRONG_MEMBER_TYPE.chuck(role, m.getType(), type);
}
}
return result;
}
public static List<RelationMember> getMembers(Relation r, String role) {
final List<RelationMember> result = new ArrayList<>();
for (RelationMember m : r.getMembers()) {
if (m.getRole().equals(role)) {
result.add(m);
}
}
return result;
}
public static List<Node> getMemberNodes(Relation r, String role) {
return mapMembers(getMembers(r, role, OsmPrimitiveType.NODE), Node.class);
}
public static List<Way> getMemberWays(Relation r, String role) {
return mapMembers(getMembers(r, role, OsmPrimitiveType.WAY), Way.class);
}
private static <T> List<T> mapMembers(List<RelationMember> ms, Class<T> t) {
final List<T> result = new ArrayList<>(ms.size());
for (RelationMember m : ms) {
result.add(t.cast(m.getMember()));
}
return result;
}
/**
*
* @param a first way
* @param b second way
* @return the node at which {@code a} and {@code b} are connected
*/
public static Node lineUp(Way a, Way b) {
final Set<Node> s = new HashSet<>(Arrays.asList(a.firstNode(), a.lastNode(), b.firstNode(), b.lastNode()));
if (a.firstNode() == a.lastNode() || b.firstNode().equals(b.lastNode()) || s.size() == 2) {
throw new IllegalArgumentException("Cycles are not allowed.");
} else if (s.size() == 4) {
throw new IllegalArgumentException("Ways are not connected (at their first and last nodes).");
}
if (a.firstNode() == b.firstNode() || a.lastNode() == b.firstNode()) {
return b.firstNode();
} else if (a.firstNode() == b.lastNode() || a.lastNode() == b.lastNode()) {
return b.lastNode();
} else {
throw new AssertionError();
}
}
public static Node getOppositeEnd(Way w, Node n) {
final boolean first = n.equals(w.firstNode());
final boolean last = n.equals(w.lastNode());
if (first && last) {
throw new IllegalArgumentException("Way starts as well as ends at the given node.");
} else if (first) {
return w.lastNode();
} else if (last) {
return w.firstNode();
} else {
throw new IllegalArgumentException("Way neither starts nor ends at given node.");
}
}
/**
* Orders the {@code ways} such that the combined ways out of each returned list form a path (in
* order) from one node out of {@code nodes} to another out of {@code nodes}.
*
* <ul>
* <li>Each way is used exactly once.</li>
* <li>Paths contain no {@code nodes} excepting the first and last nodes.</li>
* <li>Paths contain no loops w.r.t. the ways' first and last nodes</li>
* </ul>
*
* @param ways
* ways to be ordered
* @param nodes
* start/end nodes
* @return ordered list
* @throws IllegalArgumentException
* if the ways can't be ordered
*/
public static List<Route> orderWays(Iterable<Way> ways, Iterable<Node> nodes) {
final List<Way> ws = new LinkedList<>(CollectionUtils.toList(ways));
final Set<Node> ns = new HashSet<>(CollectionUtils.toList(nodes));
final List<Route> result = new ArrayList<>();
while (!ws.isEmpty()) {
result.add(findPath(ws, ns));
}
return result;
}
private static Route findPath(List<Way> ws, Set<Node> ns) {
final Way w = findPathSegment(ws, ns);
final boolean first = ns.contains(w.firstNode());
final boolean last = ns.contains(w.lastNode());
if (first && last) {
return Route.create(Arrays.asList(w), w.firstNode());
} else if (!first && !last) {
throw new AssertionError();
}
final List<Way> result = new ArrayList<>();
result.add(w);
Node n = first ? w.lastNode() : w.firstNode();
while (true) {
final Way next = findPathSegment(ws, Arrays.asList(n));
result.add(next);
n = getOppositeEnd(next, n);
if (ns.contains(n)) {
return Route.create(result, first ? w.firstNode() : w.lastNode());
}
}
}
private static Way findPathSegment(List<Way> ws, Collection<Node> ns) {
final Iterator<Way> it = ws.iterator();
while (it.hasNext()) {
final Way w = it.next();
if (ns.contains(w.firstNode()) || ns.contains(w.lastNode())) {
it.remove();
return w;
}
}
throw new IllegalArgumentException("Ways can't be ordered.");
}
public static Iterable<Way> flattenVia(Node start, List<Road> via, Node end) {
final List<Way> result = new ArrayList<>();
Node n = start;
for (Road r : via) {
final Iterable<Route.Segment> segments = r.getRoute().getFirstSegment().getWay().isFirstLastNode(n) ? r
.getRoute().getSegments() : CollectionUtils.reverse(r.getRoute().getSegments());
for (Route.Segment s : segments) {
result.add(s.getWay());
n = Utils.getOppositeEnd(s.getWay(), n);
}
}
if (!end.equals(n)) {
throw new IllegalArgumentException("The given via ways don't end at the given node.");
}
return result;
}
public static int parseIntTag(OsmPrimitive primitive, String tag) {
final String value = primitive.get(tag);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw UnexpectedDataException.Kind.INVALID_TAG_FORMAT.chuck(tag, value);
}
}
throw UnexpectedDataException.Kind.MISSING_TAG.chuck(tag);
}
}