Example #1
0
bool QgsAfsProvider::getFeature( const QgsFeatureId &id, QgsFeature &f, bool fetchGeometry, const QList<int>& /*fetchAttributes*/, const QgsRectangle filterRect )
{
  // If cached, return cached feature
  QMap<QgsFeatureId, QgsFeature>::const_iterator it = mCache.find( id );
  if ( it != mCache.end() )
  {
    f = it.value();
    return filterRect.isNull() || f.geometry().intersects( filterRect );
  }

  // Determine attributes to fetch
  /*QStringList fetchAttribNames;
  foreach ( int idx, fetchAttributes )
    fetchAttribNames.append( mFields.at( idx ).name() );
  */

  // When fetching from server, fetch all attributes and geometry by default so that we can cache them
  QStringList fetchAttribNames;
  QList<int> fetchAttribIdx;
  for ( int idx = 0, n = mFields.size(); idx < n; ++idx )
  {
    fetchAttribNames.append( mFields.at( idx ).name() );
    fetchAttribIdx.append( idx );
  }
  fetchGeometry = true;

  // Fetch 100 features at the time
  int startId = ( id / 100 ) * 100;
  int stopId = qMin( startId + 100, mObjectIds.length() );
  QList<quint32> objectIds;
  for ( int i = startId; i < stopId; ++i )
  {
    objectIds.append( mObjectIds[i] );
  }


  // Query
  QString errorTitle, errorMessage;
  QVariantMap queryData = QgsArcGisRestUtils::getObjects(
                            mDataSource.param( "url" ), objectIds, mDataSource.param( "crs" ), fetchGeometry,
                            fetchAttribNames, QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ),
                            filterRect, errorTitle, errorMessage );
  if ( queryData.isEmpty() )
  {
    const_cast<QgsAfsProvider*>( this )->pushError( errorTitle + ": " + errorMessage );
    QgsDebugMsg( "Query returned empty result" );
    return false;
  }

  QVariantList featuresData = queryData["features"].toList();
  if ( featuresData.isEmpty() )
  {
    QgsDebugMsg( "Query returned no features" );
    return false;
  }
  for ( int i = 0, n = featuresData.size(); i < n; ++i )
  {
    QVariantMap featureData = featuresData[i].toMap();
    QgsFeature feature;

    // Set FID
    feature.setFeatureId( startId + i );

    // Set attributes
    if ( !fetchAttribIdx.isEmpty() )
    {
      QVariantMap attributesData = featureData["attributes"].toMap();
      feature.setFields( mFields );
      QgsAttributes attributes( mFields.size() );
      foreach ( int idx, fetchAttribIdx )
      {
        attributes[idx] = attributesData[mFields.at( idx ).name()];
      }
      feature.setAttributes( attributes );
    }

    // Set geometry
    if ( fetchGeometry )
    {
      QVariantMap geometryData = featureData["geometry"].toMap();
      QgsAbstractGeometry* geometry = QgsArcGisRestUtils::parseEsriGeoJSON( geometryData, queryData["geometryType"].toString(),
                                      QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ) );
      // Above might return 0, which is ok since in theory empty geometries are allowed
      feature.setGeometry( QgsGeometry( geometry ) );
    }
    feature.setValid( true );
    mCache.insert( feature.id(), feature );
  }
bool QgsAfsSharedData::getFeature( QgsFeatureId id, QgsFeature &f, const QgsRectangle &filterRect, QgsFeedback *feedback )
{
  QMutexLocker locker( &mMutex );

  // If cached, return cached feature
  QMap<QgsFeatureId, QgsFeature>::const_iterator it = mCache.constFind( id );
  if ( it != mCache.constEnd() )
  {
    f = it.value();
    return filterRect.isNull() || ( f.hasGeometry() && f.geometry().intersects( filterRect ) );
  }

  // When fetching from server, fetch all attributes and geometry by default so that we can cache them
  QStringList fetchAttribNames;
  QList<int> fetchAttribIdx;
  fetchAttribIdx.reserve( mFields.size() );
  fetchAttribNames.reserve( mFields.size() );
  for ( int idx = 0, n = mFields.size(); idx < n; ++idx )
  {
    fetchAttribNames.append( mFields.at( idx ).name() );
    fetchAttribIdx.append( idx );
  }

  // Fetch 100 features at the time
  int startId = ( id / 100 ) * 100;
  int stopId = std::min( startId + 100, mObjectIds.length() );
  QList<quint32> objectIds;
  objectIds.reserve( stopId );
  for ( int i = startId; i < stopId; ++i )
  {
    if ( i >= 0 && i < mObjectIds.count() )
      objectIds.append( mObjectIds[i] );
  }

  if ( objectIds.empty() )
  {
    QgsDebugMsg( QStringLiteral( "No valid features IDs to fetch" ) );
    return false;
  }

  // don't lock while doing the fetch
  locker.unlock();

  // Query
  QString errorTitle, errorMessage;

  const QString authcfg = mDataSource.authConfigId();
  const QVariantMap queryData = QgsArcGisRestUtils::getObjects(
                                  mDataSource.param( QStringLiteral( "url" ) ), authcfg, objectIds, mDataSource.param( QStringLiteral( "crs" ) ), true,
                                  fetchAttribNames, QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ),
                                  filterRect, errorTitle, errorMessage, feedback );

  if ( queryData.isEmpty() )
  {
//    const_cast<QgsAfsProvider *>( this )->pushError( errorTitle + ": " + errorMessage );
    QgsDebugMsg( QStringLiteral( "Query returned empty result" ) );
    return false;
  }

  // but re-lock while updating cache
  locker.relock();
  const QVariantList featuresData = queryData[QStringLiteral( "features" )].toList();
  if ( featuresData.isEmpty() )
  {
    QgsDebugMsgLevel( QStringLiteral( "Query returned no features" ), 3 );
    return false;
  }
  for ( int i = 0, n = featuresData.size(); i < n; ++i )
  {
    const QVariantMap featureData = featuresData[i].toMap();
    QgsFeature feature;
    int featureId = startId + i;

    // Set attributes
    if ( !fetchAttribIdx.isEmpty() )
    {
      const QVariantMap attributesData = featureData[QStringLiteral( "attributes" )].toMap();
      feature.setFields( mFields );
      QgsAttributes attributes( mFields.size() );
      for ( int idx : qgis::as_const( fetchAttribIdx ) )
      {
        QVariant attribute = attributesData[mFields.at( idx ).name()];
        if ( attribute.isNull() )
        {
          // ensure that null values are mapped correctly for PyQGIS
          attribute = QVariant( QVariant::Int );
        }

        // date/datetime fields must be converted
        if ( mFields.at( idx ).type() == QVariant::DateTime || mFields.at( idx ).type() == QVariant::Date )
          attribute = QgsArcGisRestUtils::parseDateTime( attribute );

        if ( !mFields.at( idx ).convertCompatible( attribute ) )
        {
          QgsDebugMsg( QStringLiteral( "Invalid value %1 for field %2 of type %3" ).arg( attributesData[mFields.at( idx ).name()].toString(), mFields.at( idx ).name(), mFields.at( idx ).typeName() ) );
        }
        attributes[idx] = attribute;
        if ( mFields.at( idx ).name() == QStringLiteral( "OBJECTID" ) )
        {
          featureId = startId + objectIds.indexOf( attributesData[mFields.at( idx ).name()].toInt() );
        }
      }
      feature.setAttributes( attributes );
    }

    // Set FID
    feature.setId( featureId );

    // Set geometry
    const QVariantMap geometryData = featureData[QStringLiteral( "geometry" )].toMap();
    std::unique_ptr< QgsAbstractGeometry > geometry = QgsArcGisRestUtils::parseEsriGeoJSON( geometryData, queryData[QStringLiteral( "geometryType" )].toString(),
        QgsWkbTypes::hasM( mGeometryType ), QgsWkbTypes::hasZ( mGeometryType ) );
    // Above might return 0, which is OK since in theory empty geometries are allowed
    if ( geometry )
      feature.setGeometry( QgsGeometry( std::move( geometry ) ) );
    feature.setValid( true );
    mCache.insert( feature.id(), feature );
  }

  // If added to cache, return feature
  it = mCache.constFind( id );
  if ( it != mCache.constEnd() )
  {
    f = it.value();
    return filterRect.isNull() || ( f.hasGeometry() && f.geometry().intersects( filterRect ) );
  }

  return false;
}