OmniSciDB  72c90bc290
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Types.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2022 HEAVY.AI, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "Geospatial/Types.h"
19 
20 #include <limits>
21 #include <mutex>
22 
23 #include <gdal.h>
24 #include <ogr_geometry.h>
25 #include <ogrsf_frmts.h>
26 
27 #include "Geospatial/GDAL.h"
28 #include "Logger/Logger.h"
29 #include "Shared/sqltypes.h"
30 
40 namespace {
41 constexpr auto DOUBLE_MAX = std::numeric_limits<double>::max();
42 constexpr auto DOUBLE_MIN = std::numeric_limits<double>::lowest();
43 
44 struct Coords {
45  double x;
46  double y;
47  Coords(double x, double y) : x(x), y(y) {}
48 };
49 
50 struct BoundingBox {
53  BoundingBox() : min(DOUBLE_MAX, DOUBLE_MAX), max(DOUBLE_MIN, DOUBLE_MIN) {}
54 
55  void update(double x, double y) {
56  if (x < min.x) {
57  min.x = x;
58  }
59  if (y < min.y) {
60  min.y = y;
61  }
62  if (x > max.x) {
63  max.x = x;
64  }
65  if (y > max.y) {
66  max.y = y;
67  }
68  }
69 };
70 
71 int process_poly_ring(OGRLinearRing* ring,
72  std::vector<double>& coords,
73  BoundingBox* bbox) {
74  double last_x = DOUBLE_MAX, last_y = DOUBLE_MAX;
75  size_t first_index = coords.size();
76  int num_points_added = 0;
77  int num_points_in_ring = ring->getNumPoints();
78  if (num_points_in_ring < 3) {
80  "PolyRing",
81  "All poly rings must have more than 3 points. Found ring with " +
82  std::to_string(num_points_in_ring) + " points.");
83  }
84  for (auto i = 0; i < num_points_in_ring; i++) {
85  OGRPoint point;
86  ring->getPoint(i, &point);
87  last_x = point.getX();
88  last_y = point.getY();
89  coords.push_back(last_x);
90  coords.push_back(last_y);
91  if (bbox) {
92  bbox->update(last_x, last_y);
93  }
94  num_points_added++;
95  }
96  // Store all rings as open rings (implicitly assumes all rings are closed)
97  if ((coords[first_index] == last_x) && (coords[first_index + 1] == last_y)) {
98  coords.pop_back();
99  coords.pop_back();
100  num_points_added--;
101  if (num_points_added < 3) {
103  "PolyRing",
104  "All exterior rings must have more than 3 points. Found ring with " +
105  std::to_string(num_points_added) + " points.");
106  }
107  }
108  return num_points_added;
109 }
110 
111 } // namespace
112 
113 namespace Geospatial {
114 
116 std::map<std::tuple<int32_t, int32_t>, std::shared_ptr<OGRCoordinateTransformation>>
118 
119 std::string GeoTypesError::OGRErrorToStr(const int ogr_err) {
120  switch (ogr_err) {
121  case OGRERR_NOT_ENOUGH_DATA:
122  return std::string("not enough input data");
123  case OGRERR_NOT_ENOUGH_MEMORY:
124  return std::string("not enough memory");
125  case OGRERR_UNSUPPORTED_GEOMETRY_TYPE:
126  return std::string("unsupported geometry type");
127  case OGRERR_UNSUPPORTED_OPERATION:
128  return std::string("unsupported operation");
129  case OGRERR_CORRUPT_DATA:
130  return std::string("corrupt input data");
131  case OGRERR_FAILURE:
132  return std::string("ogr failure");
133  case OGRERR_UNSUPPORTED_SRS:
134  return std::string("unsupported spatial reference system");
135  case OGRERR_INVALID_HANDLE:
136  return std::string("invalid file handle");
137 #if (GDAL_VERSION_MAJOR > 1)
138  case OGRERR_NON_EXISTING_FEATURE:
139  return std::string("feature does not exist in input geometry");
140 #endif
141  default:
142  return std::string("Unknown OGOR error encountered: ") + std::to_string(ogr_err);
143  }
144 }
145 
147  // Note: Removing the geometry object that was pulled from an OGRFeature results in a
148  // segfault. If we are wrapping around a pre-existing OGRGeometry object, we let the
149  // caller manage the memory.
150  if (geom_ && owns_geom_obj_) {
151  OGRGeometryFactory::destroyGeometry(geom_);
152  }
153 }
154 
155 OGRErr GeoBase::createFromWktString(const std::string& wkt, OGRGeometry** geom) {
156 #if (GDAL_VERSION_MAJOR > 2) || (GDAL_VERSION_MAJOR == 2 && GDAL_VERSION_MINOR >= 3)
157  OGRErr ogr_status = OGRGeometryFactory::createFromWkt(wkt.c_str(), nullptr, geom);
158 #else
159  auto data = (char*)wkt.c_str();
160  OGRErr ogr_status = OGRGeometryFactory::createFromWkt(&data, NULL, geom);
161 #endif
162  return ogr_status;
163 }
164 
165 std::string GeoBase::getWktString() const {
166  char* wkt = nullptr;
167  geom_->exportToWkt(&wkt);
168  CHECK(wkt);
169  std::string wkt_str(wkt);
170  CPLFree(wkt);
171  return wkt_str;
172 }
173 
174 OGRErr GeoBase::createFromWkbView(OGRGeometry** geom, WkbView const wkb_view) {
175 #if (GDAL_VERSION_MAJOR > 2) || (GDAL_VERSION_MAJOR == 2 && GDAL_VERSION_MINOR >= 3)
176  OGRErr ogr_status =
177  OGRGeometryFactory::createFromWkb(wkb_view.ptr_, nullptr, geom, wkb_view.size_);
178  return ogr_status;
179 #else
180  CHECK(false);
181 #endif
182 }
183 
184 // GeoBase could also generate GEOS geometries through geom_->exportToGEOS(),
185 // conversion to WKB and subsequent load of WKB into GEOS could be eliminated.
186 
187 bool GeoBase::getWkb(std::vector<uint8_t>& wkb) const {
188  auto size = geom_->WkbSize();
189  if (size > 0) {
190  wkb.resize(size);
191  geom_->exportToWkb(wkbNDR, wkb.data());
192  return true;
193  }
194  return false;
195 }
196 
197 bool GeoBase::isEmpty() const {
198  return geom_ && geom_->IsEmpty();
199 }
200 
201 bool GeoBase::operator==(const GeoBase& other) const {
202  if (!this->geom_ || !other.geom_) {
203  return false;
204  }
205  return this->geom_->Equals(other.geom_);
206 }
207 
209 #define SRID_WORLD_MERCATOR 999000
210 
211 #define SRID_NORTH_UTM_START 999001
212 
213 #define SRID_NORTH_UTM_END 999060
214 
215 #define SRID_NORTH_LAMBERT 999061
216 
217 #define SRID_SOUTH_UTM_START 999101
218 
219 #define SRID_SOUTH_UTM_END 999160
220 
221 #define SRID_SOUTH_LAMBERT 999161
222 
223 #define SRID_LAEA_START 999163
224 
225 #define SRID_LAEA_END 999283
226 
227 int32_t GeoBase::getBestPlanarSRID() const {
228  if (!this->geom_) {
229  return 0;
230  }
231  double cx, cy, xwidth, ywidth;
232  OGREnvelope envelope;
233  geom_->getEnvelope(&envelope);
234  // Can't use GDAL's Centroid geom_->Centroid(OGRPoint*): requires GEOS
235  // Use center of the bounding box for now.
236  // TODO: hook up our own Centroid implementation
237  cx = (envelope.MaxX + envelope.MinX) / 2.0;
238  cy = (envelope.MaxY + envelope.MinY) / 2.0;
239  xwidth = envelope.MaxX - envelope.MinX;
240  ywidth = envelope.MaxY - envelope.MinY;
241 
242  // Arctic coords: Lambert Azimuthal Equal Area North
243  if (cy > 70.0 && ywidth < 45.0) {
244  return SRID_NORTH_LAMBERT;
245  }
246  // Antarctic coords: Lambert Azimuthal Equal Area South
247  if (cy < -70.0 && ywidth < 45.0) {
248  return SRID_SOUTH_LAMBERT;
249  }
250 
251  // Can geometry fit into a single UTM zone?
252  if (xwidth < 6.0) {
253  int zone = floor((cx + 180.0) / 6.0);
254  if (zone > 59) {
255  zone = 59;
256  }
257  // Below the equator: UTM South
258  // Above the equator: UTM North
259  if (cy < 0.0) {
260  return SRID_SOUTH_UTM_START + zone;
261  } else {
262  return SRID_NORTH_UTM_START + zone;
263  }
264  }
265 
266  // TODO: to be removed once we add custom LAEA zone transforms
267  // Can geometry still fit into 5 consecutive UTM zones?
268  // Then go for the mid-UTM zone, tolerating some limited distortion
269  // in the left and right corners. That's still better than Mercator.
270  if (xwidth < 30.0) {
271  int zone = floor((cx + 180.0) / 6.0);
272  if (zone > 59) {
273  zone = 59;
274  }
275  // Below the equator: UTM South
276  // Above the equator: UTM North
277  if (cy < 0.0) {
278  return SRID_SOUTH_UTM_START + zone;
279  } else {
280  return SRID_NORTH_UTM_START + zone;
281  }
282  }
283 
284  // Can geometry fit into a custom LAEA area 30 degrees high? Allow some overlap.
285  if (ywidth < 25.0) {
286  int xzone = -1;
287  int yzone = 3 + floor(cy / 30.0); // range of 0-5
288  if ((yzone == 2 || yzone == 3) && xwidth < 30.0) {
289  // Equatorial band, 12 zones, 30 degrees wide
290  xzone = 6 + floor(cx / 30.0);
291  } else if ((yzone == 1 || yzone == 4) && xwidth < 45.0) {
292  // Temperate band, 8 zones, 45 degrees wide
293  xzone = 4 + floor(cx / 45.0);
294  } else if ((yzone == 0 || yzone == 5) && xwidth < 90.0) {
295  // Arctic band, 4 zones, 90 degrees wide
296  xzone = 2 + floor(cx / 90.0);
297  }
298  // Found an appropriate xzone to fit in?
299  if (xzone != -1) {
300  return SRID_LAEA_START + 20 * yzone + xzone;
301  }
302  }
303 
304  // Fall-back to Mercator
305  return SRID_WORLD_MERCATOR;
306 }
307 
308 std::shared_ptr<OGRCoordinateTransformation> GeoBase::getTransformation(int32_t srid0,
309  int32_t srid1) {
310  std::lock_guard<std::mutex> guard(transformation_map_mutex_);
311  std::tuple<int32_t, int32_t> key{srid0, srid1};
312  auto it = transformation_map_.find(key);
313  if (it != transformation_map_.end()) {
314  return it->second;
315  }
316  auto setSpatialReference = [&](OGRSpatialReference* sr, int32_t srid) -> bool {
317  OGRErr status = OGRERR_NONE;
318  if (srid == 4326) {
319  status = sr->importFromEPSG(4326);
320  } else if (srid == SRID_NORTH_LAMBERT) {
321  // +proj=laea +lat_0=90 +lon_0=-40 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m
322  // +no_defs
323  status = sr->importFromEPSG(3574);
324  } else if (srid == SRID_SOUTH_LAMBERT) {
325  // +proj=laea +lat_0=-90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m
326  // +no_defs
327  status = sr->importFromEPSG(3409);
328  } else if (SRID_SOUTH_UTM_START <= srid && srid <= SRID_SOUTH_UTM_END) {
329  // +proj=utm +zone=%d +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs
330  int32_t zone = srid - SRID_SOUTH_UTM_START;
331  status = sr->importFromEPSG(32701 + zone);
332  } else if (SRID_NORTH_UTM_START <= srid && srid <= SRID_NORTH_UTM_END) {
333  // +proj=utm +zone=%d +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
334  int32_t zone = srid - SRID_NORTH_UTM_START;
335  status = sr->importFromEPSG(32601 + zone);
336  } else if (SRID_LAEA_START <= srid && srid <= SRID_LAEA_END) {
337  // TODO: add support and coordinate operations for custom Lambert zones,
338  // need to calculate lat/lon for the zone, SetCoordinateOperation in options.
339  // +proj=laea +ellps=WGS84 +datum=WGS84 +lat_0=%g +lon_0=%g +units=m +no_defs
340  // Go with Mercator for now
341  status = sr->importFromEPSG(3395);
342  } else if (srid == SRID_WORLD_MERCATOR) {
343  // +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m
344  // +no_defs
345  status = sr->importFromEPSG(3395);
346  } else if (srid > 0) {
347  // Attempt to import from srid directly
348  status = sr->importFromEPSG(srid);
349  } else {
350  return false;
351  }
352 #if GDAL_VERSION_MAJOR >= 3
353  // GDAL 3.x (really Proj.4 6.x) now enforces lat, lon order
354  // this results in X and Y being transposed for angle-based
355  // coordinate systems. This restores the previous behavior.
356  if (status == OGRERR_NONE) {
357  sr->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
358  }
359 #endif
360  return (status == OGRERR_NONE);
361  };
362 
363  // lazy init GDAL
364  GDAL::init();
365 
366  OGRSpatialReference sr0;
367  if (!setSpatialReference(&sr0, srid0)) {
368  return nullptr;
369  }
370  OGRSpatialReference sr1;
371  if (!setSpatialReference(&sr1, srid1)) {
372  return nullptr;
373  }
374  // GDAL 3 allows specification of advanced transformations in
375  // OGRCoordinateTransformationOptions, including multi-step pipelines.
376  // GDAL 3 would be required to handle Lambert zone proj4 strings.
377  // Using a simple transform for now.
378  std::shared_ptr<OGRCoordinateTransformation> new_transformation;
379  new_transformation.reset(OGRCreateCoordinateTransformation(&sr0, &sr1));
380  transformation_map_[key] = new_transformation;
381  return new_transformation;
382 }
383 
384 bool GeoBase::transform(int32_t srid0, int32_t srid1) {
385  auto coordinate_transformation = getTransformation(srid0, srid1);
386  if (!coordinate_transformation) {
387  return false;
388  }
389  auto ogr_status = geom_->transform(coordinate_transformation.get());
390  return (ogr_status == OGRERR_NONE);
391 }
392 
394  auto srid1 = ti.get_output_srid();
395  if (srid1 == 4326) {
396  auto srid0 = ti.get_input_srid();
397  if (srid0 > 0 && srid0 != 4326) {
398  if (!transform(srid0, srid1)) {
399  return false;
400  }
401  }
402  }
403  return true;
404 }
405 
406 // Run a specific geo operation on this and other geometries
407 std::unique_ptr<GeoBase> GeoBase::run(GeoBase::GeoOp op, const GeoBase& other) const {
408  OGRGeometry* result = nullptr;
409  // Invalid geometries are derailing geo op performance,
410  // Checking validity before running an operation doesn't have lesser penalty.
411  // DOES NOT HELP: if (geom_->IsValid() && other.geom_->IsValid()) {
412  switch (op) {
414  result = geom_->Intersection(other.geom_);
415  break;
417  result = geom_->Difference(other.geom_);
418  break;
420  result = geom_->Union(other.geom_);
421  break;
422  default:
423  break;
424  }
425  // TODO: Need to handle empty/non-POLYGON result
426  if (!result || result->IsEmpty() ||
427  !(result->getGeometryType() == wkbPolygon ||
428  result->getGeometryType() == wkbMultiPolygon)) {
429  // throw GeoTypesError(std::string(OGRGeometryTypeToName(geom_->getGeometryType())),
430  // "Currenly don't support invalid or empty result");
431  // return GeoTypesFactory::createGeoType("POLYGON EMPTY");
432  // Not supporting EMPTY polygons, return a dot polygon
433  return GeoTypesFactory::createGeoType("MULTIPOLYGON(((0 0,0.0000001 0,0 0.0000001)))",
434  false);
435  }
436  return GeoTypesFactory::createGeoType(result, false);
437 }
438 
439 // Run a specific geo operation on this and other geometries
440 std::unique_ptr<GeoBase> GeoBase::optimized_run(GeoBase::GeoOp op,
441  const GeoBase& other) const {
442  OGRGeometry* result = nullptr;
443  // Loop through polys combinations, check validity, do intersections
444  // where needed, return a union of all intersections
445  auto gc1 = geom_->toGeometryCollection();
446  auto gc2 = other.geom_->toGeometryCollection();
447  if (!gc1 || !gc2 || gc1->IsEmpty() || gc2->IsEmpty()) {
448  return nullptr;
449  }
450  for (int i1 = 0; i1 < gc1->getNumGeometries(); i1++) {
451  auto g1 = gc1->getGeometryRef(i1);
452  // Validity check is very slow
453  if (!g1 || g1->IsEmpty() /*|| !g1->IsValid()*/) {
454  continue;
455  }
456  OGREnvelope ge1;
457  g1->getEnvelope(&ge1);
458  for (int i2 = 0; i2 < gc2->getNumGeometries(); i2++) {
459  auto g2 = gc2->getGeometryRef(i2);
460  // Validity check is very slow
461  if (!g2 || g2->IsEmpty() /*|| !g2->IsValid()*/) {
462  continue;
463  }
464  // Check for bounding box overlap
465  OGREnvelope ge2;
466  g2->getEnvelope(&ge2);
467  if (!ge1.Intersects(ge2)) {
468  continue;
469  }
470  // Do intersection
471  auto intermediate_result = g1->Intersection(g2);
472  if (!intermediate_result || intermediate_result->IsEmpty()) {
473  continue;
474  }
475  if (!result) {
476  result = intermediate_result;
477  } else {
478  result = result->Union(intermediate_result);
479  }
480  }
481  }
482 
483  // TODO: Need to handle empty/non-POLYGON result
484  if (!result || result->IsEmpty() ||
485  !(result->getGeometryType() == wkbPolygon ||
486  result->getGeometryType() == wkbMultiPolygon)) {
487  // throw GeoTypesError(std::string(OGRGeometryTypeToName(geom_->getGeometryType())),
488  // "Currenly don't support invalid or empty result");
489  // return GeoTypesFactory::createGeoType("POLYGON EMPTY");
490  // Not supporting EMPTY polygons, return a dot polygon
491  return GeoTypesFactory::createGeoType("MULTIPOLYGON(((0 0,0.0000001 0,0 0.0000001)))",
492  false);
493  }
494  return GeoTypesFactory::createGeoType(result, false);
495 }
496 
497 // Run a specific geo operation on this geometry and a double param
498 std::unique_ptr<GeoBase> GeoBase::run(GeoBase::GeoOp op, double param) const {
499  OGRGeometry* result = nullptr;
500  switch (op) {
502  result = geom_->Buffer(param);
503  break;
504  default:
505  break;
506  }
507  // TODO: Need to handle empty/non-POLYGON result
508  if (!result || result->IsEmpty() ||
509  !(result->getGeometryType() == wkbPolygon ||
510  result->getGeometryType() == wkbMultiPolygon)) {
511  // throw GeoTypesError(std::string(OGRGeometryTypeToName(geom_->getGeometryType())),
512  // "Currenly don't support invalid or empty result");
513  // return GeoTypesFactory::createGeoType("POLYGON EMPTY");
514  // Not supporting EMPTY polygons, return a dot polygon
515  return GeoTypesFactory::createGeoType("MULTIPOLYGON(((0 0,0.0000001 0,0 0.0000001)))",
516  false);
517  }
518  return GeoTypesFactory::createGeoType(result, false);
519 }
520 
521 // Run a specific predicate operation on this geometry
522 bool GeoBase::run(GeoBase::GeoOp op) const {
523  auto result = false;
524  switch (op) {
526  result = geom_->IsValid();
527  break;
529  result = isEmpty();
530  break;
531  default:
532  break;
533  }
534  return result;
535 }
536 
537 std::unique_ptr<GeoBase> GeoPoint::clone() const {
538  CHECK(geom_);
539  return std::unique_ptr<GeoBase>(new GeoPoint(geom_->clone(), true));
540 }
541 
542 GeoPoint::GeoPoint(const std::vector<double>& coords) {
543  if (coords.size() != 2) {
544  throw GeoTypesError("Point",
545  "Incorrect coord size of " + std::to_string(coords.size()) +
546  " supplied. Expected 2.");
547  }
548  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbPoint);
549  OGRPoint* point = dynamic_cast<OGRPoint*>(geom_);
550  CHECK(point);
551  point->setX(coords[0]);
552  point->setY(coords[1]);
553 }
554 
555 GeoPoint::GeoPoint(const std::string& wkt) {
556  const auto err = GeoBase::createFromWktString(wkt, &geom_);
557  if (err != OGRERR_NONE) {
558  throw GeoTypesError("Point", err);
559  }
560  CHECK(geom_);
561  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbPoint) {
562  throw GeoTypesError("Point",
563  "Unexpected geometry type from WKT string: " +
564  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
565  }
566 }
567 
568 void GeoPoint::getColumns(std::vector<double>& coords) const {
569  const auto point_geom = dynamic_cast<OGRPoint*>(geom_);
570  CHECK(point_geom);
571 
572  if (point_geom->IsEmpty()) {
573  // until the run-time can handle empties
574  throw GeoTypesError("Point", "'EMPTY' not supported");
575  // we cannot yet handle NULL fixed-length array
576  // so we have to store sentinel values instead
577  coords.push_back(NULL_DOUBLE);
578  coords.push_back(NULL_DOUBLE);
579  return;
580  }
581 
582  coords.push_back(point_geom->getX());
583  coords.push_back(point_geom->getY());
584 }
585 
586 std::unique_ptr<GeoBase> GeoMultiPoint::clone() const {
587  CHECK(geom_);
588  return std::unique_ptr<GeoBase>(new GeoMultiPoint(geom_->clone(), true));
589 }
590 
591 GeoMultiPoint::GeoMultiPoint(const std::vector<double>& coords) {
592  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbMultiPoint);
593  OGRMultiPoint* multipoint = dynamic_cast<OGRMultiPoint*>(geom_);
594  CHECK(multipoint);
595  for (size_t i = 0; i < coords.size(); i += 2) {
596  OGRPoint pt(coords[i], coords[i + 1]);
597  multipoint->addGeometry(&pt);
598  }
599 }
600 
601 GeoMultiPoint::GeoMultiPoint(const std::string& wkt) {
602  const auto err = GeoBase::createFromWktString(wkt, &geom_);
603  if (err != OGRERR_NONE) {
604  throw GeoTypesError("MultiPoint", err);
605  }
606  CHECK(geom_);
607  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbMultiPoint) {
608  throw GeoTypesError("MultiPoint",
609  "Unexpected geometry type from WKT string: " +
610  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
611  }
612 }
613 
614 void GeoMultiPoint::getColumns(std::vector<double>& coords,
615  std::vector<double>& bounds) const {
616  auto multipoint_geom = dynamic_cast<OGRMultiPoint*>(geom_);
617  CHECK(multipoint_geom);
618 
619  if (multipoint_geom->IsEmpty()) {
620  // until the run-time can handle empties
621  throw GeoTypesError("MultiPoint", "'EMPTY' not supported");
622  // return null bounds
623  bounds.push_back(NULL_DOUBLE);
624  bounds.push_back(NULL_DOUBLE);
625  bounds.push_back(NULL_DOUBLE);
626  bounds.push_back(NULL_DOUBLE);
627  return;
628  }
629 
630  BoundingBox bbox;
631  for (auto i = 0; i < multipoint_geom->getNumGeometries(); i++) {
632  OGRPoint* point = dynamic_cast<OGRPoint*>(multipoint_geom->getGeometryRef(i));
633  CHECK(point);
634  double x = point->getX();
635  double y = point->getY();
636  coords.push_back(x);
637  coords.push_back(y);
638  bbox.update(x, y);
639  }
640  bounds.push_back(bbox.min.x);
641  bounds.push_back(bbox.min.y);
642  bounds.push_back(bbox.max.x);
643  bounds.push_back(bbox.max.y);
644 }
645 
646 std::unique_ptr<GeoBase> GeoLineString::clone() const {
647  CHECK(geom_);
648  return std::unique_ptr<GeoBase>(new GeoLineString(geom_->clone(), true));
649 }
650 
651 GeoLineString::GeoLineString(const std::vector<double>& coords) {
652  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbLineString);
653  OGRLineString* line = dynamic_cast<OGRLineString*>(geom_);
654  CHECK(line);
655  for (size_t i = 0; i < coords.size(); i += 2) {
656  line->addPoint(coords[i], coords[i + 1]);
657  }
658 }
659 
660 GeoLineString::GeoLineString(const std::string& wkt) {
661  const auto err = GeoBase::createFromWktString(wkt, &geom_);
662  if (err != OGRERR_NONE) {
663  throw GeoTypesError("LineString", err);
664  }
665  CHECK(geom_);
666  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbLineString) {
667  throw GeoTypesError("LineString",
668  "Unexpected geometry type from WKT string: " +
669  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
670  }
671 }
672 
673 void GeoLineString::getColumns(std::vector<double>& coords,
674  std::vector<double>& bounds) const {
675  auto linestring_geom = dynamic_cast<OGRLineString*>(geom_);
676  CHECK(linestring_geom);
677 
678  if (linestring_geom->IsEmpty()) {
679  // until the run-time can handle empties
680  throw GeoTypesError("LineString", "'EMPTY' not supported");
681  // return null bounds
682  bounds.push_back(NULL_DOUBLE);
683  bounds.push_back(NULL_DOUBLE);
684  bounds.push_back(NULL_DOUBLE);
685  bounds.push_back(NULL_DOUBLE);
686  return;
687  }
688 
689  BoundingBox bbox;
690  for (auto i = 0; i < linestring_geom->getNumPoints(); i++) {
691  OGRPoint point;
692  linestring_geom->getPoint(i, &point);
693  double x = point.getX();
694  double y = point.getY();
695  coords.push_back(x);
696  coords.push_back(y);
697  bbox.update(x, y);
698  }
699  bounds.push_back(bbox.min.x);
700  bounds.push_back(bbox.min.y);
701  bounds.push_back(bbox.max.x);
702  bounds.push_back(bbox.max.y);
703 }
704 
705 std::unique_ptr<GeoBase> GeoMultiLineString::clone() const {
706  CHECK(geom_);
707  return std::unique_ptr<GeoBase>(new GeoMultiLineString(geom_->clone(), true));
708 }
709 
710 GeoMultiLineString::GeoMultiLineString(const std::vector<double>& coords,
711  const std::vector<int32_t>& linestring_sizes) {
712  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbMultiLineString);
713  OGRMultiLineString* multilinestring = dynamic_cast<OGRMultiLineString*>(geom_);
714  CHECK(multilinestring);
715 
716  size_t coords_ctr = 0;
717  for (const auto linestring_sz : linestring_sizes) {
718  OGRLineString linestring;
719  auto next_coords_ctr = coords_ctr + 2 * linestring_sz;
720  CHECK(next_coords_ctr <= coords.size());
721  for (auto i = coords_ctr; i < next_coords_ctr; i += 2) {
722  linestring.addPoint(coords[i], coords[i + 1]);
723  }
724  coords_ctr = next_coords_ctr;
725  multilinestring->addGeometry(&linestring);
726  }
727 }
728 
729 GeoMultiLineString::GeoMultiLineString(const std::string& wkt) {
730  const auto err = GeoBase::createFromWktString(wkt, &geom_);
731  if (err != OGRERR_NONE) {
732  throw GeoTypesError("MultiLineString", err);
733  }
734  CHECK(geom_);
735  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbMultiLineString) {
736  throw GeoTypesError("MultiLineString",
737  "Unexpected geometry type from WKT string: " +
738  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
739  }
740 }
741 
742 void GeoMultiLineString::getColumns(std::vector<double>& coords,
743  std::vector<int32_t>& linestring_sizes,
744  std::vector<double>& bounds) const {
745  auto multilinestring = dynamic_cast<OGRMultiLineString*>(geom_);
746  CHECK(multilinestring);
747 
748  if (multilinestring->IsEmpty()) {
749  // until the run-time can handle empties
750  throw GeoTypesError("MultiLineString", "'EMPTY' not supported");
751  // return null bounds
752  bounds.push_back(NULL_DOUBLE);
753  bounds.push_back(NULL_DOUBLE);
754  bounds.push_back(NULL_DOUBLE);
755  bounds.push_back(NULL_DOUBLE);
756  return;
757  }
758 
759  BoundingBox bbox;
760  for (auto l = 0; l < multilinestring->getNumGeometries(); l++) {
761  const auto geom = multilinestring->getGeometryRef(l);
762  CHECK(geom);
763  const auto linestring = dynamic_cast<OGRLineString*>(geom);
764  if (!linestring) {
765  throw GeoTypesError("MultiLineString",
766  "Failed to read linestring geometry from multilinestring");
767  }
768  auto linestring_sz = linestring->getNumPoints();
769  linestring_sizes.push_back(linestring_sz);
770  for (auto i = 0; i < linestring_sz; i++) {
771  OGRPoint point;
772  linestring->getPoint(i, &point);
773  double x = point.getX();
774  double y = point.getY();
775  coords.push_back(x);
776  coords.push_back(y);
777  bbox.update(x, y);
778  }
779  }
780  bounds.push_back(bbox.min.x);
781  bounds.push_back(bbox.min.y);
782  bounds.push_back(bbox.max.x);
783  bounds.push_back(bbox.max.y);
784 }
785 
786 std::unique_ptr<GeoBase> GeoPolygon::clone() const {
787  CHECK(geom_);
788  return std::unique_ptr<GeoBase>(new GeoPolygon(geom_->clone(), true));
789 }
790 
791 GeoPolygon::GeoPolygon(const std::vector<double>& coords,
792  const std::vector<int32_t>& ring_sizes) {
793  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbPolygon);
794  OGRPolygon* poly = dynamic_cast<OGRPolygon*>(geom_);
795  CHECK(poly);
796 
797  size_t coords_ctr = 0;
798  for (size_t r = 0; r < ring_sizes.size(); r++) {
799  OGRLinearRing ring;
800  const auto ring_sz = ring_sizes[r];
801  for (auto i = 0; i < 2 * ring_sz; i += 2) {
802  ring.addPoint(coords[coords_ctr + i], coords[coords_ctr + i + 1]);
803  }
804  ring.addPoint(coords[coords_ctr], coords[coords_ctr + 1]);
805  coords_ctr += 2 * ring_sz;
806  poly->addRing(&ring);
807  }
808 }
809 
810 GeoPolygon::GeoPolygon(const std::string& wkt) {
811  const auto err = GeoBase::createFromWktString(wkt, &geom_);
812  if (err != OGRERR_NONE) {
813  throw GeoTypesError("Polygon", err);
814  }
815  CHECK(geom_);
816  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbPolygon) {
817  throw GeoTypesError("Polygon",
818  "Unexpected geometry type from WKT string: " +
819  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
820  }
821 }
822 
823 void GeoPolygon::getColumns(std::vector<double>& coords,
824  std::vector<int32_t>& ring_sizes,
825  std::vector<double>& bounds) const {
826  const auto poly_geom = dynamic_cast<OGRPolygon*>(geom_);
827  CHECK(poly_geom);
828 
829  if (poly_geom->IsEmpty()) {
830  // until the run-time can handle empties
831  throw GeoTypesError("Polygon", "'EMPTY' not supported");
832  // return null bounds
833  bounds.push_back(NULL_DOUBLE);
834  bounds.push_back(NULL_DOUBLE);
835  bounds.push_back(NULL_DOUBLE);
836  bounds.push_back(NULL_DOUBLE);
837  return;
838  }
839 
840  BoundingBox bbox;
841  const auto exterior_ring = poly_geom->getExteriorRing();
842  CHECK(exterior_ring);
843  // All exterior rings are imported CCW
844  if (exterior_ring->isClockwise()) {
845  exterior_ring->reverseWindingOrder();
846  }
847  const auto num_points_added = process_poly_ring(exterior_ring, coords, &bbox);
848  ring_sizes.push_back(num_points_added);
849  for (auto r = 0; r < poly_geom->getNumInteriorRings(); r++) {
850  auto interior_ring = poly_geom->getInteriorRing(r);
851  CHECK(interior_ring);
852  // All interior rings are imported CW
853  if (!interior_ring->isClockwise()) {
854  interior_ring->reverseWindingOrder();
855  }
856  const auto num_points_added = process_poly_ring(interior_ring, coords, nullptr);
857  ring_sizes.push_back(num_points_added);
858  }
859  bounds.push_back(bbox.min.x);
860  bounds.push_back(bbox.min.y);
861  bounds.push_back(bbox.max.x);
862  bounds.push_back(bbox.max.y);
863 }
864 
866  const auto poly_geom = dynamic_cast<OGRPolygon*>(geom_);
867  CHECK(poly_geom);
868  return poly_geom->getNumInteriorRings();
869 }
870 
871 std::unique_ptr<GeoBase> GeoMultiPolygon::clone() const {
872  CHECK(geom_);
873  return std::unique_ptr<GeoBase>(new GeoMultiPolygon(geom_->clone(), true));
874 }
875 
876 GeoMultiPolygon::GeoMultiPolygon(const std::vector<double>& coords,
877  const std::vector<int32_t>& ring_sizes,
878  const std::vector<int32_t>& poly_rings) {
879  geom_ = OGRGeometryFactory::createGeometry(OGRwkbGeometryType::wkbMultiPolygon);
880  OGRMultiPolygon* multipoly = dynamic_cast<OGRMultiPolygon*>(geom_);
881  CHECK(multipoly);
882 
883  size_t ring_ctr = 0;
884  size_t coords_ctr = 0;
885  for (const auto& rings_in_poly : poly_rings) {
886  OGRPolygon poly;
887  for (auto r = 0; r < rings_in_poly; r++) {
888  OGRLinearRing ring;
889  const auto ring_sz = ring_sizes[ring_ctr];
890  for (auto i = 0; i < 2 * ring_sz; i += 2) {
891  ring.addPoint(coords[coords_ctr + i], coords[coords_ctr + i + 1]);
892  }
893  ring.addPoint(coords[coords_ctr], coords[coords_ctr + 1]);
894  coords_ctr += 2 * ring_sz;
895  poly.addRing(&ring);
896  ring_ctr++;
897  }
898  multipoly->addGeometry(&poly);
899  }
900 }
901 
902 GeoMultiPolygon::GeoMultiPolygon(const std::string& wkt) {
903  const auto err = GeoBase::createFromWktString(wkt, &geom_);
904  if (err != OGRERR_NONE) {
905  throw GeoTypesError("MultiPolygon", err);
906  }
907  CHECK(geom_);
908  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbMultiPolygon) {
909  throw GeoTypesError("MultiPolygon",
910  "Unexpected geometry type from WKT string: " +
911  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
912  }
913 }
914 
915 void GeoMultiPolygon::getColumns(std::vector<double>& coords,
916  std::vector<int32_t>& ring_sizes,
917  std::vector<int32_t>& poly_rings,
918  std::vector<double>& bounds) const {
919  const auto mpoly = dynamic_cast<OGRMultiPolygon*>(geom_);
920  CHECK(mpoly);
921 
922  if (mpoly->IsEmpty()) {
923  // until the run-time can handle empties
924  throw GeoTypesError("MultiPolygon", "'EMPTY' not supported");
925  // return null bounds
926  bounds.push_back(NULL_DOUBLE);
927  bounds.push_back(NULL_DOUBLE);
928  bounds.push_back(NULL_DOUBLE);
929  bounds.push_back(NULL_DOUBLE);
930  return;
931  }
932 
933  BoundingBox bbox;
934  for (auto p = 0; p < mpoly->getNumGeometries(); p++) {
935  const auto mpoly_geom = mpoly->getGeometryRef(p);
936  CHECK(mpoly_geom);
937  const auto poly_geom = dynamic_cast<OGRPolygon*>(mpoly_geom);
938  if (!poly_geom) {
939  throw GeoTypesError("MultiPolygon",
940  "Failed to read polygon geometry from multipolygon");
941  }
942  const auto exterior_ring = poly_geom->getExteriorRing();
943  CHECK(exterior_ring);
944  // All exterior rings are imported CCW
945  if (exterior_ring->isClockwise()) {
946  exterior_ring->reverseWindingOrder();
947  }
948  const auto num_points_added = process_poly_ring(exterior_ring, coords, &bbox);
949  ring_sizes.push_back(num_points_added);
950 
951  for (auto r = 0; r < poly_geom->getNumInteriorRings(); r++) {
952  auto interior_ring = poly_geom->getInteriorRing(r);
953  CHECK(interior_ring);
954  // All interior rings are imported CW
955  if (!interior_ring->isClockwise()) {
956  interior_ring->reverseWindingOrder();
957  }
958  const auto num_points_added = process_poly_ring(interior_ring, coords, nullptr);
959  ring_sizes.push_back(num_points_added);
960  }
961  poly_rings.push_back(poly_geom->getNumInteriorRings() + 1);
962  }
963  bounds.push_back(bbox.min.x);
964  bounds.push_back(bbox.min.y);
965  bounds.push_back(bbox.max.x);
966  bounds.push_back(bbox.max.y);
967 }
968 
969 std::unique_ptr<GeoBase> GeoGeometry::clone() const {
970  CHECK(geom_);
971  return std::unique_ptr<GeoBase>(new GeoGeometry(geom_->clone(), true));
972 }
973 
974 std::unique_ptr<GeoBase> GeoGeometryCollection::clone() const {
975  CHECK(geom_);
976  return std::unique_ptr<GeoBase>(new GeoGeometryCollection(geom_->clone(), true));
977 }
978 
980  const auto err = GeoBase::createFromWktString(wkt, &geom_);
981  if (err != OGRERR_NONE) {
982  throw GeoTypesError("GeometryCollection", err);
983  }
984  CHECK(geom_);
985  if (wkbFlatten(geom_->getGeometryType()) != OGRwkbGeometryType::wkbGeometryCollection) {
986  throw GeoTypesError("GeometryCollection",
987  "Unexpected geometry type from WKT string: " +
988  std::string(OGRGeometryTypeToName(geom_->getGeometryType())));
989  }
990 }
991 
992 namespace {
993 
995  uint8_t table_[128];
996  constexpr HexDigitToDecimalTable() : table_{} {
997  table_[static_cast<int>('1')] = 1;
998  table_[static_cast<int>('2')] = 2;
999  table_[static_cast<int>('3')] = 3;
1000  table_[static_cast<int>('4')] = 4;
1001  table_[static_cast<int>('5')] = 5;
1002  table_[static_cast<int>('6')] = 6;
1003  table_[static_cast<int>('7')] = 7;
1004  table_[static_cast<int>('8')] = 8;
1005  table_[static_cast<int>('9')] = 9;
1006  table_[static_cast<int>('a')] = 10;
1007  table_[static_cast<int>('A')] = 10;
1008  table_[static_cast<int>('b')] = 11;
1009  table_[static_cast<int>('B')] = 11;
1010  table_[static_cast<int>('c')] = 12;
1011  table_[static_cast<int>('C')] = 12;
1012  table_[static_cast<int>('d')] = 13;
1013  table_[static_cast<int>('D')] = 13;
1014  table_[static_cast<int>('e')] = 14;
1015  table_[static_cast<int>('E')] = 14;
1016  table_[static_cast<int>('f')] = 15;
1017  table_[static_cast<int>('F')] = 15;
1018  }
1019  constexpr uint8_t operator[](const char& hex_digit) const {
1020  return (hex_digit < 0) ? 0 : table_[static_cast<int>(hex_digit)];
1021  }
1022 };
1023 
1025 
1026 inline uint8_t hex_to_binary(const char& usb, const char& lsb) {
1027  return (hex_digit_to_decimal_table[usb] << 4) | hex_digit_to_decimal_table[lsb];
1028 }
1029 
1030 std::vector<uint8_t> hex_string_to_binary_vector(const std::string& wkb_hex) {
1031  auto num_bytes = wkb_hex.size() >> 1;
1032  std::vector<uint8_t> wkb(num_bytes);
1033  auto* chars = wkb_hex.data();
1034  auto* bytes = wkb.data();
1035  for (size_t i = 0; i < num_bytes; i++) {
1036  auto const& usb = *chars++;
1037  auto const& lsb = *chars++;
1038  *bytes++ = hex_to_binary(usb, lsb);
1039  }
1040  return wkb;
1041 }
1042 
1043 bool validation_enabled_for_type(const OGRGeometry* geom) {
1044  auto const wkb_type = wkbFlatten(geom->getGeometryType());
1045  // only POLYGON and MULTIPOLYGON for now
1046  return (wkb_type == wkbPolygon) || (wkb_type == wkbMultiPolygon);
1047 }
1048 
1049 void validate_ogr(const OGRGeometry* geom) {
1050  CHECK(geom);
1052  auto const wkb_size = geom->WkbSize();
1053  std::vector<unsigned char> wkb(wkb_size);
1054  auto const err = geom->exportToWkb(wkbNDR, wkb.data());
1055  if (err == OGRERR_NONE && !geos_validate_wkb(wkb.data(), wkb_size)) {
1056  throw GeoTypesError("GeoFactory", "Geometry is invalid");
1057  }
1058  }
1059 }
1060 
1061 } // namespace
1062 
1064  const std::string& wkt_or_wkb_hex,
1065  const bool validate_with_geos_if_available) {
1066  OGRGeometry* geom = nullptr;
1067  OGRErr err = OGRERR_NONE;
1068  if (wkt_or_wkb_hex.empty()) {
1069  err = OGRERR_NOT_ENOUGH_DATA;
1070  } else if (wkt_or_wkb_hex[0] == '0') { // all WKB hex strings start with a 0
1071  auto const wkb = hex_string_to_binary_vector(wkt_or_wkb_hex);
1072  err = GeoBase::createFromWkbView(&geom, {wkb.data(), wkb.size()});
1073  } else {
1074  err = GeoBase::createFromWktString(wkt_or_wkb_hex, &geom);
1075  }
1076  if (err != OGRERR_NONE) {
1077  throw GeoTypesError("GeoFactory", err);
1078  }
1079  if (validate_with_geos_if_available) {
1080  validate_ogr(geom);
1081  }
1082  return geom;
1083 }
1084 
1085 std::unique_ptr<GeoBase> GeoTypesFactory::createGeoType(
1086  const std::string& wkt_or_wkb_hex,
1087  const bool validate_with_geos_if_available) {
1089  createOGRGeometry(wkt_or_wkb_hex, false)); // validate next
1090  if (validate_with_geos_if_available) {
1091  validate_ogr(g->getOGRGeometry());
1092  }
1093  return g;
1094 }
1095 
1096 std::unique_ptr<GeoBase> GeoTypesFactory::createGeoType(
1097  const WkbView wkb_view,
1098  const bool validate_with_geos_if_available) {
1099  OGRGeometry* geom = nullptr;
1100  const auto err = GeoBase::createFromWkbView(&geom, wkb_view);
1101  if (err != OGRERR_NONE) {
1102  throw GeoTypesError("GeoFactory", err);
1103  }
1104  auto g = GeoTypesFactory::createGeoTypeImpl(geom);
1105  if (validate_with_geos_if_available) {
1106  validate_ogr(g->getOGRGeometry());
1107  }
1108  return g;
1109 }
1110 
1111 std::unique_ptr<GeoBase> GeoTypesFactory::createGeoType(
1112  OGRGeometry* geom,
1113  const bool validate_with_geos_if_available) {
1114  auto g = GeoTypesFactory::createGeoTypeImpl(geom, false);
1115  if (validate_with_geos_if_available) {
1116  validate_ogr(g->getOGRGeometry());
1117  }
1118  return g;
1119 }
1120 
1121 bool GeoTypesFactory::getGeoColumns(const std::string& wkt_or_wkb_hex,
1122  SQLTypeInfo& ti,
1123  std::vector<double>& coords,
1124  std::vector<double>& bounds,
1125  std::vector<int>& ring_sizes,
1126  std::vector<int>& poly_rings,
1127  const bool validate_with_geos_if_available) {
1128  try {
1129  if (wkt_or_wkb_hex.empty() || wkt_or_wkb_hex == "NULL") {
1130  getNullGeoColumns(ti, coords, bounds, ring_sizes, poly_rings);
1131  return true;
1132  }
1133 
1134  const auto geospatial_base =
1135  GeoTypesFactory::createGeoType(wkt_or_wkb_hex, validate_with_geos_if_available);
1136 
1137  if (!geospatial_base || !geospatial_base->transform(ti)) {
1138  return false;
1139  }
1140 
1141  getGeoColumnsImpl(geospatial_base, ti, coords, bounds, ring_sizes, poly_rings);
1142 
1143  } catch (const std::exception& e) {
1144  LOG(ERROR) << "Geospatial Import Error: " << e.what();
1145  return false;
1146  }
1147 
1148  return true;
1149 }
1150 
1151 bool GeoTypesFactory::getGeoColumns(const std::vector<uint8_t>& wkb,
1152  SQLTypeInfo& ti,
1153  std::vector<double>& coords,
1154  std::vector<double>& bounds,
1155  std::vector<int>& ring_sizes,
1156  std::vector<int>& poly_rings,
1157  const bool validate_with_geos_if_available) {
1158  try {
1159  const auto geospatial_base = GeoTypesFactory::createGeoType(
1160  {wkb.data(), wkb.size()}, validate_with_geos_if_available);
1161 
1162  if (!geospatial_base || !geospatial_base->transform(ti)) {
1163  return false;
1164  }
1165 
1166  getGeoColumnsImpl(geospatial_base, ti, coords, bounds, ring_sizes, poly_rings);
1167 
1168  } catch (const std::exception& e) {
1169  LOG(ERROR) << "Geospatial Import Error: " << e.what();
1170  return false;
1171  }
1172 
1173  return true;
1174 }
1175 
1176 bool GeoTypesFactory::getGeoColumns(OGRGeometry* geom,
1177  SQLTypeInfo& ti,
1178  std::vector<double>& coords,
1179  std::vector<double>& bounds,
1180  std::vector<int>& ring_sizes,
1181  std::vector<int>& poly_rings,
1182  const bool validate_with_geos_if_available) {
1183  try {
1184  const auto geospatial_base =
1185  GeoTypesFactory::createGeoType(geom, validate_with_geos_if_available);
1186 
1187  if (!geospatial_base || !geospatial_base->transform(ti)) {
1188  return false;
1189  }
1190 
1191  getGeoColumnsImpl(geospatial_base, ti, coords, bounds, ring_sizes, poly_rings);
1192 
1193  } catch (const std::exception& e) {
1194  LOG(ERROR) << "Geospatial Import Error: " << e.what();
1195  return false;
1196  }
1197 
1198  return true;
1199 }
1200 
1201 bool GeoTypesFactory::getGeoColumns(const std::vector<std::string>* wkt_or_wkb_hex_column,
1202  SQLTypeInfo& ti,
1203  std::vector<std::vector<double>>& coords_column,
1204  std::vector<std::vector<double>>& bounds_column,
1205  std::vector<std::vector<int>>& ring_sizes_column,
1206  std::vector<std::vector<int>>& poly_rings_column,
1207  const bool validate_with_geos_if_available) {
1208  try {
1209  for (const auto& wkt_or_wkb_hex : *wkt_or_wkb_hex_column) {
1210  std::vector<double> coords;
1211  std::vector<double> bounds;
1212  std::vector<int> ring_sizes;
1213  std::vector<int> poly_rings;
1214 
1215  SQLTypeInfo row_ti{ti};
1216  getGeoColumns(wkt_or_wkb_hex,
1217  row_ti,
1218  coords,
1219  bounds,
1220  ring_sizes,
1221  poly_rings,
1222  validate_with_geos_if_available);
1223 
1224  if (!geo_promoted_type_match(row_ti.get_type(), ti.get_type())) {
1225  throw GeoTypesError("GeoFactory", "Columnar: Geometry type mismatch");
1226  }
1227  coords_column.push_back(coords);
1228  bounds_column.push_back(bounds);
1229  ring_sizes_column.push_back(ring_sizes);
1230  poly_rings_column.push_back(poly_rings);
1231  }
1232 
1233  } catch (const std::exception& e) {
1234  LOG(ERROR) << "Geospatial column Import Error: " << e.what();
1235  return false;
1236  }
1237 
1238  return true;
1239 }
1240 
1241 std::unique_ptr<GeoBase> GeoTypesFactory::createGeoTypeImpl(OGRGeometry* geom,
1242  const bool owns_geom_obj) {
1243  switch (wkbFlatten(geom->getGeometryType())) {
1244  case wkbPoint:
1245  return std::unique_ptr<GeoPoint>(new GeoPoint(geom, owns_geom_obj));
1246  case wkbMultiPoint:
1247  return std::unique_ptr<GeoMultiPoint>(new GeoMultiPoint(geom, owns_geom_obj));
1248  case wkbLineString:
1249  return std::unique_ptr<GeoLineString>(new GeoLineString(geom, owns_geom_obj));
1250  case wkbMultiLineString:
1251  return std::unique_ptr<GeoMultiLineString>(
1252  new GeoMultiLineString(geom, owns_geom_obj));
1253  case wkbPolygon:
1254  return std::unique_ptr<GeoPolygon>(new GeoPolygon(geom, owns_geom_obj));
1255  case wkbMultiPolygon:
1256  return std::unique_ptr<GeoMultiPolygon>(new GeoMultiPolygon(geom, owns_geom_obj));
1257  case wkbGeometryCollection:
1258  return std::unique_ptr<GeoGeometryCollection>(
1259  new GeoGeometryCollection(geom, owns_geom_obj));
1260  default:
1261  throw GeoTypesError(
1262  "GeoTypesFactory",
1263  "Unrecognized geometry type: " + std::string(geom->getGeometryName()));
1264  }
1265 }
1266 
1267 void GeoTypesFactory::getGeoColumnsImpl(const std::unique_ptr<GeoBase>& geospatial_base,
1268  SQLTypeInfo& ti,
1269  std::vector<double>& coords,
1270  std::vector<double>& bounds,
1271  std::vector<int>& ring_sizes,
1272  std::vector<int>& poly_rings) {
1273  switch (geospatial_base->getType()) {
1274  case GeoBase::GeoType::kPOINT: {
1275  const auto geospatial_point = dynamic_cast<GeoPoint*>(geospatial_base.get());
1276  CHECK(geospatial_point);
1277  geospatial_point->getColumns(coords);
1278  ti.set_type(kPOINT);
1279  // in case of promotion to MULTIPOINT
1280  CHECK_EQ(coords.size(), 2u);
1281  bounds.reserve(4);
1282  bounds.push_back(coords[0]);
1283  bounds.push_back(coords[1]);
1284  bounds.push_back(coords[0]);
1285  bounds.push_back(coords[1]);
1286  break;
1287  }
1289  const auto geospatial_multipoint =
1290  dynamic_cast<GeoMultiPoint*>(geospatial_base.get());
1291  CHECK(geospatial_multipoint);
1292  geospatial_multipoint->getColumns(coords, bounds);
1293  ti.set_type(kMULTIPOINT);
1294  break;
1295  }
1297  const auto geospatial_linestring =
1298  dynamic_cast<GeoLineString*>(geospatial_base.get());
1299  CHECK(geospatial_linestring);
1300  geospatial_linestring->getColumns(coords, bounds);
1301  ti.set_type(kLINESTRING);
1302  // in case of promotion to MULTILINESTRING
1303  ring_sizes.push_back(coords.size() / 2);
1304  break;
1305  }
1307  const auto geospatial_multilinestring =
1308  dynamic_cast<GeoMultiLineString*>(geospatial_base.get());
1309  CHECK(geospatial_multilinestring);
1310  geospatial_multilinestring->getColumns(coords, ring_sizes, bounds);
1312  break;
1313  }
1315  const auto geospatial_poly = dynamic_cast<GeoPolygon*>(geospatial_base.get());
1316  CHECK(geospatial_poly);
1317  geospatial_poly->getColumns(coords, ring_sizes, bounds);
1318  ti.set_type(kPOLYGON);
1319  // in case of promotion to MULTIPOLYGON
1320  if (ring_sizes.size()) {
1321  CHECK_GT(coords.size(), 0u);
1322  poly_rings.push_back(1 + geospatial_poly->getNumInteriorRings());
1323  } else {
1324  poly_rings.push_back(0);
1325  }
1326  break;
1327  }
1329  const auto geospatial_mpoly = dynamic_cast<GeoMultiPolygon*>(geospatial_base.get());
1330  CHECK(geospatial_mpoly);
1331  geospatial_mpoly->getColumns(coords, ring_sizes, poly_rings, bounds);
1332  ti.set_type(kMULTIPOLYGON);
1333  break;
1334  }
1337  default:
1338  throw std::runtime_error("Unrecognized geospatial type");
1339  }
1340 }
1341 
1343  std::vector<double>& coords,
1344  std::vector<double>& bounds,
1345  std::vector<int>& ring_sizes,
1346  std::vector<int>& poly_rings) {
1347  auto t = ti.get_type();
1348  switch (t) {
1349  case kPOINT: {
1350  // NULL fixlen coords array
1351  coords.push_back(NULL_ARRAY_DOUBLE);
1352  coords.push_back(NULL_DOUBLE);
1353  } break;
1354  case kMULTIPOINT:
1355  case kLINESTRING:
1356  case kMULTILINESTRING:
1357  case kPOLYGON:
1358  case kMULTIPOLYGON: {
1359  // Leaving coords array empty
1360  // NULL fixlen bounds array
1361  bounds.push_back(NULL_ARRAY_DOUBLE);
1362  bounds.push_back(NULL_DOUBLE);
1363  bounds.push_back(NULL_DOUBLE);
1364  bounds.push_back(NULL_DOUBLE);
1365  // Leaving ring_sizes and poly_rings arrays empty
1366  } break;
1367  default:
1368  throw std::runtime_error("Unsupported NULL geo");
1369  }
1370 }
1371 
1372 } // namespace Geospatial
OGRGeometry * geom_
Definition: Types.h:102
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:646
bool geo_promoted_type_match(const SQLTypes a, const SQLTypes b)
Definition: sqltypes.h:2029
std::unique_ptr< GeoBase > optimized_run(GeoOp op, const GeoBase &other) const
Definition: Types.cpp:440
#define CHECK_EQ(x, y)
Definition: Logger.h:301
#define NULL_DOUBLE
#define SRID_LAEA_START
Definition: Types.cpp:223
int32_t getNumInteriorRings() const
Definition: Types.cpp:865
#define SRID_NORTH_UTM_END
Definition: Types.cpp:213
uint8_t const * ptr_
Definition: Types.h:32
GeoMultiPolygon(const std::vector< double > &coords, const std::vector< int32_t > &ring_sizes, const std::vector< int32_t > &poly_rings)
Definition: Types.cpp:876
bool owns_geom_obj_
Definition: Types.h:103
constexpr auto DOUBLE_MAX
Definition: Types.cpp:41
#define LOG(tag)
Definition: Logger.h:285
constexpr HexDigitToDecimalTable hex_digit_to_decimal_table
Definition: Types.cpp:1024
GeoMultiPoint(const std::vector< double > &coords)
Definition: Types.cpp:591
GeoGeometryCollection(const std::vector< double > &coords, const std::vector< int32_t > &ring_sizes, const std::vector< int32_t > &poly_rings, const std::vector< int32_t > &geo_kinds)
Definition: Types.h:265
static void init()
Definition: GDAL.cpp:67
Constants for Builtin SQL Types supported by HEAVY.AI.
static void getNullGeoColumns(SQLTypeInfo &ti, std::vector< double > &coords, std::vector< double > &bounds, std::vector< int > &ring_sizes, std::vector< int > &poly_rings)
Definition: Types.cpp:1342
HOST DEVICE SQLTypes get_type() const
Definition: sqltypes.h:391
void validate_ogr(const OGRGeometry *geom)
Definition: Types.cpp:1049
static std::unique_ptr< Geospatial::GeoBase > createGeoTypeImpl(OGRGeometry *geom, const bool owns_geom_obj=true)
Definition: Types.cpp:1241
bool getWkb(std::vector< uint8_t > &) const
Definition: Types.cpp:187
void getColumns(std::vector< double > &coords) const
Definition: Types.cpp:568
#define CHECK_GT(x, y)
Definition: Logger.h:305
constexpr uint8_t operator[](const char &hex_digit) const
Definition: Types.cpp:1019
static std::shared_ptr< OGRCoordinateTransformation > getTransformation(int32_t srid0, int32_t srid1)
Definition: Types.cpp:308
std::string to_string(char const *&&v)
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:871
GeoMultiLineString(const std::vector< double > &coords, const std::vector< int32_t > &linestring_sizes)
Definition: Types.cpp:710
bool geos_validate_wkb(const unsigned char *, const size_t)
bool validation_enabled_for_type(const OGRGeometry *geom)
Definition: Types.cpp:1043
bool geos_validation_available()
bool isEmpty() const
Definition: Types.cpp:197
int process_poly_ring(OGRLinearRing *ring, std::vector< double > &coords, BoundingBox *bbox)
Definition: Types.cpp:71
virtual ~GeoBase()
Definition: Types.cpp:146
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:705
static int createFromWkbView(OGRGeometry **geom, WkbView const)
Definition: Types.cpp:174
static void getGeoColumnsImpl(const std::unique_ptr< GeoBase > &geospatial_base, SQLTypeInfo &ti, std::vector< double > &coords, std::vector< double > &bounds, std::vector< int > &ring_sizes, std::vector< int > &poly_rings)
Definition: Types.cpp:1267
void getColumns(std::vector< double > &coords, std::vector< double > &bounds) const
Definition: Types.cpp:614
void update(double x, double y)
Definition: Types.cpp:55
static bool getGeoColumns(const std::string &wkt_or_wkb_hex, SQLTypeInfo &ti, std::vector< double > &coords, std::vector< double > &bounds, std::vector< int > &ring_sizes, std::vector< int > &poly_rings, const bool validate_with_geos_if_available)
Definition: Types.cpp:1121
static std::string OGRErrorToStr(const int ogr_err)
Definition: Types.cpp:119
GeoGeometry(const std::vector< double > &coords, const std::vector< int32_t > &ring_sizes, const std::vector< int32_t > &poly_rings, const std::vector< int32_t > &geo_kinds)
Definition: Types.h:237
#define SRID_NORTH_UTM_START
Definition: Types.cpp:211
void getColumns(std::vector< double > &coords, std::vector< int32_t > &ring_sizes, std::vector< int32_t > &poly_rings, std::vector< double > &bounds) const
Definition: Types.cpp:915
std::unique_ptr< GeoBase > clone() const override
Definition: Types.cpp:537
void getColumns(std::vector< double > &coords, std::vector< double > &bounds) const
Definition: Types.cpp:673
virtual bool operator==(const GeoBase &other) const
Definition: Types.cpp:201
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:786
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:586
static std::unique_ptr< GeoBase > createGeoType(const std::string &wkt_or_wkb_hex, const bool validate_with_geos_if_available)
Definition: Types.cpp:1085
static int createFromWktString(const std::string &wkt, OGRGeometry **geom)
Definition: Types.cpp:155
tuple line
Definition: parse_ast.py:10
void getColumns(std::vector< double > &coords, std::vector< int32_t > &linestring_sizes, std::vector< double > &bounds) const
Definition: Types.cpp:742
static OGRGeometry * createOGRGeometry(const std::string &wkt_or_wkb_hex, const bool validate_with_geos_if_available)
Definition: Types.cpp:1063
std::mutex transformation_map_mutex_
Definition: Types.cpp:115
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:969
constexpr auto DOUBLE_MIN
Definition: Types.cpp:42
HOST DEVICE int get_input_srid() const
Definition: sqltypes.h:395
bool transform(int32_t srid0, int32_t srid1)
Definition: Types.cpp:384
#define NULL_ARRAY_DOUBLE
#define SRID_SOUTH_UTM_START
Definition: Types.cpp:217
#define SRID_SOUTH_UTM_END
Definition: Types.cpp:219
#define CHECK(condition)
Definition: Logger.h:291
std::string getWktString() const
Definition: Types.cpp:165
#define SRID_SOUTH_LAMBERT
Definition: Types.cpp:221
#define SRID_WORLD_MERCATOR
Definition: Types.cpp:209
GeoPoint(const std::vector< double > &coords)
Definition: Types.cpp:542
#define SRID_LAEA_END
Definition: Types.cpp:225
uint8_t hex_to_binary(const char &usb, const char &lsb)
Definition: Types.cpp:1026
std::unique_ptr< GeoBase > run(GeoOp op, const GeoBase &other) const
Definition: Types.cpp:407
#define SRID_NORTH_LAMBERT
Definition: Types.cpp:215
std::map< std::tuple< int32_t, int32_t >, std::shared_ptr< OGRCoordinateTransformation > > transformation_map_
Definition: Types.cpp:117
std::unique_ptr< GeoBase > clone() const final
Definition: Types.cpp:974
size_t size_
Definition: Types.h:33
std::vector< uint8_t > hex_string_to_binary_vector(const std::string &wkb_hex)
Definition: Types.cpp:1030
void getColumns(std::vector< double > &coords, std::vector< int32_t > &ring_sizes, std::vector< double > &bounds) const
Definition: Types.cpp:823
int32_t getBestPlanarSRID() const
Definition: Types.cpp:227
GeoPolygon(const std::vector< double > &coords, const std::vector< int32_t > &ring_sizes)
Definition: Types.cpp:791
HOST DEVICE int get_output_srid() const
Definition: sqltypes.h:397
GeoLineString(const std::vector< double > &coords)
Definition: Types.cpp:651
HOST DEVICE void set_type(SQLTypes t)
Definition: sqltypes.h:468