#pragma once
#include <config.h>
#include <iostream>
#include <fstream>
#ifdef HAVE_ZLIB
#include <foreign/zstr/zstr.hpp>
#endif
#ifdef HAVE_FOX
#include <utils/foxtools/MFXWorkerThread.h>
#endif
#include <utils/common/MapMatcher.h>
#include <utils/geom/PositionVector.h>
#include <utils/router/ReversedEdge.h>
#include <utils/router/SUMOAbstractRouter.h>
#define UNREACHABLE (std::numeric_limits<double>::max() / 1000.0)
template<class E, class V>
class AbstractLookupTable {
public:
virtual ~AbstractLookupTable() {}
virtual double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const = 0;
virtual bool consistent() const = 0;
};
template<class E, class V>
class FullLookupTable : public AbstractLookupTable<E, V> {
public:
FullLookupTable(const std::string& filename, const int size) :
myTable(size) {
std::ifstream strm(filename.c_str());
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
double val;
strm >> val;
myTable[i].push_back(val);
}
}
}
virtual ~FullLookupTable() {
}
double lowerBound(const E* from, const E* to, double , double speedFactor, double , double ) const {
return myTable[from->getNumericalID()][to->getNumericalID()] / speedFactor;
}
bool consistent() const {
return true;
}
private:
std::vector<std::vector<double> > myTable;
};
template<class E, class V, class M>
class LandmarkLookupTable : public AbstractLookupTable<E, V> {
public:
LandmarkLookupTable(const std::string& filename, const std::vector<E*>& edges, SUMOAbstractRouter<E, V>* router,
SUMOAbstractRouter<ReversedEdge<E, V>, V>* reverseRouter,
const V* defaultVehicle, const std::string& outfile, const int maxNumThreads, M* mapMatcher) {
myFirstNonInternal = -1;
std::map<std::string, int> numericID;
bool haveTaz = false;
for (E* e : edges) {
if (!e->isInternal()) {
if (myFirstNonInternal == -1) {
myFirstNonInternal = e->getNumericalID();
}
if (e->isTazConnector()) {
haveTaz = true;
}
numericID[e->getID()] = e->getNumericalID() - myFirstNonInternal;
}
}
#ifdef HAVE_ZLIB
zstr::ifstream strm(filename.c_str(), std::fstream::in | std::fstream::binary);
#else
std::ifstream strm(filename.c_str());
#endif
if (!strm.good()) {
throw ProcessError(TLF("Could not load landmark-lookup-table from '%'.", filename));
}
std::string line;
std::vector<const E*> landmarks;
int numLandMarks = 0;
bool haveData = false;
while (std::getline(strm, line)) {
if (line == "") {
break;
}
StringTokenizer st(line);
if (st.size() == 1) {
const std::string lm = st.get(0);
if (myLandmarks.count(lm) != 0) {
throw ProcessError(TLF("Duplicate edge '%' in landmark file.", lm));
}
const auto& it = numericID.find(lm);
if (it == numericID.end()) {
throw ProcessError(TLF("Landmark edge '%' does not exist in the network.", lm));
}
myLandmarks[lm] = numLandMarks++;
myFromLandmarkDists.push_back(std::vector<double>(0));
myToLandmarkDists.push_back(std::vector<double>(0));
landmarks.push_back(edges[it->second + myFirstNonInternal]);
} else if (st.size() == 2) {
try {
std::string lonStr = st.get(0);
if (!lonStr.empty() && lonStr.back() == ',') {
lonStr = lonStr.substr(0, lonStr.size() - 1);
}
double lon = StringUtils::toDouble(lonStr);
double lat = StringUtils::toDouble(st.get(1));
std::vector<const E*> mapped;
bool ok;
mapMatcher->parseGeoEdges(PositionVector({Position(lon, lat)}), true, SVC_IGNORING, mapped, "LMLT", false, ok, true);
if (mapped.size() != 1) {
throw ProcessError(TLF("Invalid coordinate in landmark file, could not find edge at '%'", line));
}
std::string lm = mapped.front()->getID();
const auto& it = numericID.find(lm);
if (it == numericID.end()) {
throw ProcessError(TLF("Landmark edge '%' does not exist in the network.", lm));
}
myLandmarks[lm] = numLandMarks++;
myFromLandmarkDists.push_back(std::vector<double>(0));
myToLandmarkDists.push_back(std::vector<double>(0));
landmarks.push_back(edges[it->second + myFirstNonInternal]);
} catch (const NumberFormatException&) {
throw ProcessError(TLF("Broken landmark file, could not parse '%' as coordinates.", line));
}
} else if (st.size() == 4) {
const std::string lm = st.get(0);
const std::string edge = st.get(1);
if (numericID[edge] != (int)myFromLandmarkDists[myLandmarks[lm]].size()) {
throw ProcessError(TLF("Unknown or unordered edge '%' in landmark file.", edge));
}
const double distFrom = StringUtils::toDouble(st.get(2));
const double distTo = StringUtils::toDouble(st.get(3));
myFromLandmarkDists[myLandmarks[lm]].push_back(distFrom);
myToLandmarkDists[myLandmarks[lm]].push_back(distTo);
haveData = true;
} else {
const std::string edge = st.get(0);
if ((int)st.size() != 2 * numLandMarks + 1) {
throw ProcessError(TLF("Broken landmark file, unexpected number of entries (%) for edge '%'.", st.size() - 1, edge));
}
if (numericID[edge] != (int)myFromLandmarkDists[0].size()) {
throw ProcessError(TLF("Unknown or unordered edge '%' in landmark file.", edge));
}
for (int i = 0; i < numLandMarks; i++) {
const double distFrom = StringUtils::toDouble(st.get(2 * i + 1));
const double distTo = StringUtils::toDouble(st.get(2 * i + 2));
myFromLandmarkDists[i].push_back(distFrom);
myToLandmarkDists[i].push_back(distTo);
}
haveData = true;
}
}
if (myLandmarks.empty()) {
WRITE_WARNINGF("No landmarks in '%', falling back to standard A*.", filename);
return;
}
#ifdef HAVE_FOX
MFXWorkerThread::Pool threadPool;
std::vector<RoutingTask*> currentTasks;
#endif
if (!haveData) {
WRITE_MESSAGE(TL("Calculating new lookup table."));
}
for (int i = 0; i < numLandMarks; ++i) {
if ((int)myFromLandmarkDists[i].size() != (int)edges.size() - myFirstNonInternal) {
const E* const landmark = landmarks[i];
if (haveData) {
if (myFromLandmarkDists[i].empty()) {
WRITE_WARNINGF(TL("No lookup table for landmark edge '%', recalculating."), landmark->getID());
} else {
const std::string tazWarning = haveTaz ? " Make sure that any used taz or junction-taz definitions are loaded when generating the table" : "";
throw ProcessError(TLF("Not all network edges were found in the lookup table '%' for landmark edge '%'.%", filename, landmark->getID(), tazWarning));
}
}
#ifdef HAVE_FOX
if (maxNumThreads > 0) {
const double lmCost = router->recomputeCosts({landmark}, defaultVehicle, 0);
router->setAutoBulkMode(true);
if (threadPool.size() == 0) {
if (reverseRouter == nullptr) {
std::vector<const E*> route;
router->compute(landmark, landmark, defaultVehicle, 0, route, true);
} else {
reverseRouter->setAutoBulkMode(true);
}
while ((int)threadPool.size() < maxNumThreads) {
auto revClone = reverseRouter == nullptr ? nullptr : reverseRouter->clone();
new WorkerThread(threadPool, router->clone(), revClone, defaultVehicle);
}
}
for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
const E* const edge = edges[j];
if (landmark != edge) {
const double sourceDestCost = lmCost + router->recomputeCosts({edge}, defaultVehicle, 0);
currentTasks.push_back(new RoutingTask(landmark, edge, sourceDestCost));
threadPool.add(currentTasks.back(), i % maxNumThreads);
}
}
}
#else
UNUSED_PARAMETER(reverseRouter);
#endif
}
}
#ifdef HAVE_FOX
threadPool.waitAll(false);
int taskIndex = 0;
#endif
for (int i = 0; i < numLandMarks; ++i) {
if ((int)myFromLandmarkDists[i].size() != (int)edges.size() - myFirstNonInternal) {
const E* landmark = landmarks[i];
const double lmCost = router->recomputeCosts({landmark}, defaultVehicle, 0);
int unreachableFrom = 0;
int unreachableTo = 0;
for (int j = (int)myFromLandmarkDists[i].size() + myFirstNonInternal; j < (int)edges.size(); ++j) {
const E* const edge = edges[j];
double distFrom = -1;
double distTo = -1;
if (landmark == edge) {
distFrom = 0;
distTo = 0;
} else {
if (maxNumThreads > 0) {
#ifdef HAVE_FOX
distFrom = currentTasks[taskIndex]->getFromCost();
distTo = currentTasks[taskIndex]->getToCost();
delete currentTasks[taskIndex++];
#endif
} else {
const double sourceDestCost = lmCost + router->recomputeCosts({edge}, defaultVehicle, 0);
std::vector<const E*> route;
std::vector<const ReversedEdge<E, V>*> reversedRoute;
if (edge->getPredecessors().size() > 0 && landmark->getSuccessors().size() > 0) {
if (router->compute(landmark, edge, defaultVehicle, 0, route, true)) {
distFrom = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
route.clear();
}
}
if (landmark->getPredecessors().size() > 0 && edge->getSuccessors().size() > 0) {
if (router->compute(edge, landmark, defaultVehicle, 0, route, true)) {
distTo = MAX2(0.0, router->recomputeCosts(route, defaultVehicle, 0) - sourceDestCost);
route.clear();
}
}
}
}
myFromLandmarkDists[i].push_back(distFrom);
myToLandmarkDists[i].push_back(distTo);
if (!edge->isTazConnector()) {
if (distFrom == -1) {
unreachableFrom++;
}
if (distTo == -1) {
unreachableTo++;
}
}
}
if (unreachableFrom > 0 || unreachableTo > 0) {
WRITE_WARNINGF(TL("Landmark % is not reachable from % edges and is unable to reach % out of % total edges."),
landmark->getID(), unreachableFrom, unreachableTo, numericID.size());
}
}
}
if (!outfile.empty()) {
std::ostream* ostrm = nullptr;
#ifdef HAVE_ZLIB
if (StringUtils::endsWith(outfile, ".gz")) {
ostrm = new zstr::ofstream(outfile.c_str(), std::ios_base::out);
} else {
#endif
ostrm = new std::ofstream(outfile.c_str());
#ifdef HAVE_ZLIB
}
#endif
if (!ostrm->good()) {
delete ostrm;
throw ProcessError(TLF("Could not open file '%' for writing.", outfile));
}
WRITE_MESSAGEF(TL("Saving new matrix to '%'."), outfile);
for (int i = 0; i < numLandMarks; ++i) {
(*ostrm) << getLandmark(i) << "\n";
}
for (int j = 0; j < (int)myFromLandmarkDists[0].size(); ++j) {
(*ostrm) << edges[j + myFirstNonInternal]->getID();
for (int i = 0; i < numLandMarks; ++i) {
(*ostrm) << " " << myFromLandmarkDists[i][j] << " " << myToLandmarkDists[i][j];
}
(*ostrm) << "\n";
}
delete ostrm;
}
}
virtual ~LandmarkLookupTable() {
}
double lowerBound(const E* from, const E* to, double speed, double speedFactor, double fromEffort, double toEffort) const {
double result = from->getDistanceTo(to) / speed;
#ifdef ASTAR_DEBUG_LOOKUPTABLE
if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM) {
std::cout << " lowerBound to=" << to->getID() << " result1=" << result << "\n";
}
#endif
for (int i = 0; i < (int)myLandmarks.size(); ++i) {
const double fl = myToLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
const double tl = myToLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
if (fl >= 0 && tl >= 0) {
const double bound = (fl - tl - toEffort) / speedFactor;
#ifdef ASTAR_DEBUG_LOOKUPTABLE
if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
std::cout << " landmarkTo=" << getLandmark(i) << " result2=" << bound
<< " fl=" << fl << " tl=" << tl << "\n";
}
#endif
result = MAX2(result, bound);
}
const double lt = myFromLandmarkDists[i][to->getNumericalID() - myFirstNonInternal];
const double lf = myFromLandmarkDists[i][from->getNumericalID() - myFirstNonInternal];
if (lt >= 0 && lf >= 0) {
const double bound = (lt - lf - fromEffort) / speedFactor;
#ifdef ASTAR_DEBUG_LOOKUPTABLE
if (from->getID() == ASTAR_DEBUG_LOOKUPTABLE_FROM && result < bound) {
std::cout << " landmarkFrom=" << getLandmark(i) << " result3=" << bound
<< " lt=" << lt << " lf=" << lf << "\n";
}
#endif
result = MAX2(result, bound);
}
if ((tl >= 0 && fl < 0)
|| (lf >= 0 && lt < 0)) {
#ifdef ASTAR_DEBUG_UNREACHABLE
std::cout << " unreachable: from=" << from->getID() << " to=" << to->getID() << " landmark=" << getLandmark(i) << " "
<< ((tl >= 0 && fl < 0) ? " (toLandmark)" : " (fromLandmark)")
<< " fl=" << fl << " tl=" << tl << " lt=" << lt << " lf=" << lf
<< "\n";
#endif
return UNREACHABLE;
}
}
return result;
}
bool consistent() const {
return false;
}
private:
std::map<std::string, int> myLandmarks;
std::vector<std::vector<double> > myFromLandmarkDists;
std::vector<std::vector<double> > myToLandmarkDists;
int myFirstNonInternal;
#ifdef HAVE_FOX
private:
class WorkerThread : public MFXWorkerThread {
public:
WorkerThread(MFXWorkerThread::Pool& pool,
SUMOAbstractRouter<E, V>* router,
SUMOAbstractRouter<ReversedEdge<E, V>, V>* reverseRouter, const V* vehicle)
: MFXWorkerThread(pool), myRouter(router), myReversedRouter(reverseRouter), myVehicle(vehicle) {}
virtual ~WorkerThread() {
delete myRouter;
delete myReversedRouter;
}
const std::pair<double, double> compute(const E* src, const E* dest, const double costOff) {
double fromResult = -1.;
if (myRouter->compute(src, dest, myVehicle, 0, myRoute, true)) {
fromResult = MAX2(0.0, myRouter->recomputeCosts(myRoute, myVehicle, 0) + costOff);
myRoute.clear();
}
double toResult = -1.;
if (myReversedRouter != nullptr) {
if (myReversedRouter->compute(src->getReversedRoutingEdge(), dest->getReversedRoutingEdge(), myVehicle, 0, myReversedRoute, true)) {
toResult = MAX2(0.0, myReversedRouter->recomputeCosts(myReversedRoute, myVehicle, 0) + costOff);
myReversedRoute.clear();
}
} else {
if (myRouter->compute(dest, src, myVehicle, 0, myRoute, true)) {
toResult = MAX2(0.0, myRouter->recomputeCosts(myRoute, myVehicle, 0) + costOff);
myRoute.clear();
}
}
return std::make_pair(fromResult, toResult);
}
private:
SUMOAbstractRouter<E, V>* myRouter;
SUMOAbstractRouter<ReversedEdge<E, V>, V>* myReversedRouter;
const V* myVehicle;
std::vector<const E*> myRoute;
std::vector<const ReversedEdge<E, V>*> myReversedRoute;
};
class RoutingTask : public MFXWorkerThread::Task {
public:
RoutingTask(const E* src, const E* dest, const double costOff)
: mySrc(src), myDest(dest), myCostOff(-costOff) {}
void run(MFXWorkerThread* context) {
myCost = ((WorkerThread*)context)->compute(mySrc, myDest, myCostOff);
}
double getFromCost() {
return myCost.first;
}
double getToCost() {
return myCost.second;
}
private:
const E* const mySrc;
const E* const myDest;
const double myCostOff;
std::pair<double, double> myCost;
private:
RoutingTask& operator=(const RoutingTask&) = delete;
};
private:
#endif
std::string getLandmark(int i) const {
for (std::map<std::string, int>::const_iterator it = myLandmarks.begin(); it != myLandmarks.end(); ++it) {
if (it->second == i) {
return it->first;
}
}
return "";
}
};