Краткий: отмечен краткостью выражения или высказывания: свободен от всех разработок и лишних деталей. Просто выбросить это там ...
Dialock
Ответы:
25
Самый простой способ, вероятно, состоит в том, чтобы получить угол вектора, используя atan2(), как предлагает Тетрад в комментариях, а затем масштабировать и округлить его, например, (псевдокод):
// enumerated counterclockwise, starting from east = 0:enum compassDir {
E =0, NE =1,
N =2, NW =3,
W =4, SW =5,
S =6, SE =7};// for string conversion, if you can't just do e.g. dir.toString():conststring[8] headings ={"E","NE","N","NW","W","SW","S","SE"};// actual conversion code:float angle = atan2( vector.y, vector.x );int octant = round(8* angle /(2*PI)+8)%8;
compassDir dir =(compassDir) octant;// typecast to enum: 0 -> E etc.string dirStr = headings[octant];
octant = round( 8 * angle / (2*PI) + 8 ) % 8Линии , возможно , потребуется какое - то объяснение. В почти всех языках , которые я знаю , что у него, функция возвращает угол в радианах. Разделив его на 2 π, мы преобразуем его из радианов в доли полного круга, а умножив на 8, затем преобразуем его в восьмые части круга, которые затем округляем до ближайшего целого числа. Наконец, мы уменьшаем его по модулю 8, чтобы позаботиться о циклическом переходе, так что и 0, и 8 правильно отображаются на восток.atan2()
Причина + 8, о которой я пропустил выше, состоит в том, что в некоторых языках atan2()могут возвращаться отрицательные результаты (т. Е. От - π до + π, а не от 0 до 2 π ), и оператор modulo ( %) может быть определен так, чтобы возвращать отрицательные значения для отрицательные аргументы (или его поведение для отрицательных аргументов может быть неопределенным). Добавление 8(т. Е. Один полный оборот) к входу до сокращения гарантирует, что аргументы всегда положительны, не влияя на результат каким-либо другим образом.
Если ваш язык не предоставляет удобную функцию округления до ближайшего, вы можете вместо этого использовать усеченное целочисленное преобразование и просто добавить 0,5 к аргументу, например так:
int octant =int(8* angle /(2*PI)+8.5)%8;// int() rounds down
Обратите внимание, что в некоторых языках стандартное преобразование с плавающей точкой в целое округляет отрицательные значения до нуля, а не до нуля, что является еще одной причиной того, что входные данные всегда положительны.
Конечно, вы можете заменить все вхождения в 8этой строке каким-либо другим числом (например, 4 или 16, или даже 6 или 12, если вы находитесь на шестнадцатеричной карте), чтобы разделить круг на такое количество направлений. Просто настройте enum / array соответственно.
Обратите внимание, что обычно это atan2(y,x)не так atan2(x,y).
Сэм Хоцевар
@Sam: К сожалению, исправлено. Конечно, atan2(x,y)это также сработало бы, если бы кто-то просто перечислял заголовки компаса по часовой стрелке, начиная с севера.
Илмари Каронен
2
+1 кстати, я действительно считаю, что это самый простой и сложный ответ.
Сэм Хоцевар
1
@TheLima:octant = round(8 * angle / 360 + 8) % 8
Илмари Каронен
1
Обратите внимание , что это может быть легко преобразован в 4-ходовым компас: quadtant = round(4 * angle / (2*PI) + 4) % 4и с помощью перечисления: { E, N, W, S }.
Спойк
10
У вас есть 8 вариантов (или 16 или более, если вы хотите еще более высокую точность).
Используйте, atan2(y,x)чтобы получить угол для вашего вектора.
atan2() работает следующим образом:
Таким образом, x = 1, y = 0 приведет к 0, и оно будет прерывистым при x = -1, y = 0, содержащем как π, так и -π.
Теперь нам просто нужно отобразить выходные данные atan2()в соответствии с компасом, который мы имеем выше.
Вероятно, наиболее простым для реализации является инкрементная проверка углов. Вот некоторый псевдокод, который легко изменить для повышения точности:
//start direction from the lowest value, in this case it's west with -πenum direction {
west,
south,
east,
north
}
increment =(2PI)/direction.count
angle = atan2(y,x);
testangle =-PI + increment/2
index =0while angle > testangle
index++if(index > direction.count -1)return direction[0]//roll over
testangle += increment
return direction[index]
Теперь, чтобы добавить больше точности, просто добавьте значения в перечисление direction.
Алгоритм работает, проверяя возрастающие значения вокруг компаса, чтобы увидеть, лежит ли наш угол где-то между тем, где мы в последний раз проверяли, и новой позицией. Вот почему мы начинаем с -PI + increment / 2. Мы хотим компенсировать наши проверки, чтобы включить одинаковое пространство вокруг каждого направления. Что-то вроде этого:
Запад разбит на две части, потому что возвращаемые значения atan2()на Западе носят прерывистый характер.
Простой способ «преобразовать их в угол» - использовать atan2, хотя имейте в виду, что 0 градусов, вероятно, будут восточными, а не северными.
Тетрад
1
Вам не нужны angle >=проверки в коде выше; например, если угол меньше 45, то север уже будет возвращен, поэтому вам не нужно проверять угол> = 45 для проверки на восток. Точно так же перед возвращением на запад вам вообще не нужна проверка - это единственная оставшаяся возможность.
MrKWatkins
4
Я бы не назвал это кратким способом определения направления. Это кажется довольно неуклюжим и потребует много изменений, чтобы приспособить это к различным «разрешениям». Не говоря уже о куче ifутверждений, если вы хотите пойти по 16 или более направлениям.
bummzack
2
Нет необходимости нормализовать вектор: угол остается неизменным при изменении величины.
Килотан
Спасибо @bummzack, я отредактировал пост, чтобы сделать его более кратким и простым для повышения точности, просто добавив больше значений enum.
MichaelHouse
8
Всякий раз, когда вы имеете дело с векторами, рассмотрите фундаментальные векторные операции вместо преобразования в углы в каком-то конкретном кадре.
Учитывая вектор запроса vи набор единичных векторов s, наиболее выровненный вектор - это вектор, s_iкоторый максимизируется dot(v,s_i). Это связано с тем, что произведение точек с заданной фиксированной длиной для параметров имеет максимум для векторов с одинаковым направлением и минимум для векторов с противоположными направлениями, плавно изменяющихся между ними.
Это обобщает тривиально в большее количество измерений, чем два, расширяется с произвольными направлениями и не страдает от специфических для кадра проблем, таких как бесконечные градиенты.
С точки зрения реализации, это сводится к тому, чтобы связать вектор из каждого кардинального направления с идентификатором (enum, string, что вам нужно), представляющим это направление. Затем вы переберите свой набор направлений, найдя направление с наибольшим точечным произведением.
map<float2,Direction> candidates;
candidates[float2(1,0)]= E; candidates[float2(0,1)]= N;// etc.for each (float2 dir in candidates){float goodness = dot(dir, v);if(goodness > bestResult){
bestResult = goodness;
bestDir = candidates[dir];}}
Эта реализация также может быть написана без ветвления и векторизована без особых проблем.
Promit
1
А mapс float2ключом? Это не выглядит очень серьезно.
Сэм Хоцевар
Это «псевдокод» в дидактической манере. Если вам нужны реализации, оптимизированные для паники, скорее всего, GDSE - это не то место, куда вы можете отправиться за своей копировальной пастой. Что касается использования float2 в качестве ключа, float может точно представлять целые числа, которые мы здесь используем, и вы можете сделать для них отличный компаратор. Ключи с плавающей запятой не подходят, только если они содержат специальные значения или вы пытаетесь найти вычисленные результаты. Итерация по ассоциативной последовательности - это хорошо. Конечно, я мог бы использовать линейный поиск в массиве, но это было бы бесполезным беспорядком.
Ларс Виклунд
3
Один из способов, который здесь не был упомянут, - рассматривать векторы как комплексные числа. Они не требуют тригонометрии и могут быть довольно интуитивно понятными для сложения, умножения или округления вращений, тем более что заголовки уже представлены в виде пар чисел.
В случае, если вы не знакомы с ними, направления выражаются в виде a + b (i) с существенным вещественным компонентом, а b (i) является мнимым. Если вы представите декартову плоскость, где X - реальное, а Y - мнимое, 1 будет востоком (справа), я буду севером.
Вот ключевая часть: 8 основных направлений представлены исключительно числами 1, -1 или 0 для их действительных и мнимых компонентов. Поэтому все, что вам нужно сделать, это уменьшить координаты X, Y как отношение и округлить оба до ближайшего целого числа, чтобы получить направление.
NW (-1+ i) N (i) NE (1+ i)
W (-1)Origin E (1)
SW (-1- i) S (-i) SE (1- i)
Для преобразования диагонали от направления к ближайшему уменьшите пропорционально значения X и Y, чтобы большее значение было равно 1 или -1. Устанавливать
Округление обоих компонентов того, что было изначально (10, -2), дает вам 1 + 0 (i) или 1. Таким образом, самое близкое направление - восток.
Вышесказанное на самом деле не требует использования сложной числовой структуры, но если рассматривать их как таковые, можно быстрее найти 8 основных направлений. Вы можете сделать векторную математику обычным способом, если хотите получить чистый заголовок из двух или более векторов. (Как комплексные числа, вы не добавляете, а умножаете для результата)
Это потрясающе, но делает ошибку, аналогичную той, которую я сделал в своей собственной попытке. Ответы близки, но не верны. Граничный угол между E и NE составляет 22,5 градуса, но он составляет 26,6 градуса.
IZB
Max(x, y)должен Max(Abs(x, y))работать на отрицательные квадранты. Я попробовал это и получил тот же результат, что и izb - это переключает направления компаса под неправильными углами. Я предполагаю, что он будет переключаться, когда heading.y / heading.x пересекает 0,5 (поэтому округленное значение переключается с 0 на 1), что составляет arctan (0,5) = 26,565 °.
amitp
Другой способ использования комплексных чисел здесь состоит в том, чтобы наблюдать, что умножение комплексных чисел включает вращение. Если вы строите комплексное число, представляющее 1/8 оборота вокруг круга, то каждый раз, когда вы умножаетесь на него, вы перемещаете один октант. Таким образом, вы можете спросить: можем ли мы посчитать, сколько умножений потребовалось, чтобы перейти с Востока на текущий курс? Ответ «сколько раз мы должны умножить на это» - логарифм . Если вы ищите логарифмы для комплексных чисел ... он использует atan2. Так что это в конечном итоге эквивалентно ответу Ильмари.
Скорее всего, потому что за вашим кодом нет объяснения. Почему это решение и как оно работает?
Vaillancourt
ты запускал это?
Рэй Тайек
Нет, и учитывая название класса, я предположил, что вы сделали, и это сработало. И это здорово. Но вы спросили, почему люди проголосовали против, и я ответил; Я никогда не подразумевал, что это не сработало :)
Vaillancourt
-2
Е = 0, СВ = 1, N = 2, СЗ = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + знак (x)) * * (1 знак (y ^ 2)) - (2 + знак (x)) * знак (y)
// main direction constants
DIR_E =0x1
DIR_W =0x2
DIR_S =0x4
DIR_N =0x8// mixed direction constants
DIR_NW = DIR_N | DIR_W
DIR_SW = DIR_S | DIR_W
DIR_NE = DIR_N | DIR_E
DIR_SE = DIR_S | DIR_E
// calculating the direction
dir =0x0if(x >0) dir |= DIR_E
if(x <0) dir |= DIR_W
if(y >0) dir |= DIR_S
if(y <0) dir |= DIR_N
return dir
Небольшое улучшение производительности может заключаться в том, чтобы поместить <-checks в ветку else соответствующих >-checks, но я воздержался от этого, потому что это вредит читабельности.
Извините, но это не даст именно тот ответ, который я ищу. С этим кодом он выдаст «N», только если вектор расположен точно на север, а NE или NW, если x - любое другое значение. То, что мне нужно, это ближайшее направление компаса, например, если вектор ближе к N, чем к северо-западу, тогда он даст N.
izb
Это действительно дало бы самое близкое направление? Кажется, вектор (0,00001,100) даст вам северо-восток. редактировать: вы избили меня к этому изб.
CiscoIPPhone
Вы не сказали, что хотите самое близкое направление.
Филипп
1
Извините, я спрятал это в заголовке. Если бы яснее в теле вопроса
IZB
1
Как насчет использования бесконечной нормы? Деление на max (abs (vector.components)) дает нормализованный вектор по отношению к этой норме. Теперь вы можете написать небольшую контрольную таблицу на основе if (x > 0.9) dir |= DIR_Eвсего остального. Это должно быть лучше, чем оригинальный код Филиппа, и немного дешевле, чем использование нормы L2 и atan2. Может быть, а может и нет.
Ответы:
Самый простой способ, вероятно, состоит в том, чтобы получить угол вектора, используя
atan2()
, как предлагает Тетрад в комментариях, а затем масштабировать и округлить его, например, (псевдокод):octant = round( 8 * angle / (2*PI) + 8 ) % 8
Линии , возможно , потребуется какое - то объяснение. В почти всех языках , которые я знаю , что у него, функция возвращает угол в радианах. Разделив его на 2 π, мы преобразуем его из радианов в доли полного круга, а умножив на 8, затем преобразуем его в восьмые части круга, которые затем округляем до ближайшего целого числа. Наконец, мы уменьшаем его по модулю 8, чтобы позаботиться о циклическом переходе, так что и 0, и 8 правильно отображаются на восток.atan2()
Причина
+ 8
, о которой я пропустил выше, состоит в том, что в некоторых языкахatan2()
могут возвращаться отрицательные результаты (т. Е. От - π до + π, а не от 0 до 2 π ), и оператор modulo (%
) может быть определен так, чтобы возвращать отрицательные значения для отрицательные аргументы (или его поведение для отрицательных аргументов может быть неопределенным). Добавление8
(т. Е. Один полный оборот) к входу до сокращения гарантирует, что аргументы всегда положительны, не влияя на результат каким-либо другим образом.Если ваш язык не предоставляет удобную функцию округления до ближайшего, вы можете вместо этого использовать усеченное целочисленное преобразование и просто добавить 0,5 к аргументу, например так:
Обратите внимание, что в некоторых языках стандартное преобразование с плавающей точкой в целое округляет отрицательные значения до нуля, а не до нуля, что является еще одной причиной того, что входные данные всегда положительны.
Конечно, вы можете заменить все вхождения в
8
этой строке каким-либо другим числом (например, 4 или 16, или даже 6 или 12, если вы находитесь на шестнадцатеричной карте), чтобы разделить круг на такое количество направлений. Просто настройте enum / array соответственно.источник
atan2(y,x)
не такatan2(x,y)
.atan2(x,y)
это также сработало бы, если бы кто-то просто перечислял заголовки компаса по часовой стрелке, начиная с севера.octant = round(8 * angle / 360 + 8) % 8
quadtant = round(4 * angle / (2*PI) + 4) % 4
и с помощью перечисления:{ E, N, W, S }
.У вас есть 8 вариантов (или 16 или более, если вы хотите еще более высокую точность).
Используйте,
atan2(y,x)
чтобы получить угол для вашего вектора.atan2()
работает следующим образом:Таким образом, x = 1, y = 0 приведет к 0, и оно будет прерывистым при x = -1, y = 0, содержащем как π, так и -π.
Теперь нам просто нужно отобразить выходные данные
atan2()
в соответствии с компасом, который мы имеем выше.Вероятно, наиболее простым для реализации является инкрементная проверка углов. Вот некоторый псевдокод, который легко изменить для повышения точности:
Теперь, чтобы добавить больше точности, просто добавьте значения в перечисление direction.
Алгоритм работает, проверяя возрастающие значения вокруг компаса, чтобы увидеть, лежит ли наш угол где-то между тем, где мы в последний раз проверяли, и новой позицией. Вот почему мы начинаем с -PI + increment / 2. Мы хотим компенсировать наши проверки, чтобы включить одинаковое пространство вокруг каждого направления. Что-то вроде этого:
Запад разбит на две части, потому что возвращаемые значения
atan2()
на Западе носят прерывистый характер.источник
atan2
, хотя имейте в виду, что 0 градусов, вероятно, будут восточными, а не северными.angle >=
проверки в коде выше; например, если угол меньше 45, то север уже будет возвращен, поэтому вам не нужно проверять угол> = 45 для проверки на восток. Точно так же перед возвращением на запад вам вообще не нужна проверка - это единственная оставшаяся возможность.if
утверждений, если вы хотите пойти по 16 или более направлениям.Всякий раз, когда вы имеете дело с векторами, рассмотрите фундаментальные векторные операции вместо преобразования в углы в каком-то конкретном кадре.
Учитывая вектор запроса
v
и набор единичных векторовs
, наиболее выровненный вектор - это вектор,s_i
который максимизируетсяdot(v,s_i)
. Это связано с тем, что произведение точек с заданной фиксированной длиной для параметров имеет максимум для векторов с одинаковым направлением и минимум для векторов с противоположными направлениями, плавно изменяющихся между ними.Это обобщает тривиально в большее количество измерений, чем два, расширяется с произвольными направлениями и не страдает от специфических для кадра проблем, таких как бесконечные градиенты.
С точки зрения реализации, это сводится к тому, чтобы связать вектор из каждого кардинального направления с идентификатором (enum, string, что вам нужно), представляющим это направление. Затем вы переберите свой набор направлений, найдя направление с наибольшим точечным произведением.
источник
map
сfloat2
ключом? Это не выглядит очень серьезно.Один из способов, который здесь не был упомянут, - рассматривать векторы как комплексные числа. Они не требуют тригонометрии и могут быть довольно интуитивно понятными для сложения, умножения или округления вращений, тем более что заголовки уже представлены в виде пар чисел.
В случае, если вы не знакомы с ними, направления выражаются в виде a + b (i) с существенным вещественным компонентом, а b (i) является мнимым. Если вы представите декартову плоскость, где X - реальное, а Y - мнимое, 1 будет востоком (справа), я буду севером.
Вот ключевая часть: 8 основных направлений представлены исключительно числами 1, -1 или 0 для их действительных и мнимых компонентов. Поэтому все, что вам нужно сделать, это уменьшить координаты X, Y как отношение и округлить оба до ближайшего целого числа, чтобы получить направление.
Для преобразования диагонали от направления к ближайшему уменьшите пропорционально значения X и Y, чтобы большее значение было равно 1 или -1. Устанавливать
Округление обоих компонентов того, что было изначально (10, -2), дает вам 1 + 0 (i) или 1. Таким образом, самое близкое направление - восток.
Вышесказанное на самом деле не требует использования сложной числовой структуры, но если рассматривать их как таковые, можно быстрее найти 8 основных направлений. Вы можете сделать векторную математику обычным способом, если хотите получить чистый заголовок из двух или более векторов. (Как комплексные числа, вы не добавляете, а умножаете для результата)
источник
Max(x, y)
долженMax(Abs(x, y))
работать на отрицательные квадранты. Я попробовал это и получил тот же результат, что и izb - это переключает направления компаса под неправильными углами. Я предполагаю, что он будет переключаться, когда heading.y / heading.x пересекает 0,5 (поэтому округленное значение переключается с 0 на 1), что составляет arctan (0,5) = 26,565 °.это похоже на работу:
источник
Е = 0, СВ = 1, N = 2, СЗ = 3, W = 4, SW = 5, S = 6, SE = 7
f (x, y) = mod ((4-2 * (1 + знак (x)) * * (1 знак (y ^ 2)) - (2 + знак (x)) * знак (y)
источник
Когда вы хотите строку:
Это дает вам константы, используя битовые поля:
Небольшое улучшение производительности может заключаться в том, чтобы поместить
<
-checks в ветку else соответствующих>
-checks, но я воздержался от этого, потому что это вредит читабельности.источник
if (x > 0.9) dir |= DIR_E
всего остального. Это должно быть лучше, чем оригинальный код Филиппа, и немного дешевле, чем использование нормы L2 и atan2. Может быть, а может и нет.