Shapes
GIS made easy, a lightweight header-only planar geometry library for Modern C++
linestring.hpp
1 #pragma once
2 
3 #include <ciso646>
4 #include <vector>
5 #include <set>
6 #include <iterator>
7 #include <sstream>
8 #include <iomanip>
9 #include <simo/geom/detail/geometry.hpp>
10 #include <simo/geom/detail/utils.hpp>
11 #include <simo/geom/detail/point.hpp>
12 #include <simo/geom/detail/bounds.hpp>
13 
14 namespace simo
15 {
16 namespace shapes
17 {
18 
19 template <typename T, typename AllocatorType = std::allocator<T>>
20 class basic_linestring : public std::vector<T, AllocatorType>, public basic_geometry<basic_linestring<T>>
21 {
22  public:
23  using base_type = std::vector<T, AllocatorType>;
24 
25  using point_type = typename T::point_type;
26  using point_iterator = typename std::vector<T>::iterator;
27  using point_const_iterator = typename std::vector<T>::const_iterator;
28 
29  using coord_type = typename T::coord_type;
30  using coord_iterator = typename std::vector<coord_type>::iterator;
31  using coord_const_iterator = typename std::vector<coord_type>::const_iterator;
32 
34  : base_type() {}
35 
36  basic_linestring(std::initializer_list<T> init)
37  : base_type(init.begin(), init.end()) {}
38 
39  explicit basic_linestring(coord_const_iterator first, coord_const_iterator last)
40  {
42  size_t n = this->ndim();
43  this->reserve(std::distance(first, last));
44  for (auto it = first; it != last; it += n)
45  {
46  this->emplace_back(it, it + n);
47  }
48  }
49 
50  explicit basic_linestring(coord_iterator first, coord_iterator last)
51  {
53  size_t n = this->ndim();
54  this->reserve(std::distance(first, last));
55  for (auto it = first; it != last; it += n)
56  {
57  this->emplace_back(it, it + n);
58  }
59  }
60 
61  basic_linestring(point_iterator first, point_iterator last)
62  : base_type(first, last)
63  {
64  }
65 
66  basic_linestring(point_const_iterator first, point_const_iterator last)
67  : base_type(first, last)
68  {
69  }
70 
71  // operators
72 
80  friend bool operator==(const basic_linestring<T>& lhs, const basic_linestring<T>& rhs)
81  {
82  if (lhs.size() != rhs.size())
83  {
84  return false;
85  }
86  for (size_t i = 0; i < lhs.size(); ++i)
87  {
88  if (lhs[i] != rhs[i])
89  {
90  return false;
91  }
92  }
93  return true;
94  }
95 
103  friend bool operator!=(const basic_linestring<T>& lhs, const basic_linestring<T>& rhs)
104  {
105  return not operator==(lhs, rhs);
106  }
107 
112  std::vector<std::tuple<double, double>> xy() const
113  {
114  std::vector<std::tuple<double, double>> res;
115  res.reserve(this->size());
116  for (const auto& p : *this)
117  {
118  res.emplace_back(p.x, p.y);
119  }
120  return res;
121  }
122 
123  // polyline
124 
133  static basic_linestring<T> from_polyline(const std::string& polyline, std::int32_t precision = 5)
134  {
135  static_assert(is_basic_point<T>::value, "must contain XY points");
136 
137  auto coords = polyline::decode(polyline, precision);
138  return {coords.begin(), coords.end()};
139  }
140 
148  std::string polyline(std::int32_t precision = 5) const
149  {
150  static_assert(is_basic_point<T>::value, "must contain XY points");
151 
152  std::string res;
153  res.reserve(this->size() * 6);
154  double prev_lng = 0;
155  double prev_lat = 0;
156  for (const auto& p : *this)
157  {
158  res += polyline::encode(p.lat - prev_lat, precision);
159  res += polyline::encode(p.lng - prev_lng, precision);
160  prev_lat = p.lat;
161  prev_lng = p.lng;
162  }
163  return res;
164  }
165 
166  private:
167  friend class basic_geometry<basic_linestring<T>>;
168 
170  geometry_type geom_type_() const noexcept
171  {
173  {
174  return geometry_type::LINESTRINGZ;
175  }
177  {
178  return geometry_type::LINESTRINGM;
179  }
181  {
182  return geometry_type::LINESTRINGZM;
183  }
184  return geometry_type::LINESTRING;
185  }
186 
188  bool is_closed_() const noexcept
189  {
190  if (this->empty())
191  {
192  return true;
193  }
194  return *this[0] == *this[this->size() - 1];
195  }
196 
198  void throw_for_invalid_() const
199  {
200  if (this->empty())
201  {
202  return;
203  }
204 
205  if (this->size() < 2)
206  {
207  throw exceptions::geometry_error("LineString should be either empty or with 2 or more points");
208  }
209 
210  if (this->size() == 2)
211  {
212  if (*this[0] == *this[1])
213  {
214  throw exceptions::geometry_error("LineString with exactly two equal points");
215  }
216  }
217  }
218 
220  bounds_t bounds_() const
221  {
222  bounds_t res{};
223  for (const auto& p : *this)
224  {
225  res.extend(p.x, p.y);
226  }
227  return res;
228  }
229 
230  // json
231 
233  static basic_linestring<T> from_json_(const std::string& json)
234  {
235  try
236  {
237  auto j = nlohmann::json::parse(json);
238  auto geom_type = j.at("type").get<std::string>();
239  if (geom_type != "LineString")
240  {
241  throw exceptions::parse_error("invalid geometry type: " + std::string(geom_type));
242  }
243  const auto& coords = j.at("coordinates").get<std::vector<std::vector<double>>>();
244  std::vector<point_type> res;
245  res.reserve(coords.size());
246  std::for_each(std::begin(coords), std::end(coords), [&](const std::vector<double>& coord) {
247  res.emplace_back(coord.begin(), coord.end());
248  });
249  return basic_linestring<T>(res.begin(), res.end());
250  }
251  catch (const nlohmann::json::exception& e)
252  {
253  throw exceptions::parse_error("invalid json: " + std::string(e.what()));
254  }
255  catch (const exceptions::geometry_error& e)
256  {
257  throw exceptions::parse_error("invalid geometry: " + std::string(e.what()));
258  }
259  }
260 
262  std::string json_(std::int32_t precision = -1) const
263  {
264  std::stringstream ss;
265  if (precision >= 0)
266  {
267  ss << std::setprecision(precision);
268  }
269  ss << "{\"type\":\"LineString\",\"coordinates\":[";
270  int i = 0;
271  for (const auto& p : *this)
272  {
273  if (i > 0)
274  {
275  ss << ",";
276  }
277  ss << "[";
278  for (size_t j = 0; j < p.size(); ++j)
279  {
280  if (j > 0)
281  {
282  ss << ",";
283  }
284  ss << p.coords[j];
285  }
286  ss << "]";
287  ++i;
288  }
289  ss << "]}";
290  return ss.str();
291  }
292 
293  // wkt
294 
296  static basic_linestring<T> from_wkt_(const std::string& wkt)
297  {
298  wkt_reader reader{};
299  auto result = reader.read(wkt);
300  const auto& data = result.data;
301  if (not utils::is_linestring(data.geom_type))
302  {
303  throw exceptions::parse_error("invalid wkt string");
304  }
305  return basic_linestring<T>(result.data.coords.begin(), result.data.coords.end());
306  }
307 
309  std::string wkt_(std::int32_t precision = -1) const
310  {
311  std::stringstream ss;
312  if (precision >= 0)
313  {
314  ss << std::setprecision(precision);
315  }
316  ss << "LINESTRING";
317  if (this->has_z())
318  {
319  ss << "Z";
320  }
321  if (this->has_m())
322  {
323  ss << "M";
324  }
325  ss << "(";
326  int i = 0;
327  for (const auto& p : *this)
328  {
329  if (i > 0)
330  {
331  ss << ",";
332  }
333  for (size_t j = 0; j < p.size(); ++j)
334  {
335  if (j > 0)
336  {
337  ss << " ";
338  }
339  ss << p.coords[j];
340  }
341  ++i;
342  }
343  ss << ")";
344  return ss.str();
345  }
346 };
347 
348 template <typename>
349 struct is_basic_linestring : std::false_type
350 {};
351 
352 template <typename T>
354 {};
355 
356 template <typename>
357 struct is_basic_linestring_z : std::false_type
358 {};
359 
360 template <typename T>
362 {};
363 
364 template <typename>
365 struct is_basic_linestring_m : std::false_type
366 {};
367 
368 template <typename T>
370 {};
371 
372 template <typename>
373 struct is_basic_linestring_zm : std::false_type
374 {};
375 
376 template <typename T>
378 {};
379 
380 } // namespace shapes
381 } // namespace simo
geometry_type geom_type() const noexcept
Returns the geometry type.
Definition: geometry.hpp:35
basic_linestring(coord_const_iterator first, coord_const_iterator last)
Definition: linestring.hpp:39
Base class for all geometries.
Definition: geometry.hpp:26
wkt_data data
the parser result data
Definition: wkt_parser.hpp:44
wkt_result read(const std::string &wkt)
parse the given wkt string
Definition: wkt_reader.hpp:47
Exception thrown when a geometry error is found.
Definition: exceptions.hpp:121
std::vector< std::tuple< double, double > > xy() const
DOCUMENT ME!
Definition: linestring.hpp:112
const char * what() const noexceptoverride
Returns the exception reason.
Definition: exceptions.hpp:39
std::string polyline(std::int32_t precision=5) const
DOCUMENT ME!
Definition: linestring.hpp:148
Represents an axis-aligned bounding box.
Definition: bounds.hpp:18
friend bool operator==(const basic_linestring< T > &lhs, const basic_linestring< T > &rhs)
Definition: linestring.hpp:80
bool has_z() const noexcept
Whether the geometry has the z-coordinate.
Definition: geometry.hpp:214
static basic_linestring< T > from_polyline(const std::string &polyline, std::int32_t precision=5)
DOCUMENT ME!
Definition: linestring.hpp:133
bounds_t & extend(double x, double y)
Extends the bounds to contain the given point.
Definition: bounds.hpp:69
Exception thrown when an error has been found while parsing.
Definition: exceptions.hpp:67
std::string json(std::int32_t precision=-1) const
Dumps the geojson representation of the geometry.
Definition: geometry.hpp:255
friend bool operator!=(const basic_linestring< T > &lhs, const basic_linestring< T > &rhs)
Definition: linestring.hpp:103
bool has_m() const noexcept
Whether the geometry has the m-coordinate (measurement coordinate)
Definition: geometry.hpp:226
std::string wkt(std::int32_t precision=-1) const
Dumps the wkt representation of the geometry.
Definition: geometry.hpp:283
size_t ndim() const noexcept
Returns the number of dimensions of the geometry.
Definition: geometry.hpp:142
basic_linestring(coord_iterator first, coord_iterator last)
Definition: linestring.hpp:50