template<> NValue NValue::callUnary<FUNC_VOLT_POLYGONFROMTEXT>() const { bool is_shell = true; if (isNull()) { return NValue::getNullValue(VALUE_TYPE_GEOGRAPHY); } int32_t textLength; const char* textData = getObject_withoutNull(&textLength); const std::string wkt(textData, textLength); // Discard whitespace, but return commas or parentheses as tokens Tokenizer tokens(wkt, boost::char_separator<char>(" \f\n\r\t\v", ",()")); Tokenizer::iterator it = tokens.begin(); Tokenizer::iterator end = tokens.end(); if (! boost::iequals(*it, "polygon")) { throwInvalidWktPoly("does not start with POLYGON keyword"); } ++it; if (! boost::iequals(*it, "(")) { throwInvalidWktPoly("missing left parenthesis after POLYGON keyword"); } ++it; std::size_t length = Polygon::serializedLengthNoLoops(); std::vector<std::unique_ptr<S2Loop> > loops; while (it != end) { loops.push_back(std::unique_ptr<S2Loop>(new S2Loop())); readLoop(is_shell, wkt, it, end, loops.back().get()); // Only the first loop is a shell. is_shell = false; length += Loop::serializedLength(loops.back()->num_vertices()); if (*it == ",") { ++it; } else if (*it == ")") { ++it; break; } else { throwInvalidWktPoly("unexpected token: '" + (*it) + "'"); } } if (it != end) { // extra stuff after input throwInvalidWktPoly("unrecognized input after WKT: '" + (*it) + "'"); } NValue nval = ValueFactory::getUninitializedTempGeographyValue(length); char* storage = const_cast<char*>(ValuePeeker::peekObjectValue(nval)); Polygon poly; poly.init(&loops); // polygon takes ownership of loops here. SimpleOutputSerializer output(storage, length); poly.saveToBuffer(output); return nval; }
static NValue polygonFromText(const std::string &wkt, bool doValidation) { // Discard whitespace, but return commas or parentheses as tokens Tokenizer tokens(wkt, boost::char_separator<char>(" \f\n\r\t\v", ",()")); Tokenizer::iterator it = tokens.begin(); Tokenizer::iterator end = tokens.end(); if (! boost::iequals(*it, "polygon")) { throwInvalidWktPoly("does not start with POLYGON keyword"); } ++it; if (! boost::iequals(*it, "(")) { throwInvalidWktPoly("missing left parenthesis after POLYGON keyword"); } ++it; bool is_shell = true; std::size_t length = Polygon::serializedLengthNoLoops(); std::vector<std::unique_ptr<S2Loop> > loops; while (it != end) { loops.push_back(std::unique_ptr<S2Loop>(new S2Loop())); readLoop(is_shell, wkt, it, end, loops.back().get()); // Only the first loop is a shell. is_shell = false; length += Loop::serializedLength(loops.back()->num_vertices()); if (*it == ",") { ++it; } else if (*it == ")") { ++it; break; } else { throwInvalidWktPoly("unexpected token: '" + (*it) + "'"); } } if (it != end) { // extra stuff after input throwInvalidWktPoly("unrecognized input after WKT: '" + (*it) + "'"); } NValue nval = ValueFactory::getUninitializedTempGeographyValue(length); char* storage = const_cast<char*>(ValuePeeker::peekObjectValue(nval)); Polygon poly; poly.init(&loops); // polygon takes ownership of loops here. if (doValidation) { std::stringstream validReason; if (!poly.IsValid(&validReason) || isMultiPolygon(poly, &validReason)) { throwInvalidWktPoly(validReason.str()); } } SimpleOutputSerializer output(storage, length); poly.saveToBuffer(output); return nval; }
static GeographyPointValue::Coord stringToCoord(int pointOrPoly, const std::string& input, const std::string& val) { GeographyPointValue::Coord coord = 0.0; try { coord = boost::lexical_cast<GeographyPointValue::Coord>(val); } catch (const boost::bad_lexical_cast&) { if (pointOrPoly == POLY) { throwInvalidWktPoly("expected a number but found '" + val + "'"); } else { throwInvalidWktPoint(input); } } return coord; }
static void readLoop(bool is_shell, const std::string &wkt, Tokenizer::iterator &it, const Tokenizer::iterator &end, S2Loop *loop) { if (! boost::iequals(*it, "(")) { throwInvalidWktPoly("expected left parenthesis to start a loop"); } ++it; std::vector<S2Point> points; while (it != end && *it != ")") { GeographyPointValue::Coord lng = stringToCoord(POLY, wkt, *it); if (lng < -180 || lng > 180) { throwInvalidPolygonLongitude(*it); } ++it; GeographyPointValue::Coord lat = stringToCoord(POLY, wkt, *it); if (lat < -90 || lat > 90) { throwInvalidPolygonLatitude(*it); } ++it; // Note: This is S2. It takes latitude, longitude, not // longitude, latitude. points.push_back(S2LatLng::FromDegrees(lat, lng).ToPoint()); if (*it == ",") { ++it; // continue to next lat long pair } else if (*it != ")") { throwInvalidWktPoly("unexpected token: '" + (*it) + "'"); } } if (it == end) { // we hit the end of input before the closing parenthesis throwInvalidWktPoly("unexpected end of input"); } assert (*it == ")"); // Advance iterator to next token ++it; if (points.size() < 4) { throwInvalidWktPoly("A polygon ring must contain at least 4 points (including repeated closing vertex)"); } const S2Point& first = points.at(0); const S2Point& last = points.at(points.size() - 1); if (first != last) { throwInvalidWktPoly("A polygon ring's first vertex must be equal to its last vertex"); } // S2 format considers the closing vertex in a loop to be // implicit, while in WKT it is explicit. Remove the closing // vertex here to reflect this. points.pop_back(); // The first is a shell. All others are holes. We need to reverse // the order of the vertices for holes. if (!is_shell) { // Don't touch the first point. We don't want to // cycle the vertices. std::reverse(++(points.begin()), points.end()); } loop->Init(points); }