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.IPrimitive;
007import org.openstreetmap.josm.data.osm.Node;
008import org.openstreetmap.josm.data.osm.OsmPrimitive;
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 SubclassFilteredCollection<OsmPrimitive, Way> ways = Utils.filteredCollection(n.getReferrers(), Way.class);
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            final int prime = 31;
085            int result = 1;
086            long temp = Double.doubleToLongBits(angle);
087            result = prime * result + (int) (temp ^ (temp >>> 32));
088            return result;
089        }
090
091        @Override
092        public boolean equals(Object obj) {
093            if (this == obj) {
094                return true;
095            }
096            if (obj == null || getClass() != obj.getClass()) {
097                return false;
098            }
099            StaticRotationAngle other = (StaticRotationAngle) obj;
100            return Double.doubleToLongBits(angle) == Double.doubleToLongBits(other.angle);
101        }
102    }
103
104    /**
105     * A no-rotation angle that always returns 0.
106     * @since 11726
107     */
108    RotationAngle NO_ROTATION = new StaticRotationAngle(0);
109
110    /**
111     * Calculates the rotation angle depending on the primitive to be displayed.
112     * @param p primitive
113     * @return rotation angle
114     * @since 13623 (signature)
115     */
116    double getRotationAngle(IPrimitive p);
117
118    /**
119     * Always returns the fixed {@code angle}.
120     * @param angle angle
121     * @return rotation angle
122     */
123    static RotationAngle buildStaticRotation(final double angle) {
124        return new StaticRotationAngle(angle);
125    }
126
127    /**
128     * Parses the rotation angle from the specified {@code string}.
129     * @param string angle as string
130     * @return rotation angle
131     */
132    static RotationAngle buildStaticRotation(final String string) {
133        try {
134            return buildStaticRotation(parseCardinalRotation(string));
135        } catch (IllegalArgumentException e) {
136            throw new IllegalArgumentException("Invalid string: " + string, e);
137        }
138    }
139
140    /**
141     * Converts an angle diven in cardinal directions to radians.
142     * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast},
143     * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south},
144     * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}.
145     * @param cardinal the angle in cardinal directions
146     * @return the angle in radians
147     */
148    static double parseCardinalRotation(final String cardinal) {
149        switch (cardinal.toLowerCase(Locale.ENGLISH)) {
150            case "n":
151            case "north":
152                return 0; // 0 degree => 0 radian
153            case "ne":
154            case "northeast":
155                return Utils.toRadians(45);
156            case "e":
157            case "east":
158                return Utils.toRadians(90);
159            case "se":
160            case "southeast":
161                return Utils.toRadians(135);
162            case "s":
163            case "south":
164                return Math.PI; // 180 degree
165            case "sw":
166            case "southwest":
167                return Utils.toRadians(225);
168            case "w":
169            case "west":
170                return Utils.toRadians(270);
171            case "nw":
172            case "northwest":
173                return Utils.toRadians(315);
174            default:
175                throw new IllegalArgumentException("Unexpected cardinal direction " + cardinal);
176        }
177    }
178
179    /**
180     * Computes the angle depending on the referencing way segment, or {@code 0} if none exists.
181     * @return rotation angle
182     */
183    static RotationAngle buildWayDirectionRotation() {
184        return new WayDirectionRotationAngle();
185    }
186}