From 83c0db36cbf98fa56730c43a16ceab09f94879b8 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 9 Apr 2014 19:40:14 -0400 Subject: [PATCH] add support for rendering (vs skipping) 3D and 4D postgis geometries like POINTZM, LINESTRINGZM, and POLYGONZM - closes #44 --- src/wkb.cpp | 150 ++++++++++++++++- tests/python_tests/postgis_test.py | 254 +++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+), 2 deletions(-) diff --git a/src/wkb.cpp b/src/wkb.cpp index 65f6145df..aceebf204 100644 --- a/src/wkb.cpp +++ b/src/wkb.cpp @@ -62,14 +62,31 @@ public: wkbMultiLineString=5, wkbMultiPolygon=6, wkbGeometryCollection=7, + // Z wkbPointZ=1001, wkbLineStringZ=1002, wkbPolygonZ=1003, wkbMultiPointZ=1004, wkbMultiLineStringZ=1005, wkbMultiPolygonZ=1006, - wkbGeometryCollectionZ=1007 - }; + wkbGeometryCollectionZ=1007, + // M + wkbPointM=2001, + wkbLineStringM=2002, + wkbPolygonM=2003, + wkbMultiPointM=2004, + wkbMultiLineStringM=2005, + wkbMultiPolygonM=2006, + wkbGeometryCollectionM=2007, + // ZM + wkbPointZM=3001, + wkbLineStringZM=3002, + wkbPolygonZM=3003, + wkbMultiPointZM=3004, + wkbMultiLineStringZM=3005, + wkbMultiPolygonZM=3006, + wkbGeometryCollectionZM=3007 + }; wkb_reader(const char* wkb, std::size_t size, wkbFormat format) : wkb_(wkb), @@ -142,24 +159,50 @@ public: read_collection(paths); break; case wkbPointZ: + case wkbPointM: read_point_xyz(paths); break; + case wkbPointZM: + read_point_xyzm(paths); + break; case wkbLineStringZ: + case wkbLineStringM: read_linestring_xyz(paths); break; + case wkbLineStringZM: + read_linestring_xyzm(paths); + break; case wkbPolygonZ: + case wkbPolygonM: read_polygon_xyz(paths); break; + case wkbPolygonZM: + read_polygon_xyzm(paths); + break; case wkbMultiPointZ: + case wkbMultiPointM: read_multipoint_xyz(paths); break; + case wkbMultiPointZM: + read_multipoint_xyzm(paths); + break; case wkbMultiLineStringZ: + case wkbMultiLineStringM: read_multilinestring_xyz(paths); break; + case wkbMultiLineStringZM: + read_multilinestring_xyzm(paths); + break; case wkbMultiPolygonZ: + case wkbMultiPolygonM: read_multipolygon_xyz(paths); break; + case wkbMultiPolygonZM: + read_multipolygon_xyzm(paths); + break; case wkbGeometryCollectionZ: + case wkbGeometryCollectionM: + case wkbGeometryCollectionZM: read_collection(paths); break; default: @@ -245,6 +288,27 @@ private: } } + void read_coords_xyzm(CoordinateArray& ar) + { + if (! needSwap_) + { + for (unsigned i = 0; i < ar.size(); ++i) + { + read_double_ndr(wkb_ + pos_, ar[i].x); + read_double_ndr(wkb_ + pos_ + 8, ar[i].y); + pos_ += 32; // skip XYZM + } + } + else + { + for (unsigned i = 0; i < ar.size(); ++i) + { + read_double_xdr(wkb_ + pos_, ar[i].x); + read_double_xdr(wkb_ + pos_ + 8, ar[i].y); + pos_ += 32; // skip XYZM + } + } + } void read_point(boost::ptr_vector & paths) { @@ -275,6 +339,16 @@ private: paths.push_back(pt); } + void read_point_xyzm(boost::ptr_vector & paths) + { + double x = read_double(); + double y = read_double(); + std::auto_ptr pt(new geometry_type(Point)); + pos_ += 16; + pt->move_to(x, y); + paths.push_back(pt); + } + void read_multipoint_xyz(boost::ptr_vector & paths) { int num_points = read_integer(); @@ -285,6 +359,16 @@ private: } } + void read_multipoint_xyzm(boost::ptr_vector & paths) + { + int num_points = read_integer(); + for (int i = 0; i < num_points; ++i) + { + pos_ += 5; + read_point_xyzm(paths); + } + } + void read_linestring(boost::ptr_vector & paths) { int num_points = read_integer(); @@ -329,6 +413,23 @@ private: } } + void read_linestring_xyzm(boost::ptr_vector & paths) + { + int num_points = read_integer(); + if (num_points > 0) + { + CoordinateArray ar(num_points); + read_coords_xyzm(ar); + std::auto_ptr line(new geometry_type(LineString)); + line->move_to(ar[0].x, ar[0].y); + for (int i = 1; i < num_points; ++i) + { + line->line_to(ar[i].x, ar[i].y); + } + paths.push_back(line); + } + } + void read_multilinestring_xyz(boost::ptr_vector & paths) { int num_lines = read_integer(); @@ -339,6 +440,15 @@ private: } } + void read_multilinestring_xyzm(boost::ptr_vector & paths) + { + int num_lines = read_integer(); + for (int i = 0; i < num_lines; ++i) + { + pos_ += 5; + read_linestring_xyzm(paths); + } + } void read_polygon(boost::ptr_vector & paths) { @@ -402,6 +512,32 @@ private: } } + void read_polygon_xyzm(boost::ptr_vector & paths) + { + int num_rings = read_integer(); + if (num_rings > 0) + { + std::auto_ptr poly(new geometry_type(Polygon)); + for (int i = 0; i < num_rings; ++i) + { + int num_points = read_integer(); + if (num_points > 0) + { + CoordinateArray ar(num_points); + read_coords_xyzm(ar); + poly->move_to(ar[0].x, ar[0].y); + for (int j = 1; j < num_points; ++j) + { + poly->line_to(ar[j].x, ar[j].y); + } + poly->close_path(); + } + } + if (poly->size() > 2) // ignore if polygon has less than 3 vertices + paths.push_back(poly); + } + } + void read_multipolygon_xyz(boost::ptr_vector & paths) { int num_polys = read_integer(); @@ -412,6 +548,16 @@ private: } } + void read_multipolygon_xyzm(boost::ptr_vector & paths) + { + int num_polys = read_integer(); + for (int i = 0; i < num_polys; ++i) + { + pos_ += 5; + read_polygon_xyzm(paths); + } + } + void read_collection(boost::ptr_vector & paths) { int num_geometries = read_integer(); diff --git a/tests/python_tests/postgis_test.py b/tests/python_tests/postgis_test.py index 256801c3e..d90bcb714 100644 --- a/tests/python_tests/postgis_test.py +++ b/tests/python_tests/postgis_test.py @@ -164,6 +164,34 @@ INSERT INTO test11(label,geom) values ('label_7',GeomFromEWKT('SRID=4326;MULTIPO INSERT INTO test11(label,geom) values ('label_8',GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION(POLYGON((1 1, 2 1, 2 2, 1 2,1 1)),POINT(2 3),LINESTRING(2 3,3 4))')); """ +insert_table_12 = """ +CREATE TABLE test12(gid serial PRIMARY KEY, name varchar(40), geom geometry); +INSERT INTO test12(name,geom) values ('Point',GeomFromEWKT('SRID=4326;POINT(0 0)')); +INSERT INTO test12(name,geom) values ('PointZ',GeomFromEWKT('SRID=4326;POINTZ(0 0 0)')); +INSERT INTO test12(name,geom) values ('PointM',GeomFromEWKT('SRID=4326;POINTM(0 0 0)')); +INSERT INTO test12(name,geom) values ('PointZM',GeomFromEWKT('SRID=4326;POINTZM(0 0 0 0)')); +INSERT INTO test12(name,geom) values ('MultiPoint',GeomFromEWKT('SRID=4326;MULTIPOINT(0 0, 1 1)')); +INSERT INTO test12(name,geom) values ('MultiPointZ',GeomFromEWKT('SRID=4326;MULTIPOINTZ(0 0 0, 1 1 1)')); +INSERT INTO test12(name,geom) values ('MultiPointM',GeomFromEWKT('SRID=4326;MULTIPOINTM(0 0 0, 1 1 1)')); +INSERT INTO test12(name,geom) values ('MultiPointZM',GeomFromEWKT('SRID=4326;MULTIPOINTZM(0 0 0 0, 1 1 1 1)')); +INSERT INTO test12(name,geom) values ('LineString',GeomFromEWKT('SRID=4326;LINESTRING(0 0, 1 1)')); +INSERT INTO test12(name,geom) values ('LineStringZ',GeomFromEWKT('SRID=4326;LINESTRINGZ(0 0 0, 1 1 1)')); +INSERT INTO test12(name,geom) values ('LineStringM',GeomFromEWKT('SRID=4326;LINESTRINGM(0 0 0, 1 1 1)')); +INSERT INTO test12(name,geom) values ('LineStringZM',GeomFromEWKT('SRID=4326;LINESTRINGZM(0 0 0 0, 1 1 1 1)')); +INSERT INTO test12(name,geom) values ('Polygon',GeomFromEWKT('SRID=4326;POLYGON((0 0, 1 1, 2 2, 0 0))')); +INSERT INTO test12(name,geom) values ('PolygonZ',GeomFromEWKT('SRID=4326;POLYGONZ((0 0 0, 1 1 1, 2 2 2, 0 0 0))')); +INSERT INTO test12(name,geom) values ('PolygonM',GeomFromEWKT('SRID=4326;POLYGONZ((0 0 0, 1 1 1, 2 2 2, 0 0 0))')); +INSERT INTO test12(name,geom) values ('PolygonZM',GeomFromEWKT('SRID=4326;POLYGONZM((0 0 0 0, 1 1 1 1, 2 2 2 2, 0 0 0 0))')); +INSERT INTO test12(name,geom) values ('MultiLineString',GeomFromEWKT('SRID=4326;MULTILINESTRING((0 0, 1 1),(2 2, 3 3))')); +INSERT INTO test12(name,geom) values ('MultiLineStringZ',GeomFromEWKT('SRID=4326;MULTILINESTRINGZ((0 0 0, 1 1 1),(2 2 2, 3 3 3))')); +INSERT INTO test12(name,geom) values ('MultiLineStringM',GeomFromEWKT('SRID=4326;MULTILINESTRINGM((0 0 0, 1 1 1),(2 2 2, 3 3 3))')); +INSERT INTO test12(name,geom) values ('MultiLineStringZM',GeomFromEWKT('SRID=4326;MULTILINESTRINGZM((0 0 0 0, 1 1 1 1),(2 2 2 2, 3 3 3 3))')); +INSERT INTO test12(name,geom) values ('MultiPolygon',GeomFromEWKT('SRID=4326;MULTIPOLYGON(((0 0, 1 1, 2 2, 0 0)),((0 0, 1 1, 2 2, 0 0)))')); +INSERT INTO test12(name,geom) values ('MultiPolygonZ',GeomFromEWKT('SRID=4326;MULTIPOLYGONZ(((0 0 0, 1 1 1, 2 2 2, 0 0 0)),((0 0 0, 1 1 1, 2 2 2, 0 0 0)))')); +INSERT INTO test12(name,geom) values ('MultiPolygonM',GeomFromEWKT('SRID=4326;MULTIPOLYGONM(((0 0 0, 1 1 1, 2 2 2, 0 0 0)),((0 0 0, 1 1 1, 2 2 2, 0 0 0)))')); +INSERT INTO test12(name,geom) values ('MultiPolygonZM',GeomFromEWKT('SRID=4326;MULTIPOLYGONZM(((0 0 0 0, 1 1 1 1, 2 2 2 2, 0 0 0 0)),((0 0 0 0, 1 1 1 1, 2 2 2 2, 0 0 0 0)))')); +""" + def postgis_setup(): call('dropdb %s' % MAPNIK_TEST_DBNAME,silent=True) @@ -182,6 +210,7 @@ def postgis_setup(): call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_9),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_10),silent=False) call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_11),silent=False) + call('''psql -q %s -c "%s"''' % (MAPNIK_TEST_DBNAME,insert_table_12),silent=False) def postgis_takedown(): pass @@ -757,6 +786,231 @@ if 'postgis' in mapnik.DatasourceCache.plugin_names() \ # This used to raise an exception before correction of issue 2042 mapnik.render_to_file(map2,'world2.png', 'png') + def test_handling_of_zm_dimensions(): + ds = mapnik.PostGIS(dbname=MAPNIK_TEST_DBNAME, + table='(select gid,ST_CoordDim(geom) as dim,name,geom from test12) as tmp', + geometry_field='geom') + eq_(len(ds.fields()),3) + eq_(ds.fields(),['gid', 'dim', 'name']) + eq_(ds.field_types(),['int', 'int', 'str']) + fs = ds.featureset() + # Point (2d) + feat = fs.next() + eq_(feat.id(),1) + eq_(feat['gid'],1) + eq_(feat['dim'],2) + eq_(feat['name'],'Point') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Point(0 0)') + # PointZ + feat = fs.next() + eq_(feat.id(),2) + eq_(feat['gid'],2) + eq_(feat['dim'],3) + eq_(feat['name'],'PointZ') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Point(0 0)') + # PointM + feat = fs.next() + eq_(feat.id(),3) + eq_(feat['gid'],3) + eq_(feat['dim'],3) + eq_(feat['name'],'PointM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Point(0 0)') + # PointZM + feat = fs.next() + eq_(feat.id(),4) + eq_(feat['gid'],4) + eq_(feat['dim'],4) + eq_(feat['name'],'PointZM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Point(0 0)') + # MultiPoint + feat = fs.next() + eq_(feat.id(),5) + eq_(feat['gid'],5) + eq_(feat['dim'],2) + eq_(feat['name'],'MultiPoint') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Point(0 0)') + eq_(geoms[1].to_wkt(),'Point(1 1)') + # MultiPointZ + feat = fs.next() + eq_(feat.id(),6) + eq_(feat['gid'],6) + eq_(feat['dim'],3) + eq_(feat['name'],'MultiPointZ') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Point(0 0)') + eq_(geoms[1].to_wkt(),'Point(1 1)') + # MultiPointM + feat = fs.next() + eq_(feat.id(),7) + eq_(feat['gid'],7) + eq_(feat['dim'],3) + eq_(feat['name'],'MultiPointM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Point(0 0)') + eq_(geoms[1].to_wkt(),'Point(1 1)') + # MultiPointZM + feat = fs.next() + eq_(feat.id(),8) + eq_(feat['gid'],8) + eq_(feat['dim'],4) + eq_(feat['name'],'MultiPointZM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Point(0 0)') + eq_(geoms[1].to_wkt(),'Point(1 1)') + # LineString + feat = fs.next() + eq_(feat.id(),9) + eq_(feat['gid'],9) + eq_(feat['dim'],2) + eq_(feat['name'],'LineString') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + # LineStringZ + feat = fs.next() + eq_(feat.id(),10) + eq_(feat['gid'],10) + eq_(feat['dim'],3) + eq_(feat['name'],'LineStringZ') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + # LineStringM + feat = fs.next() + eq_(feat.id(),11) + eq_(feat['gid'],11) + eq_(feat['dim'],3) + eq_(feat['name'],'LineStringM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + # LineStringZM + feat = fs.next() + eq_(feat.id(),12) + eq_(feat['gid'],12) + eq_(feat['dim'],4) + eq_(feat['name'],'LineStringZM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + # Polygon + feat = fs.next() + eq_(feat.id(),13) + eq_(feat['gid'],13) + eq_(feat['name'],'Polygon') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # PolygonZ + feat = fs.next() + eq_(feat.id(),14) + eq_(feat['gid'],14) + eq_(feat['name'],'PolygonZ') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # PolygonM + feat = fs.next() + eq_(feat.id(),15) + eq_(feat['gid'],15) + eq_(feat['name'],'PolygonM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # PolygonZM + feat = fs.next() + eq_(feat.id(),16) + eq_(feat['gid'],16) + eq_(feat['name'],'PolygonZM') + geoms = feat.geometries() + eq_(len(geoms),1) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # MultiLineString + feat = fs.next() + eq_(feat.id(),17) + eq_(feat['gid'],17) + eq_(feat['name'],'MultiLineString') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + eq_(geoms[1].to_wkt(),'LineString(2 2,3 3)') + # MultiLineStringZ + feat = fs.next() + eq_(feat.id(),18) + eq_(feat['gid'],18) + eq_(feat['name'],'MultiLineStringZ') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + eq_(geoms[1].to_wkt(),'LineString(2 2,3 3)') + # MultiLineStringM + feat = fs.next() + eq_(feat.id(),19) + eq_(feat['gid'],19) + eq_(feat['name'],'MultiLineStringM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + eq_(geoms[1].to_wkt(),'LineString(2 2,3 3)') + # MultiLineStringZM + feat = fs.next() + eq_(feat.id(),20) + eq_(feat['gid'],20) + eq_(feat['name'],'MultiLineStringZM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'LineString(0 0,1 1)') + eq_(geoms[1].to_wkt(),'LineString(2 2,3 3)') + # MultiPolygon + feat = fs.next() + eq_(feat.id(),21) + eq_(feat['gid'],21) + eq_(feat['name'],'MultiPolygon') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + eq_(geoms[1].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # MultiPolygonZ + feat = fs.next() + eq_(feat.id(),22) + eq_(feat['gid'],22) + eq_(feat['name'],'MultiPolygonZ') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + eq_(geoms[1].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # MultiPolygonM + feat = fs.next() + eq_(feat.id(),23) + eq_(feat['gid'],23) + eq_(feat['name'],'MultiPolygonM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + eq_(geoms[1].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + # MultiPolygonZM + feat = fs.next() + eq_(feat.id(),24) + eq_(feat['gid'],24) + eq_(feat['name'],'MultiPolygonZM') + geoms = feat.geometries() + eq_(len(geoms),2) + eq_(geoms[0].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + eq_(geoms[1].to_wkt(),'Polygon((0 0,1 1,2 2,0 0))') + atexit.register(postgis_takedown)