Skip to content

Commit 75f5a5f

Browse files
committedMay 3, 2017
Add spatial and temporal extents to metadata
1 parent 8545b80 commit 75f5a5f

File tree

5 files changed

+283
-7
lines changed

5 files changed

+283
-7
lines changed
 

‎python/core/metadata/qgslayermetadata.sip

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,62 @@ class QgsLayerMetadata
4444

4545
typedef QMap< QString, QStringList > KeywordMap;
4646

47+
struct SpatialExtent
48+
{
49+
50+
QgsCoordinateReferenceSystem extentCrs;
51+
%Docstring
52+
Coordinate reference system for spatial extent.
53+
\see spatial
54+
%End
55+
56+
QgsBox3d bounds;
57+
%Docstring
58+
Geospatial extent of the resource. X and Y coordinates are in the
59+
CRS defined by the metadata (see extentCrs).
60+
61+
While the spatial extent can include a Z dimension, this is not
62+
compulsory.
63+
\see extentCrs
64+
%End
65+
};
66+
67+
struct Extent
68+
{
69+
public:
70+
71+
QList< QgsLayerMetadata::SpatialExtent > spatialExtents() const;
72+
%Docstring
73+
Spatial extents of the resource.
74+
\see setSpatialExtents()
75+
:rtype: list of QgsLayerMetadata.SpatialExtent
76+
%End
77+
78+
void setSpatialExtents( const QList< QgsLayerMetadata::SpatialExtent > &extents );
79+
%Docstring
80+
Sets the spatial ``extents`` of the resource.
81+
\see spatialExtents()
82+
%End
83+
84+
QList< QgsDateTimeRange > temporalExtents() const;
85+
%Docstring
86+
Temporal extents of the resource. Use QgsDateTimeRange.isInstant() to determine
87+
whether the temporal extent is a range or a single point in time.
88+
If QgsDateTimeRange.isInfinite() returns true then the temporal extent
89+
is considered to be indeterminate and continuous.
90+
\see setTemporalExtents()
91+
:rtype: list of QgsDateTimeRange
92+
%End
93+
94+
void setTemporalExtents( const QList< QgsDateTimeRange > &extents );
95+
%Docstring
96+
Sets the temporal ``extents`` of the resource.
97+
\see temporalExtents()
98+
%End
99+
100+
101+
};
102+
47103
struct Constraint
48104
{
49105

@@ -394,6 +450,20 @@ class QgsLayerMetadata
394450
\see encoding()
395451
%End
396452

453+
454+
QgsLayerMetadata::Extent &extent();
455+
%Docstring
456+
Returns the spatial and temporal extents associated with the resource.
457+
\see setExtent()
458+
:rtype: QgsLayerMetadata.Extent
459+
%End
460+
461+
void setExtent( const QgsLayerMetadata::Extent &extent );
462+
%Docstring
463+
Sets the spatial and temporal extents associated with the resource.
464+
\see setExtent()
465+
%End
466+
397467
QgsCoordinateReferenceSystem crs() const;
398468
%Docstring
399469
Returns the coordinate reference system described by the layer's metadata.
@@ -548,8 +618,6 @@ class QgsLayerMetadata
548618
};
549619

550620

551-
552-
553621
/************************************************************************
554622
* This file has been generated automatically from *
555623
* *

‎src/core/metadata/qgslayermetadata.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ void QgsLayerMetadata::saveToLayer( QgsMapLayer *layer ) const
215215
layer->setCustomProperty( QStringLiteral( "metadata/language" ), mLanguage );
216216
layer->setCustomProperty( QStringLiteral( "metadata/type" ), mType );
217217
layer->setCustomProperty( QStringLiteral( "metadata/title" ), mTitle );
218+
layer->setCustomProperty( QStringLiteral( "metadata/extent" ), QVariant::fromValue( mExtent ) );
218219
layer->setCustomProperty( QStringLiteral( "metadata/abstract" ), mAbstract );
219220
layer->setCustomProperty( QStringLiteral( "metadata/fees" ), mFees );
220221
layer->setCustomProperty( QStringLiteral( "metadata/rights" ), mRights );
@@ -243,8 +244,44 @@ void QgsLayerMetadata::readFromLayer( const QgsMapLayer *layer )
243244
mEncoding = layer->customProperty( QStringLiteral( "metadata/encoding" ) ).toString();
244245
QString crsAuthId = layer->customProperty( QStringLiteral( "metadata/crs" ) ).toString();
245246
mCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsAuthId );
247+
mExtent = layer->customProperty( QStringLiteral( "metadata/extent" ) ).value<Extent>();
246248
mConstraints = layer->customProperty( QStringLiteral( "metadata/constraints" ) ).value<ConstraintList>();
247249
mKeywords = layer->customProperty( QStringLiteral( "metadata/keywords" ) ).value<KeywordMap>();
248250
mContacts = layer->customProperty( QStringLiteral( "metadata/contacts" ) ).value<ContactList>();
249251
mLinks = layer->customProperty( QStringLiteral( "metadata/links" ) ).value<LinkList>();
250252
}
253+
254+
const QgsLayerMetadata::Extent &QgsLayerMetadata::extent() const
255+
{
256+
return mExtent;
257+
}
258+
259+
QgsLayerMetadata::Extent &QgsLayerMetadata::extent()
260+
{
261+
return mExtent;
262+
}
263+
264+
void QgsLayerMetadata::setExtent( const Extent &extent )
265+
{
266+
mExtent = extent;
267+
}
268+
269+
QList<QgsLayerMetadata::SpatialExtent> QgsLayerMetadata::Extent::spatialExtents() const
270+
{
271+
return mSpatialExtents;
272+
}
273+
274+
void QgsLayerMetadata::Extent::setSpatialExtents( const QList<QgsLayerMetadata::SpatialExtent> &spatialExtents )
275+
{
276+
mSpatialExtents = spatialExtents;
277+
}
278+
279+
QList<QgsDateTimeRange> QgsLayerMetadata::Extent::temporalExtents() const
280+
{
281+
return mTemporalExtents;
282+
}
283+
284+
void QgsLayerMetadata::Extent::setTemporalExtents( const QList<QgsDateTimeRange> &temporalExtents )
285+
{
286+
mTemporalExtents = temporalExtents;
287+
}

‎src/core/metadata/qgslayermetadata.h

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "qgis.h"
2222
#include "qgis_core.h"
2323
#include "qgscoordinatereferencesystem.h"
24+
#include "qgsbox3d.h"
25+
#include "qgsrange.h"
2426

2527
class QgsMapLayer;
2628

@@ -59,6 +61,73 @@ class CORE_EXPORT QgsLayerMetadata
5961
*/
6062
typedef QMap< QString, QStringList > KeywordMap;
6163

64+
/**
65+
* Metadata spatial extent structure.
66+
*/
67+
struct SpatialExtent
68+
{
69+
70+
/**
71+
* Coordinate reference system for spatial extent.
72+
* \see spatial
73+
*/
74+
QgsCoordinateReferenceSystem extentCrs;
75+
76+
/**
77+
* Geospatial extent of the resource. X and Y coordinates are in the
78+
* CRS defined by the metadata (see extentCrs).
79+
*
80+
* While the spatial extent can include a Z dimension, this is not
81+
* compulsory.
82+
* \see extentCrs
83+
*/
84+
QgsBox3d bounds;
85+
};
86+
87+
/**
88+
* Metadata extent structure.
89+
*/
90+
struct Extent
91+
{
92+
public:
93+
94+
/**
95+
* Spatial extents of the resource.
96+
* \see setSpatialExtents()
97+
*/
98+
QList< QgsLayerMetadata::SpatialExtent > spatialExtents() const;
99+
100+
/**
101+
* Sets the spatial \a extents of the resource.
102+
* \see spatialExtents()
103+
*/
104+
void setSpatialExtents( const QList< QgsLayerMetadata::SpatialExtent > &extents );
105+
106+
/**
107+
* Temporal extents of the resource. Use QgsDateTimeRange::isInstant() to determine
108+
* whether the temporal extent is a range or a single point in time.
109+
* If QgsDateTimeRange::isInfinite() returns true then the temporal extent
110+
* is considered to be indeterminate and continuous.
111+
* \see setTemporalExtents()
112+
*/
113+
QList< QgsDateTimeRange > temporalExtents() const;
114+
115+
/**
116+
* Sets the temporal \a extents of the resource.
117+
* \see temporalExtents()
118+
*/
119+
void setTemporalExtents( const QList< QgsDateTimeRange > &extents );
120+
121+
#ifndef SIP_RUN
122+
private:
123+
124+
QList< QgsLayerMetadata::SpatialExtent > mSpatialExtents;
125+
QList< QgsDateTimeRange > mTemporalExtents;
126+
127+
#endif
128+
129+
};
130+
62131
/**
63132
* Metadata constraint structure.
64133
*/
@@ -432,6 +501,24 @@ class CORE_EXPORT QgsLayerMetadata
432501
*/
433502
void setEncoding( const QString &encoding );
434503

504+
/**
505+
* Returns the spatial and temporal extents associated with the resource.
506+
* \see setExtent()
507+
*/
508+
SIP_SKIP const QgsLayerMetadata::Extent &extent() const;
509+
510+
/**
511+
* Returns the spatial and temporal extents associated with the resource.
512+
* \see setExtent()
513+
*/
514+
QgsLayerMetadata::Extent &extent();
515+
516+
/**
517+
* Sets the spatial and temporal extents associated with the resource.
518+
* \see setExtent()
519+
*/
520+
void setExtent( const QgsLayerMetadata::Extent &extent );
521+
435522
/**
436523
* Returns the coordinate reference system described by the layer's metadata.
437524
*
@@ -604,6 +691,8 @@ class CORE_EXPORT QgsLayerMetadata
604691
QString mEncoding;
605692
QgsCoordinateReferenceSystem mCrs;
606693

694+
Extent mExtent;
695+
607696
/**
608697
* Keywords map. Key is the vocabulary, value is a list of keywords for that vocabulary.
609698
*/
@@ -627,7 +716,6 @@ Q_DECLARE_METATYPE( QgsLayerMetadata::KeywordMap )
627716
Q_DECLARE_METATYPE( QgsLayerMetadata::ConstraintList )
628717
Q_DECLARE_METATYPE( QgsLayerMetadata::ContactList )
629718
Q_DECLARE_METATYPE( QgsLayerMetadata::LinkList )
630-
631-
719+
Q_DECLARE_METATYPE( QgsLayerMetadata::Extent )
632720

633721
#endif // QGSLAYERMETADATA_H

‎src/core/metadata/qgslayermetadatavalidator.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ bool QgsNativeMetadataValidator::validate( const QgsLayerMetadata &metadata, QLi
6565
results << ValidationResult( QObject::tr( "crs" ), QObject::tr( "A valid CRS element is required." ) );
6666
}
6767

68+
int index = 0;
69+
Q_FOREACH ( const QgsLayerMetadata::SpatialExtent &extent, metadata.extent().spatialExtents() )
70+
{
71+
if ( !extent.extentCrs.isValid() )
72+
{
73+
result = false;
74+
results << ValidationResult( QObject::tr( "extent" ), QObject::tr( "A valid CRS element for the spatial extent is required." ), index );
75+
}
76+
77+
if ( extent.bounds.width() == 0.0 || extent.bounds.height() == 0.0 )
78+
{
79+
result = false;
80+
results << ValidationResult( QObject::tr( "extent" ), QObject::tr( "A valid spatial extent is required." ), index );
81+
}
82+
index++;
83+
}
84+
6885
if ( metadata.contacts().isEmpty() )
6986
{
7087
result = false;
@@ -80,7 +97,7 @@ bool QgsNativeMetadataValidator::validate( const QgsLayerMetadata &metadata, QLi
8097
// validate keywords
8198
QgsLayerMetadata::KeywordMap keywords = metadata.keywords();
8299
QgsLayerMetadata::KeywordMap::const_iterator keywordIt = keywords.constBegin();
83-
int index = 0;
100+
index = 0;
84101
for ( ; keywordIt != keywords.constEnd(); ++keywordIt )
85102
{
86103
if ( keywordIt.key().isEmpty() )

‎tests/src/python/test_qgslayermetadata.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
from qgis.core import (QgsLayerMetadata,
1818
QgsCoordinateReferenceSystem,
1919
QgsVectorLayer,
20-
QgsNativeMetadataValidator)
20+
QgsNativeMetadataValidator,
21+
QgsBox3d,
22+
QgsDateTimeRange)
23+
from qgis.PyQt.QtCore import (QDate,
24+
QTime,
25+
QDateTime)
2126
from qgis.testing import start_app, unittest
2227

2328
start_app()
@@ -72,6 +77,28 @@ def testGettersSetters(self):
7277
m.setCrs(QgsCoordinateReferenceSystem.fromEpsgId(3111))
7378
self.assertEqual(m.crs().authid(), 'EPSG:3111')
7479

80+
def testExtent(self):
81+
e = QgsLayerMetadata.Extent()
82+
se = QgsLayerMetadata.SpatialExtent()
83+
se.extentCrs = QgsCoordinateReferenceSystem.fromEpsgId(3111)
84+
se.bounds = QgsBox3d(1, 2, 3, 4, 5, 6)
85+
e.setSpatialExtents([se])
86+
e.setTemporalExtents([QgsDateTimeRange(QDateTime(QDate(2017, 1, 3), QTime(11, 34, 56)), QDateTime(QDate(2018, 1, 3), QTime(12, 35, 57)))])
87+
88+
m = QgsLayerMetadata()
89+
m.setExtent(e)
90+
91+
extents = m.extent().spatialExtents()
92+
self.assertEqual(extents[0].extentCrs.authid(), 'EPSG:3111')
93+
self.assertEqual(extents[0].bounds.xMinimum(), 1.0)
94+
self.assertEqual(extents[0].bounds.yMinimum(), 2.0)
95+
self.assertEqual(extents[0].bounds.zMinimum(), 3.0)
96+
self.assertEqual(extents[0].bounds.xMaximum(), 4.0)
97+
self.assertEqual(extents[0].bounds.yMaximum(), 5.0)
98+
self.assertEqual(extents[0].bounds.zMaximum(), 6.0)
99+
self.assertEqual(m.extent().temporalExtents()[0].begin(), QDateTime(QDate(2017, 1, 3), QTime(11, 34, 56)))
100+
self.assertEqual(m.extent().temporalExtents()[0].end(), QDateTime(QDate(2018, 1, 3), QTime(12, 35, 57)))
101+
75102
def testKeywords(self):
76103
m = QgsLayerMetadata()
77104

@@ -204,6 +231,14 @@ def createTestMetadata(self):
204231
m.setEncoding('utf-8')
205232
m.setCrs(QgsCoordinateReferenceSystem.fromOgcWmsCrs('EPSG:4326'))
206233

234+
e = QgsLayerMetadata.Extent()
235+
se = QgsLayerMetadata.SpatialExtent()
236+
se.extentCrs = QgsCoordinateReferenceSystem.fromOgcWmsCrs('EPSG:4326')
237+
se.bounds = QgsBox3d(-180, -90, 0, 180, 90, 0)
238+
e.setSpatialExtents([se])
239+
e.setTemporalExtents([QgsDateTimeRange(QDateTime(QDate(2001, 12, 17), QTime(9, 30, 47)), QDateTime(QDate(2001, 12, 17), QTime(9, 30, 47)))])
240+
m.setExtent(e)
241+
207242
c = QgsLayerMetadata.Contact()
208243
c.name = 'John Smith'
209244
c.organization = 'ACME'
@@ -266,6 +301,16 @@ def checkExpectedMetadata(self, m):
266301
self.assertEqual(m.encoding(), 'utf-8')
267302
self.assertEqual(m.keywords(), {'GEMET': ['kw1', 'kw2']})
268303
self.assertEqual(m.crs().authid(), 'EPSG:4326')
304+
305+
extent = m.extent().spatialExtents()[0]
306+
self.assertEqual(extent.extentCrs.authid(), 'EPSG:4326')
307+
self.assertEqual(extent.bounds.xMinimum(), -180.0)
308+
self.assertEqual(extent.bounds.yMinimum(), -90.0)
309+
self.assertEqual(extent.bounds.xMaximum(), 180.0)
310+
self.assertEqual(extent.bounds.yMaximum(), 90.0)
311+
self.assertEqual(m.extent().temporalExtents()[0].begin(), QDateTime(QDate(2001, 12, 17), QTime(9, 30, 47)))
312+
self.assertTrue(m.extent().temporalExtents()[0].isInstant())
313+
269314
self.assertEqual(m.contacts()[0].name, 'John Smith')
270315
self.assertEqual(m.contacts()[0].organization, 'ACME')
271316
self.assertEqual(m.contacts()[0].position, 'staff')
@@ -319,7 +364,6 @@ def testValidateNative(self): # spellok
319364
"""
320365
Test validating metadata against QGIS native schema
321366
"""
322-
323367
m = self.createTestMetadata()
324368
v = QgsNativeMetadataValidator()
325369

@@ -396,6 +440,28 @@ def testValidateNative(self): # spellok
396440
self.assertEqual(list[0].section, 'keywords')
397441
self.assertEqual(list[0].identifier, 0)
398442

443+
m = self.createTestMetadata()
444+
e = m.extent()
445+
se = e.spatialExtents()[0]
446+
se.extentCrs = QgsCoordinateReferenceSystem()
447+
e.setSpatialExtents([se])
448+
m.setExtent(e)
449+
res, list = v.validate(m)
450+
self.assertFalse(res)
451+
self.assertEqual(list[0].section, 'extent')
452+
self.assertEqual(list[0].identifier, 0)
453+
454+
m = self.createTestMetadata()
455+
e = m.extent()
456+
se = e.spatialExtents()[0]
457+
se.bounds = QgsBox3d(1, 1, 0, 1, 2)
458+
e.setSpatialExtents([se])
459+
m.setExtent(e)
460+
res, list = v.validate(m)
461+
self.assertFalse(res)
462+
self.assertEqual(list[0].section, 'extent')
463+
self.assertEqual(list[0].identifier, 0)
464+
399465
m = self.createTestMetadata()
400466
c = m.contacts()[0]
401467
c.name = ''

0 commit comments

Comments
 (0)
Please sign in to comment.