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}