Linestring is generalized by an untuned (Ramer-)Douglas-Peucker
algorithm. Distance computation is still a naive implementation and can be further sped up if necessary
This commit is contained in:
		
							parent
							
								
									14c999fc82
								
							
						
					
					
						commit
						99641bd55c
					
				
							
								
								
									
										135
									
								
								Algorithms/DouglasPeucker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								Algorithms/DouglasPeucker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | ||||
| /*
 | ||||
|     open source routing machine | ||||
|     Copyright (C) Dennis Luxen, others 2010 | ||||
| 
 | ||||
| This program is free software; you can redistribute it and/or modify | ||||
| it under the terms of the GNU AFFERO General Public License as published by | ||||
| the Free Software Foundation; either version 3 of the License, or | ||||
| any later version. | ||||
| 
 | ||||
| This program 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 Affero General Public License | ||||
| along with this program; if not, write to the Free Software | ||||
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| or see http://www.gnu.org/licenses/agpl.txt.
 | ||||
|  */ | ||||
| 
 | ||||
| #ifndef DOUGLASPEUCKER_H_ | ||||
| #define DOUGLASPEUCKER_H_ | ||||
| 
 | ||||
| #include <cfloat> | ||||
| #include <stack> | ||||
| 
 | ||||
| /*This class object computes the bitvector of indicating generalized input points
 | ||||
|  * according to the (Ramer-)Douglas-Peucker algorithm. | ||||
|  * | ||||
|  * Input is vector of pairs. Each pair consists of the point information and a bit | ||||
|  * indicating if the points is present in the generalization. | ||||
|  * Note: points may also be pre-selected*/ | ||||
| 
 | ||||
| //These thresholds are more or less heuristically chosen.
 | ||||
| static double DouglasPeuckerThresholds[19] = { 10240000., 512., 2560000., 1280000., 640000., 320000., 160000., 80000., 40000., 20000., 10000., 5000., 2400., 1200., 200, 16, 6, 3., 1. }; | ||||
| 
 | ||||
| template<class PointT> | ||||
| class DouglasPeucker { | ||||
| private: | ||||
|     typedef std::pair<std::size_t, std::size_t> PairOfPoints; | ||||
|     //Stack to simulate the recursion
 | ||||
|     std::stack<PairOfPoints > recursionStack; | ||||
| 
 | ||||
|     double ComputeDistance(const _Coordinate& inputPoint, const _Coordinate& source, const _Coordinate& target) { | ||||
|         double r; | ||||
|         const double x = (double)inputPoint.lat; | ||||
|         const double y = (double)inputPoint.lon; | ||||
|         const double a = (double)source.lat; | ||||
|         const double b = (double)source.lon; | ||||
|         const double c = (double)target.lat; | ||||
|         const double d = (double)target.lon; | ||||
|         double p,q,mX,nY; | ||||
|         if(c != a) { | ||||
|             const double m = (d-b)/(c-a); // slope
 | ||||
|             // Projection of (x,y) on line joining (a,b) and (c,d)
 | ||||
|             p = ((x + (m*y)) + (m*m*a - m*b))/(1 + m*m); | ||||
|             q = b + m*(p - a); | ||||
|         } else { | ||||
|             p = c; | ||||
|             q = y; | ||||
|         } | ||||
|         nY = (d*p - c*q)/(a*d - b*c); | ||||
|         mX = (p - nY*a)/c;// These values are actually n/m+n and m/m+n , we neednot calculate the values of m an n as we are just interested in the ratio
 | ||||
|         r = mX; | ||||
|         if(r<=0){ | ||||
|             return ((b - y)*(b - y) + (a - x)*(a - x)); | ||||
|         } | ||||
|         else if(r >= 1){ | ||||
|             return ((d - y)*(d - y) + (c - x)*(c - x)); | ||||
| 
 | ||||
|         } | ||||
|         // point lies in between
 | ||||
|         return (p-x)*(p-x) + (q-y)*(q-y); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
|     void Run(std::vector<PointT> & inputVector, const unsigned zoomLevel) { | ||||
|         { | ||||
|             assert(zoomLevel < 19); | ||||
|             assert(1 < inputVector.size()); | ||||
|             std::size_t leftBorderOfRange = 0; | ||||
|             std::size_t rightBorderOfRange = 1; | ||||
|             //Sweep linerarily over array and identify those ranges that need to be checked
 | ||||
|             do { | ||||
|                 assert(inputVector[leftBorderOfRange].necessary); | ||||
|                 assert(inputVector[inputVector.size()-1].necessary); | ||||
| 
 | ||||
|                 if(inputVector[rightBorderOfRange].necessary) { | ||||
|                     recursionStack.push(std::make_pair(leftBorderOfRange, rightBorderOfRange)); | ||||
|                     leftBorderOfRange = rightBorderOfRange; | ||||
|                 } | ||||
|                 ++rightBorderOfRange; | ||||
|             } while( rightBorderOfRange < inputVector.size()); | ||||
|         } | ||||
|         while(!recursionStack.empty()) { | ||||
|             //pop next element
 | ||||
|             const PairOfPoints pair = recursionStack.top(); | ||||
|             recursionStack.pop(); | ||||
|             assert(inputVector[pair.first].necessary); | ||||
|             assert(inputVector[pair.second].necessary); | ||||
|             assert(pair.second < inputVector.size()); | ||||
|             assert(pair.first < pair.second); | ||||
|             double maxDistance = -DBL_MAX; | ||||
|             std::size_t indexOfFarthestElement = pair.second; | ||||
|             INFO("[" << recursionStack.size() << "] left: " << pair.first << ", right: " << pair.second); | ||||
|             //find index idx of element with maxDistance
 | ||||
|             for(std::size_t i = pair.first+1; i < pair.second; ++i){ | ||||
|                 double distance = std::fabs(ComputeDistance(inputVector[i].location, inputVector[pair.first].location, inputVector[pair.second].location)); | ||||
|                 if(distance > DouglasPeuckerThresholds[zoomLevel] && distance > maxDistance) { | ||||
|                     indexOfFarthestElement = i; | ||||
|                     maxDistance = distance; | ||||
|                 } | ||||
|             } | ||||
|             INFO("distance: " << maxDistance << ", recurse: " << (maxDistance > DouglasPeuckerThresholds[zoomLevel] ? "yes" : "no") << ", index: " << indexOfFarthestElement << ", threshold: " << DouglasPeuckerThresholds[zoomLevel] << ", z: " << zoomLevel); | ||||
|             if (maxDistance > DouglasPeuckerThresholds[zoomLevel]) { | ||||
|                 //  mark idx as necessary
 | ||||
|                 inputVector[indexOfFarthestElement].necessary = true; | ||||
|                 INFO("1 < " << indexOfFarthestElement << " - " << pair.first << "=" << (1 > indexOfFarthestElement - pair.first ? "yes" : "no")); | ||||
|                 if (1 < indexOfFarthestElement - pair.first) { | ||||
|                     recursionStack.push(std::make_pair(pair.first, indexOfFarthestElement) ); | ||||
|                 } | ||||
|                 if (1 < pair.second - indexOfFarthestElement) | ||||
|                     recursionStack.push(std::make_pair(indexOfFarthestElement, pair.second) ); | ||||
|             } | ||||
|         } | ||||
|         unsigned necessaryCount = 0; | ||||
|         BOOST_FOREACH(PointT & segment, inputVector) { | ||||
|             if(segment.necessary) | ||||
|                 ++necessaryCount; | ||||
|         } | ||||
|         INFO("[" << necessaryCount << "|" << inputVector.size() << "] points are necessary"); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| #endif /* DOUGLASPEUCKER_H_ */ | ||||
| @ -58,18 +58,23 @@ private: | ||||
| 
 | ||||
| public: | ||||
|     inline void printEncodedString(const vector<SegmentInformation>& polyline, string &output) { | ||||
|         vector<int> deltaNumbers(2*polyline.size()); | ||||
|         vector<int> deltaNumbers; | ||||
|         output += "\""; | ||||
|         if(!polyline.empty()) { | ||||
|             deltaNumbers[0] = polyline[0].location.lat; | ||||
|             deltaNumbers[1] = polyline[0].location.lon; | ||||
|             _Coordinate lastCoordinate = polyline[0].location; | ||||
|             deltaNumbers.push_back( lastCoordinate.lat ); | ||||
|             deltaNumbers.push_back( lastCoordinate.lon ); | ||||
|             for(unsigned i = 1; i < polyline.size(); ++i) { | ||||
|                 deltaNumbers[(2*i)]   = (polyline[i].location.lat - polyline[i-1].location.lat); | ||||
|                 deltaNumbers[(2*i)+1] = (polyline[i].location.lon - polyline[i-1].location.lon); | ||||
|                 if(!polyline[i].necessary) | ||||
|                     continue; | ||||
|                 deltaNumbers.push_back(polyline[i].location.lat - lastCoordinate.lat); | ||||
|                 deltaNumbers.push_back(polyline[i].location.lon - lastCoordinate.lon); | ||||
|                 lastCoordinate = polyline[i].location; | ||||
|             } | ||||
|             encodeVectorSignedNumber(deltaNumbers, output); | ||||
|         } | ||||
|         output += "\""; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 	inline void printEncodedString(const vector<_Coordinate>& polyline, string &output) { | ||||
| @ -109,6 +114,8 @@ public: | ||||
|         output += "["; | ||||
|         string tmp; | ||||
|         for(unsigned i = 0; i < polyline.size(); i++) { | ||||
|             if(!polyline[i].necessary) | ||||
|                 continue; | ||||
|             convertInternalLatLonToString(polyline[i].location.lat, tmp); | ||||
|             output += "["; | ||||
|             output += tmp; | ||||
|  | ||||
| @ -18,9 +18,6 @@ | ||||
|  or see http://www.gnu.org/licenses/agpl.txt.
 | ||||
|  */ | ||||
| 
 | ||||
| #include <boost/foreach.hpp> | ||||
| 
 | ||||
| #include "../typedefs.h" | ||||
| #include "DescriptionFactory.h" | ||||
| 
 | ||||
| DescriptionFactory::DescriptionFactory() { } | ||||
| @ -38,25 +35,11 @@ void DescriptionFactory::SetStartSegment(const PhantomNode & _startPhantom) { | ||||
| 
 | ||||
| void DescriptionFactory::SetEndSegment(const PhantomNode & _targetPhantom) { | ||||
|     targetPhantom = _targetPhantom; | ||||
|     AppendSegment(_targetPhantom.location, _PathData(0, _targetPhantom.nodeBasedEdgeNameID, 0, _targetPhantom.weight1)); | ||||
|     pathDescription.push_back(SegmentInformation(_targetPhantom.location, _targetPhantom.nodeBasedEdgeNameID, 0, _targetPhantom.weight1, 0, true) ); | ||||
| } | ||||
| 
 | ||||
| void DescriptionFactory::AppendSegment(const _Coordinate & coordinate, const _PathData & data ) { | ||||
|     //Segment information has following format:
 | ||||
|     //["instruction","streetname",length,position,time,"length","earth_direction",azimuth]
 | ||||
|     //Example: ["Turn left","High Street",200,4,10,"200m","NE",22.5]
 | ||||
|     //See also: http://developers.cloudmade.com/wiki/navengine/JSON_format
 | ||||
| //    (_Coordinate & loc, NodeID nam, unsigned len, unsigned dur, short tInstr)
 | ||||
| 
 | ||||
|     //Is a new instruction necessary?
 | ||||
|     //yes: data.turnInstruction != 0;
 | ||||
|     //no: data.turnInstruction == 0;
 | ||||
| inline void DescriptionFactory::AppendSegment(const _Coordinate & coordinate, const _PathData & data ) { | ||||
|     pathDescription.push_back(SegmentInformation(coordinate, data.nameID, 0, data.durationOfSegment, data.turnInstruction) ); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void DescriptionFactory::AppendRouteInstructionString(std::string & output) { | ||||
|     output += "[\"Turn left\",\"High Street\",200,0,10,\"200m\",\"NE\",22.5]"; | ||||
| } | ||||
| 
 | ||||
| void DescriptionFactory::AppendEncodedPolylineString(std::string & output, bool isEncoded) { | ||||
| @ -74,7 +57,7 @@ void DescriptionFactory::AppendUnencodedPolylineString(std::string &output) { | ||||
|     pc.printUnencodedString(pathDescription, output); | ||||
| } | ||||
| 
 | ||||
| unsigned DescriptionFactory::Run() { | ||||
| unsigned DescriptionFactory::Run(const unsigned zoomLevel) { | ||||
|     if(0 == pathDescription.size()) | ||||
|         return 0; | ||||
| 
 | ||||
| @ -105,10 +88,7 @@ unsigned DescriptionFactory::Run() { | ||||
|     } | ||||
| 
 | ||||
|     //Generalize poly line
 | ||||
|     BOOST_FOREACH(SegmentInformation & segment, pathDescription) { | ||||
|         //TODO: Replace me by real generalization
 | ||||
|         segment.necessary = true; | ||||
|     } | ||||
|     dp.Run(pathDescription, zoomLevel); | ||||
| 
 | ||||
|     //fix what needs to be fixed else
 | ||||
|     return entireLength; | ||||
|  | ||||
| @ -23,6 +23,8 @@ | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "../typedefs.h" | ||||
| #include "../Algorithms/DouglasPeucker.h" | ||||
| #include "../Algorithms/PolylineCompressor.h" | ||||
| #include "../DataStructures/ExtractorStructs.h" | ||||
| #include "../DataStructures/SegmentInformation.h" | ||||
| @ -31,6 +33,7 @@ | ||||
|  *  and produces the description plus the encoded polyline */ | ||||
| 
 | ||||
| class DescriptionFactory { | ||||
|     DouglasPeucker<SegmentInformation> dp; | ||||
|     PolylineCompressor pc; | ||||
|     PhantomNode startPhantom, targetPhantom; | ||||
| public: | ||||
| @ -42,11 +45,10 @@ public: | ||||
|     void AppendEncodedPolylineString(std::string &output); | ||||
|     void AppendUnencodedPolylineString(std::string &output); | ||||
|     void AppendSegment(const _Coordinate & coordinate, const _PathData & data); | ||||
|     void AppendRouteInstructionString(std::string & output); | ||||
|     void SetStartSegment(const PhantomNode & startPhantom); | ||||
|     void SetEndSegment(const PhantomNode & startPhantom); | ||||
|     void AppendEncodedPolylineString(std::string & output, bool isEncoded); | ||||
|     unsigned Run(); | ||||
|     unsigned Run(const unsigned zoomLevel); | ||||
| 
 | ||||
| //    static inline void getDirectionOfInstruction(double angle, DirectionOfInstruction & dirInst) {
 | ||||
| //        if(angle >= 23 && angle < 67) {
 | ||||
|  | ||||
| @ -44,8 +44,6 @@ public: | ||||
| 
 | ||||
|     void Run(http::Reply & reply, RawRouteData &rawRoute, PhantomNodes &phantomNodes, SearchEngineT &sEngine, unsigned durationOfTrip) { | ||||
|         WriteHeaderToOutput(reply.content); | ||||
|         //We do not need to do much, if there is no route ;-)
 | ||||
| 
 | ||||
|         if(durationOfTrip != INT_MAX && rawRoute.routeSegments.size() > 0) { | ||||
|             summary.startName = sEngine.GetEscapedNameForNameID(phantomNodes.startPhantom.nodeBasedEdgeNameID); | ||||
|             descriptionFactory.SetStartSegment(phantomNodes.startPhantom); | ||||
| @ -62,12 +60,12 @@ public: | ||||
|             } | ||||
|             descriptionFactory.SetEndSegment(phantomNodes.targetPhantom); | ||||
|         } else { | ||||
|             //no route found
 | ||||
|             //We do not need to do much, if there is no route ;-)
 | ||||
|             reply.content += "207," | ||||
|                     "\"status_message\": \"Cannot find route between points\","; | ||||
|         } | ||||
| 
 | ||||
|         summary.BuildDurationAndLengthStrings(descriptionFactory.Run(), durationOfTrip); | ||||
|         summary.BuildDurationAndLengthStrings(descriptionFactory.Run(config.z), durationOfTrip); | ||||
| 
 | ||||
|         reply.content += "\"route_summary\": {" | ||||
|                 "\"total_distance\":"; | ||||
| @ -94,11 +92,14 @@ public: | ||||
|         reply.content += "," | ||||
|                 "\"route_instructions\": ["; | ||||
|         if(config.instructions) { | ||||
|             //Segment information has following format:
 | ||||
|             //["instruction","streetname",length,position,time,"length","earth_direction",azimuth]
 | ||||
|             //Example: ["Turn left","High Street",200,4,10,"200m","NE",22.5]
 | ||||
|             //See also: http://developers.cloudmade.com/wiki/navengine/JSON_format
 | ||||
|             unsigned prefixSumOfNecessarySegments = 0; | ||||
|             std::string tmpDist, tmpLength, tmp; | ||||
|             //Fetch data from Factory and generate a string from it.
 | ||||
|             BOOST_FOREACH(SegmentInformation segment, descriptionFactory.pathDescription) { | ||||
|                 //["instruction","streetname",length,position,time,"length","earth_direction",azimuth]
 | ||||
|                 if(0 != segment.turnInstruction) { | ||||
|                     if(0 != prefixSumOfNecessarySegments) | ||||
|                         reply.content += ","; | ||||
| @ -123,8 +124,6 @@ public: | ||||
|                 if(segment.necessary) | ||||
|                     ++prefixSumOfNecessarySegments; | ||||
|             } | ||||
|             //            descriptionFactory.AppendRouteInstructionString(reply.content);
 | ||||
| 
 | ||||
|         } | ||||
|         reply.content += "],"; | ||||
|         //list all viapoints so that the client may display it
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user