cgal/Nef_2/noweb/Svens_point_location.lw

2533 lines
103 KiB
Plaintext
Raw Blame History

% ----------------------------------------------------------------------------
% sweep.lw: Punkt-Lokalisierung in der Ebene
% $Id$
% ----------------------------------------------------------------------------
% Seitenumbruch bei PointLocator member functions !!!
% style: Typen, Klassen und member-Funktionen kursiv
% ToDo: s. auch ~/diplom/todo
% - GenericXStructure:
% wahrscheinlich ist es deutlich effizienter, ein C++-Array zu verwenden
% und binary_locate selbst zu implementieren, denn dann kann bei der
% Compare-Fkt. inlining verwendet werden
\documentclass[a4paper]{article}
\usepackage{amsmath,amsthm,amssymb,Lweb,version}
\usepackage[german]{babel}
\begin{document}
\newcommand{\chapter}{\section}
\newcommand{\card}[1]{\Labs{#1}}
\newcommand{\Set}[2]{ \{ #1 \quad : \quad #2 \} }
\newcommand{\RR}{ R }
\newcommand{\degree}{ ^\circ }
\newcommand{\sign}{ \mathrm{sign} }
\newcommand{\EGplpl}{ \texttt{eg++} }
\newcommand{\rem}[1]{}
\newcommand{\SET}[1]{ \{ #1 \} }
\newcommand{\Seg}[2]{ \ensuremath{\overline{#1#2}} }
\newcommand{\abs}[1]{ \Labs{#1} }
\theoremstyle{thmstyle}\newtheorem{lemma}{Lemma}[section]
\theoremstyle{thmstyle}\newtheorem{beweis}{Beweis}[section]
\theoremstyle{thmstyle}\newtheorem{bemerkung}{Bemerkung}[section]
\newcommand{\Figger}[3]{ NO FIGURES INCLUDED \\ }
\newcommand{\figref}[1]{ NOREF }
\newcommand{\epsfig}[1]{ NO FIGURES INCLUDED \\ }
\newcommand{\figlabel}[1]{}
\chapter{Punkt-Lokalisierung in der Ebene} \label{PLoc}
In diesem Kapitel wird eine Anwendung f"ur die Datenstrukturen vorgestellt,
die in den letzten Kapiteln entwickelt wurden.
Es handelt sich um die Lokalisierung eines Punktes in der Ebene.
Das Problem l"a"st sich folgenderma"sen beschreiben:
Gegeben sind ein Graph |G=(V,E)| und zwei Abbildungen |P| und |K|.
Jedem Knoten |v| von |G| ist ein Punkt |P(v)| in der Ebene zugeordnet.
Mit jeder Kante $e = \{v,w\}$ bzw. $e=(v,w)$ ist eine Kurve |K(e)| assoziiert,
die zwischen |P(v)| und |P(w)| verl"auft. Sie ist stetig und beidseitig
offen, sowie entweder x-monoton (jede Parallele zur Y-Achse schneidet
die Kurve in h"ochstens einem Punkt) oder ein vertikales Geradensegment.
Die Abbildungen |P| und |K| stellen eine planare Einbettung von |G| dar,
d.h. alle Kurven sind disjunkt, |P| ist injektiv und keiner der Punkte in
|P(V)| liegt auf einer Kurve.
(Informal bedeutet dies, da"s sich der Graph gem"a"s |P| und |K| ohne
"Uberschneidungen zeichnen l"a"st.)
Es mu"s sich jedoch nicht um eine ordnungserhaltende Einbettung handeln, d.h.
die Reihenfolge der Kanten in der Adjazenzliste eines Knotens ist unerheblich
(vgl. \cite{LEDA_book} Abschnitt 8.4).
~\\
Die Aufgabe besteht darin, eine Datenstruktur zu generieren, die Anfragen
der folgenden Form beantworten kann:
\begin{quote}
Bestimme zu einem Punkt |p| das Objekt von |G|, das als erstes von dem
Strahl geschnitten wird, der in |p| beginnt und vertikal nach oben bzw.
nach unten gerichtet ist.
\end{quote}
Die Antwort auf eine solche Anfrage ist ein Knoten oder eine Kante von |G|
oder aber Wert |nil|, falls der Strahl |G| nicht schneidet.
Punkt-Lokalisierung ist Grundbaustein vieler Verfahren im Bereich
Computational Geometry und hat zahlreiche Anwendungen bei geographischen
Informationssystemen und bei der Bewegungsplanung.
Das Problem war und ist Gegenstand vieler Forschungsarbeiten, und es gibt eine
Reihe von unterschiedlichen L"osungsans"atzen (vgl. \cite{Preparata_Tour}).
Die Qualit"at einer L"osung wird durch 3 Faktoren bestimmt:
die Zeit zum Aufbau der Datenstruktur, ihr Platzverbrauch und die Zeit f"ur
die Beantwortung einer Lokalisierungsanfrage.
Da der Graph in der Regel statisch ist und f"ur viele Anfragen benutzt wird,
sind die beiden letzten Faktoren die wichtigsten.
Sei $k:=\card{V}+\card{E}$ die Anzahl der Objekte des Graphen |G|. Wenn man
zur Lokalisierung eines Punktes Lagevergleiche der Form Punkt-Punkt und
Punkt-Kurve verwendet, so ergibt sich als untere Schranke f"ur die
Lokalisierungszeit $\Omega(\log k)$%
\footnote{%
Eine exaktere untere Schranke findet sich in \cite{Adamy_Seidel}.
}.
Der Ansatz, der in dieser Arbeit verfolgt wird, basiert auf einem Vorschlag
von Sarnak und Tarjan \cite{ST86}, die persistente Suchb"aume zur L"osung des
Problems einsetzten. Ihre Datenstruktur kann in Zeit $O(k \cdot \log k)$
aufgebaut werden, ben"otigt $O(k)$ Platz und kann Anfragen in Zeit $O(\log k)$
beantworten. Damit ist sie also bis auf konstante Faktoren optimal
hinsichtlich Platzverbrauch und Lokalisierungszeit.
J"ungere Arbeiten \cite{Goodrich_SODA, Adamy_Seidel} besch"aftigten sich mit
sich mit der Reduzierung des konstanten Faktors bei der Lokalisierungszeit
unter Beihaltung des linearen Platzverbrauchs; Adamy und Seidel
\cite{Adamy_Seidel} gelang es eine Datenstruktur zu entwickeln, die mit
$\log k + o(\log k)$ Vergleichen pro Lokalisierungsanfrage auskommt.
@ % === <20>berblick <20>ber die Datenstruktur =====================================
\section{"Uberblick "uber die Datenstruktur} \label{PLoc_Overview}
Um die Datenstruktur aufzubauen, wird ein Sweepline-Algorithmus verwendet.
Dieser l"a"st eine Parallele zur Y-Achse, die sogenannte Sweepline, entlang
der X-Achse von $-\infty$ bis $\infty$ wandern. Man kann sich zun"achst einmal
vorstellen, da"s f"ur jede X-Koordinate alle Objekte des Graphen gespeichert
werden, die die Sweepline an dieser Stelle schneidet, und zwar geordnet nach
den Y-Koordinaten der Schnittpunkte.
\Figger{sweep_idee}
{Sweep "uber einen Graphen mit 7 Knoten und 4 Kanten}
{sweep_idee}
Wie man leicht sieht, ver"andert sich die Menge der geschnittenen Objekte
im Laufe eines Sweeps nur selten, n"amlich genau dann, wenn die Sweepline
einen oder mehrere Knoten des Graphen "uberstreicht.
Dies motiviert die Definition der Menge |S| der Haltestellen eines Sweeps "uber
einen Graphen |G|: $S = \Set{x \in \RR}{\exists y \in \RR: (x,y) \in |P(v)| }$.
Hat ein Sweep die Haltestellen%
\footnote{ %
Es gibt Sweepline-Algorithmen (vgl. \cite{LEDA_book} Abschnitt 10.7),
welche die Menge |P(V)|, also die Punkte selbst, als Haltestellen nehmen und
diese in lexikographischer Reihenfolge bearbeiten; die Sweepline ist dann
keine Gerade mehr, sondern eine Stufe mit einem infinitesimal kleinen Absatz.
Dies hat jedoch den Nachteil, da"s man bei einer Punkt-Lokalisierung nur
nach unten gerichtete Strahlen verfolgen kann.}
$x_0 < x_1 < \dots < x_n$, dann mu"s man den
Zustand der Sweepline an diesen Haltestellen und in den Intervallen %[
$x_i^+ := ]x_i,x_{i+1}[$ ($i=0, \dots, n-1$) speichern
(vgl. Abbildung \figref{sweep_idee}). Damit l"a"st sich zu jeder X-Koordinate
die Sweepline an dieser Stelle rekonstruieren.
In den Intervallen $]{-\infty},x_0[$ und $]x_n,{+\infty}[ =: x_n^+$
schneidet die Sweepline kein Objekt von |G|. %]
Um die Berechnung der Sweeplines f"ur die Haltestelle $x_i$ und das
Intervall $x_i^+$ einfacher beschreiben zu k"onnen, werden zwei Begriffe
definiert.
Sei |e| eine Kante von |G| mit den inzidenten Knoten |v| und |w|, so da"s
$P(v) \neq P(w)$.
Dann \emph{startet} |e| in |v| und \emph{endet} in |w|, falls
$P(v) \prec_{lex} P(w)$.
(Hierbei bezeichnet $\prec_{lex}$ die lexikographische Ordnung auf den
Punkten nach X- und Y-Koordinate.)
Die Begriffe "`starten"' und "`enden"' beziehen sich also nur auf die Lage
der inzidenten Knoten einer Kante, nicht aber auf eine eventuelle Richtung
der Kante im Graphen.
Die "Anderungen der Sweeplines f"ur die Haltestelle $x_i$ und das Intervall
$x_i^+$ gegen"uber der Sweepline des vorhergehenden Intervalles kann man
wie folgt finden:
\begin{itemize}
\item Berechne die Menge |NodesOnSL| aller Knoten von |G|, die die Sweepline
an dieser Haltestelle "uberstreicht.
Alle diese Knoten geh"oren zur Sweepline f"ur $x_i$, nicht aber zu der f"ur
$x_i^+$.
\item F"ur jede Kante |e|, die zu einem Knoten |v| in |NodesOnSL| inzident
ist, unterscheide 3 F"alle:
\begin{enumerate}
\item |e| ist nicht vertikal und endet in |v|, dann ist |e| weder in der
Sweepline f"ur $x_i$ noch f"ur $x_i^+$ enthalten.
\item |e| ist nicht vertikal und startet in |v|, dann ist |e| nicht in der
Sweepline f"ur $x_i$, aber in der f"ur $x_i^+$ enthalten.
\item |e| ist vertikal, dann ist |e| zwar in der Sweepline f"ur $x_i$, nicht
aber in der Sweepline f"ur $x_i^+$ enthalten.
\end{enumerate}
\end{itemize}
Eine Sweepline (oder genauer die Menge der von ihr geschnittenen
Objekte) kann als ein bin"arer Suchbaum repr"asentiert werden. Da sich
aufeinanderfolgende Sweeplines nur wenig unterscheiden, bietet es sich
an, den Suchbaum nicht f"ur jede Haltestelle und jedes Intervall neu
aufzubauen, sondern nur die "Anderungen zwischen aufeinanderfolgenden
Suchb"aumen zu speichern. Eine geeignete Datenstruktur hierf"ur ist
ein persistenter Suchbaum. Bei dem Sweep kann man die X-Achse als
Zeitachse in der Welt des persistenten Suchbaumes ansehen, wobei die
Haltestellen und die Intervalle den Versionen des Suchbaumes
entsprechen.
F"ur jede Haltestelle und jedes Intervall mu"s man also im Laufe des
Sweeps die zugeh"orige Version des persistenten Suchbaumes speichern.
Wenn man sp"ater eine Lokalisationsanfrage nach einem Punkt $p =
(x,y)$ beantworten soll, sucht man anhand der X-Koordinate die
richtige Version des persistenten Suchbaumes und kann so die Sweepline
an der Stelle $x$ rekonstruieren. Diese enth"alt alle Objekte des
Graphen, die von der Parallelen zur Y-Achse an dieser Stelle
geschnitten werden. Weil die Objekte nach Y-Koordinate sortiert sind,
kann man mittels bin"arer Suche das gesuchte Objekt finden bzw.
feststellen, da"s keines existiert. Die Aufgabe der Speicherung und
der Rekonstruktion der Sweeplines wird von der sogenannten
\emph{X-Struktur} erledigt, sie ist damit das Herzst"uck der
Datenstruktur zur Punkt-Lokalisierung.%"
@ % === Implementierung ======================================================
\section{Implementierung}
Bei der Implementierung wurde darauf Wert gelegt, da"s die Datenstruktur
|PointLocator| mit verschiedenen Eingabetypen benutzt werden kann.
So kann sie z.B. LEDA Graphen der Form |Graph<Point,...>| verarbeiten, wobei
|Point| einer der LEDA Punkttypen |point| oder |rat_point| sein darf.
Aber auch Voronoi-Diagramme, die durch Objekte vom Typ |GRAPH<Circle,Point>|
repr"asentiert werden, k"onnen verwendet werden.
Dar"uber hinaus hat der Benutzer die M"oglichkeit, seine eigenen Datentypen
f"ur Punkte, Graphen, usw. einzusetzen.
@ % === Traits f<>r Punkt-Lokalisierung =======================================
\subsection{Generische Implementierung mit Traits}
Um diese Anforderungen verwirklichen zu k"onnen, werden Traits-Klassen
\cite{Mye95} verwendet, wie sie auch in den Bibliotheken STL
\cite{STL} oder CGAL \cite{CGAL_design} zum Einsatz kommen. Die
Traits-Klasse teilt dem |PointLocator| die an der Punkt-Lokalisation
beteiligten Typen wie z.B. Graph, Punkt und Kurve mit und stellt die
ben"otigten Operationen auf diesen Typen zur Verf"ugung. F"ur einige
LEDA Graphentypen werden in den Abschnitten
\ref{PLocTraits_LEDA_Graph} und \ref{PLocTraits_LEDA_VD} konkrete
Implementierungen von Traits-Klassen besprochen. Will der Anwender
andere Datentypen benutzen, so mu"s er selbst eine geeignete
Traits-Klasse implementieren.
Die Anforderungen an eine Traits-Klasse f"ur Punkt-Lokalisation werden im
folgenden behandelt. \label{PLocTraits_interface}
Neben Anforderungen an das Interface der Klasse werden auch semantische
Bedingungen f"ur die von ihr zur Verf"ugung gestellten Typen und Operationen
diskutiert:
\begin{itemize}
\item Grundlegende Typen sind Graph, Knoten und Kante, sowie Iteratoren
zum Durchmustern von Knoten und Kanten.
<<PLocTraitsInterface>>=
class Graph;
class Node;
class Edge;
class NodeIterator;
NodeIterator Nodes_begin(const Graph& G) const;
NodeIterator Nodes_end(const Graph& G) const;
class IncEdgeIterator;
IncEdgeIterator IncEdges_begin(const Graph& G, const Node& n) const;
IncEdgeIterator IncEdges_end(const Graph& G, const Node& n) const;
Node toNode(const NodeIterator& n);
Edge toEdge(const IncEdgeIterator& e);
@ Die Klassen |NodeIterator| und |IncEdgeIterator| m"ussen den
Anforderungen an einen STL-ForwardIterator (s. \cite{STL})
entsprechen.
Ein |NodeIterator| dient dazu, "uber die Knotenmenge des Graphen zu
iterieren, wenn der Sweep initialisiert wird.
Die Funktionen |Nodes_begin(...)| und |Nodes_end(...)| liefern einen
Iterator, der auf den ersten Knoten des Graphen bzw. hinter den letzten
zeigt.
Der |IncEdgeIterator| erlaubt es dem Algorithmus, "uber die zu einem Knoten
inzidenten Kanten zu iterieren; die Semantik der |begin|- und |end|-
Funktionen ist analog wie bei |NodeIterator|.%"
\item Der Sweep-Algorithmus ist darauf angewiesen, jede Kante |e| bzgl. eines
ihrer inzidenten Knoten |u| in eine der folgenden 4 Kategorien einzuteilen.
Hierbei sei |v| der andere zu |e| inzidente Knoten:%
\label{PLoc_edge_classification}
\begin{enumerate}
\item |StartingNonVertical|: $P(u).x < P(v).x$
\item |EndingNonVertical|: $P(u).x > P(v).x$
\item |StartingVertical|: $P(u).x = P(v).x \wedge P(u).y < P(v).y$
\item |EndingVertical|: $P(u).x = P(v).x \wedge P(u).y > P(v).y$
\end{enumerate}
<<PLocTraitsInterface>>=
enum EdgeCategory
{ StartingNonVertical, StartingVertical, EndingNonVertical, EndingVertical };
EdgeCategory ClassifyEdge(const Graph& G, const Edge& e, const Node& u);
@ %
\item Um Punkt-Lokalisierungen durchzuf"uhren, mu"s der |PointLocator|
nat"urlich den Punkttyp kennen.
Wie der "Uberblick in Abschnitt \ref{PLoc_Overview} gezeigt hat, mu"s
man auch die X-Koordinaten von Punkten (Haltestellen des Sweeps) speichern
und miteinander vergleichen k"onnen.
<<PLocTraitsInterface>>=
class Point;
class XCoord;
XCoord getXCoord(const Point& p) const;
XCoord getXCoord(const Graph& G, const Node& n) const;
class PredLessThanX;
PredLessThanX getLessThanX() const;
@ Der Typ |XCoord| repr"asentiert zwar die X-Koordinaten der Positionen der
Knoten eines Graphen, dies mu"s aber nicht wirklich eine Zahl sein; bei einem
Voronoi-Diagramm z.B. k"onnte man |XCoord| durch Kreise repr"asentieren,
so da"s |PredLessThanX| auf den exakten Koordinaten arbeiten kann, was
Ungenauigkeiten durch Rundungsfehler vermeidet.
Die Klasse |PredLessThanX| mu"s einen Funktionsaufruf-Operator zur Verf"ugung
stellen, der zwei Objekte vom Typ |XCoord| als Eingabe nimmt und das
Ergebnis des Vergleichs als |bool| zur"uckliefert. Die Semantik ist dieselbe
wie bei einem |compare|-Object der STL.
\item Beim "Uberblick "uber die |PointLocator| Datenstruktur wurde gesagt,
da"s in einem persistenten Suchbaum, der die Sweeplines repr"asentiert,
Objekte des gegebenen Graphen sortiert gespeichert werden.
Die Schl"ussel, die in dem Suchbaum verwendet werden, m"ussen nicht nur
die Kurven repr"asentieren k"onnen, die mit den Knoten und Kanten des
Graphen assoziiert sind, sondern auch die Punkte der Lokalisationsanfragen.
Dies ist der Grund daf"ur, da"s die Objekte des Graphen nicht als Schl"ussel
verwendet werden, sondern Objekte vom Typ |Curve|. Die Objekte des Graphen
werden als Informationen im Baum gespeichert.
Es gibt noch einen weiteren Grund der daf"ur spricht, dem Benutzer die
Freiheit zu geben, seinen eigenen Kurventyp zu w"ahlen. Es kann eine
Repr"asentation der Kurven geben, die Vergleiche relativ billig macht,
deren Berechnung aber relativ teuer ist. Insofern kann es sich lohnen, diese
Repr"asentation der Kurven zu speichern und sie nicht vor jedem Vergleich
neu zu berechnen.
<<PLocTraitsInterface>>=
class Curve;
Curve makeCurve(const Point& p) const;
Curve makeCurve(const Graph& G, const Node& n) const;
Curve makeCurve(const Graph& G, const Edge& e) const;
class PredCompareCurves;
PredCompareCurves getCompareCurves() const;
@ |PredCompareCurves| mu"s analog wie |PredCompareX| einen
Funktionsaufruf-Operator bereitstellen, mit dem zwei Kurven |C1| und |C2|
vom Typ |Curve| verglichen werden k"onnen.
Die Semantik ist folgende:\\
$$|PredCompareCurves|(C1, C2) \left\{ \begin{array}{r@{\quad:\quad}l}
<0 & |C1| \mbox{ unter } |C2| \\
=0 & |C1| \cap |C2| \neq \emptyset \\
>0 & |C1| \mbox{ "uber } |C2| \\ %"}
\end{array} \right.$$
Die |PointLocator|-Klasse stellt sicher, da"s |C1| und |C2| bei jedem
Aufruf von |PredCompareCurves| die folgenden Bedingungen erf"ullen:
\begin{enumerate}
\item Die Kurven |C1| und |C2| sind \emph{vergleichbar}, d.h. es existiert
eine Parallele zur Y-Achse, die beide Kurven schneidet.
\item Sowohl |C1| als auch |C2| ist durch den Aufruf einer |makeCurve|
Funktion erzeugt worden.
\end{enumerate}
Der Ausdruck $|C1| \cap |C2| \neq \emptyset$ ist eine Abk"urzung f"ur
folgende Situationen:
\begin{itemize}
\item |C1| oder |C2| ist ein Anfragepunkt |p|.
Dann bedeutet $|C1| \cap |C2| \neq \emptyset$, da"s |p| auf der anderen
Kurve liegt.
\item |C1| und |C2| repr"asentieren Objekte des Graphen. In diesem Fall
hei"st $|C1| \cap |C2| \neq \emptyset$, da"s $|C1| \equiv |C2|$, da der
Graph planar eingebettet ist.
\end{itemize}
\item Das Ergebnis einer Lokalisierungsanfrage kann ein Knoten oder eine Kante
sein oder aber der Wert |nil|. Ein Objekt der Klasse |GenericLocation|, die
mit den Typen der Knoten und der Kanten parametrisiert ist, dient dazu, das
Resultat einer Anfrage aufzunehmen und es gegebenenfalls in einen Knoten
oder eine Kante zur"uck zu konvertieren.
% === Generische Lokation ==================================================
<<Location>>=
/*{\Manpage {GenericLocation}{Node, Edge}
{Return Type for Planar Point Location}{L}}*/
template <class Node, class Edge>
class GenericLocation {
/*{\Mdefinition
An instance of the data type |\Mtype| is used as return value for planar
point location. It can store a node or an edge of a graph or the special
value |nil| which is used to signal that no node or edge could be found.
}*/
typedef void* GenPtr;
public:
/*{\Mtypes}*/
enum Type { NIL, NODE, EDGE };
/*{\Menum This enumeration allows to specify the 3 basic types of the
values that a |\Mtype| can represent.}*/
<<Konvertierungs- und Zuweisungsoperatoren>>
private:
Type type;
GenPtr value;
};
<<Vergleichsoperatoren>>
@ \rem{
<<Konvertierungs- und Zuweisungsoperatoren>>=
/*{\Mcreation}*/
GenericLocation() { init(); }
/*{\Mcreate creates a |\Mtype| and initializes with the value |nil|.}*/
GenericLocation(Node n) { init(n); }
/*{\Mcreate creates a |\Mtype| and initializes with the node |n|.}*/
GenericLocation(Edge e) { init(e); }
/*{\Mcreate creates a |\Mtype| and initializes with the edge |e|.}*/
~GenericLocation() { clear(); }
GenericLocation(const GenericLocation<Node, Edge>& L) { assign(L); }
GenericLocation<Node, Edge>& operator=(
const GenericLocation<Node, Edge>& L)
{ clear(); assign(L); return *this; }
/*{\Moperations}*/
operator const Node&() const
{
#if !defined(CHECKING_OFF)
if (type != NODE) error_handler(1, "Location: not convertible to node");
#endif
return geninfo<Node>::const_access(value);
}
/*{\Mconversion converts |\Mvar| into a node.\\
\precond |\Mvar| represents a node.}*/
operator const Edge&() const
{
#if !defined(CHECKING_OFF)
if (type != EDGE) error_handler(1, "Location: not convertible to edge");
#endif
return geninfo<Edge>::const_access(value);
}
/*{\Mconversion converts |\Mvar| into an edge.\\
\precond |\Mvar| represents an edge.}*/
GenericLocation<Node, Edge>& operator=(Node n)
{ clear(); init(n); return *this; }
/*{\Mbinop makes |\Mvar| represent the node |n|.}*/
GenericLocation<Node, Edge>& operator=(Edge e)
{ clear(); init(e); return *this; }
/*{\Mbinop makes |\Mvar| represent the edge |e|.}*/
Type get_type() const { return type; }
/*{\Mop returns the type of the value contained in |\Mvar|.}*/
bool is_nil() const { return type == NIL; }
/*{\Mop returns |true| iff |\Mvar| represents the value |nil|.}*/
bool is_node() const { return type == NODE; }
/*{\Mop returns |true| iff |\Mvar| represents a node.}*/
bool is_edge() const { return type == EDGE; }
/*{\Mop returns |true| iff |\Mvar| represents an edge.}*/
private:
void init() { type = NIL; }
void init(Node n)
{ type = NODE;
geninfo<Node>::create(value);
geninfo<Node>::access(value) = n;
}
void init(Edge e)
{ type = EDGE;
geninfo<Edge>::create(value);
geninfo<Edge>::access(value) = e;
}
void clear()
{
switch(type) {
case NODE: geninfo<Node>::clear(value); break;
case EDGE: geninfo<Edge>::clear(value); break;
case NIL: break;
}
}
void assign(const GenericLocation<Node, Edge>& L)
{
type = L.type;
switch(type) {
case NODE:
geninfo<Node>::access(value) = geninfo<Node>::const_access(L.value);
break;
case EDGE:
geninfo<Edge>::access(value) = geninfo<Edge>::const_access(L.value);
break;
case NIL: break;
}
}
public:
typedef GenericLocation<Node, Edge> self;
<<Vergleichsoperatoren>>=
/*{\Mimplementation
The data type |\Mtype| is implemented as a union of the types |Node| and
|Edge|. There is only constant time and space overhead.
}*/
template <class Node, class Edge>
inline
bool
operator==(const GenericLocation<Node, Edge>& L1,
const GenericLocation<Node, Edge>& L2)
{
if (L1.get_type() != L2.get_type()) return false;
switch (L1.get_type()) {
case GenericLocation<Node, Edge>::NIL: return true;
case GenericLocation<Node, Edge>::NODE: return Node(L1) == Node(L2);
case GenericLocation<Node, Edge>::EDGE: return Edge(L1) == Edge(L2);
}
}
template <class Node, class Edge>
inline
bool
operator!=(const GenericLocation<Node, Edge>& L1,
const GenericLocation<Node, Edge>& L2)
{ return ! (L1==L2); }
template <class Node, class Edge>
std::ostream& operator<<(std::ostream& o,
const GenericLocation<Node, Edge>& L)
{
switch (L.get_type()) {
case GenericLocation<Node, Edge>::NIL: return o<<"nil";
case GenericLocation<Node, Edge>::NODE: return o<<"node("<<&*Node(L)<<')';
case GenericLocation<Node, Edge>::EDGE: return o<<"edge("<<&*Edge(L)<<')';
}
return o;
}
template <class Node, class Edge>
std::istream& operator>>(std::istream& i, GenericLocation<Node, Edge>&)
{ return i; }
@ }% of rem
(Die Implementierung der Konvertierungs- und Vergleichsoperatoren ist trivial.)
Wenn die Punkt-Lokalisierung eine Lokation berechnet hat, liefert sie diese
nicht sofort zur"uck, sondern wendet die Funktion |PostProcess| aus der
Traits-Klasse darauf an. Der R"uckgabewert dieser Funktion ist das
tats"achliche Ergebnis der Punkt-Lokalisierung. Wie man sieht, darf dieses
Resultat den frei w"ahlbaren Typ |QueryResult| haben.
Falls man z.B. Facetten des Graphen anstatt Knoten oder Kanten
zur"uckliefern will, so kann man dies erreichen, indem man |QueryResult| und
|PostProcess| entsprechend w"ahlt. In der Regel ist |QueryResult| jedoch
mit |GenericLocation<Node, Edge>| identisch.
Man kann |PostProcess| auch dazu benutzen, um beim LEDA Datentyp
|planar_map| daf"ur zu sorgen, da"s immer die gebietsbegrenzende Kante
geliefert wird, falls das gefundene Objekt eine Kante ist
(vgl. Abschnitt \ref{PLocTraits_Map}).
<<PLocTraitsInterface>>=
class QueryResult;
QueryResult PostProcess(const GenericLocation<Node, Edge>& Result,
const GenericLocation<Node, Edge>& Result_plus_epsilon,
const Point& query) const;
@ %
\item Schlie"slich mu"s die Traits-Klasse noch einige Funktionen zur
Verf"ugung stellen, die es dem |PointLocator| erlauben, sie "uber den
Anfang, die aktuelle Haltestelle und das Ende des Sweeps zu informieren.
Die Funktion |clear| wird dazu benutzt, um nicht mehr von der Traits-Klasse
ben"otigte Daten freizugeben, wenn die Funktion |clear| von |PointLocator|
aufgerufen wird.%"
<<PLocTraitsInterface>>=
void sweep_begin(const Graph&);
void sweep_moveto(const XCoord&);
void sweep_end();
void clear();
@ \end{itemize}
Die Traits-Klasse mu"s gewisse Anforderungen bez"uglich Platz- und
Zeitkomplexit"at erf"ullen, damit man Aussagen "uber den
Ressourcenbedarf der Punkt-Lokalisierung machen kann.
Sei $k$ die Summe der Knotenanzahl und der Kantenanzahl des Graphen $G$.
Dann darf der Platzbedarf der Traits-Klasse $O(k)$ nicht "uberschreiten.
Als Zeitschranken ergeben sich $O(k \cdot \log k)$ f"ur |sweep_begin| und
|sweep_end|, $O(k)$ f"ur |clear|, $O(\log k)$ f"ur |PostProcess| und $O(1)$
f"ur die restlichen Operationen.
\rem{
% === <20>bersicht <20>ber das PLocTraits Interface ================================
<<PLocTraitsInterface>>=
class PLocTraitsInterface {
public:
// der Graph-Typ sowie dessen Knoten und Kanten
class Graph;
class Node;
class Edge;
// Iteratoren fuer die Knoten und Kanten des Graphen
class NodeIterator;
NodeIterator Nodes_begin(const Graph& G) const;
NodeIterator Nodes_end(const Graph& G) const;
class IncEdgeIterator;
IncEdgeIterator IncEdges_begin(const Graph& G, const Node& n) const;
IncEdgeIterator IncEdges_end(const Graph& G, const Node& n) const;
Node toNode(const NodeIterator& n);
Edge toEdge(const IncEdgeIterator& e);
// Klassifizierung der Beziehung zwischen Kante und inzidenten Knoten
enum EdgeCategory
{ StartingNonVertical, StartingVertical, EndingNonVertical, EndingVertical };
EdgeCategory ClassifyEdge(const Graph& G, const Edge& e, const Node& n);
// der Punkt-Typ, X-Koordinate der Punkte und Vergleichsobjekt dafuer
class Point;
class XCoord;
XCoord getXCoord(const Point& p) const;
XCoord getXCoord(const Graph& G, const Node& n) const;
class PredLessThanX;
PredLessThanX getLessThanX() const;
// der Kurven-Typ und Vergleichsobjekt dafuer
class Curve;
Curve makeCurve(const Point& p) const;
Curve makeCurve(const Graph& G, const Node& n) const;
Curve makeCurve(const Graph& G, const Edge& e) const;
class PredCompareCurves;
PredCompareCurves getCompareCurves() const;
// Sonstiges
class QueryResult;
QueryResult
PostProcess(const GenericLocation<Node,Edge>& Result,
const GenericLocation<Node,Edge>& Result_plus_epsilon,
const Point& query) const;
void sweep_begin(const Graph&);
void sweep_moveto(const XCoord&);
void sweep_end();
void clear();
};
@ }% of rem
@ % === Generic X Structure ==================================================
\subsection{Die X-Struktur}
Die Klasse |GenericXStructure| verwaltet die Sweeplines, die beim Aufbau der
Datenstruktur zur Punkt-Lokalisierung berechnet werden. Zu jeder Haltestelle
$x$ eines Sweeps, speichert sie die Sweepline bei $x$ und in $x^+$.
Die Datenstruktur besteht im wesentlichen aus 2 Feldern:
Das Feld |Coordinates| enth"alt die Haltestellen in sortierter Reihenfolge,
das Feld |SweepLines| enth"alt die Sweeplines.
Daher kann man zu einer gegebenen X-Koordinate mittels bin"arer Suche
in logarithmischer Zeit die zugeh"orige Sweepline finden.
%"
<<Generische X-Struktur>>=
template <class XCoord, class PredLessThanX, class Sweepline>
class GenericXStructure {
public:
typedef std::vector<XCoord> Array_Coordinates;
typedef std::vector<Sweepline> Array_Sweeplines;
typedef typename Array_Coordinates::const_iterator Coord_iterator;
typedef typename Array_Sweeplines::const_iterator Sweepline_iterator;
private:
int stops;
PredLessThanX LtX;
Array_Coordinates Coordinates;
Array_Sweeplines SweepLines;
// SweepLines[0] is EmptyLine;
public:
GenericXStructure() { clear(); }
GenericXStructure(int n, const PredLessThanX& cmp) { init(n, cmp); }
~GenericXStructure() { clear(); }
void init(int n, const PredLessThanX& cmp)
{ TRACEN("XSinit "<<n);
LtX = cmp;
Coordinates = Array_Coordinates(n);
SweepLines = Array_Sweeplines(2*n+1);
stops = 0;
}
void clear()
{ Coordinates.clear();
SweepLines.clear();
stops = 0;
}
void insertLines(const XCoord& X,
const Sweepline& atX, const Sweepline& inXplus)
{ TRACEN("XSinsert "<<X);
Coordinates[stops] = X;
SweepLines[2*stops+1] = atX;
SweepLines[2*stops+2] = inXplus;
++stops;
}
Sweepline_iterator getLineAt(const XCoord& X) const
{ TRACEN("XSgetLineAt "<<X);
Sweepline_iterator sit = SweepLines.begin(); // EmptyLine
if ( LtX(X,*Coordinates.begin()) ) {
TRACEN("infinity first");
return sit; // ]-infinity, x0[
}
Coord_iterator stopit = std::lower_bound (
Coordinates.begin(),Coordinates.end(),X,LtX);
/* determines stopit maximal such that
\forall j \in [begin,stopit) : *j < X
as a consequence now: *stopit >= X */
bool found_exact = false;
if ( LtX(X,*stopit) ) --stopit; // X < *stopit
else found_exact = true; // X >= *stopit
TRACEN("stopit "<<*stopit);
int offset = stopit-Coordinates.begin();
return found_exact ?
SweepLines.begin() + (2*offset+1) :
SweepLines.begin() + (2*offset+2);
}
Sweepline_iterator begin() const { return SweepLines.begin(); }
Sweepline_iterator end() const { return SweepLines.end();}
};
@ Die leere Sweepline-Instanz (|EmptyLine|) wird aus Effizienzgr"unden
gespeichert, sie erlaubt es der Funktion |getLineAt| eine Referenz
zur"uckzugeben, so da"s keine Kopie erforderlich ist.
Wie bereits im "Uberblick erw"ahnt ist ein Objekt der Klasse |Sweepline| kein
ephemerer Suchbaum, sondern die Version eines teilweise persistenten
Suchbaumes, d.h. ein solches Objekt kann in konstantem Platz repr"asentiert
werden. Somit ist der Platzbedarf der X-Struktur linear in der Anzahl der
Haltestellen des Sweeps und die Funktion |insertLines| arbeitet in konstanter
Zeit.
@ % === PointLocator =========================================================
\subsection{Die PointLocator Klasse}
Die |PointLocator| Klasse verwaltet die Datenstrukturen zur
Punkt-Lokalisierung und stellt die ben"otigte Funktionalit"at zur Verf"ugung.
Die Funktionen sind generisch, ihre tats"achliche Arbeitsweise wird
weitgehend durch die Traits-Klasse bestimmt, mit der die Klasse |PointLocator|
parametrisiert ist. Um dieser Traits-Klasse die Verwaltung von eigenen
Datenstrukturen zu erlauben, speichert ein |PointLocator| immer eine Instanz
seiner Traits. Die zentrale Datenstruktur ist die oben beschriebene X-Struktur.
%Aus Effizienzgr"unden enth"alt die Klasse eine mit |nil| initialisierte
%Instanz der |Location| Klasse, so da"s der Wert |nil| nicht wiederholt
%dynamisch generiert werden mu"s.
<<PointLocator>>=
/*{\Manpage {PointLocator} {PLocTraits} {Planar Point Location} {PL}}*/
template <class PLocTraits>
class PointLocator {
/*{\Mdefinition
An instance |\Mvar| of the parameterized data type |\Mtype| can be used
to perform point location queries in the two-dimensional plane.
Every non-empty instance |\Mvar| is associated with an embedded planar
graph |G|, which has to remain unchanged while it is referenced by |PL|.\\
A location query for a point |p| returns the first object (node or edge)
of |G| which is intersected by the straight ray starting in |p| and going
vertically downwards/upwards.
If the ray does not intersect any node or edge of |G|, then |nil| is
returned.\\
The class |\Mtype| is generic, it is parameterized with a traits class
|PLocTraits| which widely controls its behaviour.
The traits may even change the return type of a query and its semantics.
There are predined traits classes for the LEDA graph types, which are
described below in a seperate section.
}*/
public:
<<Importiere Typen aus PLocTraits>>
/*{\Mtypes}*/
// define additional types
typedef GenericLocation<Node, Edge> Location;
/*{\Mtypedef usual return value for the point loaction.}*/
enum Direction { downwards, upwards};
/*{\Menum used to specify the direction for the point location.}*/
typedef pp_dictionary<Curve, Location, PredCompareCurves> Sweepline;
typedef GenericXStructure<XCoord, PredLessThanX, Sweepline> XStructure;
typedef typename Sweepline::item SL_item;
typedef typename XStructure::Sweepline_iterator Sweepline_iterator;
<<Operationen von PointLocator>>
private:
PLocTraits traits;
XStructure X_Structure;
};
@ \rem{
<<Importiere Typen aus PLocTraits>>=
// copied types from PLocTraits
typedef typename PLocTraits::Point Point;
typedef typename PLocTraits::XCoord XCoord;
typedef typename PLocTraits::PredLessThanX PredLessThanX;
typedef typename PLocTraits::Graph Graph;
typedef typename PLocTraits::Node Node;
typedef typename PLocTraits::Edge Edge;
typedef typename PLocTraits::NodeIterator NodeIterator;
typedef typename PLocTraits::IncEdgeIterator IncEdgeIterator;
typedef typename PLocTraits::Curve Curve;
typedef typename PLocTraits::PredCompareCurves PredCompareCurves;
typedef typename PLocTraits::QueryResult QueryResult;
@ }% end of rem
@ % === PointLocator member functions ========================================
\pagebreak[4]
{\noindent}%
Im folgenden werden die Operationen von |PointLocator| beschrieben.
Die Konstruktoren und die Funktion |clear| sind trivial:
<<Operationen von PointLocator>>=
/*{\Mcreation}*/
PointLocator() { clear(); }
/*{\Mcreate creates an empty |\Mtype|.}*/
PointLocator(const Graph& G, const PLocTraits& PLT = PLocTraits()) :
traits(PLT) { init(G); }
/*{\Mcreate creates a |\Mtype| for the graph |G| and the traits |PLT|.}*/
/*{\Moperations}*/
void clear() { X_Structure.clear(); traits.clear(); }
/*{\Mop makes |\Mvar| empty.}*/
@ Als n"achstes wird die Operation |init| behandelt, sie baut die
Datenstrukturen mit Hilfe eines Sweeps auf. %"
<<Operationen von PointLocator>>=
void init(const Graph& G, const PLocTraits& PLT) { traits = PLT; init(G); }
/*{\Mop makes |\Mvar| a |\Mtype| for the graph |G| and the traits |PLT|.}*/
void init(const Graph& G);
/*{\Mop makes |\Mvar| a |\Mtype| for the graph |G|.}*/
<<PointLocator>>=
template <class PLocTraits>
void
PointLocator<PLocTraits>::init(const Graph& G)
{
traits.sweep_begin(G);
<<Initialisierung des Sweeps>>
<<Sweep>>
traits.sweep_end();
}
@ Bevor der Sweep beginnen kann, wird die Datenstruktur |stops|
initialisiert, die dazu benutzt wird, die einzelnen Haltestellen des
Sweeps in sortierter Reihenfolge zu durchlaufen. Bei der
Initialisierung wird mit jeder Haltestelle die Liste aller Knoten
gespeichert, die an dieser Stelle auf der Sweepline liegen.
Schlie"slich wird die leere Ausgangssweepline konstruiert und die
X-Struktur initialisiert. %
<<Initialisierung des Sweeps>>=
PredLessThanX LtX = traits.getLessThanX();
typedef std::map<XCoord, std::list<Node>, PredLessThanX> dictionary;
typedef typename dictionary::iterator dic_iterator;
dictionary stops(LtX);
// Note: X_Structure, Sweepline, and stops copy compare object
NodeIterator ni = traits.Nodes_begin(G), beyond = traits.Nodes_end(G);
for(; ni != beyond; ++ni) {
XCoord currentX = traits.getXCoord(G, ni);
stops[currentX].push_front(traits.toNode(ni));
}
Sweepline SL(traits.getCompareCurves());
X_Structure.init(stops.size(), LtX);
@ Der Sweep besucht die Haltestellen in sortierter Reihenfolge, berechnet
f"ur jede Stelle |x| den Zustand der Sweepline in |x| und den Zustand in
$x^+$ und f"ugt die Zust"ande in die X-Struktur ein: %"
<<Sweep>>=
dic_iterator stop;
for(stop = stops.begin(); stop != stops.end(); ++stop) {
std::list<Node>& NodesOnSL = stop->second;
traits.sweep_moveto(traits.getXCoord(G, *NodesOnSL.begin()));
<<Berechne die Sweeplines bei $x~(\mathit{SLatX})$ und in $x^+~(\mathit{SL})$>>
X_Structure.insertLines(traits.getXCoord(G, *NodesOnSL.begin()),
SL_at_X, SL);
}
@ Die Zust"ande der Sweepline bei |x| und in $x^+$ werden gr"o"stenteils
simultan berechnet. Es werden alle Knoten, die auf der Sweepline liegen, und
deren inzidente Kanten abgearbeitet. Startende Kanten werden in der jeweiligen
Liste (|EmergingEdges| bzw. |VerticalEdges|) zur sp"ateren Verarbeitung
gespeichert; endende nichtvertikale Kanten werden sofort aus der Sweepline
gel"oscht; endende vertikale Kanten werden ignoriert, da man sie sonst doppelt
bearbeiten w"urde.
<<Berechne die Sweeplines bei $x~(\mathit{SLatX})$ und in $x^+~(\mathit{SL})$>>=
std::list<Edge> EmergingEdges, VerticalEdges;
// explore the nodes on SL
typename std::list<Node>::iterator cur_node;
for(cur_node = NodesOnSL.begin();
cur_node != NodesOnSL.end(); ++cur_node) {
IncEdgeIterator ei = traits.IncEdges_begin(G, *cur_node);
IncEdgeIterator beyond = traits.IncEdges_end(G, *cur_node);
TRACEN("NODE: "<<(*cur_node)->point());
for(; ei != beyond; ++ei) {
switch (traits.ClassifyEdge(G, traits.toEdge(ei), *cur_node)) {
case PLocTraits::StartingNonVertical:
EmergingEdges.push_front(traits.toEdge(ei)); break;
case PLocTraits::StartingVertical:
VerticalEdges.push_front(traits.toEdge(ei)); break;
case PLocTraits::EndingNonVertical:
SL.del(traits.makeCurve(G, traits.toEdge(ei))); break;
case PLocTraits::EndingVertical: break;
}
}
}
@ Nachdem in obigem Schritt alle Kanten aus der Sweepline entfernt wurden, die
diese nicht mehr schneiden, enth"alt die Sweepline nun nur noch die Kanten,
die sie "`"uberqueren"'. Jetzt f"ugt man die Objekte des Graphen |G| ein, die
ganz auf der Sweepline liegen: die vertikalen Kanten und die Knoten in
|NodesOnSL|. So erh"alt man die Sweepline bei $x$.
<<Berechne die Sweeplines bei $x~(\mathit{SLatX})$ und in $x^+~(\mathit{SL})$>>=
// compute SL_at_X
typename std::list<Edge>::iterator cur_edge;
for(cur_edge=VerticalEdges.begin();
cur_edge!=VerticalEdges.end(); ++cur_edge)
SL.insert(traits.makeCurve(G, *cur_edge), Location(*cur_edge));
for(cur_node=NodesOnSL.begin();
cur_node!=NodesOnSL.end(); ++cur_node)
SL.insert(traits.makeCurve(G, *cur_node), Location(*cur_node));
Sweepline SL_at_X = SL;
@ Um die Sweepline in $x^+$ zu berechnen, mu"s man die Objekte, die ganz auf
der Sweepline liegen, wieder l"oschen.
(Bei einer voll persistenten Datenstruktur k"onnte man sich diese Arbeit
sparen.) %"
Dann mu"s man noch die nichtvertikalen Kanten einf"ugen, deren Startpunkt auf
der Sweepline liegt.
<<Berechne die Sweeplines bei $x~(\mathit{SLatX})$ und in $x^+~(\mathit{SL})$>>=
// compute SL_in_X_plus
for(cur_edge=VerticalEdges.begin();
cur_edge!=VerticalEdges.end(); ++cur_edge)
SL.del(traits.makeCurve(G, *cur_edge));
for(cur_node=NodesOnSL.begin();
cur_node!=NodesOnSL.end(); ++cur_node)
SL.del(traits.makeCurve(G, *cur_node));
for(cur_edge=EmergingEdges.begin();
cur_edge!=EmergingEdges.end(); ++cur_edge)
SL.insert(traits.makeCurve(G, *cur_edge), Location(*cur_edge));
@ Damit ist die Beschreibung der Initialisierung der Datenstruktur
abgeschlossen. Man kann nun Schranken f"ur den Platzverbrauch der
Klasse |PointLocator| und f"ur die Laufzeit der Operation |init|
ermitteln. Der Sweepline-Algorithmus f"ugt jedes Objekt des Graphen
$G$, egal ob Knoten oder Kante, genau einmal in eine Sweepline ein und
l"oscht es wieder. Auf dem persistenten Suchbaum, der die Sweeplines
speichert, werden also $2 k$ "Anderungsoperationen ausgef"uhrt, wobei
$k$ die Anzahl der Objekte des Graphen $G$ sei. F"ur jede Operation
ist der erwartete Platzbedarf konstant und die erwartete Laufzeit
$O(\log k)$. Die Anzahl $n$ der Haltestellen des Sweeps ist
h"ochstens so gro"s wie die Anzahl der Knoten des Graphen; daher ist
sowohl die Zeit zum Einf"ugen der Sweeplines in die X-Struktur als
auch deren Platzbedarf $O(n) = O(k)$. Somit ergibt sich insgesamt
eine erwartete Laufzeit von $O(k \cdot \log k)$ f"ur den Sweep und ein
erwarteter Platzbedarf von $O(k)$.
\medskip
Als n"achstes werden die eigentlichen Lokalisierungsoperationen vorgestellt.
Die Operation |locate| ruft eine der beiden Operationen |locate_down| und
|locate_up| auf, je nachdem welche Richtung ihr "ubergeben wurde. %"
<<Operationen von PointLocator>>=
QueryResult locate(const Point& p, const Direction dir) const
{ return dir == downwards ? locate_down(p) : locate_up(p); }
/*{\Mop locates the point |p| in the direction |dir|.}*/
QueryResult locate_down(const Point& p) const;
/*{\Mop locates the point |p| vertically downwards.}*/
QueryResult locate_up(const Point& p) const;
/*{\Mop locates the point |p| vertically upwards.}*/
Location location(Sweepline_iterator S, SL_item it) const
{ return (it == nil ? Location() : S->inf(it)); }
std::string str(const Sweepline& S) const
{ std::ostrstream os; os << "Sweepline:\n";
SL_item it;
forall_items(it,S) { os << " " << S.key(it) << std::endl; }
std::string res(os.str());
os.freeze(0);
return res;
}
@ Die beiden letztgenannten Operationen sind sehr "ahnlich:
Zuerst rekonstruiert man den Zustand der Sweepline an der X-Koordinate des
gegebenen Punktes |p|. Diese Sweepline fragt man nach dem Vorg"anger bzw.
Nachfolger von |p|. Falls dieser nicht existiert, liefert man einfach die
leere Lokation |nil| zur"uck, ansonsten liefert man die entsprechende
Lokation, wobei man der Traits-Klasse noch die M"oglichkeit zur
Nachbearbeitung gibt.
<<PointLocator>>=
template <class PLocTraits>
PointLocator<PLocTraits>::QueryResult
PointLocator<PLocTraits>::
locate_down(const typename PLocTraits::Point& p) const
{
Sweepline_iterator line_at_x = X_Structure.getLineAt(traits.getXCoord(p)),
line_plus = line_at_x;
TRACEN("locate_down "<<str(*line_at_x));
Curve p_curve = traits.makeCurve(p);
PredCompareCurves cmp = traits.getCompareCurves();
SL_item it = line_at_x->locate_pred(p_curve), it_plus(0);
if ( it && line_at_x->inf(it).is_node() &&
cmp(p_curve, line_at_x->key(it))!=0 ) {
// p hit a feature exactly
line_plus = line_at_x+1;
if ( line_plus != X_Structure.end() )
it_plus = line_plus->locate_pred(p_curve);
}
return traits.PostProcess(location(line_at_x,it),
location(line_plus,it_plus),p);
}
template <class PLocTraits>
PointLocator<PLocTraits>::QueryResult
PointLocator<PLocTraits>::locate_up(const typename PLocTraits::Point& p) const
{
Sweepline_iterator line_at_x =
X_Structure.getLineAt(traits.getXCoord(p)), line_plus;
Curve p_curve = traits.makeCurve(p);
PredCompareCurves cmp = traits.getCompareCurves();
SL_item it = line_at_x->locate_succ(p_curve), it_plus(0);
if ( it && line_at_x->inf(it).is_node() &&
cmp(p_curve, line_at_x->key(it))!=0 ) {
// p hit a feature exactly
line_plus = line_at_x+1;
if ( line_plus != X_Structure.end() )
it_plus = line_plus->locate_succ(p_curve);
}
return traits.PostProcess(location(line_at_x,it),
location(line_plus,it_plus), p);
}
@ Die erwartete Laufzeit dieser Operationen ist $O(\log k)$; denn die
X-Struktur ben"otigt $O(\log n) = O(\log k)$ Zeit zum Auffinden der richtigen
Sweepline, der Erwartungswert f"ur die Zeit zur Lokalisierung des Punktes
in der Sweepline ist ebenfalls $O(\log k)$ und die Funktion |PostProcess| mu"s
diese Laufzeitschranke auch einhalten.
@ \rem{
<<PointLocator>>=
/*{\Mimplementation
The implementation of the data type |\Mtype| is based on partially
persistent binary search trees.
The expected space requirement is $O(k)$ where $k$ is the sum of the number
of nodes and the number of edges in the graph $G$.
The expected time needed for construction and the operation |init| is
$O(k \cdot \log k)$, for the |locate|-operations it is $O(\log k)$. The
operation |clear| runs in $O(k)$.
}*/
/*{\Mtext
\headerline{\arabic{manctr}. Predefined traits classes}
\stepcounter{manctr}
All predefined traits classes have in common that the return type of a query
is the type |Location|.
The embedding of the given graph |G| is a straight-line embedding, so that
it is totally determined by the position of the nodes of |G|.
Such a position is specified by a |Point| which can be one of the LEDA point
types |point| or |rat_point|. The positions can be specified implicitly by
the node attribute of a parameterized graph (e.g. |GRAPH<Point,...>|) or
explicitly by a |node_array<Point>|. In case of explicit specification a
|node_array| with the positions of the nodes can be passed to the constructor
of the traits class.
Further, the point location processes for maps and for standard graphs differ
slightly. As a map is a bidirected graph where each edge knows its reversal,
the traits classes for maps can ensure the following property:
If the result of a query for point |p| is an edge |e| (not containing |p|),
then |e| bounds the face of |G| which contains |p|, i.e. |p| lies to the
left of |e|.\\
Here comes a list of the predefined traits classes:\\[-5.5ex]
\begin{itemize}
\item |PLocTraits<Graph>|: standard traits for implicitly specified node
positions\\
|Graph| can be |GRAPH<Point,...>| (standard graph) or
|PLANAR_MAP<Point,...,...>| (map).
\item |PLocTraits_NodeArray<Graph,Point>|: std. traits for explicitly
specified node positions\\
|Graph| can be |graph| (standard graph) or |planar_map| (map).
\item |PLocTraits_Map<Graph>| and |PLocTraits_Map_NodeArray<Graph,Point>|:\\
The parameter |Graph| can be |GRAPH<Point,...>| and |graph| respectively.
These traits classes assume that the given graphs are maps.
\item |PLocTraits< GRAPH<Circle,Point> >|: traits class for closest-site
voronoi diagrams
\end{itemize}
Note that a traits class instantiated with |Graph| can also handle graph
types which are derived from |Graph|. Thus |PLocTraits< graph<Point,T> >|
can be used for graphs of type |ugraph<Point,T>| for example.
}*/
/*{\Mexample
First we show an example where the node positions are given implicitly
as node attributes:
\begin{verbatim}
typedef PointLocator< PLocTraits< GRAPH<Point, int> > > PLocator1;
typedef PLocator1::Location Location;
UGRAPH<Point, int> G;
... // construct G
PLocator1 PL1(G);
Point p = ...; // compute p
Location L1 = PL1.locate_down(p);
\end{verbatim}
The second example shows how a |node_array| can be used to determine the
node positions:
\begin{verbatim}
typedef PLocTraits_NodeArray<planar_map,Point> PLocTraits2;
typedef PointLocator<PLocTraits2> PLocator2;
planar_map pm;
node_array<Point> na;
... // construct pm and na
PLocator2 PL2(pm, PLocTraits2(na));
Point q = ...; // compute q
Location L2 = PL2.locate_up(q);
\end{verbatim}
}*/
@ }% end of rem
@ % === Traits f<>r LEDA Graphen ==============================================
\section{Traits f"ur LEDA Graphen} \label{PLocTraits_LEDA_Graph} %"
In diesem Abschnitt werden einige Traits-Klassen entwickelt, die es erlauben,
LEDA Graphentypen mit der |PointLocator|-Klasse zu benutzen.
Bei den Einbettungen der Graphen in die Ebene sind die Kurven, die mit den
Kanten assoziiert sind, immer Geradensegmente. Deshalb wird die Einbettung
durch die Angabe der Positionen der Knoten eindeutig festgelegt.
Diese Positionen werden durch Objekte vom Typ |Point| spezifiziert, wobei
|Point| einer der LEDA Punkttypen |point| oder |rat_point| sein kann.
Die "Ubergabe der Positionen erfolgt entweder implizit durch Knotenattribute
vom Typ |Point| oder explizit durch ein |node_array<Point>|.\\
Hier ist eine "Ubersicht "uber die Traits-Klassen, die dem Anwender zur
Verf"ugung gestellt werden:
\begin{enumerate}
\item |PLocTraits< GRAPH<Point,...> >| und
|PLocTraits_NodeArray<graph>|
\item |PLocTraits< PLANAR_MAP<Point,...,...> >| und
|PLocTraits_NodeArray<planar_map>|
\item |PLocTraits_Map< GRAPH<Point,...> >| und
|PLocTraits_Map_NodeArray<graph>|
\end{enumerate}
Eine Traits-Klasse, die mit dem Typ |Graph| parametrisiert ist, kann auch
f"ur Graphentypen benutzt werden, die von |Graph| abgeleitet sind.
So kann |PLocTraits_NodeArray<graph>| z.B. nicht nur mit dem Typ |graph|,
sondern auch mit dem Typ |ugraph| verwendet werden.
@ \rem{
<<LEDA traits>>=
// exported traits classes
template <class ParameterizedGraph> class PLocTraits;
template <class Graph, class Point> class PLocTraits_NodeArray;
template <class ParameterizedGraph> class PLocTraits_Map;
template <class Graph, class Point> class PLocTraits_Map_NodeArray;
@ }% end of rem
Bei der Punkt-Lokalisierung wird zwischen Standardgraphen (Punkt 1.) und
Karten (Punkte 2. und 3.) unterschieden.
Ein gerichteter Graph $G=(V,E)$ ohne Schleifen und parallele Kanten ist eine
\emph{Karte} (vgl. \cite{LEDA_book} Abschnitt 8.2), falls f"ur jede Kante
$e=(v,w)$ in |E| auch die Umkehrkante $e^R=(w,v)$ in |E| enthalten ist und
jede Kante ihre Umkehrkante "`kennt"'. Der Begriff "`kennen"' bedeutet hier,
da"s jede Kante einen Zeiger hat, der auf die Umkehrkante verweist.
Man kann sich vorstellen, da"s die Menge $\SET{e,e^R}$ eine ungerichtete Kante
in der Karte darstellt, zumal die Kurven $K(e)$ und $K(e^R)$ zusammenfallen.
Bei der Lokalisierung eines Punktes |p| in der Sweepline k"onnen die beiden
Kanten daher nicht unterschieden werden, und es wird irgendeine der Kanten
geliefert, falls der von |p| ausgehende Strahl als erstes die Kurve $K(e)$
schneidet.
Man m"ochte jedoch sicherstellen, da"s die Antwort auf die Anfrage die Kante
ist, die das Gebiet begrenzt, in dem |p| liegt
(vgl. Abbildung \figref{PLoc_in_Map}).
\Figger{PLoc_in_Map}
{Lokalisierung des Punktes |p| in der Karte liefert die Kante |e|}
{PLoc_in_Map}
Die Traits-Klassen f"ur Karten stellen mit Hilfe der Funktion |PostProcess|
sicher, da"s |p| nicht negativ bez"uglich der gelieferten Kante orientiert
ist, d.h. |p| liegt niemals rechts von der orientierten Gerade durch die
Punkte |P(source(e))| und |P(target(e))|.%"
@ % === Hilfsklassen =========================================================
\subsection{Hilfsklassen}
Oftmals ist es bei der Implementierung wichtig, zu einem gegebenen Punkttyp
die korrespondierenden Typen wie z.B. Koordinaten- oder Segmenttyp
herausfinden zu k"onnen. Dazu dient die Klasse |LEDA_PointTraits|, die f"ur
die LEDA Punkttypen |point| und |rat_point| spezialisiert ist.
<<LEDA traits>>=
template <class point_type>
class LEDA_PointTraits;
template<>
class LEDA_PointTraits<point> {
public:
typedef double Coord;
typedef point Point;
typedef segment Segment;
typedef vector Vector;
typedef circle Circle;
};
template<>
class LEDA_PointTraits<rat_point> {
public:
typedef rational Coord;
typedef rat_point Point;
typedef rat_segment Segment;
typedef rat_vector Vector;
typedef rat_circle Circle;
};
@ % === Compare Funktionsobjekt f<>r LEDA Segmente =============================
Die Kurven, die mit den Knoten und Kanten eines LEDA Graphen in der planaren
Einbettung assoziiert sind, werden durch LEDA Segmente modelliert.
Die Klasse |LEDA_CompareSegments| dient zum Vergleich dieser Segmente;
sie realisiert den Typ |PredCompareCurves| f"ur die Traits-Klassen.
Es wird nicht nur der Funktionsaufruf-Operator zur Verf"ugung gestellt, der
den Vergleich durchf"uhrt, sondern auch statische Funktionen zum Erzeugen von
Segmenten.
Diese Funktionen stellen sicher, da"s die erzeugten Segmente die folgenden
Invarianten erf"ullen, was eine effiziente Implementierung der
Vergleichsfunktion erlaubt.
\begin{enumerate}
\item F"ur jedes Segment $s = \Seg{a}{b}$ gilt: $a \preceq_{lex} b$.
\item Bei einem trivialen Segment $s = \Seg{a}{a}$ haben die Punkte nicht nur
die gleichen Koordinaten, sondern die sie repr"asentierenden Objekte sind
identisch.
(Wenn man also die Funktion |identical| von LEDA auf die Randpunkte eines
Segmentes anwendet, kann man ohne Koordinatenvergleich herausfinden, ob das
Segment trivial ist.)
\end{enumerate}
%"
@ \rem{
<<Importiere Typen aus PointTraits>>=
typedef typename PointTraits::Coord Coord;
typedef typename PointTraits::Point Point;
typedef typename PointTraits::Segment Segment;
@ }% of rem
<<LEDA traits>>=
template <class PointTraits>
class LEDA_CompareSegments {
public:
<<Importiere Typen aus PointTraits>>
int operator() (const Segment& s1, const Segment& s2) const;
static Segment CreateSegment(const Point& p, const Point& q)
{ return (compare(p,q) < 0) ? Segment(p,q) : Segment(q,p); }
static Segment CreateTrivialSegment(const Point& p)
{ return Segment(p,p); }
private:
inline int Cmp_Pnt_NonTrivSeg(const Point& p, const Segment& s) const;
};
@ Bei der Punkt-Lokalisierung ist mindestens eines der beiden am Vergleich
beteiligten Segmente $s_1$ und $s_2$ ein Punkt, also trivial.
Dieser Fall wird von der Vergleichsfunktion zuerst behandelt.
Sind beide Segmente in Wirklichkeit Punkte, so mu"s man deren Y-Koordinaten
vergleichen.
Ist jedoch eines der Segmente nicht trivial, so wird die Funktion
|Cmp_Pnt_NonTrivSeg| benutzt, um das Ergebnis des Vergleichs von $s_1$ und
$s_2$ zu bestimmen.%"
<<LEDA traits>>=
template <class PointTraits>
int
LEDA_CompareSegments<PointTraits>::operator()
(const typename PointTraits::Segment& s1,
const typename PointTraits::Segment& s2) const
{
const Point& a = s1.source(); const Point& b = s1.target();
const Point& c = s2.source(); const Point& d = s2.target();
if (identical(a,b)) {
if (identical(c,d)) return compare(a.ycoord(), c.ycoord());
return Cmp_Pnt_NonTrivSeg(a, s2);
}
if (identical(c,d)) return - Cmp_Pnt_NonTrivSeg(c, s1);
<<Vergleiche zwei nicht-triviale Segmente>>
}
@ Die Hilfsfunktion |Cmp_Pnt_NonTrivSeg| liefert die vertikale Lage des
Punktes |p| bez"uglich des nichttrivialen Segmentes |s|.
Voraussetzung ist, da"s |p| und |s| vergleichbar sind, d.h. die Parallele zur
Y-Achse durch |p| schneidet |s|.
Wenn das Segment nicht vertikal ist, so ist das Ergebnis einfach die
Orientierung von |p| bzgl. |s| (vgl. \cite{LEDA_Manual} Abschnitt
"`segment"').
Ansonsten mu"s man |p| mit dem Start- und dem Endpunkt von |s| hinsichtlich
der Y-Koordinate vergleichen.%"
\label{Cmp_Pnt_Edge}
<<LEDA traits>>=
template <class PointTraits>
inline
int
LEDA_CompareSegments<PointTraits>::Cmp_Pnt_NonTrivSeg
(const typename PointTraits::Point& p,
const typename PointTraits::Segment& s) const
{
if (!s.is_vertical()) return orientation(s, p);
if (p.ycoord() <= s.ycoord1()) return -1;
if (p.ycoord() >= s.ycoord2()) return +1;
return 0;
}
@ Sind die zu vergleichenden Segmente beide nicht trivial, was nur w"ahrend
eines Sweeps auftreten kann, so vergleicht man zun"achst die X-Koordinaten
der Anfangspunkte. \label{Cmp_Segments}
Wenn diese verschieden sind, so gen"ugt die Berechnung einer Orientierung
eines Punktes bzgl. eines Segmentes um die vertikale Lage der Segmente zu
bestimmen. Dies sieht man wie folgt:
\begin{quote}
Sei $s_1 = \Seg{a}{b}$ und $s_2 = \Seg{c}{d}$ sowie o.E. $a_x < c_x$.
Dann mu"s $c_x \leq b_x$ sein, denn sonst w"aren die Segmente nicht
vergleichbar.
Also ist $a_x < b_x$, so da"s $s_1$ nicht vertikal sein kann.
Somit liegt $s_1$ genau dann unter $s_2$, falls |c| "uber $s_1$ liegt, also
positiv orientiert ist.
\end{quote}%"
<<Vergleiche zwei nicht-triviale Segmente>>=
int cmpX = compare(a.xcoord(), c.xcoord());
if (cmpX < 0) return - orientation(s1, c);
if (cmpX > 0) return orientation(s2, a);
@ Stimmen die X-Koordinaten der Anfangspunkte "uberein, so werden auch die
Y-Koordinaten verglichen. Sind diese verschieden, so ist die vertikale Lage
der Segmente klar, da ihre Anfangspunkte "ubereinander liegen.
Ansonsten sind die Anfangspunkte identisch und die Orientierung des Endpunktes
von $s_1$ bzgl. $s_2$ liefert das Ergebnis des Vergleiches. Dies sieht man
wie folgt:
\begin{quote}
Falls beide Segmente nicht vertikal sind, ist die Behauptung offensichtlich.
Ist aber eines der Segmente vertikal, so m"ussen aufgrund der Definition
von Vergleichbarkeit beide Segmente vertikal sein. Beide haben also
denselben Anfangspunkt, und ihre Endpunkte liegen (wegen Invariante 2.)
"uber diesem Anfangspunkt; d.h. die Segmente schneiden sich.
Da das Ergebnis der Orientierungsberechnung in diesem Fall 0 ist, arbeitet
die Funktion auch dann korrekt.
\end{quote}
<<Vergleiche zwei nicht-triviale Segmente>>=
int cmpY = compare(a.ycoord(), c.ycoord());
if (cmpY != 0) return cmpY;
// cmpX == cmpY == 0 => a == c
return orientation(s2, b);
@ \rem{
<<LEDA traits>>=
/*
The following declarations are necessary to use LEDA Node Data Accessors
with nodes instead of iterators.
*/
template<>
inline
node get_object(LedaNodeAccessor, const node& it) { return it; }
template<class SN>
inline
SN& get_object(const LedaNodeMemberAccessor&,const SN&, const node& it) {
return ((GIT_dummygraph<SN>*)(graph_of(it)))->get_entry(it); }
@ }% end of rem
@ % === Traits-Basisklasse ====================================================
\subsection{Traits-Basisklasse}
Die Basisklasse |LEDA_PLocTraits| erf"ullt die Anforderungen an eine
Traits-Klasse f"ur die Klasse |PointLocator|
(vgl. Seite \pageref{PLocTraits_interface} ff).
Sie ist nicht nur mit dem Graphentyp parametrisiert, sondern besitzt noch
zwei weitere Parameter.
Der |NodeDataAccessor| dient dazu, den Zugriff auf die Position |P(v)| eines
Knotens |v| unabh"angig von deren Repr"asentation zu machen, d.h. der Code
von |getPosition| ist unabh"angig davon, ob die Information in einem
|node_array| oder beim Knoten selbst gespeichert ist, wie dies z.B. bei einem
Graph vom Typ |GRAPH<Point,...>| der Fall ist.
Dieses Konzept wurde von Marco Nissen \cite{Nissen_diplom} f"ur LEDA
entwickelt; in derselben Arbeit wurden auch die Iteratoren f"ur LEDA Graphen
implementiert.
Der letzte Parameter der Traits-Klasse ist der Iterator f"ur die zu einem
Knoten inzidenten Kanten. Bei einem Standardgraphen wird hier ein Iterator
benutzt, der alle inzidenten Kanten durchl"auft; bei einer Karte reicht
dagegen ein Iterator, der nur die ausgehenden Kanten durchmustert, da die
eingehenden Kanten gerade die Umkehrungen der ausgehenden sind, so da"s die
assoziierten Kurven "ubereinstimmen.%"
<<LEDA traits>>=
template <class GraphType, class NodeDataAccessor, class IncEdgeIt>
class LEDA_PLocTraits {
public:
typedef GraphType Graph;
typedef typename NodeDataAccessor::value_type Point;
<<Operationen und Typen von LEDA_PLocTraits>>
virtual ~LEDA_PLocTraits() {}
protected:
NodeDataAccessor positions;
const Point& getPosition(node n) const
{ return access(const_cast<NodeDataAccessor&>(positions), n); }
};
@ Die in LEDA vorhandenen Typen f"ur Knoten, Kanten und Iteratoren erf"ullen
die f"ur die Traits-Klasse geforderten Eigenschaften und k"onnen daher
"ubernommen werden. %"
<<Operationen und Typen von LEDA_PLocTraits>>=
typedef node Node;
typedef edge Edge;
typedef NodeIt_n NodeIterator;
NodeIterator Nodes_begin(const Graph& G) { return NodeIterator(G); }
NodeIterator Nodes_end(const Graph& G) { return NodeIterator(G).end(); }
typedef IncEdgeIt IncEdgeIterator;
IncEdgeIterator IncEdges_begin(const Graph& G, const Node& n)
{ return IncEdgeIterator(G, n); }
IncEdgeIterator IncEdges_end(const Graph& G, const Node& n)
{ return IncEdgeIterator(G, n).end(); }
@ Die Einteilung einer Kante in die 4 Kategorien von Seite
\pageref{PLoc_edge_classification} erfolgt durch Anwendung der Definition
der einzelnen Kategorien:
<<Operationen und Typen von LEDA_PLocTraits>>=
enum EdgeCategory
{ StartingNonVertical, StartingVertical, EndingNonVertical, EndingVertical };
EdgeCategory ClassifyEdge(const Graph& G, const Edge& e, const Node& u)
{
Point p_u = getPosition(u);
Point p_v = getPosition(G.opposite(u, e));
int cmpX = compare(p_u.xcoord(), p_v.xcoord());
if (cmpX < 0) return StartingNonVertical;
if (cmpX > 0) return EndingNonVertical;
int cmpY = compare(p_u.ycoord(), p_v.ycoord()); assert(cmpY != 0);
if (cmpY < 0) return StartingVertical;
return EndingVertical;
}
@ Als X-Koordinaten (Haltestellen) werden die tats"achliche Koordinaten der
Punkte, also |double| bzw. |rational| benutzt.
Ein interessantes Implementierungsdetail ist hier, da"s die Klasse
|PredCompareX| nicht einfach als |leda_cmp_base<XCoord>| definiert, sondern
davon abgeleitet wird. Das Verhalten w"are in beiden F"allen das gleiche:
Zur Durchf"uhrung des Vergleiches wird die Funktion |compare| aufgerufen,
die von LEDA zur Verf"ugung gestellt wird.
Es bestehen jedoch Unterschiede hinsichtlich der Effizienz.
Die hier vorgestellte Variante erm"oglicht dem Compiler ein Inlining der
|compare|-Funktion.
Bei der anderen Variante (vgl. |param_types.h| von LEDA) w"urde die Funktion
"uber einen Funktionszeiger aufgerufen, was die meisten Compiler nicht
weg\-optimieren k"onnen.
<<Operationen und Typen von LEDA_PLocTraits>>=
typedef typename LEDA_PointTraits<Point>::Coord XCoord;
const XCoord getXCoord(const Point& p) const
{ return p.xcoord(); }
const XCoord getXCoord(const Graph& G, const Node& n) const
{ return getPosition(n).xcoord(); }
class PredCompareX : public leda_cmp_base<XCoord> {
public:
int operator() (const XCoord& x1, const XCoord& x2) const
{ return compare(x1, x2); }
};
PredCompareX getCompareX() const { return PredCompareX(); }
@ Die Typen und Operationen, die mit Kurven zusammenh"angen, werden von der
Klasse |LEDA_CompareSegments| zur Verf"ugung gestellt und hier nur noch
aufgerufen.
<<Operationen und Typen von LEDA_PLocTraits>>=
typedef LEDA_CompareSegments< LEDA_PointTraits<Point> > PredCompareCurves;
typedef typename PredCompareCurves::Segment Curve;
Curve makeCurve(const Point& p) const
{ return PredCompareCurves::CreateTrivialSegment(p); }
Curve makeCurve(const Graph& G, const Node& n) const
{ return makeCurve(getPosition(n)); }
Curve makeCurve(const Graph& G, const Edge& e) const
{
return PredCompareCurves::CreateSegment(getPosition(source(e)),
getPosition(target(e)));
}
PredCompareCurves getCompareCurves() const
{ return PredCompareCurves(); }
@ Die Standardversion der Funktion |PostProcess| tut einfach gar nichts, d.h.
sie liefert die ihr "ubergebene |Location| unver"andert zur"uck.%"
<<Operationen und Typen von LEDA_PLocTraits>>=
typedef GenericLocation<node, edge> Location;
typedef Location QueryResult;
virtual Location PostProcess(const Location& L, const Location& L_plus,
const Point&) const { return L; }
@ Die Funktionen, die dazu dienen, da"s die Traits-Klasse "uber den
Fortschritt des Sweeps informiert wird, sowie die Funktion |clear| bleiben
leer:
<<Operationen und Typen von LEDA_PLocTraits>>=
virtual void sweep_begin(const Graph&) {}
virtual void sweep_moveto(const XCoord&) {}
virtual void sweep_end() {}
virtual void clear() {}
@ % === Traits f<>r Standardgraphen ============================================
\subsection{Traits f"ur Standardgraphen} %"
Bei Standardgraphen kann die Funktionalit"at der Basisklasse unver"andert
"ubernommen werden. Bei der Variante f"ur |node_array|s wird noch ein
Konstruktor zur Verf"ugung gestellt, dem man das |node_array| direkt
"ubergeben kann, sowie eine Funktion |init|, die es erlaubt, das |node_array|
erst sp"ater zu "ubergeben.
(Da der |PointLocator| die ihm "ubergebene Instanz der Traits-Klasse kopiert,
mu"s das |node_array| vor der "Ubergabe gesetzt werden.)%"
<<LEDA traits>>=
// Standard Graphs
template <class Pnt, class etype>
class PLocTraits< GRAPH<Pnt,etype> > :
public LEDA_PLocTraits<GRAPH<Pnt,etype>,node_attribute_da<Pnt>,AdjIt_e>
{};
template <class Pnt>
class PLocTraits_NodeArray<graph, Pnt> :
public LEDA_PLocTraits<graph,node_array_da<Pnt>,AdjIt_e>
{
public:
PLocTraits_NodeArray() {}
PLocTraits_NodeArray(node_array<Pnt>& na) { init(na); }
void init(node_array<Pnt>& na) { positions.init(na); }
};
@ % === Traits f<>r planare Karten =============================================
\subsection{Traits f"ur planare Karten} \label{PLocTraits_Map} %"
Als n"achstes wird die Klasse |LEDA_PLoc_Traits_Map| beschrieben, sie ist die
Basisklasse aller Traits-Klassen f"ur planare Karten.
In ihr wird die Funktion |PostProcess| f"ur planare Karten angepa"st.
Falls die Lokation, die der Funktion "ubergeben wird, eine Kante |e|
repr"asentiert, so mu"s die Funktion sicherstellen, da"s als Endergebnis
der Lokalisierungsanfrage diejenige der Kanten |e| und $e^R$ geliefert wird,
die das Gebiet begrenzt, in dem der Anfragepunkt |p| liegt.
Dazu fragt die Funktion die Orientierung des Punktes |p| bez"uglich der
orientierten Gerade durch |P(source(e))| und |P(target(e))| ab.
Liegt |p| nicht rechts von dieser Geraden, so ist das Ergebnis die Kante |e|;
ansonsten gibt die Funktion die Umkehrkante $e^R$ zur"uck.
<<LEDA traits>>=
// Planar Maps
template <class GraphType, class NodeDataAccessor>
class LEDA_PLocTraits_Map :
public LEDA_PLocTraits<GraphType,NodeDataAccessor,OutAdjIt_e> {
public:
virtual Location PostProcess(const Location& L,
const Location& L_plus, const Point& p) const
{
if (!L.is_edge()) return L;
edge e = L;
if (orientation(getPosition(source(e)), getPosition(target(e)), p) >= 0)
return L;
else
return Location(graph_of(e)->reversal(e));
}
};
@ Bei den Traits-Klassen f"ur die Graphentypen |PLANAR_MAP| und
|planar_map| (mit |node_array|) wird die Funktionalit"at der Basisklasse
unver"andert "ubernommen.%"
<<LEDA traits>>=
template <class Pnt, class etype, class ftype>
class PLocTraits< PLANAR_MAP<Pnt,etype,ftype> > :
public LEDA_PLocTraits_Map< PLANAR_MAP<Pnt,etype,ftype>,node_attribute_da<Pnt> >
{};
template <class Pnt>
class PLocTraits_NodeArray<planar_map, Pnt> :
public LEDA_PLocTraits_Map< planar_map,node_array_da<Pnt> >
{
public:
PLocTraits_NodeArray() {}
PLocTraits_NodeArray(node_array<Pnt>& na) { init(na); }
void init(node_array<Pnt>& na) { positions.init(na); }
};
@ Die Traits-Klassen mit dem Zusatz |Map| in ihrem Namen erlauben es dem
Anwender, Punkt-Lokalisierung in Karten vom Typ |GRAPH<Pnt,...>| bzw.
|graph| zu betreiben, ohne da"s er seine Objekte zuvor in den Typ
|PLANAR_MAP<Pnt,...,...>| bzw. |planar_map| konvertieren mu"s.
Vor dem Sweep "uberpr"ufen beide Traits-Klassen, da"s es sich bei dem Graphen
|G| tats"achlich um eine Karte handelt.%"
<<LEDA traits>>=
template <class Pnt, class etype>
class PLocTraits_Map< GRAPH<Pnt,etype> > :
public LEDA_PLocTraits_Map< GRAPH<Pnt,etype>, node_attribute_da<Pnt> >
{
public:
virtual void sweep_begin(const Graph& G)
{
#if !defined(LEDA_CHECKING_OFF)
if (! Is_Map(G))
error_handler(1, "PLocTraits_Map: graph is not a map");
#endif
}
};
template <class Pnt>
class PLocTraits_Map_NodeArray<graph, Pnt> :
public LEDA_PLocTraits_Map< graph, node_array_da<Pnt> >
{
public:
PLocTraits_Map_NodeArray() {}
PLocTraits_Map_NodeArray(node_array<Pnt>& na) { init(na); }
void init(node_array<Pnt>& na) { positions.init(na); }
virtual void sweep_begin(const Graph& G)
{
#if !defined(LEDA_CHECKING_OFF)
if (! Is_Map(G))
error_handler(1, "PLocTraits_Map_NodeArray: graph is not a map");
#endif
}
};
@ % === Traits f<>r Voronoi-Diagramme =========================================
\section{Traits f"ur LEDA Voronoi-Diagramme} \label{PLocTraits_LEDA_VD} %"
Im folgenden Abschnitt wird eine Traits-Klasse vorgestellt, die es erlaubt,
die Klasse |PointLocator| auch mit Voronoi-Diagrammen zu verwenden.
Doch zun"achst werden Voronoi-Diagramme definiert, einige Eigenschaften
vorgestellt und ihre Repr"asentation in LEDA beschrieben.
Diese Darstellung folgt Abschnitt 10.5 in \cite{LEDA_book}.
Gegeben ist eine Menge $S \subset \RR^2$ von Orten (Sites) in der Ebene.
F"ur jeden Punkt |p| sei |close(p)| die Menge aller Orte in |S|, die am
n"achsten bei |p| liegen, d.h.
$|close(p)| = \Set{s \in S}{\forall t \in S: |dist(s,p)| \leq |dist(t,p)|}$.
In der Regel enth"alt |close(p)| nur einen einzigen Ort, aber f"ur einige
Punkte |p| enth"alt |close(p)| zwei oder mehr Orte.
Diese Punkte bilden das Voronoi-Diagramm:
$|VD(S)| = \Set{p \in \RR^2}{\abs{|close(p)|} \geq 2}$.
Abbildung \figref{voro_example} zeigt ein Beispiel-Diagramm.
Voronoi-Diagramme "ahneln Graphen; die Knoten sind alle Punkte |p| mit
$|close(p)| \geq 3$, die Kanten sind maximale zusammenh"angende Mengen mit
$|close(p)| = 2$ und die Facetten sind maximale zusammenh"angende Mengen mit
$|close(p)| = 1$.\\
In \cite{LEDA_book} wird gezeigt, da"s jede Facette ein konvexes Gebiet ist,
das genau einen Ort enth"alt und dessen Rand ein Polygon ist. Die zu einem
Ort |s| geh"orende Facette wird als Voronoi-Region |VR(s)| bezeichnet.
Es gilt: $|VR(s)| = \Set{p \in \RR^2}{\forall t \in S \setminus \{s\}:
|dist(s,p)| < |dist(t,p)|}$.
Eine Kante, die zwei aneinandergrenzende Regionen |VR(s)| und |VR(t)| trennt,
liegt immer auf der Mittelsenkrechten von |s| und |t|.
\Figger{voro_example}{Voronoi-Diagramm mit 27 Sites}{voro_example}
Voronoi-Diagramme werden in LEDA als planare Karten vom Typ
|GRAPH<Circle,Point>| dargestellt, wobei |Point| einer der Punkttypen |point|
oder |rat_point| sein kann und |Circle| der zugeh"orige Kreisttyp ist.
Das Problem hierbei sind die unbeschr"ankten Kanten; hat eine Kante keinen
Start- bzw. Endknoten, so erh"alt sie einen "`Knoten im Unendlichen"', siehe
Abbildung \figref{VD_to_Graph}. Ein Knoten im Unendlichen hat einen
Ausgangsgrad von 1; die anderen Knoten haben einen Ausgangsgrad von mindestens
3, sie werden "`echte Knoten"' genannt.
Die Attribute der Knoten und der Kanten des Graphen geben Aufschlu"s "uber die
Positionen der Knoten und "uber die Voronoi-Regionen:
\begin{itemize}
\item Jede Kante ist mit dem Ort beschriftet, dessen Region links von ihr
liegt.
\item Jeder echte Knoten |v| ist mit einem Kreis |Circle(a,b,c)| beschriftet,
wobei |a|, |b|, |c| drei verschiedene Orte sind, deren Regionen an |v|
grenzen.
Der Mittelpunkt des Kreises ist die Position von |v| in der Ebene.
\item Jeder Knoten |v| im Unendlichen ist inzident zu einer unbeschr"ankten
Kante, die zwei Voronoi-Regionen |VR(a)| und |VR(b)| trennt. Man kann |v|
daher als einen "`Endpunkt"' der Mittelsenkrechten von |a| und |b| ansehen.
W"ahlt man |a| und |b| so, da"s |v| links von dem orientierten
Geradensegment \Seg{a}{b} liegt, so sind |a| und |b| eindeutig bestimmt.
Man beschriftet |v| mit dem Kreis |Circle(a,x,b)|, wobei man f"ur |x| einen
beliebigen Punkt auf der Geraden durch |a| und |b| w"ahlt, so da"s der Kreis
entartet.
\end{itemize}
\Figger{VD_to_Graph}
{Umwandlung eines Voronoi-Diagrammes in einen Graph}
{VD_to_Graph}
Die Einbettung des Graphen in die Ebene ist ordnungserhaltend, d.h. wenn man
die Liste der ausgehenden Kanten eines Knotens |v| als zyklische Liste
betrachtet, so gilt, da"s jede Kante links von ihrer Vorg"angerkante liegt.
Oder anders formuliert: die Reihenfolge der ausgehenden Kanten in der
Adjazenzliste von |v| ist dieselbe wie bei einem Umlauf um |v| im
Gegenuhrzeigersinn.
\subsection{Handhabung der Knoten im Unendlichen}
\label{Handling_nodes_at_infty}
Die Datenstrukturen und Algorithmen von |PointLocator| und den bisher
vorgestellten Traits-Klassen basieren wesentlich auf den Positionen der
Knoten in der Ebene; vor allem die X-Koordinaten waren als Haltestellen des
Sweeps wichtig.
Um die Positionen von Knoten im Unendlichen beschreiben zu k"onnen, erweitert
man den Wertebereich f"ur die Koordinaten um die Werte ${-\infty}$ und
${+\infty}$. Sei |v| ein Knoten im Unendlichen mit der Beschriftung
|Circle(a,?,b)|, dann besitzt |v| genau eine eingehende Kante |e=(u,v)|.
Dieser Kante ordnet man einen Richtungsvektor $\vec{r}$ zu, der von |u| nach
|v| zeigt. Da |e| die Voronoi-Regionen |VR(a)| und |VR(b)| trennt, liegt |e|
auf der Mittelsenkrechten von |a| und |b|. Daher steht $\vec{r}$ senkrecht auf
dem Vektor $b-a$. Weil |v| links von \Seg{a}{b} liegt, erh"alt man $\vec{r}$,
indem man den Vektor $b-a$ um $+90\degree$ dreht.
Also kann man $\vec{r}=(a_y-b_y, b_x-a_x)$ w"ahlen und $|P(v)|=(x(v),y(v))$
wie folgt definieren:
$x(v) = \left\{ \begin{array}{c@{\quad,\quad}l}
{-\infty} & a_y < b_y \\
\frac{1}{2} (a_x+b_x) & a_y = b_y \\
{+\infty} & a_y > b_y
\end{array}\right.$
und
$y(v) = \left\{ \begin{array}{c@{\quad,\quad}l}
{-\infty} & a_x > b_x \\
\frac{1}{2} (a_y+b_y) & a_x = b_x \\
{+\infty} & a_x < b_x
\end{array}\right.$
~\\
Wenn eine Komponente von $\vec{r}$ Null ist, so setzt man $x(v)$ bzw. $y(v)$
also auf die entsprechende Koordinate des Mittelpunktes von |a| und |b|.
Durch diese Definition kann es passieren, da"s man verschiedenen Knoten im
Unendlichen dieselbe Position zuordnet, und es stellt sich die Frage, wie man
die Objekte in der Sweepline an den Haltestellen ordnen sollte, bei denen
Knoten im Unendlichen vorkommen.
%(Unbeschr"ankte Kanten k"onnen genauso wie beschr"ankte Kanten geordnet
%werden.)
Betrachtet man die Sweeplines bei ${-\infty}$ und ${+\infty}$, so stellt
man fest, da"s sie nur Knoten im Unendlichen und keine anderen Objekte
enthalten und da"s sie nach dem Sweep nie mehr benutzt werden, da alle
Anfragepunkte endliche Koordinaten haben. Daher gen"ugt es, alle diese Knoten
als gleich zu betrachten, so da"s die Sweeplines an diesen Stellen nur ein
Element enthalten.
Nun kann es aber auch eine reelle Haltestelle |x| geben, bei der die Sweepline
einen oder zwei Knoten im Unendlichen enth"alt, n"amlich bei vertikalen
unbeschr"ankten Kanten. Auch diese Knoten k"onnen niemals das Ergebnis einer
Punkt-Lokalisierung sein, sondern h"ochstens die unbeschr"ankten Kanten, zu
denen sie inzident sind.
Somit kommt man mit folgender Ordnung aus: Die Knoten im Unendlichen werden
identifiziert, und ein Knoten im Unendlichen ist stets kleiner als jeder echte
Knoten und als jede Kante.%"
@ % === VoronoiCurve =========================================================
\subsection{Hilfsklassen}
Die Klasse |VoronoiCurve| dient zur Darstellung der Kurven, die bei der
Bearbeitung von Voronoi-Diagrammen auftreten.
Sie ist im wesentlichen eine Vereinigung der LEDA Typen |edge| und |Point|.
Die Kurve, die mit einer Kante assoziiert ist, wird nicht explizit
abgespeichert, sondern es wird die Kante selbst gespeichert.
Dar"uber hinaus werden in dem Bitvektor |properties| noch einige Eigenschaften
der Kante abgelegt, wie z.B. ob die Kante vertikal ist oder ob ihr Start- oder
ihr Zielknoten im Unendlichen liegt.
Die Kurve, die mit einem echten Knoten assoziiert ist, wird in Form eines
Objektes vom Typ |Point| gespeichert, sie hei"st \emph{echter Punkt}.
F"ur einen Knoten im Unendlichen wird keine Kurve abgespeichert, es wird nur
ein Flag in |properties| gesetzt, das das Objekt als
\emph{unbeschr"ankten Punkt} kennzeichnet.
<<LEDA traits>>=
template <class Point>
class VoronoiCurve {
public:
enum { mask_edge = 1, mask_vertical = 2, mask_unbounded = 4,
mask_src_at_infty = 8, mask_tgt_at_infty = 16 };
VoronoiCurve() : properties(mask_unbounded) {}
VoronoiCurve(const Point& p) : properties(0) { val = leda_copy(p); }
VoronoiCurve(edge e) : properties(mask_edge) { val = leda_copy(e); }
~VoronoiCurve() { if (is_proper_pnt()) LEDA_CLEAR(Point, val); }
operator const Point&() const { return LEDA_CONST_ACCESS(Point, val); }
operator edge() const { return LEDA_CONST_ACCESS(edge, val); }
int is_unbounded_pnt() const { return properties == mask_unbounded; }
int is_proper_pnt() const { return !properties; }
int is_edge() const { return properties & mask_edge; }
<<Operationen zum Manipulieren und Abfragen von properties>>
private:
int properties;
GenPtr val;
};
@ Die Operationen zum Manipulieren und Abfragen von |properties| sind einfache
Bitoperationen und werden hier nicht aufgef"uhrt.%"
\rem{
<<Operationen zum Manipulieren und Abfragen von properties>>=
void mark_vertical() { properties |= mask_vertical; }
int is_vertical() const { return properties & mask_vertical; }
void mark_unbounded() { properties |= mask_unbounded; }
int is_unbounded() const { return properties & mask_unbounded;}
void mark_src_at_infty()
{ properties |= (mask_unbounded | mask_src_at_infty); }
int has_src_at_infty() const { return properties & mask_src_at_infty; }
void mark_tgt_at_infty()
{ properties |= (mask_unbounded | mask_tgt_at_infty); }
int has_tgt_at_infty() const { return properties & mask_tgt_at_infty; }
VoronoiCurve(const VoronoiCurve& VC) : properties(VC.properties), val(VC.val)
{ if (is_proper_pnt()) LEDA_COPY(Point, val); }
VoronoiCurve& operator=(const VoronoiCurve& VC)
{
properties = VC.properties; val = VC.val;
if (is_proper_pnt()) LEDA_COPY(Point, val);
return *this;
}
LEDA_MEMORY(VoronoiCurve)
<<LEDA traits>>=
template <class Point>
inline
ostream& operator<<(ostream& o, const VoronoiCurve<Point>&) { return o; }
template <class Point>
inline
istream& operator>>(istream& i, const VoronoiCurve<Point>&) { return i; }
@ }% end of rem
@ % === Vergleichsobjekt f<>r VoronoiCurve ====================================
\medskip
Als n"achstes wird das Vergleichsobjekt f"ur |VoronoiCurve|s beschrieben.
Abgesehen vom Funktionsaufruf-Operator, der den Vergleich durchf"uhrt,
enth"alt die Klasse noch einige Hilfsfunktionen.
<<LEDA traits>>=
template <class Point>
class PredCompareVoronoiCurves {
public:
typedef VoronoiCurve<Point> Curve;
typedef typename LEDA_PointTraits<Point>::Coord Coord;
typedef typename LEDA_PointTraits<Point>::Circle Circle;
int operator() (const Curve& C1, const Curve& C2) const;
inline static const Point& get_site(edge e);
inline static const Point get_position(node n);
inline static int orientation(edge e, const Point& p);
private:
int Cmp_Pnt_Curve(const Point& p, const Curve& c) const;
int Cmp_Two_Edges(const Curve& c1, const Curve& c2) const;
};
@ Die Funktion |get_site| liefert den Ort, dessen Voronoi-Region die Kante |e|
begrenzt.
Sie benutzt dazu die Klasse |GIT_dummygraph<>| der LEDA Grapheniteratoren
(s. |graph_iterator.h| in LEDA), um auf die Kanteninformation zuzugreifen.
Da |GRAPH<Circle, Point>| virtuell von |graph| abgeleitet ist, l"a"st sich der
Zeiger, den die Funktion |graph_of| liefert, nicht in einen Zeiger auf
|GRAPH<Circle, Point>| casten.
<<LEDA traits>>=
template <class Point>
inline
const Point&
PredCompareVoronoiCurves<Point>::get_site(edge e)
{
return ((GIT_dummygraph<Point>*) graph_of(e))->get_entry(e);
}
@ Analog liefert |get_position| die Position des Knotens |n|.
Voraussetzung ist hier, da"s der Knoten nicht im Unendlichen liegt.%"
<<LEDA traits>>=
template <class Point>
inline
const Point
PredCompareVoronoiCurves<Point>::get_position(node n)
{
#ifdef DEBUG
{
Circle C = ((GIT_dummygraph<Circle>*) graph_of(n))->get_entry(n);
assert(!C.is_degenerate);
}
#endif
return (((GIT_dummygraph<Circle>*) graph_of(n))->get_entry(n)).center();
}
@ Die Funktion |orientation| liefert die Orientierung des Punktes |p|
bez"uglich der Kante |e|.
Der Ort |s| sei das Attribut der Kante |e|, und der Ort |t| sei das Attribut
der Umkehrkante $e^R$, d.h. |e| trennt die Voronoi-Regionen |VR(s)| und
|VR(t)|. Die Unterst"utzungsgerade von |e| ist die Mittelsenkrechte von
|s| und |t|, also die Menge aller Punkte, die von |s| und |t| gleich weit
entfernt sind. Daher l"a"st sich die Orientierung von |p| aus den Abst"anden
von |p| zu den Orten |s| und |t| ermitteln.
Da |s| positiv bez"uglich |e| orientiert ist, ergibt sich f"ur die
Orientierung von |p|:
$\sign(|dist(t,p)|-|dist(s,p)|) = \sign(|dist(t,p)|^2-|dist(s,p)|^2)$.
Weil man nur am Vorzeichen des R"uckgabewertes der Funktion interessiert ist,
gen"ugt es, $|compare|(|dist(t,p)|^2,|dist(s,p)|^2)$ zu berechnen.
<<LEDA traits>>=
template <class Point>
inline
int
PredCompareVoronoiCurves<Point>::orientation(edge e, const Point& p)
{
edge rev_e = graph_of(e)->reversal(e);
return compare( p.sqr_dist(get_site(rev_e)), p.sqr_dist(get_site(e)) );
}
@ Die Vergleichsfunktion f"ur zwei |VoronoiCurve|s |c1| und |c2| behandelt
zuerst den Fall, da"s |c1| oder |c2| einen echten Punkt repr"asentiert; dann
wird das Ergebnis des Vergleichs mit der Funktion |Cmp_Pnt_Curve| berechnet.
Ist keine der beiden Kurven ein echter Punkt, so wird "uberpr"uft, ob es sich
bei beiden Kurven um Kanten handelt und falls ja die Funktion |Cmp_Two_Edges|
aufgerufen.\\
Ansonsten bleiben 3 F"alle:
\begin{enumerate}
\item |c1| ist eine Kante, |c2| ist ein unbeschr"ankter Punkt.
$\Rightarrow$ Ergebnis: $+1$\\
denn: Kanten liegen per Definition "uber unbeschr"ankten Punkten.
\item |c1| ist ein unbeschr"ankter Punkt, |c2| ist eine Kante.
$\Rightarrow$ Ergebnis: $-1$
\item |c1| und |c2| sind unbeschr"ankte Punkte.
$\Rightarrow$ Ergebnis: $0$\\
denn: Unbeschr"ankte Punkte werden identifiziert.
\end{enumerate}
<<LEDA traits>>=
template <class Point>
inline
int
PredCompareVoronoiCurves<Point>::operator()
(const Curve& c1, const Curve& c2) const
{
if (c1.is_proper_pnt())
return Cmp_Pnt_Curve(c1, c2);
if (c2.is_proper_pnt())
return -Cmp_Pnt_Curve(c2, c1);
if (c1.is_edge())
if (c2.is_edge())
return Cmp_Two_Edges(c1, c2);
else
return +1;
else
return c2.is_edge() ? -1 : 0;
}
@ Die folgende Funktion vergleicht einen Punkt |p| mit einer beliebigen
|VoronoiCurve| |c|, die von der Parallelen zur Y-Achse durch |p| geschnitten
wird.
Sie unterscheidet 3 F"alle:
\begin{enumerate}
\item |c| ist ein echter Punkt.
$\Rightarrow$ Vergleiche die Y-Koordinaten.
\item |c| ist eine Kante.
$\Rightarrow$ Der Vergleich l"auft wie der Punkt-Kante Vergleich auf
Seite \pageref{Cmp_Pnt_Edge}.
\item |c| ist ein unbeschr"ankter Punkt.
$\Rightarrow$ |p| liegt "uber |c| per Definition.
\end{enumerate}
<<LEDA traits>>=
template <class Point>
inline
int
PredCompareVoronoiCurves<Point>::Cmp_Pnt_Curve
(const Point& p, const VoronoiCurve<Point>& c) const
{
if (c.is_proper_pnt())
return compare(p.ycoord(), ((const Point&)c).ycoord());
if (c.is_edge()) {
if (!c.is_vertical()) return orientation((edge)c, p);
// c is a vertical edge:
Coord Y = p.ycoord();
if ((!c.has_src_at_infty()) && (Y <= get_position(source(c)).ycoord()))
return -1;
if ((!c.has_tgt_at_infty()) && (Y >= get_position(target(c)).ycoord()))
return +1;
return 0;
}
return +1; // c is an unbounded point => p > c
}
@ Die Funktion |Cmp_Two_Edges| macht folgende Annahmen "uber die ihr
"ubergebenen Kurven |c1| und |c2|:
\begin{enumerate}
\item |c1| und |c2| stellen 2 Kanten |e1| und |e2| dar, die allerdings auch
unbeschr"ankt sein d"urfen.
\item |c1| und |c2| sind vergleichbar, d.h. es gibt eine Parallele zur
Y-Achse, die beide Kurven schneidet.
\item Es gilt:
$P(|source|(e_i)) \prec_{lex} P(|target|(e_i)),~ i \in \SET{1,2}$.
Informal bedeutet dies, da"s die Kurven von "`links nach rechts"' bzw.
von "`unten nach oben"' verlaufen.
Diese Annahme stellt unter anderem sicher, da"s $|e2| \neq |e1|^R$.
\end{enumerate}
Als erstes wird getestet, ob die beiden Kanten identisch sind.
Danach wird "uberpr"uft, ob die Kanten dieselbe Quelle oder dasselbe Ziel
haben; in diesem Fall kann der Lagevergleich sehr effizient ausgef"uhrt
werden.
Am Ende wird der Fall behandelt, da"s weder die Start- noch die Endknoten der
Kanten "ubereinstimmen.%"
<<LEDA traits>>=
template <class Point>
inline
int
PredCompareVoronoiCurves<Point>::Cmp_Two_Edges
(const Curve& c1, const Curve& c2) const
{
edge e1 = c1, e2 = c2;
if (e1 == e2) return 0;
if (source(e1) == source(e2)) {
<<Vergleiche Kanten mit gleicher Quelle>>
}
if (target(e1) == target(e2)) {
<<Vergleiche Kanten mit gleichem Ziel>>
}
<<Vergleiche Kanten mit unterschiedlichen Quellen und Zielen>>
}
@ Der Code f"ur den Vergleich zweier Kanten mit gleicher Quelle oder gleichem
Ziel beruht auf folgenden Lemmata:
\begin{lemma} \label{VD_1}
Sei |v| ein echter Knoten in einem Voronoi-Diagramm, und seien $e_1$ und
$e_2$ zwei ausgehende Kanten, so da"s $e_2$ der zyklische Nachfolger $e_1$ in
der Adjazenzliste von |v| ist.\\
Dann gilt: $\angle~{e_1,e_2} < 180 \degree$.\\
(Hierbei ist $\angle~{e_1,e_2}$ der Winkel, der "uberstrichen wird, wenn man
$e_1$ im Gegenuhrzeigersinn auf $e_2$ dreht.)
\end{lemma}
\begin{beweis}
Der Beweis des Lemmas ergibt sich im wesentlichen aus Abbildung
\figref{VD_Lemma1} (links).
Dreht man $e_1$ im Gegenuhrzeigersinn auf $e_2$, so "uberstreicht man keine
von |v| ausgehende Kante, denn die Adjazenzliste von |v| ist gerade so
geordnet.
Folglich "uberstreicht man nur einen Ort |s|, der auf dem Kreis liegt, mit dem
der Knoten |v| beschriftet ist.
Seien |r| und |t| die Orte, mit denen $e_1^R$ bzw. $e_2$ beschriftet sind.
Da $e_1$ auf der Mittelsenkrechten von |s| und |r| liegt, halbiert sie den
Winkel $\angle~{s,|P(v)|,r}$; analog halbiert $e_2$ den Winkel
$\angle~{t,|P(v)|,s}$.
Weil ein echter Knoten einen Ausgangsgrad von mindestens 3 hat, gilt $r \neq t$
und damit $\gamma > 0$.
Wegen $2\alpha + 2\beta + \gamma = 360 \degree$ folgt
$\alpha+\beta < 180 \degree$.
\end{beweis}
\Figger{VD_Lemma1}
{Skizzen zu Beweis und Anwendung von Lemma \ref{VD_1}}
{VD_Lemma1}
Wie wendet man Lemma \ref{VD_1} an?
Geht man einmal davon aus, da"s von |v| keine vertikalen Kanten ausgehen, so
kann man die zyklische Liste der ausgehenden Kanten -- wie in Abbildung
\figref{VD_Lemma1} (rechts) -- in zwei Listen $L_v^+ = (e_1^+, \dots, e_h^+)$
und $L_v^- = (e_1^-, \dots, e_k^-)$ partitionieren; $L_v^+$ enth"alt die
Kanten, deren Zielknoten "`rechts"' von |P(v)| liegen, und $L_v^-$ die Kanten
mit Zielknoten "`links"' von |P(v)|.
Man sieht, da"s die Kanten in $L_v^+$ und $L_v^-$ untereinander vergleichbar
sind, zwei Kanten aus verschiedenen Listen jedoch nicht.
Es gilt: $e_1^+ < \dots < e_h^+$ und $e_1^- > \dots > e_k^-$.
Aus Lemma \ref{VD_1} folgt, da"s in der Liste aller ausgehenden Kanten $e_1^+$
nicht der Nachfolger von $e_h^+$ und $e_1^-$ nicht der Nachfolger von $e_k^-$
sein kann.
Hat man also zwei benachbarte Kanten aus $L_v^+$ oder $L_v^-$ gegeben, so kann
man sie vergleichen, ohne arithmetische Operationen auszuf"uhren.
Wenn der Ausgangsgrad von |v| drei ist, dann enth"alt jede der Listen
h"ochstens 2 Elemente, so da"s man immer ohne arithmetische Operationen
auskommt.
Falls |v| viele ausgehende Kanten hat, so benutzt man folgendes Lemma:
\begin{lemma} \label{VD_2}
Sei |v| ein echter Knoten in einem Voronoi-Diagramm, und seien $e_1$ und $e_2$
zwei vergleichbare ausgehende Kanten, die mit den Orten |s| bzw. |t|
beschriftet sind.\\
Dann gilt f"ur $\theta \in \SET{<,=,>}: e_1 ~ \theta ~ e_2 \Leftrightarrow
s_y ~ \theta ~ t_y$.
\end{lemma}
\begin{beweis}
Wenn $e_1 = e_2$ ist, dann ist sicherlich $s = t$.
Betrachte nun den Fall $e_1 < e_2$, d.h. $e_1$ liegt unter $e_2$.
Da $e_1$ und $e_2$ vergleichbar sind, existiert eine Stelle $x \in \RR$, so
da"s die Parallele zur Y-Achse an dieser Stelle die beiden Kanten schneidet.\\
Unterscheide 3 F"alle (vgl. Abbildung \figref{VD_Lemma2}):
\begin{enumerate}
\item $x > P(v)_x$, d.h. $e_1$ und $e_2$ sind in $L_v^+$\\
Sei |r| der Ort, mit dem $e_2^R$ beschriftet ist.
Weil $e_1$ unter $e_2$ liegt, ist $s_y \leq r_y$. Da die Kante $e_2$
"`rechts"' von |v| liegt, gilt: $r_y < t_y$. $\Rightarrow s_y < t_y$
\item $x < P(v)_x$, d.h. $e_1$ und $e_2$ sind in $L_v^-$\\
Sei |r| der Ort, mit dem $e_1^R$ beschriftet ist.
Weil die Kante $e_1$ "`links"' von |v| liegt, gilt: $s_y < r_y$.
Da $e_1$ unter $e_2$ liegt, ist $r_y \leq t_y$. $\Rightarrow s_y < t_y$
\item $x = P(v)_x$, d.h. $e_1$ und $e_2$ sind vertikal\\
Sei |r| der Ort, mit dem $e_2^R$ beschriftet ist.
Aus Lemma \ref{VD_1} folgt, da"s eine Kante $e_3$ existieren mu"s, die
zwischen |s| und |r| verl"auft. $\Rightarrow s_y < r_y = t_y$
\end{enumerate}
In jedem Fall hat man: $s_y < t_y$.\\
Damit gilt f"ur $\theta \in \SET{<,=,>}: e_1 ~ \theta ~ e_2 \Rightarrow
s_y ~ \theta ~ t_y$. Die R"uckrichtung "`$\Leftarrow$"' folgt daraus.
\end{beweis}
\Figger{VD_Lemma2}
{Skizzen zum Beweis von Lemma \ref{VD_2}}
{VD_Lemma2}
Bei dem Vergleich zweier Kanten $e_1$ und $e_2$ mit gemeinsamer Quelle |v|
nutzt man aus, da"s $e_1$ und $e_2$ in $L_v^+$ liegen m"ussen, da
$P(v) \prec_{lex} P(|target|(e_i)), ~ i \in \SET{1,2}$.
%"
<<Vergleiche Kanten mit gleicher Quelle>>=
graph* Gp = graph_of(e1);
if (e2 == Gp->cyclic_adj_succ(e1)) return -1;
if (e2 == Gp->cyclic_adj_pred(e1)) return +1;
return compare(get_site(e1).ycoord(), get_site(e2).ycoord());
@ Bei dem Vergleich zweier Kanten mit demselben Ziel |v| geht man "ahnlich
vor. Man mu"s jedoch beachten, da"s nur die Liste der ausgehenden Kanten im
Gegenuhrzeigersinn geordnet ist, die Liste der eingehenden Kanten jedoch
nicht. Darum arbeitet man mit den Umkehrkanten der zu vergleichenden Kanten,
diese liegen in $L_v^-$.%"
<<Vergleiche Kanten mit gleichem Ziel>>=
graph* Gp = graph_of(e1);
edge rev_e1 = Gp->reversal(e1), rev_e2 = Gp->reversal(e2);
if (rev_e2 == Gp->cyclic_adj_succ(rev_e1)) return +1;
if (rev_e2 == Gp->cyclic_adj_pred(rev_e1)) return -1;
return compare(get_site(rev_e1).ycoord(), get_site(rev_e2).ycoord());
@ Als letztes wird der Fall behandelt, da"s zwei Kanten mit unterschiedlichen
Start- und Endknoten verglichen werden.
Sei $a=P(|source|(e_1)), b=P(|target|(e_1), c=P(|source|(e_2)),
d=P(|target|(e_2)$, wobei die Punkte $a,b,c,d$ auch nicht reelle Koordinaten
haben k"onnen. Es gilt: $a \prec_{lex} b$ und $c \prec_{lex} d$.\\
Betrachte zun"achst den Fall $a \in \RR^2$:
\begin{itemize}
\item Gilt auch $c \in \RR^2$, so kann man den Vergleich ausf"uhren wie den
Vergleich zweier nichttrivialer Segmente in Abschnitt \ref{Cmp_Segments}.
\item Ansonsten gibt es 2 M"oglichkeiten:
\begin{itemize}
\item Ist $e_2$ vertikal, so ist $c_y={-\infty}$. Da $e_1$ und $e_2$ sich
nicht schneiden, mu"s $e_1$ "uber $e_2$ liegen.
\item Falls $e_2$ nicht vertikal ist, gilt $c_x={-\infty}$. Aus der
Vergleichbarkeit der Kanten folgt $d_x > a_x$ und daher ist das Ergebnis
des Vergleichs die Orientierung von |a| bzgl. $e_2$.
\end{itemize}
\end{itemize}%"
<<Vergleiche Kanten mit unterschiedlichen Quellen und Zielen>>=
if (! c1.has_src_at_infty()) {
Point a = get_position(source(e1));
if (! c2.has_src_at_infty()) {
Point c = get_position(source(e2));
// Note: a != c since sources differ
int cmpX = compare(a.xcoord(), c.xcoord());
if (cmpX < 0) return - orientation(e1, c);
if (cmpX > 0) return orientation(e2, a);
return compare(a.ycoord(), c.ycoord()); // cmpX == 0 !
}
else {
if (c2.is_vertical()) return +1;
else return orientation(e2, a);
}
}
@ Nun wird der Fall $a \notin \RR^2$ behandelt.
\begin{itemize}
\item Ist $e_1$ vertikal, so ist $a_y={-\infty}$ und damit liegt $e_1$ unter
$e_2$.
\item Gilt $c \in \RR^2$ und ist $e_1$ nicht vertikal, so hat man
${-\infty}=a_x < c_x < b_x$, und man kann das Ergebnis dadurch bestimmen,
da"s man die Orientierung von |c| bzgl. $e_1$ berechnet.
\end{itemize}
<<Vergleiche Kanten mit unterschiedlichen Quellen und Zielen>>=
if (c1.is_vertical()) return -1;
if (! c2.has_src_at_infty())
return -orientation(e1, get_position(source(e2)));
@ Wenn bis hierhin kein Ergebnis berechnet werden konnte, so gilt:
$a, c \notin \RR^2$. Es gibt dann noch 2 M"oglichkeiten:
\begin{itemize}
\item $b \notin \RR^2$:\\
Es gibt also eine Kante, deren inzidente Knoten beide im Unendlichen liegen.
Dann sind die Orte des Voronoi-Diagrammes kolinear:
\begin{quote}
Annahme: Dies ist nicht der Fall.\\
Betrachte die Orte |s| und |t|, mit denen $e_1$ bzw. $e_1^R$ beschriftet
sind. Dann existiert ein Ort |r|, der nicht auf der Geraden durch |s| und
|t| liegt. Der Mittelpunkt des Kreises durch |r|, |s| und |t| ist ein
Knoten des Voronoi-Diagrammes und liegt auf der Mittelsenkrechten von |s|
und |t|, also auf $e_1$. Dies ist ein Widerspruch!
\end{quote}
In diesem Fall sind alle Kanten parallel, und es gibt keine echten Knoten.
Da $e_1$ nicht vertikal ist, l"a"st sich das Ergebnis des Vergleiches der
Kanten ermitteln, indem man die Y-Koordinaten der Orte vergleicht, mit denen
die Kanten beschriftet sind.
\item $b \in \RR^2$:\\
Es ergibt sich, da"s dann auch $d \in \RR^2$ gelten mu"s, denn sonst w"aren
die Orte des Diagrammes kolinear, was $b \notin \RR^2$ bedeuten w"urde.
Daher kann man den Vergleich analog wie in Abschnitt \ref{Cmp_Segments}
durchf"uhren.
\end{itemize}
<<Vergleiche Kanten mit unterschiedlichen Quellen und Zielen>>=
if (c1.has_tgt_at_infty())
return compare(get_site(e1).ycoord(), get_site(e2).ycoord());
else {
assert(!c2.has_tgt_at_infty());
Point b = get_position(target(e1)); Point d = get_position(target(e2));
int cmpX = compare(b.xcoord(), d.xcoord());
if (cmpX > 0) return - orientation(e1, d);
if (cmpX < 0) return orientation(e2, b);
return compare(b.ycoord(), d.ycoord());
}
@ % === LEDA VD Traits-Klassen ===============================================
\subsection{Die Traits-Klassen}
Von der Klasse |LEDA_PLocTraits_VD| werden die Traits-Klassen f"ur LEDA
Voronoi-Diagramme abgeleitet.
Gro"se Teile des Codes stimmen mit der Traits-Klasse f"ur planare Karten
"uberein. In diesem Abschnitt werden nur die Aspekte besprochen, in denen sich
|LEDA_PLocTraits_VD| von dieser Klasse unterscheidet.
In Abschnitt \ref{Handling_nodes_at_infty} wurde der Wertebereich f"ur die
X-Koordinate um die Werte ${-\infty}$ und ${+\infty}$ erweitert. Jetzt stellt
sich die Frage, wie diese Werte konkret repr"asentiert werden. Aus
Effizienzgr"unden wird der Wertebereich nicht tats"achlich erweitert, sondern
der Koordinatentyp bleibt weiterhin |double| bzw. |rational|; stattdessen
werden zwei Werte $x_{-\infty}$ und $x_{+\infty}$ aus dem Wertebereich dieser
Typen als ${-\infty}$ und ${+\infty}$ festgelegt und in den Attributen
|neg_infty_x| und |pos_infty_x| gespeichert.
X-Koordinaten treten an zwei Stellen in |PointLocator| auf:
\begin{itemize}
\item Sie sind die Haltestellen des Sweeps, und daher sollte man
$x_{\pm\infty}$ so w"ahlen, da"s $x_{-\infty}<x_0<x_1<\dots<x_n<x_{+\infty}$
gilt, wobei $\SET{x_0, \dots, x_n}$ die Menge der reellen Haltestellen des
Sweeps ist.
\item Wenn der |PointLocator| einen Punkt |p| lokalisieren soll, dann fragt er
sein Traits-Objekt nach der X-Koordinate diese Punktes, damit seine
X-Struktur die relevante Sweepline rekonstruieren kann. Daher sollte sich
die Traits-Klasse folgenderma"sen verhalten:
\begin{itemize}
\item $p_x \in [x_0;x_n]$ $\Rightarrow$ Liefere $p_x$ als X-Koordinate.%[
\item $p_x < x_0$ $\Rightarrow$ Liefere einen Wert aus $]x_{-\infty};x_0[$.
\item $p_x > x_n$ $\Rightarrow$ Liefere einen Wert aus $]x_n;x_{+\infty}[$.
\end{itemize}
\end{itemize}
%]"
<<LEDA traits>>=
template <class POINT>
class LEDA_PLocTraits_VD {
public:
virtual ~LEDA_PLocTraits_VD() {}
<<Operationen und Typen von LEDA_PLocTraits_VD>>
protected:
bool at_infty(node n) const
{ return outdeg(n) == 1; }
protected:
Coord neg_infty_x, pos_infty_x;
};
@ \rem{
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
// import types from PointTraits
typedef typename LEDA_PointTraits<POINT>::Coord Coord;
typedef typename LEDA_PointTraits<POINT>::Point Point;
typedef typename LEDA_PointTraits<POINT>::Segment Segment;
typedef typename LEDA_PointTraits<POINT>::Vector Vector;
typedef typename LEDA_PointTraits<POINT>::Circle Circle;
typedef GRAPH<Circle, Point> Graph;
typedef node Node;
typedef edge Edge;
typedef NodeIt_n NodeIterator;
NodeIterator Nodes_begin(const Graph& G) { return NodeIterator(G); }
NodeIterator Nodes_end(const Graph& G) { return NodeIterator(G).end(); }
typedef OutAdjIt_e IncEdgeIterator;
IncEdgeIterator IncEdges_begin(const Graph& G, const Node& n)
{ return IncEdgeIterator(G, n); }
IncEdgeIterator IncEdges_end(const Graph& G, const Node& n)
{ return IncEdgeIterator(G, n).end(); }
typedef Coord XCoord;
class PredCompareX : public leda_cmp_base<XCoord> {
public:
int operator() (const XCoord& x1, const XCoord& x2) const
{ return compare(x1, x2); }
};
PredCompareX getCompareX() const { return PredCompareX(); }
@ }% end of rem
@ Die Wahl der Werte $x_{\pm\infty}$ und die Berechnung der X-Koordinate
eines Anfragepunktes |p| wird f"ur die beiden Punkttypen unterschiedlich
realisiert; daher wird diese Funktionalit"at nicht in der Basisklasse
implementiert.
Die Berechnung der X-Koordinate eines Knotens |v| ist dagegen in beiden
F"allen gleich. Als erstes wird "uberpr"uft, ob |v| im Unendlichen liegt.
Ist dies der Fall, so wird die Definition f"ur |x(v)| aus Abschnitt
\ref{Handling_nodes_at_infty} angewendet, um das Ergebnis zu berechnen.
Bei einem echten Knoten |v| wird seine tats"achliche X-Koordinate geliefert,
also die X-Koordinate des Mittelpunktes des Kreises, mit dem er beschriftet
ist.
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
XCoord getXCoord(const Graph& G, const node& v) const
{
if (at_infty(v)) {
edge e = G.first_adj_edge(v); edge rev_e = G.reversal(e);
const Point& site_e = G[e]; const Point& site_rev_e = G[rev_e];
int cmpY = compare(site_e.ycoord(), site_rev_e.ycoord());
if (cmpY > 0) return neg_infty_x;
if (cmpY < 0) return pos_infty_x;
return (site_e.xcoord() + site_rev_e.xcoord()) / 2;
}
else return G[v].center().xcoord();
}
@ Die Klassifizierung einer Kante |e| bzgl. des Knotens |v| erfolgt in zwei
Schritten. Da $|ClassifyEdge|(G,e,v)=|ClassifyEdge|(G,e^R,v)$ gilt, kann man
$E \in \SET{e, e^R}$ so w"ahlen, da"s |v| die Quelle von |E| ist.
Die eigentliche Klassifizierung erfolgt nun "uber den Richtungsvektor
$\vec{r}$ der Kante |E| (vgl. Abschnitt \ref{Handling_nodes_at_infty}); dies
funktioniert sowohl f"ur unbeschr"ankte als auch f"ur beschr"ankte Kanten.
%"
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
enum EdgeCategory
{ StartingNonVertical, StartingVertical, EndingNonVertical, EndingVertical };
EdgeCategory ClassifyEdge(const Graph& G, const Edge& e, const Node& v) const
{
edge E, rev_E;
if (v == source(e)) { E = e; rev_E = G.reversal(E); }
else { rev_E = e; E = G.reversal(rev_E); }
const Point& site_E = G[E]; const Point& site_rev_E = G[rev_E];
int cmpY = compare(site_E.ycoord(), site_rev_E.ycoord());
if (cmpY > 0) return StartingNonVertical;
if (cmpY < 0) return EndingNonVertical;
return (site_E.xcoord() < site_rev_E.xcoord()) ?
StartingVertical : EndingVertical;
}
@ \rem{
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
typedef VoronoiCurve<Point> Curve;
typedef PredCompareVoronoiCurves<Point> PredCompareCurves;
PredCompareCurves getCompareCurves() const
{ return PredCompareCurves(); }
Curve makeCurve(const Point& p) const
{ return Curve(p); }
Curve makeCurve(const Graph& G, const Node& n) const
{ return at_infty(n) ? Curve() : Curve(G[n].center()); }
@ }% end of rem
@ Die Funktion |makeCurve| erzeugt aus einer Kante |e| eine |VoronoiCurve|
|c|, welche die Kante repr"asentiert.
Dieses Objekt speichert eine Kante $E \in \SET{e, e^R}$. Sie wird so gew"ahlt,
da"s $P(|source|(E)) \prec_{lex} P(|target|(E))$ gilt. Dies ist n"otig, damit
die Vergleichsfunktion f"ur |VoronoiCurve|s korrekt arbeitet. Dar"uber hinaus
wird in |c| gespeichert, ob die Kante vertikal ist und welche inzidenten
Knoten im Unendlichen liegen.
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
Curve makeCurve(const Graph& G, const Edge& e) const
{
edge E = e;
Curve c;
switch (ClassifyEdge(G, e, source(e))) {
case EndingNonVertical: E = G.reversal(E); // fall-through
case StartingNonVertical: c = Curve(E); break;
case EndingVertical: E = G.reversal(E); // fall-through
case StartingVertical: c = Curve(E); c.mark_vertical(); break;
}
if (at_infty(source(E))) c.mark_src_at_infty();
if (at_infty(target(E))) c.mark_tgt_at_infty();
return c;
}
@ \rem{
<<Operationen und Typen von LEDA_PLocTraits_VD>>=
typedef GenericLocation<node, edge> Location;
typedef Location QueryResult;
Location PostProcess(const Location& L,
const Location& L, const Point& P) const
{
if (!L.is_edge()) return L;
edge e = L;
if (PredCompareCurves::orientation(e, P) >= 0)
return L;
else
return Location(graph_of(e)->reversal(e));
}
virtual void sweep_begin(const Graph&) {}
virtual void sweep_moveto(const XCoord&) {}
virtual void sweep_end() {}
virtual void clear() {}
@ }% end of rem
@ Die Traits-Klasse f"ur Voronoi-Diagramme, deren Orte durch den Typ |point|
dargestellt werden, ist einfach zu implementieren.
Weil die X-Koordinaten der Punkte vom Typ |double| sind, liegen die Werte f"ur
$x_{\pm\infty}$ auf der Hand: $\pm|MAXDOUBLE|$.
Die Funkion |getXCoord| liefert bei einem Punkt |p| dessen tats"achliche
X-Koordinate; der Fall $p_x = x_{\pm\infty}$ wird aus Effizienzgr"unden nicht
abgefangen.
<<LEDA traits>>=
class PLocTraits< GRAPH<circle, point> > :
public LEDA_PLocTraits_VD<point>
{
public:
typedef LEDA_PLocTraits_VD<point> BASE;
public:
PLocTraits() { neg_infty_x = -MAXDOUBLE; pos_infty_x = +MAXDOUBLE; }
virtual ~PLocTraits() {}
XCoord getXCoord(const Point& p) const { return p.xcoord(); }
XCoord getXCoord(const Graph& G, const node& n) const
{ return BASE::getXCoord(G, n); }
};
@ Werden die Orte im Voronoi-Diagramm durch den Typ |rat_point| dargestellt,
so ist die Implementierung der Traits-Klasse etwas aufwendiger.
Die Koordinaten der Punkte sind hier vom Typ |rational|, so da"s leider keine
kanonischen Werte f"ur $x_{-\infty}$ und $x_{+\infty}$ existieren. Vielmehr
m"ussen diese Werte dynamisch gew"ahlt werden, d.h. in Abh"angigkeit von dem
Graphen $G=(V,E)$ und dessen Einbettung.
Sei $S_r = \Set{P(v)_x}{v \in V} \cap \RR$ die Menge aller reellen
Haltestellen des sp"ateren Sweeps. Die Funktion |sweep_begin| setzt
$x_{-\infty} = \lfloor \min(S_r \cup \SET{0}) \rfloor-2$ und
$x_{+\infty} = \lceil \max(S_r \cup \SET{0}) \rceil +2$.
Das Runden sorgt daf"ur, da"s der Platz f"ur die Darstellung von
$x_{\pm\infty}$ minimiert wird, da der Nenner auf 1 gesetzt wird.
Da auch Knoten im Unendlichen zu $S_r$ beitragen beitragen k"onnen, n"amlich
dann, wenn sie zu einer vertikalen Kante inzident sind, kann sich die Funktion
|sweep_begin| nicht nur auf die echten Knoten beschr"anken.
Nach dem Sweep setzt die Funktion |sweep_end| die Attribute |neg_infty_x| und
|pos_infty_x| auf die Werte $x_{-\infty}+1$ bzw. $x_{+\infty}-1$. Dadurch wird
gew"ahrleistet, da"s die Funktion |getXCoord| bei einem Anfragepunkt |p| eine
X-Koordinate im Intervall $]x_{-\infty};x_{+\infty}[$ berechnet. %]
<<LEDA traits>>=
class PLocTraits< GRAPH<rat_circle, rat_point> > :
public LEDA_PLocTraits_VD<rat_point>
{
public:
typedef LEDA_PLocTraits_VD<rat_point> BASE;
public:
virtual ~PLocTraits() {}
virtual void sweep_begin(const Graph& G)
{
if (G.number_of_nodes() > 0) {
neg_infty_x = pos_infty_x = 0;
node n;
forall_nodes(n, G) {
Coord X = BASE::getXCoord(G, n); // Note: Nodes at infty_y
if (X < neg_infty_x) neg_infty_x = X;
else if (X > pos_infty_x) pos_infty_x = X;
}
neg_infty_x = floor(neg_infty_x)-2; pos_infty_x = ceil(pos_infty_x)+2;
}
}
virtual void sweep_end() { ++neg_infty_x; --pos_infty_x; }
XCoord getXCoord(const Point& p) const
{
XCoord X = p.xcoord();
if (X < neg_infty_x) return neg_infty_x;
if (X > pos_infty_x) return pos_infty_x;
return X;
}
XCoord getXCoord(const Graph& G, const node& n) const
{ return BASE::getXCoord(G, n); }
};
@ \rem{
@ % === Header Files =========================================================
\section{Die Header-Dateien}
Der oben vorgestellte Code ist auf zwei Header-Dateien verteilt.
Die Datei |gen_point_location.h| enth"alt alle generischen, oder genauer nicht
LEDA-spezifischen Klassen:%"
<<gen_point_location.h>>=
<<CGAL Header>>
#ifndef GEN_POINT_LOCATION_H
#define GEN_POINT_LOCATION_H
#if CGAL_LEDA_VERSION < 500
#include <LEDA/pp_dictionary.h>
#else
#include <LEDA/core/pp_dictionary.h>
#endif
#include <strstream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <CGAL/Nef_2/geninfo.h>
#undef _DEBUG
#define _DEBUG 17
#include <CGAL/Nef_2/debug.h>
// #define CHECKING_OFF
// for dictionary
template <class Node>
inline std::ostream& operator<<(std::ostream& o, const std::list<Node>& n)
{ return o; }
<<Location>>
<<Generische X-Struktur>>
<<PointLocator>>
#endif // GEN_POINT_LOCATION_H
<<CGAL Header>>=
// ============================================================================
//
// Copyright (c) 1997-2000 The CGAL Consortium
//
// This software and related documentation is part of an INTERNAL release
// of the Computational Geometry Algorithms Library (CGAL). It is not
// intended for general use.
//
// ----------------------------------------------------------------------------
//
// release : $CGAL_Revision$
// release_date : $CGAL_Date$
//
// file : include/CGAL/Nef_2/gen_point_location.h
// package : Nef_2
// chapter : Nef Polyhedra
//
// source : nef_2d/Svens_point_location.lw
// revision : $Id$
// revision_date : $Date$
//
// author(s) : Michael Seel <seel@mpi-sb.mpg.de>
// maintainer : Michael Seel <seel@mpi-sb.mpg.de>
// coordinator : Michael Seel <seel@mpi-sb.mpg.de>
//
// implementation: Polynomials in one variable
// ============================================================================
@
\end{document}