/* 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 JSON_DESCRIPTOR_H_ #define JSON_DESCRIPTOR_H_ #include #include #include #include "BaseDescriptor.h" #include "DescriptionFactory.h" #include "../Algorithms/ObjectToBase64.h" #include "../DataStructures/SegmentInformation.h" #include "../DataStructures/TurnInstructions.h" #include "../Util/Azimuth.h" #include "../Util/StringUtil.h" template class JSONDescriptor : public BaseDescriptor{ private: _DescriptorConfig config; DescriptionFactory descriptionFactory; DescriptionFactory alternateDescriptionFactory; _Coordinate current; unsigned numberOfEnteredRestrictedAreas; struct { int startIndex; int nameID; int leaveAtExit; } roundAbout; struct Segment { Segment() : nameID(-1), length(-1), position(-1) {} Segment(int n, int l, int p) : nameID(n), length(l), position(p) {} int nameID; int length; int position; }; std::vector shortestSegments, alternativeSegments; struct RouteNames { std::string shortestPathName1; std::string shortestPathName2; std::string alternativePathName1; std::string alternativePathName2; }; public: JSONDescriptor() : numberOfEnteredRestrictedAreas(0) {} void SetConfig(const _DescriptorConfig & c) { config = c; } void Run(http::Reply & reply, const RawRouteData &rawRoute, PhantomNodes &phantomNodes, SearchEngineT &sEngine) { WriteHeaderToOutput(reply.content); if(rawRoute.lengthOfShortestPath != INT_MAX) { descriptionFactory.SetStartSegment(phantomNodes.startPhantom); reply.content += "0," "\"status_message\": \"Found route between points\","; //Get all the coordinates for the computed route BOOST_FOREACH(const _PathData & pathData, rawRoute.computedShortestPath) { sEngine.GetCoordinatesForNodeID(pathData.node, current); descriptionFactory.AppendSegment(current, pathData ); } descriptionFactory.SetEndSegment(phantomNodes.targetPhantom); } else { //We do not need to do much, if there is no route ;-) reply.content += "207," "\"status_message\": \"Cannot find route between points\","; } descriptionFactory.Run(sEngine, config.z); reply.content += "\"route_geometry\": "; if(config.geometry) { descriptionFactory.AppendEncodedPolylineString(reply.content, config.encodeGeometry); } else { reply.content += "[]"; } reply.content += "," "\"route_instructions\": ["; numberOfEnteredRestrictedAreas = 0; if(config.instructions) { BuildTextualDescription(descriptionFactory, reply, rawRoute.lengthOfShortestPath, sEngine, shortestSegments); } else { BOOST_FOREACH(const SegmentInformation & segment, descriptionFactory.pathDescription) { TurnInstruction currentInstruction = segment.turnInstruction & TurnInstructions.InverseAccessRestrictionFlag; numberOfEnteredRestrictedAreas += (currentInstruction != segment.turnInstruction); } } reply.content += "],"; descriptionFactory.BuildRouteSummary(descriptionFactory.entireLength, rawRoute.lengthOfShortestPath - ( numberOfEnteredRestrictedAreas*TurnInstructions.AccessRestrictionPenalty)); reply.content += "\"route_summary\":"; reply.content += "{"; reply.content += "\"total_distance\":"; reply.content += descriptionFactory.summary.lengthString; reply.content += "," "\"total_time\":"; reply.content += descriptionFactory.summary.durationString; reply.content += "," "\"start_point\":\""; reply.content += sEngine.GetEscapedNameForNameID(descriptionFactory.summary.startName); reply.content += "\"," "\"end_point\":\""; reply.content += sEngine.GetEscapedNameForNameID(descriptionFactory.summary.destName); reply.content += "\""; reply.content += "}"; reply.content +=","; //only one alternative route is computed at this time, so this is hardcoded if(rawRoute.lengthOfAlternativePath != INT_MAX) { alternateDescriptionFactory.SetStartSegment(phantomNodes.startPhantom); //Get all the coordinates for the computed route BOOST_FOREACH(const _PathData & pathData, rawRoute.computedAlternativePath) { sEngine.GetCoordinatesForNodeID(pathData.node, current); alternateDescriptionFactory.AppendSegment(current, pathData ); } alternateDescriptionFactory.SetEndSegment(phantomNodes.targetPhantom); } alternateDescriptionFactory.Run(sEngine, config.z); //give an array of alternative routes reply.content += "\"alternative_geometries\": ["; if(config.geometry && INT_MAX != rawRoute.lengthOfAlternativePath) { //Generate the linestrings for each alternative alternateDescriptionFactory.AppendEncodedPolylineString(reply.content, config.encodeGeometry); } reply.content += "],"; reply.content += "\"alternative_instructions\":["; numberOfEnteredRestrictedAreas = 0; if(INT_MAX != rawRoute.lengthOfAlternativePath) { reply.content += "["; //Generate instructions for each alternative if(config.instructions) { BuildTextualDescription(alternateDescriptionFactory, reply, rawRoute.lengthOfAlternativePath, sEngine, alternativeSegments); } else { BOOST_FOREACH(const SegmentInformation & segment, alternateDescriptionFactory.pathDescription) { TurnInstruction currentInstruction = segment.turnInstruction & TurnInstructions.InverseAccessRestrictionFlag; numberOfEnteredRestrictedAreas += (currentInstruction != segment.turnInstruction); } } reply.content += "]"; } reply.content += "],"; reply.content += "\"alternative_summaries\":["; if(INT_MAX != rawRoute.lengthOfAlternativePath) { //Generate route summary (length, duration) for each alternative alternateDescriptionFactory.BuildRouteSummary(alternateDescriptionFactory.entireLength, rawRoute.lengthOfAlternativePath - ( numberOfEnteredRestrictedAreas*TurnInstructions.AccessRestrictionPenalty)); reply.content += "{"; reply.content += "\"total_distance\":"; reply.content += alternateDescriptionFactory.summary.lengthString; reply.content += "," "\"total_time\":"; reply.content += alternateDescriptionFactory.summary.durationString; reply.content += "," "\"start_point\":\""; reply.content += sEngine.GetEscapedNameForNameID(descriptionFactory.summary.startName); reply.content += "\"," "\"end_point\":\""; reply.content += sEngine.GetEscapedNameForNameID(descriptionFactory.summary.destName); reply.content += "\""; reply.content += "}"; } reply.content += "],"; //Get Names for both routes RouteNames routeNames; GetRouteNames(shortestSegments, alternativeSegments, sEngine, routeNames); reply.content += "\"route_name\":[\""; reply.content += routeNames.shortestPathName1; reply.content += "\",\""; reply.content += routeNames.shortestPathName2; reply.content += "\"]," "\"alternative_names\":["; reply.content += "[\""; reply.content += routeNames.alternativePathName1; reply.content += "\",\""; reply.content += routeNames.alternativePathName2; reply.content += "\"]"; reply.content += "],"; //list all viapoints so that the client may display it reply.content += "\"via_points\":["; std::string tmp; if(config.geometry && INT_MAX != rawRoute.lengthOfShortestPath) { for(unsigned i = 0; i < rawRoute.segmentEndCoordinates.size(); ++i) { reply.content += "["; if(rawRoute.segmentEndCoordinates[i].startPhantom.location.isSet()) convertInternalReversedCoordinateToString(rawRoute.segmentEndCoordinates[i].startPhantom.location, tmp); else convertInternalReversedCoordinateToString(rawRoute.rawViaNodeCoordinates[i], tmp); reply.content += tmp; reply.content += "],"; } reply.content += "["; if(rawRoute.segmentEndCoordinates.back().startPhantom.location.isSet()) convertInternalReversedCoordinateToString(rawRoute.segmentEndCoordinates.back().targetPhantom.location, tmp); else convertInternalReversedCoordinateToString(rawRoute.rawViaNodeCoordinates.back(), tmp); reply.content += tmp; reply.content += "]"; } reply.content += "],"; reply.content += "\"hint_data\": {"; reply.content += "\"checksum\":"; intToString(rawRoute.checkSum, tmp); reply.content += tmp; reply.content += ", \"locations\": ["; std::string hint; for(unsigned i = 0; i < rawRoute.segmentEndCoordinates.size(); ++i) { reply.content += "\""; EncodeObjectToBase64(rawRoute.segmentEndCoordinates[i].startPhantom, hint); reply.content += hint; reply.content += "\", "; } EncodeObjectToBase64(rawRoute.segmentEndCoordinates.back().targetPhantom, hint); reply.content += "\""; reply.content += hint; reply.content += "\"]"; reply.content += "},"; reply.content += "\"transactionId\": \"OSRM Routing Engine JSON Descriptor (v0.3)\""; reply.content += "}"; } void GetRouteNames(std::vector & shortestSegments, std::vector & alternativeSegments, SearchEngineT &sEngine, RouteNames & routeNames) { /*** extract names for both alternatives ***/ Segment shortestSegment1, shortestSegment2; Segment alternativeSegment1, alternativeSegment2; if(0 < shortestSegments.size()) { sort(shortestSegments.begin(), shortestSegments.end(), boost::bind(&Segment::length, _1) > boost::bind(&Segment::length, _2) ); shortestSegment1 = shortestSegments[0]; if(0 < alternativeSegments.size()) { sort(alternativeSegments.begin(), alternativeSegments.end(), boost::bind(&Segment::length, _1) > boost::bind(&Segment::length, _2) ); alternativeSegment1 = alternativeSegments[0]; } std::vector shortestDifference(shortestSegments.size()); std::vector alternativeDifference(alternativeSegments.size()); std::set_difference(shortestSegments.begin(), shortestSegments.end(), alternativeSegments.begin(), alternativeSegments.end(), shortestDifference.begin(), boost::bind(&Segment::nameID, _1) < boost::bind(&Segment::nameID, _2) ); if(0 < shortestDifference.size() ) { unsigned i = 0; while( i < shortestDifference.size() && shortestDifference[i].nameID == shortestSegments[0].nameID) { ++i; } if(i < shortestDifference.size()) { shortestSegment2 = shortestDifference[i]; } } std::set_difference(alternativeSegments.begin(), alternativeSegments.end(), shortestSegments.begin(), shortestSegments.end(), alternativeDifference.begin(), boost::bind(&Segment::nameID, _1) < boost::bind(&Segment::nameID, _2) ); if(0 < alternativeDifference.size() ) { unsigned i = 0; while( i < alternativeDifference.size() && alternativeDifference[i].nameID == alternativeSegments[0].nameID) { ++i; } if(i < alternativeDifference.size()) { alternativeSegment2 = alternativeDifference[i]; } } if(shortestSegment1.position > shortestSegment2.position) std::swap(shortestSegment1, shortestSegment2); if(alternativeSegment1.position > alternativeSegment2.position) std::swap(alternativeSegment1, alternativeSegment2); routeNames.shortestPathName1 = sEngine.GetEscapedNameForNameID(shortestSegment1.nameID); routeNames.shortestPathName2 = sEngine.GetEscapedNameForNameID(shortestSegment2.nameID); routeNames.alternativePathName1 = sEngine.GetEscapedNameForNameID(alternativeSegment1.nameID); routeNames.alternativePathName2 += sEngine.GetEscapedNameForNameID(alternativeSegment2.nameID); } } inline void WriteHeaderToOutput(std::string & output) { output += "{" "\"version\": 0.3," "\"status\":"; } inline void BuildTextualDescription(DescriptionFactory & descriptionFactory, http::Reply & reply, const int lengthOfRoute, const SearchEngineT &sEngine, std::vector & segmentVector) { //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; roundAbout.leaveAtExit = 0; roundAbout.nameID = 0; std::string tmpDist, tmpLength, tmpDuration, tmpBearing, tmpInstruction; //Fetch data from Factory and generate a string from it. BOOST_FOREACH(const SegmentInformation & segment, descriptionFactory.pathDescription) { TurnInstruction currentInstruction = segment.turnInstruction & TurnInstructions.InverseAccessRestrictionFlag; numberOfEnteredRestrictedAreas += (currentInstruction != segment.turnInstruction); if(TurnInstructions.TurnIsNecessary( currentInstruction) ) { if(TurnInstructions.EnterRoundAbout == currentInstruction) { roundAbout.nameID = segment.nameID; roundAbout.startIndex = prefixSumOfNecessarySegments; } else { if(0 != prefixSumOfNecessarySegments){ reply.content += ","; } reply.content += "[\""; if(TurnInstructions.LeaveRoundAbout == currentInstruction) { intToString(TurnInstructions.EnterRoundAbout, tmpInstruction); reply.content += tmpInstruction; reply.content += "-"; intToString(roundAbout.leaveAtExit+1, tmpInstruction); reply.content += tmpInstruction; roundAbout.leaveAtExit = 0; } else { intToString(currentInstruction, tmpInstruction); reply.content += tmpInstruction; } reply.content += "\",\""; reply.content += sEngine.GetEscapedNameForNameID(segment.nameID); reply.content += "\","; intToString(segment.length, tmpDist); reply.content += tmpDist; reply.content += ","; intToString(prefixSumOfNecessarySegments, tmpLength); reply.content += tmpLength; reply.content += ","; intToString(segment.duration/10, tmpDuration); reply.content += tmpDuration; reply.content += ",\""; intToString(segment.length, tmpLength); reply.content += tmpLength; reply.content += "m\",\""; reply.content += Azimuth::Get(segment.bearing); reply.content += "\","; intToString(round(segment.bearing), tmpBearing); reply.content += tmpBearing; reply.content += "]"; segmentVector.push_back( Segment(segment.nameID, segment.length, segmentVector.size() )); } } else if(TurnInstructions.StayOnRoundAbout == currentInstruction) { ++roundAbout.leaveAtExit; } if(segment.necessary) ++prefixSumOfNecessarySegments; } if(INT_MAX != lengthOfRoute) { reply.content += ",[\""; intToString(TurnInstructions.ReachedYourDestination, tmpInstruction); reply.content += tmpInstruction; reply.content += "\",\""; reply.content += "\","; reply.content += "0"; reply.content += ","; intToString(prefixSumOfNecessarySegments-1, tmpLength); reply.content += tmpLength; reply.content += ","; reply.content += "0"; reply.content += ",\""; reply.content += "\",\""; reply.content += Azimuth::Get(0.0); reply.content += "\","; reply.content += "0.0"; reply.content += "]"; } } }; #endif /* JSON_DESCRIPTOR_H_ */