Files
secondo/Algebras/SpatialLR/AreaOp.cpp
2026-01-23 17:03:45 +08:00

812 lines
21 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2016, University in Hagen,
Faculty of Mathematics and Computer Science,
Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
Defines and includes.
*/
#include "AreaOp.h"
#include "Curve.h"
#include <stdexcept>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <limits>
using namespace std;
namespace salr {
/*
6.6 Implementation of ~Edge~ methods
Implementation of constructors.
*/
Edge::Edge(Curve *c, int ctag) :
curve(c), ctag(ctag), etag(AreaOp::ETAG_IGNORE) {
lastEdge = 0;
}
Edge::Edge(Curve *c, int ctag, int etag) : curve(c), ctag(ctag), etag(etag) {
lastEdge = 0;
}
/*
Implementation of access methods.
*/
Curve *Edge::getCurve() const {
return curve;
}
int Edge::getCurveTag() {
return ctag;
}
int Edge::getEdgeTag() {
return etag;
}
void Edge::setEdgeTag(int etag) {
this->etag = etag;
}
int Edge::getEquivalence() {
return equivalence;
}
void Edge::setEquivalence(int eq) {
equivalence = eq;
}
/*
Compares ~Edge~ in arguments with this instance within the given ~yrange~.
*/
int Edge::compareTo(Edge *other, double *yrange) {
if (other == lastEdge && yrange[0] < lastLimit) {
if (yrange[1] > lastLimit) {
yrange[1] = lastLimit;
}
return lastResult;
}
if (this == other->lastEdge && yrange[0] < other->lastLimit) {
if (yrange[1] > other->lastLimit) {
yrange[1] = other->lastLimit;
}
return 0 - other->lastResult;
}
int ret;
if(curve->getOrder() == 1) {
ret = (dynamic_cast<LineCurve *>(curve))->compareTo(other->curve, yrange);
} else {
ret = curve->compareTo(other->curve, yrange);
}
lastEdge = other;
lastLimit = yrange[1];
lastResult = ret;
return ret;
}
void Edge::record(double yend, int etag) {
this->activey = yend;
this->etag = etag;
}
bool Edge::isActiveFor(double y, int etag) {
return (this->etag == etag && this->activey >= y);
}
/*
Comparator used to sort two ~Edge~ by their highest y coordinate.
*/
bool comparator(const Edge *l, const Edge *r) {
Curve *c1 = l->getCurve();
Curve *c2 = r->getCurve();
double v1, v2;
v1 = v2 = 0;
if ((v1 = c1->getYTop()) == (v2 = c2->getYTop())) {
if ((v1 = c1->getXTop()) == (v2 = c2->getXTop())) {
return 0;
}
}
if (v1 < v2) {
return 1;
}
return 0;
}
/*
6.7 Implementation of ~CurveLink~ methods
Implementation of constructor.
*/
CurveLink::CurveLink(Curve *c, double ystart, double yend, int etag) :
curve(c), ytop(ystart), ybot(yend), etag(etag) {
hasNext = false;
next = 0;
if (ytop < c->getYTop() || ybot > c->getYBot()) {
stringstream sstm;
sstm << "bad curvelink [" << ytop << "=>" << ybot << "] for " << &c;
throw runtime_error(sstm.str());
}
}
bool CurveLink::absorb(CurveLink *link) {
return absorb(link->curve, link->ytop, link->ybot, link->etag);
}
/*
Changes ~ytop~ and ~ybot~ if criteria are met.
*/
bool CurveLink::absorb(Curve *curve, double ystart, double yend, int etag) {
if (this->curve != curve || this->etag != etag ||
ybot < ystart || ytop > yend) {
return false;
}
if (ystart < curve->getYTop() || yend > curve->getYBot()) {
stringstream sstm;
sstm << "bad curvelink [" << ystart << "=>" << yend << "] for " << &curve;
throw runtime_error(sstm.str());
}
ytop = min(ytop, ystart);
ybot = max(ybot, yend);
return true;
}
bool CurveLink::isEmpty() {
return (ytop == ybot);
}
/*
Implementation of access methods.
*/
Curve *CurveLink::getCurve() {
return curve;
}
Curve *CurveLink::getSubCurve() {
if (ytop == curve->getYTop() && ybot == curve->getYBot()) {
return curve->getWithDirection(etag);
}
return curve->getSubCurve(ytop, ybot, etag);
}
Curve *CurveLink::getMoveto() {
return new MoveCurve(getXTop(), getYTop());
}
double CurveLink::getXTop() {
return curve->XforY(ytop);
}
double CurveLink::getYTop() {
return ytop;
}
double CurveLink::getXBot() {
return curve->XforY(ybot);
}
double CurveLink::getYBot() {
return ybot;
}
double CurveLink::getX() {
return curve->XforY(ytop);
}
int CurveLink::getEdgeTag() {
return etag;
}
void CurveLink::setNext(CurveLink *link) {
this->next = link;
hasNext = true;
}
void CurveLink::resetCurve() {
curve = 0;
}
CurveLink *CurveLink::getNext() {
if (hasNext) {
return next;
}
return NULL;
}
/*
6.8 Implementation of ~ChainEnd~ methods
Implementation of constructor.
*/
ChainEnd::ChainEnd(CurveLink* first, ChainEnd* partner) :
head(first), tail(first), partner(partner), etag(first->getEdgeTag()) {
}
/*
Implementation of access methods.
*/
CurveLink* ChainEnd::getChain() {
return head;
}
void ChainEnd::setOtherEnd(ChainEnd* partner) {
this->partner = partner;
}
ChainEnd* ChainEnd::getPartner() {
return partner;
}
/*
Returns head of a complete chain to be added to subcurves or null if the
links did not complete such a chain.
*/
CurveLink* ChainEnd::linkTo(ChainEnd* that) {
if (etag == AreaOp::ETAG_IGNORE ||
that->etag == AreaOp::ETAG_IGNORE)
{
throw runtime_error("ChainEnd linked more than once!");
}
if (etag == that->etag) {
throw runtime_error("Linking chains of the same type!");
}
ChainEnd* enter;
ChainEnd* exit;
if (etag == AreaOp::ETAG_ENTER) {
enter = this;
exit = that;
} else {
enter = that;
exit = this;
}
// Now make sure these ChainEnds are not linked to any others...
etag = AreaOp::ETAG_IGNORE;
that->etag = AreaOp::ETAG_IGNORE;
// Now link everything up...
enter->tail->setNext(exit->head);
enter->tail = exit->tail;
if (partner == that) {
// Curve has closed on itself...
return enter->head;
}
// Link this chain into one end of the chain formed by the partners
ChainEnd* otherenter = exit->partner;
ChainEnd* otherexit = enter->partner;
otherenter->partner = otherexit;
otherexit->partner = otherenter;
if (enter->head->getYTop() < otherenter->head->getYTop()) {
enter->tail->setNext(otherenter->head);
otherenter->head = enter->head;
} else {
otherexit->tail->setNext(enter->head);
otherexit->tail = enter->tail;
}
return 0;
}
void ChainEnd::addLink(CurveLink* newlink) {
if (etag == AreaOp::ETAG_ENTER) {
tail->setNext(newlink);
tail = newlink;
} else {
newlink->setNext(head);
head = newlink;
}
}
double ChainEnd::getX() {
if (etag == AreaOp::ETAG_ENTER) {
return tail->getXBot();
} else {
return head->getXBot();
}
}
/*
6.9 Implementation of ~AreaOp~ methods
Used to finalize the subcurves by iterating over all remaining ~ChainEnd~ and
closing unfinished subcurves.
*/
void AreaOp::finalizeSubCurves(vector<CurveLink*>* subcurves,
vector<ChainEnd*>* chains) {
int numchains = chains->size();
if (numchains == 0) {
return;
}
if ((numchains & 1) != 0) {
throw runtime_error("Odd number of chains!");
}
ChainEnd* endlist[numchains];
copy(chains->begin(), chains->end(), endlist);
for (int i = 1; i < numchains; i += 2) {
ChainEnd* open = endlist[i - 1];
ChainEnd* close = endlist[i];
CurveLink* subcurve = open->linkTo(close);
if (subcurve != NULL) {
subcurves->push_back(subcurve);
}
}
chains->clear();
}
/*
Checks if the position of the next edge at v1 "obstruct" the connectivity
between current edge and the potential partner edge which is positioned at v2.
*/
bool AreaOp::obstructs(double v1, double v2, int phase) {
return (((phase & 1) == 0) ? (v1 <= v2) : (v1 < v2));
}
/*
Used to connect matching CurveLinks with each other. Creates new ChainEnds
for newly created subcurves. Adds newly created subcurves to ~subcurves~.
*/
void AreaOp::resolveLinks(vector<CurveLink*> *subcurves,
vector<ChainEnd*> *chains,
vector<CurveLink*> *links)
{
int numlinks = links->size();
if (numlinks == 0 && (numlinks & 1) != 0) {
throw runtime_error("Odd number of new curves!");
}
vector<CurveLink*> linklist(*links);
linklist.push_back(NULL);
linklist.push_back(NULL);
int numchains = chains->size();
if (numlinks == 0 && (numchains & 1) != 0) {
throw runtime_error("Odd number of chains!");
}
vector<ChainEnd*> endlist(*chains);
endlist.push_back(NULL);
endlist.push_back(NULL);
int curchain = 0;
int curlink = 0;
chains->clear();
ChainEnd* chain = endlist.at(0);
ChainEnd* nextchain = endlist.at(1);
CurveLink* link = linklist.at(0);
CurveLink* nextlink = linklist.at(1);
while (chain != NULL || link != NULL) {
/*
* Strategy 1:
* Connect chains or links if they are the only things left...
*/
bool connectchains = (link == NULL);
bool connectlinks = (chain == NULL);
if (!connectchains && !connectlinks) {
// assert(link != null && chain != null);
/*
* Strategy 2:
* Connect chains or links if they close off an open area...
*/
connectchains = ((curchain & 1) == 0 &&
chain->getX() == nextchain->getX());
connectlinks = ((curlink & 1) == 0 &&
link->getX() == nextlink->getX());
if (!connectchains && !connectlinks) {
/*
* Strategy 3:
* Connect chains or links if their successor is
* between them and their potential connectee...
*/
double cx = chain->getX();
double lx = link->getX();
connectchains =
(nextchain != NULL && cx < lx &&
obstructs(nextchain->getX(), lx, curchain));
connectlinks =
(nextlink != NULL && lx < cx &&
obstructs(nextlink->getX(), cx, curlink));
}
}
if (connectchains) {
CurveLink* subcurve = chain->linkTo(nextchain);
if (subcurve != NULL) {
subcurves->push_back(subcurve);
}
curchain += 2;
chain = endlist.at(curchain);
nextchain = endlist.at(curchain+1);
}
if (connectlinks) {
ChainEnd* openend = new ChainEnd(link, 0);
ChainEnd* closeend = new ChainEnd(nextlink, openend);
allChainEnds.push_back(openend);
allChainEnds.push_back(closeend);
openend->setOtherEnd(closeend);
chains->push_back(openend);
chains->push_back(closeend);
curlink += 2;
link = linklist.at(curlink);
nextlink = linklist.at(curlink + 1);
}
if (!connectchains && !connectlinks) {
chain->addLink(link);
chains->push_back(chain);
curchain++;
chain = nextchain;
nextchain = endlist.at(curchain+1);
curlink++;
link = nextlink;
nextlink = linklist.at(curlink+1);
}
}
if ((chains->size() & 1) != 0) {
cout << "Odd number of chains!" << endl;
}
}
/*
Calculates the new area modelled by the two curve vectors depending on the
typ of the current instance.
*/
void AreaOp::calculate(vector<Curve*>* left, vector<Curve*>* right) {
vector < Edge * > *edges = new vector<Edge *>(0);
addEdges(edges, left, AreaOp::CTAG_LEFT);
addEdges(edges, right, AreaOp::CTAG_RIGHT);
pruneEdges(edges, left);
for(unsigned int i = 0; i < edges->size(); i++) {
Edge* e = edges->at(i);
delete e;
}
delete edges;
cleanupPointer();
}
/*
Wraps each curve inside an ~Edge~ and adds all edges to a vector. Tags each
edge according to which region it came from.
*/
void AreaOp::addEdges(vector<Edge*>* edges,
vector<Curve*>* curves, int curvetag) {
if(curves != NULL) {
for(unsigned int i = 0; i < curves->size(); i++) {
Curve* c = curves->at(i);
c->used = false;
startCurves.push_back(c);
if(c->getOrder() > 0) {
edges->push_back(new Edge(c, curvetag));
}
}
}
}
/*
Removes edges which are not needed for the result area. Checks for
intersections between edges and changes underlying ~Curve~ if necessary.
*/
void AreaOp::pruneEdges(vector<Edge*>* edges, vector<Curve*>* result) {
int numedges = edges->size();
if (numedges < 2) {
return;
}
vector<Edge*> edgelist = *edges;
sort(edgelist.begin(), edgelist.end(), comparator);
Edge* e;
int left = 0;
int right = 0;
int cur = 0;
int next = 0;
double mindouble = numeric_limits<double>::min();
double yrange[2] = {mindouble, mindouble};
vector<CurveLink*> subcurves;
vector<ChainEnd*> chains;
vector<CurveLink*> links;
// Active edges are between left (inclusive) and right (exclusive)
while (left < numedges) {
double y = yrange[0];
// Prune active edges that fall off the top of the active y range
for (cur = next = right - 1; cur >= left; cur--) {
e = edgelist[cur];
if (e->getCurve()->getYBot() > y) {
if (next > cur) {
edgelist[next] = e;
}
next--;
}
}
left = next + 1;
// Grab a new "top of Y range" if the active edges are empty
if (left >= right) {
if (right >= numedges) {
break;
}
y = edgelist[right]->getCurve()->getYTop();
if (y > yrange[0]) {
finalizeSubCurves(&subcurves, &chains);
}
yrange[0] = y;
}
// Incorporate new active edges that enter the active y range
while (right < numedges) {
e = edgelist[right];
if (e->getCurve()->getYTop() > y) {
break;
}
right++;
}
// Sort the current active edges by their X values and
// determine the maximum valid Y range where the X ordering
// is correct
yrange[1] = edgelist[left]->getCurve()->getYBot();
if (right < numedges) {
y = edgelist[right]->getCurve()->getYTop();
if (yrange[1] > y) {
yrange[1] = y;
}
}
// Note: We could start at left+1, but we need to make
// sure that edgelist[left] has its equivalence set to 0.
int nexteq = 1;
for (cur = left; cur < right; cur++) {
e = edgelist[cur];
e->setEquivalence(0);
for (next = cur; next > left; next--) {
Edge* prevedge = edgelist[next-1];
int ordering = e->compareTo(prevedge, yrange);
if (yrange[1] <= yrange[0]) {
stringstream sstm;
sstm << "backstepping to " << yrange[1] << " from " << yrange[0];
throw runtime_error(sstm.str());
}
if (ordering >= 0) {
if (ordering == 0) {
// If the curves are equal, mark them to be
// deleted later if they cancel each other
// out so that we avoid having extraneous
// curve segments.
int eq = prevedge->getEquivalence();
if (eq == 0) {
eq = nexteq++;
prevedge->setEquivalence(eq);
}
e->setEquivalence(eq);
}
break;
}
edgelist[next] = prevedge;
}
edgelist[next] = e;
}
// Now prune the active edge list.
// For each edge in the list, determine its classification
// (entering shape, exiting shape, ignore - no change) and
// record the current Y range and its classification in the
// Edge object for use later in constructing the new outline.
newRow();
double ystart = yrange[0];
double yend = yrange[1];
for (cur = left; cur < right; cur++) {
e = edgelist[cur];
int etag;
int eq = e->getEquivalence();
if (eq != 0) {
// Find one of the segments in the "equal" range
// with the right transition state and prefer an
// edge that was either active up until ystart
// or the edge that extends the furthest downward
// (i.e. has the most potential for continuation)
int origstate = getState();
etag = (origstate == AreaOp::RSTAG_INSIDE
? AreaOp::ETAG_EXIT
: AreaOp::ETAG_ENTER);
Edge* activematch=NULL;
Edge* longestmatch = e;
double furthesty = yend;
do {
// Note: classify() must be called
// on every edge we consume here.
classify(e);
if (activematch == NULL &&
e->isActiveFor(ystart, etag))
{
activematch = e;
}
y = e->getCurve()->getYBot();
if (y > furthesty) {
longestmatch = e;
furthesty = y;
}
} while (++cur < right &&
(e = edgelist[cur])->getEquivalence() == eq);
--cur;
if (getState() == origstate) {
etag = AreaOp::ETAG_IGNORE;
} else {
e = (activematch != NULL ? activematch : longestmatch);
}
} else {
etag = classify(e);
}
if (etag != AreaOp::ETAG_IGNORE) {
e->record(yend, etag);
CurveLink* link = new CurveLink(e->getCurve(), ystart, yend, etag);
links.push_back(link);
allCurveLinks.push_back(link);
}
}
resolveLinks(&subcurves, &chains, &links);
links.clear();
// Finally capture the bottom of the valid Y range as the top
// of the next Y range.
yrange[0] = yend;
}
finalizeSubCurves(&subcurves, &chains);
result->clear();
for(unsigned int i = 0; i < subcurves.size(); i++) {
CurveLink* link = subcurves.at(i);
result->push_back(link->getMoveto());
CurveLink* nextlink = link;
while((nextlink = nextlink->getNext()) != NULL) {
if (!link->absorb(nextlink)) {
result->push_back(link->getSubCurve());
link = nextlink;
}
}
result->push_back(link->getSubCurve());
}
for(unsigned int i = 0; i < result->size(); i++) {
result->at(i)->used = true;
}
}
void AreaOp::cleanupPointer() {
for(unsigned int i = 0; i < allCurveLinks.size(); i++) {
CurveLink* link = allCurveLinks.at(i);
delete link;
link = 0;
}
for(unsigned int i = 0; i < startCurves.size(); i++) {
Curve* curve = startCurves.at(i);
if(!curve->used) {
delete curve;
}
curve = 0;
}
for(unsigned int i = 0; i < allChainEnds.size(); i++) {
ChainEnd* chainEnd = allChainEnds.at(i);
delete chainEnd;
chainEnd = 0;
}
}
/*
6.10 Implementation of child class methods
Methods used by ~NZWindOp~ to classify an ~Edge~.
*/
void NZWindOp::newRow() {
count = 0;
}
int NZWindOp::classify(Edge* e) {
int newCount = count;
int type = (newCount == 0 ? ETAG_ENTER : ETAG_IGNORE);
newCount += e->getCurve()->getDirection();
count = newCount;
return (newCount == 0 ? ETAG_EXIT : type);
}
int NZWindOp::getState() {
return ((count == 0) ? RSTAG_OUTSIDE : RSTAG_INSIDE);
}
/*
Methods used by ~EOWindOp~ to classify an ~Edge~.
*/
void EOWindOp::newRow() {
inside = false;
}
int EOWindOp::classify(Edge* e) {
bool newInside = !inside;
inside = newInside;
return (newInside ? ETAG_ENTER : ETAG_EXIT);
}
int EOWindOp::getState() {
return (inside ? RSTAG_INSIDE : RSTAG_OUTSIDE);
}
/*
Methods used by ~CAGOp~ to classify an ~Edge~.
*/
void CAGOp::newRow() {
inLeft = inRight = inResult = false;
}
int CAGOp::classify(Edge* e) {
if (e->getCurveTag() == CTAG_LEFT) {
inLeft = !inLeft;
} else {
inRight = !inRight;
}
bool newClass = newClassification(inLeft, inRight);
if (inResult == newClass) {
return ETAG_IGNORE;
}
inResult = newClass;
return (newClass ? ETAG_ENTER : ETAG_EXIT);
}
int CAGOp::getState() {
return (inResult ? RSTAG_INSIDE : RSTAG_OUTSIDE);
}
/*
Classification criteria for union operation.
*/
bool UnionOp::newClassification(bool inLeft, bool inRight) {
return (inLeft || inRight);
}
/*
Classification criteria for minus operation.
*/
bool MinusOp::newClassification(bool inLeft, bool inRight) {
return (inLeft && !inRight);
}
/*
Classification criteria for intersection operation.
*/
bool IntersectsOp::newClassification(bool inLeft, bool inRight) {
return (inLeft && inRight);
}
/*
Classification criteria for xor operation.
*/
bool XorOp::newClassification(bool inLeft, bool inRight) {
return (inLeft != inRight);
}
}