/src/s2geometry/src/s2/s2text_format.cc
Line | Count | Source |
1 | | // Copyright Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS-IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | |
16 | | #include "s2/s2text_format.h" |
17 | | |
18 | | #include <cstddef> |
19 | | #include <memory> |
20 | | #include <string> |
21 | | #include <utility> |
22 | | #include <vector> |
23 | | |
24 | | #include "absl/container/inlined_vector.h" |
25 | | #include "absl/log/absl_check.h" |
26 | | #include "absl/status/status.h" |
27 | | #include "absl/status/statusor.h" |
28 | | #include "absl/strings/ascii.h" |
29 | | #include "absl/strings/numbers.h" |
30 | | #include "absl/strings/str_cat.h" |
31 | | #include "absl/strings/str_format.h" |
32 | | #include "absl/strings/str_split.h" |
33 | | #include "absl/strings/string_view.h" |
34 | | #include "absl/types/span.h" |
35 | | |
36 | | #include "s2/mutable_s2shape_index.h" |
37 | | #include "s2/s1angle.h" |
38 | | #include "s2/s2cell_id.h" |
39 | | #include "s2/s2cell_union.h" |
40 | | #include "s2/s2debug.h" |
41 | | #include "s2/s2latlng.h" |
42 | | #include "s2/s2latlng_rect.h" |
43 | | #include "s2/s2lax_polygon_shape.h" |
44 | | #include "s2/s2lax_polyline_shape.h" |
45 | | #include "s2/s2loop.h" |
46 | | #include "s2/s2point.h" |
47 | | #include "s2/s2point_vector_shape.h" |
48 | | #include "s2/s2polygon.h" |
49 | | #include "s2/s2polyline.h" |
50 | | #include "s2/s2shape.h" |
51 | | #include "s2/s2shape_index.h" |
52 | | #include "s2/util/task/status_macros.h" |
53 | | |
54 | | using absl::Span; |
55 | | using absl::string_view; |
56 | | using std::make_unique; |
57 | | using std::string; |
58 | | using std::unique_ptr; |
59 | | using std::vector; |
60 | | |
61 | | namespace s2textformat { |
62 | | |
63 | | namespace { |
64 | 14.5k | static vector<string_view> SplitString(string_view str, char separator) { |
65 | 14.5k | vector<string_view> result = |
66 | 14.5k | absl::StrSplit(str, separator, absl::SkipWhitespace()); |
67 | 3.37M | for (string_view& e : result) { |
68 | 3.37M | e = absl::StripAsciiWhitespace(e); |
69 | 3.37M | } |
70 | 14.5k | return result; |
71 | 14.5k | } |
72 | | } // namespace |
73 | | |
74 | 2.90M | absl::StatusOr<vector<S2LatLng>> ParseLatLngs(string_view str) { |
75 | 2.90M | vector<S2LatLng> latlngs; |
76 | 2.90M | for (const string_view lat_lng_str : |
77 | 2.90M | absl::StrSplit(str, ',', absl::SkipEmpty())) { |
78 | 10.4k | const absl::InlinedVector<string_view, 2> lat_lng = |
79 | 10.4k | absl::StrSplit(lat_lng_str, ':'); |
80 | 10.4k | if (lat_lng.size() != 2) { |
81 | 213 | return absl::InvalidArgumentError(absl::StrCat( |
82 | 213 | "Invalid latlng format (expected lat:lng): ", lat_lng_str)); |
83 | 213 | } |
84 | 10.2k | double lat, lng; |
85 | 10.2k | if (!absl::SimpleAtod(lat_lng[0], &lat)) { |
86 | 442 | return absl::InvalidArgumentError( |
87 | 442 | absl::StrCat("Failed to parse latitude: ", lat_lng[0])); |
88 | 442 | } |
89 | 9.82k | if (!absl::SimpleAtod(lat_lng[1], &lng)) { |
90 | 1.16k | return absl::InvalidArgumentError( |
91 | 1.16k | absl::StrCat("Failed to parse longitude: ", lat_lng[1])); |
92 | 1.16k | } |
93 | 8.66k | S2LatLng latlng = S2LatLng::FromDegrees(lat, lng); |
94 | 8.66k | if (!latlng.is_valid()) { |
95 | 354 | return absl::InvalidArgumentError( |
96 | 354 | absl::StrCat("Invalid latlng coordinates: ", lat_lng_str)); |
97 | 354 | } |
98 | 8.30k | latlngs.push_back(latlng); |
99 | 8.30k | } |
100 | 2.90M | return latlngs; |
101 | 2.90M | } |
102 | | |
103 | 2.90M | absl::StatusOr<vector<S2Point>> ParsePoints(string_view str) { |
104 | 2.90M | ASSIGN_OR_RETURN(vector<S2LatLng> latlngs, ParseLatLngs(str)); |
105 | 2.90M | vector<S2Point> points; |
106 | 2.90M | points.reserve(latlngs.size()); |
107 | 2.90M | for (const S2LatLng& latlng : latlngs) { |
108 | 6.51k | points.push_back(latlng.ToPoint()); |
109 | 6.51k | } |
110 | 2.90M | return points; |
111 | 2.90M | } |
112 | | |
113 | 3.51k | absl::StatusOr<S2Point> MakePoint(string_view str) { |
114 | 3.51k | ASSIGN_OR_RETURN(vector<S2Point> points, ParsePoints(str)); |
115 | 3.11k | if (points.size() != 1) { |
116 | 2 | return absl::InvalidArgumentError( |
117 | 2 | absl::StrCat("Expected 1 point, got ", points.size())); |
118 | 2 | } |
119 | 3.11k | return points[0]; |
120 | 3.11k | } |
121 | | |
122 | 0 | absl::StatusOr<S2LatLng> MakeLatLng(string_view str) { |
123 | 0 | ASSIGN_OR_RETURN(vector<S2LatLng> latlngs, ParseLatLngs(str)); |
124 | 0 | if (latlngs.size() != 1) { |
125 | 0 | return absl::InvalidArgumentError( |
126 | 0 | absl::StrCat("Expected 1 latlng, got ", latlngs.size())); |
127 | 0 | } |
128 | 0 | return latlngs[0]; |
129 | 0 | } |
130 | | |
131 | 0 | absl::StatusOr<S2LatLngRect> MakeLatLngRect(string_view str) { |
132 | 0 | ASSIGN_OR_RETURN(vector<S2LatLng> latlngs, ParseLatLngs(str)); |
133 | 0 | if (latlngs.empty()) { |
134 | 0 | return absl::InvalidArgumentError( |
135 | 0 | "No points found to construct S2LatLngRect"); |
136 | 0 | } |
137 | 0 | S2LatLngRect rect = S2LatLngRect::FromPoint(latlngs[0]); |
138 | 0 | for (size_t i = 1; i < latlngs.size(); ++i) { |
139 | 0 | rect.AddPoint(latlngs[i]); |
140 | 0 | } |
141 | 0 | return rect; |
142 | 0 | } |
143 | | |
144 | 0 | absl::StatusOr<S2CellId> MakeCellId(string_view str) { |
145 | 0 | S2CellId cell_id = S2CellId::FromDebugString(str); |
146 | 0 | if (cell_id == S2CellId::None()) { |
147 | 0 | return absl::InvalidArgumentError(absl::StrCat("Invalid S2CellId: ", str)); |
148 | 0 | } |
149 | 0 | return cell_id; |
150 | 0 | } |
151 | | |
152 | 0 | absl::StatusOr<S2CellUnion> MakeCellUnion(string_view str) { |
153 | 0 | vector<S2CellId> cell_ids; |
154 | 0 | for (const string_view cell_str : SplitString(str, ',')) { |
155 | 0 | ASSIGN_OR_RETURN(S2CellId cell_id, MakeCellId(cell_str)); |
156 | 0 | cell_ids.push_back(cell_id); |
157 | 0 | } |
158 | 0 | return S2CellUnion(std::move(cell_ids)); |
159 | 0 | } |
160 | | |
161 | | absl::StatusOr<unique_ptr<S2Loop>> MakeLoop(string_view str, |
162 | 0 | S2Debug debug_override) { |
163 | 0 | if (str == "empty") { |
164 | 0 | return make_unique<S2Loop>(S2Loop::kEmpty()); |
165 | 0 | } |
166 | 0 | if (str == "full") { |
167 | 0 | return make_unique<S2Loop>(S2Loop::kFull()); |
168 | 0 | } |
169 | 0 | ASSIGN_OR_RETURN(vector<S2Point> vertices, ParsePoints(str)); |
170 | 0 | return make_unique<S2Loop>(vertices, debug_override); |
171 | 0 | } |
172 | | |
173 | | absl::StatusOr<unique_ptr<S2Polyline>> MakePolyline(string_view str, |
174 | 0 | S2Debug debug_override) { |
175 | 0 | ASSIGN_OR_RETURN(vector<S2Point> vertices, ParsePoints(str)); |
176 | 0 | return make_unique<S2Polyline>(vertices, debug_override); |
177 | 0 | } |
178 | | |
179 | | absl::StatusOr<unique_ptr<S2LaxPolylineShape>> MakeLaxPolyline( |
180 | 549k | string_view str) { |
181 | 549k | ASSIGN_OR_RETURN(vector<S2Point> vertices, ParsePoints(str)); |
182 | 549k | return make_unique<S2LaxPolylineShape>(vertices); |
183 | 549k | } |
184 | | |
185 | | static absl::StatusOr<unique_ptr<S2Polygon>> InternalMakePolygon( |
186 | 0 | string_view str, S2Debug debug_override, bool normalize_loops) { |
187 | 0 | if (str == "empty") str = ""; |
188 | 0 | vector<string_view> loop_strs = SplitString(str, ';'); |
189 | 0 | vector<unique_ptr<S2Loop>> loops; |
190 | 0 | for (const string_view loop_str : loop_strs) { |
191 | 0 | ASSIGN_OR_RETURN(unique_ptr<S2Loop> loop, |
192 | 0 | MakeLoop(loop_str, debug_override)); |
193 | | // Don't normalize loops that were explicitly specified as "full". |
194 | 0 | if (normalize_loops && !loop->is_full()) loop->Normalize(); |
195 | 0 | loops.push_back(std::move(loop)); |
196 | 0 | } |
197 | 0 | return make_unique<S2Polygon>(std::move(loops), debug_override); |
198 | 0 | } |
199 | | |
200 | | absl::StatusOr<unique_ptr<S2Polygon>> MakePolygon(string_view str, |
201 | 0 | S2Debug debug_override) { |
202 | 0 | return InternalMakePolygon(str, debug_override, /*normalize_loops=*/true); |
203 | 0 | } |
204 | | |
205 | 0 | absl::StatusOr<unique_ptr<S2Polygon>> MakeVerbatimPolygon(string_view str) { |
206 | 0 | return InternalMakePolygon(str, S2Debug::ALLOW, /*normalize_loops=*/false); |
207 | 0 | } |
208 | | |
209 | 6.15k | absl::StatusOr<unique_ptr<S2LaxPolygonShape>> MakeLaxPolygon(string_view str) { |
210 | 6.15k | vector<string_view> loop_strs = SplitString(str, ';'); |
211 | 6.15k | vector<vector<S2Point>> loops; |
212 | 2.35M | for (const string_view loop_str : loop_strs) { |
213 | 2.35M | if (loop_str == "full") { |
214 | 786 | loops.push_back(vector<S2Point>()); |
215 | 2.35M | } else if (loop_str != "empty") { |
216 | 2.35M | ASSIGN_OR_RETURN(vector<S2Point> points, ParsePoints(loop_str)); |
217 | 2.35M | loops.push_back(std::move(points)); |
218 | 2.35M | } |
219 | 2.35M | } |
220 | 4.95k | return make_unique<S2LaxPolygonShape>(std::move(loops)); |
221 | 6.15k | } |
222 | | |
223 | 3.24k | absl::StatusOr<unique_ptr<MutableS2ShapeIndex>> MakeIndex(string_view str) { |
224 | 3.24k | absl::InlinedVector<string_view, 3> strs = absl::StrSplit(str, '#'); |
225 | 3.24k | if (strs.size() != 3) { |
226 | 0 | return absl::InvalidArgumentError( |
227 | 0 | absl::StrCat("Must contain two # characters: ", str)); |
228 | 0 | } |
229 | 3.24k | auto index = make_unique<MutableS2ShapeIndex>(); |
230 | 3.24k | vector<S2Point> points; |
231 | 3.51k | for (const string_view point_str : SplitString(strs[0], '|')) { |
232 | 3.51k | ASSIGN_OR_RETURN(S2Point point, MakePoint(point_str)); |
233 | 3.11k | points.push_back(point); |
234 | 3.11k | } |
235 | 2.83k | if (!points.empty()) { |
236 | 111 | index->Add(make_unique<S2PointVectorShape>(std::move(points))); |
237 | 111 | } |
238 | 549k | for (const string_view line_str : SplitString(strs[1], '|')) { |
239 | 549k | ASSIGN_OR_RETURN(unique_ptr<S2LaxPolylineShape> lax_polyline, |
240 | 549k | MakeLaxPolyline(line_str)); |
241 | 549k | index->Add(std::move(lax_polyline)); |
242 | 549k | } |
243 | 6.15k | for (const string_view polygon_str : SplitString(strs[2], '|')) { |
244 | 6.15k | ASSIGN_OR_RETURN(unique_ptr<S2LaxPolygonShape> lax_polygon, |
245 | 4.95k | MakeLaxPolygon(polygon_str)); |
246 | 4.95k | index->Add(std::move(lax_polygon)); |
247 | 4.95k | } |
248 | 1.07k | return index; |
249 | 2.27k | } |
250 | | |
251 | | static void AppendVertex(const S2LatLng& ll, string* out, |
252 | 0 | bool roundtrip_precision = false) { |
253 | 0 | if (roundtrip_precision) { |
254 | 0 | absl::StrAppendFormat(out, "%.17g:%.17g", ll.lat().degrees(), |
255 | 0 | ll.lng().degrees()); |
256 | 0 | } else { |
257 | 0 | absl::StrAppendFormat(out, "%.15g:%.15g", ll.lat().degrees(), |
258 | 0 | ll.lng().degrees()); |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | | static void AppendVertex(const S2Point& p, string* out, |
263 | 0 | bool roundtrip_precision = false) { |
264 | 0 | S2LatLng ll(p); |
265 | 0 | return AppendVertex(ll, out, roundtrip_precision); |
266 | 0 | } |
267 | | |
268 | 0 | static void AppendVertices(const S2Point* v, int n, string* out) { |
269 | 0 | for (int i = 0; i < n; ++i) { |
270 | 0 | if (i > 0) *out += ", "; |
271 | 0 | AppendVertex(v[i], out); |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | 0 | string ToString(const S2Point& point) { |
276 | 0 | string out; |
277 | 0 | AppendVertex(point, &out); |
278 | 0 | return out; |
279 | 0 | } |
280 | | |
281 | 0 | string ToString(const S2LatLng& latlng) { |
282 | 0 | string out; |
283 | 0 | AppendVertex(latlng, &out); |
284 | 0 | return out; |
285 | 0 | } |
286 | | |
287 | 0 | string ToString(const S2LatLngRect& rect) { |
288 | 0 | string out; |
289 | 0 | AppendVertex(rect.lo(), &out); |
290 | 0 | out += ", "; |
291 | 0 | AppendVertex(rect.hi(), &out); |
292 | 0 | return out; |
293 | 0 | } |
294 | | |
295 | 0 | string ToString(const S2CellId cell_id) { |
296 | 0 | return cell_id.ToString(); |
297 | 0 | } |
298 | | |
299 | 0 | string ToString(const S2CellUnion& cell_union) { |
300 | 0 | string out; |
301 | 0 | for (S2CellId cell_id : cell_union) { |
302 | 0 | if (!out.empty()) out += ", "; |
303 | 0 | out += cell_id.ToString(); |
304 | 0 | } |
305 | 0 | return out; |
306 | 0 | } |
307 | | |
308 | 0 | string ToString(const S2Loop& loop) { |
309 | 0 | if (loop.is_empty()) { |
310 | 0 | return "empty"; |
311 | 0 | } else if (loop.is_full()) { |
312 | 0 | return "full"; |
313 | 0 | } |
314 | 0 | string out; |
315 | 0 | if (loop.num_vertices() > 0) { |
316 | 0 | AppendVertices(&loop.vertex(0), loop.num_vertices(), &out); |
317 | 0 | } |
318 | 0 | return out; |
319 | 0 | } |
320 | | |
321 | 0 | string ToString(const S2Polyline& polyline) { |
322 | 0 | string out; |
323 | 0 | if (polyline.num_vertices() > 0) { |
324 | 0 | AppendVertices(&polyline.vertex(0), polyline.num_vertices(), &out); |
325 | 0 | } |
326 | 0 | return out; |
327 | 0 | } |
328 | | |
329 | 0 | string ToString(const S2Polygon& polygon, string_view loop_separator) { |
330 | 0 | if (polygon.is_empty()) { |
331 | 0 | return "empty"; |
332 | 0 | } else if (polygon.is_full()) { |
333 | 0 | return "full"; |
334 | 0 | } |
335 | 0 | string out; |
336 | 0 | for (int i = 0; i < polygon.num_loops(); ++i) { |
337 | 0 | if (i > 0) absl::StrAppend(&out, loop_separator); |
338 | 0 | const S2Loop& loop = *polygon.loop(i); |
339 | 0 | AppendVertices(&loop.vertex(0), loop.num_vertices(), &out); |
340 | 0 | } |
341 | 0 | return out; |
342 | 0 | } |
343 | | |
344 | 0 | string ToString(Span<const S2Point> points) { |
345 | 0 | string out; |
346 | 0 | AppendVertices(points.data(), points.size(), &out); |
347 | 0 | return out; |
348 | 0 | } |
349 | | |
350 | 0 | string ToString(Span<const S2LatLng> latlngs) { |
351 | 0 | string out; |
352 | 0 | for (size_t i = 0; i < latlngs.size(); ++i) { |
353 | 0 | if (i > 0) out += ", "; |
354 | 0 | AppendVertex(latlngs[i], &out); |
355 | 0 | } |
356 | 0 | return out; |
357 | 0 | } |
358 | | |
359 | 0 | string ToString(const S2Shape& shape) { |
360 | | // Polygon chains are separated by a ; instead of |. |
361 | 0 | const char* separator = shape.dimension() == 2 ? "; " : " | "; |
362 | |
|
363 | 0 | string out; |
364 | 0 | if (shape.dimension() == 1) out += "# "; |
365 | 0 | if (shape.dimension() == 2) out += "## "; |
366 | |
|
367 | 0 | int nchain = 0; |
368 | 0 | for (const auto& chain : shape.chains()) { |
369 | 0 | if (nchain++ > 0) { |
370 | 0 | out += separator; |
371 | 0 | } |
372 | |
|
373 | 0 | int nvertex = 0; |
374 | 0 | for (const S2Point& vertex : shape.vertices(chain)) { |
375 | 0 | if (nvertex++ > 0) { |
376 | 0 | out += ", "; |
377 | 0 | } |
378 | 0 | AppendVertex(vertex, &out); |
379 | 0 | } |
380 | 0 | } |
381 | |
|
382 | 0 | if (shape.dimension() == 1) out += " #"; |
383 | 0 | if (shape.dimension() == 0) out += " ##"; |
384 | 0 | return out; |
385 | 0 | } |
386 | | |
387 | 0 | string ToString(const S2LaxPolylineShape& polyline) { |
388 | 0 | string out; |
389 | 0 | if (polyline.num_vertices() > 0) { |
390 | 0 | AppendVertices(&polyline.vertex(0), polyline.num_vertices(), &out); |
391 | 0 | } |
392 | 0 | return out; |
393 | 0 | } |
394 | | |
395 | 0 | string ToString(const S2LaxPolygonShape& polygon, string_view loop_separator) { |
396 | 0 | string out; |
397 | 0 | for (int i = 0; i < polygon.num_loops(); ++i) { |
398 | 0 | if (i > 0) absl::StrAppend(&out, loop_separator); |
399 | 0 | int n = polygon.num_loop_vertices(i); |
400 | 0 | if (n == 0) { |
401 | 0 | out += "full"; |
402 | 0 | } else { |
403 | 0 | AppendVertices(&polygon.loop_vertex(i, 0), n, &out); |
404 | 0 | } |
405 | 0 | } |
406 | 0 | return out; |
407 | 0 | } |
408 | | |
409 | 0 | string ToString(const S2ShapeIndex& index, bool roundtrip_precision) { |
410 | 0 | string out; |
411 | 0 | for (int dim = 0; dim < 3; ++dim) { |
412 | 0 | if (dim > 0) out += "#"; |
413 | 0 | int count = 0; |
414 | 0 | for (const S2Shape* shape : index) { |
415 | 0 | if (shape == nullptr || shape->dimension() != dim) continue; |
416 | 0 | out += (count > 0) ? " | " : (dim > 0) ? " " : ""; |
417 | 0 | for (int i = 0; i < shape->num_chains(); ++i, ++count) { |
418 | 0 | if (i > 0) out += (dim == 2) ? "; " : " | "; |
419 | 0 | S2Shape::Chain chain = shape->chain(i); |
420 | 0 | if (chain.length == 0) { |
421 | 0 | ABSL_DCHECK_EQ(dim, 2); |
422 | 0 | out += "full"; |
423 | 0 | } else { |
424 | 0 | AppendVertex(shape->edge(chain.start).v0, &out, roundtrip_precision); |
425 | 0 | } |
426 | 0 | int limit = chain.start + chain.length; |
427 | 0 | if (dim != 1) --limit; |
428 | 0 | for (int e = chain.start; e < limit; ++e) { |
429 | 0 | out += ", "; |
430 | 0 | AppendVertex(shape->edge(e).v1, &out, roundtrip_precision); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | } |
434 | | // Example output: "# #", "0:0 # #", "# # 0:0, 0:1, 1:0" |
435 | 0 | if (dim == 1 || (dim == 0 && count > 0)) out += " "; |
436 | 0 | } |
437 | 0 | return out; |
438 | 0 | } |
439 | | |
440 | | } // namespace s2textformat |