// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.util; import java.util.Locale; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.tools.Geometry; import org.openstreetmap.josm.tools.SubclassFilteredCollection; import org.openstreetmap.josm.tools.Utils; /** * Determines how an icon is to be rotated depending on the primitive to be displayed. * @since 8199 (creation) * @since 10599 (functional interface) */ @FunctionalInterface public interface RotationAngle { /** * The rotation along a way. */ final class WayDirectionRotationAngle implements RotationAngle { @Override public double getRotationAngle(OsmPrimitive p) { if (!(p instanceof Node)) { return 0; } final Node n = (Node) p; final SubclassFilteredCollection<OsmPrimitive, Way> ways = Utils.filteredCollection(n.getReferrers(), Way.class); if (ways.isEmpty()) { return 0; } final Way w = ways.iterator().next(); final int idx = w.getNodes().indexOf(n); if (idx == 0) { return -Geometry.getSegmentAngle(n.getEastNorth(), w.getNode(idx + 1).getEastNorth()); } else { return -Geometry.getSegmentAngle(w.getNode(idx - 1).getEastNorth(), n.getEastNorth()); } } @Override public String toString() { return "way-direction"; } @Override public int hashCode() { return 1; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } return true; } } /** * A static rotation */ final class StaticRotationAngle implements RotationAngle { private final double angle; private StaticRotationAngle(double angle) { this.angle = angle; } @Override public double getRotationAngle(OsmPrimitive p) { return angle; } @Override public String toString() { return angle + "rad"; } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(angle); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } StaticRotationAngle other = (StaticRotationAngle) obj; if (Double.doubleToLongBits(angle) != Double.doubleToLongBits(other.angle)) { return false; } return true; } } /** * A no-rotation angle that always returns 0. * @since 11726 */ RotationAngle NO_ROTATION = new StaticRotationAngle(0); /** * Calculates the rotation angle depending on the primitive to be displayed. * @param p primitive * @return rotation angle */ double getRotationAngle(OsmPrimitive p); /** * Always returns the fixed {@code angle}. * @param angle angle * @return rotation angle */ static RotationAngle buildStaticRotation(final double angle) { return new StaticRotationAngle(angle); } /** * Parses the rotation angle from the specified {@code string}. * @param string angle as string * @return rotation angle */ static RotationAngle buildStaticRotation(final String string) { try { return buildStaticRotation(parseCardinalRotation(string)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid string: " + string, e); } } /** * Converts an angle diven in cardinal directions to radians. * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast}, * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south}, * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}. * @param cardinal the angle in cardinal directions * @return the angle in radians */ static double parseCardinalRotation(final String cardinal) { switch (cardinal.toLowerCase(Locale.ENGLISH)) { case "n": case "north": return 0; // 0 degree => 0 radian case "ne": case "northeast": return Math.toRadians(45); case "e": case "east": return Math.toRadians(90); case "se": case "southeast": return Math.toRadians(135); case "s": case "south": return Math.PI; // 180 degree case "sw": case "southwest": return Math.toRadians(225); case "w": case "west": return Math.toRadians(270); case "nw": case "northwest": return Math.toRadians(315); default: throw new IllegalArgumentException("Unexpected cardinal direction " + cardinal); } } /** * Computes the angle depending on the referencing way segment, or {@code 0} if none exists. * @return rotation angle */ static RotationAngle buildWayDirectionRotation() { return new WayDirectionRotationAngle(); } }