コード例 #1
0
/** Destroys a pool of worker threads.
 *
 * Any still pending job will be completed before the destructor returns.
 */
CPLWorkerThreadPool::~CPLWorkerThreadPool()
{
    if( hCond )
    {
        WaitCompletion();

        CPLAcquireMutex(hMutex, 1000.0);
        eState = CPLWTS_STOP;
        CPLReleaseMutex(hMutex);

        for(size_t i=0;i<aWT.size();i++)
        {
            CPLAcquireMutex(aWT[i].hMutex, 1000.0);
            CPLCondSignal(aWT[i].hCond);
            CPLReleaseMutex(aWT[i].hMutex);
            CPLJoinThread(aWT[i].hThread);
            CPLDestroyCond(aWT[i].hCond);
            CPLDestroyMutex(aWT[i].hMutex);
        }

        CPLListDestroy(psWaitingWorkerThreadsList);

        CPLDestroyCond(hCond);
    }
    CPLDestroyMutex(hMutex);
}
コード例 #2
0
int main(int argc, char* argv[])
{
    CPLJoinableThread* hThread;

    printf("main thread %p\n", (void*)CPLGetPID());

    argc = GDALGeneralCmdLineProcessor( argc, &argv, 0 );

    CPLSetConfigOption("GDAL_CACHEMAX", "0");
    CPLSetConfigOption("GDAL_DEBUG_BLOCK_CACHE", "ON");

    MyDataset* poDS = new MyDataset();

    char buf1[] = { 1 } ;
    CPL_IGNORE_RET_VAL(GDALRasterIO(GDALGetRasterBand(poDS, 1), GF_Write, 0, 0, 1, 1, buf1, 1, 1, GDT_Byte, 0, 0));

    hThread = CPLCreateJoinableThread(thread_func, NULL);
    CPLSleep(0.3);
    CPL_IGNORE_RET_VAL(GDALRasterIO(GDALGetRasterBand(poDS, 1), GF_Write, 1, 0, 1, 1, buf1, 1, 1, GDT_Byte, 0, 0));
    GDALFlushCacheBlock();

    CPLJoinThread(hThread);

    delete poDS;
    GDALDestroyDriverManager();
    CSLDestroy( argv );

    return 0;
}
コード例 #3
0
void VSICurlStreamingHandle::StopDownload()
{
    if (hThread)
    {
        //if (ENABLE_DEBUG)
            CPLDebug("VSICURL", "Stop download for %s", pszURL);

        AcquireMutex();
        /* Signal to the producer that we ask for download interruption */
        bAskDownloadEnd = TRUE;
        CPLCondSignal(hCondConsumer);

        /* Wait for the producer to have finished */
        while(bDownloadInProgress)
            CPLCondWait(hCondProducer, hRingBufferMutex);

        bAskDownloadEnd = FALSE;

        ReleaseMutex();

        CPLJoinThread(hThread);
        hThread = NULL;

        curl_easy_cleanup(hCurlHandle);
        hCurlHandle = NULL;
    }

    oRingBuffer.Reset();
    bDownloadStopped = FALSE;
}
コード例 #4
0
ファイル: test_virtualmem.cpp プロジェクト: rashadkm/lib_gdal
static int test_two_pages()
{
    CPLVirtualMem* ctxt;
    volatile char* addr;
    void* hThread;

    ctxt = CPLVirtualMemNew(3*MINIMUM_PAGE_SIZE,
                            MINIMUM_PAGE_SIZE,
                            MINIMUM_PAGE_SIZE,
                            FALSE,
                            VIRTUALMEM_READONLY,
                            test_two_pages_cbk,
                            NULL,
                            NULL, NULL);
    if( ctxt == NULL )
        return FALSE;

    addr = (char*) CPLVirtualMemGetAddr(ctxt);
    assert(CPLVirtualMemGetPageSize(ctxt) == MINIMUM_PAGE_SIZE);
    assert(CPLVirtualMemIsAccessThreadSafe(ctxt));
    /*fprintfstderr("main thread is %X, addr=%p\n", pthread_self(), addr);*/
    hThread = CPLCreateJoinableThread(test_two_pages_thread, ctxt);
    CPLVirtualMemDeclareThread(ctxt);
    {
        int i=0;
        for(i=0; i<50*1000; i++)
        {
            char val = addr[MINIMUM_PAGE_SIZE * (i % 3)];
            /*fprintfstderr("T1: val[%d] = %d\n", MINIMUM_PAGE_SIZE * (i % 2), val);*/
            assert(val == (((i % 3) == 0) ? 0x3F : ((i % 3) == 1) ? 0x5F : 0x7F));
        }
    }
    CPLVirtualMemUnDeclareThread(ctxt);
    CPLJoinThread(hThread);
    CPLVirtualMemFree(ctxt);

    return TRUE;
}
コード例 #5
0
int main(int argc, char* argv[])
{
    int i;
    int nThreads = CPLGetNumCPUs();
    std::vector<CPLJoinableThread*> apsThreads;
    Strategy eStrategy = STRATEGY_RANDOM;
    int bNewDatasetOption = FALSE;
    int nXSize = 5000;
    int nYSize = 5000;
    int nBands = 4;
    char** papszOptions = NULL;
    int bOnDisk = FALSE;
    std::vector<ThreadDescription> asThreadDescription;
    int bMemDriver = FALSE;
    GDALDataset* poMEMDS = NULL;
    int bMigrate = FALSE;
    int nMaxRequests = -1;

    argc = GDALGeneralCmdLineProcessor( argc, &argv, 0 );

    GDALAllRegister();

    for(i = 1; i < argc; i++)
    {
        if( EQUAL(argv[i], "-threads") && i + 1 < argc)
        {
            i ++;
            nThreads = atoi(argv[i]);
        }
        else if( EQUAL(argv[i], "-loops") && i + 1 < argc)
        {
            i ++;
            nLoops = atoi(argv[i]);
            if( nLoops <= 0 )
                nLoops = INT_MAX;
        }
        else if( EQUAL(argv[i], "-max_requests") && i + 1 < argc)
        {
            i ++;
            nMaxRequests = atoi(argv[i]);
        }
        else if( EQUAL(argv[i], "-strategy") && i + 1 < argc)
        {
            i ++;
            if( EQUAL(argv[i], "random") )
                eStrategy = STRATEGY_RANDOM;
            else if( EQUAL(argv[i], "line") )
                eStrategy = STRATEGY_LINE;
            else if( EQUAL(argv[i], "block") )
                eStrategy = STRATEGY_BLOCK;
            else
                Usage();
        }
        else if( EQUAL(argv[i], "-xsize") && i + 1 < argc)
        {
            i ++;
            nXSize = atoi(argv[i]);
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-ysize") && i + 1 < argc)
        {
            i ++;
            nYSize = atoi(argv[i]);
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-bands") && i + 1 < argc)
        {
            i ++;
            nBands = atoi(argv[i]);
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-co") && i + 1 < argc)
        {
            i ++;
            papszOptions = CSLAddString(papszOptions, argv[i]);
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-ondisk"))
        {
            bOnDisk = TRUE;
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-check"))
        {
            bCheck = TRUE;
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-memdriver"))
        {
            bMemDriver = TRUE;
            bNewDatasetOption = TRUE;
        }
        else if( EQUAL(argv[i], "-migrate"))
            bMigrate = TRUE;
        else if( argv[i][0] == '-' )
            Usage();
        else if( pszDataset == NULL )
            pszDataset = argv[i];
        else
        {
            Usage();
        }
    }

    if( pszDataset != NULL && bNewDatasetOption )
        Usage();

    CPLDebug("TEST", "Using %d threads", nThreads);

    int bCreatedDataset = FALSE;
    if( pszDataset == NULL )
    {
        bCreatedDataset = TRUE;
        if( bOnDisk )
            pszDataset = "/tmp/tmp.tif";
        else
            pszDataset = "/vsimem/tmp.tif";
        GDALDataset* poDS = ((GDALDriver*)GDALGetDriverByName((bMemDriver) ? "MEM" : "GTiff"))->Create(pszDataset,
                                nXSize, nYSize, nBands, GDT_Byte, papszOptions);
        if( bCheck )
        {
            GByte* pabyLine = (GByte*) VSIMalloc(nBands * nXSize);
            for(int iY=0;iY<nYSize;iY++)
            {
                for(int iX=0;iX<nXSize;iX++)
                {
                    for(int iBand=0;iBand<nBands;iBand++)
                    {
                        unsigned long seed = iBand * nXSize * nYSize + iY * nXSize + iX;
                        pabyLine[iBand * nXSize + iX] = (GByte)(myrand_r(&seed) & 0xff);
                    }
                }
                CPL_IGNORE_RET_VAL(poDS->RasterIO(GF_Write, 0, iY, nXSize, 1,
                               pabyLine, nXSize, 1,
                               GDT_Byte,
                               nBands, NULL,
                               0, 0, 0
#ifdef GDAL_COMPILATION
                               , NULL
#endif
                               ));
            }
            VSIFree(pabyLine);
        }
        if( bMemDriver ) 
            poMEMDS = poDS;
        else
            GDALClose(poDS);
    }
    else
    {
        bCheck = FALSE;
    }
    CSLDestroy(papszOptions);
    papszOptions = NULL;
    
    Request* psGlobalRequestLast = NULL;

    for(i = 0; i < nThreads; i++ )
    {
        GDALDataset* poDS;
        // Since GDAL 2.0, the MEM driver is thread-safe, i.e. does not use the block
        // cache, but only for operations not involving resampling, which is
        // the case here
        if( poMEMDS ) 
            poDS = poMEMDS;
        else
        {
            poDS = (GDALDataset*)GDALOpen(pszDataset, GA_ReadOnly);
            if( poDS == NULL )
                exit(1);
        }
        if( bMigrate )
        {
            Resource* psResource = (Resource*)CPLMalloc(sizeof(Resource));
            psResource->poDS = poDS;
            int nBufferSize;
            if( eStrategy == STRATEGY_RANDOM )
                nBufferSize = CreateRandomStrategyRequests(
                        poDS, nMaxRequests, psGlobalRequestList, psGlobalRequestLast);
            else if( eStrategy == STRATEGY_LINE )
                nBufferSize = CreateLineStrategyRequests(
                        poDS, nMaxRequests, psGlobalRequestList, psGlobalRequestLast);
            else
                nBufferSize = CreateBlockStrategyRequests(
                        poDS, nMaxRequests, psGlobalRequestList, psGlobalRequestLast);
            psResource->pBuffer = CPLMalloc(nBufferSize);
            PutResourceAtEnd(psResource);
        }
        else
        {
            ThreadDescription sThreadDescription;
            sThreadDescription.poDS = poDS;
            sThreadDescription.psRequestList = NULL;
            Request* psRequestLast = NULL;
            if( eStrategy == STRATEGY_RANDOM )
                sThreadDescription.nBufferSize = CreateRandomStrategyRequests(
                        poDS, nMaxRequests, sThreadDescription.psRequestList, psRequestLast);
            else if( eStrategy == STRATEGY_LINE )
                sThreadDescription.nBufferSize = CreateLineStrategyRequests(
                        poDS, nMaxRequests, sThreadDescription.psRequestList, psRequestLast);
            else
                sThreadDescription.nBufferSize = CreateBlockStrategyRequests(
                        poDS, nMaxRequests, sThreadDescription.psRequestList, psRequestLast);
            asThreadDescription.push_back(sThreadDescription);
        }
    }

    if( bCreatedDataset && poMEMDS == NULL && bOnDisk )
    {
        CPLPushErrorHandler(CPLQuietErrorHandler);
        VSIUnlink(pszDataset);
        CPLPopErrorHandler();
    }
    
    if( bMigrate )
    {
        psLock = CPLCreateLock(LOCK_SPIN);
    }

    for(i = 0; i < nThreads; i++ )
    {
        CPLJoinableThread* pThread;
        if( bMigrate )
            pThread = CPLCreateJoinableThread(ThreadFuncWithMigration, NULL);
        else
            pThread = CPLCreateJoinableThread(ThreadFuncDedicatedDataset,
                                              &(asThreadDescription[i]));
        apsThreads.push_back(pThread);
    }
    for(i = 0; i < nThreads; i++ )
    {
        CPLJoinThread(apsThreads[i]);
        if( !bMigrate && poMEMDS == NULL )
            GDALClose(asThreadDescription[i].poDS);
    }
    while( psGlobalResourceList != NULL )
    {
        CPLFree( psGlobalResourceList->pBuffer);
        if( poMEMDS == NULL )
            GDALClose(psGlobalResourceList->poDS);
        Resource* psNext = psGlobalResourceList->psNext;
        CPLFree( psGlobalResourceList );
        psGlobalResourceList = psNext;
    }

    if( psLock )
    {
        CPLDestroyLock( psLock );
    }

    if( bCreatedDataset && poMEMDS == NULL  )
    {
        CPLPushErrorHandler(CPLQuietErrorHandler);
        VSIUnlink(pszDataset);
        CPLPopErrorHandler();
    }
    if( poMEMDS )
        GDALClose(poMEMDS);

    assert( GDALGetCacheUsed64() == 0 );

    GDALDestroyDriverManager();
    CSLDestroy( argv );

    return 0;
}
コード例 #6
0
void *GDALCreateTPSTransformerInt( int nGCPCount, const GDAL_GCP *pasGCPList,
                                   int bReversed, char** papszOptions )

{
    TPSTransformInfo *psInfo;
    int    iGCP;

    /* -------------------------------------------------------------------- */
    /*      Allocate transform info.                                        */
    /* -------------------------------------------------------------------- */
    psInfo = (TPSTransformInfo *) CPLCalloc(sizeof(TPSTransformInfo),1);

    psInfo->pasGCPList = GDALDuplicateGCPs( nGCPCount, pasGCPList );
    psInfo->nGCPCount = nGCPCount;

    psInfo->bReversed = bReversed;
    psInfo->poForward = new VizGeorefSpline2D( 2 );
    psInfo->poReverse = new VizGeorefSpline2D( 2 );

    strcpy( psInfo->sTI.szSignature, "GTI" );
    psInfo->sTI.pszClassName = "GDALTPSTransformer";
    psInfo->sTI.pfnTransform = GDALTPSTransform;
    psInfo->sTI.pfnCleanup = GDALDestroyTPSTransformer;
    psInfo->sTI.pfnSerialize = GDALSerializeTPSTransformer;

    /* -------------------------------------------------------------------- */
    /*      Attach all the points to the transformation.                    */
    /* -------------------------------------------------------------------- */
    for( iGCP = 0; iGCP < nGCPCount; iGCP++ )
    {
        double    afPL[2], afXY[2];

        afPL[0] = pasGCPList[iGCP].dfGCPPixel;
        afPL[1] = pasGCPList[iGCP].dfGCPLine;
        afXY[0] = pasGCPList[iGCP].dfGCPX;
        afXY[1] = pasGCPList[iGCP].dfGCPY;

        if( bReversed )
        {
            psInfo->poReverse->add_point( afPL[0], afPL[1], afXY );
            psInfo->poForward->add_point( afXY[0], afXY[1], afPL );
        }
        else
        {
            psInfo->poForward->add_point( afPL[0], afPL[1], afXY );
            psInfo->poReverse->add_point( afXY[0], afXY[1], afPL );
        }
    }

    psInfo->nRefCount = 1;

    int nThreads = 1;
    if( nGCPCount > 100 )
    {
        const char* pszWarpThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
        if (pszWarpThreads == NULL)
            pszWarpThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "1");
        if (EQUAL(pszWarpThreads, "ALL_CPUS"))
            nThreads = CPLGetNumCPUs();
        else
            nThreads = atoi(pszWarpThreads);
    }

    if( nThreads > 1 )
    {
        /* Compute direct and reverse transforms in parallel */
        void* hThread = CPLCreateJoinableThread(GDALTPSComputeForwardInThread, psInfo);
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
        if( hThread != NULL )
            CPLJoinThread(hThread);
        else
            psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
    }
    else
    {
        psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
    }

    if( !psInfo->bForwardSolved || !psInfo->bReverseSolved )
    {
        GDALDestroyTPSTransformer(psInfo);
        return NULL;
    }

    return psInfo;
}
コード例 #7
0
ファイル: gdal_tps.cpp プロジェクト: StephenHolzman/UVAmisc
void *GDALCreateTPSTransformerInt( int nGCPCount, const GDAL_GCP *pasGCPList,
                                   int bReversed, char** papszOptions )

{
    TPSTransformInfo *psInfo;
    int    iGCP;

/* -------------------------------------------------------------------- */
/*      Allocate transform info.                                        */
/* -------------------------------------------------------------------- */
    psInfo = (TPSTransformInfo *) CPLCalloc(sizeof(TPSTransformInfo),1);

    psInfo->pasGCPList = GDALDuplicateGCPs( nGCPCount, pasGCPList );
    psInfo->nGCPCount = nGCPCount;

    psInfo->bReversed = bReversed;
    psInfo->poForward = new VizGeorefSpline2D( 2 );
    psInfo->poReverse = new VizGeorefSpline2D( 2 );

    memcpy( psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE, strlen(GDAL_GTI2_SIGNATURE) );
    psInfo->sTI.pszClassName = "GDALTPSTransformer";
    psInfo->sTI.pfnTransform = GDALTPSTransform;
    psInfo->sTI.pfnCleanup = GDALDestroyTPSTransformer;
    psInfo->sTI.pfnSerialize = GDALSerializeTPSTransformer;
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarTPSTransformer;

/* -------------------------------------------------------------------- */
/*      Attach all the points to the transformation.                    */
/* -------------------------------------------------------------------- */
    std::map< std::pair<double, double>, int > oMapPixelLineToIdx;
    std::map< std::pair<double, double>, int > oMapXYToIdx;
    for( iGCP = 0; iGCP < nGCPCount; iGCP++ )
    {
        double    afPL[2], afXY[2];

        afPL[0] = pasGCPList[iGCP].dfGCPPixel;
        afPL[1] = pasGCPList[iGCP].dfGCPLine;
        afXY[0] = pasGCPList[iGCP].dfGCPX;
        afXY[1] = pasGCPList[iGCP].dfGCPY;

        std::map< std::pair<double, double>, int >::iterator oIter;
        oIter = oMapPixelLineToIdx.find( std::pair<double,double>(afPL[0], afPL[1]) );
        if( oIter != oMapPixelLineToIdx.end() )
        {
            if( afXY[0] == pasGCPList[oIter->second].dfGCPX &&
                afXY[1] == pasGCPList[oIter->second].dfGCPY )
            {
                continue;
            }
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "GCP %d and %d have same (pixel,line)=(%f,%f) but different (X,Y): (%f,%f) vs (%f,%f)",
                         iGCP + 1, oIter->second,
                         afPL[0], afPL[1],
                         afXY[0], afXY[1],
                         pasGCPList[oIter->second].dfGCPX, pasGCPList[oIter->second].dfGCPY);
            }
        }
        else
        {
            oMapPixelLineToIdx[ std::pair<double,double>(afPL[0], afPL[1]) ] = iGCP;
        }

        oIter = oMapXYToIdx.find( std::pair<double,double>(afXY[0], afXY[1]) );
        if( oIter != oMapXYToIdx.end() )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                        "GCP %d and %d have same (x,y)=(%f,%f) but different (pixel,line): (%f,%f) vs (%f,%f)",
                        iGCP + 1, oIter->second,
                        afXY[0], afXY[1],
                        afPL[0], afPL[1],
                        pasGCPList[oIter->second].dfGCPPixel, pasGCPList[oIter->second].dfGCPLine);
        }
        else
        {
            oMapXYToIdx[ std::pair<double,double>(afXY[0], afXY[1]) ] = iGCP;
        }

        bool bOK = true;
        if( bReversed )
        {
            bOK &= psInfo->poReverse->add_point( afPL[0], afPL[1], afXY );
            bOK &= psInfo->poForward->add_point( afXY[0], afXY[1], afPL );
        }
        else
        {
            bOK &= psInfo->poForward->add_point( afPL[0], afPL[1], afXY );
            bOK &= psInfo->poReverse->add_point( afXY[0], afXY[1], afPL );
        }
        if( !bOK )
        {
            GDALDestroyTPSTransformer(psInfo);
            return NULL;
        }
    }

    psInfo->nRefCount = 1;

    int nThreads = 1;
    if( nGCPCount > 100 )
    {
        const char* pszWarpThreads = CSLFetchNameValue(papszOptions, "NUM_THREADS");
        if (pszWarpThreads == NULL)
            pszWarpThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "1");
        if (EQUAL(pszWarpThreads, "ALL_CPUS"))
            nThreads = CPLGetNumCPUs();
        else
            nThreads = atoi(pszWarpThreads);
    }

    if( nThreads > 1 )
    {
        /* Compute direct and reverse transforms in parallel */
        CPLJoinableThread* hThread = CPLCreateJoinableThread(GDALTPSComputeForwardInThread, psInfo);
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
        if( hThread != NULL )
            CPLJoinThread(hThread);
        else
            psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
    }
    else
    {
        psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
    }

    if( !psInfo->bForwardSolved || !psInfo->bReverseSolved )
    {
        GDALDestroyTPSTransformer(psInfo);
        return NULL;
    }

    return psInfo;
}
コード例 #8
0
ファイル: nomads.c プロジェクト: psuliuxf/windninja
/*
** Fetch a nomads forecast from the NWS servers.  The forecasts consist of grib
** files, one forecast for each forecast hour.  The time starts and the nearest
** forecast hour *before* the reference time passed.  If no reference time is
** passed, or it is not valid, now() is used.
**
** The forecasts are downloaded into a temporary directory.  If the entire
** download succeeds, then the files are copied into the path specified.  If
** the path specified is a zip file (ends in *.zip), then the forecast files
** are zipped.
**
** Available compile time configuration options:
**        NOMADS_USE_IP: Use the ip address instead of the hostname.  It
**                       possibly goes around dns lookup, but it's doubtfull.
**        NOMADS_ENABLE_ASYNC: Allow for asynchronous connections to the
**                             server.
**        NOMADS_EXPER_FORECASTS: Compile in forecasts that may not work with
**                                the current configuration, essentially
**                                unsupported (ie NARRE, RTMA).
**        NOMADS_USE_VSI_READ: Use the VSI*L api for downloading.  This will
**                             allow definition of chunk sizes for download,
**                             although it is probably unnecessary.  See
**                             NOMADS_VSI_BLOCK_SIZE below.  If not enabled, a
**                             single fetch is made for each file, which is
**                             faster.
** Available runtime configuration options:
**        NOMADS_THREAD_COUNT: Number of threads to use for downloads if
**                             NOMADS_ENABLE_ASYNC is set to ON during
**                             compilation.  Default is 4.
**        NOMADS_VSI_BLOCK_SIZE: Number of bytes to request at a time when
**                               downloading files if NOMADS_USE_VSI_READ is
**                               set to ON during compilation. Default is 512.
**        NOMADS_MAX_FCST_REWIND: Number of forecast run time steps to go back
**                                to attempt to get a full time frame.
**        GDAL_HTTP_TIMEOUT: Timeout for HTTP requests in seconds.  We should
**                           be able to set this reasonably low.
**
** \param pszModelKey The name key of the model to use, ie "nam_conus".  For a
**                    listing of models, see nomads.ncep.gov or /see nomads.h
**
** \param pszRefTime The reference time to begin searching for forecasts from.
**                   The search starts at refrence time, then goes back until
**                   it hits a valid forecast time.  If the download cannot be
**                   complete, it will step back NOMADS_MAX_FCST_REWIND
**                   foreacst times to attempt to get a full forecast.
**
** \param nHours The extent of hours to download forecasts from the reference
**               time.  If we step back, we will still grab all forecasts up
**               until nHours from the reference time, not the forecast time.
**
** \param nStride The number of forecasts to skip in time steps.  For example,
**                if 12 hours of gfs is requested (normally 5 files/steps (0,
**                3, 6, 9, 12) with a stride of 2, you'd get 0, 6, 12 forecast
**                hours.
**
** \param padfBbox The bounding box of the request in WGS84 decimal degrees.
**                 Order is xmin, xmax, ymax, ymin.
**
** \param pszDstVsiPath The location to write the files.  If the location
**                      has an extension of ".zip", then the output files are
**                      written as a zip archive, otherwise to a path.
**
** \param papszOptions List of key=value options, unused.
**
** \param pfnProgress Optional progress function.
**
** \return NOMADS_OK(0) on success, NOMADS_ERR(1) otherwise.
*/
int NomadsFetch( const char *pszModelKey, const char *pszRefTime,
                 int nHours, int nStride, double *padfBbox,
                 const char *pszDstVsiPath, char ** papszOptions,
                 GDALProgressFunc pfnProgress )
{
    const char **ppszKey = NULL;
    int nFcstHour = 0;
    int *panRunHours = NULL;
    int i = 0;
    int j = 0;
    int k = 0;
    int t = 0;
    int rc = 0;
    char **papszDownloadUrls = NULL;
    char **papszOutputFiles = NULL;
    char **papszFinalFiles = NULL;
    int nFilesToGet = 0;
    const char *pszTmpDir;
    const char *pszConfigOpt;
    int nFcstTries;
    int nMaxFcstRewind;
    int nrc;
    int bZip;
    void **pThreads;
    int nThreads;
    const char *pszThreadCount;

    NomadsThreadData *pasData;
    nomads_utc *ref, *end, *fcst;
    nrc = NOMADS_OK;

    CPLDebug( "NOMADS", "Fetching data for bounding box: %lf, %lf, %lf, %lf",
              padfBbox[0], padfBbox[1], padfBbox[2], padfBbox[3] );

    ppszKey = NomadsFindModel( pszModelKey );
    if( ppszKey == NULL )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Could not find model key in nomads data" );
        return NOMADS_ERR;
    }

    NomadsUtcCreate( &ref );
    NomadsUtcCreate( &end );
    rc = NOMADS_OK;
    if( pszRefTime )
    {
        rc = NomadsUtcFromIsoFrmt( ref, pszRefTime );
    }
    if( rc != NOMADS_OK || pszRefTime == NULL )
    {
        NomadsUtcNow( ref );
    }
    NomadsUtcCopy( end, ref );
    NomadsUtcAddHours( end, nHours );

    /* Disable unneeded reading of entire directories, good speedup */
    CPLSetConfigOption( "GDAL_DISABLE_READDIR_ON_OPEN", "TRUE" );

    pszConfigOpt = CPLGetConfigOption( "NOMADS_HTTP_TIMEOUT", "20" );
    if( pszConfigOpt != NULL )
    {
        CPLSetConfigOption( "GDAL_HTTP_TIMEOUT", pszConfigOpt );
    }

    nMaxFcstRewind = atoi( CPLGetConfigOption( "NOMADS_MAX_FCST_REWIND", "2" ) );
    if( nMaxFcstRewind < 1 || nMaxFcstRewind > 24 )
    {
        nMaxFcstRewind = 2;
    }
    /* Go back at least 3 for rap, as it may not get updated all the time. */
    if( EQUALN( pszModelKey, "rap", 3 ) || EQUALN( pszModelKey, "hrrr", 4 ) )
    {
        nMaxFcstRewind = nMaxFcstRewind > 3 ? nMaxFcstRewind : 3;
    }

#ifdef NOMADS_ENABLE_ASYNC
    pszThreadCount = CPLGetConfigOption( "NOMADS_THREAD_COUNT", "4" );
    nThreads = atoi( pszThreadCount );
    if( nThreads < 1 || nThreads > 96 )
    {
        nThreads = 4;
    }
    pThreads = CPLMalloc( sizeof( void * ) * nThreads );
    pasData = CPLMalloc( sizeof( NomadsThreadData ) * nThreads );
#else /* NOMADS_ENABLE_ASYNC */
    /* Unused variables, set to null to so free is no-op */
    nThreads = 1;
    pThreads = NULL;
    pasData = NULL;
#endif /* NOMADS_ENABLE_ASYNC */

    fcst = NULL;
    nFcstTries = 0;
    while( nFcstTries < nMaxFcstRewind )
    {
        nrc = NOMADS_OK;
        fcst = NomadsSetForecastTime( ppszKey, ref, nFcstTries );
        nFcstHour = fcst->ts->tm_hour;
        CPLDebug( "WINDNINJA", "Generated forecast time in utc: %s",
                  NomadsUtcStrfTime( fcst, "%Y%m%dT%HZ" ) );

        if( EQUAL( pszModelKey, "rtma_conus" ) )
        {
            panRunHours = (int*)CPLMalloc( sizeof( int ) );
            nFilesToGet = 1;
        }
        else
        {
            nFilesToGet =
                NomadsBuildForecastRunHours( ppszKey, fcst, end, nHours,
                                             nStride, &panRunHours );
        }

        papszDownloadUrls =
            NomadsBuildForecastFileList( pszModelKey, nFcstHour, panRunHours,
                                         nFilesToGet, fcst, padfBbox );
        if( papszDownloadUrls == NULL )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Could not generate list of URLs to download, invalid data" );
            nFcstTries++;
            NomadsUtcFree( fcst );
            fcst = NULL;
            nrc = NOMADS_ERR;
            continue;
        }
        pszTmpDir = CPLStrdup( CPLGenerateTempFilename( NULL ) );
        CPLDebug( "WINDNINJA", "Creating Temp directory: %s", pszTmpDir );
        VSIMkdir( pszTmpDir, 0777 );
        papszOutputFiles =
            NomadsBuildOutputFileList( pszModelKey, nFcstHour, panRunHours,
                                       nFilesToGet, pszTmpDir, FALSE );
        if( papszOutputFiles == NULL )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Could not generate list of URLs to download, invalid data" );
            nFcstTries++;
            CSLDestroy( papszDownloadUrls );
            NomadsUtcFree( fcst );
            fcst = NULL;
            nrc = NOMADS_ERR;
            continue;
        }

        CPLAssert( CSLCount( papszDownloadUrls ) == nFilesToGet );
        CPLAssert( CSLCount( papszOutputFiles ) == nFilesToGet );

        if( pfnProgress )
        {
            pfnProgress( 0.0, "Starting download...", NULL );
        }

        /* Download one file and start over if it's not there. */
#ifdef NOMADS_USE_VSI_READ
        nrc = NomadsFetchVsi( papszDownloadUrls[0], papszOutputFiles[0] );
#else /* NOMADS_USE_VSI_READ */
        nrc = NomadsFetchHttp( papszDownloadUrls[0], papszOutputFiles[0] );
#endif /* NOMADS_USE_VSI_READ */
        if( nrc != NOMADS_OK )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                      "Failed to download forecast, " \
                      "stepping back one forecast run time step." );
            nFcstTries++;
            CPLSleep( 1 );
            /*
            ** Don't explicitly break here.  We'll skip the while loop because
            ** nrc != NOMADS_OK, and we can clean up memory and shift times in
            ** one spot to avoid duplicate code.
            */
        }
        /* Get the rest */
        i = 1;
        while( i < nFilesToGet && nrc == NOMADS_OK )
        {
            if( pfnProgress )
            {
                if( pfnProgress( (double)i / nFilesToGet,
                                 CPLSPrintf( "Downloading %s...",
                                             CPLGetFilename( papszOutputFiles[i] ) ),
                                 NULL ) )
                {
                    CPLError( CE_Failure, CPLE_UserInterrupt,
                              "Cancelled by user." );
                    nrc = NOMADS_ERR;
                    nFcstTries = nMaxFcstRewind;
                    break;
                }
            }
#ifdef NOMADS_ENABLE_ASYNC
            k = i > nFilesToGet - nThreads ? (nFilesToGet - 1) % nThreads : nThreads;
            for( t = 0; t < k; t++ )
            {
                pasData[t].pszUrl = papszDownloadUrls[i];
                pasData[t].pszFilename = papszOutputFiles[i];
                pThreads[t] =
                    CPLCreateJoinableThread( NomadsFetchAsync, &pasData[t] );
                i++;
            }
            for( t = 0; t < k; t++ )
            {
                CPLJoinThread( pThreads[t] );
            }
            for( t = 0; t < k; t++ )
            {
                if( pasData[t].nErr )
                {
                    CPLError( CE_Warning, CPLE_AppDefined,
                              "Threaded download failed, attempting " \
                              "serial download for %s",
                              CPLGetFilename( pasData[t].pszFilename ) );
                    /* Try again, serially though */
                    if( CPLCheckForFile( (char *)pasData[t].pszFilename, NULL ) );
                    {
                        VSIUnlink( pasData[t].pszFilename );
                    }
#ifdef NOMADS_USE_VSI_READ
                    rc = NomadsFetchVsi( pasData[t].pszUrl,
                                         pasData[t].pszFilename );
#else /* NOMADS_USE_VSI_READ */
                    rc = NomadsFetchHttp( pasData[t].pszUrl,
                                          pasData[t].pszFilename );
#endif /* NOMADS_USE_VSI_READ */
                    if( rc != NOMADS_OK )
                    {
                        nrc = rc;
                    }
                }
            }
#else /* NOMADS_ENABLE_ASYNC */
#ifdef NOMADS_USE_VSI_READ
            nrc = NomadsFetchVsi( papszDownloadUrls[i], papszOutputFiles[i] );
#else /* NOMADS_USE_VSI_READ */
            nrc = NomadsFetchHttp( papszDownloadUrls[i], papszOutputFiles[i] );
#endif /* NOMADS_USE_VSI_READ */
            i++;
#endif /* NOMADS_ENABLE_ASYNC */
            if( nrc != NOMADS_OK )
            {
                CPLError( CE_Warning, CPLE_AppDefined,
                          "Failed to download forecast, " \
                          "stepping back one forecast run time step." );
                nFcstTries++;
                CPLSleep( 1 );
                break;
            }
        }
        /*
        ** XXX Cleanup XXX
        ** After each loop we can get rid of the urls, but we can only get rid
        ** of the others if they are to be reallocated in the next loop.  Any
        ** cleanup in the else nrc == NOMADS_OK clause should be cleaned up in 
        ** the nrc == NOMADS_OK outside the loop.  Those are held so we can
        ** process the output files and zip them into an archive.
        */
        CSLDestroy( papszDownloadUrls );
        NomadsUtcFree( fcst );
        if( nrc == NOMADS_OK )
        {
            break;
        }
        else
        {
            CPLFree( (void*)panRunHours );
            CSLDestroy( papszOutputFiles );
            CPLUnlinkTree( pszTmpDir );
            CPLFree( (void*)pszTmpDir );
        }
    }
    if( nrc == NOMADS_OK )
    {
        bZip = EQUAL( CPLGetExtension( pszDstVsiPath ), "zip" ) ? TRUE : FALSE;
        papszFinalFiles =
            NomadsBuildOutputFileList( pszModelKey, nFcstHour, panRunHours,
                                       nFilesToGet, pszDstVsiPath,
                                       bZip );
        CPLFree( (void*)panRunHours );
        if( CPLCheckForFile( (char*)pszDstVsiPath, NULL ) )
        {
            CPLUnlinkTree( pszDstVsiPath );
        }
        if( !bZip )
        {
            VSIMkdir( pszDstVsiPath, 0777 );
        }
        nrc = NomadsZipFiles( papszOutputFiles, papszFinalFiles );
        CSLDestroy( papszOutputFiles );
        CSLDestroy( papszFinalFiles );
        if( nrc != NOMADS_OK )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Could not copy files into path, unknown i/o failure" );
            CPLUnlinkTree( pszDstVsiPath );
        }
        CPLUnlinkTree( pszTmpDir );
        CPLFree( (void*)pszTmpDir );
    }
    CPLFree( (void*)pasData );
    CPLFree( (void**)pThreads );

    NomadsUtcFree( ref );
    NomadsUtcFree( end );
    CPLSetConfigOption( "GDAL_HTTP_TIMEOUT", NULL );
    CPLSetConfigOption( "GDAL_DISABLE_READDIR_ON_OPEN", NULL );
    if( nrc == NOMADS_OK && pfnProgress )
    {
        pfnProgress( 1.0, NULL, NULL );
    }
    return nrc;
}