예제 #1
0
bool QgsWFSProvider::describeFeatureType( QString& geometryAttribute, QgsFields& fields, QGis::WkbType& geomType )
{
  fields.clear();

  QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI.uri() );
  if ( !describeFeatureType.requestFeatureType( mShared->mWFSVersion,
       mShared->mURI.typeName() ) )
  {
    QgsMessageLog::logMessage( tr( "DescribeFeatureType failed for url %1: %2" ).
                               arg( dataSourceUri() ).arg( describeFeatureType.errorMessage() ), tr( "WFS" ) );
    return false;
  }

  const QByteArray& response = describeFeatureType.response();

  QDomDocument describeFeatureDocument;
  QString errorMsg;
  if ( !describeFeatureDocument.setContent( response, true, &errorMsg ) )
  {
    QgsDebugMsg( response );
    QgsMessageLog::logMessage( tr( "DescribeFeatureType failed for url %1: %2" ).
                               arg( dataSourceUri() ).arg( errorMsg ), tr( "WFS" ) );
    return false;
  }

  if ( readAttributesFromSchema( describeFeatureDocument,
                                 geometryAttribute, fields, geomType ) != 0 )
  {
    QgsMessageLog::logMessage( tr( "DescribeFeatureType failed for url %1: %2" ).
                               arg( dataSourceUri() ).arg( tr( "failed retrieving attributes from schema" ) ), tr( "WFS" ) );
    return false;
  }

  return true;
}
예제 #2
0
QgsRasterInterface *QgsAmsProvider::clone() const
{
  QgsDataProvider::ProviderOptions options;
  QgsAmsProvider *provider = new QgsAmsProvider( dataSourceUri(), options );
  provider->copyBaseSettings( *this );
  return provider;
}
예제 #3
0
QgsAmsProvider::QgsAmsProvider( const QString &uri, const ProviderOptions &options )
  : QgsRasterDataProvider( uri, options )
{
  mLegendFetcher = new QgsAmsLegendFetcher( this );

  QgsDataSourceUri dataSource( dataSourceUri() );
  const QString authcfg = dataSource.authConfigId();

  mServiceInfo = QgsArcGisRestUtils::getServiceInfo( dataSource.param( QStringLiteral( "url" ) ), authcfg, mErrorTitle, mError );
  mLayerInfo = QgsArcGisRestUtils::getLayerInfo( dataSource.param( QStringLiteral( "url" ) ) + "/" + dataSource.param( QStringLiteral( "layer" ) ), authcfg, mErrorTitle, mError );

  const QVariantMap extentData = mLayerInfo.value( QStringLiteral( "extent" ) ).toMap();
  mExtent.setXMinimum( extentData[QStringLiteral( "xmin" )].toDouble() );
  mExtent.setYMinimum( extentData[QStringLiteral( "ymin" )].toDouble() );
  mExtent.setXMaximum( extentData[QStringLiteral( "xmax" )].toDouble() );
  mExtent.setYMaximum( extentData[QStringLiteral( "ymax" )].toDouble() );
  mCrs = QgsArcGisRestUtils::parseSpatialReference( extentData[QStringLiteral( "spatialReference" )].toMap() );
  if ( !mCrs.isValid() )
  {
    appendError( QgsErrorMessage( tr( "Could not parse spatial reference" ), QStringLiteral( "AMSProvider" ) ) );
    return;
  }
  const QVariantList subLayersList = mLayerInfo.value( QStringLiteral( "subLayers" ) ).toList();
  mSubLayers.reserve( subLayersList.size() );
  for ( const QVariant &sublayer : subLayersList )
  {
    mSubLayers.append( sublayer.toMap()[QStringLiteral( "id" )].toString() );
    mSubLayerVisibilities.append( true );
  }

  mTimestamp = QDateTime::currentDateTime();
  mValid = true;
}
예제 #4
0
bool QgsDb2Provider::setSubsetString( const QString& theSQL, bool )
{
  QString prevWhere = mSqlWhereClause;
  QgsDebugMsg( theSQL );
  mSqlWhereClause = theSQL.trimmed();

  QString sql = QString( "SELECT COUNT(*) FROM " );

  sql += QString( "%1.%2" ).arg( mSchemaName, mTableName );

  if ( !mSqlWhereClause.isEmpty() )
  {
    sql += QString( " WHERE %1" ).arg( mSqlWhereClause );
  }

  if ( !openDatabase( mDatabase ) )
  {
    return false;
  }

  QSqlQuery query = QSqlQuery( mDatabase );
  query.setForwardOnly( true );
  QgsDebugMsg( sql );
  if ( !query.exec( sql ) )
  {
    pushError( query.lastError().text() );
    mSqlWhereClause = prevWhere;
    QgsDebugMsg( query.lastError().text() );
    return false;
  }

  if ( query.isActive() && query.next() )
  {
    mNumberFeatures = query.value( 0 ).toInt();
    QgsDebugMsg( QString( "count: %1" ).arg( mNumberFeatures ) );
  }
  else
  {
    pushError( query.lastError().text() );
    mSqlWhereClause = prevWhere;
    QgsDebugMsg( query.lastError().text() );
    return false;
  }

  QgsDataSourceUri anUri = QgsDataSourceUri( dataSourceUri() );
  anUri.setSql( mSqlWhereClause );

  setDataSourceUri( anUri.uri() );

  mExtent.setMinimal();

  emit dataChanged();

  return true;
}
예제 #5
0
bool QgsWFSProvider::getCapabilities()
{
  mCapabilities = 0;

  QgsWFSCapabilities getCapabilities( mShared->mURI.uri() );
  if ( !getCapabilities.requestCapabilities( true ) )
  {
    QgsMessageLog::logMessage( tr( "GetCapabilities failed for url %1: %2" ).
                               arg( dataSourceUri() ).arg( getCapabilities.errorMessage() ), tr( "WFS" ) );
    return false;
  }

  const QgsWFSCapabilities::Capabilities caps = getCapabilities.capabilities();

  mShared->mWFSVersion = caps.version;
  if ( mShared->mURI.maxNumFeatures() > 0 )
    mShared->mMaxFeatures = mShared->mURI.maxNumFeatures();
  else
    mShared->mMaxFeatures = caps.maxFeatures;
  mShared->mMaxFeaturesServer = caps.maxFeatures;
  mShared->mSupportsHits = caps.supportsHits;
  mShared->mSupportsPaging = caps.supportsPaging;

  //find the <FeatureType> for this layer
  QString thisLayerName = mShared->mURI.typeName();
  for ( int i = 0; i < caps.featureTypes.size(); i++ )
  {
    if ( thisLayerName == caps.featureTypes[i].name )
    {
      const QgsRectangle& r = caps.featureTypes[i].bboxLongLat;
      if ( mShared->mSourceCRS.authid().isEmpty() && caps.featureTypes[i].crslist.size() != 0 )
      {
        mShared->mSourceCRS.createFromOgcWmsCrs( caps.featureTypes[i].crslist[0] );
      }
      if ( !r.isNull() )
      {
        QgsCoordinateReferenceSystem src;
        src.createFromOgcWmsCrs( "CRS:84" );
        QgsCoordinateTransform ct( src, mShared->mSourceCRS );

        QgsDebugMsg( "latlon ext:" + r.toString() );
        QgsDebugMsg( "src:" + src.authid() );
        QgsDebugMsg( "dst:" + mShared->mSourceCRS.authid() );

        mExtent = ct.transformBoundingBox( r, QgsCoordinateTransform::ForwardTransform );

        QgsDebugMsg( "layer ext:" + mExtent.toString() );
      }
      if ( caps.featureTypes[i].insertCap )
      {
        mCapabilities |= QgsVectorDataProvider::AddFeatures;
      }
      if ( caps.featureTypes[i].updateCap )
      {
        mCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
        mCapabilities |= QgsVectorDataProvider::ChangeGeometries;
      }
      if ( caps.featureTypes[i].deleteCap )
      {
        mCapabilities |= QgsVectorDataProvider::DeleteFeatures;
      }

      return true;
    }
  }
  QgsMessageLog::logMessage( tr( "Could not find typename %1 in capabilites for url %2" ).
                             arg( thisLayerName ).arg( dataSourceUri() ), tr( "WFS" ) );
  return false;
}
예제 #6
0
QgsRasterInterface *QgsGrassRasterProvider::clone() const
{
  QgsGrassRasterProvider *provider = new QgsGrassRasterProvider( dataSourceUri() );
  provider->copyBaseSettings( *this );
  return provider;
}
QgsRasterInterface * QgsGrassRasterProvider::clone() const
{
  QgsGrassRasterProvider * provider = new QgsGrassRasterProvider( dataSourceUri() );
  return provider;
}
예제 #8
0
QgsDelimitedTextProvider::QgsDelimitedTextProvider( QString uri )
    : QgsVectorDataProvider( uri )
    , mDelimiter( "," )
    , mDelimiterType( "plain" )
    , mFieldCount( 0 )
    , mXFieldIndex( -1 )
    , mYFieldIndex( -1 )
    , mWktFieldIndex( -1 )
    , mWktHasZM( false )
    , mWktZMRegexp( "\\s+(?:z|m|zm)(?=\\s*\\()", Qt::CaseInsensitive )
    , mWktCrdRegexp( "(\\-?\\d+(?:\\.\\d*)?\\s+\\-?\\d+(?:\\.\\d*)?)\\s[\\s\\d\\.\\-]+" )
    , mFile( 0 )
    , mStream( 0 )
    , mSkipLines( 0 )
    , mFirstDataLine( 0 )
    , mShowInvalidLines( false )
    , mCrs()
    , mWkbType( QGis::WKBUnknown )
{
  QUrl url = QUrl::fromEncoded( uri.toAscii() );

  // Extract the provider definition from the url
  mFileName = url.toLocalFile();

  QString wktField;
  QString xField;
  QString yField;

  if ( url.hasQueryItem( "delimiter" ) )
    mDelimiter = url.queryItemValue( "delimiter" );
  if ( url.hasQueryItem( "delimiterType" ) )
    mDelimiterType = url.queryItemValue( "delimiterType" );
  if ( url.hasQueryItem( "wktField" ) )
    wktField = url.queryItemValue( "wktField" );
  if ( url.hasQueryItem( "xField" ) )
    xField = url.queryItemValue( "xField" );
  if ( url.hasQueryItem( "yField" ) )
    yField = url.queryItemValue( "yField" );
  if ( url.hasQueryItem( "skipLines" ) )
    mSkipLines = url.queryItemValue( "skipLines" ).toInt();
  if ( url.hasQueryItem( "crs" ) )
    mCrs.createFromString( url.queryItemValue( "crs" ) );
  if ( url.hasQueryItem( "decimalPoint" ) )
    mDecimalPoint = url.queryItemValue( "decimalPoint" );

  QgsDebugMsg( "Data source uri is " + uri );
  QgsDebugMsg( "Delimited text file is: " + mFileName );
  QgsDebugMsg( "Delimiter is: " + mDelimiter );
  QgsDebugMsg( "Delimiter type is: " + mDelimiterType );
  QgsDebugMsg( "wktField is: " + wktField );
  QgsDebugMsg( "xField is: " + xField );
  QgsDebugMsg( "yField is: " + yField );
  QgsDebugMsg( "skipLines is: " + QString::number( mSkipLines ) );

  // if delimiter contains some special characters, convert them
  if ( mDelimiterType != "regexp" )
    mDelimiter.replace( "\\t", "\t" ); // replace "\t" with a real tabulator

  // Set the selection rectangle to null
  mSelectionRectangle = QgsRectangle();
  // assume the layer is invalid until proven otherwise
  mValid = false;
  if ( mFileName.isEmpty() || mDelimiter.isEmpty() )
  {
    // uri is invalid so the layer must be too...
    QgsDebugMsg( "Data source is invalid" );
    return;
  }

  // check to see that the file exists and perform some sanity checks
  if ( !QFile::exists( mFileName ) )
  {
    QgsDebugMsg( "Data source " + dataSourceUri() + " doesn't exist" );
    return;
  }

  // Open the file and get number of rows, etc. We assume that the
  // file has a header row and process accordingly. Caller should make
  // sure that the delimited file is properly formed.
  mFile = new QFile( mFileName );
  if ( !mFile->open( QIODevice::ReadOnly ) )
  {
    QgsDebugMsg( "Data source " + dataSourceUri() + " could not be opened" );
    delete mFile;
    mFile = 0;
    return;
  }

  // now we have the file opened and ready for parsing

  // set the initial extent
  mExtent = QgsRectangle();

  QMap<int, bool> couldBeInt;
  QMap<int, bool> couldBeDouble;

  mStream = new QTextStream( mFile );
  QString line;
  mNumberFeatures = 0;
  int lineNumber = 0;
  bool hasFields = false;
  while ( !mStream->atEnd() )
  {
    lineNumber++;
    line = readLine( mStream ); // line of text excluding '\n', default local 8 bit encoding.

    if ( lineNumber < mSkipLines + 1 )
      continue;

    if ( line.isEmpty() )
      continue;

    if ( !hasFields )
    {
      // Get the fields from the header row and store them in the
      // fields vector
      QStringList fieldList = splitLine( line );

      mFieldCount = fieldList.count();

      // We don't know anything about a text based field other
      // than its name. All fields are assumed to be text
      int fieldPos = 0;
      for ( int column = 0; column < mFieldCount; column++ )
      {
        QString field = fieldList[column];

        if (( field.left( 1 ) == "'" || field.left( 1 ) == "\"" ) &&
            field.left( 1 ) == field.right( 1 ) )
          // eat quotes
          field = field.mid( 1, field.length() - 2 );

        if ( field.length() == 0 )
          // skip empty field names
          continue;

        // check to see if this field matches either the x or y field
        if ( !wktField.isEmpty() && wktField == field )
        {
          QgsDebugMsg( "Found wkt field: " + ( field ) );
          mWktFieldIndex = column;
        }
        else if ( !xField.isEmpty() && xField == field )
        {
          QgsDebugMsg( "Found x field: " + ( field ) );
          mXFieldIndex = column;
        }
        else if ( !yField.isEmpty() && yField == field )
        {
          QgsDebugMsg( "Found y field: " + ( field ) );
          mYFieldIndex = column;
        }

        // WKT geometry field won't be displayed in attribute tables
        if ( column == mWktFieldIndex )
          continue;

        QgsDebugMsg( "Adding field: " + ( field ) );
        // assume that the field could be integer or double
        // for now, let's set field type as text
        attributeColumns.append( column );
        attributeFields[fieldPos] = QgsField( field, QVariant::String, "Text" );
        couldBeInt.insert( fieldPos, true );
        couldBeDouble.insert( fieldPos, true );
        fieldPos++;
      }
      if ( mWktFieldIndex >= 0 )
      {
        mXFieldIndex = -1;
        mYFieldIndex = -1;
      }
      QgsDebugMsg( "wktfield index: " + QString::number( mWktFieldIndex ) );
      QgsDebugMsg( "xfield index: " + QString::number( mXFieldIndex ) );
      QgsDebugMsg( "yfield index: " + QString::number( mYFieldIndex ) );
      QgsDebugMsg( "Field count for the delimited text file is " + QString::number( attributeFields.size() ) );
      hasFields = true;
    }
    else // hasFields == true - field names already read
    {
      if ( mFirstDataLine == 0 )
        mFirstDataLine = lineNumber;

      // split the line on the delimiter
      QStringList parts = splitLine( line );

      // Ensure that the input has at least the required number of fields (mainly to tolerate
      // missed blank strings at end of row)
      while ( parts.size() < mFieldCount )
        parts.append( QString::null );

      if ( mWktFieldIndex >= 0 )
      {
        // Get the wkt - confirm it is valid, get the type, and
        // if compatible with the rest of file, add to the extents

        QString sWkt = parts[mWktFieldIndex];
        QgsGeometry *geom = 0;
        try
        {
          if ( !mWktHasZM && sWkt.indexOf( mWktZMRegexp ) >= 0 )
            mWktHasZM = true;
          if ( mWktHasZM )
          {
            sWkt.remove( mWktZMRegexp ).replace( mWktCrdRegexp, "\\1" );
          }
          geom = QgsGeometry::fromWkt( sWkt );
        }
        catch ( ... )
        {
          mInvalidLines << line;
          geom = 0;
        }

        if ( geom )
        {
          QGis::WkbType type = geom->wkbType();
          if ( type != QGis::WKBNoGeometry )
          {
            if ( mNumberFeatures == 0 )
            {
              mNumberFeatures++;
              mWkbType = type;
              mExtent = geom->boundingBox();
            }
            else if ( type == mWkbType )
            {
              mNumberFeatures++;
              QgsRectangle bbox( geom->boundingBox() );
              mExtent.combineExtentWith( &bbox );
            }
          }
          delete geom;
        }
      }
      else if ( mWktFieldIndex == -1 && mXFieldIndex >= 0 && mYFieldIndex >= 0 )
      {
        // Get the x and y values, first checking to make sure they
        // aren't null.

        QString sX = parts[mXFieldIndex];
        QString sY = parts[mYFieldIndex];


        if ( !mDecimalPoint.isEmpty() )
        {
          sX.replace( mDecimalPoint, "." );
          sY.replace( mDecimalPoint, "." );
        }

        bool xOk = false;
        bool yOk = false;
        double x = sX.toDouble( &xOk );
        double y = sY.toDouble( &yOk );

        if ( xOk && yOk )
        {
          if ( mNumberFeatures > 0 )
          {
            mExtent.combineExtentWith( x, y );
          }
          else
          {
            // Extent for the first point is just the first point
            mExtent.set( x, y, x, y );
            mWkbType = QGis::WKBPoint;
          }
          mNumberFeatures++;
        }
        else
        {
          mInvalidLines << line;
        }
      }
      else
      {
        mWkbType = QGis::WKBNoGeometry;
        mNumberFeatures++;
      }

      for ( int i = 0; i < attributeFields.size(); i++ )
      {
        QString &value = parts[attributeColumns[i]];
        if ( value.isEmpty() )
          continue;
        // try to convert attribute values to integer and double
        if ( couldBeInt[i] )
        {
          value.toInt( &couldBeInt[i] );
        }
        if ( couldBeDouble[i] )
        {
          value.toDouble( &couldBeDouble[i] );
        }
      }
    }
  }

  QgsDebugMsg( "geometry type is: " + QString::number( mWkbType ) );
  QgsDebugMsg( "feature count is: " + QString::number( mNumberFeatures ) );

  // now it's time to decide the types for the fields
  for ( QgsFieldMap::iterator it = attributeFields.begin(); it != attributeFields.end(); ++it )
  {
    if ( couldBeInt[it.key()] )
    {
      it->setType( QVariant::Int );
      it->setTypeName( "integer" );
    }
    else if ( couldBeDouble[it.key()] )
    {
      it->setType( QVariant::Double );
      it->setTypeName( "double" );
    }
  }

  mValid = mWkbType != QGis::WKBUnknown;
}
예제 #9
0
QgsRasterIdentifyResult QgsAmsProvider::identify( const QgsPointXY &point, QgsRaster::IdentifyFormat format, const QgsRectangle &extent, int width, int height, int dpi )
{
  // http://resources.arcgis.com/en/help/rest/apiref/identify.html
  QgsDataSourceUri dataSource( dataSourceUri() );
  QUrl queryUrl( dataSource.param( QStringLiteral( "url" ) ) + "/identify" );
  queryUrl.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "json" ) );
  queryUrl.addQueryItem( QStringLiteral( "geometryType" ), QStringLiteral( "esriGeometryPoint" ) );
  queryUrl.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "{x: %1, y: %2}" ).arg( point.x(), 0, 'f' ).arg( point.y(), 0, 'f' ) );
//  queryUrl.addQueryItem( "sr", mCrs.postgisSrid() );
  queryUrl.addQueryItem( QStringLiteral( "layers" ), QStringLiteral( "all:%1" ).arg( dataSource.param( QStringLiteral( "layer" ) ) ) );
  queryUrl.addQueryItem( QStringLiteral( "imageDisplay" ), QStringLiteral( "%1,%2,%3" ).arg( width ).arg( height ).arg( dpi ) );
  queryUrl.addQueryItem( QStringLiteral( "mapExtent" ), QStringLiteral( "%1,%2,%3,%4" ).arg( extent.xMinimum(), 0, 'f' ).arg( extent.yMinimum(), 0, 'f' ).arg( extent.xMaximum(), 0, 'f' ).arg( extent.yMaximum(), 0, 'f' ) );
  queryUrl.addQueryItem( QStringLiteral( "tolerance" ), QStringLiteral( "10" ) );

  const QString authcfg = dataSource.param( QStringLiteral( "authcfg" ) );
  const QVariantList queryResults = QgsArcGisRestUtils::queryServiceJSON( queryUrl, authcfg, mErrorTitle, mError ).value( QStringLiteral( "results" ) ).toList();

  QMap<int, QVariant> entries;

  if ( format == QgsRaster::IdentifyFormatText )
  {
    for ( const QVariant &result : queryResults )
    {
      const QVariantMap resultMap = result.toMap();
      QVariantMap attributesMap = resultMap[QStringLiteral( "attributes" )].toMap();
      QString valueStr;
      for ( auto it = attributesMap.constBegin(); it != attributesMap.constEnd(); ++it )
      {
        valueStr += QStringLiteral( "%1 = %2\n" ).arg( it.key(), it.value().toString() );
      }
      entries.insert( entries.size(), valueStr );
    }
  }
  else if ( format == QgsRaster::IdentifyFormatFeature )
  {
    for ( const QVariant &result : queryResults )
    {
      const QVariantMap resultMap = result.toMap();

      QgsFields fields;
      const QVariantMap attributesMap = resultMap[QStringLiteral( "attributes" )].toMap();
      QgsAttributes featureAttributes;
      for ( auto it = attributesMap.constBegin(); it != attributesMap.constEnd(); ++it )
      {
        fields.append( QgsField( it.key(), QVariant::String, QStringLiteral( "string" ) ) );
        featureAttributes.append( it.value().toString() );
      }
      QgsCoordinateReferenceSystem crs;
      std::unique_ptr< QgsAbstractGeometry > geometry = QgsArcGisRestUtils::parseEsriGeoJSON( resultMap[QStringLiteral( "geometry" )].toMap(), resultMap[QStringLiteral( "geometryType" )].toString(), false, false, &crs );
      QgsFeature feature( fields );
      feature.setGeometry( QgsGeometry( std::move( geometry ) ) );
      feature.setAttributes( featureAttributes );
      feature.setValid( true );
      QgsFeatureStore store( fields, crs );
      QMap<QString, QVariant> params;
      params[QStringLiteral( "sublayer" )] = resultMap[QStringLiteral( "layerName" )].toString();
      params[QStringLiteral( "featureType" )] = attributesMap[resultMap[QStringLiteral( "displayFieldName" )].toString()].toString();
      store.setParams( params );
      store.addFeature( feature );
      entries.insert( entries.size(), qVariantFromValue( QList<QgsFeatureStore>() << store ) );
    }
  }
  return QgsRasterIdentifyResult( format, entries );
}
예제 #10
0
void QgsAmsProvider::draw( const QgsRectangle &viewExtent, int pixelWidth, int pixelHeight )
{
  if ( !mCachedImage.isNull() && mCachedImageExtent == viewExtent )
  {
    return;
  }
  QgsDataSourceUri dataSource( dataSourceUri() );
  const QString authcfg = dataSource.param( QStringLiteral( "authcfg" ) );

  // Use of tiles currently only implemented if service CRS is meter based
  if ( mServiceInfo[QStringLiteral( "singleFusedMapCache" )].toBool() && mCrs.mapUnits() == QgsUnitTypes::DistanceMeters )
  {
    // Compute ideal resolution
    // - Measure distance in meters along lower and upper edge of bounding box
    // - Target resolution is the coarser resolution (resolution = distMeters / pixelWidth)
    double width = viewExtent.xMaximum() - viewExtent.xMinimum();
    double targetRes = width / ( pixelWidth );

    // Tiles available, assemble image from tiles
    QVariantMap tileInfo = mServiceInfo[QStringLiteral( "tileInfo" )].toMap();
    int tileWidth = tileInfo[QStringLiteral( "cols" )].toInt();
    int tileHeight = tileInfo[QStringLiteral( "rows" )].toInt();
    QVariantMap origin = tileInfo[QStringLiteral( "origin" )].toMap();
    double ox = origin[QStringLiteral( "x" )].toDouble();
    double oy = origin[QStringLiteral( "y" )].toDouble();

    // Search matching resolution (tile resolution <= targetRes)
    const QList<QVariant> lodEntries = tileInfo[QStringLiteral( "lods" )].toList();
    if ( lodEntries.isEmpty() )
    {
      mCachedImage = QImage();
      mCachedImage.fill( Qt::transparent );
      return;
    }
    int level = 0;
    double resolution = lodEntries.front().toMap()[QStringLiteral( "resolution" )].toDouble();
    for ( const QVariant &lodEntry : lodEntries )
    {
      QVariantMap lodEntryMap = lodEntry.toMap();
      level = lodEntryMap[QStringLiteral( "level" )].toInt();
      resolution = lodEntryMap[QStringLiteral( "resolution" )].toDouble();
      if ( lodEntryMap[QStringLiteral( "resolution" )].toDouble() <= 1.5 * targetRes )
      {
        break;
      }
    }

    // Get necessary tiles to fill extent
    // tile_x = ox + i * (resolution * tileWidth)
    // tile_y = oy - j * (resolution * tileHeight)
    int ixStart = std::floor( ( viewExtent.xMinimum() - ox ) / ( tileWidth * resolution ) );
    int iyStart = std::floor( ( oy - viewExtent.yMaximum() ) / ( tileHeight * resolution ) );
    int ixEnd = std::ceil( ( viewExtent.xMaximum() - ox ) / ( tileWidth * resolution ) );
    int iyEnd = std::ceil( ( oy - viewExtent.yMinimum() ) / ( tileHeight * resolution ) );
    double imX = ( viewExtent.xMinimum() - ox ) / resolution;
    double imY = ( oy - viewExtent.yMaximum() ) / resolution;

    // Query tiles
    int ixCount = ( ixEnd - ixStart + 1 );
    QVector<QByteArray> results( ixCount * ( iyEnd - iyStart + 1 ) );
    QVector<QUrl> queries( ixCount * ( iyEnd - iyStart + 1 ) );
    for ( int iy = iyStart; iy <= iyEnd; ++iy )
    {
      for ( int ix = ixStart; ix <= ixEnd; ++ix )
      {
        queries[( iy - iyStart ) * ixCount + ( ix - ixStart )] = QUrl( dataSource.param( QStringLiteral( "url" ) ) + QStringLiteral( "/tile/%1/%2/%3" ).arg( level ).arg( iy ).arg( ix ) );
      }
    }
    QgsArcGisAsyncParallelQuery query;
    QEventLoop evLoop;
    connect( &query, &QgsArcGisAsyncParallelQuery::finished, &evLoop, &QEventLoop::quit );
    query.start( queries, &results, true );
    evLoop.exec( QEventLoop::ExcludeUserInputEvents );

    // Fill image
    mCachedImage = QImage( pixelWidth, pixelHeight, QImage::Format_ARGB32 );
    mCachedImage.fill( Qt::transparent );
    QPainter painter( &mCachedImage );
    painter.setRenderHint( QPainter::SmoothPixmapTransform, true );
    double resScale = resolution / targetRes;
    painter.scale( resScale, resScale );
    for ( int iy = iyStart; iy <= iyEnd; ++iy )
    {
      for ( int ix = ixStart; ix <= ixEnd; ++ix )
      {
        QImage image = QImage::fromData( results[( iy - iyStart ) * ixCount + ( ix - ixStart )], tileInfo[QStringLiteral( "format" )].toByteArray() );
        painter.drawImage( QPointF( ix * tileWidth - imX, iy * tileHeight - imY ), image );
      }
    }
  }
  else
  {
    QUrl requestUrl( dataSource.param( QStringLiteral( "url" ) ) + "/export" );
    requestUrl.addQueryItem( QStringLiteral( "bbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( viewExtent.xMinimum(), 0, 'f', -1 ).arg( viewExtent.yMinimum(), 0, 'f', -1 ).arg( viewExtent.xMaximum(), 0, 'f', -1 ).arg( viewExtent.yMaximum(), 0, 'f', -1 ) );
    requestUrl.addQueryItem( QStringLiteral( "size" ), QStringLiteral( "%1,%2" ).arg( pixelWidth ).arg( pixelHeight ) );
    requestUrl.addQueryItem( QStringLiteral( "format" ), dataSource.param( QStringLiteral( "format" ) ) );
    requestUrl.addQueryItem( QStringLiteral( "layers" ), QStringLiteral( "show:%1" ).arg( dataSource.param( QStringLiteral( "layer" ) ) ) );
    requestUrl.addQueryItem( QStringLiteral( "transparent" ), QStringLiteral( "true" ) );
    requestUrl.addQueryItem( QStringLiteral( "f" ), QStringLiteral( "image" ) );
    QByteArray reply = QgsArcGisRestUtils::queryService( requestUrl, authcfg, mErrorTitle, mError );
    mCachedImage = QImage::fromData( reply, dataSource.param( QStringLiteral( "format" ) ).toLatin1() );
    if ( mCachedImage.format() != QImage::Format_ARGB32 )
    {
      mCachedImage = mCachedImage.convertToFormat( QImage::Format_ARGB32 );
    }
  }
}
QgsDelimitedTextProvider::QgsDelimitedTextProvider( QString uri )
    : QgsVectorDataProvider( uri ),
    mXFieldIndex( -1 ), mYFieldIndex( -1 ),
    mShowInvalidLines( true )
{
  // Get the file name and mDelimiter out of the uri
  mFileName = uri.left( uri.indexOf( "?" ) );
  // split the string up on & to get the individual parameters
  QStringList parameters = uri.mid( uri.indexOf( "?" ) ).split( "&", QString::SkipEmptyParts );

  QgsDebugMsg( "Parameter count after split on &" + QString::number( parameters.size() ) );

  // get the individual parameters and assign values
  QStringList temp = parameters.filter( "delimiter=" );
  mDelimiter = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : "";
  temp = parameters.filter( "delimiterType=" );
  mDelimiterType = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : "";
  temp = parameters.filter( "xField=" );
  QString xField = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : "";
  temp = parameters.filter( "yField=" );
  QString yField = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : "";
  // Decode the parts of the uri. Good if someone entered '=' as a delimiter, for instance.
  mFileName  = QUrl::fromPercentEncoding( mFileName.toUtf8() );
  mDelimiter = QUrl::fromPercentEncoding( mDelimiter.toUtf8() );
  mDelimiterType = QUrl::fromPercentEncoding( mDelimiterType.toUtf8() );
  xField    = QUrl::fromPercentEncoding( xField.toUtf8() );
  yField    = QUrl::fromPercentEncoding( yField.toUtf8() );

  QgsDebugMsg( "Data source uri is " + uri );
  QgsDebugMsg( "Delimited text file is: " + mFileName );
  QgsDebugMsg( "Delimiter is: " + mDelimiter );
  QgsDebugMsg( "Delimiter type is: " + mDelimiterType );
  QgsDebugMsg( "xField is: " + xField );
  QgsDebugMsg( "yField is: " + yField );

  // if delimiter contains some special characters, convert them
  if ( mDelimiterType == "regexp" )
    mDelimiterRegexp = QRegExp( mDelimiter );
  else
    mDelimiter.replace( "\\t", "\t" ); // replace "\t" with a real tabulator

  // Set the selection rectangle to null
  mSelectionRectangle = QgsRectangle();
  // assume the layer is invalid until proven otherwise
  mValid = false;
  if ( mFileName.isEmpty() || mDelimiter.isEmpty() || xField.isEmpty() || yField.isEmpty() )
  {
    // uri is invalid so the layer must be too...
    QString( "Data source is invalid" );
    return;
  }

  // check to see that the file exists and perform some sanity checks
  if ( !QFile::exists( mFileName ) )
  {
    QgsDebugMsg( "Data source " + dataSourceUri() + " doesn't exist" );
    return;
  }

  // Open the file and get number of rows, etc. We assume that the
  // file has a header row and process accordingly. Caller should make
  // sure the the delimited file is properly formed.
  mFile = new QFile( mFileName );
  if ( !mFile->open( QIODevice::ReadOnly ) )
  {
    QgsDebugMsg( "Data source " + dataSourceUri() + " could not be opened" );
    delete mFile;
    return;
  }

  // now we have the file opened and ready for parsing

  // set the initial extent
  mExtent = QgsRectangle();

  QMap<int, bool> couldBeInt;
  QMap<int, bool> couldBeDouble;

  mStream = new QTextStream( mFile );
  QString line;
  mNumberFeatures = 0;
  int lineNumber = 0;
  bool firstPoint = true;
  bool hasFields = false;
  while ( !mStream->atEnd() )
  {
    lineNumber++;
    line = mStream->readLine(); // line of text excluding '\n', default local 8 bit encoding.
    if ( !hasFields )
    {
      // Get the fields from the header row and store them in the
      // fields vector
      QgsDebugMsg( "Attempting to split the input line: " + line + " using delimiter " + mDelimiter );

      QStringList fieldList;
      if ( mDelimiterType == "regexp" )
        fieldList = line.split( mDelimiterRegexp );
      else
        fieldList = line.split( mDelimiter );
      QgsDebugMsg( "Split line into " + QString::number( fieldList.size() ) + " parts" );

      // We don't know anything about a text based field other
      // than its name. All fields are assumed to be text
      int fieldPos = 0;
      for ( QStringList::Iterator it = fieldList.begin();
            it != fieldList.end(); ++it )
      {
        QString field = *it;
        if ( field.length() > 0 )
        {
          // for now, let's set field type as text
          attributeFields[fieldPos] = QgsField( *it, QVariant::String, "Text" );

          // check to see if this field matches either the x or y field
          if ( xField == *it )
          {
            QgsDebugMsg( "Found x field: " + ( *it ) );
            mXFieldIndex = fieldPos;
          }
          else if ( yField == *it )
          {
            QgsDebugMsg( "Found y field: " + ( *it ) );
            mYFieldIndex = fieldPos;
          }

          QgsDebugMsg( "Adding field: " + ( *it ) );
          // assume that the field could be integer or double
          couldBeInt.insert( fieldPos, true );
          couldBeDouble.insert( fieldPos, true );
          fieldPos++;
        }
      }
      QgsDebugMsg( "Field count for the delimited text file is " + QString::number( attributeFields.size() ) );
      hasFields = true;
    }
    else if ( mXFieldIndex != -1 && mYFieldIndex != -1 )
    {
      mNumberFeatures++;

      // split the line on the delimiter
      QStringList parts;
      if ( mDelimiterType == "regexp" )
        parts = line.split( mDelimiterRegexp );
      else
        parts = line.split( mDelimiter );

      // Skip malformed lines silently. Report line number with nextFeature()
      if ( attributeFields.size() != parts.size() )
      {
        continue;
      }

      // Get the x and y values, first checking to make sure they
      // aren't null.
      QString sX = parts[mXFieldIndex];
      QString sY = parts[mYFieldIndex];

      bool xOk = true;
      bool yOk = true;
      double x = sX.toDouble( &xOk );
      double y = sY.toDouble( &yOk );

      if ( xOk && yOk )
      {
        if ( !firstPoint )
        {
          mExtent.combineExtentWith( x, y );
        }
        else
        { // Extent for the first point is just the first point
          mExtent.set( x, y, x, y );
          firstPoint = false;
        }
      }

      int i = 0;
      for ( QStringList::iterator it = parts.begin(); it != parts.end(); ++it, ++i )
      {
        // try to convert attribute values to integer and double
        if ( couldBeInt[i] )
        {
          it->toInt( &couldBeInt[i] );
        }
        if ( couldBeDouble[i] )
        {
          it->toDouble( &couldBeDouble[i] );
        }
      }
    }
  }

  // now it's time to decide the types for the fields
  for ( QgsFieldMap::iterator it = attributeFields.begin(); it != attributeFields.end(); ++it )
  {
    if ( couldBeInt[it.key()] )
    {
      it->setType( QVariant::Int );
      it->setTypeName( "integer" );
    }
    else if ( couldBeDouble[it.key()] )
    {
      it->setType( QVariant::Double );
      it->setTypeName( "double" );
    }
  }

  if ( mXFieldIndex != -1 && mYFieldIndex != -1 )
  {
    QgsDebugMsg( "Data store is valid" );
    QgsDebugMsg( "Number of features " + QString::number( mNumberFeatures ) );
    QgsDebugMsg( "Extents " + mExtent.toString() );

    mValid = true;
  }
  else
  {
    QgsDebugMsg( "Data store is invalid. Specified x,y fields do not match those in the database" );
  }
  QgsDebugMsg( "Done checking validity" );

}