Skip to content

Commit fafef65

Browse files
committed
shapefile reader bug fix, multipolygons support (correctly materialize shapefile's polygons with more than one ring)
1 parent bc30c41 commit fafef65

File tree

5 files changed

+244
-61
lines changed

5 files changed

+244
-61
lines changed

fdaPDE/geoframe/shp.h

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,22 @@ template <typename T> T read(char* buff, int& head, tag_little_endian) {
5252
class shp_reader {
5353
void skip(int n_bytes) { head_ += n_bytes; } // move head_ n_bytes forward
5454

55-
struct sf_header_t {
55+
struct sf_header_t_ {
5656
static constexpr int size = 100; // the size (in number of bytes) of the header
5757
int file_code; // file code: 9994
5858
int file_length; // total length of the file in 8-bit words
5959
int version; // shapefile version: 1000
6060
std::array<double, 8> bbox; // x_min, y_min, x_max, y_max, z_min, z_max, m_min, m_max
6161
int shape_type;
6262
};
63-
struct sf_point_t {
63+
struct sf_point_t_ {
6464
int shape_type;
6565
int record_number;
6666
double x, y, z, m; // point coordinates (z and m optional)
6767

68-
sf_point_t() = default;
69-
sf_point_t(double x_, double y_) : x(x_), y(y_) { }
70-
sf_point_t(int record_number_, shp_reader& file) :
68+
sf_point_t_() = default;
69+
sf_point_t_(double x_, double y_) : x(x_), y(y_) { }
70+
sf_point_t_(int record_number_, shp_reader& file) :
7171
shape_type(file.shape_type()), record_number(record_number_) {
7272
// point specific content
7373
x = read<double>(file.buff_, file.head_, LittleEndian);
@@ -78,8 +78,16 @@ class shp_reader {
7878
m = read<double>(file.buff_, file.head_, LittleEndian);
7979
}
8080
}
81+
friend bool operator==(const sf_point_t_& lhs, const sf_point_t_& rhs) {
82+
if (lhs.shape_type != rhs.shape_type) { return false; }
83+
bool equal = (lhs.x == rhs.x) && (lhs.y == rhs.y);
84+
if (lhs.shape_type == shape_t::PointM) { equal &= (lhs.m == rhs.m); }
85+
if (lhs.shape_type == shape_t::PointZ) { equal &= ((lhs.m == rhs.m) && (lhs.z == rhs.z)); }
86+
return equal;
87+
}
88+
friend bool operator!=(const sf_point_t_& lhs, const sf_point_t_& rhs) { return !(lhs == rhs); }
8189
};
82-
struct sf_multipoint_t {
90+
struct sf_multipoint_t_ {
8391
private:
8492
void read_zm_block(int n_points, std::array<double, 2>& range, std::vector<double> points, shp_reader& file) {
8593
points.resize(n_points);
@@ -95,8 +103,8 @@ class shp_reader {
95103
int n_points; // number of points in the record
96104
std::vector<double> x, y, z, m; // points coordinates (z and m optional)
97105

98-
sf_multipoint_t() = default;
99-
sf_multipoint_t(int record_number_, shp_reader& file) :
106+
sf_multipoint_t_() = default;
107+
sf_multipoint_t_(int record_number_, shp_reader& file) :
100108
shape_type(file.shape_type()), record_number(record_number_) {
101109
// multipoint specific content
102110
for (int i = 0; i < 4; ++i) { bbox[i] = read<double>(file.buff_, file.head_, LittleEndian); }
@@ -114,10 +122,10 @@ class shp_reader {
114122
}
115123
}
116124
};
117-
struct sf_polygon_t {
125+
struct sf_polygon_t_ {
118126
private:
119127
void
120-
read_zm_block(int n_points, std::array<double, 2>& range, std::vector<sf_point_t>& points, shp_reader& file) {
128+
read_zm_block(int n_points, std::array<double, 2>& range, std::vector<sf_point_t_>& points, shp_reader& file) {
121129
range[0] = read<double>(file.buff_, file.head_, LittleEndian);
122130
range[1] = read<double>(file.buff_, file.head_, LittleEndian);
123131
for (int i = 0; i < n_points; ++i) {
@@ -137,10 +145,10 @@ class shp_reader {
137145
int n_rings; // number of closed polygons in the record
138146
int n_points; // overall number of points
139147
std::vector<int> ring_begin, ring_end; // first and last points in each ring, as offsets in points vector
140-
std::vector<sf_point_t> points; // points coordinates (z and m optional)
148+
std::vector<sf_point_t_> points; // points coordinates (z and m optional)
141149

142-
sf_polygon_t() = default;
143-
sf_polygon_t(int record_number_, shp_reader& file) :
150+
sf_polygon_t_() = default;
151+
sf_polygon_t_(int record_number_, shp_reader& file) :
144152
shape_type(file.shape_type()), record_number(record_number_) {
145153
// polygon specific content
146154
for (int i = 0; i < 4; ++i) { bbox[i] = read<double>(file.buff_, file.head_, LittleEndian); }
@@ -175,17 +183,17 @@ class shp_reader {
175183
}
176184
// iterator over rings
177185
class ring_iterator {
178-
const sf_polygon_t* polygon_;
186+
const sf_polygon_t_* polygon_;
179187
int index_;
180-
std::vector<sf_point_t>::const_iterator it;
188+
std::vector<sf_point_t_>::const_iterator it;
181189
public:
182-
ring_iterator(const sf_polygon_t* polygon, int index) : polygon_(polygon), index_(index) {
190+
ring_iterator(const sf_polygon_t_* polygon, int index) : polygon_(polygon), index_(index) {
183191
it = polygon_->points.begin();
184192
}
185-
std::vector<sf_point_t>::const_iterator begin() const { return it + polygon_->ring_begin[index_]; }
186-
std::vector<sf_point_t>::const_iterator end() const { return it + polygon_->ring_end[index_]; }
187-
const sf_point_t* operator->() { return it.operator->(); }
188-
const sf_point_t& operator*() { return *it; };
193+
std::vector<sf_point_t_>::const_iterator begin() const { return it + polygon_->ring_begin[index_]; }
194+
std::vector<sf_point_t_>::const_iterator end() const { return it + polygon_->ring_end[index_]; }
195+
const sf_point_t_* operator->() { return it.operator->(); }
196+
const sf_point_t_& operator*() { return *it; };
189197
ring_iterator& operator++() {
190198
index_++;
191199
return *this;
@@ -194,31 +202,41 @@ class shp_reader {
194202
return it1.index_ != it2.index_;
195203
}
196204
int n_nodes() const { return polygon_->ring_end[index_] - polygon_->ring_begin[index_]; }
197-
// RowMajor expansion of polygon coordinates
198-
Eigen::Matrix<double, Dynamic, Dynamic> nodes() {
199-
int n_rows = n_nodes();
205+
// RowMajor expansion of ring coordinates (already discards last point, as it coincides with end point)
206+
Eigen::Matrix<double, Dynamic, Dynamic> nodes() const {
207+
int n_rows = n_nodes() - (end() != polygon_->points.end() ? 0 : 1);
200208
int n_cols =
201209
2 +
202210
(polygon_->shape_type == shape_t::PointM ? 1 : (polygon_->shape_type == shape_t::PointZ ? 2 : 0));
203211
Eigen::Matrix<double, Dynamic, Dynamic> coords(n_rows, n_cols);
204212
int row = 0;
205-
for (auto jt = begin(), ht = end(); jt != ht; ++jt) {
213+
for (auto jt = begin(), ht = end(); jt != ht && row < n_rows; ++jt) {
206214
coords(row, 0) = jt->x;
207215
coords(row, 1) = jt->y;
208216
if (polygon_->shape_type == shape_t::PointM) { coords(row, 2) = jt->m; }
209-
if (polygon_->shape_type == shape_t::PointZ) { coords(row, 3) = jt->z; }
210-
row++;
217+
if (polygon_->shape_type == shape_t::PointZ) {
218+
coords(row, 2) = jt->m;
219+
coords(row, 3) = jt->z;
220+
}
221+
row++;
211222
}
212223
return coords;
213224
}
214225
};
215226
ring_iterator begin() const { return ring_iterator(this, 0); }
216227
ring_iterator end() const { return ring_iterator(this, n_rings); }
228+
// provides all nodes coordinates, orgnanized by rings
229+
std::vector<Eigen::Matrix<double, Dynamic, Dynamic>> nodes() const {
230+
std::vector<Eigen::Matrix<double, Dynamic, Dynamic>> nodes_;
231+
nodes_.reserve(n_rings);
232+
for (ring_iterator it = begin(); it != end(); ++it) { nodes_.push_back(it.nodes()); }
233+
return nodes_;
234+
}
217235
};
218236
// a polyline has the same layout of a polygon, where rings are not necessarily closed (and can self-intersect)
219-
struct sf_polyline_t : public sf_polygon_t {
220-
sf_polyline_t() = default;
221-
sf_polyline_t(int record_number_, shp_reader& file) : sf_polygon_t(record_number_, file) { }
237+
struct sf_polyline_t_ : public sf_polygon_t_ {
238+
sf_polyline_t_() = default;
239+
sf_polyline_t_(int record_number_, shp_reader& file) : sf_polygon_t(record_number_, file) { }
222240
};
223241

224242
template <typename T> void read_into(T& container) {
@@ -232,15 +250,19 @@ class shp_reader {
232250

233251
std::string file_name_;
234252
int n_records_;
235-
sf_header_t header_; // shapefile header
253+
sf_header_t_ header_; // shapefile header
236254
int head_ = 0; // currently pointed byte in buff_
237255
char* buff_ = nullptr; // loaded binary data
238256
// only one of the following containers is active (by shapefile specification)
239-
std::vector<sf_point_t> points_;
240-
std::vector<sf_polyline_t> polylines_;
241-
std::vector<sf_polygon_t> polygons_;
242-
std::vector<sf_multipoint_t> multipoints_;
257+
std::vector<sf_point_t_> points_;
258+
std::vector<sf_polyline_t_> polylines_;
259+
std::vector<sf_polygon_t_> polygons_;
260+
std::vector<sf_multipoint_t_> multipoints_;
243261
public:
262+
using sf_point_t = sf_point_t_;
263+
using sf_polyline_t = sf_polyline_t_;
264+
using sf_polygon_t = sf_polygon_t_;
265+
using sf_multipoint_t = sf_multipoint_t_;
244266
// supported shapefile format
245267
// all the non-null shapes in a shapefile are required to be of the same shape type (cit. Shapefile standard)
246268
enum shape_t {
@@ -287,27 +309,24 @@ class shp_reader {
287309
int shape_type() const { return header_.shape_type; }
288310
std::array<double, 4> bbox() const { return {header_.bbox[0], header_.bbox[1], header_.bbox[2], header_.bbox[3]}; }
289311
int n_records() const { return n_records_; }
290-
const sf_header_t& header() const { return header_; }
312+
const sf_header_t_& header() const { return header_; }
291313
int n_points() const { return points_.size(); }
292314
const sf_point_t& point(int index) const {
293315
fdapde_assert(
294316
shape_type() == shape_t::Point || shape_type() == shape_t::PointZ || shape_type() == shape_t::PointM);
295317
return points_[index];
296318
}
297-
int n_polylines() const { return polylines_.size(); }
298319
const sf_polyline_t& polyline(int index) const {
299320
fdapde_assert(
300321
shape_type() == shape_t::PolyLine || shape_type() == shape_t::PolyLineZ ||
301322
shape_type() == shape_t::PolyLineM);
302323
return polylines_[index];
303324
}
304-
int n_polygons() const { return polygons_.size(); }
305325
const sf_polygon_t& polygon(int index) const {
306326
fdapde_assert(
307327
shape_type() == shape_t::Polygon || shape_type() == shape_t::PolygonZ || shape_type() == shape_t::PolygonM);
308328
return polygons_[index];
309329
}
310-
int n_multipoints() const { return multipoints_.size(); }
311330
const sf_multipoint_t& multipoint(int index) const {
312331
fdapde_assert(
313332
shape_type() == shape_t::MultiPoint || shape_type() == shape_t::MultiPointZ ||
@@ -417,7 +436,14 @@ class ShapeFile {
417436
std::string gcs_ = "UNDEFINED"; // geographic coordinate system (GCS)
418437
std::string filename_;
419438
public:
420-
ShapeFile(std::string filename) : filename_(filename) {
439+
ShapeFile(std::string filename) : filename_() {
440+
std::filesystem::path filepath(filename);
441+
if (!std::filesystem::exists(filepath)) { throw std::runtime_error("File " + filename + " not found."); }
442+
if (filepath.extension() == ".shp") {
443+
filename_ = filepath.parent_path() / filepath.stem();
444+
} else {
445+
throw std::runtime_error(filename + "is not a .shp file.");
446+
}
421447
// load geometric features and associated data
422448
shp_ = shp_reader(filename_ + ".shp");
423449
dbf_ = dbf_reader(filename_ + ".dbf");
@@ -433,10 +459,20 @@ class ShapeFile {
433459
gcs_ = line.substr(i, j - i);
434460
}
435461
}
436-
// getters
462+
// observers
437463
const shp_reader& shp() const { return shp_; }
438464
template <typename T> std::vector<T> get_as(std::string colname) const { return dbf_.get_as<T>(colname); }
439465
std::vector<std::pair<std::string, char>> field_descriptors() const { return dbf_.field_descriptors(); }
466+
int shape_type() const { return shp_.shape_type(); }
467+
int n_records() const { return shp_.n_records(); }
468+
// geometry
469+
const auto& point(int index) const { return shp_.point(index); }
470+
const auto& polyline(int index) const { return shp_.polyline(index); }
471+
const auto& polygon(int index) const { return shp_.polygon(index); }
472+
const auto& multipoint(int index) const { return shp_.multipoint(index); }
473+
// accessor
474+
template <typename T> std::vector<T> get(std::string colname) const { return dbf_.get_as<T>(colname); }
475+
// output stream
440476
friend std::ostream& operator<<(std::ostream& os, const ShapeFile& sf) {
441477
os << "file: " << sf.filename_ << std::endl;
442478
std::string shape_type;

fdaPDE/geometry/dcel.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
#ifndef __DCEL_H__
1818
#define __DCEL_H__
1919

20-
namespace fdapde {
20+
#include "../utils/traits.h"
21+
#include "../utils/symbols.h"
2122

23+
namespace fdapde {
24+
2225
// implementation of the Double Connected Edge List data structure (also known as DCEL or half-edge)
2326
template <int LocalDim, int EmbedDim> class DCEL {
2427
public:

0 commit comments

Comments
 (0)