001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.util.Locale; 005 006import org.openstreetmap.josm.data.osm.Node; 007import org.openstreetmap.josm.data.osm.OsmPrimitive; 008import org.openstreetmap.josm.data.osm.Way; 009 010/** 011 * Determines how an icon is to be rotated depending on the primitive to be displayed. 012 * @since 8199 (creation) 013 * @since 10599 (functional interface) 014 * @since 12756 (moved from {@code gui.util} package) 015 */ 016@FunctionalInterface 017public interface RotationAngle { 018 019 /** 020 * The rotation along a way. 021 */ 022 final class WayDirectionRotationAngle implements RotationAngle { 023 @Override 024 public double getRotationAngle(OsmPrimitive p) { 025 if (!(p instanceof Node)) { 026 return 0; 027 } 028 final Node n = (Node) p; 029 final SubclassFilteredCollection<OsmPrimitive, Way> ways = Utils.filteredCollection(n.getReferrers(), Way.class); 030 if (ways.isEmpty()) { 031 return 0; 032 } 033 final Way w = ways.iterator().next(); 034 final int idx = w.getNodes().indexOf(n); 035 if (idx == 0) { 036 return -Geometry.getSegmentAngle(n.getEastNorth(), w.getNode(idx + 1).getEastNorth()); 037 } else { 038 return -Geometry.getSegmentAngle(w.getNode(idx - 1).getEastNorth(), n.getEastNorth()); 039 } 040 } 041 042 @Override 043 public String toString() { 044 return "way-direction"; 045 } 046 047 @Override 048 public int hashCode() { 049 return 1; 050 } 051 052 @Override 053 public boolean equals(Object obj) { 054 if (this == obj) { 055 return true; 056 } 057 return obj != null && getClass() == obj.getClass(); 058 } 059 } 060 061 /** 062 * A static rotation 063 */ 064 final class StaticRotationAngle implements RotationAngle { 065 private final double angle; 066 067 private StaticRotationAngle(double angle) { 068 this.angle = angle; 069 } 070 071 @Override 072 public double getRotationAngle(OsmPrimitive p) { 073 return angle; 074 } 075 076 @Override 077 public String toString() { 078 return angle + "rad"; 079 } 080 081 @Override 082 public int hashCode() { 083 final int prime = 31; 084 int result = 1; 085 long temp = Double.doubleToLongBits(angle); 086 result = prime * result + (int) (temp ^ (temp >>> 32)); 087 return result; 088 } 089 090 @Override 091 public boolean equals(Object obj) { 092 if (this == obj) { 093 return true; 094 } 095 if (obj == null || getClass() != obj.getClass()) { 096 return false; 097 } 098 StaticRotationAngle other = (StaticRotationAngle) obj; 099 return Double.doubleToLongBits(angle) == Double.doubleToLongBits(other.angle); 100 } 101 } 102 103 /** 104 * A no-rotation angle that always returns 0. 105 * @since 11726 106 */ 107 RotationAngle NO_ROTATION = new StaticRotationAngle(0); 108 109 /** 110 * Calculates the rotation angle depending on the primitive to be displayed. 111 * @param p primitive 112 * @return rotation angle 113 */ 114 double getRotationAngle(OsmPrimitive p); 115 116 /** 117 * Always returns the fixed {@code angle}. 118 * @param angle angle 119 * @return rotation angle 120 */ 121 static RotationAngle buildStaticRotation(final double angle) { 122 return new StaticRotationAngle(angle); 123 } 124 125 /** 126 * Parses the rotation angle from the specified {@code string}. 127 * @param string angle as string 128 * @return rotation angle 129 */ 130 static RotationAngle buildStaticRotation(final String string) { 131 try { 132 return buildStaticRotation(parseCardinalRotation(string)); 133 } catch (IllegalArgumentException e) { 134 throw new IllegalArgumentException("Invalid string: " + string, e); 135 } 136 } 137 138 /** 139 * Converts an angle diven in cardinal directions to radians. 140 * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast}, 141 * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south}, 142 * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}. 143 * @param cardinal the angle in cardinal directions 144 * @return the angle in radians 145 */ 146 static double parseCardinalRotation(final String cardinal) { 147 switch (cardinal.toLowerCase(Locale.ENGLISH)) { 148 case "n": 149 case "north": 150 return 0; // 0 degree => 0 radian 151 case "ne": 152 case "northeast": 153 return Utils.toRadians(45); 154 case "e": 155 case "east": 156 return Utils.toRadians(90); 157 case "se": 158 case "southeast": 159 return Utils.toRadians(135); 160 case "s": 161 case "south": 162 return Math.PI; // 180 degree 163 case "sw": 164 case "southwest": 165 return Utils.toRadians(225); 166 case "w": 167 case "west": 168 return Utils.toRadians(270); 169 case "nw": 170 case "northwest": 171 return Utils.toRadians(315); 172 default: 173 throw new IllegalArgumentException("Unexpected cardinal direction " + cardinal); 174 } 175 } 176 177 /** 178 * Computes the angle depending on the referencing way segment, or {@code 0} if none exists. 179 * @return rotation angle 180 */ 181 static RotationAngle buildWayDirectionRotation() { 182 return new WayDirectionRotationAngle(); 183 } 184}