Coverage Report

Created: 2026-05-30 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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