001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.util;
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;
009import org.openstreetmap.josm.tools.Geometry;
010import org.openstreetmap.josm.tools.SubclassFilteredCollection;
011import org.openstreetmap.josm.tools.Utils;
012
013/**
014 * Determines how an icon is to be rotated depending on the primitive to displayed.
015 */
016public abstract class RotationAngle {
017
018    /**
019     * Calculates the rotation angle depending on the primitive to displayed.
020     * @param p primitive
021     * @return rotation angle
022     */
023    public abstract double getRotationAngle(OsmPrimitive p);
024
025    /**
026     * Always returns the fixed {@code angle}.
027     * @param angle angle
028     * @return rotation angle
029     */
030    public static RotationAngle buildStaticRotation(final double angle) {
031        return new RotationAngle() {
032            @Override
033            public double getRotationAngle(OsmPrimitive p) {
034                return angle;
035            }
036
037            @Override
038            public String toString() {
039                return angle + "rad";
040            }
041        };
042    }
043
044    /**
045     * Parses the rotation angle from the specified {@code string}.
046     * @param string angle as string
047     * @return rotation angle
048     */
049    public static RotationAngle buildStaticRotation(final String string) {
050        try {
051            return buildStaticRotation(parseCardinalRotation(string));
052        } catch (IllegalArgumentException e) {
053            throw new IllegalArgumentException("Invalid string: " + string, e);
054        }
055    }
056
057    /**
058     * Converts an angle diven in cardinal directions to radians.
059     * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast},
060     * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south},
061     * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}.
062     * @param cardinal the angle in cardinal directions
063     * @return the angle in radians
064     */
065    public static double parseCardinalRotation(final String cardinal) {
066        switch (cardinal.toLowerCase(Locale.ENGLISH)) {
067            case "n":
068            case "north":
069                return Math.toRadians(0);
070            case "ne":
071            case "northeast":
072                return Math.toRadians(45);
073            case "e":
074            case "east":
075                return Math.toRadians(90);
076            case "se":
077            case "southeast":
078                return Math.toRadians(135);
079            case "s":
080            case "south":
081                return Math.toRadians(180);
082            case "sw":
083            case "southwest":
084                return Math.toRadians(225);
085            case "w":
086            case "west":
087                return Math.toRadians(270);
088            case "nw":
089            case "northwest":
090                return Math.toRadians(315);
091            default:
092                throw new IllegalArgumentException("Unexpected cardinal direction " + cardinal);
093        }
094    }
095
096    /**
097     * Computes the angle depending on the referencing way segment, or {@code 0} if none exists.
098     * @return rotation angle
099     */
100    public static RotationAngle buildWayDirectionRotation() {
101        return new RotationAngle() {
102            @Override
103            public double getRotationAngle(OsmPrimitive p) {
104                if (!(p instanceof Node)) {
105                    return 0;
106                }
107                final Node n = (Node) p;
108                final SubclassFilteredCollection<OsmPrimitive, Way> ways = Utils.filteredCollection(n.getReferrers(), Way.class);
109                if (ways.isEmpty()) {
110                    return 0;
111                }
112                final Way w = ways.iterator().next();
113                final int idx = w.getNodes().indexOf(n);
114                if (idx == 0) {
115                    return -Geometry.getSegmentAngle(n.getEastNorth(), w.getNode(idx + 1).getEastNorth());
116                } else {
117                    return -Geometry.getSegmentAngle(w.getNode(idx - 1).getEastNorth(), n.getEastNorth());
118                }
119            }
120
121            @Override
122            public String toString() {
123                return "way-direction";
124            }
125        };
126    }
127}