web-dev-qa-db-fra.com

Qu'est-ce qu'un algorithme efficace pour trouver une zone de rectangles se chevauchant

Ma situation

  • Entrée: un ensemble de rectangles 
  • chaque rect est composé de 4 doubles comme ceci: (x0, y0, x1, y1)
  • ils ne sont "tournés" à aucun angle, ils sont tous des rectangles "normaux" qui vont "haut/bas" et "gauche/droite" par rapport à l'écran
  • ils sont placés au hasard - ils peuvent se toucher sur les bords, se chevaucher ou ne pas avoir de contact
  • J'aurai plusieurs centaines de rectangles
  • ceci est implémenté en C #

J'ai besoin de trouver

  • La zone qui est formée par leur chevauchement - toute la zone de la toile qui est recouverte par plus d'un rectangle (par exemple, avec deux rectangles, ce serait l'intersection)
  • Je n'ai pas besoin de la géométrie du chevauchement - juste de la surface (exemple: 4 pouces carrés)
  • Les chevauchements ne doivent pas être comptés plusieurs fois - imaginons par exemple 3 rats qui ont la même taille et la même position - ils sont superposés - cette zone doit être comptée une fois (pas trois fois)

Exemple

  • L'image ci-dessous contient trois rectangles: A, B, C
  • Chevauchement de A et B (comme indiqué par des tirets)
  • B et C se chevauchent (comme indiqué par des tirets)
  • Ce que je recherche, c’est la zone où les tirets sont visibles

-

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
44
namenlos

Un moyen efficace de calculer cette zone consiste à utiliser un algorithme de balayage. Supposons que nous balayons une ligne verticale L(x) à travers l'union des rectangles U: 

  • tout d’abord, vous devez créer une file d’événements Q, qui est dans ce cas la liste ordonnée de toutes les coordonnées x (gauche et droite) des rectangles. 
  • pendant le balayage, vous devez conserver une structure de données 1D, qui devrait vous donner la longueur totale de l'intersection de L(x) et de U. L'important est que cette longueur soit constante entre deux événements consécutifs q et q ' de Q. Donc, si l(q) indique la longueur totale de L (q +) (c'est-à-dire que L ne se trouve que du côté droit de q) croisée avec U, la zone balayée par L entre les événements q et q 'est l (q) * (q '- q).
  • il suffit de résumer toutes ces zones balayées pour obtenir le total.

Nous devons encore résoudre le problème 1D. Vous voulez une structure 1D, qui calcule dynamiquement une union de segments (verticaux). Par dynamique, je veux dire que vous ajoutez parfois un nouveau segment et en supprimez parfois un. 

J'ai déjà détaillé dans ma réponse à cette question collapsing range comment le faire de manière statique (qui est en fait un balayage 1D). Donc, si vous voulez quelque chose de simple, vous pouvez l'appliquer directement (en recalculant l'union pour chaque événement). Si vous voulez quelque chose de plus efficace, il vous suffit de l'adapter un peu:

  • en supposant que vous connaissez l'union des segments S1... Sn se compose de segments D disjoints1...RÉk. Ajout de Sn + 1 est très facile, il vous suffit de localiser les deux extrémités de Sn + 1 parmi les extrémités de D1...RÉk.
  • en supposant que vous connaissez l'union des segments S1... Sn se compose de segments D disjoints1...RÉk, suppression du segment Sje (en supposant que Sje a été inclus dans Dj) signifie recalculer l’union des segments que Dj composé de, sauf Sje (en utilisant l'algorithme statique).

Ceci est votre algorithme dynamique. En supposant que vous utilisiez des ensembles triés avec des requêtes d'emplacement de journal pour représenter D1...RÉk, c’est probablement la méthode non spécialisée la plus efficace que vous puissiez obtenir.

51
Camille

Une approche possible consiste à le tracer sur une toile! Dessinez chaque rectangle en utilisant une couleur semi-transparente. Le runtime .NET dessinera dans un code natif optimisé - ou même à l'aide d'un accélérateur matériel.

Ensuite, vous devez relire les pixels. Chaque pixel est-il la couleur de fond, la couleur du rectangle ou une autre couleur? La seule façon de choisir une autre couleur est si deux ou plusieurs rectangles se chevauchent ...

Si c'est trop compliqué, je recommanderais le quad-tree comme un autre répondeur, ou le r-tree .

13
Will

C'est un code rapide et sale que j'ai utilisé dans le TopCoder SRM 160 Div 2.

t = top
b = botttom
l = left
r = right

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
LeppyR64

La solution la plus simple

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)

Dans cet exemple, nous créons deux matrices nulles qui correspondent à la taille du canevas. Pour chaque rectangle, remplissez l'une de ces matrices avec des matrices où le rectangle occupe de la place. Puis additionnez les matrices. Maintenant sum(A+B > 0) est la zone du syndicat et sum(A+B > 1) est la zone de chevauchement. Cet exemple peut facilement généraliser à plusieurs rectangles.

8
Rose Perrone

Voici quelque chose qui me dit que cela pourrait marcher:

  1. Créez un dictionnaire avec une double clé et une liste de valeurs rectangle + booléen, comme ceci:

    Dictionary <Double, List <KeyValuePair <Rectangle, Boolean >>> rectangles;

  2. Pour chaque rectangle de votre ensemble, recherchez la liste correspondante pour les valeurs x0 et x1 et ajoutez le rectangle à cette liste, avec une valeur booléenne true pour x0 et false pour x1. De cette façon, vous avez maintenant une liste complète de toutes les coordonnées x que chaque rectangle entre (true) ou laisse (false) la direction x

  3. Prenez toutes les clés de ce dictionnaire (toutes les coordonnées x distinctes), triez-les et passez-les dans l'ordre, assurez-vous que vous pouvez obtenir à la fois la valeur x actuelle et la suivante (vous en aurez besoin à la fois ). Cela vous donne des bandes individuelles de rectangles

  4. Conservez un ensemble de rectangles que vous êtes en train de regarder, qui commencent vides. Pour chaque valeur x que vous parcourez au point 3, si le rectangle est enregistré avec une valeur vraie, ajoutez-le à l'ensemble, sinon supprimez-le.
  5. Pour une bande, trier les rectangles par leur coordonnée y
  6. Parcourez les rectangles de la bande en comptant les distances qui se chevauchent (je ne sais pas comment faire cela efficacement)
  7. Calculer la largeur de la bande multipliée par la hauteur des distances qui se chevauchent pour obtenir des zones

Exemple, 5 rectangles, se superposent, de 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

Voici la liste des coordonnées 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

La liste serait (où chaque v reçoit simplement une coordonnée commençant à 0 et allant vers le haut):

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

Chaque bande serait donc (des rectangles triés de haut en bas):

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

pour chaque bande, le chevauchement serait:

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

J'imagine qu'une variante de l'algorithme sort + entrée/sortie pour la vérification de haut en bas serait également réalisable:

  1. triez les rectangles analysés dans la bande, de haut en bas, pour obtenir des rectangles avec la même coordonnée supérieure, triez-les également
  2. parcourez les coordonnées y et, lorsque vous entrez un rectangle, ajoutez-le à l'ensemble, supprimez-le lorsque vous quittez un rectangle
  3. le jeu comporte plusieurs chevauchements (et si vous veillez à ajouter/supprimer tous les rectangles dont la coordonnée haut/bas est identique à celle que vous regardez actuellement, plusieurs rectangles superposés ne poseraient pas de problème.)

Pour la bande 1-2 ci-dessus, vous voudriez itérer comme ceci:

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

Vous n’auriez pas besoin de conserver un ensemble réel ici non plus, mais seulement le nombre de rectangles dans lesquels vous vous trouvez, chaque fois que cela passe de 1 à 2, enregistrez le y, et chaque fois qu’il passe de 2 à 1, calculez le y actuel - y stocké, et additionnez cette différence.

J'espère que cela était compréhensible, et comme je l'ai dit, cela ne vient pas de moi, cela n'a pas été testé.

En utilisant l'exemple:

    1 2 3 4 5 6 

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

1) rassemblez toutes les coordonnées x (à gauche et à droite) dans une liste, puis triez-la et supprimez les doublons

1 3 4 5 6

2) rassemblez toutes les coordonnées y (haut et bas) dans une liste, puis triez-la et éliminez les doublons

1 2 3 4 6

3) créer un tableau 2D par nombre d'espaces entre les coordonnées x uniques * nombre d'espaces entre les coordonnées y uniques.

4 * 4

4) Peignez tous les rectangles dans cette grille, en incrémentant le nombre de cellules sur lesquelles elle apparaît.

 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) la somme des zones des cellules de la grille dont le nombre est supérieur à un est la zone de chevauchement. Pour une meilleure efficacité dans les cas d'utilisation peu nombreux, vous pouvez réellement conserver un total cumulé de la surface lorsque vous peignez les rectangles, chaque fois que vous déplacez une cellule de 1 à 2.


Dans la question, les rectangles sont décrits comme étant quatre doubles. Les doublons contiennent généralement des erreurs d'arrondi et des erreurs peuvent se glisser dans la zone de chevauchement calculée. Si les coordonnées légales sont en points finis, envisagez d'utiliser une représentation entière.


PS en utilisant l'accélérateur matériel comme dans mon autre réponse n'est pas une si mauvaise idée, si la résolution est acceptable. Il est également facile à implémenter dans beaucoup moins de code que l’approche ci-dessus. Chevaux de course.

3
Will

Voici le code que j'ai écrit pour l'algorithme de balayage de zone:

#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
extraeee

Vous pouvez simplifier un peu ce problème en scindant chaque rectangle en rectangles plus petits. Rassemblez toutes les coordonnées X et Y de tous les rectangles et ceux-ci deviennent vos points de scission - si un rectangle traverse la ligne, scindez-les en deux. Lorsque vous avez terminé, vous avez une liste de rectangles qui se chevauchent soit à 0%, soit à 100%. Si vous les triez, il devrait être facile de trouver ceux qui sont identiques.

2
Mark Ransom

Il existe une solution répertoriée au lien http://codercareer.blogspot.com/2011/12/no-27-area-of-rectangles.html pour rechercher la surface totale de plusieurs rectangles telle que la zone superposée est compté qu'une seule fois. 

La solution ci-dessus peut être étendue pour ne calculer que la zone superposée (et cela aussi une seule fois, même si la zone superposée est recouverte de plusieurs rectangles) avec des lignes horizontales pour chaque paire de lignes verticales consécutives. 

Si l'objectif est simplement de connaître la surface totale couverte par tous les rectangles, les lignes horizontales de balayage ne sont pas nécessaires et une simple fusion de tous les rectangles entre deux lignes de balayage verticales donnerait l'aire. 

D'autre part, si vous souhaitez calculer uniquement la zone superposée, les lignes de balayage horizontales sont nécessaires pour déterminer le nombre de rectangles qui se chevauchent entre les lignes de balayage verticales (y1, y2).

Voici le code de travail pour la solution que j'ai implémentée en 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
tick_tack_techie

Si vos rectangles vont être clairsemés (la plupart du temps ne se croisent pas), alors il vaut peut-être la peine de regarder la mise en grappe dimensionnelle récursive. Sinon, un quad-arbre semble être la voie à suivre (comme cela a été mentionné par d'autres affiches.

Il s’agit d’un problème courant dans la détection des collisions dans les jeux informatiques. Il ne manque donc pas de ressources suggérant des moyens de le résoudre.

Here est un billet de blog de Nice résumant le RCD.

Here est un article de Dr.Dobbs résumant divers algorithmes de détection de collision, qui conviendrait.

0
Oliver Hallam

Considérant que nous avons deux rectangles (A et B) et que nous avons leur coordination en bas à gauche (x1, y1) et en haut à droite (x2, y2). Le morceau de code suivant vous permet de calculer la zone superposée en C++.

    #include <iostream>
using namespace std;

int rectoverlap (int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2)
{
    int width, heigh, area;

    if (ax2<bx1 || ay2<by1 || ax1>bx2 || ay1>by2) {
        cout << "Rectangles are not overlapped" << endl;
        return 0;
    }
    if (ax2>=bx2 && bx1>=ax1){
        width=bx2-bx1;
        heigh=by2-by1;
    } else if (bx2>=ax2 && ax1>=bx1) {
        width=ax2-ax1;
        heigh=ay2-ay1;
    } else {
        if (ax2>bx2){
            width=bx2-ax1;
        } else {
            width=ax2-bx1;
        }
        if (ay2>by2){
            heigh=by2-ay1;
        } else {
            heigh=ay2-by1;
        }
    }
    area= heigh*width;
    return (area);
}

int main()
{
    int ax1,ay1,ax2,ay2,bx1,by1,bx2,by2;
    cout << "Inter the x value for bottom left for rectangle A" << endl;
    cin >> ax1;
    cout << "Inter the y value for bottom left for rectangle A" << endl;
    cin >> ay1;
    cout << "Inter the x value for top right for rectangle A" << endl;
    cin >> ax2;
    cout << "Inter the y value for top right for rectangle A" << endl;
    cin >> ay2;
    cout << "Inter the x value for bottom left for rectangle B" << endl;
    cin >> bx1;
    cout << "Inter the y value for bottom left for rectangle B" << endl;
    cin >> by1;
    cout << "Inter the x value for top right for rectangle B" << endl;
    cin >> bx2;
    cout << "Inter the y value for top right for rectangle B" << endl;
    cin >> by2;
    cout << "The overlapped area is " <<  rectoverlap (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) << endl;
}
0
user3048546

La réponse suivante devrait donner la surface totale une seule fois… .. elle vient avec les réponses précédentes, mais elle est maintenant mise en œuvre en C # . Elle fonctionne aussi avec des flottants (ou double si vous avez besoin [cela ne vaut pas la valeur] .

Crédits: http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html

MODIFIER: Le PO a demandé la zone de chevauchement - ce qui est évidemment très simple:

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

et alors la réponse est:

var overlappingArea =totArea-GetArea(rects)

Voici le code:

   #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
0
ephraim

Ce type de détection de collision est souvent appelé AABB (Axis Aligned Bounding Boxes), ce qui constitue un bon point de départ pour une recherche google

0
grapefrukt

J'ai trouvé une solution différente de celle de l'algorithme de balayage.

Puisque vos rectangles sont tous rectangulaires, les lignes horizontales et verticales des rectangles formeront une grille rectangulaire irrégulière. Vous pouvez "peindre" les rectangles sur cette grille; ce qui signifie que vous pouvez déterminer quels champs de la grille seront remplis. Comme les lignes de la grille sont formées à partir des limites des rectangles donnés, un champ de cette grille sera toujours soit complètement vide, soit complètement rempli par un rectangle.

Je devais résoudre le problème en Java, voici donc ma solution: http://Pastebin.com/03mss8yf

Cette fonction calcule la surface complète occupée par les rectangles. Si vous ne vous intéressez qu'à la partie "chevauchement", vous devez étendre le bloc de code entre les lignes 70 et 72. Vous pouvez peut-être utiliser un deuxième jeu pour stocker les champs de grille utilisés plusieurs fois. Votre code entre les lignes 70 et 72 doit être remplacé par un bloc tel que:

GridLocation gl = new GridLocation(curX, curY);
if(usedLocations.contains(gl) && usedLocations2.add(gl)) {
  ret += width*height;
} else {
  usedLocations.add(gl);
}

La variable usedLocations2 est ici du même type que usedLocations; il sera construitau même point.

Je ne suis pas vraiment familier avec les calculs de complexité; donc je ne sais pas laquelle des deux solutions (balayage ou ma solution de grille) sera plus performante/évolutive.

0
Torsten Fehre

Vous pouvez trouver le chevauchement sur les axes x et y et les multiplier. 

int LineOverlap(int line1a, line1b, line2a, line2b) 
{
  // assume line1a <= line1b and line2a <= line2b
  if (line1a < line2a) 
  {
    if (line1b > line2b)
      return line2b-line2a;
    else if (line1b > line2a)
      return line1b-line2a;
    else 
      return 0;
  }
  else if (line2a < line1b)
    return line2b-line1a;
  else 
    return 0;
}


int RectangleOverlap(Rect rectA, rectB) 
{
  return LineOverlap(rectA.x1, rectA.x2, rectB.x1, rectB.x2) *
    LineOverlap(rectA.y1, rectA.y2, rectB.y1, rectB.y2);
}
0
Toon Krijthe