Что такое эффективный алгоритм для нахождения области перекрывающихся прямоугольников - PullRequest
45 голосов
/ 28 октября 2008

Моя ситуация

  • Ввод: набор прямоугольников
  • каждый прямоугольник состоит из 4 двойных чисел, как это: (x0, y0, x1, y1)
  • они не "повернуты" ни под каким углом, все они являются "нормальными" прямоугольниками, которые идут "вверх / вниз" и "влево / вправо" относительно экрана
  • они расположены случайным образом - они могут касаться краев, перекрываться или не иметь никакого контакта
  • У меня будет несколько сотен прямоугольников
  • это реализовано в C #

Мне нужно найти

  • Область, которая образована их перекрытием - вся область холста, которую "покрывает" более одного прямоугольника (например, двумя прямоугольниками, это будет пересечение)
  • Мне не нужна геометрия перекрытия - просто площадь (пример: 4 кв. Дюйма)
  • Наложения не должны учитываться несколько раз - например, представьте 3 рита одинакового размера и положения - они расположены прямо друг над другом - эта область должна учитываться один раз (а не три раза)

Пример

  • Изображение ниже содержит три прямоугольника: A, B, C
  • A и B перекрываются (как показано штрихами)
  • B и C перекрываются (как показано штрихами)
  • То, что я ищу, - это область, где отображаются штрихи

-

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
                BBBBBBBBBBBBBBBBB
                BBBBBBBBBBBBBBBBB
                BBBBBBBBBBBBBBBBB
                BBBBBB-----------CCCCCCCC
                BBBBBB-----------CCCCCCCC
                BBBBBB-----------CCCCCCCC
                      CCCCCCCCCCCCCCCCCCC
                      CCCCCCCCCCCCCCCCCCC
                      CCCCCCCCCCCCCCCCCCC
                      CCCCCCCCCCCCCCCCCCC

Ответы [ 15 ]

52 голосов
/ 29 октября 2008

Эффективным способом вычисления этой области является использование алгоритма развертки. Предположим, что мы проходим вертикальную линию L (x) через объединение прямоугольников U:

  • Прежде всего, вам нужно построить очередь событий Q, которая в данном случае представляет собой упорядоченный список всех x-координат (левой и правой) прямоугольников.
  • во время развертки вы должны поддерживать одномерную структуру данных, которая должна давать вам общую длину пересечения L (x) и U. Важно то, что эта длина постоянна между двумя последовательными событиями q и q ' Q. Таким образом, если l (q) обозначает общую длину L (q +) (т.е. L только на правой стороне q), пересекаемого с U, площадь, охватываемая L между событиями q и q ', равна точно l (q) * (q '- q).
  • вам просто нужно сложить все эти области, чтобы получить общую.

Нам все еще нужно решить проблему 1D. Вам нужна одномерная структура, которая динамически вычисляет объединение (вертикальных) сегментов. Под динамически я подразумеваю, что вы иногда добавляете новый сегмент, а иногда удаляете один.

Я уже подробно изложил в своем ответе на этот вопрос о коллапсирующих диапазонах как сделать это статическим способом (который на самом деле является 1D-разверткой). Поэтому, если вы хотите что-то простое, вы можете применить это непосредственно (пересчитав объединение для каждого события). Если вы хотите что-то более эффективное, вам просто нужно немного его адаптировать:

  • при условии, что вы знаете объединение сегментов S 1 ... S n состоит из разделительных сегментов D 1 ... D k . Добавить S n + 1 очень просто, вам просто нужно найти оба конца S n + 1 среди концов D 1 ... D к .
  • при условии, что вы знаете объединение сегментов S 1 ... S n состоит из разделительных сегментов D 1 ... D k , удаление сегмента S i (при условии, что S i был включен в D j ) означает пересчет объединения сегментов, в котором D j состоит из, кроме S i (с использованием статического алгоритма).

Это ваш динамический алгоритм. Предполагая, что вы будете использовать отсортированные наборы с запросами местоположения во время журнала для представления D 1 ... D k , это, вероятно, самый эффективный неспециализированный метод, который вы можете получить.

13 голосов
/ 28 октября 2008

Один из возможных подходов - нарисовать его на холсте! Нарисуйте каждый прямоугольник, используя полупрозрачный цвет. Среда выполнения .NET будет выполнять рисование в оптимизированном собственном коде или даже с использованием аппаратного ускорителя.

Затем вы должны перечитать пиксели. Является ли каждый пиксель цветом фона, цветом прямоугольника или другим цветом? Единственный способ, которым это может быть другим цветом, - это если два или более прямоугольника перекрываются ...

Если это слишком много читов, я бы порекомендовал квад-дерево, как это сделал другой ответчик, или r-дерево .

10 голосов
/ 28 октября 2008

Это быстрый и грязный код, который я использовал в TopCoder SRM 160 Div 2.

t = top
b = нижняя часть
л = левый
r = вправо

public class Rect
{
    public int t, b, l, r;

    public Rect(int _l, int _b, int _r, int _t)
    {
        t = _t;
        b = _b;
        l = _l;
        r = _r;
    }   

    public bool Intersects(Rect R)
    {
        return !(l > R.r || R.l > r || R.b > t || b > R.t);
    }

    public Rect Intersection(Rect R)
    {
        if(!this.Intersects(R))
            return new Rect(0,0,0,0);
        int [] horiz = {l, r, R.l, R.r};
        Array.Sort(horiz);
        int [] vert = {b, t, R.b, R.t};
        Array.Sort(vert);

        return new Rect(horiz[1], vert[1], horiz[2], vert[2]);
    } 

    public int Area()
    {
        return (t - b)*(r-l);
    }

    public override string ToString()
    {
        return l + " " + b + " " + r + " " + t;
    }
}
9 голосов
/ 18 августа 2014

Самое простое решение

import numpy as np

A = np.zeros((100, 100))
B = np.zeros((100, 100))

A[rect1.top : rect1.bottom,  rect1.left : rect1.right] = 1
B[rect2.top : rect2.bottom,  rect2.left : rect2.right] = 1

area_of_union     = np.sum((A + B) > 0)
area_of_intersect = np.sum((A + B) > 1)

В этом примере мы создаем две нулевые матрицы размером с холст. Для каждого прямоугольника заполните одну из этих матриц теми, где прямоугольник занимает место. Затем сложите матрицы. Теперь sum(A+B > 0) - это область объединения, а sum(A+B > 1) - это область перекрытия. Этот пример может легко обобщаться на несколько прямоугольников.

6 голосов
/ 29 октября 2008

Вот что-то, что с моей головы звучит так, как будто это может работать:

  1. Создать словарь с двойным ключом и списком прямоугольников + логических значений, например:

    Словарь >> прямоугольники;

  2. Для каждого прямоугольника в вашем наборе найдите соответствующий список для значений x0 и x1 и добавьте прямоугольник в этот список с логическим значением true для x0 и false для x1. Таким образом, теперь у вас есть полный список всех x-координат, которые каждый прямоугольник либо вводит (true), либо оставляет (false) x-direction

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

  4. Сохраняйте набор прямоугольников, которые вы сейчас просматриваете, и который начинается пустым. Для каждого значения x, которое вы повторяете в точке 3, если прямоугольник зарегистрирован с истинным значением, добавьте его в набор, в противном случае удалите его.
  5. Для полосы отсортируйте прямоугольники по их координате y
  6. Проходить по прямоугольникам в полосе, считая перекрывающиеся расстояния (пока неясно, как это сделать эффективно)
  7. Рассчитать ширину полосы, умноженную на высоту перекрывающихся расстояний, чтобы получить области

Пример, 5 прямоугольников, нарисованных друг над другом, от a до e:

aaaaaaaaaaaaaaaa          bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa          bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa          bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa          bbbbbbbbbbbbbbbbb
aaaaaaaadddddddddddddddddddddddddddddbbbbbb
aaaaaaaadddddddddddddddddddddddddddddbbbbbb
        ddddddddddddddddddddddddddddd
        ddddddddddddddddddddddddddddd
        ddddddddddddddeeeeeeeeeeeeeeeeee
        ddddddddddddddeeeeeeeeeeeeeeeeee
        ddddddddddddddeeeeeeeeeeeeeeeeee
ccccccccddddddddddddddeeeeeeeeeeeeeeeeee
ccccccccddddddddddddddeeeeeeeeeeeeeeeeee
cccccccccccc          eeeeeeeeeeeeeeeeee
cccccccccccc          eeeeeeeeeeeeeeeeee
cccccccccccc
cccccccccccc

Вот список x-координат:

v       v  v   v      v   v         v  v  v   
|aaaaaaa|aa|aaaa      |   bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa      |   bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa      |   bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa      |   bbbbbbbbbb|bb|bbb
|aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb
|aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb
|       ddd|dddddddddd|dddddddddddddd  |
|       ddd|dddddddddd|dddddddddddddd  |
|       ddd|ddddddddddeeeeeeeeeeeeeeeeee
|       ddd|ddddddddddeeeeeeeeeeeeeeeeee
|       ddd|ddddddddddeeeeeeeeeeeeeeeeee
ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee
ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee
cccccccccccc          eeeeeeeeeeeeeeeeee
cccccccccccc          eeeeeeeeeeeeeeeeee
cccccccccccc
cccccccccccc

Список будет (где каждому v просто дается координата, начинающаяся с 0 и повышающаяся):

0: +a, +c
1: +d
2: -c
3: -a
4: +e
5: +b
6: -d
7: -e
8: -b

Таким образом, каждая полоса будет (прямоугольники отсортированы сверху вниз):

0-1: a, c
1-2: a, d, c
2-3: a, d
3-4: d
4-5: d, e
5-6: b, d, e
6-7: b, e
7-8: b

для каждой полосы перекрытие будет:

0-1: none
1-2: a/d, d/c
2-3: a/d
3-4: none
4-5: d/e
5-6: b/d, d/e
6-7: none
7-8: none

Я полагаю, что вариант алгоритма сортировки + ввода / вывода для проверки сверху вниз также будет выполним:

  1. отсортируйте прямоугольники, которые мы сейчас анализируем в полосе сверху вниз, для прямоугольников с одинаковой верхней координатой, а также отсортируем их по нижней координате
  2. итерация по y-координатам, и когда вы вводите прямоугольник, добавляете его в набор, когда вы покидаете прямоугольник, удаляете его из набора
  3. всякий раз, когда набор имеет более одного прямоугольника, у вас есть перекрытие (и если вы убедитесь, что добавляете / удаляете все прямоугольники, которые имеют ту же самую верхнюю / нижнюю координату, на которую вы в данный момент смотрите, множественные перекрывающиеся прямоугольники не будут проблемой

Для 1-2 полоски выше, вы должны выполнить итерацию так:

0. empty set, zero sum
1. enter a, add a to set (1 rectangle in set)
2. enter d, add d to set (>1 rectangles in set = overlap, store this y-coordinate)
3. leave a, remove a from set (now back from >1 rectangles in set, add to sum: y - stored_y
4. enter c, add c to set (>1 rectangles in set = overlap, store this y-coordinate)
5. leave d, remove d from set (now back from >1 rectangles in set, add to sum: y - stored_y)
6. multiply sum with width of strip to get overlapping areas

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

Надеюсь, это было понятно, и, как я уже сказал, это не в моей голове, никоим образом не проверено.

3 голосов
/ 13 апреля 2010

Вот код, который я написал для алгоритма развертки области:

#include <iostream>
#include <vector>

using namespace std;


class Rectangle {
public:
    int x[2], y[2];

    Rectangle(int x1, int y1, int x2, int y2) {
        x[0] = x1;
        y[0] = y1;
        x[1] = x2;
        y[1] = y2; 
    };
    void print(void) {
        cout << "Rect: " << x[0] << " " << y[0] << " " << x[1] << " " << y[1] << " " <<endl;
    };
};

// return the iterator of rec in list
vector<Rectangle *>::iterator bin_search(vector<Rectangle *> &list, int begin, int end, Rectangle *rec) {
    cout << begin << " " <<end <<endl;
    int mid = (begin+end)/2;
    if (list[mid]->y[0] == rec->y[0]) {
        if (list[mid]->y[1] == rec->y[1])
            return list.begin() + mid;
        else if (list[mid]->y[1] < rec->y[1]) {
            if (mid == end)
                return list.begin() + mid+1;
            return bin_search(list,mid+1,mid,rec);
        }
        else {
            if (mid == begin)
                return list.begin()+mid;
            return bin_search(list,begin,mid-1,rec);
        }
    }
    else if (list[mid]->y[0] < rec->y[0]) {
        if (mid == end) {
            return list.begin() + mid+1;
        }
        return bin_search(list, mid+1, end, rec);
    }
    else {
        if (mid == begin) {
            return list.begin() + mid;
        }
        return bin_search(list, begin, mid-1, rec);
    }
}

// add rect to rects
void add_rec(Rectangle *rect, vector<Rectangle *> &rects) {
    if (rects.size() == 0) {
        rects.push_back(rect);
    }
    else {
        vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect);
        rects.insert(it, rect);
    }
}

// remove rec from rets
void remove_rec(Rectangle *rect, vector<Rectangle *> &rects) {
    vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect);
    rects.erase(it);
}

// calculate the total vertical length covered by rectangles in the active set
int vert_dist(vector<Rectangle *> as) {
    int n = as.size();

    int totallength = 0;
    int start, end;

    int i = 0;
    while (i < n) {
        start = as[i]->y[0];
        end = as[i]->y[1];
        while (i < n && as[i]->y[0] <= end) {
            if (as[i]->y[1] > end) {
                end = as[i]->y[1];
            }
            i++;
        }
        totallength += end-start;
    }
    return totallength;
}

bool mycomp1(Rectangle* a, Rectangle* b) {
    return (a->x[0] < b->x[0]);
}

bool mycomp2(Rectangle* a, Rectangle* b) {
    return (a->x[1] < b->x[1]);
}

int findarea(vector<Rectangle *> rects) {
    vector<Rectangle *> start = rects;
    vector<Rectangle *> end = rects;
    sort(start.begin(), start.end(), mycomp1);
    sort(end.begin(), end.end(), mycomp2);

    // active set
    vector<Rectangle *> as;

    int n = rects.size();

    int totalarea = 0;
    int current = start[0]->x[0];
    int next;
    int i = 0, j = 0;
    // big loop
    while (j < n) {
        cout << "loop---------------"<<endl;
        // add all recs that start at current
        while (i < n && start[i]->x[0] == current) {
            cout << "add" <<endl;
            // add start[i] to AS
            add_rec(start[i], as);
            cout << "after" <<endl;
            i++;
        }
        // remove all recs that end at current
        while (j < n && end[j]->x[1] == current) {
            cout << "remove" <<endl;
            // remove end[j] from AS
            remove_rec(end[j], as);
            cout << "after" <<endl;
            j++;
        }

        // find next event x
        if (i < n && j < n) {
            if (start[i]->x[0] <= end[j]->x[1]) {
                next = start[i]->x[0];
            }
            else {
                next = end[j]->x[1];
            }
        }
        else if (j < n) {
            next = end[j]->x[1];
        }

        // distance to next event
        int horiz = next - current;
        cout << "horiz: " << horiz <<endl;

        // figure out vertical dist
        int vert = vert_dist(as);
        cout << "vert: " << vert <<endl;

        totalarea += vert * horiz;

        current = next;
    }
    return totalarea;
}

int main() {
    vector<Rectangle *> rects;
    rects.push_back(new Rectangle(0,0,1,1));

    rects.push_back(new Rectangle(1,0,2,3));

    rects.push_back(new Rectangle(0,0,3,3));

    rects.push_back(new Rectangle(1,0,5,1));

    cout << findarea(rects) <<endl;
}
3 голосов
/ 18 ноября 2008

Используя пример:

   1   2   3   4   5   6

1  +---+---+
   |       |   
2  +   A   +---+---+
   |       | B     |
3  +       +   +---+---+
   |       |   |   |   |
4  +---+---+---+---+   +
               |       |
5              +   C   +
               |       |
6              +---+---+

1) собрать все координаты х (как левые, так и правые) в список, затем отсортировать и удалить дубликаты

1 3 4 5 6

2) собрать все координаты y (как верхнюю, так и нижнюю) в список, затем отсортировать и удалить дубликаты

1 2 3 4 6

3) создать двумерный массив по количеству промежутков между уникальными координатами x * числу промежутков между уникальными координатами y.

4 * 4

4) закрасить все прямоугольники в эту сетку, увеличивая счетчик каждой ячейки, над которой она происходит:

   1   3   4   5   6

1  +---+
   | 1 | 0   0   0
2  +---+---+---+
   | 1 | 1 | 1 | 0
3  +---+---+---+---+
   | 1 | 1 | 2 | 1 |
4  +---+---+---+---+
     0   0 | 1 | 1 |
6          +---+---+

5) общая площадь областей ячеек в сетке, число которых превышает единицу, является областью перекрытия. Для повышения эффективности в разреженных сценариях вы можете сохранять промежуточные суммы площади при рисовании прямоугольников при каждом перемещении ячейки с 1 на 2.


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


PS Использование аппаратного ускорителя, как в моем другом ответе, не такая уж и плохая идея, если разрешение приемлемо. Его также легко реализовать в гораздо меньшем количестве кода, чем описанный выше подход. Лошади на курсы.

2 голосов
/ 06 января 2016

Существует решение, указанное по ссылке http://codercareer.blogspot.com/2011/12/no-27-area-of-rectangles.html для определения общей площади нескольких прямоугольников, чтобы перекрывающаяся область учитывалась только один раз.

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

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

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

Вот рабочий код для решения, которое я реализовал в Java.

import java.io.*;
import java.util.*;

class Solution {

static class Rectangle{
         int x;
         int y;
         int dx;
         int dy;

         Rectangle(int x, int y, int dx, int dy){
           this.x = x;
           this.y = y;
           this.dx = dx;
           this.dy = dy;
         }

         Range getBottomLeft(){
            return new Range(x, y);
         }

         Range getTopRight(){
            return new Range(x + dx, y + dy);
         }

         @Override
         public int hashCode(){
            return (x+y+dx+dy)/4;
         }

         @Override
         public boolean equals(Object other){
            Rectangle o = (Rectangle) other;
            return o.x == this.x && o.y == this.y && o.dx == this.dx && o.dy == this.dy;
         }

        @Override
        public String toString(){
            return String.format("X = %d, Y = %d, dx : %d, dy : %d", x, y, dx, dy);
        }
     }     

     static class RW{
         Rectangle r;
         boolean start;

         RW (Rectangle r, boolean start){
           this.r = r;
           this.start = start;
         }

         @Override
         public int hashCode(){
             return r.hashCode() + (start ? 1 : 0);
         }

         @Override
         public boolean equals(Object other){
              RW o = (RW)other;
             return o.start == this.start && o.r.equals(this.r);
         }

        @Override
        public String toString(){
            return "Rectangle : " + r.toString() + ", start = " + this.start;
        }
     }

     static class Range{
         int l;
         int u;   

       public Range(int l, int u){
         this.l = l;
         this.u = u;
       }

         @Override
         public int hashCode(){
            return (l+u)/2;
         }

         @Override
         public boolean equals(Object other){
            Range o = (Range) other;
            return o.l == this.l && o.u == this.u;
         }

        @Override
        public String toString(){
            return String.format("L = %d, U = %d", l, u);
        }
     }

     static class XComp implements Comparator<RW>{
             @Override
             public int compare(RW rw1, RW rw2){
                 //TODO : revisit these values.
                 Integer x1 = -1;
                 Integer x2 = -1;

                 if(rw1.start){
                     x1 = rw1.r.x;
                 }else{
                     x1 = rw1.r.x + rw1.r.dx;
                 }   

                 if(rw2.start){
                     x2 = rw2.r.x;
                 }else{
                     x2 = rw2.r.x + rw2.r.dx;
                 }

                 return x1.compareTo(x2);
             }
     }

     static class YComp implements Comparator<RW>{
             @Override
             public int compare(RW rw1, RW rw2){
                 //TODO : revisit these values.
                 Integer y1 = -1;
                 Integer y2 = -1;

                 if(rw1.start){
                     y1 = rw1.r.y;
                 }else{
                     y1 = rw1.r.y + rw1.r.dy;
                 }   

                 if(rw2.start){
                     y2 = rw2.r.y;
                 }else{
                     y2 = rw2.r.y + rw2.r.dy;
                 }

                 return y1.compareTo(y2);
             }
     }

     public static void main(String []args){
         Rectangle [] rects = new Rectangle[4];

         rects[0] = new Rectangle(10, 10, 10, 10);
         rects[1] = new Rectangle(15, 10, 10, 10);
         rects[2] = new Rectangle(20, 10, 10, 10);
         rects[3] = new Rectangle(25, 10, 10, 10);

         int totalArea = getArea(rects, false);
         System.out.println("Total Area : " + totalArea);

         int overlapArea = getArea(rects, true);              
         System.out.println("Overlap Area : " + overlapArea);
     }


     static int getArea(Rectangle []rects, boolean overlapOrTotal){
         printArr(rects);

         // step 1: create two wrappers for every rectangle
         RW []rws = getWrappers(rects);       

         printArr(rws);        

         // steps 2 : sort rectangles by their x-coordinates
         Arrays.sort(rws, new XComp());   

         printArr(rws);        

         // step 3 : group the rectangles in every range.
         Map<Range, List<Rectangle>> rangeGroups = groupRects(rws, true);

         for(Range xrange : rangeGroups.keySet()){
             List<Rectangle> xRangeRects = rangeGroups.get(xrange);
             System.out.println("Range : " + xrange);
             System.out.println("Rectangles : ");
             for(Rectangle rectx : xRangeRects){
                System.out.println("\t" + rectx);               
             }
         }   

         // step 4 : iterate through each of the pairs and their rectangles

         int sum = 0;
         for(Range range : rangeGroups.keySet()){
             List<Rectangle> rangeRects = rangeGroups.get(range);
             sum += getOverlapOrTotalArea(rangeRects, range, overlapOrTotal);
         }
         return sum;         
     }    

     static Map<Range, List<Rectangle>> groupRects(RW []rws, boolean isX){
         //group the rws with either x or y coordinates.

         Map<Range, List<Rectangle>> rangeGroups = new HashMap<Range, List<Rectangle>>();

         List<Rectangle> rangeRects = new ArrayList<Rectangle>();            

         int i=0;
         int prev = Integer.MAX_VALUE;

         while(i < rws.length){
             int curr = isX ? (rws[i].start ? rws[i].r.x : rws[i].r.x + rws[i].r.dx): (rws[i].start ? rws[i].r.y : rws[i].r.y + rws[i].r.dy);

             if(prev < curr){
                Range nRange = new Range(prev, curr);
                rangeGroups.put(nRange, rangeRects);
                rangeRects = new ArrayList<Rectangle>(rangeRects);
             }
             prev = curr;

             if(rws[i].start){
               rangeRects.add(rws[i].r);
             }else{
               rangeRects.remove(rws[i].r);
             }

           i++;
         }
       return rangeGroups;
     }

     static int getOverlapOrTotalArea(List<Rectangle> rangeRects, Range range, boolean isOverlap){
         //create horizontal sweep lines similar to vertical ones created above

         // Step 1 : create wrappers again
         RW []rws = getWrappers(rangeRects);

         // steps 2 : sort rectangles by their y-coordinates
         Arrays.sort(rws, new YComp());

         // step 3 : group the rectangles in every range.
         Map<Range, List<Rectangle>> yRangeGroups = groupRects(rws, false);

         //step 4 : for every range if there are more than one rectangles then computer their area only once.

         int sum = 0;
         for(Range yRange : yRangeGroups.keySet()){
             List<Rectangle> yRangeRects = yRangeGroups.get(yRange);

             if(isOverlap){
                 if(yRangeRects.size() > 1){
                     sum += getArea(range, yRange);
                 }
             }else{
                 if(yRangeRects.size() > 0){
                     sum += getArea(range, yRange);
                 }
             }
         }         
         return sum;
     } 

    static int getArea(Range r1, Range r2){
      return (r2.u-r2.l)*(r1.u-r1.l);      
    }

    static RW[] getWrappers(Rectangle []rects){
         RW[] wrappers = new RW[rects.length * 2];

         for(int i=0,j=0;i<rects.length;i++, j+=2){
             wrappers[j] = new RW(rects[i], true); 
             wrappers[j+1] = new RW(rects[i], false); 
         }
         return wrappers;
     }

    static RW[] getWrappers(List<Rectangle> rects){
         RW[] wrappers = new RW[rects.size() * 2];

         for(int i=0,j=0;i<rects.size();i++, j+=2){
             wrappers[j] = new RW(rects.get(i), true); 
             wrappers[j+1] = new RW(rects.get(i), false); 
         }
         return wrappers;
     }

  static void printArr(Object []a){
    for(int i=0; i < a.length;i++){
      System.out.println(a[i]);
    }
    System.out.println();
  }     
2 голосов
/ 29 октября 2008

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

0 голосов
/ 16 октября 2017

Следующий ответ должен дать общую площадь только один раз. это приходит к предыдущим ответам, но теперь реализовано в C #. Он также работает с числами с плавающей запятой (или двойными, если вам нужно [это не влияет на значения)

Кредиты: http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html

EDIT: ОП попросил перекрывающуюся область - это, очевидно, очень просто:

var totArea = rects.Sum(x => x.Width * x.Height);

и тогда ответ:

var overlappingArea =totArea-GetArea(rects)

Вот код:

   #region rectangle overlapping
        /// <summary>
        /// see algorithm for detecting overlapping areas here: https://stackoverflow.com/a/245245/3225391
        /// or easier here:
        /// http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html
        /// </summary>
        /// <param name="dim"></param>
        /// <returns></returns>
        public static float GetArea(RectangleF[] rects)
        {
            List<float> xs = new List<float>();
            foreach (var item in rects)
            {
                xs.Add(item.X);
                xs.Add(item.Right);
            }
            xs = xs.OrderBy(x => x).Cast<float>().ToList();
            rects = rects.OrderBy(rec => rec.X).Cast<RectangleF>().ToArray();
            float area = 0f;
            for (int i = 0; i < xs.Count - 1; i++)
            {
                if (xs[i] == xs[i + 1])//not duplicate
                    continue;
                int j = 0;
                while (rects[j].Right < xs[i])
                    j++;
                List<Range> rangesOfY = new List<Range>();
                var rangeX = new Range(xs[i], xs[i + 1]);
                GetRangesOfY(rects, j, rangeX, out rangesOfY);
                area += GetRectArea(rangeX, rangesOfY);
            }
            return area;
        }

        private static void GetRangesOfY(RectangleF[] rects, int rectIdx, Range rangeX, out List<Range> rangesOfY)
        {
            rangesOfY = new List<Range>();
            for (int j = rectIdx; j < rects.Length; j++)
            {
                if (rangeX.less < rects[j].Right && rangeX.greater > rects[j].Left)
                {
                    rangesOfY = Range.AddRange(rangesOfY, new Range(rects[j].Top, rects[j].Bottom));
#if DEBUG
                    Range rectXRange = new Range(rects[j].Left, rects[j].Right);
#endif
                }
            }
        }

        static float GetRectArea(Range rangeX, List<Range> rangesOfY)
        {
            float width = rangeX.greater - rangeX.less,
                area = 0;

            foreach (var item in rangesOfY)
            {
                float height = item.greater - item.less;
                area += width * height;
            }
            return area;
        }

        internal class Range
        {
            internal static List<Range> AddRange(List<Range> lst, Range rng2add)
            {
                if (lst.isNullOrEmpty())
                {
                    return new List<Range>() { rng2add };
                }

                for (int i = lst.Count - 1; i >= 0; i--)
                {
                    var item = lst[i];
                    if (item.IsOverlapping(rng2add))
                    {
                        rng2add.Merge(item);
                        lst.Remove(item);
                    }
                }
                lst.Add(rng2add);
                return lst;
            }
            internal float greater, less;
            public override string ToString()
            {
                return $"ln{less} gtn{greater}";
            }

            internal Range(float less, float greater)
            {
                this.less = less;
                this.greater = greater;
            }

            private void Merge(Range rng2add)
            {
                this.less = Math.Min(rng2add.less, this.less);
                this.greater = Math.Max(rng2add.greater, this.greater);
            }
            private bool IsOverlapping(Range rng2add)
            {
                return !(less > rng2add.greater || rng2add.less > greater);
                //return
                //    this.greater < rng2add.greater && this.greater > rng2add.less
                //    || this.less > rng2add.less && this.less < rng2add.greater

                //    || rng2add.greater < this.greater && rng2add.greater > this.less
                //    || rng2add.less > this.less && rng2add.less < this.greater;
            }
        }
        #endregion rectangle overlapping
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...