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