Как я могу определить, находится ли 2D точка внутри многоугольника?

Реализация левого внешнего соединения с помощью методов расширения может выглядеть как

public static IEnumerable<Result> LeftJoin<TOuter, TInner, TKey, Result>(
  this IEnumerable<TOuter> outer, IEnumerable<TInner> inner
  , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
  , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    if (outer == null)
      throw new ArgumentException("outer");

    if (inner == null)
      throw new ArgumentException("inner");

    if (outerKeySelector == null)
      throw new ArgumentException("outerKeySelector");

    if (innerKeySelector == null)
      throw new ArgumentException("innerKeySelector");

    if (resultSelector == null)
      throw new ArgumentException("resultSelector");

    return LeftJoinImpl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer ?? EqualityComparer<TKey>.Default);
  }

  static IEnumerable<Result> LeftJoinImpl<TOuter, TInner, TKey, Result>(
      IEnumerable<TOuter> outer, IEnumerable<TInner> inner
      , Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector
      , Func<TOuter, TInner, Result> resultSelector, IEqualityComparer<TKey> comparer)
  {
    var innerLookup = inner.ToLookup(innerKeySelector, comparer);

    foreach (var outerElment in outer)
    {
      var outerKey = outerKeySelector(outerElment);
      var innerElements = innerLookup[outerKey];

      if (innerElements.Any())
        foreach (var innerElement in innerElements)
          yield return resultSelector(outerElment, innerElement);
      else
        yield return resultSelector(outerElment, default(TInner));
     }
   }

. Затем в результате выборщик должен заботиться о нулевых элементах. Fx.

   static void Main(string[] args)
   {
     var inner = new[] { Tuple.Create(1, "1"), Tuple.Create(2, "2"), Tuple.Create(3, "3") };
     var outer = new[] { Tuple.Create(1, "11"), Tuple.Create(2, "22") };

     var res = outer.LeftJoin(inner, item => item.Item1, item => item.Item1, (it1, it2) =>
     new { Key = it1.Item1, V1 = it1.Item2, V2 = it2 != null ? it2.Item2 : default(string) });

     foreach (var item in res)
       Console.WriteLine(string.Format("{0}, {1}, {2}", item.Key, item.V1, item.V2));
   }
468
задан Dragon Energy 14 December 2015 в 23:42
поделиться

7 ответов

Для графики я не предпочел бы целые числа. Много систем используют целые числа для рисования UI (пиксели являются ints, в конце концов), но macOS, например, использует плавание для всего. macOS только знает точки, и точка может перевести в один пиксель, но в зависимости от разрешения монитора, это могло бы перевести во что-то еще. На экранах сетчатки половина точки (0.5/0.5) является пикселем. Однако, я никогда не замечал, что macOS UIs значительно медленнее, чем другой UIs. После всех 3D API (OpenGL или Direct3D) также работает с плаваниями, и современные графические библиотеки очень часто используют в своих интересах ускорение GPU.

Теперь Вы сказали, что скорость является Вашим основным беспокойством, хорошо, давайте пойдем для скорости. Перед выполнением любого сложного алгоритма сначала сделайте простой тест. Создайте , ось выровняла ограничительную рамку вокруг Вашего полигона. Это очень легко, быстро и уже может безопасный Вы много вычислений. Как это работает? Выполните итерации по всем точкам полигона и найдите минимальные значения / макс. значения X и Y.

, Например, у Вас есть точки (9/1), (4/3), (2/7), (8/2), (3/6). Это означает, что Xmin равняется 2, Xmax равняется 9, Ymin равняется 1, и Ymax равняется 7. Точка за пределами прямоугольника с этими двумя краями (2/1) и (9/7) не может быть в полигоне.

// p is your point, p.x is the x coord, p.y is the y coord
if (p.x < Xmin || p.x > Xmax || p.y < Ymin || p.y > Ymax) {
    // Definitely not within the polygon!
}

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

Polygon without hole

И вот один с дырой:

Polygon with hole

зеленый имеет дыру в середине!

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

Demonstrating how the ray cuts through a polygon

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

Вы все еще имеете ограничительную рамку вышеупомянутых, помните? Просто выберите точку вне ограничительной рамки и используйте ее в качестве начальной точки для Вашего луча. Например, точка (Xmin - e/p.y) вне полигона наверняка.

, Но что e? Ну, e (на самом деле эпсилон) дает ограничительную рамку приблизительно [1 120] дополнение . Как я сказал, сбои трассировки лучей, если мы запускаем слишком близкий к строке полигона. Так как ограничительная рамка могла бы равняться полигону (если полигон является выровненным прямоугольником оси, ограничительная рамка равна самому полигону!), нам нужно некоторое дополнение для создания этого сейфа, это - все. Как большой необходимо выбрать e? Не слишком большой. Это зависит от системы координат, масштабируют Вас использование для рисования. Если Ваша пиксельная ширина шага 1.0, то просто выбирают 1.0 (все же 0.1, работал бы также)

Теперь, когда у нас есть луч с его запуском и координатами конца, сдвиги задач от" точка в полигоне " к" , как часто делает луч, пересекает сторону полигона ". Поэтому мы не можем только работать с точками полигона как прежде, теперь нам нужны фактические стороны. Сторона всегда определяется двумя точками.

side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:

необходимо протестировать луч против всех сторон. Полагайте, что луч вектор и каждая сторона, чтобы быть вектором. Луч должен поразить каждую сторону точно однажды или никогда вообще. Это не может поразить ту же сторону дважды. Две строки в 2D пространстве будут всегда пересекаться точно однажды, если они не будут параллельны, в этом случае они никогда не пересекаются. Однако, так как векторы имеют ограниченную длину, два вектора не могли бы быть параллельными и все еще никогда не пересекаться, потому что они слишком коротки, чтобы когда-либо встретить друг друга.

// Test the ray against all sides
int intersections = 0;
for (side = 0; side < numberOfSides; side++) {
    // Test if current side intersects with ray.
    // If yes, intersections++;
}
if ((intersections & 1) == 1) {
    // Inside of polygon
} else {
    // Outside of polygon
}

До сих пор так хорошо, но как Вы тестируете, если два вектора пересекаются? Вот некоторый код C (не протестированный), который должен добиться цели:

#define NO 0
#define YES 1
#define COLLINEAR 2

int areIntersecting(
    float v1x1, float v1y1, float v1x2, float v1y2,
    float v2x1, float v2y1, float v2x2, float v2y2
) {
    float d1, d2;
    float a1, a2, b1, b2, c1, c2;

    // Convert vector 1 to a line (line 1) of infinite length.
    // We want the line in linear equation standard form: A*x + B*y + C = 0
    // See: http://en.wikipedia.org/wiki/Linear_equation
    a1 = v1y2 - v1y1;
    b1 = v1x1 - v1x2;
    c1 = (v1x2 * v1y1) - (v1x1 * v1y2);

    // Every point (x,y), that solves the equation above, is on the line,
    // every point that does not solve it, is not. The equation will have a
    // positive result if it is on one side of the line and a negative one 
    // if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
    // 2 into the equation above.
    d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
    d2 = (a1 * v2x2) + (b1 * v2y2) + c1;

    // If d1 and d2 both have the same sign, they are both on the same side
    // of our line 1 and in that case no intersection is possible. Careful, 
    // 0 is a special case, that's why we don't test ">=" and "<=", 
    // but "<" and ">".
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // The fact that vector 2 intersected the infinite line 1 above doesn't 
    // mean it also intersects the vector 1. Vector 1 is only a subset of that
    // infinite line 1, so it may have intersected that line before the vector
    // started or after it ended. To know for sure, we have to repeat the
    // the same test the other way round. We start by calculating the 
    // infinite line 2 in linear equation standard form.
    a2 = v2y2 - v2y1;
    b2 = v2x1 - v2x2;
    c2 = (v2x2 * v2y1) - (v2x1 * v2y2);

    // Calculate d1 and d2 again, this time using points of vector 1.
    d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
    d2 = (a2 * v1x2) + (b2 * v1y2) + c2;

    // Again, if both have the same sign (and neither one is 0),
    // no intersection is possible.
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // If we get here, only two possibilities are left. Either the two
    // vectors intersect in exactly one point or they are collinear, which
    // means they intersect in any number of points from zero to infinite.
    if ((a1 * b2) - (a2 * b1) == 0.0f) return COLLINEAR;

    // If they are not collinear, they must intersect in exactly one point.
    return YES;
}

входные значения две конечных точки из вектора 1 (v1x1/v1y1 и v1x2/v1y2) и вектора 2 (v2x1/v2y1 и v2x2/v2y2). Таким образом, у Вас есть 2 вектора, 4 точки, 8 координат. YES и NO ясны. YES пересечения увеличений, NO ничего не делает.

Что относительно КОЛЛИНЕАРНОГО? Это означает, что оба вектора лежат на той же бесконечной строке, в зависимости от положения и длины, они не пересекаются вообще, или они пересекаются в бесконечном числе очков. Я не абсолютно уверен, как обработать этот случай, я не считал бы его как пересечение так или иначе. Ну, этот случай довольно редок на практике так или иначе из-за погрешностей округления с плавающей точкой; лучший код, вероятно, не протестировал бы на [1 117], но вместо этого на что-то как [1 118], где эпсилон является довольно небольшим числом.

, Если необходимо протестировать большее число очков, можно, конечно, ускорить все это немного путем хранения форм стандарта линейного уравнения сторон полигона в памяти, таким образом, Вы не должны повторно вычислять их каждый раз. Это сохранит Вас два умножения с плавающей точкой и три вычитания с плавающей точкой на каждом тесте в обмен на хранение трех значений с плавающей точкой на сторону полигона в памяти. Это - типичная память по сравнению с компромиссом времени вычисления.

Наконец, что не менее важно: Если можно использовать 3D аппаратные средства для решения проблемы, существует интересная альтернатива. Просто позвольте GPU сделать всю работу для Вас. Создайте поверхность рисования, которая является от экрана. Заполните его полностью черным цветом. Теперь позвольте OpenGL, или Direct3D красят Ваш полигон (или даже все Ваши полигоны, если Вы просто хотите протестировать, если точка в каком-либо из них, но Вы не заботитесь, какой), и заполняют полигон (полигоны) другим цветом, например, белый. Чтобы проверить, ли точка в полигоне, получите цвет этой точки от поверхности рисунка. Это - просто O (1) выборка памяти.

, Конечно, этот метод только применим, если Ваша поверхность рисунка не должна быть огромной. Если это не может вписаться в память GPU, этот метод медленнее, чем выполнение его на ЦП. Если это должно было бы быть огромно, и Ваш GPU поддерживает современные программы построения теней, можно все еще использовать GPU путем реализации кастинга луча, показанного выше как программа построения теней GPU, которая абсолютно возможна. Для большего числа полигонов или большого числа очков для тестирования, это окупится, рассмотрит, некоторые GPU смогут протестировать 64 - 256 точек параллельно. Обратите внимание однако, что передача данных от ЦП до GPU и назад является всегда дорогой, таким образом, для того, чтобы просто протестировать несколько точек против нескольких простых полигонов, где или точки или полигоны являются динамичными и будут часто изменяться, подход GPU будет редко окупаться.

692
ответ дан Mecki 15 December 2015 в 09:42
поделиться

Тривиальное решение состояло бы в том, чтобы разделить полигон к треугольникам и совершить нападки, тестируют треугольники, как объяснено здесь

, Если Ваш полигон ВЫПУКЛ мог бы быть лучший подход все же. Посмотрите на полигон как на набор бесконечных строк. Каждое деление строки располагает с интервалами в два. для каждой точки легко сказать если относительно одной стороны или другой стороны строки. Если точка находится на той же стороне всех строк затем, это в полигоне.

3
ответ дан shoosh 15 December 2015 в 09:42
поделиться
  • 1
    I' m не уверенный, сколько времени Вы думаете, запрос HTTP возьмет, но насколько он идет, я don' t верят загрузке всех правил CSS, и затем не использование их будет быстрее, чем получение файла CSS you' потребность ll, если Вы не смотрите всего на несколько правил CSS. О каком количестве CSS мы говорим? – Jeremy B. 23 April 2010 в 04:11

Я сделал некоторую работу над этой спиной, когда я был исследователем под Michael Stonebraker - Вы знаете, преподаватель, который придумал Ingres, PostgreSQL, и т.д.

, Мы поняли, что самый быстрый путь состоял в том, чтобы сначала сделать ограничительную рамку, потому что это СУПЕР быстро. Если это вне ограничительной рамки, это снаружи. Иначе Вы делаете более трудную работу...

, Если Вы хотите замечательный алгоритм, обратитесь к исходному коду PostgreSQL проекта с открытым исходным кодом для гео-работы...

я хочу указать, мы никогда не разбирались ни в каком понимании по сравнению с левой хиральностью (также выразимый как "внутренняя часть" по сравнению с "внешней" проблемой...

<час>

ОБНОВЛЕНИЕ

ссылка BKB обеспечила большое количество разумных алгоритмов. Я работал над проблемами науки о Земле и поэтому нуждался в решении, которое работает в широте/долготе, и она имеет специфическую проблему хиральности - действительно ли областью внутри является меньшая область или большая область? Ответ - то, что "направление" вопросов verticies - это или предназначено для левой руки или предназначено для правой руки, и таким образом можно указать на любую область как "в" любом данном полигоне. По сути, моя работа использовала решение три перечисленных на той странице.

, Кроме того, моя работа использовала отдельные функции для "на строке" тесты.

... Так как кто-то спросил: мы выяснили, что тесты ограничительной рамки были лучшими, когда количество verticies пошло вне некоторого числа - делают очень быстрый тест прежде, чем сделать более длинный тест при необходимости... Ограничительная рамка создается путем простого взятия самого большого x, самого маленького x, самого большого y и самого маленького y и соединения их для создания четырех точек поля...

Другая подсказка для тех, которые следуют: мы сделали все наши более сложные и "потускневшие свет" вычисления в пространстве сетки все в положительных точках на плоскости и затем повторно спроектировали назад в "реальную" долготу/широту, таким образом избежав возможных ошибок переноса когда одна пересеченная строка 180 из долготы и при обработке полярных регионов. Работавший отлично!

7
ответ дан Peter Mortensen 15 December 2015 в 09:42
поделиться
  • 1
    почему don' t Вы просто вставляют его? < свяжите href =" файл css" введите =" text/css" рэл =" stylesheet"/> – Jeremy B. 23 April 2010 в 03:55

Вычислите ориентированную сумму углов между точкой p и каждой из вершин полигона. Если общий ориентированный угол составляет 360 градусов, точка внутри. Если общее количество 0, точка снаружи.

мне нравится этот метод лучше, потому что это более устойчиво и менее зависит от числовой точности.

Методы, которые вычисляют четность количества пересечений, ограничены, потому что можно 'поразить' вершину во время вычисления количества пересечений.

РЕДАКТИРОВАНИЕ: Между прочим, этот метод работает с вогнутыми и выпуклыми полигонами.

РЕДАКТИРОВАНИЕ: Я недавно нашел целое статья Wikipedia о теме.

29
ответ дан David Segonds 15 December 2015 в 09:42
поделиться
  • 1
    @Ken Browning - Да, я уже попробовал то же самое как Ваше решение. Это действительно работает в представлении совместимости и IE8. Но я просто думаю это < code> var управляет = ' 100 букв here'; $ (' < тип стиля =" text/css" > </style> ') .text (правила) .appendTo (' head'); </code> выглядит более симпатичным, чем < code> var управляет = ' 100 букв here'; $ (' < тип стиля =" text/css" > ' + управляет + ' </style> ') .appendTo (' head'); </code> – powerboy 23 April 2010 в 04:20

Ответ David Segond является в значительной степени стандартным общим ответом, и T Richard является наиболее распространенной оптимизацией, хотя существуют некоторые другие. Другая сильная оптимизация основана на менее общих решениях. Например, если Вы собираетесь проверить, что тот же полигон с большим количеством точек, триангулируя полигон может ускорить вещи чрезвычайно, поскольку существует много очень быстрых ОЛОВЯННЫХ алгоритмов поиска. Другой - то, если полигон и точки находятся на ограниченной плоскости в низком разрешении скажем отображение на экране, можно нарисовать полигон на дисплейный буфер с отображенной памятью в данном цвете и проверить цвет данного пикселя, чтобы видеть, находится ли это в полигонах.

Как много оптимизации, они основаны на определенных а не общих случаях и приводят к beneifits на основе амортизируемого времени, а не единственного использования.

Работа в этом поле, я нашел Joeseph O'Rourkes 'Геометрией вычисления в C' ISBN 0-521-44034-3, чтобы быть большой справкой.

5
ответ дан SmacL 15 December 2015 в 09:42
поделиться
  • 1
    Вы решение вставляете файл CSS динамично. Но то, что я ищу, является простым способом вставить несколько (но не только один), правила CSS (не файл CSS) динамично, начиная с присоединения другого файла CSS инициализирует другой Запрос HTTP. Моя программа должна работать очень быстро, и сервер может иногда быть довольно занятым. Я должен сделать все для сокращения времени отклика. – powerboy 23 April 2010 в 04:07

Статья Эрика Хейнса , цитируемая бобобом, действительно превосходна. Особенно интересны таблицы, сравнивающие производительность алгоритмов; метод суммирования углов действительно плох по сравнению с другими. Также интересен тот факт, что такие оптимизации, как использование сетки поиска для дальнейшего разделения полигона на "внутри" и "вне" секторов, могут сделать тест невероятно быстрым даже на полигонах с > 1000 гранями.

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

Кстати, вот одна из таблиц производительности из статьи Эрика Хейнса для интереса, тестирование на случайных полигонах.

                       number of edges per polygon
                         3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693

Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278
18
ответ дан 22 November 2019 в 22:50
поделиться

Я думаю, что следующий кусок кода является лучшим решением (взято из здесь):

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

Аргументы

  • nvert: Количество вершин в многоугольнике. Вопрос о том, повторять ли первую вершину в конце, обсуждался в статье, ссылка на которую приведена выше.
  • vertx, verty: Массивы, содержащие x- и y-координаты вершин многоугольника.
  • testx, testy: X- и y-координата тестовой точки.

Это короткий и эффективный метод, который работает как для выпуклых, так и для вогнутых многоугольников. Как уже предлагалось ранее, сначала следует проверить ограничивающий прямоугольник и отдельно обработать отверстия в многоугольнике.

Идея, лежащая в основе этого, довольно проста. Автор описывает ее следующим образом:

Я провожу полубесконечный луч по горизонтали (возрастающий x, фиксированный y) из тестовой точки и считаю, сколько ребер он пересекает. При каждом пересечении луч переключается между внутренней и внешней сторонами. Это называется теоремой Жордана о кривой.

Переменная c меняется с 0 на 1 и с 1 на 0 каждый раз, когда горизонтальный луч пересекает любое ребро. То есть, по сути, она отслеживает, четное или нечетное количество пересеченных ребер. 0 означает четное, а 1 - нечетное.

567
ответ дан 22 November 2019 в 22:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: