17 |
17 |
|
18 |
18 |
#define WFS_THRESHOLD 200
|
19 |
19 |
|
20 |
|
#include "qgis.h"
|
21 |
20 |
#include "qgsapplication.h"
|
22 |
21 |
#include "qgsmaplayerregistry.h"
|
23 |
22 |
#include "qgsfeature.h"
|
... | ... | |
49 |
48 |
QgsWFSProvider::QgsWFSProvider( const QString& uri )
|
50 |
49 |
: QgsVectorDataProvider( uri ),
|
51 |
50 |
mNetworkRequestFinished( true ),
|
|
51 |
mGeometryAttribute( 0 ),
|
52 |
52 |
mEncoding( QgsWFSProvider::GET ),
|
53 |
53 |
mUseIntersect( false ),
|
54 |
54 |
mWKBType( QGis::WKBUnknown ),
|
... | ... | |
57 |
57 |
mValid( true ),
|
58 |
58 |
mLayer( 0 ),
|
59 |
59 |
mGetRenderedOnly( false ),
|
60 |
|
mInitGro( false )
|
|
60 |
mInitUri(),
|
|
61 |
mGetExtent(),
|
|
62 |
mWfsFids( 0 ),
|
|
63 |
mFilterCaps( 0 )
|
61 |
64 |
{
|
62 |
65 |
mSpatialIndex = 0;
|
63 |
66 |
if ( uri.isEmpty() )
|
... | ... | |
66 |
69 |
return;
|
67 |
70 |
}
|
68 |
71 |
|
69 |
|
//Local url or HTTP? [WBC 111221] refactored from getFeature()
|
|
72 |
//Local url or HTTP? refactored from getFeature()
|
|
73 |
//[wbc111228 issue 4280: fetch un-cached features on demand]
|
70 |
74 |
if ( uri.startsWith( "http" ) )
|
71 |
75 |
{
|
72 |
76 |
mEncoding = QgsWFSProvider::GET;
|
... | ... | |
75 |
79 |
{
|
76 |
80 |
mEncoding = QgsWFSProvider::FILE;
|
77 |
81 |
}
|
78 |
|
|
79 |
|
//create mSourceCRS from url if possible [WBC 111221] refactored from GetFeatureGET()
|
|
82 |
//create mSourceCRS from url if possible; refactored from GetFeatureGET()
|
80 |
83 |
QString srsname = parameterFromUrl( "SRSNAME" );
|
81 |
84 |
if ( !srsname.isEmpty() )
|
82 |
85 |
{
|
83 |
86 |
mSourceCRS.createFromOgcWmsCrs( srsname );
|
84 |
87 |
}
|
85 |
|
|
86 |
88 |
//fetch attributes of layer and type of its geometry attribute
|
87 |
|
//WBC 111221: extracting geometry type here instead of getFeature allows successful
|
|
89 |
//extracting geometry type before getFeature usually allows successful
|
88 |
90 |
//layer creation even when no features are retrieved (due to, e.g., BBOX or FILTER)
|
89 |
|
if ( describeFeatureType( uri, mGeometryAttribute, mFields, mWKBType ) )
|
|
91 |
QString describeFeatureUri = uri;
|
|
92 |
describeFeatureUri.replace( QString( "GetFeature" ), QString( "DescribeFeatureType" ) );
|
|
93 |
if ( describeFeatureType( describeFeatureUri, mGeometryAttribute, mFields, mWKBType ) )
|
90 |
94 |
{
|
91 |
|
mValid = false;
|
92 |
95 |
QgsDebugMsg( QString( "describeFeatureType failed, URI=%1" ).arg( uri ) );
|
93 |
|
QMessageBox( QMessageBox::Warning, "DescribeFeatureType failed!",
|
94 |
|
QString( "Layer cannot be created from\n%1" ).arg( uri ) );
|
|
96 |
QMessageBox::critical ( 0, tr( "DescribeFeatureType failed!" ),
|
|
97 |
tr( "Layer cannot be created from\n%1" ).arg( uri ) );
|
|
98 |
mValid = false;
|
95 |
99 |
return;
|
96 |
100 |
}
|
97 |
|
|
98 |
|
if ( ! uri.contains( "BBOX" ) )
|
99 |
|
{ //"Cache Features" option; get all features in layer immediately
|
|
101 |
//fetch features now or on first render?
|
|
102 |
if ( mEncoding == QgsWFSProvider::FILE || ! uri.contains("BBOX") || mWKBType == QGis::WKBUnknown )
|
|
103 |
{ //now if (1) local URL, (2) "Cache All Features" option, or (3) WFS describeFeatureType does
|
|
104 |
//not disclose a specific geometry type (e.g., TinyOWS reports "GeometryPropertyType")
|
|
105 |
//retrieve all features in layer immediately
|
100 |
106 |
reloadData();
|
101 |
|
} //otherwise, defer feature retrieval until layer is first rendered
|
|
107 |
} //otherwise: defer feature fetch until layer is first rendered
|
|
108 |
else
|
|
109 |
{
|
|
110 |
mSpatialIndex = new QgsSpatialIndex();
|
|
111 |
}
|
|
112 |
//if we still do not know geometry type, user needs to change source select parameters
|
|
113 |
if ( mWKBType == QGis::WKBUnknown )
|
|
114 |
{
|
|
115 |
QgsDebugMsg( QString( "Cannot obtain geometry type, URI=%1" ).arg( uri ) );
|
|
116 |
QMessageBox::critical ( 0, tr( "Cannot obtain geometry type!" ),
|
|
117 |
tr( "This layer contains no geometry features in the current map canvas extent. "
|
|
118 |
"Reposition the map or check \"Cache All Features\" in the layer selection dialog." ) );
|
|
119 |
mValid = false;
|
|
120 |
}
|
|
121 |
//[end mods for issue 4280]
|
102 |
122 |
|
103 |
123 |
if ( mValid )
|
104 |
124 |
{
|
... | ... | |
118 |
138 |
delete mSpatialIndex;
|
119 |
139 |
mSpatialIndex = new QgsSpatialIndex();
|
120 |
140 |
mValid = !getFeature( dataSourceUri() );
|
|
141 |
if ( mValid )
|
|
142 |
{ //record fetched extent per stand-alone BBOX (if any) in getFeature URI
|
|
143 |
mGetExtent = getUriBBox();
|
|
144 |
}
|
|
145 |
else
|
|
146 |
{ //getFeature failed; clear getFeature fetch extent
|
|
147 |
mGetExtent = QgsRectangle();
|
|
148 |
}
|
121 |
149 |
}
|
122 |
150 |
|
123 |
151 |
void QgsWFSProvider::deleteData()
|
... | ... | |
128 |
156 |
delete mFeatures[i];
|
129 |
157 |
}
|
130 |
158 |
mFeatures.clear();
|
|
159 |
mFeatureCount = 0;
|
|
160 |
mIdMap.clear();
|
131 |
161 |
}
|
132 |
162 |
|
133 |
163 |
void QgsWFSProvider::copyFeature( QgsFeature* f, QgsFeature& feature, bool fetchGeometry, QgsAttributeList fetchAttributes )
|
... | ... | |
269 |
299 |
mAttributesToFetch = fetchAttributes;
|
270 |
300 |
mFetchGeom = fetchGeometry;
|
271 |
301 |
|
|
302 |
//first time through, initialize GetRenderedOnly logic
|
|
303 |
//(layer object not available during ctor initialization)
|
|
304 |
//[wbc111228 issue 4604: fetch rendered features only]
|
|
305 |
//[issue 4280: fetch un-cached features on demand]
|
|
306 |
if ( mEncoding == QgsWFSProvider::GET && mInitUri.isEmpty() )
|
|
307 |
{ //did user check "Cache All Features" in WFS layer source selection?
|
|
308 |
if ( dataSourceUri().contains( "BBOX" ) )
|
|
309 |
{ //no: initialize incremental getFeature
|
|
310 |
mGetRenderedOnly = initGetRenderedOnly( rect );
|
|
311 |
}
|
|
312 |
//getRenderedOnly manipulates dataSourceUri; save original URI
|
|
313 |
mInitUri = dataSourceUri();
|
|
314 |
}
|
|
315 |
|
272 |
316 |
if ( rect.isEmpty() )
|
273 |
|
{ //select all features
|
|
317 |
{ //select all previously-retrieved features
|
274 |
318 |
mSpatialFilter = mExtent;
|
275 |
319 |
mSelectedFeatures = mFeatures.keys();
|
276 |
320 |
}
|
277 |
321 |
else
|
278 |
|
{ //select features intersecting caller's extent
|
279 |
|
QString dsURI = dataSourceUri();
|
280 |
|
//first time through, initialize GetRenderedOnly args
|
281 |
|
//ctor cannot initialize because layer object not available then
|
282 |
|
if ( ! mInitGro )
|
283 |
|
{ //did user check "Cache Features" in WFS layer source selection?
|
284 |
|
if ( dsURI.contains( "BBOX" ) )
|
285 |
|
{ //no: initialize incremental getFeature
|
286 |
|
if ( initGetRenderedOnly( rect ) )
|
287 |
|
{
|
288 |
|
mGetRenderedOnly = true;
|
289 |
|
}
|
290 |
|
else
|
291 |
|
{ //initialization failed;
|
292 |
|
QgsDebugMsg( QString( "GetRenderedOnly initialization failed; incorrect operation may occur\n%1" )
|
293 |
|
.arg( dataSourceUri() ) );
|
294 |
|
QMessageBox( QMessageBox::Warning, "Non-Cached layer initialization failed!",
|
295 |
|
QString( "Incorrect operation may occur:\n%1" ).arg( dataSourceUri() ) );
|
296 |
|
}
|
297 |
|
}
|
298 |
|
mInitGro = true;
|
299 |
|
}
|
300 |
|
|
|
322 |
{ //select features intersecting caller's extent (e.g., map canvas extent)
|
301 |
323 |
if ( mGetRenderedOnly )
|
302 |
|
{ //"Cache Features" was not selected for this layer
|
303 |
|
//has rendered extent expanded beyond last-retrieved WFS extent?
|
304 |
|
//NB: "intersect" instead of "contains" tolerates rounding errors;
|
305 |
|
// avoids unnecessary second fetch on zoom-in/zoom-out sequences
|
306 |
|
QgsRectangle olap( rect );
|
307 |
|
olap = olap.intersect( &mGetExtent );
|
308 |
|
if ( doubleNear( rect.width(), olap.width() ) && doubleNear( rect.height(), olap.height() ) )
|
309 |
|
{ //difference between canvas and layer extents is within rounding error: do not re-fetch
|
310 |
|
QgsDebugMsg( QString( "Layer %1 GetRenderedOnly: no fetch required" ).arg( mLayer->name() ) );
|
311 |
|
}
|
312 |
|
else
|
313 |
|
{ //combined old and new extents might speed up local panning & zooming
|
314 |
|
mGetExtent.combineExtentWith( &rect );
|
315 |
|
//but see if the combination is useless or too big
|
316 |
|
double pArea = mGetExtent.width() * mGetExtent.height();
|
317 |
|
double cArea = rect.width() * rect.height();
|
318 |
|
if ( olap.isEmpty() || pArea > ( cArea * 4.0 ) )
|
319 |
|
{ //new canvas extent does not overlap or combining old and new extents would
|
320 |
|
//fetch > 4 times the area to be rendered; get only what will be rendered
|
321 |
|
mGetExtent = rect;
|
322 |
|
}
|
323 |
|
QgsDebugMsg( QString( "Layer %1 GetRenderedOnly: fetching extent %2" )
|
324 |
|
.arg( mLayer->name(), mGetExtent.asWktCoordinates() ) );
|
325 |
|
dsURI = dsURI.replace( QRegExp( "BBOX=[^&]*" ),
|
326 |
|
QString( "BBOX=%1,%2,%3,%4" )
|
327 |
|
.arg( mGetExtent.xMinimum(), 0, 'f' )
|
328 |
|
.arg( mGetExtent.yMinimum(), 0, 'f' )
|
329 |
|
.arg( mGetExtent.xMaximum(), 0, 'f' )
|
330 |
|
.arg( mGetExtent.yMaximum(), 0, 'f' ) );
|
331 |
|
//TODO: BBOX may not be combined with FILTER. WFS spec v. 1.1.0, sec. 14.7.3 ff.
|
332 |
|
// if a FILTER is present, the BBOX must be merged into it, capabilities permitting.
|
333 |
|
// Else one criterion must be abandoned and the user warned. [WBC 111221]
|
334 |
|
setDataSourceUri( dsURI );
|
335 |
|
reloadData();
|
336 |
|
mLayer->updateExtents();
|
337 |
|
}
|
|
324 |
{ //"Cache All Features" was not selected for this layer; fetch features to be rendered
|
|
325 |
//[issues 4604 + 4280]
|
|
326 |
incrementalGet( rect );
|
338 |
327 |
}
|
339 |
328 |
|
340 |
329 |
mSpatialFilter = rect;
|
... | ... | |
713 |
702 |
}
|
714 |
703 |
|
715 |
704 |
int QgsWFSProvider::describeFeatureType( const QString& uri, QString& geometryAttribute,
|
716 |
|
QgsFieldMap& fields, QGis::WkbType& geomType )
|
|
705 |
QgsFieldMap& fields, QGis::WkbType& geomType )
|
717 |
706 |
//NB: also called from QgsWFSSourceSelect::on_treeWidget_itemDoubleClicked() to build filters.
|
718 |
707 |
// a temporary provider object is constructed with a null URI, which bypasses much provider
|
719 |
708 |
// instantiation logic: refresh(), getFeature(), etc. therefore, many provider class members
|
720 |
709 |
// are only default values or uninitialized when running under the source select dialog!
|
721 |
710 |
{
|
722 |
711 |
fields.clear();
|
723 |
|
//Local url or HTTP? WBC111221 refactored here from getFeature()
|
|
712 |
//Local url or HTTP?
|
724 |
713 |
switch ( mEncoding )
|
725 |
714 |
{
|
726 |
715 |
case QgsWFSProvider::GET:
|
727 |
|
{
|
728 |
716 |
return describeFeatureTypeGET( uri, geometryAttribute, fields, geomType );
|
729 |
|
}
|
730 |
717 |
case QgsWFSProvider::FILE:
|
731 |
|
{
|
732 |
718 |
return describeFeatureTypeFile( uri, geometryAttribute, fields, geomType );
|
733 |
|
}
|
734 |
719 |
}
|
735 |
720 |
QgsDebugMsg( "SHOULD NOT OCCUR: mEncoding undefined" );
|
736 |
721 |
return 1;
|
... | ... | |
768 |
753 |
QObject::connect( this, SIGNAL( dataReadProgressMessage( QString ) ), mainWindow, SLOT( showStatusMessage( QString ) ) );
|
769 |
754 |
}
|
770 |
755 |
|
771 |
|
if ( dataReader.getWFSData() != 0 )
|
|
756 |
if ( dataReader.getWFSData( mWfsFids ) != 0 )
|
772 |
757 |
{
|
773 |
758 |
QgsDebugMsg( "getWFSData returned with error" );
|
774 |
759 |
return 1;
|
... | ... | |
777 |
762 |
QgsDebugMsg( QString( "feature count after request is: %1" ).arg( mFeatures.size() ) );
|
778 |
763 |
QgsDebugMsg( QString( "mExtent after request is: %1" ).arg( mExtent.toString() ) );
|
779 |
764 |
|
780 |
|
for ( QMap<QgsFeatureId, QgsFeature*>::iterator it = mFeatures.begin(); it != mFeatures.end(); ++it )
|
|
765 |
for ( QMap<QgsFeatureId, QgsFeature*>::iterator it = mFeatures.find( mFeatureCount ); it != mFeatures.end(); ++it )
|
781 |
766 |
{
|
782 |
|
QgsDebugMsg( "feature " + FID_TO_STRING(( *it )->id() ) );
|
|
767 |
//QgsDebugMsg( "feature " + FID_TO_STRING(( *it )->id() ) );
|
783 |
768 |
mSpatialIndex->insertFeature( *( it.value() ) );
|
784 |
769 |
}
|
785 |
770 |
|
... | ... | |
832 |
817 |
|
833 |
818 |
mNetworkRequestFinished = false;
|
834 |
819 |
|
835 |
|
QString describeFeatureUri = uri;
|
836 |
|
describeFeatureUri.replace( QString( "GetFeature" ), QString( "DescribeFeatureType" ) );
|
837 |
|
QNetworkRequest request( describeFeatureUri );
|
|
820 |
QNetworkRequest request( uri );
|
838 |
821 |
QNetworkReply* reply = QgsNetworkAccessManager::instance()->get( request );
|
839 |
822 |
connect( reply, SIGNAL( finished() ), this, SLOT( networkRequestFinished() ) );
|
840 |
823 |
while ( !mNetworkRequestFinished )
|
... | ... | |
867 |
850 |
mNetworkRequestFinished = true;
|
868 |
851 |
}
|
869 |
852 |
|
870 |
|
int QgsWFSProvider::describeFeatureTypeFile( const QString& uri, QString& geometryAttribute, QgsFieldMap& fields, QGis::WkbType& geomType )
|
|
853 |
int QgsWFSProvider::describeFeatureTypeFile( const QString& uri, QString& geometryAttribute, QgsFieldMap& fields, QGis::WkbType& geomType)
|
871 |
854 |
{
|
872 |
855 |
//first look in the schema file
|
873 |
856 |
QString noExtension = uri;
|
... | ... | |
910 |
893 |
|
911 |
894 |
int QgsWFSProvider::readAttributesFromSchema( QDomDocument& schemaDoc, QString& geometryAttribute, QgsFieldMap& fields, QGis::WkbType& geomType )
|
912 |
895 |
{
|
|
896 |
QgsDebugMsg( schemaDoc.toString() );
|
913 |
897 |
//get the <schema> root element
|
914 |
898 |
QDomNodeList schemaNodeList = schemaDoc.elementsByTagNameNS( "http://www.w3.org/2001/XMLSchema", "schema" );
|
915 |
899 |
if ( schemaNodeList.length() < 1 )
|
... | ... | |
2323 |
2307 |
}
|
2324 |
2308 |
|
2325 |
2309 |
mCapabilities = capabilities;
|
|
2310 |
|
|
2311 |
//look for filter capabilities used by incremental get
|
|
2312 |
QDomElement e = capabilitiesDocument.elementsByTagNameNS( "http://www.opengis.net/ogc", "Filter_Capabilities" ).item(0).toElement();
|
|
2313 |
if ( ! e.tagName().isEmpty() )
|
|
2314 |
{
|
|
2315 |
mFilterCaps |= Filter;
|
|
2316 |
QDomNodeList o = e.elementsByTagNameNS( "http://www.opengis.net/ogc", "Logical_Operators" );
|
|
2317 |
if ( o.size() )
|
|
2318 |
{
|
|
2319 |
mFilterCaps |= LogicOps;
|
|
2320 |
}
|
|
2321 |
o = e.elementsByTagNameNS( "http://www.opengis.net/ogc", "BBOX" );
|
|
2322 |
if ( o.size() )
|
|
2323 |
{
|
|
2324 |
mFilterCaps |= BBox;
|
|
2325 |
}
|
|
2326 |
}
|
2326 |
2327 |
}
|
2327 |
2328 |
|
2328 |
2329 |
void QgsWFSProvider::appendSupportedOperations( const QDomElement& operationsElem, int& capabilities ) const
|
... | ... | |
2353 |
2354 |
}
|
2354 |
2355 |
|
2355 |
2356 |
//initialization for getRenderedOnly option
|
2356 |
|
//(formerly "Only request features overlapping the current view extent")
|
|
2357 |
//[wbc111228 issue 4604: fetch rendered features only]
|
|
2358 |
//[issue 4280: fetch un-cached features on demand]
|
2357 |
2359 |
bool QgsWFSProvider::initGetRenderedOnly( const QgsRectangle rect )
|
2358 |
2360 |
{ //find our layer
|
|
2361 |
Q_UNUSED( rect );
|
2359 |
2362 |
QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
|
2360 |
2363 |
QMap<QString, QgsMapLayer*>::const_iterator layersIt = layers.begin();
|
2361 |
2364 |
for ( ; layersIt != layers.end() ; ++layersIt )
|
2362 |
2365 |
{
|
2363 |
|
if (( mLayer = dynamic_cast<QgsVectorLayer*>( layersIt.value() ) ) )
|
|
2366 |
if ( ( mLayer = dynamic_cast<QgsVectorLayer*>( layersIt.value() ) ) )
|
2364 |
2367 |
{
|
2365 |
2368 |
if ( mLayer->dataProvider() == this )
|
2366 |
2369 |
{
|
... | ... | |
2371 |
2374 |
}
|
2372 |
2375 |
if ( layersIt == layers.end() )
|
2373 |
2376 |
{
|
2374 |
|
QgsDebugMsg( "SHOULD NOT OCCUR: initialize() did not find layer." );
|
|
2377 |
QgsDebugMsg( QString( "SHOULD NOT OCCUR: initialize() did not find layer for %1." )
|
|
2378 |
.arg( dataSourceUri() ) );
|
|
2379 |
QMessageBox::critical ( 0, tr( "Non-Cached layer initialization failed!" ),
|
|
2380 |
tr( "Incorrect operation may occur:\n%1" ).arg( dataSourceUri() ) );
|
2375 |
2381 |
return false;
|
2376 |
2382 |
}
|
2377 |
2383 |
return true;
|
... | ... | |
2379 |
2385 |
|
2380 |
2386 |
QGis::WkbType QgsWFSProvider::geomTypeFromPropertyType( QString attName, QString propType )
|
2381 |
2387 |
{
|
2382 |
|
const QStringList geomTypes = ( QStringList()
|
2383 |
|
//all GML v.2.1.3 _geometryProperty group members, except MultiGeometryPropertyType
|
2384 |
|
//sequence must exactly match enum Qgis::WkbType
|
2385 |
|
<< "" // unknown geometry, enum 0
|
2386 |
|
<< "Point"
|
2387 |
|
<< "LineString"
|
2388 |
|
<< "Polygon"
|
2389 |
|
<< "MultiPoint"
|
2390 |
|
<< "MultiLineString"
|
2391 |
|
<< "MultiPolygon" );
|
|
2388 |
const QStringList geomTypes = ( QStringList ()
|
|
2389 |
//all GML v.2.1.3 _geometryProperty group members, except MultiGeometryPropertyType
|
|
2390 |
//sequence must exactly match enum Qgis::WkbType
|
|
2391 |
<< "" // unknown geometry, enum 0
|
|
2392 |
<< "Point"
|
|
2393 |
<< "LineString"
|
|
2394 |
<< "Polygon"
|
|
2395 |
<< "MultiPoint"
|
|
2396 |
<< "MultiLineString"
|
|
2397 |
<< "MultiPolygon" );
|
2392 |
2398 |
|
2393 |
2399 |
QgsDebugMsg( QString( "DescribeFeatureType geometry attribute \"%1\" type is \"%2\"" )
|
2394 |
|
.arg( attName, propType ) );
|
|
2400 |
.arg( attName, propType ) );
|
2395 |
2401 |
int i = geomTypes.indexOf( propType );
|
2396 |
2402 |
if ( i <= 0 )
|
2397 |
2403 |
{ // feature type missing or unknown
|
2398 |
|
i = ( int ) QGis::WKBUnknown;
|
|
2404 |
i = (int) QGis::WKBUnknown;
|
|
2405 |
}
|
|
2406 |
return (QGis::WkbType) i;
|
|
2407 |
}
|
|
2408 |
|
|
2409 |
//if not caching whole layer, add features to the cache incrementally
|
|
2410 |
//as pans and zooms expose new areas on the map canvas
|
|
2411 |
//[wbc111228 issue 4280: fetch un-cached features on demand]
|
|
2412 |
void QgsWFSProvider::incrementalGet( QgsRectangle rect )
|
|
2413 |
{
|
|
2414 |
QgsDebugMsg( QString( "\nprevious get extent %1\n select extent %2" ).arg( mGetExtent.toString(), rect.toString() ) );
|
|
2415 |
//has rendered extent expanded beyond last-retrieved WFS extent?
|
|
2416 |
//NB: compare overlap area instead of using "contains" function
|
|
2417 |
// avoids double fetch due to rounding error on zoom-in/zoom-out sequences
|
|
2418 |
QgsRectangle olap( rect );
|
|
2419 |
olap = olap.intersect( &mGetExtent );
|
|
2420 |
//is there any overlap between current layer extent and new canvas?
|
|
2421 |
//and can WFS provider do ORed BBOX filters?
|
|
2422 |
if ( olap.isEmpty() || ( ( mFilterCaps & ( Filter | LogicOps | BBox ) ) != ( Filter | LogicOps | BBox ) ) )
|
|
2423 |
{ //no, discard feature cache and do fresh fetch for new extent
|
|
2424 |
QgsDebugMsg( QString( "Layer %1 GetRenderedOnly: fetch replace" ).arg( mLayer->name() ) );
|
|
2425 |
//does original URI contain a user-specified filter?
|
|
2426 |
if ( mInitUri.contains( "&FILTER=" ) )
|
|
2427 |
{ //yes, but FILTER & BBOX are mutually exclusive in WSF getFeature URL; merge BBOX into FILTER
|
|
2428 |
setDataSourceUri( makeFilteredUri( QList<QgsRectangle>() << rect ) );
|
|
2429 |
}
|
|
2430 |
else
|
|
2431 |
{ //URI contains no FILTER; continue with stand-alone BBOX
|
|
2432 |
setDataSourceUri( setUriBBox( rect ) );
|
|
2433 |
}
|
|
2434 |
reloadData();
|
|
2435 |
if ( mValid )
|
|
2436 |
{ //reloadData() cannot set mGetExtent for URI with filter
|
|
2437 |
mGetExtent = rect;
|
|
2438 |
}
|
|
2439 |
}
|
|
2440 |
else if ( ( ( rect.width() - olap.width() ) < 1e-6 )
|
|
2441 |
&& ( ( rect.height() - olap.height() ) < 1e-6 ) )
|
|
2442 |
{ //difference between canvas and layer extents is less than URL BBOX precision (6 decimal places)
|
|
2443 |
QgsDebugMsg( QString( "Layer %1 GetRenderedOnly: no fetch required" ).arg( mLayer->name() ) );
|
|
2444 |
return;
|
2399 |
2445 |
}
|
2400 |
|
return ( QGis::WkbType ) i;
|
|
2446 |
else
|
|
2447 |
{ //new canvas extent overlaps old extent: add features in new areas to existing layer
|
|
2448 |
|
|
2449 |
//compute minimum expansion: at least 1/3 of desired extent in each direction that expands
|
|
2450 |
QgsRectangle newGetExt( mGetExtent );
|
|
2451 |
newGetExt.combineExtentWith( &rect );
|
|
2452 |
double minDeltaX = ( rect.xMaximum() - rect.xMinimum() ) / 3.;
|
|
2453 |
double minDeltaY = ( rect.yMaximum() - rect.yMinimum() ) / 3.;
|
|
2454 |
if ( rect.xMinimum() < mGetExtent.xMinimum() && rect.xMinimum() > ( mGetExtent.xMinimum() - minDeltaX) )
|
|
2455 |
{
|
|
2456 |
newGetExt.setXMinimum ( mGetExtent.xMinimum() - minDeltaX );
|
|
2457 |
}
|
|
2458 |
if ( rect.yMinimum() < mGetExtent.yMinimum() && rect.yMinimum() > ( mGetExtent.yMinimum() - minDeltaY) )
|
|
2459 |
{
|
|
2460 |
newGetExt.setYMinimum ( mGetExtent.yMinimum() - minDeltaY );
|
|
2461 |
}
|
|
2462 |
if ( rect.xMaximum() > mGetExtent.xMaximum() && rect.xMaximum() < ( mGetExtent.xMaximum() + minDeltaX) )
|
|
2463 |
{
|
|
2464 |
newGetExt.setXMaximum ( mGetExtent.xMaximum() + minDeltaX );
|
|
2465 |
}
|
|
2466 |
if ( rect.yMaximum() > mGetExtent.yMaximum() && rect.yMaximum() < ( mGetExtent.yMaximum() + minDeltaY) )
|
|
2467 |
{
|
|
2468 |
newGetExt.setYMaximum ( mGetExtent.yMaximum() + minDeltaY );
|
|
2469 |
}
|
|
2470 |
|
|
2471 |
//calculate between 1 and 4 rectangles to be fetched around perimeter of existing layer extent
|
|
2472 |
QList<QgsRectangle> addArea;
|
|
2473 |
if ( newGetExt.xMinimum() < mGetExtent.xMinimum() )
|
|
2474 |
{ //new area left of current extent
|
|
2475 |
addArea.append( QgsRectangle( newGetExt.xMinimum(), mGetExtent.yMinimum(), mGetExtent.xMinimum(), mGetExtent.yMaximum() ) );
|
|
2476 |
}
|
|
2477 |
if ( newGetExt.yMaximum() > mGetExtent.yMaximum() )
|
|
2478 |
{ //new area above current extent
|
|
2479 |
addArea.append( QgsRectangle( newGetExt.xMinimum(), mGetExtent.yMaximum(), mGetExtent.xMaximum(), newGetExt.yMaximum() ) );
|
|
2480 |
}
|
|
2481 |
if ( newGetExt.xMaximum() > mGetExtent.xMaximum() )
|
|
2482 |
{ //new area right of current extent
|
|
2483 |
addArea.append( QgsRectangle( mGetExtent.xMaximum(), mGetExtent.yMinimum(), newGetExt.xMaximum(), newGetExt.yMaximum() ) );
|
|
2484 |
}
|
|
2485 |
if ( newGetExt.yMinimum() < mGetExtent.yMinimum() )
|
|
2486 |
{ //new area beneath current extent
|
|
2487 |
addArea.append( QgsRectangle( newGetExt.xMinimum(), newGetExt.yMinimum(), newGetExt.xMaximum(), mGetExtent.yMinimum() ) );
|
|
2488 |
}
|
|
2489 |
QgsDebugMsg( QString( "Layer %1 GetRenderedOnly: incremental fetch, %2 areas" ).arg( mLayer->name() ).arg( addArea.size() ) );
|
|
2490 |
|
|
2491 |
//commented logic below allows duplicate FIDS to pass because some WFS
|
|
2492 |
//providers transmit features that do not strictly intersect the BBOX
|
|
2493 |
/*get WFS provider IDs of already-fetched features
|
|
2494 |
QList<QgsFeatureId> edgeFids;
|
|
2495 |
mWfsFids = new QSet<QString>;
|
|
2496 |
for ( int i = 0; i < addArea.size() ; ++i )
|
|
2497 |
{ //identify features extending into each new area
|
|
2498 |
edgeFids.append( mSpatialIndex->intersects( addArea[i] ) );
|
|
2499 |
}
|
|
2500 |
while ( ! edgeFids.isEmpty() )
|
|
2501 |
{ //collect known WFS provider FIDs of identified
|
|
2502 |
mWfsFids->insert( mIdMap.value( edgeFids.takeFirst() ) );
|
|
2503 |
}
|
|
2504 |
QgsDebugMsg( QString( "WFS FIDs to discard next fetch: %1" ).arg( mWfsFids->size() ) );*/
|
|
2505 |
//because of problem above, every known WFS FID is a discard candidate
|
|
2506 |
mWfsFids = new QSet<QString>( mIdMap.values().toSet() );
|
|
2507 |
|
|
2508 |
//save current provider extent (getFeature calls to data reader overwrite mExtent)
|
|
2509 |
QgsRectangle saveMExtent( mExtent );
|
|
2510 |
//fetch features in new areas, discarding features already fetched
|
|
2511 |
setDataSourceUri ( makeFilteredUri( addArea ) );
|
|
2512 |
if ( getFeature( dataSourceUri() ) ) //QgsWFSData::endElement discards IDs in mWfsFids
|
|
2513 |
{ //getFeature failed (timeout, abort); we no longer know the extent actually fetched
|
|
2514 |
mValid = false; //layer is now invalid
|
|
2515 |
mGetExtent = QgsRectangle(); //getFeature extent is now unknown
|
|
2516 |
}
|
|
2517 |
else
|
|
2518 |
{
|
|
2519 |
mExtent.combineExtentWith ( &saveMExtent ); //sum of extents just received & previously cached
|
|
2520 |
mGetExtent = newGetExt;
|
|
2521 |
}
|
|
2522 |
delete mWfsFids;
|
|
2523 |
mWfsFids = 0;
|
|
2524 |
//save URI for possible later user refresh()
|
|
2525 |
setDataSourceUri ( setUriBBox ( rect ) );
|
|
2526 |
}
|
|
2527 |
QgsDebugMsg( QString( "features=%1; WSF FIDs=%2; unique WSF FIDs=%3" )
|
|
2528 |
.arg( mFeatures.size() ).arg( mIdMap.size() )
|
|
2529 |
.arg( mIdMap.values().toSet().size() ) );
|
|
2530 |
}
|
|
2531 |
|
|
2532 |
//transform provider URI to express getFeatures BBOX as FILTER
|
|
2533 |
QString QgsWFSProvider::makeFilteredUri( QList<QgsRectangle> addArea )
|
|
2534 |
{
|
|
2535 |
//start with initial URI
|
|
2536 |
QString uri = mInitUri;
|
|
2537 |
//if the user specified a filter, get it
|
|
2538 |
QDomElement root;
|
|
2539 |
QDomNode n;
|
|
2540 |
QDomDocument filter = QDomDocument();
|
|
2541 |
QRegExp fre( "&FILTER=(.*)" ); // assumes &FILTER is last argument on URL
|
|
2542 |
//does the URI already contain a valid XML filter expression?
|
|
2543 |
if ( uri.contains( fre ) && filter.setContent( fre.cap(1) ) )
|
|
2544 |
{ //yes, create insertion point to AND a BBOX filter
|
|
2545 |
root = filter.firstChild().toElement(); // <Filter> element
|
|
2546 |
QDomNode userFilter = root.removeChild( root.firstChild() );
|
|
2547 |
n = root.appendChild( filter.createElement( "And" ) );
|
|
2548 |
n.appendChild( userFilter );
|
|
2549 |
QgsDebugMsg( QString( "filter=%1" ).arg( filter.toString() ) );
|
|
2550 |
}
|
|
2551 |
else
|
|
2552 |
{ //no, create new OGC XML Filter
|
|
2553 |
QDomElement root = filter.createElement( "Filter" );
|
|
2554 |
n = filter.appendChild( root );
|
|
2555 |
}
|
|
2556 |
//define namespace for "Box" and "coordinates"
|
|
2557 |
root.setAttribute( "xmlns:gml", "http://www.opengis.net/gml" );
|
|
2558 |
//OR a new BBOX clause (ver. 1.0.0) for each new area
|
|
2559 |
if ( addArea.size() > 1 )
|
|
2560 |
{
|
|
2561 |
n = n.appendChild( filter.createElement( "Or" ) );
|
|
2562 |
}
|
|
2563 |
while ( ! addArea.isEmpty() )
|
|
2564 |
{
|
|
2565 |
QDomNode b = n.appendChild( filter.createElement( "BBOX" ) );
|
|
2566 |
QDomElement p = filter.createElement("PropertyName" );
|
|
2567 |
p.appendChild( filter.createTextNode( mGeometryAttribute ) );
|
|
2568 |
b.appendChild( p );
|
|
2569 |
//filter XML v. 1.0.0 -- accepted but ignored on some arcgis systems [wbc121130]
|
|
2570 |
QDomElement v = filter.createElement( "gml:Box" );
|
|
2571 |
v.setAttribute( "srsName", mSourceCRS.authid() );
|
|
2572 |
QDomNode e = b.appendChild( v );
|
|
2573 |
QDomNode c = e.appendChild( filter.createElement( "gml:coordinates" ) );
|
|
2574 |
QgsRectangle delta = addArea.takeFirst();
|
|
2575 |
c.appendChild( filter.createTextNode( QString( "%1,%2 %3,%4" )
|
|
2576 |
.arg( delta.xMinimum(), 0, 'f').arg(delta.yMinimum(), 0, 'f')
|
|
2577 |
.arg( delta.xMaximum(), 0, 'f').arg(delta.yMaximum(), 0, 'f') ) );
|
|
2578 |
}
|
|
2579 |
QgsDebugMsg( QString( "filter=%1" ).arg( filter.toString() ) );
|
|
2580 |
|
|
2581 |
//get features contained in each new area
|
|
2582 |
uri.replace( QRegExp( "BBOX=.*" ), QString( "FILTER=%1" ).arg( filter.toString( -1 ) ) );
|
|
2583 |
return uri;
|
2401 |
2584 |
}
|
2402 |
2585 |
|
|
2586 |
QString QgsWFSProvider::setUriBBox( QgsRectangle rect )
|
|
2587 |
{
|
|
2588 |
QgsDebugMsg( QString( "layer %1 GetRenderedOnly: fetching extent %2" )
|
|
2589 |
.arg( mLayer->name(), rect.asWktCoordinates() ) );
|
|
2590 |
QString dsURI = mInitUri;
|
|
2591 |
dsURI = dsURI.replace( QRegExp( "BBOX=[^&]*" ),
|
|
2592 |
QString( "BBOX=%1,%2,%3,%4" )
|
|
2593 |
.arg( rect.xMinimum(), 0, 'f' )
|
|
2594 |
.arg( rect.yMinimum(), 0, 'f' )
|
|
2595 |
.arg( rect.xMaximum(), 0, 'f' )
|
|
2596 |
.arg( rect.yMaximum(), 0, 'f' ) );
|
|
2597 |
return dsURI;
|
|
2598 |
}
|
|
2599 |
|
|
2600 |
QgsRectangle QgsWFSProvider::getUriBBox()
|
|
2601 |
{
|
|
2602 |
bool ok = true;
|
|
2603 |
QRegExp uriBBox( "&BBOX=(([-+.0-9]+,?){4})" );
|
|
2604 |
if ( dataSourceUri().contains( uriBBox ) )
|
|
2605 |
{
|
|
2606 |
QList<QString> coords = uriBBox.cap(1).split( "," );
|
|
2607 |
QList<double> coordd;
|
|
2608 |
while ( ok && ! coords.isEmpty() )
|
|
2609 |
{
|
|
2610 |
coordd.append( coords.takeFirst().toDouble( &ok ) );
|
|
2611 |
}
|
|
2612 |
if ( ok )
|
|
2613 |
{
|
|
2614 |
return QgsRectangle( coordd[0], coordd[1], coordd[2], coordd[3] );
|
|
2615 |
}
|
|
2616 |
}
|
|
2617 |
return QgsRectangle();
|
|
2618 |
}
|
|
2619 |
//[end issue 4280]
|
|
2620 |
|
2403 |
2621 |
void QgsWFSProvider::handleException( const QDomDocument& serverResponse ) const
|
2404 |
2622 |
{
|
2405 |
2623 |
QDomElement exceptionElem = serverResponse.documentElement();
|
... | ... | |
2435 |
2653 |
QGISEXTERN bool isProvider()
|
2436 |
2654 |
{
|
2437 |
2655 |
return true;
|
2438 |
|
}
|
|
2656 |
}
|