Skip to content

Commit

Permalink
PostGIS: Allow to load TIN, PS and Triangle layers
Browse files Browse the repository at this point in the history
The postgres provider is modified so that layers with
TIN, PolyhedralSurface and Triangle geometries can be loaded.
Geometries are converted to MultiPolygons (and Polygons for Triangles).

The postgres test is completed to cover the loading of different types
of layers
  • Loading branch information
Hugo Mercier committed Oct 21, 2015
1 parent c45fc09 commit c7aeb77
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/core/geometry/qgsabstractgeometryv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ QgsRectangle QgsAbstractGeometryV2::boundingBox() const

bool QgsAbstractGeometryV2::is3D() const
{
return(( mWkbType >= 1001 && mWkbType <= 1012 ) || ( mWkbType > 3000 || mWkbType & 0x80000000 ) );

This comment has been minimized.

Copy link
@nyalldawson

nyalldawson Oct 21, 2015

Collaborator

@mhugo - this is a bit odd... I wonder why this isn't using QgsWKBTypes::hasZ / hasM instead?

return(( mWkbType >= 1001 && mWkbType <= 1017 ) || ( mWkbType > 3000 || mWkbType & 0x80000000 ) );
}

bool QgsAbstractGeometryV2::isMeasure() const
{
return ( mWkbType >= 2001 && mWkbType <= 3012 );
return ( mWkbType >= 2001 && mWkbType <= 3017 );
}

#if 0
Expand Down
25 changes: 21 additions & 4 deletions src/providers/postgres/qgspostgresconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1527,15 +1527,32 @@ int QgsPostgresConn::postgisWkbTypeDim( QGis::WkbType wkbType )

QGis::WkbType QgsPostgresConn::wkbTypeFromPostgis( QString type )
{
// Polyhedral surfaces and TIN are stored in PostGIS as geometry collections
// of Polygons and Triangles.
// So, since QGIS does not natively support PS and TIN, but we would like to open them if possible,
// we consider them as multipolygons. WKB will be converted by the feature iterator
if (( type == "POLYHEDRALSURFACE" ) || ( type == "TIN" ) )
{
return QGis::WKBMultiPolygon;
}
else if ( type == "TRIANGLE" )
{
return QGis::WKBPolygon;
}
return ( QGis::WkbType )QgsWKBTypes::parseType( type );
}

QgsWKBTypes::Type QgsPostgresConn::wkbTypeFromOgcWkbType( unsigned int wkbType )
{
// polyhedralsurface / TIN / triangle => MultiPolygon
if ( wkbType % 100 >= 15 )
wkbType = wkbType / 1000 * 1000 + QGis::WKBMultiPolygon;

// PolyhedralSurface => MultiPolygon
if ( wkbType % 1000 == 15 )
return ( QgsWKBTypes::Type )( wkbType / 1000 * 1000 + QgsWKBTypes::MultiPolygon );
// TIN => MultiPolygon
if ( wkbType % 1000 == 16 )
return ( QgsWKBTypes::Type )( wkbType / 1000 * 1000 + QgsWKBTypes::MultiPolygon );
// Triangle => Polygon
if ( wkbType % 1000 == 17 )
return ( QgsWKBTypes::Type )( wkbType / 1000 * 1000 + QgsWKBTypes::Polygon );
return ( QgsWKBTypes::Type ) wkbType;
}

Expand Down
55 changes: 20 additions & 35 deletions src/providers/postgres/qgspostgresfeatureiterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,57 +451,42 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
memcpy( featureGeom, PQgetvalue( queryResult.result(), row, col ), returnedLength );
memset( featureGeom + returnedLength, 0, 1 );

// modify 2.5D WKB types to make them compliant with OGR
unsigned int wkbType;
memcpy( &wkbType, featureGeom + 1, sizeof( wkbType ) );
wkbType = QgsPostgresConn::wkbTypeFromOgcWkbType( wkbType );
memcpy( featureGeom + 1, &wkbType, sizeof( wkbType ) );
QgsWKBTypes::Type newType = QgsPostgresConn::wkbTypeFromOgcWkbType( wkbType );

// change wkb type of inner geometries
if ( wkbType == QGis::WKBMultiPoint25D ||
wkbType == QGis::WKBMultiLineString25D ||
wkbType == QGis::WKBMultiPolygon25D )
if (( unsigned int )newType != wkbType )
{
// overwrite type
unsigned int n = newType;
memcpy( featureGeom + 1, &n, sizeof( n ) );
}

// PostGIS stores TIN as a collection of Triangles.
// Since Triangles are not supported, they have to be converted to Polygons
const int nDims = 2 + ( QgsWKBTypes::hasZ( newType ) ? 1 : 0 ) + ( QgsWKBTypes::hasM( newType ) ? 1 : 0 );
if ( wkbType % 1000 == 16 )
{
unsigned int numGeoms;
memcpy( &numGeoms, featureGeom + 5, sizeof( unsigned int ) );
unsigned char *wkb = featureGeom + 9;
for ( unsigned int i = 0; i < numGeoms; ++i )
{
unsigned int localType;
memcpy( &localType, wkb + 1, sizeof( localType ) );
localType = QgsPostgresConn::wkbTypeFromOgcWkbType( localType );
const unsigned int localType = QgsWKBTypes::singleType( newType ); // polygon(Z|M)
memcpy( wkb + 1, &localType, sizeof( localType ) );

// skip endian and type info
wkb += sizeof( unsigned int ) + 1;

// skip coordinates
switch ( wkbType )
unsigned int nRings;
memcpy( &nRings, wkb, sizeof( int ) );
wkb += sizeof( int );
for ( unsigned int j = 0; j < nRings; ++j )
{
case QGis::WKBMultiPoint25D:
wkb += sizeof( double ) * 3;
break;
case QGis::WKBMultiLineString25D:
{
unsigned int nPoints;
memcpy( &nPoints, wkb, sizeof( int ) );
wkb += sizeof( int ) + sizeof( double ) * 3 * nPoints;
}
break;
default:
case QGis::WKBMultiPolygon25D:
{
unsigned int nRings;
memcpy( &nRings, wkb, sizeof( int ) );
wkb += sizeof( int );
for ( unsigned int j = 0; j < nRings; ++j )
{
unsigned int nPoints;
memcpy( &nPoints, wkb, sizeof( int ) );
wkb += sizeof( nPoints ) + sizeof( double ) * 3 * nPoints;
}
}
break;
unsigned int nPoints;
memcpy( &nPoints, wkb, sizeof( int ) );
wkb += sizeof( nPoints ) + sizeof( double ) * nDims * nPoints;
}
}
}
Expand Down
32 changes: 31 additions & 1 deletion tests/src/python/test_provider_postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import qgis
import os
import sys
from qgis.core import NULL

from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsProviderRegistry
Expand All @@ -34,8 +35,11 @@ class TestPyQgsPostgresProvider(TestCase, ProviderTestCase):
@classmethod
def setUpClass(cls):
"""Run before all tests"""
cls.dbconn = u'dbname=\'qgis_test\' host=localhost port=5432 user=\'postgres\' password=\'postgres\''
if os.environ.has_key('QGIS_PGTEST_DB'):
cls.dbconn = os.environ['QGIS_PGTEST_DB']
# Create test layer
cls.vl = QgsVectorLayer(u'dbname=\'qgis_test\' host=localhost port=5432 user=\'postgres\' password=\'postgres\' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'postgres')
cls.vl = QgsVectorLayer( cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'postgres')
assert(cls.vl.isValid())
cls.provider = cls.vl.dataProvider()

Expand All @@ -55,5 +59,31 @@ def testDefaultValue(self):
assert self.provider.defaultValue(1) == NULL
assert self.provider.defaultValue(2) == '\'qgis\'::text'

def testWkbTypes(self):
def test_table( dbconn, table_name, wkt ):
vl = QgsVectorLayer( '%s srid=4326 table="qgis_test".%s (geom) sql=' % (dbconn, table_name), "testgeom", "postgres" )
assert( vl.isValid() )
for f in vl.getFeatures():
print f.geometry().exportToWkt(), wkt
assert f.geometry().exportToWkt() == wkt

test_table(self.dbconn, 'p2d', 'Polygon ((0 0, 1 0, 1 1, 0 1, 0 0))')
test_table(self.dbconn, 'p3d', 'PolygonZ ((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))')
test_table(self.dbconn, 'triangle2d', 'Polygon ((0 0, 1 0, 1 1, 0 0))')
test_table(self.dbconn, 'triangle3d', 'PolygonZ ((0 0 0, 1 0 0, 1 1 0, 0 0 0))')
test_table(self.dbconn, 'tin2d', 'MultiPolygon (((0 0, 1 0, 1 1, 0 0)),((0 0, 0 1, 1 1, 0 0)))')
test_table(self.dbconn, 'tin3d', 'MultiPolygonZ (((0 0 0, 1 0 0, 1 1 0, 0 0 0)),((0 0 0, 0 1 0, 1 1 0, 0 0 0)))')
test_table(self.dbconn, 'ps2d', 'MultiPolygon (((0 0, 1 0, 1 1, 0 1, 0 0)))')
test_table(self.dbconn, 'ps3d', 'MultiPolygonZ (((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)),((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),((1 0 0, 1 0 1, 0 0 1, 0 0 0, 1 0 0)))')
test_table(self.dbconn, 'mp3d', 'MultiPolygonZ (((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)),((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),((1 0 0, 1 0 1, 0 0 1, 0 0 0, 1 0 0)))')
test_table(self.dbconn, 'pt2d', 'Point (0 0)')
test_table(self.dbconn, 'pt3d', 'PointZ (0 0 0)')
test_table(self.dbconn, 'ls2d', 'LineString (0 0, 1 1)')
test_table(self.dbconn, 'ls3d', 'LineStringZ (0 0 0, 1 1 1)')
test_table(self.dbconn, 'mpt2d', 'MultiPoint ((0 0),(1 1))')
test_table(self.dbconn, 'mpt3d', 'MultiPointZ ((0 0 0),(1 1 1))')
test_table(self.dbconn, 'mls2d', 'MultiLineString ((0 0, 1 1),(2 2, 3 3))')
test_table(self.dbconn, 'mls3d', 'MultiLineStringZ ((0 0 0, 1 1 1),(2 2 2, 3 3 3))')

if __name__ == '__main__':
unittest.main()
131 changes: 122 additions & 9 deletions tests/testdata/provider/testdata.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ SET client_min_messages = warning;
-- Name: qgis_test; Type: SCHEMA; Schema: -; Owner: postgres
--

CREATE SCHEMA qgis_test;

CREATE EXTENSION IF NOT EXISTS postgis;

ALTER SCHEMA qgis_test OWNER TO postgres;
DROP SCHEMA IF EXISTS qgis_test CASCADE;
CREATE SCHEMA qgis_test;

SET search_path = qgis_test, pg_catalog;

SET default_tablespace = '';

Expand All @@ -34,23 +33,21 @@ SET default_with_oids = false;
-- Name: someData; Type: TABLE; Schema: qgis_test; Owner: postgres; Tablespace:
--

CREATE TABLE "someData" (
CREATE TABLE qgis_test."someData" (
pk SERIAL NOT NULL,
cnt integer,
name text DEFAULT 'qgis',
geom public.geometry(Point,4326)
);


ALTER TABLE qgis_test."someData" OWNER TO postgres;

--
-- TOC entry 4068 (class 0 OID 377761)
-- Dependencies: 171
-- Data for Name: someData; Type: TABLE DATA; Schema: qgis_test; Owner: postgres
--

COPY "someData" (pk, cnt, name, geom) FROM stdin;
COPY qgis_test."someData" (pk, cnt, name, geom) FROM stdin;
5 -200 \N 0101000020E61000001D5A643BDFC751C01F85EB51B88E5340
3 300 Pear \N
1 100 Orange 0101000020E61000006891ED7C3F9551C085EB51B81E955040
Expand All @@ -64,7 +61,7 @@ COPY "someData" (pk, cnt, name, geom) FROM stdin;
-- Name: someData_pkey; Type: CONSTRAINT; Schema: qgis_test; Owner: postgres; Tablespace:
--

ALTER TABLE ONLY "someData"
ALTER TABLE ONLY qgis_test."someData"
ADD CONSTRAINT "someData_pkey" PRIMARY KEY (pk);


Expand All @@ -74,3 +71,119 @@ ALTER TABLE ONLY "someData"
-- PostgreSQL database dump complete
--

CREATE TABLE qgis_test.p2d(
id int,
geom Geometry(Polygon,4326)
);
INSERT INTO qgis_test.p2d values (1, 'srid=4326;Polygon((0 0,1 0,1 1,0 1,0 0))'::geometry);

CREATE TABLE qgis_test.p3d(
id int,
geom Geometry(PolygonZ,4326)
);
INSERT INTO qgis_test.p3d values (1, 'srid=4326;Polygon((0 0 0,1 0 0,1 1 0,0 1 0,0 0 0))'::geometry);

CREATE TABLE qgis_test.triangle2d(
id int,
geom Geometry(Triangle,4326)
);

INSERT INTO qgis_test.triangle2d values (1, 'srid=4326;triangle((0 0,1 0,1 1,0 0))'::geometry);

CREATE TABLE qgis_test.triangle3d(
id int,
geom Geometry(TriangleZ,4326)
);

INSERT INTO qgis_test.triangle3d values (1, 'srid=4326;triangle((0 0 0,1 0 0,1 1 0,0 0 0))'::geometry);

CREATE TABLE qgis_test.tin2d(
id int,
geom Geometry(TIN,4326)
);

INSERT INTO qgis_test.tin2d values (1, 'srid=4326;TIN(((0 0,1 0,1 1,0 0)),((0 0,0 1,1 1,0 0)))'::geometry);

CREATE TABLE qgis_test.tin3d(
id int,
geom Geometry(TINZ,4326)
);

INSERT INTO qgis_test.tin3d values (1, 'srid=4326;TIN(((0 0 0,1 0 0,1 1 0,0 0 0)),((0 0 0,0 1 0,1 1 0,0 0 0)))'::geometry);

CREATE TABLE qgis_test.ps2d(
id int,
geom Geometry(PolyhedralSurface,4326)
);

INSERT INTO qgis_test.ps2d values (1, 'srid=4326;PolyhedralSurface(((0 0,1 0,1 1,0 1,0 0)))'::geometry);

CREATE TABLE qgis_test.ps3d(
id int,
geom Geometry(PolyhedralSurfaceZ,4326)
);

INSERT INTO qgis_test.ps3d values (1, 'srid=4326;PolyhedralSurface Z(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)),((1 1 0,1 1 1,1 0 1,1 0 0,1 1 0)),((1 0 0,1 0 1,0 0 1,0 0 0,1 0 0)))'::geometry);

CREATE TABLE qgis_test.mp3d(
id int,
geom Geometry(MultipolygonZ,4326)
);

INSERT INTO qgis_test.mp3d values (1, 'srid=4326;Multipolygon Z(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)),((1 1 0,1 1 1,1 0 1,1 0 0,1 1 0)),((1 0 0,1 0 1,0 0 1,0 0 0,1 0 0)))'::geometry);

CREATE TABLE qgis_test.pt2d(
id int,
geom Geometry(Point,4326)
);

INSERT INTO qgis_test.pt2d values (1, 'srid=4326;Point(0 0)'::geometry);

CREATE TABLE qgis_test.pt3d(
id int,
geom Geometry(PointZ,4326)
);

INSERT INTO qgis_test.pt3d values (1, 'srid=4326;PointZ(0 0 0)'::geometry);

CREATE TABLE qgis_test.ls2d(
id int,
geom Geometry(LineString,4326)
);

INSERT INTO qgis_test.ls2d values (1, 'srid=4326;Linestring(0 0, 1 1)'::geometry);

CREATE TABLE qgis_test.ls3d(
id int,
geom Geometry(LineStringZ,4326)
);

INSERT INTO qgis_test.ls3d values (1, 'srid=4326;Linestring(0 0 0, 1 1 1)'::geometry);

CREATE TABLE qgis_test.mpt2d(
id int,
geom Geometry(MultiPoint,4326)
);

INSERT INTO qgis_test.mpt2d values (1, 'srid=4326;MultiPoint((0 0),(1 1))'::geometry);

CREATE TABLE qgis_test.mpt3d(
id int,
geom Geometry(MultiPointZ,4326)
);

INSERT INTO qgis_test.mpt3d values (1, 'srid=4326;MultiPoint((0 0 0),(1 1 1))'::geometry);

CREATE TABLE qgis_test.mls2d(
id int,
geom Geometry(MultiLineString,4326)
);

INSERT INTO qgis_test.mls2d values (1, 'srid=4326;MultiLineString((0 0, 1 1),(2 2, 3 3))'::geometry);

CREATE TABLE qgis_test.mls3d(
id int,
geom Geometry(MultiLineStringZ,4326)
);

INSERT INTO qgis_test.mls3d values (1, 'srid=4326;MultiLineString((0 0 0, 1 1 1),(2 2 2, 3 3 3))'::geometry);

0 comments on commit c7aeb77

Please sign in to comment.