Als Voronoi-Diagramm, auch Thiessen-Polygone oder Dirichlet-Zerlegung, wird eine Zerlegung des Raumes in Regionen bezeichnet, die durch eine vorgegebene Menge an Punkten des Raumes, hier als Zentren bezeichnet, bestimmt werden. Jede Region wird durch genau ein Zentrum bestimmt und umfasst alle Punkte des Raumes, die in Bezug zur euklidischen Metrik näher an dem Zentrum der Region liegen als an jedem anderen Zentrum. Derartige Regionen werden auch als Voronoi-Regionen bezeichnet. Aus allen Punkten, die mehr als ein nächstgelegenes Zentrum besitzen und somit die Grenzen der Regionen bilden, entsteht das Voronoi-Diagramm.
Voronoi-Diagramme werden in verschiedensten wissenschaftlichen Bereichen wie der Biologie, Chemie, Meteorologie, Kristallographie, Architektur und anderen wissenschaftlichen Disziplinen wie der Algorithmischen Geometrie und der Materialwissenschaft verwendet. Ein Spezialfall des Voronoi-Diagramms im dreidimensionalen Raum ist die Wigner-Seitz-Zelle. Obwohl 1644 schon durch Descartes in seinem Buch Principia Philosophiae erwähnt, erfuhren sie erstmals durch Dirichlet und Voronoi eine genauere mathematische Analyse.[1] Voronoi-Diagramme können durchaus auch als Zerlegung hochdimensionaler Räume verwendet werden. In der Literatur ist die Definition meist auf den zweidimensionalen reellen Raum beschränkt.
Entdeckungen
Voronoi-Diagramme wurden mehrmals wiederentdeckt.[2]
Die Thiessen-Polygone oder das Voronoi-Diagramm bestehen aus dem gesamten Raum abzüglich der Voronoi-Regionen, welche in Bezug auf den euklidischen Abstand aus allen Punkten des Raumes entstehen, die näher am korrespondierenden Zentrum liegen als an allen anderen Zentren. Diese können im Zweidimensionalen als Schnitt mehrerer offener Halbebenen betrachtet werden, welche wiederum durch einen Bisektor zwischen je zwei Punkten der Zentren begrenzt werden.
Formal ist eine Voronoi-Region des Punktes , wobei eine vorgegebene Menge an Punkten des ist, gegeben durch
,
wobei als offene Halbebene definiert ist und durch
gegeben ist. Sind nun alle Voronoi-Regionen durch
gegeben, erhält man das Voronoi-Diagramm durch
Informell bedeutet das, dass genau die Grenzen der Regionen, welche selbst nicht zu diesen dazu gehören, das Diagramm bzw. die Polygone bilden. Die resultierenden Polygone können in Voronoi-Kanten (Kanten des Polygons) und Voronoi-Knoten (Ecken des Polygons) eingeteilt werden. Alle Punkte auf den Voronoi-Kanten haben dabei zu den Punkten , deren Voronoi-Region neben der Kante liegen, den gleichen Abstand.
Dualität
Das Voronoi-Diagramm verhält sich dual zur Delaunay-Triangulierung und wird zur Konstruktion einer entsprechend triangulierten Oberfläche verwendet.
Um die Delaunay-Triangulierung zu berechnen, wird der entsprechende duale Graph zum Voronoi-Diagramm gebildet. Dies geschieht, indem die Zentren der Polygone derart miteinander verbunden werden, so dass zu jeder Voronoi-Kante eine orthogonale Linie eingezeichnet wird, die die entsprechenden zwei Zentren miteinander verbindet (siehe Abbildung).
Polygon-Methode
Thiessen-Polygone werden unter anderem bei der kartographischen Darstellung von Messwerten eingesetzt. Die Polygon-Methode ist ein nichtstatistisches (d. h. vergleichsweise einfaches) Interpolationsverfahren der Geostatistik zur einfachen Darstellung der räumlichen Verteilung georeferenzierter Messdaten.
Als Grundannahme gilt, dass die Ähnlichkeit des unbekannten Wertes eines Punktes in der Fläche zum bekannten Messwert mit der Entfernung von diesem abnimmt, die Daten also umso unähnlicher sind, je weiter sie auseinanderliegen. Dieser Zusammenhang wird bei der Polygon-Methode dadurch zum Ausdruck gebracht, dass jeder Messwert für ein ihn umgebendes Thiessen-Polygon homogenisiert wird, also alle Schätzwerte innerhalb dieses Polygons identisch zum jeweiligen Messwert sind.
Das Verfahren bildet insofern eine schlechte Näherung an die beobachtbare Realität, da an den Polygongrenzen scharfe Wertesprünge auftreten. Fließende Übergänge zwischen zwei Messwerten können mit dieser Methode also nicht dargestellt werden. Durch diesen Umstand ist die Polygon-Methode wiederum gut geeignet zur flächigen Verteilung von diskreten Daten, etwa binären (z. B. Messwert: „Schneefall: ja/nein“).
Metriken
Angenommen, es soll die Anzahl der Kunden eines bestimmten Ladens in einer Stadt geschätzt werden. Bei ansonsten gleichen Bedingungen (Preis, Produkte, Qualität usw.) ist davon auszugehen, dass Kunden in den nächstgelegenen Laden, also den Laden mit dem kleinsten Abstand, gehen. In diesem Fall kann die Voronoi-Zelle eines bestimmten Ladens verwendet werden, um eine grobe Schätzung der Anzahl potenzieller Kunden abzugeben, die diesen Laden besuchen. Die Läden der Stadt werden als Punkte des Voronoi-Diagramms modelliert. Für die meisten Städte kann der Abstand zwischen Punkten als euklidischer Abstand gemessen werden:
Die entsprechenden Voronoi-Diagramme sehen für verschiedene Metriken unterschiedlich aus.
Algorithmus
Die Berechnung eines Voronoi-Diagramms mithilfe der Delaunay-Triangulation ist für beliebige Dimensionen möglich. Im Folgenden wird ein Algorithmus für den zweidimensionalen Fall beschrieben, der sich analog auf höhere Dimensionen erweitern lässt. Die Berechnung erfolgt in drei Schritten. Zunächst werden alle gegebenen Punkte in der -Ebene in eine zusätzliche Dimension auf einen (Hyper-)Paraboloid mit den dreidimensionalenKoordinaten projiziert. Von den so gewonnenen Punkten wird die konvexe Hülle berechnet. In einem zweiten Schritt werden alle Flächen der konvexen Hülle, deren Flächennormale nach unten zeigt, wieder auf die ursprüngliche Ebene zurückprojiziert. Die so gewonnenen Regionen sind die Dreiecke der Delaunay-Triangulation. In einem letzten Schritt werden die Umkreismittelpunkte aller Dreiecke zwischen angrenzenden Dreiecken verbunden, was die gesuchten Kanten der Voronoi-Polygone ergibt.
Bezeichnungen: Punkte , Delaunay-Triangulation , Konvexe Hülle , Voronoi-Diagramm .[6]
1: Initialisiere leere Mengen P', DT(P) und V(P)
2:
3: //Berechnung der konvexen Hülle
4: Für alle p = (px, py) P:
5: Füge p' = (px, py, px2 + py2) zu P' hinzu
6: Berechne KH(P') //Mit geeignetem Algorithmus
7:
8: //Berechnung der Delaunay-Triangulation
9: Für alle Seiten s KH(P'):
10: Falls Normalenvektor von s nach unten zeigt:
11: Für alle Kanten k von s:
12: Setze z-Wert von jedem Knoten k auf 0
13: Erstelle neue Kante k' = k
14: Füge k' zu DT(P) hinzu
15:
16: //Berechnung der Voronoi-Zellen
17: Für alle Dreiecke d in DT(P):
18: Für alle an d angrenzenden Dreiecke d':
19: Erstelle Kante m durch Verbindung der Umkreismittelpunkte von d und d'
20: Füge m zu V(P) hinzu
Algorithmus von Fortune
Der Algorithmus von Fortune ist ein Sweep-Line-Algorithmus, der schrittweise ein Voronoi-Diagramm erzeugt, indem eine sogenannte Sweep Line in einer Richtung durch die Ebene geschoben wird. Die Sweep Line ist eine Gerade, von der man annehmen kann, dass sie vertikal ist und sich von links nach rechts in der Ebene bewegt. Zu jedem Zeitpunkt werden die eingegebenen Punkte links der Sweep Line in das Voronoi-Diagramm eingebaut, während die Punkte rechts der Sweep Line noch nicht berücksichtigt wurden. Außerdem verwendet der Algorithmus von Fortune eine sogenannte Beach Line. Die Beach Line ist keine gerade Linie, sondern eine komplizierte stückweise Kurve links der Sweep Line, die aus Parabelbögen besteht. Die Beach Line teilt den Abschnitt der Ebene, in dem das Voronoi-Diagramm bekannt ist, vom Rest der Ebene, unabhängig von den Punkten rechts der Sweep Linie.
Das folgende Programm in der ProgrammierspracheC# zeigt eine Implementierung des Algorithmus von Fortune. Das Voronoi-Diagramm wird auf dem Hauptfenster gezeichnet. Das Programm verwendet mehrere Klassen. Die Methoden für den eigentlichen Algorithmus werden in der Klasse Fortune deklariert.[8]
Code-Schnipsel
usingSystem;usingSystem.Collections.Generic;usingSystem.Drawing;usingSystem.Windows.Forms;// Diese Klasse implementiert eine Vergleichsmethode für das Sortieren der PunkteclassPointComparer:Comparer<PointF>{// Vergleichsmethode, die die Punkte von links nach rechts und bei Gleichheit von oben nach unten sortiertpublicoverrideintCompare(PointFpoint1,PointFpoint2){intresult=point1.X.CompareTo(point2.X);if(result==0){returnpoint1.Y.CompareTo(point2.Y);}returnresult;}}// Diese Klasse implementiert eine Vergleichsmethode für das Sortieren der CircleEventsclassEventComparer:Comparer<CircleEvent>{// Vergleichsmethode, die die CircleEventspublicoverrideintCompare(CircleEventevent1,CircleEventevent2){returnevent1.x.CompareTo(event2.x);}}// Klasse für die CircleEventsclassCircleEvent{publicdoublex;// x-Koordinate der Sweep LinepublicPointFpoint;// Das Zentrum, das der Brennpunkt der Parabel istpublicParabolaArcarc;// ParabelbogenpublicboolisValid;// Gibt an, ob das CircleEvent aktuell ist// Konstruktor für die Initialisierung der AttributepublicCircleEvent(double_x,PointF_point,ParabolaArc_arc){x=_x;point=_point;arc=_arc;isValid=true;}};// Klasse für die ParabelbögenclassParabolaArc{publicPointFpoint;// Das Zentrum, das der Brennpunkt der Parabel istpublicParabolaArcpreviousArc,nextArc;// Benachbarte ParabelbögenpublicCircleEventcircleEvent;// Zugeordnetes CircleEventpublicVoronoiEdgeedge1,edge2;// Benachbarte Voronoi-Kanten// Konstruktor für die Initialisierung der AttributepublicParabolaArc(PointF_point,ParabolaArcarc1,ParabolaArcarc2){{point=_point;previousArc=arc1;nextArc=arc2;circleEvent=null;edge1=null;edge2=null;}}}// Klasse für die Voronoi-KantenclassVoronoiEdge{publicPointFpoint1,point2;// Endpunkte der KantepublicboolisFinished;// Gibt an, ob die Kante fertiggestellt wurdepublicstaticList<VoronoiEdge>edges=newList<VoronoiEdge>();// Liste der Kanten// Konstruktor für die Initialisierung der AttributepublicVoronoiEdge(PointFpoint){point1=point;// Setzt den ersten Endpunktpoint2.X=0;point2.Y=0;isFinished=false;edges.Add(this);// Fügt die Kante der Liste hinzu}// Diese Methode stellt die Kante fertigpublicvoidFinish(PointFpoint){if(!isFinished){point2=point;// Setzt den zweiten EndpunktisFinished=true;}}}// Klasse, die die Methoden für den Algorithmus von Fortune deklariertclassFortune{publicParabolaArcroot=null;publicList<PointF>points=newList<PointF>();// Liste der PunktepublicList<CircleEvent>circleEvents=newList<CircleEvent>();// Liste der CircleEvents// Verarbeitet den verbleibenden Punkt (rechts der Sweep Line), der ganz links liegtpublicvoidProcessPoint(doublex){PointFpoint=points[0];points.RemoveAt(0);// Entfernt den ersten Punkt aus der ListeAddNewArc(point,x);// Fügt der Beach Line einen Parabelbogen hinzu}// Diese Methode verarbeitet das CircleEvent mit der kleinsten x-KoordinatepublicvoidProcessCircleEvent(){CircleEventcircleEvent=circleEvents[0];circleEvents.RemoveAt(0);// Entfernt das erste CircleEvent aus der Listeif(circleEvent.isValid)// Wenn das CircleEvent aktuell ist{VoronoiEdgeedge=newVoronoiEdge(circleEvent.point);// Erzeugt eine neue Kante// Entfernt den zugehörigen ParabelbogenParabolaArcarc=circleEvent.arc;if(arc.previousArc!=null){arc.previousArc.nextArc=arc.nextArc;arc.previousArc.edge2=edge;}if(arc.nextArc!=null){arc.nextArc.previousArc=arc.previousArc;arc.nextArc.edge1=edge;}// Stellt die benachbarten Kanten des Parabelbogens fertigif(arc.edge1!=null){arc.edge1.Finish(circleEvent.point);}if(arc.edge2!=null){arc.edge2.Finish(circleEvent.point);}// Prüft die CircleEvents auf beiden Seiten des Parabelbogensif(arc.previousArc!=null){CheckCircleEvent(arc.previousArc,circleEvent.x);}if(arc.nextArc!=null){CheckCircleEvent(arc.nextArc,circleEvent.x);}}}// Diese Methode fügt einen neuen Parabelbogen mit dem gegebenen Brennpunkt hinzupublicvoidAddNewArc(PointFpoint,doublex){if(root==null){root=newParabolaArc(point,null,null);return;}// Bestimmt den aktuellen Parabelbogen mit der y-Koordinate des Punkts pointParabolaArcarc;for(arc=root;arc!=null;arc=arc.nextArc)// Diese for-Schleife durchläuft die Parabelbögen{PointFintersection1=newPointF(0,0),intersection2=newPointF(0,0);if(GetIntersection(point,arc,refintersection1))// Wenn die neue Parabel den Parabelbogen schneidet{// Dupliziert gegebenenfalls den Parabelbogenif(arc.nextArc!=null&&!GetIntersection(point,arc.nextArc,refintersection2)){arc.nextArc.previousArc=newParabolaArc(arc.point,arc,arc.nextArc);arc.nextArc=arc.nextArc.previousArc;}else{arc.nextArc=newParabolaArc(arc.point,arc,null);}arc.nextArc.edge2=arc.edge2;// Fügt einen neuen Parabelbogen zwischen den Parabelbögen arc und arc.nextArc ein.arc.nextArc.previousArc=newParabolaArc(point,arc,arc.nextArc);arc.nextArc=arc.nextArc.previousArc;arc=arc.nextArc;// Verbindet 2 neue Kanten mit den Endpunkten des Parabelbogensarc.previousArc.edge2=arc.edge1=newVoronoiEdge(intersection1);arc.nextArc.edge1=arc.edge2=newVoronoiEdge(intersection1);// Prüft die benachbarten CircleEvents des ParabelbogensCheckCircleEvent(arc,point.X);CheckCircleEvent(arc.previousArc,point.X);CheckCircleEvent(arc.nextArc,point.X);return;}}// Spezialfall: Wenn der Parabelbogen keinen anderen schneidet, wird er der doppelt verketteten Liste hinzugefügtfor(arc=root;arc.nextArc!=null;arc=arc.nextArc);// Bestimmt den letzten Parabelbogenarc.nextArc=newParabolaArc(point,arc,null);// Fügt eine Kante zwischen den Parabelbögen einPointFstart=newPointF(0,0);start.X=(float)x;start.Y=(arc.nextArc.point.Y+arc.point.Y)/2;arc.edge2=arc.nextArc.edge1=newVoronoiEdge(start);}// Diese Methode erzeugt wenn nötig ein neues CircleEvent für den gegebenen ParabelbogenpublicvoidCheckCircleEvent(ParabolaArcarc,double_x){// Setzt das bisherige CircleEvent auf nicht aktuellif(arc.circleEvent!=null&&arc.circleEvent.x!=_x){arc.circleEvent.isValid=false;}arc.circleEvent=null;if(arc.previousArc==null||arc.nextArc==null){return;}doublex=0;PointFpoint=newPointF(0,0);if(GetRightmostCirclePoint(arc.previousArc.point,arc.point,arc.nextArc.point,refx,refpoint)&&x>_x){arc.circleEvent=newCircleEvent(x,point,arc);// Erzeugt ein neues CircleEventcircleEvents.Add(arc.circleEvent);circleEvents.Sort(newEventComparer());// Sortiert die Liste}}// Diese Methode bestimmt die x-Koordinate des Kreises durch die 3 Punkte, der ganz rechts liegt und prüft, ob die 3 Punkte auf einer Geraden liegenpublicboolGetRightmostCirclePoint(PointFpoint1,PointFpoint2,PointFpoint3,refdoublex,refPointFpoint){// Prüft, dass die 3 Punkt im Uhrzeigersinn orientiert sindif((point2.X-point1.X)*(point3.Y-point1.Y)>(point3.X-point1.X)*(point2.Y-point1.Y)){returnfalse;}doublex1=point2.X-point1.X;doubley1=point2.Y-point1.Y;doublea=2*(x1*(point3.Y-point2.Y)-y1*(point3.X-point2.X));if(a==0){returnfalse;// Wenn die 3 Punkte auf einer Geraden liegen, wird false zurückgegeben}doublex2=point3.X-point1.X;doubley2=point3.Y-point1.Y;doublea1=x1*(point1.X+point2.X)+y1*(point1.Y+point2.Y);doublea2=x2*(point1.X+point3.X)+y2*(point1.Y+point3.Y);// Berechnet den Mittelpunkt des Kreises durch die 3 Punktepoint.X=(float)((y2*a1-y1*a2)/a);point.Y=(float)((x1*a2-x2*a1)/a);x=point.X+Math.Sqrt(Math.Pow(point1.X-point.X,2)+Math.Pow(point1.Y-point.Y,2));// x-Koordinate des Mittelpunkts plus Radiusreturntrue;}// Diese Methode bestimmt gegebenenfalls den Schnittpunkt zwischen der Parabel mit dem gegebenen Brennpunkt und dem Parabelbogen und prüft, ob er existiertpublicboolGetIntersection(PointFpoint,ParabolaArcarc,refPointFintersection){if(arc.point.X==point.X){returnfalse;// Wenn die Brennpunkte übereinstimmen, wird false zurückgegeben}doubley1=0,y2=0;if(arc.previousArc!=null){y1=GetParabolasIntersection(arc.previousArc.point,arc.point,point.X).Y;// Berechnet die y-Koordinate des Schnittpunkts zwischen dem aktuellen und dem vorherigen Parabelbogen}if(arc.nextArc!=null){y2=GetParabolasIntersection(arc.point,arc.nextArc.point,point.X).Y;// Berechnet die y-Koordinate des Schnittpunkts zwischen dem aktuellen und dem nächsten Parabelbogen}// Berechnet die Koordinaten des Schnittpunkts, falls vorhandenif((arc.previousArc==null||y1<=point.Y)&&(arc.nextArc==null||point.Y<=y2)){intersection.Y=point.Y;intersection.X=(arc.point.X*arc.point.X+(arc.point.Y-intersection.Y)*(arc.point.Y-intersection.Y)-point.X*point.X)/(2*arc.point.X-2*point.X);returntrue;}returnfalse;}// Diese Methode bestimmt den Schnittpunkt zwischen den Parabeln mit den gegebenen Brennpunkten für die Sweep Line mit der gegebenen x-KoordinatepublicPointFGetParabolasIntersection(PointFpoint1,PointFpoint2,doublex){PointFintersection=newPointF(0,0),point=point1;// Spezialfälleif(point1.X==point2.X){intersection.Y=(point1.Y+point2.Y)/2;}elseif(point2.X==x){intersection.Y=point2.Y;}elseif(point1.X==x){intersection.Y=point1.Y;point=point2;}else// Standardfall{// Verwendet die Lösungsformel für quadratische Gleichungen, um die y-Koordinate des Schnittpunkts zu berechnendoublex1=2*(point1.X-x);doublex2=2*(point2.X-x);doublea=1/x1-1/x2;doubleb=-2*(point1.Y/x1-point2.Y/x2);doublec=(point1.Y*point1.Y+point1.X*point1.X-x*x)/x1-(point2.Y*point2.Y+point2.X*point2.X-x*x)/x2;intersection.Y=(float)((-b-Math.Sqrt(b*b-4*a*c))/(2*a));}// Setzt die y-Koordinate in die Parabelgleichung ein, um die x-Koordinate zu berechnenintersection.X=(float)((point.X*point.X+(point.Y-intersection.Y)*(point.Y-intersection.Y)-x*x)/(2*point.X-2*x));returnintersection;}// Diese Methode stellt die benachbarten Kanten der Parabelbögen fertigpublicvoidFinishEdges(doublex1,doubley1,doublex2,doubley2){// Verschiebt die Sweep Line, sodass keine Parabel die Zeichenfläche schneiden kanndoublex=x2+(x2-x1)+(y2-y1);// Verlängert jede benachbarte Kante bis zum Schnittpunkt der neuen Parabelnfor(ParabolaArcarc=root;arc.nextArc!=null;arc=arc.nextArc){if(arc.edge2!=null){arc.edge2.Finish(GetParabolasIntersection(arc.point,arc.nextArc.point,2*x));}}}}// Klasse für das HauptfensterpublicpartialclassMainForm:Form{privateGraphicsgraphics;privateList<PointF>points=newList<PointF>();// Liste der Punkteprivatedoublex1,y1,x2,y2;publicMainForm(){x1=0;y1=0;x2=800;y2=800;// Setzt die Koordinaten der Eckpunkte der quadratischen ZeichenflächeRandomrandom=newRandom();// Initialisiert den ZufallsgeneratorintnumberOfPoints=10;for(inti=0;i<numberOfPoints;i++)// Diese for-Schleife erzeugt 10 zufällige Punkte innerhalb der quadratischen Zeichenfläche{PointFpoint=newPointF();point.X=(float)(random.NextDouble()*(x2-x1)+x1);point.Y=(float)(random.NextDouble()*(y2-y1)+y1);points.Add(point);// Fügt den Punkt der Liste hinzu}points.Sort(newPointComparer());// Sortiert die PunkteFortunefortune=newFortune();// Erzeugt ein Objekt der Klasse Fortunefortune.points.AddRange(points);// Fügt die Punkte der Liste hinzu// Diese for-Schleife verarbeitet bei jedem Durchlauf das Element mit der kleinsten x-Koordinatewhile(fortune.points.Count!=0)// Solange die Liste der Punkte nicht leer ist{if(fortune.circleEvents.Count!=0&&fortune.circleEvents[0].x<=fortune.points[0].X){fortune.ProcessCircleEvent();// Aufruf der Methode, verarbeitet das CircleEvent}else{fortune.ProcessPoint(x1);// Aufruf der Methode, verarbeitet den Punkt}}// Nachdem alle Punkte verarbeitet wurden, werden die verbleibenden circleEvents verarbeitet.while(fortune.circleEvents.Count!=0)// Solange die Liste der CircleEvents nicht leer ist{fortune.ProcessCircleEvent();}fortune.FinishEdges(x1,y1,x2,y2);// Aufruf der Methode, stellt die benachbarten Kanten der Parabelbögen fertigInitializeComponent();Text="Voronoi-Diagramm";Width=800;Height=800;graphics=CreateGraphics();// Erzeugt ein Grafikobjekt für das Zeichnen auf dem Hauptfenster.Paint+=OnPaint;// Verknüpft die Ereignisbehandlungsmethode mit dem Paint Ereignis des Hauptfensters.}// Diese Methode wird aufgerufen, wenn das Hauptfenster gezeichnet wird.privatevoidOnPaint(objectsender,PaintEventArgse){for(inti=0;i<points.Count;i++){graphics.FillRectangle(newSolidBrush(Color.FromArgb(0,0,0)),points[i].X-1,points[i].Y-1,2,2);// Zeichnet die Voronoi-Zentrums}for(inti=0;i<VoronoiEdge.edges.Count;i++){graphics.DrawLine(newPen(Color.FromArgb(0,0,255)),VoronoiEdge.edges[i].point1,VoronoiEdge.edges[i].point2);// Zeichnet die Voronoi-Kanten}}}
Programmierung
Das folgende Programm in der ProgrammierspracheC++ erzeugt ein zufälliges Voronoi-Diagramm, indem alle Pixel einzeln gesetzt werden, zeigt es auf dem Konsolenfenster an und speichert es in einer Bilddatei.[9]
#include<windows.h>#include<vector>usingnamespacestd;classVoronoi{private:vector<Point>points_;vector<DWORD>colors_;MyBitmap*bitmap_;public:// Diese Funktion erzeugt ein Bitmap der Klasse MyBitmap, das ein Voronoi-Diagramm enthältvoidCreate(MyBitmap*bitmap,intcount){bitmap_=bitmap;for(inti=0;i<count;i++)// Diese for-Schleife erzeugt zufällige Punkte (Zentren){points_.push_back({rand()%(bitmap_->width()-20)+10,rand()%(bitmap_->height()-20)+10});}for(size_ti=0;i<points_.size();i++)// Diese for-Schleife erzeugt zufällige Farben für die Voronoi-Regionen{colors_.push_back(RGB(rand()%200+50,rand()%200+55,rand()%200+50));}intwidth=bitmap_->width();intheight=bitmap_->height();// Die folgenden zwei verschachtelten for-Schleifen durchlaufen alle Pixel des Bitmapsfor(inti=0;i<width;i++){for(intj=0;j<height;j++){intindex=-1;intminimumDistance=INT_MAX;// Die folgende for-Schleife durchläuft alle Zentren des Voronoi-Diagramms und bestimmt das Zentrum, das den kleinsten Abstand zum aktuellen Pixel mit den Koordinaten (i, j) hatfor(size_tk=0;k<points_.size();k++){constPoint&point=points_[k];intx=i-point.x;inty=j-point.y;intdistance=x*x+y*y;if(distance<minimumDistance)// Wenn der aktuelle Abstand kleiner als der bisher kleinste Abstand ist{minimumDistance=distance;// Aktualisiert den kleinsten Abstandindex=k;// Aktualisiert den Index für das Zentrum}}SetPixel(bitmap_->hdc(),i,j,colors_[index]);// Setzt das aktuelle Pixel auf die Farbe mit dem Index}}// Die folgende for-Schleife durchläuft alle Zentren des Voronoi-Diagramms und zeichnet sie in das Bitmapfor(Pointpoint:points_){for(inti=-1;i<=1;i++){for(intj=-1;j<=1;j++){SetPixel(bitmap_->hdc(),point.x+i,point.y+j,0);}}}}};// Hauptfunktion die das Programm ausführtintmain(){ShowWindow(GetConsoleWindow(),SW_MAXIMIZE);MyBitmapbitmap;bitmap.Create(512,512);// Erzeugt ein Bitmap mit der angegebenen Breite und HöheVoronoivoronoi;voronoi.Create(&bitmap,50);// Aufruf der Funktion, erzeugt das Voronoi-DiagrammBitBlt(GetDC(GetConsoleWindow()),20,20,512,512,bitmap.hdc(),0,0,SRCCOPY);// Zeigt das Bitmap auf dem Konsolenfenster anbitmap.SaveBitmap("VoronoiDiagram.png");// Speichert das erzeugte Bitmap als Bilddatei mit dem angegebenen Dateinamen}
Das Programm verwendet die KlasseMyBitmap, die wie folgt deklariert ist:
Voronoi-Diagramme dienen zum Erstellen von Karten der Repräsentativität von Punkten in der Meteorologie, z. B. von Niederschlagsgebieten. Voronoi-Diagramme werden auch in der Erforschung sozioökonomischer Phänomene eingesetzt. Polygone in ihrer klassischen Form wurden verwendet, um den Transportzugang von Eisenbahnstationen und anderen Transporthaltestellen, Schulen, Krankenhäusern zu ermitteln. Die Polygon-Methode wurde verwendet, um räumliche Muster der Vertriebs- und Zugänglichkeit von Läden zu analysieren, um den Zugang zu Grünflächen in Großstädten und die Beziehung zwischen der Gemeinschaft und dem nächstgelegenen Dienstanbieter zu ermitteln.
In der Forschung wurden Voronoi-Polygone verwendet, um die Zone des Transittransports zu bestimmen, und zur Abgrenzung von Meereszonen. Das sogenannte Verfahren der gewichteten Voronoi-Diagramme ist eine Modifikation von Voronoi-Diagrammen. Es besteht in der Vergrößerung oder Verkleinerung der Voronoi-Zellen um einen Punkt in Abhängigkeit von dem Gewichtungsparameter, das einem solchen Punkt zugeordnet ist. Das Verfahren wird in dieser Form verwendet, um den Algorithmus zur Berechnung konvexer Entfernungen in Verkehrsnetzen zu erhalten.[10]
In der Analyse von Fußballspielen und taktischem Verhalten der Spieler werden Voronoi-Diagramme verwendet, um die Raumkontrolle beider Mannschaften zu visualisieren: „Die einzelnen Linien trennen die Räume ab, welche die Verteidiger und welche die Angreifer zuerst erreichen können. So zeigt sich, welche Räume die angreifende Mannschaft kontrolliert und welche Räume die verteidigende Mannschaft kontrolliert.“[13] Eine gute Raumkontrolle der verteidigenden Mannschaft zeichnet sich dadurch aus, dass sie der angreifenden Mannschaft keine Regionen in Nähe des eigenen Tores ermöglicht, in denen ein Angreifer vor einem Verteidiger in Ballbesitz kommen und somit eine Torchance kreieren könnte.[14] Die reine Betrachtung der Abstände führt dabei jedoch nur zu einer ersten Näherung, da in der Praxis des Spiels auch Aspekte wie Reaktionsgeschwindigkeit, aktuelle Laufrichtung, Geschicklichkeit bei der Balleroberung etc. in Betracht gezogen werden müssten. Das Gleiche gilt für den Deckungsschatten eines Spielers, in den der Ball in der Regel nicht direkt gespielt werden kann.[13]
Literatur
Atsuyuki Okabe, Barry Boots, Kokichi Sugihara, Sung Nok Chiu (Hrsg.): Spatial Tessallations: Concepts and Applications of Voronoi Diagrams. 2. Auflage. John Wiley & Sons, 2000, ISBN 978-0-471-98635-5.
Franz Aurenhammer, Rolf Klein, Der-Tsai Lee: Voronoi Diagrams and Delaunay Triangulations. World Scientific Publishing Company, Singapore 2013, ISBN 9814447633.
↑G. S. Brown: Point density in stems per acre (= N.Z. For. Res. Notes. Band38). 1965, S.11.
↑R. Mead: A Relationship between Individual Plant-spacing and Yield. In: Annals of Botany. Band30, Nr.2, April 1966, S.301–309, doi:10.1093/oxfordjournals.aob.a084076.
↑Tonio Hölscher, Susanne Krömker und Hubert Mara: Der Kopf Sabouroff in Berlin: Zwischen archäologischer Beobachtung und geometrischer Vermessung. In: Benaki-Museum (Hrsg.): Gedenkschrift für Georgios Despinis. Athen, Griechenland 2020.
↑ abTobias Escher: Der Schlüssel zum Spiel: Wie moderner Fußball funktioniert. 2. Auflage. Rowohlt Verlag, Hamburg 2020, ISBN 978-3-499-00198-7, S.29–31.