Un algoritmo per gonfiare / sgonfiare (offset, buffering) poligoni


202

Come potrei "gonfiare" un poligono? Cioè, voglio fare qualcosa di simile a questo:

testo alternativo

Il requisito è che i bordi / i punti del nuovo poligono (gonfiato) siano tutti alla stessa distanza costante dal vecchio poligono (originale) (nella foto di esempio non lo sono, da allora dovrebbe usare gli archi per i vertici gonfiati, ma cerchiamo di dimenticatene per ora;)).

Il termine matematico per quello che sto cercando è in realtà un offset poligonale verso l'interno / verso l'esterno . +1 a balint per averlo sottolineato. La denominazione alternativa è il buffering poligonale .

Risultati della mia ricerca:

Ecco alcuni link:


17
Questa non è affatto una domanda banale: se la deflazione / inflazione è piccola, non accade nulla di grave, ma a un certo punto i vertici spariranno. Probabilmente questo è già stato fatto prima, quindi direi: usa l'algoritmo di qualcun altro, non crearne uno tuo.
Martijn,

1
Infatti, se il tuo poligono è concavo per iniziare (come nell'esempio sopra), devi decidere cosa dovrebbe accadere nel punto in cui l'algoritmo ingenuo vuole creare un "poligono"
autointersecante

Sì, il problema principale sono le parti concave del poligono, qui sta la complessità. Penso ancora che non dovrebbe essere un tale problema calcolare quando un certo vertice deve essere eliminato. La domanda principale è che tipo di complessità asintotica ciò richiederebbe.
Igor Brejc,

Ciao, questo è anche il mio problema, tranne per il fatto che devo farlo in 3D. Esiste un'alternativa all'approccio Scheletri dritti di poliedri tridimensionali descritto nel documento arxiv.org/pdf/0805.0022.pdf ?
Stephanmg,

Risposte:


138

Ho pensato di poter menzionare brevemente la mia libreria di ritaglio e offset di poligoni : Clipper .

Mentre Clipper è progettato principalmente per le operazioni di ritaglio dei poligoni, esegue anche la compensazione dei poligoni. La libreria è freeware open source scritta in Delphi, C ++ e C # . Ha un boost molto libero licenza consente di utilizzarla gratuitamente sia in applicazioni freeware che commerciali.

L'offset poligonale può essere eseguito utilizzando uno dei tre stili di offset: quadrato, rotondo e obliquo.

Stili di offset poligonali


2
Molto bello! Dov'eri 2 anni fa? :) Alla fine ho dovuto implementare la mia logica di compensazione (e ho perso molto tempo). Quale algoritmo stai usando per la compensazione poligonale, BTW? Ho usato il fuoco d'erba. Gestisci buchi nei poligoni?
Igor Brejc,

2
2 anni fa stavo cercando una soluzione decente al clipping poligonale che non fosse gravata da complicati problemi di licenza :). La compensazione dei bordi viene ottenuta generando unità normali per tutti i bordi. Le giunture dei bordi vengono riordinate dal mio tagliatore di poligoni poiché gli orientamenti di queste intersezioni sovrapposte sono opposti all'orientamento dei poligoni. I fori sono certamente gestiti come poligoni che si intersecano da soli, ecc. Non ci sono restrizioni al loro tipo o numero. Vedi anche "Compensazione poligonale calcolando i
Angus Johnson

Whoa! Non per un secondo pensare che questa domanda sia "dimenticata"! Ho guardato qui la scorsa settimana - non mi aspettavo di tornare su questo! Grazie mille!
Chris Burt-Brown,

I documenti di Clipper sul poly buffering sono qui: angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/…
Drew Noakes

5
Per chiunque voglia farlo, un'altra alternativa è usare GEOS, e se usi python, il wrapper di GEOS, Shapely. Un esempio davvero carino: toblerity.github.com/shapely/manual.html#object.buffer
pelson,

40

Il poligono che stai cercando è chiamato poligono offset interno / esterno nella geometria computazionale ed è strettamente correlato allo scheletro dritto .

Questi sono diversi poligoni offset per un poligono complicato:

E questo è lo scheletro dritto per un altro poligono:

Come sottolineato anche in altri commenti, a seconda di quanto si prevede di "gonfiare / sgonfiare" il poligono, è possibile ottenere una connettività diversa per l'output.

Dal punto di vista del calcolo: una volta che hai lo scheletro dritto, dovresti essere in grado di costruire i poligoni offset relativamente facilmente. La libreria CGAL open source e (gratuita per non commerciale) ha un pacchetto che implementa queste strutture. Vedi questo esempio di codice per calcolare i poligoni offset usando CGAL.

Il manuale del pacchetto dovrebbe fornire un buon punto di partenza su come costruire queste strutture anche se non si utilizzerà CGAL e contiene riferimenti ai documenti con le definizioni e le proprietà matematiche:

Manuale CGAL: 2D Straight Skeleton e Polygon Offsetting


12

Per questi tipi di cose di solito uso JTS . A scopo dimostrativo ho creato questo jsFiddle che utilizza JSTS (porta JavaScript di JTS). Hai solo bisogno di convertire le coordinate che hai in coordinate JSTS:

function vectorCoordinates2JTS (polygon) {
  var coordinates = [];
  for (var i = 0; i < polygon.length; i++) {
    coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y));
  }
  return coordinates;
}

Il risultato è qualcosa del genere:

inserisci qui la descrizione dell'immagine

Informazioni aggiuntive : di solito uso questo tipo di gonfiaggio / sgonfiamento (un po 'modificato per i miei scopi) per impostare confini con raggio su poligoni che sono disegnati su una mappa (con Leaflet o Google maps). Basta convertire le coppie (lat, lng) in coordinate JSTS e tutto il resto è uguale. Esempio:

inserisci qui la descrizione dell'immagine


9

Mi sembra che quello che vuoi sia:

  • A partire da un vertice, faccia in senso antiorario lungo un bordo adiacente.
  • Sostituire il bordo con un nuovo bordo parallelo posto a distanza ddalla "sinistra" di quello vecchio.
  • Ripetere l'operazione per tutti i bordi.
  • Trova le intersezioni dei nuovi bordi per ottenere i nuovi vertici.
  • Rileva se sei diventato un poligono incrociato e decidi cosa fare al riguardo. Probabilmente aggiungi un nuovo vertice al punto di attraversamento e sbarazzati di quelli vecchi. Non sono sicuro che ci sia un modo migliore per rilevare questo che confrontare solo ogni coppia di bordi non adiacenti per vedere se la loro intersezione si trova tra entrambe le coppie di vertici.

Il poligono risultante si trova alla distanza richiesta dal vecchio poligono "abbastanza lontano" dai vertici. Vicino a un vertice, l'insieme di punti a distanza ddal vecchio poligono non è, come dici tu, non un poligono, quindi il requisito indicato non può essere soddisfatto.

Non so se questo algoritmo abbia un nome, un codice di esempio sul Web o un'ottimizzazione diabolica, ma penso che descriva ciò che desideri.



5

Ogni linea dovrebbe dividere il piano in "interno" e "contorno"; puoi scoprirlo usando il solito metodo del prodotto interno.

Sposta tutte le linee verso l'esterno di una certa distanza.

Considera tutte le coppie di linee vicine (linee, non segmento), trova l'intersezione. Questi sono i nuovi vertici.

Pulisci il nuovo vertice rimuovendo le parti intersecanti. - abbiamo alcuni casi qui

(a) Caso 1:

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

se lo spendi per uno, ottieni questo:

0----a----3
|    |    |
|    |    |
|    b    |
|         |
|         |
1---------2

7 e 4 si sovrappongono .. se vedi questo, rimuovi questo punto e tutti i punti in mezzo.

(b) caso 2

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

se lo spendi per due, ottieni questo:

0----47----3
|    ||    |
|    ||    |
|    ||    |
|    56    |
|          |
|          |
|          |
1----------2

per risolvere questo, per ogni segmento di linea, è necessario verificare se si sovrappone a questi ultimi segmenti.

(c) caso 3

       4--3
 0--X9 |  |
 |  78 |  |
 |  6--5  |
 |        |
 1--------2

spesa di 1. questo è un caso più generale per il caso 1.

(d) caso 4

come case3, ma spese di due.

In realtà, se riesci a gestire il caso 4. Tutti gli altri casi sono solo casi speciali con qualche riga o vertice sovrapposti.

Per fare il caso 4, mantieni una pila di vertici .. spingi quando trovi linee che si sovrappongono con quest'ultima, fai scoppiare quando ottieni quest'ultima. - proprio come quello che fai nello scafo convesso.


conosci qualche algoritmo psedo per questo.
EmptyData

5

Ecco una soluzione alternativa, vedi se ti piace di più.

  1. Esegui una triangolazione , non deve essere delaunay - qualsiasi triangolazione farebbe.

  2. Gonfia ogni triangolo - questo dovrebbe essere banale. se memorizzi il triangolo in senso antiorario, sposta le linee sul lato destro ed esegui l'intersezione.

  3. Uniscili usando un algoritmo di ritaglio Weiler-Atherton modificato


come si gonfiano esattamente i triangoli? Il tuo output dipende dalla triangolazione? Con questo approccio puoi gestire il caso quando riduci il poligono?
balint.miklos,

Sei sicuro che questo approccio funzioni davvero per l'inflazione poligonale? Cosa succede quando le parti concave del poligono sono gonfiate a tal punto che alcuni vertici devono essere eliminati. Il fatto è: quando guardi cosa succede ai triangoli dopo poli. inflazione, i triangoli non sono gonfiati, ma sono distorti.
Igor Brejc,

1
Igor: l'algoritmo di ritaglio di Weiler-Atherton è in grado di gestire correttamente il caso "alcuni vertici devono essere eliminati";
J-16 SDiZ,

@balint: gonfiare un triangolo è banale: se si memorizza il vertice in ordine normale, il lato destro è sempre "esteriore". Tratta questi segmenti come linee, spostali verso l'esterno e trova l'interazione: sono il nuovo vertice. Per la stessa triangolazione, ripensandoci, la triangolazione delaunay può dare risultati migliori.
J-16 SDiZ,

4
Penso che questo approccio possa facilmente dare risultati negativi. Anche per un semplice esempio come quad triangolare usando una diagonale. Per i due triangoli ingranditi ottieni: img200.imageshack.us/img200/2640/counterm.png e la loro unione non è proprio quello che stai cercando. Non vedo come questo metodo sia utile.
balint.miklos,

3

Grazie mille ad Angus Johnson per la sua libreria di clipper. Ci sono buoni esempi di codice per fare il ritaglio sulla homepage del clipper all'indirizzo http://www.angusj.com/delphi/clipper.php#code ma non ho visto un esempio di compensazione del poligono. Quindi ho pensato che forse è utile per qualcuno se pubblico il mio codice:

    public static List<Point> GetOffsetPolygon(List<Point> originalPath, double offset)
    {
        List<Point> resultOffsetPath = new List<Point>();

        List<ClipperLib.IntPoint> polygon = new List<ClipperLib.IntPoint>();
        foreach (var point in originalPath)
        {
            polygon.Add(new ClipperLib.IntPoint(point.X, point.Y));
        }

        ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset();
        co.AddPath(polygon, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);

        List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
        co.Execute(ref solution, offset);

        foreach (var offsetPath in solution)
        {
            foreach (var offsetPathPoint in offsetPath)
            {
                resultOffsetPath.Add(new Point(Convert.ToInt32(offsetPathPoint.X), Convert.ToInt32(offsetPathPoint.Y)));
            }
        }

        return resultOffsetPath;
    }

2

Un'ulteriore opzione è usare boost :: poligono - la documentazione è in qualche modo carente, ma dovresti trovare che i metodi resizee bloat, e anche l' +=operatore sovraccarico , che implementano effettivamente il buffering. Quindi, ad esempio, aumentare la dimensione di un poligono (o un insieme di poligoni) di un valore può essere semplice come:

poly += 2; // buffer polygon by 2

Non capisco come dovresti fare qualcosa con boost :: poligono poiché supporta solo coordinate intere? Supponiamo che io abbia un poligono generale (coordinate in virgola mobile) e che voglia espanderlo: cosa farei?
David Doria,

@DavidDoria: dipende dalla risoluzione / accuratezza e dalla gamma dinamica di cui hai bisogno per le tue coordinate, ma potresti usare un int a 32 bit o 64 bit con un fattore di ridimensionamento appropriato. Per inciso ho usato (accidentalmente) boost :: poligono con coordinate float in passato e sembra funzionare bene, ma potrebbe non essere robusto al 100% (i documenti lo mettono in guardia!).
Paolo R

Starei bene con "funzionerà per la maggior parte del tempo" :). Ho provato questo: ideone.com/XbZeBf ma non si compila - qualche pensiero?
David Doria,

Ovviamente non vedo nulla di sbagliato, ma nel mio caso stavo usando le specializzazioni rettilinee (polygon_90), quindi non so se questo faccia la differenza. Sono passati alcuni anni da quando ho giocato con questo però.
Paul R

OK - mi sta tornando adesso - puoi usarlo solo +=con un set di poligoni , non con singoli poligoni. Provalo con uno std :: vettore di poligoni. (Naturalmente il vettore deve contenere solo un poligono).
Paul R

1

Sulla base dei consigli di @ JoshO'Brian, sembra che il rGeospacchetto nella Rlingua implementa questo algoritmo. Vedere rGeos::gBuffer.


0

Esistono un paio di librerie che è possibile utilizzare (utilizzabili anche per set di dati 3D).

  1. https://github.com/otherlab/openmesh
  2. https://github.com/alecjacobson/nested_cages
  3. http://homepage.tudelft.nl/h05k3/Projects/MeshThickeningProj.htm

Si possono anche trovare pubblicazioni corrispondenti per queste librerie per comprendere gli algoritmi in modo più dettagliato.

L'ultimo ha il minor numero di dipendenze, è autonomo e può leggere in file .obj.

I migliori auguri, Stephan


0

Uso una geometria semplice: vettori e / o trigonometria

  1. Ad ogni angolo trova il vettore medio e l'angolo medio. Il vettore medio è la media aritmetica dei due vettori unitari definiti dai bordi dell'angolo. L'angolo medio è la metà dell'angolo definito dai bordi.

  2. Se è necessario espandere (o contrarre) il poligono per la quantità di d di ciascun bordo; dovresti uscire (in) dalla quantità d / sin (midAngle) per ottenere il nuovo punto d'angolo.

  3. Ripeti questo per tutti gli angoli

*** Stai attento alla tua direzione. Esegui CounterClockWise Test usando i tre punti che definiscono l'angolo; per scoprire da che parte è o dentro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.