Алгоритм кардинального направления в Java

В эти выходные я трачу несколько минут на обдумывание алгоритма, который будет принимать заголовок (в градусах) и возвращать строку для кардинального направления (я использую его в приложении компаса для Android, которое использую). В итоге я получил следующее:

private String headingToString(Float heading)
{
    String strHeading = "?";
    Hashtable<String, Float> cardinal = new Hashtable<String, Float>();
    cardinal.put("North_1", new Float(0));
    cardinal.put("Northeast", new Float(45));
    cardinal.put("East", new Float(90));
    cardinal.put("Southeast", new Float(135));
    cardinal.put("South", new Float(180));
    cardinal.put("Southwest", new Float(225));
    cardinal.put("West", new Float(270));
    cardinal.put("Northwest", new Float(315));
    cardinal.put("North_2", new Float(360));

    for (String key: cardinal.keySet())
    {
        Float value = cardinal.get(key);
        if (Math.abs(heading - value) < 30)
        {
            strHeading = key;
            if (key.contains("North_"))
            {
                strHeading = "North";
            }
            break;
        }
    }
    return strHeading;
}

Мой вопрос: это лучший способ сделать это? Это, должно быть, было сделано много раз раньше, хотя я еще не искал примеры в Интернете. Кто-нибудь еще пробовал это и нашел более аккуратное решение?

Изменить для ответов Тило, Синджина и Хрстоффера Преподобного:

Решение

public static String headingToString2(double x)
{
    String directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"};
    return directions[ (int)Math.round((  ((double)x % 360) / 45)) ];
}

person MattyW    schedule 25.01.2010    source источник


Ответы (5)


В большинстве случаев это нормально, но чтобы сделать его оптимизированным и (ИМО) более чистым, вы можете найти функцию, связывающую входной заголовок с заголовком, используемым на карте.

Например: (Я почти уверен, что это правильно, но вы захотите это проверить)

45* (int)Math.round((  ((double)x % 360) / 45))

Что это делает, так это то, что сначала x % 360 проверяется, находится ли заголовок в допустимом диапазоне. тогда

45 * round(.../45)

находит ближайшее кратное 45.

Теперь измените свою карту на

  HashMap<Integer, String> map = new HashMap<Integer, String>()
  map.put(0, "North")
  map.put(45, "Northeast")
  etc...

Итак, теперь ваш алгоритм превращается в быстрый математический расчет, а не в итерацию по карте. Кроме того, здесь вам не нужен Hashtable, поскольку он предоставляет конструкции для параллелизма (если я правильно помню), и в вашем случае это фактически приведет к снижению производительности.

Опять же, снижение производительности может быть совершенно незначительным для ваших нужд.

Изменить предложения Тило и Синджина:

Вместо умножения на 45 просто сохраните остальную часть уравнения, которое дает вам значения от 0 до 7, и создайте массив ваших строк.

String directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}
return directions[ (int)Math.round((  ((double)x % 360) / 45)) % 8 ]

и ваша проблема решена в две строки.

Одно замечание: модуль не будет корректно работать с отрицательными числами. Если наш входной заголовок отрицательный, вам нужно сначала сделать его положительным.

person Reverend Gonzo    schedule 25.01.2010
comment
+1. Очень хорошо. Сделав еще один шаг, добавьте еще одно небольшое преобразование в функцию, и он может просто получить индекс в массиве строк: String[] directions = { "N", "NE", "E" ... } - person Thilo; 25.01.2010
comment
Если опустить умножение на 45, то вместо хеш-карты можно использовать простой массив. - person shinjin; 25.01.2010
comment
Вам нужен дополнительный N в этом массиве. Заголовки 337,5 и выше будут округлены до 8. - person Christoffer Hammarström; 25.01.2010
comment
В этом решении есть две опечатки, которые приводят к неверным результатам и исключению IndexOutOfBoundsException: 1) Отсутствует W в массиве: String directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"} 2) Индекс 8 должен возвращаться к 0 return directions[ (int)Math.round(( ((double)x % 360) / 45)) % 8 ] - person Andrey; 11.10.2013
comment
@Walmart_Hobo, ты имел в виду %7? - person XCore; 01.02.2015
comment
@XCore Нет, мод 8 правильный. Допустимые индексы: [0, 7], 0%8=0, 1%8=1, ..., 7%8=7, 8%8=0, по желанию. - person Andrey; 11.02.2015
comment
@Walmart_Hobo, простите, неправильно посчитал массив. - person XCore; 12.02.2015
comment
@Walmart_Hobo Я отредактировал ответ, чтобы исправить ошибку, теперь отлично работает - person Ben Clayton; 06.05.2016

Большинство ответов здесь отклонены на 22,5 градуса для их интервалов в 45 градусов, и карта, например. 0-45 как N, а не [337,5-360], [0-22,5] в N. Вам нужно сместить, прежде чем выполнять математические вычисления, чтобы исправить это.

Вот решение, которое использует интервалы в 22,5 градуса, такие как вы можете видеть для направлений ветра:

  private String formatBearing(double bearing) {
    if (bearing < 0 && bearing > -180) {
      // Normalize to [0,360]
      bearing = 360.0 + bearing;
    }
    if (bearing > 360 || bearing < -180) {
      return "Unknown";
    }

    String directions[] = {
      "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
      "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",
      "N"};
    String cardinal = directions[(int) Math.floor(((bearing + 11.25) % 360) / 22.5)];
    return cardinal + " (" + formatBearing.format(bearing) + " deg)";
  }
person magdalar    schedule 17.08.2014
comment
нормально ли, что ваш массив направлений содержит N дважды? - person Pak; 20.01.2017

Вы могли бы добавить 15 градусов вперед, чтобы избежать North_1 и North_2.

person Thilo    schedule 25.01.2010

Предыдущие примеры не точны, вот более точное решение на JavaScript.

function getCardinalDirection(input) {
    var directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"];
    var index = Math.floor( ((input-22.5)%360) / 45 );
    return directions[index+1];
}
person Andrei    schedule 28.10.2013

в яве:

String _directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"};

public String getHeading(int hea) {
  return _directions[(int)Math.floor((hea % 360) / 45)];
}

В случае «java» вам необходимо создать класс.

в джаваскрипте:

var _directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];

function getDirection (hea) {
  return _directions[Math.floor((hea % 360) / 45)];
};
person outaTiME    schedule 14.03.2011