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