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: | public: | ||||||
|     inline void printEncodedString(const vector<SegmentInformation>& polyline, string &output) { |     inline void printEncodedString(const vector<SegmentInformation>& polyline, string &output) { | ||||||
|         vector<int> deltaNumbers(2*polyline.size()); |         vector<int> deltaNumbers; | ||||||
|         output += "\""; |         output += "\""; | ||||||
|         if(!polyline.empty()) { |         if(!polyline.empty()) { | ||||||
|             deltaNumbers[0] = polyline[0].location.lat; |             _Coordinate lastCoordinate = polyline[0].location; | ||||||
|             deltaNumbers[1] = polyline[0].location.lon; |             deltaNumbers.push_back( lastCoordinate.lat ); | ||||||
|  |             deltaNumbers.push_back( lastCoordinate.lon ); | ||||||
|             for(unsigned i = 1; i < polyline.size(); ++i) { |             for(unsigned i = 1; i < polyline.size(); ++i) { | ||||||
|                 deltaNumbers[(2*i)]   = (polyline[i].location.lat - polyline[i-1].location.lat); |                 if(!polyline[i].necessary) | ||||||
|                 deltaNumbers[(2*i)+1] = (polyline[i].location.lon - polyline[i-1].location.lon); |                     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); |             encodeVectorSignedNumber(deltaNumbers, output); | ||||||
|         } |         } | ||||||
|         output += "\""; |         output += "\""; | ||||||
|  | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	inline void printEncodedString(const vector<_Coordinate>& polyline, string &output) { | 	inline void printEncodedString(const vector<_Coordinate>& polyline, string &output) { | ||||||
| @ -109,6 +114,8 @@ public: | |||||||
|         output += "["; |         output += "["; | ||||||
|         string tmp; |         string tmp; | ||||||
|         for(unsigned i = 0; i < polyline.size(); i++) { |         for(unsigned i = 0; i < polyline.size(); i++) { | ||||||
|  |             if(!polyline[i].necessary) | ||||||
|  |                 continue; | ||||||
|             convertInternalLatLonToString(polyline[i].location.lat, tmp); |             convertInternalLatLonToString(polyline[i].location.lat, tmp); | ||||||
|             output += "["; |             output += "["; | ||||||
|             output += tmp; |             output += tmp; | ||||||
|  | |||||||
| @ -18,9 +18,6 @@ | |||||||
|  or see http://www.gnu.org/licenses/agpl.txt.
 |  or see http://www.gnu.org/licenses/agpl.txt.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <boost/foreach.hpp> |  | ||||||
| 
 |  | ||||||
| #include "../typedefs.h" |  | ||||||
| #include "DescriptionFactory.h" | #include "DescriptionFactory.h" | ||||||
| 
 | 
 | ||||||
| DescriptionFactory::DescriptionFactory() { } | DescriptionFactory::DescriptionFactory() { } | ||||||
| @ -38,25 +35,11 @@ void DescriptionFactory::SetStartSegment(const PhantomNode & _startPhantom) { | |||||||
| 
 | 
 | ||||||
| void DescriptionFactory::SetEndSegment(const PhantomNode & _targetPhantom) { | void DescriptionFactory::SetEndSegment(const PhantomNode & _targetPhantom) { | ||||||
|     targetPhantom = _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 ) { | inline 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;
 |  | ||||||
|     pathDescription.push_back(SegmentInformation(coordinate, data.nameID, 0, data.durationOfSegment, data.turnInstruction) ); |     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) { | void DescriptionFactory::AppendEncodedPolylineString(std::string & output, bool isEncoded) { | ||||||
| @ -74,7 +57,7 @@ void DescriptionFactory::AppendUnencodedPolylineString(std::string &output) { | |||||||
|     pc.printUnencodedString(pathDescription, output); |     pc.printUnencodedString(pathDescription, output); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| unsigned DescriptionFactory::Run() { | unsigned DescriptionFactory::Run(const unsigned zoomLevel) { | ||||||
|     if(0 == pathDescription.size()) |     if(0 == pathDescription.size()) | ||||||
|         return 0; |         return 0; | ||||||
| 
 | 
 | ||||||
| @ -105,10 +88,7 @@ unsigned DescriptionFactory::Run() { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //Generalize poly line
 |     //Generalize poly line
 | ||||||
|     BOOST_FOREACH(SegmentInformation & segment, pathDescription) { |     dp.Run(pathDescription, zoomLevel); | ||||||
|         //TODO: Replace me by real generalization
 |  | ||||||
|         segment.necessary = true; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     //fix what needs to be fixed else
 |     //fix what needs to be fixed else
 | ||||||
|     return entireLength; |     return entireLength; | ||||||
|  | |||||||
| @ -23,6 +23,8 @@ | |||||||
| 
 | 
 | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | 
 | ||||||
|  | #include "../typedefs.h" | ||||||
|  | #include "../Algorithms/DouglasPeucker.h" | ||||||
| #include "../Algorithms/PolylineCompressor.h" | #include "../Algorithms/PolylineCompressor.h" | ||||||
| #include "../DataStructures/ExtractorStructs.h" | #include "../DataStructures/ExtractorStructs.h" | ||||||
| #include "../DataStructures/SegmentInformation.h" | #include "../DataStructures/SegmentInformation.h" | ||||||
| @ -31,6 +33,7 @@ | |||||||
|  *  and produces the description plus the encoded polyline */ |  *  and produces the description plus the encoded polyline */ | ||||||
| 
 | 
 | ||||||
| class DescriptionFactory { | class DescriptionFactory { | ||||||
|  |     DouglasPeucker<SegmentInformation> dp; | ||||||
|     PolylineCompressor pc; |     PolylineCompressor pc; | ||||||
|     PhantomNode startPhantom, targetPhantom; |     PhantomNode startPhantom, targetPhantom; | ||||||
| public: | public: | ||||||
| @ -42,11 +45,10 @@ public: | |||||||
|     void AppendEncodedPolylineString(std::string &output); |     void AppendEncodedPolylineString(std::string &output); | ||||||
|     void AppendUnencodedPolylineString(std::string &output); |     void AppendUnencodedPolylineString(std::string &output); | ||||||
|     void AppendSegment(const _Coordinate & coordinate, const _PathData & data); |     void AppendSegment(const _Coordinate & coordinate, const _PathData & data); | ||||||
|     void AppendRouteInstructionString(std::string & output); |  | ||||||
|     void SetStartSegment(const PhantomNode & startPhantom); |     void SetStartSegment(const PhantomNode & startPhantom); | ||||||
|     void SetEndSegment(const PhantomNode & startPhantom); |     void SetEndSegment(const PhantomNode & startPhantom); | ||||||
|     void AppendEncodedPolylineString(std::string & output, bool isEncoded); |     void AppendEncodedPolylineString(std::string & output, bool isEncoded); | ||||||
|     unsigned Run(); |     unsigned Run(const unsigned zoomLevel); | ||||||
| 
 | 
 | ||||||
| //    static inline void getDirectionOfInstruction(double angle, DirectionOfInstruction & dirInst) {
 | //    static inline void getDirectionOfInstruction(double angle, DirectionOfInstruction & dirInst) {
 | ||||||
| //        if(angle >= 23 && angle < 67) {
 | //        if(angle >= 23 && angle < 67) {
 | ||||||
|  | |||||||
| @ -44,8 +44,6 @@ public: | |||||||
| 
 | 
 | ||||||
|     void Run(http::Reply & reply, RawRouteData &rawRoute, PhantomNodes &phantomNodes, SearchEngineT &sEngine, unsigned durationOfTrip) { |     void Run(http::Reply & reply, RawRouteData &rawRoute, PhantomNodes &phantomNodes, SearchEngineT &sEngine, unsigned durationOfTrip) { | ||||||
|         WriteHeaderToOutput(reply.content); |         WriteHeaderToOutput(reply.content); | ||||||
|         //We do not need to do much, if there is no route ;-)
 |  | ||||||
| 
 |  | ||||||
|         if(durationOfTrip != INT_MAX && rawRoute.routeSegments.size() > 0) { |         if(durationOfTrip != INT_MAX && rawRoute.routeSegments.size() > 0) { | ||||||
|             summary.startName = sEngine.GetEscapedNameForNameID(phantomNodes.startPhantom.nodeBasedEdgeNameID); |             summary.startName = sEngine.GetEscapedNameForNameID(phantomNodes.startPhantom.nodeBasedEdgeNameID); | ||||||
|             descriptionFactory.SetStartSegment(phantomNodes.startPhantom); |             descriptionFactory.SetStartSegment(phantomNodes.startPhantom); | ||||||
| @ -62,12 +60,12 @@ public: | |||||||
|             } |             } | ||||||
|             descriptionFactory.SetEndSegment(phantomNodes.targetPhantom); |             descriptionFactory.SetEndSegment(phantomNodes.targetPhantom); | ||||||
|         } else { |         } else { | ||||||
|             //no route found
 |             //We do not need to do much, if there is no route ;-)
 | ||||||
|             reply.content += "207," |             reply.content += "207," | ||||||
|                     "\"status_message\": \"Cannot find route between points\","; |                     "\"status_message\": \"Cannot find route between points\","; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         summary.BuildDurationAndLengthStrings(descriptionFactory.Run(), durationOfTrip); |         summary.BuildDurationAndLengthStrings(descriptionFactory.Run(config.z), durationOfTrip); | ||||||
| 
 | 
 | ||||||
|         reply.content += "\"route_summary\": {" |         reply.content += "\"route_summary\": {" | ||||||
|                 "\"total_distance\":"; |                 "\"total_distance\":"; | ||||||
| @ -94,11 +92,14 @@ public: | |||||||
|         reply.content += "," |         reply.content += "," | ||||||
|                 "\"route_instructions\": ["; |                 "\"route_instructions\": ["; | ||||||
|         if(config.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; |             unsigned prefixSumOfNecessarySegments = 0; | ||||||
|             std::string tmpDist, tmpLength, tmp; |             std::string tmpDist, tmpLength, tmp; | ||||||
|             //Fetch data from Factory and generate a string from it.
 |             //Fetch data from Factory and generate a string from it.
 | ||||||
|             BOOST_FOREACH(SegmentInformation segment, descriptionFactory.pathDescription) { |             BOOST_FOREACH(SegmentInformation segment, descriptionFactory.pathDescription) { | ||||||
|                 //["instruction","streetname",length,position,time,"length","earth_direction",azimuth]
 |  | ||||||
|                 if(0 != segment.turnInstruction) { |                 if(0 != segment.turnInstruction) { | ||||||
|                     if(0 != prefixSumOfNecessarySegments) |                     if(0 != prefixSumOfNecessarySegments) | ||||||
|                         reply.content += ","; |                         reply.content += ","; | ||||||
| @ -123,8 +124,6 @@ public: | |||||||
|                 if(segment.necessary) |                 if(segment.necessary) | ||||||
|                     ++prefixSumOfNecessarySegments; |                     ++prefixSumOfNecessarySegments; | ||||||
|             } |             } | ||||||
|             //            descriptionFactory.AppendRouteInstructionString(reply.content);
 |  | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
|         reply.content += "],"; |         reply.content += "],"; | ||||||
|         //list all viapoints so that the client may display it
 |         //list all viapoints so that the client may display it
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user