/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Implementation of query operations on rasters.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include <assert.h>
#include "mapserver.h"
#include "mapresample.h"
#include "mapthread.h"
#include "mapraster.h"


int msRASTERLayerGetShape(layerObj *layer, shapeObj *shape, resultObj *record);
int msRASTERLayerGetItems(layerObj *layer);

/* ==================================================================== */
/*      For now the rasterLayerInfo lives here since it is just used    */
/*      to hold information related to queries.                         */
/* ==================================================================== */
typedef struct {

  /* query cache results */
  int query_results;
  int query_alloc_max;
  int query_request_max;
  int query_result_hard_max;
  int raster_query_mode;
  int band_count;

  int refcount;

  rectObj which_rect;
  int     next_shape;

  double *qc_x;
  double *qc_y;
  double *qc_x_reproj;
  double *qc_y_reproj;
  float *qc_values;
  int    *qc_class;
  int    *qc_red;
  int    *qc_green;
  int    *qc_blue;
  int    *qc_count;
  int    *qc_tileindex;

  /* query bound in force */
  shapeObj *searchshape;

  /* Only nearest result to this point. */
  int      range_mode; /* MS_QUERY_SINGLE, MS_QUERY_MULTIPLE or -1 (skip test) */
  double   range_dist;
  pointObj target_point;

  GDALColorTableH hCT;

  double   shape_tolerance;

} rasterLayerInfo;

#define RQM_UNKNOWN               0
#define RQM_ENTRY_PER_PIXEL       1
#define RQM_HIST_ON_CLASS         2
#define RQM_HIST_ON_VALUE         3

extern int InvGeoTransform( double *gt_in, double *gt_out );
#define GEO_TRANS(tr,x,y)  ((tr)[0]+(tr)[1]*(x)+(tr)[2]*(y))

/************************************************************************/
/*                             addResult()                              */
/*                                                                      */
/*      this is a copy of the code in mapquery.c.  Should we prepare    */
/*      a small public API for managing results caches?                 */
/************************************************************************/

static int addResult(resultCacheObj *cache, int classindex, int shapeindex, int tileindex)
{
  int i;

  if(cache->numresults == cache->cachesize) { /* just add it to the end */
    if(cache->cachesize == 0)
      cache->results = (resultObj *) malloc(sizeof(resultObj)*MS_RESULTCACHEINCREMENT);
    else
      cache->results = (resultObj *) realloc(cache->results, sizeof(resultObj)*(cache->cachesize+MS_RESULTCACHEINCREMENT));
    if(!cache->results) {
      msSetError(MS_MEMERR, "Realloc() error.", "addResult()");
      return(MS_FAILURE);
    }
    cache->cachesize += MS_RESULTCACHEINCREMENT;
  }

  i = cache->numresults;

  cache->results[i].classindex = classindex;
  cache->results[i].tileindex = tileindex;
  cache->results[i].shapeindex = shapeindex;
  cache->results[i].resultindex = -1; /* unused */
  cache->results[i].shape = NULL;
  cache->numresults++;

  return(MS_SUCCESS);
}

/************************************************************************/
/*                       msRasterLayerInfoFree()                        */
/************************************************************************/

static void msRasterLayerInfoFree( layerObj *layer )

{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  if( rlinfo == NULL )
    return;

  if( rlinfo->qc_x != NULL ) {
    free( rlinfo->qc_x );
    free( rlinfo->qc_y );
    free( rlinfo->qc_x_reproj );
    free( rlinfo->qc_y_reproj );
  }

  if( rlinfo->qc_values )
    free( rlinfo->qc_values );

  if( rlinfo->qc_class ) {
    free( rlinfo->qc_class );
  }

  if( rlinfo->qc_red ) {
    free( rlinfo->qc_red );
    free( rlinfo->qc_green );
    free( rlinfo->qc_blue );
  }

  if( rlinfo->qc_count != NULL )
    free( rlinfo->qc_count );

  if( rlinfo->qc_tileindex != NULL )
    free( rlinfo->qc_tileindex );

  free( rlinfo );

  layer->layerinfo = NULL;
}

/************************************************************************/
/*                    msRasterLayerInfoInitialize()                     */
/************************************************************************/

static void msRasterLayerInfoInitialize( layerObj *layer )

{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  if( rlinfo != NULL )
    return;

  rlinfo = (rasterLayerInfo *) msSmallCalloc(1,sizeof(rasterLayerInfo));
  layer->layerinfo = rlinfo;

  rlinfo->band_count = -1;
  rlinfo->raster_query_mode = RQM_ENTRY_PER_PIXEL;
  rlinfo->range_mode = -1; /* inactive */
  rlinfo->refcount = 0;
  rlinfo->shape_tolerance = 0.0;

  /* We need to do this or the layer->layerinfo will be interpreted */
  /* as shapefile access info because the default connectiontype is */
  /* MS_SHAPEFILE. */
  if (layer->connectiontype != MS_WMS && layer->connectiontype != MS_KERNELDENSITY)
    layer->connectiontype = MS_RASTER;

  rlinfo->query_result_hard_max = 1000000;

  if( CSLFetchNameValue( layer->processing, "RASTER_QUERY_MAX_RESULT" )
      != NULL ) {
    rlinfo->query_result_hard_max =
      atoi(CSLFetchNameValue( layer->processing, "RASTER_QUERY_MAX_RESULT" ));
  }
}

/************************************************************************/
/*                       msRasterQueryAddPixel()                        */
/************************************************************************/

static void msRasterQueryAddPixel( layerObj *layer, pointObj *location,
                                   pointObj *reprojectedLocation,
                                   float *values )

{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;
  int red = 0, green = 0, blue = 0, nodata = FALSE;
  int p_class = -1;

  if( rlinfo->query_results == rlinfo->query_result_hard_max )
    return;

  /* -------------------------------------------------------------------- */
  /*      Is this our first time in?  If so, do an initial allocation     */
  /*      for the data arrays suitable to our purposes.                   */
  /* -------------------------------------------------------------------- */
  if( rlinfo->query_alloc_max == 0 ) {
    rlinfo->query_alloc_max = 2;

    switch( rlinfo->raster_query_mode ) {
      case RQM_ENTRY_PER_PIXEL:
        rlinfo->qc_x = (double *)
                       msSmallCalloc(sizeof(double),rlinfo->query_alloc_max);
        rlinfo->qc_y = (double *)
                       msSmallCalloc(sizeof(double),rlinfo->query_alloc_max);
        rlinfo->qc_x_reproj = (double *)
                       msSmallCalloc(sizeof(double),rlinfo->query_alloc_max);
        rlinfo->qc_y_reproj = (double *)
                       msSmallCalloc(sizeof(double),rlinfo->query_alloc_max);
        rlinfo->qc_values = (float *)
                            msSmallCalloc(sizeof(float),
                                          rlinfo->query_alloc_max*rlinfo->band_count);
        rlinfo->qc_red = (int *)
                         msSmallCalloc(sizeof(int),rlinfo->query_alloc_max);
        rlinfo->qc_green = (int *)
                           msSmallCalloc(sizeof(int),rlinfo->query_alloc_max);
        rlinfo->qc_blue = (int *)
                          msSmallCalloc(sizeof(int),rlinfo->query_alloc_max);
        if( layer->numclasses > 0 )
          rlinfo->qc_class = (int *)
                             msSmallCalloc(sizeof(int),rlinfo->query_alloc_max);
        break;

      case RQM_HIST_ON_CLASS:
        break;

      case RQM_HIST_ON_VALUE:
        break;

      default:
        assert( FALSE );
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Reallocate the data arrays larger if they are near the max      */
  /*      now.                                                            */
  /* -------------------------------------------------------------------- */
  if( rlinfo->query_results == rlinfo->query_alloc_max ) {
    rlinfo->query_alloc_max = rlinfo->query_alloc_max * 2 + 100;

    if( rlinfo->qc_x != NULL )
      rlinfo->qc_x = msSmallRealloc(rlinfo->qc_x,
                                    sizeof(double) * rlinfo->query_alloc_max);
    if( rlinfo->qc_y != NULL )
      rlinfo->qc_y = msSmallRealloc(rlinfo->qc_y,
                                    sizeof(double) * rlinfo->query_alloc_max);
    if( rlinfo->qc_x_reproj != NULL )
      rlinfo->qc_x_reproj = msSmallRealloc(rlinfo->qc_x_reproj,
                                    sizeof(double) * rlinfo->query_alloc_max);
    if( rlinfo->qc_y_reproj != NULL )
      rlinfo->qc_y_reproj = msSmallRealloc(rlinfo->qc_y_reproj,
                                    sizeof(double) * rlinfo->query_alloc_max);
    if( rlinfo->qc_values != NULL )
      rlinfo->qc_values =
        msSmallRealloc(rlinfo->qc_values,
                       sizeof(float) * rlinfo->query_alloc_max
                       * rlinfo->band_count );
    if( rlinfo->qc_class != NULL )
      rlinfo->qc_class = msSmallRealloc(rlinfo->qc_class,
                                        sizeof(int) * rlinfo->query_alloc_max);
    if( rlinfo->qc_red != NULL )
      rlinfo->qc_red = msSmallRealloc(rlinfo->qc_red,
                                      sizeof(int) * rlinfo->query_alloc_max);
    if( rlinfo->qc_green != NULL )
      rlinfo->qc_green = msSmallRealloc(rlinfo->qc_green,
                                        sizeof(int) * rlinfo->query_alloc_max);
    if( rlinfo->qc_blue != NULL )
      rlinfo->qc_blue = msSmallRealloc(rlinfo->qc_blue,
                                       sizeof(int) * rlinfo->query_alloc_max);
    if( rlinfo->qc_count != NULL )
      rlinfo->qc_count = msSmallRealloc(rlinfo->qc_count,
                                        sizeof(int) * rlinfo->query_alloc_max);
    if( rlinfo->qc_tileindex != NULL )
      rlinfo->qc_tileindex = msSmallRealloc(rlinfo->qc_tileindex,
                                            sizeof(int) * rlinfo->query_alloc_max);
  }

  /* -------------------------------------------------------------------- */
  /*      Handle colormap                                                 */
  /* -------------------------------------------------------------------- */
  if( rlinfo->hCT != NULL ) {
    int pct_index = (int) floor(values[0]);
    GDALColorEntry sEntry;

    if( GDALGetColorEntryAsRGB( rlinfo->hCT, pct_index, &sEntry ) ) {
      red = sEntry.c1;
      green = sEntry.c2;
      blue = sEntry.c3;

      if( sEntry.c4 == 0 )
        nodata = TRUE;
    } else
      nodata = TRUE;
  }

  /* -------------------------------------------------------------------- */
  /*      Color derived from greyscale value.                             */
  /* -------------------------------------------------------------------- */
  else {
    if( rlinfo->band_count >= 3 ) {
      red = (int) MS_MAX(0,MS_MIN(255,values[0]));
      green = (int) MS_MAX(0,MS_MIN(255,values[1]));
      blue = (int) MS_MAX(0,MS_MIN(255,values[2]));
    } else {
      red = green = blue = (int) MS_MAX(0,MS_MIN(255,values[0]));
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Handle classification.                                          */
  /*                                                                      */
  /*      NOTE: The following is really quite inadequate to deal with     */
  /*      classifications based on [red], [green] and [blue] as           */
  /*      described in:                                                   */
  /*       http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=1021         */
  /* -------------------------------------------------------------------- */
  if( rlinfo->qc_class != NULL ) {
    p_class = msGetClass_FloatRGB(layer, values[0],
                                  red, green, blue );

    if( p_class == -1 )
      nodata = TRUE;
    else {
      nodata = FALSE;
      rlinfo->qc_class[rlinfo->query_results] = p_class;
      if( layer->class[p_class]->numstyles > 0 ) {
        red   = layer->class[p_class]->styles[0]->color.red;
        green = layer->class[p_class]->styles[0]->color.green;
        blue  = layer->class[p_class]->styles[0]->color.blue;
      } else {
        red = green = blue = 0;
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Record the color.                                               */
  /* -------------------------------------------------------------------- */
  rlinfo->qc_red[rlinfo->query_results] = red;
  rlinfo->qc_green[rlinfo->query_results] = green;
  rlinfo->qc_blue[rlinfo->query_results] = blue;

  /* -------------------------------------------------------------------- */
  /*      Record spatial location.                                        */
  /* -------------------------------------------------------------------- */
  if( rlinfo->qc_x != NULL ) {
    rlinfo->qc_x[rlinfo->query_results] = location->x;
    rlinfo->qc_y[rlinfo->query_results] = location->y;
    rlinfo->qc_x_reproj[rlinfo->query_results] = reprojectedLocation->x;
    rlinfo->qc_y_reproj[rlinfo->query_results] = reprojectedLocation->y;
  }

  /* -------------------------------------------------------------------- */
  /*      Record actual pixel value(s).                                   */
  /* -------------------------------------------------------------------- */
  if( rlinfo->qc_values != NULL )
    memcpy( rlinfo->qc_values + rlinfo->query_results * rlinfo->band_count,
            values, sizeof(float) * rlinfo->band_count );

  /* -------------------------------------------------------------------- */
  /*      Add to the results cache.                                       */
  /* -------------------------------------------------------------------- */
  if( ! nodata ) {
    addResult( layer->resultcache, p_class, rlinfo->query_results, 0 );
    rlinfo->query_results++;
  }
}

/************************************************************************/
/*                       msRasterQueryByRectLow()                       */
/************************************************************************/

static int
msRasterQueryByRectLow(mapObj *map, layerObj *layer, GDALDatasetH hDS,
                       rectObj queryRect)

{
  double  adfGeoTransform[6], adfInvGeoTransform[6];
  double      dfXMin, dfYMin, dfXMax, dfYMax, dfX, dfY, dfAdjustedRange;
  int         nWinXOff, nWinYOff, nWinXSize, nWinYSize;
  int         nRXSize, nRYSize;
  float       *pafRaster;
  int         nBandCount, *panBandMap, iPixel, iLine;
  CPLErr      eErr;
  rasterLayerInfo *rlinfo;
  rectObj     searchrect;
#if PROJ_VERSION_MAJOR < 6
  int         mayNeedLonWrapAdjustment = MS_FALSE;
#endif

  rlinfo = (rasterLayerInfo *) layer->layerinfo;

  /* -------------------------------------------------------------------- */
  /*      Reproject the search rect into the projection of this           */
  /*      layer/file if needed.                                           */
  /* -------------------------------------------------------------------- */
  searchrect = queryRect;
  layer->project = msProjectionsDiffer(&(layer->projection), &(map->projection));
  if(layer->project)
    msProjectRect(&(map->projection), &(layer->projection), &searchrect);

  /* -------------------------------------------------------------------- */
  /*      Transform the rectangle in target ground coordinates to         */
  /*      pixel/line extents on the file.  Process all 4 corners, to      */
  /*      build extents.                                                  */
  /* -------------------------------------------------------------------- */
  nRXSize = GDALGetRasterXSize( hDS );
  nRYSize = GDALGetRasterYSize( hDS );

  msGetGDALGeoTransform( hDS, map, layer, adfGeoTransform );
  InvGeoTransform( adfGeoTransform, adfInvGeoTransform );

  /* top left */
  dfXMin = dfXMax = GEO_TRANS(adfInvGeoTransform,
                              searchrect.minx, searchrect.maxy);
  dfYMin = dfYMax = GEO_TRANS(adfInvGeoTransform+3,
                              searchrect.minx, searchrect.maxy);

  /* top right */
  dfX = GEO_TRANS(adfInvGeoTransform  , searchrect.maxx, searchrect.maxy);
  dfY = GEO_TRANS(adfInvGeoTransform+3, searchrect.maxx, searchrect.maxy);
  dfXMin = MS_MIN(dfXMin,dfX);
  dfXMax = MS_MAX(dfXMax,dfX);
  dfYMin = MS_MIN(dfYMin,dfY);
  dfYMax = MS_MAX(dfYMax,dfY);

  /* bottom left */
  dfX = GEO_TRANS(adfInvGeoTransform  , searchrect.minx, searchrect.miny);
  dfY = GEO_TRANS(adfInvGeoTransform+3, searchrect.minx, searchrect.miny);
  dfXMin = MS_MIN(dfXMin,dfX);
  dfXMax = MS_MAX(dfXMax,dfX);
  dfYMin = MS_MIN(dfYMin,dfY);
  dfYMax = MS_MAX(dfYMax,dfY);

  /* bottom right */
  dfX = GEO_TRANS(adfInvGeoTransform  , searchrect.maxx, searchrect.miny);
  dfY = GEO_TRANS(adfInvGeoTransform+3, searchrect.maxx, searchrect.miny);
  dfXMin = MS_MIN(dfXMin,dfX);
  dfXMax = MS_MAX(dfXMax,dfX);
  dfYMin = MS_MIN(dfYMin,dfY);
  dfYMax = MS_MAX(dfYMax,dfY);

  /* -------------------------------------------------------------------- */
  /*      Trim the rectangle to the area of the file itself, but out      */
  /*      to the edges of the touched edge pixels.                        */
  /* -------------------------------------------------------------------- */
  dfXMin = MS_MAX(0.0,MS_MIN(nRXSize,floor(dfXMin)));
  dfYMin = MS_MAX(0.0,MS_MIN(nRYSize,floor(dfYMin)));
  dfXMax = MS_MAX(0.0,MS_MIN(nRXSize,ceil(dfXMax)));
  dfYMax = MS_MAX(0.0,MS_MIN(nRYSize,ceil(dfYMax)));

  /* -------------------------------------------------------------------- */
  /*      Convert to integer offset/size values.                          */
  /* -------------------------------------------------------------------- */
  nWinXOff = (int) dfXMin;
  nWinYOff = (int) dfYMin;
  nWinXSize = (int) (dfXMax - dfXMin);
  nWinYSize = (int) (dfYMax - dfYMin);

  /* -------------------------------------------------------------------- */
  /*      What bands are we operating on?                                 */
  /* -------------------------------------------------------------------- */
  panBandMap = msGetGDALBandList( layer, hDS, 0, &nBandCount );

  if( rlinfo->band_count == -1 )
    rlinfo->band_count = nBandCount;

  if( nBandCount != rlinfo->band_count ) {
    msSetError( MS_IMGERR,
                "Got %d bands, but expected %d bands.",
                "msRasterQueryByRectLow()",
                nBandCount, rlinfo->band_count );

    return -1;
  }

  /* -------------------------------------------------------------------- */
  /*      Try to load the raster data.  For now we just load the first    */
  /*      band in the file.  Later we will deal with the various band     */
  /*      selection criteria.                                             */
  /* -------------------------------------------------------------------- */
  pafRaster = (float *)
              calloc(sizeof(float),nWinXSize*nWinYSize*nBandCount);
  MS_CHECK_ALLOC(pafRaster, sizeof(float)*nWinXSize*nWinYSize*nBandCount, -1);

  eErr = GDALDatasetRasterIO( hDS, GF_Read,
                              nWinXOff, nWinYOff, nWinXSize, nWinYSize,
                              pafRaster, nWinXSize, nWinYSize, GDT_Float32,
                              nBandCount, panBandMap,
                              4 * nBandCount,
                              4 * nBandCount * nWinXSize,
                              4 );

  if( eErr != CE_None ) {
    msSetError( MS_IOERR, "GDALDatasetRasterIO() failed: %s",
                "msRasterQueryByRectLow()", CPLGetLastErrorMsg() );

    free( pafRaster );
    return -1;
  }

  /* -------------------------------------------------------------------- */
  /*      Fetch color table for intepreting colors if needed.             */
  /* -------------------------------------------------------------------- */
  rlinfo->hCT = GDALGetRasterColorTable(
                  GDALGetRasterBand( hDS, panBandMap[0] ) );

  free( panBandMap );

  /* -------------------------------------------------------------------- */
  /*      When computing whether pixels are within range we do it         */
  /*      based on the center of the pixel to the target point but        */
  /*      really it ought to be the nearest point on the pixel.  It       */
  /*      would be too much trouble to do this rigerously, so we just     */
  /*      add a fudge factor so that a range of zero will find the        */
  /*      pixel the target falls in at least.                             */
  /* -------------------------------------------------------------------- */
  dfAdjustedRange =
    sqrt(adfGeoTransform[1] * adfGeoTransform[1]
         + adfGeoTransform[2] * adfGeoTransform[2]
         + adfGeoTransform[4] * adfGeoTransform[4]
         + adfGeoTransform[5] * adfGeoTransform[5]) * 0.5 * 1.41421356237
    + sqrt( rlinfo->range_dist );
  dfAdjustedRange = dfAdjustedRange * dfAdjustedRange;

#if PROJ_VERSION_MAJOR < 6
    if( layer->project &&
        msProjIsGeographicCRS(&(layer->projection)) &&
        msProjIsGeographicCRS(&(map->projection)) )
    {
        double dfLonWrap = 0;
        mayNeedLonWrapAdjustment = msProjectHasLonWrap(&(layer->projection), &dfLonWrap);
    }
#endif

  reprojectionObj* reprojector = NULL;
  if( layer->project )
  {
      reprojector = msProjectCreateReprojector(&(layer->projection), &(map->projection));
  }

  /* -------------------------------------------------------------------- */
  /*      Loop over all pixels determining which are "in".                */
  /* -------------------------------------------------------------------- */
  for( iLine = 0; iLine < nWinYSize; iLine++ ) {
    for( iPixel = 0; iPixel < nWinXSize; iPixel++ ) {
      pointObj  sPixelLocation,sReprojectedPixelLocation;

      if( rlinfo->query_results == rlinfo->query_result_hard_max )
        break;

      /* transform pixel/line to georeferenced */
      sPixelLocation.x =
        GEO_TRANS(adfGeoTransform,
                  iPixel + nWinXOff + 0.5, iLine + nWinYOff + 0.5 );
      sPixelLocation.y =
        GEO_TRANS(adfGeoTransform+3,
                  iPixel + nWinXOff + 0.5, iLine + nWinYOff + 0.5 );

      /* If projections differ, convert this back into the map  */
      /* projection for distance testing, and comprison to the  */
      /* search shape.  Save the original pixel location coordinates */
      /* in sPixelLocationInLayerSRS, so that we can return those */
      /* coordinates if we have a hit */
      sReprojectedPixelLocation = sPixelLocation;
      if( reprojector )
      {
#if PROJ_VERSION_MAJOR < 6
        /* Works around a bug in PROJ < 6 when reprojecting from a lon_wrap */
        /* geogCRS to a geogCRS, and the input abs(longitude) is > 180. Then */
        /* lon_wrap was ignored and the output longitude remained as the source */
        if( mayNeedLonWrapAdjustment )
        {
            if( rlinfo->target_point.x < sReprojectedPixelLocation.x - 180 )
                sReprojectedPixelLocation.x -= 360;
            else if( rlinfo->target_point.x > sReprojectedPixelLocation.x + 180 )
                sReprojectedPixelLocation.x += 360;
        }
#endif

        msProjectPointEx( reprojector, &sReprojectedPixelLocation);
      }

      /* If we are doing QueryByShape, check against the shape now */
      if( rlinfo->searchshape != NULL ) {
        if( rlinfo->shape_tolerance == 0.0
            && rlinfo->searchshape->type == MS_SHAPE_POLYGON ) {
          if( msIntersectPointPolygon(
                &sReprojectedPixelLocation, rlinfo->searchshape ) == MS_FALSE )
            continue;
        } else {
          shapeObj  tempShape;
          lineObj   tempLine;

          memset( &tempShape, 0, sizeof(shapeObj) );
          tempShape.type = MS_SHAPE_POINT;
          tempShape.numlines = 1;
          tempShape.line = &tempLine;
          tempLine.numpoints = 1;
          tempLine.point = &sReprojectedPixelLocation;

          if( msDistanceShapeToShape(rlinfo->searchshape, &tempShape)
              > rlinfo->shape_tolerance )
            continue;
        }
      }

      if( rlinfo->range_mode >= 0 ) {
        double dist;

        dist = (rlinfo->target_point.x - sReprojectedPixelLocation.x)
               * (rlinfo->target_point.x - sReprojectedPixelLocation.x)
               + (rlinfo->target_point.y - sReprojectedPixelLocation.y)
               * (rlinfo->target_point.y - sReprojectedPixelLocation.y);

        if( dist >= dfAdjustedRange )
          continue;

        /* If we can only have one feature, trim range and clear */
        /* previous result.  */
        if( rlinfo->range_mode == MS_QUERY_SINGLE ) {
          rlinfo->range_dist = dist;
          rlinfo->query_results = 0;
        }
      }

      msRasterQueryAddPixel( layer,
			                       &sPixelLocation, // return coords in layer SRS
                             &sReprojectedPixelLocation,
                             pafRaster
                             + (iLine*nWinXSize + iPixel) * nBandCount );
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Cleanup.                                                        */
  /* -------------------------------------------------------------------- */
  free( pafRaster );
  msProjectDestroyReprojector( reprojector );

  return MS_SUCCESS;
}

/************************************************************************/
/*                        msRasterQueryByRect()                         */
/************************************************************************/

int msRasterQueryByRect(mapObj *map, layerObj *layer, rectObj queryRect)

{
  int status = MS_SUCCESS;
  char *filename=NULL;

  layerObj *tlp=NULL; /* pointer to the tile layer either real or temporary */
  int tileitemindex=-1, tilelayerindex=-1, tilesrsindex=-1;
  shapeObj tshp;
  char tilename[MS_PATH_LENGTH], tilesrsname[1024];
  int  done, destroy_on_failure;

  char szPath[MS_MAXPATHLEN];
  rectObj searchrect;
  rasterLayerInfo *rlinfo = NULL;

  /* -------------------------------------------------------------------- */
  /*      Get the layer info.                                             */
  /* -------------------------------------------------------------------- */
  if(!layer->layerinfo) {
    msRasterLayerInfoInitialize( layer );
    destroy_on_failure = 1;
  } else {
    destroy_on_failure = 0;
  }
  rlinfo = (rasterLayerInfo *) layer->layerinfo;

  /* -------------------------------------------------------------------- */
  /*      Clear old results cache.                                        */
  /* -------------------------------------------------------------------- */
  if(layer->resultcache) {
    if(layer->resultcache->results) free(layer->resultcache->results);
    free(layer->resultcache);
    layer->resultcache = NULL;
  }

  /* -------------------------------------------------------------------- */
  /*      Initialize the results cache.                                   */
  /* -------------------------------------------------------------------- */
  layer->resultcache = (resultCacheObj *)msSmallMalloc(sizeof(resultCacheObj));
  layer->resultcache->results = NULL;
  layer->resultcache->numresults = layer->resultcache->cachesize = 0;
  layer->resultcache->bounds.minx =
    layer->resultcache->bounds.miny =
      layer->resultcache->bounds.maxx =
        layer->resultcache->bounds.maxy = -1;

  /* -------------------------------------------------------------------- */
  /*      Check if we should really be acting on this layer and           */
  /*      provide debug info in various cases.                            */
  /* -------------------------------------------------------------------- */
  if(layer->debug > 0 || map->debug > 1)
    msDebug( "msRasterQueryByRect(%s): entering.\n", layer->name );

  if(!layer->data && !layer->tileindex) {
    if(layer->debug > 0 || map->debug > 0 )
      msDebug( "msRasterQueryByRect(%s): layer data and tileindex NULL ... doing nothing.", layer->name );
    return MS_SUCCESS;
  }

  if((layer->status != MS_ON) && layer->status != MS_DEFAULT ) {
    if(layer->debug > 0 )
      msDebug( "msRasterQueryByRect(%s): not status ON or DEFAULT, doing nothing.", layer->name );
    return MS_SUCCESS;
  }

  /* ==================================================================== */
  /*      Handle setting up tileindex layer.                              */
  /* ==================================================================== */
  if(layer->tileindex) { /* we have an index file */
    msInitShape(&tshp);
    searchrect = queryRect;

    status = msDrawRasterSetupTileLayer(map, layer,
                           &searchrect,
                           MS_TRUE,
                           &tilelayerindex,
                           &tileitemindex,
                           &tilesrsindex,
                           &tlp);
    if (status != MS_SUCCESS) {
      goto cleanup;
    }
  } else {
    /* we have to manually apply to scaletoken logic as we're not going through msLayerOpen() */
    status = msLayerApplyScaletokens(layer,map->scaledenom);
    if (status != MS_SUCCESS) {
      goto cleanup;
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Iterate over all tiles (just one in untiled case).              */
  /* -------------------------------------------------------------------- */
  done = MS_FALSE;
  while( done == MS_FALSE && status == MS_SUCCESS ) {

    GDALDatasetH  hDS;
    char *decrypted_path = NULL;

    /* -------------------------------------------------------------------- */
    /*      Get filename.                                                   */
    /* -------------------------------------------------------------------- */
    if(layer->tileindex) {
      status = msDrawRasterIterateTileIndex(layer, tlp, &tshp,
                                            tileitemindex, tilesrsindex,
                                            tilename, sizeof(tilename),
                                            tilesrsname, sizeof(tilesrsname));
      if( status == MS_FAILURE) {
        break;
      }

      if(status == MS_DONE) break; /* no more tiles/images */
      filename = tilename;
    } else {
      filename = layer->data;
      done = MS_TRUE; /* only one image so we're done after this */
    }

    if(strlen(filename) == 0) continue;

    /* -------------------------------------------------------------------- */
    /*      Open the file.                                                  */
    /* -------------------------------------------------------------------- */
    msGDALInitialize();

    msDrawRasterBuildRasterPath(map, layer, filename, szPath);

    decrypted_path = msDecryptStringTokens( map, szPath );
    if( !decrypted_path ) {
      status = MS_FAILURE;
      goto cleanup;
    }

    msAcquireLock( TLOCK_GDAL );
    if( !layer->tileindex )
    {
        char** connectionoptions = msGetStringListFromHashTable(&(layer->connectionoptions));
        hDS = GDALOpenEx(decrypted_path,
                                    GDAL_OF_RASTER,
                                    NULL,
                                    (const char* const*)connectionoptions,
                                    NULL);
        CSLDestroy(connectionoptions);
    }
    else
    {
        hDS = GDALOpen(decrypted_path, GA_ReadOnly );
    }

    if( hDS == NULL ) {
      int ignore_missing = msMapIgnoreMissingData( map );
      const char *cpl_error_msg = msDrawRasterGetCPLErrorMsg(decrypted_path, szPath);

      msFree( decrypted_path );
      decrypted_path = NULL;

      msReleaseLock( TLOCK_GDAL );

      if ( ignore_missing == MS_MISSING_DATA_FAIL ) {
        if( layer->debug || map->debug )
          msSetError( MS_IMGERR,
                      "Unable to open file %s for layer %s ... fatal error.\n%s",
                      "msRasterQueryByRect()",
                      szPath, layer->name, cpl_error_msg);
        status = MS_FAILURE;
        goto cleanup;
      }
      if( ignore_missing == MS_MISSING_DATA_LOG ) {
        if( layer->debug || map->debug )
          msDebug( "Unable to open file %s for layer %s ... ignoring this missing data.\n%s",
                   filename, layer->name, cpl_error_msg );
      }
      continue;
    }

    msFree( decrypted_path );
    decrypted_path = NULL;

    if( msDrawRasterLoadProjection(layer, hDS, filename, tilesrsindex, tilesrsname) != MS_SUCCESS )
    {
        msReleaseLock( TLOCK_GDAL );
        status = MS_FAILURE;
        goto cleanup;
    }

    /* -------------------------------------------------------------------- */
    /*      Perform actual query on this file.                              */
    /* -------------------------------------------------------------------- */
    if( status == MS_SUCCESS )
      status = msRasterQueryByRectLow( map, layer, hDS, queryRect );

    GDALClose( hDS );
    msReleaseLock( TLOCK_GDAL );

  } /* next tile */

  /* -------------------------------------------------------------------- */
  /*      Cleanup tileindex if it is open.                                */
  /* -------------------------------------------------------------------- */
cleanup:
  if(layer->tileindex) { /* tiling clean-up */
    msDrawRasterCleanupTileLayer(tlp, tilelayerindex);
  } else {
    msLayerRestoreFromScaletokens(layer);
  }

  /* -------------------------------------------------------------------- */
  /*      On failure, or empty result set, cleanup the rlinfo since we    */
  /*      likely won't ever have it accessed or cleaned up later.         */
  /* -------------------------------------------------------------------- */
  if( status == MS_FAILURE || rlinfo->query_results == 0 ) {
    if(destroy_on_failure) {
      msRasterLayerInfoFree( layer );
    }
  } else {
    /* populate the items/numitems layer-level values */
    msRASTERLayerGetItems(layer);
  }

  return status;
}

/************************************************************************/
/*                        msRasterQueryByShape()                        */
/************************************************************************/

int msRasterQueryByShape(mapObj *map, layerObj *layer, shapeObj *selectshape)

{
  rasterLayerInfo *rlinfo = NULL;
  int status;
  double tolerance;
  rectObj searchrect;

  msRasterLayerInfoInitialize( layer );

  rlinfo = (rasterLayerInfo *) layer->layerinfo;

  /* If the selection shape is point or line we use the default tolerance of
     3, but for polygons we require an exact hit. */
  if(layer->tolerance == -1) {
    if(selectshape->type == MS_SHAPE_POINT ||
        selectshape->type == MS_SHAPE_LINE)
      tolerance = 3;
    else
      tolerance = 0;
  } else
    tolerance = layer->tolerance;

  if(layer->toleranceunits == MS_PIXELS)
    tolerance = tolerance
                * msAdjustExtent(&(map->extent), map->width, map->height);
  else
    tolerance = tolerance
                * (msInchesPerUnit(layer->toleranceunits,0)
                   / msInchesPerUnit(map->units,0));

  rlinfo->searchshape = selectshape;
  rlinfo->shape_tolerance = tolerance;

  msComputeBounds( selectshape );
  searchrect = selectshape->bounds;

  searchrect.minx -= tolerance; /* expand the search box to account for layer tolerances (e.g. buffered searches) */
  searchrect.maxx += tolerance;
  searchrect.miny -= tolerance;
  searchrect.maxy += tolerance;

  status = msRasterQueryByRect( map, layer, searchrect );

  rlinfo = (rasterLayerInfo *) layer->layerinfo;
  if( rlinfo )
    rlinfo->searchshape = NULL;

  return status;
}

/************************************************************************/
/*                        msRasterQueryByPoint()                        */
/************************************************************************/

int msRasterQueryByPoint(mapObj *map, layerObj *layer, int mode, pointObj p,
                         double buffer, int maxresults)
{
  int result;
  double layer_tolerance;
  rectObj bufferRect;
  rasterLayerInfo *rlinfo = NULL;

  msRasterLayerInfoInitialize( layer );

  rlinfo = (rasterLayerInfo *) layer->layerinfo;

  /* -------------------------------------------------------------------- */
  /*      If the buffer is not set, then use layer tolerances             */
  /*      instead.   The "buffer" distince is now in georeferenced        */
  /*      units.  Note that tolerances in pixels are basically map        */
  /*      display pixels, not underlying raster pixels.  It isn't         */
  /*      clear that there is any way of requesting a buffer size in      */
  /*      underlying pixels.                                              */
  /* -------------------------------------------------------------------- */
  if(buffer <= 0) { /* use layer tolerance */
    if(layer->tolerance == -1)
      layer_tolerance = 3;
    else
      layer_tolerance = layer->tolerance;

    if(layer->toleranceunits == MS_PIXELS)
      buffer = layer_tolerance
               * msAdjustExtent(&(map->extent), map->width, map->height);
    else
      buffer = layer_tolerance
               * (msInchesPerUnit(layer->toleranceunits,0)
                  / msInchesPerUnit(map->units,0));
  }

  /* -------------------------------------------------------------------- */
  /*      Setup target point information, at this point they are in       */
  /*      map coordinates.                                                */
  /* -------------------------------------------------------------------- */
  rlinfo->range_dist = buffer * buffer;
  rlinfo->target_point = p;

  /* -------------------------------------------------------------------- */
  /*      if we are in the MS_QUERY_SINGLE mode, first try a query with   */
  /*      zero tolerance.  If this gets a raster pixel then we can be     */
  /*      reasonably assured that it is the closest to the query          */
  /*      point.  This will potentially be must more efficient than       */
  /*      processing all pixels within the tolerance.                     */
  /* -------------------------------------------------------------------- */
  if( mode == MS_QUERY_SINGLE ) {
    rectObj pointRect;

    pointRect.minx = p.x;
    pointRect.maxx = p.x;
    pointRect.miny = p.y;
    pointRect.maxy = p.y;

    rlinfo->range_mode = MS_QUERY_SINGLE;
    result = msRasterQueryByRect( map, layer, pointRect );
    if( rlinfo->query_results > 0 )
      return result;
  }

  /* -------------------------------------------------------------------- */
  /*      Setup a rectangle that is everything within the designated      */
  /*      range and do a search against that.                             */
  /* -------------------------------------------------------------------- */
  bufferRect.minx = p.x - buffer;
  bufferRect.maxx = p.x + buffer;
  bufferRect.miny = p.y - buffer;
  bufferRect.maxy = p.y + buffer;

  rlinfo->range_mode = mode;

  if( maxresults != 0 ) {
    const int previous_maxresults = rlinfo->query_result_hard_max;
    rlinfo->query_result_hard_max = maxresults;
    result = msRasterQueryByRect( map, layer, bufferRect );
    rlinfo->query_result_hard_max = previous_maxresults;
  } else {
    result = msRasterQueryByRect( map, layer, bufferRect );
  }

  return result;
}

/************************************************************************/
/* ==================================================================== */
/*                          RASTER Query Layer                          */
/*                                                                      */
/*      The results of a raster query are treated as a set of shapes    */
/*      that can be accessed using the normal layer semantics.          */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                         msRASTERLayerOpen()                          */
/************************************************************************/

int msRASTERLayerOpen(layerObj *layer)
{
  rasterLayerInfo *rlinfo;

  /* If we don't have info, initialize an empty one now */
  if( layer->layerinfo == NULL )
    msRasterLayerInfoInitialize( layer );

  rlinfo = (rasterLayerInfo *) layer->layerinfo;

  rlinfo->refcount = rlinfo->refcount + 1;

  return MS_SUCCESS;
}

/************************************************************************/
/*                         msRASTERIsLayerOpen()                        */
/************************************************************************/

int msRASTERLayerIsOpen(layerObj *layer)
{
  if (layer->layerinfo)
    return MS_TRUE;
  return MS_FALSE;
}


/************************************************************************/
/*                     msRASTERLayerFreeItemInfo()                      */
/************************************************************************/
void msRASTERLayerFreeItemInfo(layerObj *layer)
{}

/************************************************************************/
/*                     msRASTERLayerInitItemInfo()                      */
/*                                                                      */
/*      Perhaps we should be validating the requested items here?       */
/************************************************************************/

int msRASTERLayerInitItemInfo(layerObj *layer)
{
  return MS_SUCCESS;
}

/************************************************************************/
/*                      msRASTERLayerWhichShapes()                      */
/************************************************************************/
int msRASTERLayerWhichShapes(layerObj *layer, rectObj rect, int isQuery)

{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  rlinfo->which_rect = rect;
  rlinfo->next_shape = 0;

  return MS_SUCCESS;
}

/************************************************************************/
/*                         msRASTERLayerClose()                         */
/************************************************************************/

int msRASTERLayerClose(layerObj *layer)
{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  if( rlinfo != NULL ) {
    rlinfo->refcount--;

    if( rlinfo->refcount < 0 )
      msRasterLayerInfoFree( layer );
  }
  return MS_SUCCESS;
}

/************************************************************************/
/*                       msRASTERLayerNextShape()                       */
/************************************************************************/

int msRASTERLayerNextShape(layerObj *layer, shapeObj *shape)
{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  if( rlinfo->next_shape < 0
      || rlinfo->next_shape >= rlinfo->query_results ) {
    msFreeShape(shape);
    shape->type = MS_SHAPE_NULL;
    return MS_DONE;
  } else {
    resultObj record;

    record.shapeindex = rlinfo->next_shape++;
    record.tileindex = 0;
    record.classindex = record.resultindex = -1;

    return msRASTERLayerGetShape( layer, shape, &record);
  }
}

/************************************************************************/
/*                       msRASTERLayerGetShape()                        */
/************************************************************************/

int msRASTERLayerGetShape(layerObj *layer, shapeObj *shape, resultObj *record)
{
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;
  int i;

  long shapeindex = record->shapeindex;

  msFreeShape(shape);
  shape->type = MS_SHAPE_NULL;

  /* -------------------------------------------------------------------- */
  /*      Validate requested record id.                                   */
  /* -------------------------------------------------------------------- */
  if( shapeindex < 0 || shapeindex >= rlinfo->query_results ) {
    msSetError(MS_MISCERR,
               "Out of range shape index requested.  Requested %ld\n"
               "but only %d shapes available.",
               "msRASTERLayerGetShape()",
               shapeindex, rlinfo->query_results );
    return MS_FAILURE;
  }

  /* -------------------------------------------------------------------- */
  /*      Apply the geometry.                                             */
  /* -------------------------------------------------------------------- */
  if( rlinfo->qc_x != NULL ) {
    lineObj line;
    pointObj point;

    shape->type = MS_SHAPE_POINT;

    line.numpoints = 1;
    line.point = &point;

    point.x = rlinfo->qc_x[shapeindex];
    point.y = rlinfo->qc_y[shapeindex];
#ifdef USE_POINT_Z_M
    point.m = 0.0;
#endif

    msAddLine( shape, &line );
    msComputeBounds( shape );
  }

  /* -------------------------------------------------------------------- */
  /*      Apply the requested items.                                      */
  /* -------------------------------------------------------------------- */
  if( layer->numitems > 0 ) {
    shape->values = (char **) msSmallMalloc(sizeof(char *) * layer->numitems);
    shape->numvalues = layer->numitems;

    for( i = 0; i < layer->numitems; i++ ) {
      const size_t bufferSize = 1000;
      char szWork[1000];

      szWork[0] = '\0';
      if( EQUAL(layer->items[i],"x") && rlinfo->qc_x_reproj )
        snprintf( szWork, bufferSize, "%.8g", rlinfo->qc_x_reproj[shapeindex] );
      else if( EQUAL(layer->items[i],"y") && rlinfo->qc_y_reproj )
        snprintf( szWork, bufferSize, "%.8g", rlinfo->qc_y_reproj[shapeindex] );

      else if( EQUAL(layer->items[i],"value_list") && rlinfo->qc_values ) {
        int iValue;

        for( iValue = 0; iValue < rlinfo->band_count; iValue++ ) {
          if( iValue != 0 )
            strlcat( szWork, ",", bufferSize);

          snprintf( szWork+strlen(szWork), bufferSize-strlen(szWork), "%.8g",
                    rlinfo->qc_values[shapeindex * rlinfo->band_count
                                      + iValue] );
        }
      } else if( EQUALN(layer->items[i],"value_",6) && rlinfo->qc_values ) {
        int iValue = atoi(layer->items[i]+6);
        snprintf( szWork, bufferSize, "%.8g",
                  rlinfo->qc_values[shapeindex*rlinfo->band_count+iValue] );
      } else if( EQUAL(layer->items[i],"class") && rlinfo->qc_class ) {
        int p_class = rlinfo->qc_class[shapeindex];
        if( layer->class[p_class]->name != NULL )
          snprintf( szWork, bufferSize, "%.999s", layer->class[p_class]->name );
        else
          snprintf( szWork, bufferSize, "%d", p_class );
      } else if( EQUAL(layer->items[i],"red") && rlinfo->qc_red )
        snprintf( szWork, bufferSize, "%d", rlinfo->qc_red[shapeindex] );
      else if( EQUAL(layer->items[i],"green") && rlinfo->qc_green )
        snprintf( szWork, bufferSize, "%d", rlinfo->qc_green[shapeindex] );
      else if( EQUAL(layer->items[i],"blue") && rlinfo->qc_blue )
        snprintf( szWork, bufferSize, "%d", rlinfo->qc_blue[shapeindex] );
      else if( EQUAL(layer->items[i],"count") && rlinfo->qc_count )
        snprintf( szWork, bufferSize, "%d", rlinfo->qc_count[shapeindex] );

      shape->values[i] = msStrdup(szWork);
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Eventually we should likey apply the geometry properly but      */
  /*      we don't really care about the geometry for query purposes.     */
  /* -------------------------------------------------------------------- */

  return MS_SUCCESS;
}

/************************************************************************/
/*                       msRASTERLayerGetItems()                        */
/************************************************************************/

int msRASTERLayerGetItems(layerObj *layer)
{
  int maxnumitems = 0;
  rasterLayerInfo *rlinfo = (rasterLayerInfo *) layer->layerinfo;

  if( rlinfo == NULL )
    return MS_FAILURE;

  maxnumitems = 8 + (rlinfo->qc_values?rlinfo->band_count:0);
  layer->items = (char **) msSmallCalloc(sizeof(char *),maxnumitems);

  layer->numitems = 0;
  if( rlinfo->qc_x_reproj )
    layer->items[layer->numitems++] = msStrdup("x");
  if( rlinfo->qc_y_reproj )
    layer->items[layer->numitems++] = msStrdup("y");
  if( rlinfo->qc_values ) {
    int i;
    for( i = 0; i < rlinfo->band_count; i++ ) {
      char szName[100];
      snprintf( szName, sizeof(szName), "value_%d", i );
      layer->items[layer->numitems++] = msStrdup(szName);
    }
    layer->items[layer->numitems++] = msStrdup("value_list");
  }
  if( rlinfo->qc_class )
    layer->items[layer->numitems++] = msStrdup("class");
  if( rlinfo->qc_red )
    layer->items[layer->numitems++] = msStrdup("red");
  if( rlinfo->qc_green )
    layer->items[layer->numitems++] = msStrdup("green");
  if( rlinfo->qc_blue )
    layer->items[layer->numitems++] = msStrdup("blue");
  if( rlinfo->qc_count )
    layer->items[layer->numitems++] = msStrdup("count");

  assert(layer->numitems <= maxnumitems);

  return msRASTERLayerInitItemInfo(layer);
}

/************************************************************************/
/*                       msRASTERLayerGetExtent()                       */
/************************************************************************/

int msRASTERLayerGetExtent(layerObj *layer, rectObj *extent)

{
  char szPath[MS_MAXPATHLEN];
  mapObj *map = layer->map;
  shapefileObj *tileshpfile;
  int tilelayerindex = -1;

  if( (!layer->data || strlen(layer->data) == 0)
      && layer->tileindex == NULL) {
    /* should we be issuing a specific error about not supporting
       extents for tileindexed raster layers? */
    return MS_FAILURE;
  }

  if( map == NULL )
    return MS_FAILURE;

  /* If the layer use a tileindex, return the extent of the tileindex shapefile/referenced layer */
  if (layer->tileindex) {
    tilelayerindex = msGetLayerIndex(map, layer->tileindex);
    if(tilelayerindex != -1) /* does the tileindex reference another layer */
      return msLayerGetExtent(GET_LAYER(map, tilelayerindex), extent);
    else {
      tileshpfile = (shapefileObj *) malloc(sizeof(shapefileObj));
      MS_CHECK_ALLOC(tileshpfile, sizeof(shapefileObj), MS_FAILURE);

      if(msShapefileOpen(tileshpfile, "rb", msBuildPath3(szPath, map->mappath, map->shapepath, layer->tileindex), MS_TRUE) == -1)
        if(msShapefileOpen(tileshpfile, "rb", msBuildPath(szPath, map->mappath, layer->tileindex), MS_TRUE) == -1)
          return MS_FAILURE;

      *extent = tileshpfile->bounds;
      msShapefileClose(tileshpfile);
      free(tileshpfile);
      return MS_SUCCESS;
    }
  }

  msTryBuildPath3(szPath, map->mappath, map->shapepath, layer->data);
  char* decrypted_path = msDecryptStringTokens( map, szPath );
  if( !decrypted_path )
      return MS_FAILURE;

  char** connectionoptions = msGetStringListFromHashTable(&(layer->connectionoptions));
  GDALDatasetH hDS = GDALOpenEx(decrypted_path,
                                GDAL_OF_RASTER,
                                NULL,
                                (const char* const*)connectionoptions,
                                NULL);
  CSLDestroy(connectionoptions);
  msFree( decrypted_path );
  if( hDS == NULL ) {
    return MS_FAILURE;
  }

  const int nXSize = GDALGetRasterXSize( hDS );
  const int nYSize = GDALGetRasterYSize( hDS );
  double adfGeoTransform[6] = {0};
  const CPLErr eErr = GDALGetGeoTransform( hDS, adfGeoTransform );
  if( eErr != CE_None ) {
    GDALClose( hDS );
    return MS_FAILURE;
  }

  /* If this appears to be an ungeoreferenced raster than flip it for
     mapservers purposes. */
  if( adfGeoTransform[5] == 1.0 && adfGeoTransform[3] == 0.0 ) {
    adfGeoTransform[5] = -1.0;
    adfGeoTransform[3] = nYSize;
  }

  extent->minx = adfGeoTransform[0];
  extent->maxy = adfGeoTransform[3];

  extent->maxx = adfGeoTransform[0] + nXSize * adfGeoTransform[1];
  extent->miny = adfGeoTransform[3] + nYSize * adfGeoTransform[5];

  return MS_SUCCESS;
}


/************************************************************************/
/*                     msRASTERLayerSetTimeFilter()                     */
/*                                                                      */
/*      This function is actually just used in the context of           */
/*      setting a filter on the tileindex for time based queries.       */
/*      For instance via WMS requests.  So it isn't really related      */
/*      to the "raster query" support at all.                           */
/*                                                                      */
/*      If a local shapefile tileindex is in use, we will set a         */
/*      backtics filter (shapefile compatible).  If another layer is    */
/*      being used as the tileindex then we will forward the            */
/*      SetTimeFilter call to it.  If there is no tileindex in          */
/*      place, we do nothing.                                           */
/************************************************************************/

int msRASTERLayerSetTimeFilter(layerObj *layer, const char *timestring,
                               const char *timefield)
{
  int tilelayerindex;

  /* -------------------------------------------------------------------- */
  /*      If we don't have a tileindex the time filter has no effect.     */
  /* -------------------------------------------------------------------- */
  if( layer->tileindex == NULL )
    return MS_SUCCESS;

  /* -------------------------------------------------------------------- */
  /*      Find the tileindex layer.                                       */
  /* -------------------------------------------------------------------- */
  tilelayerindex = msGetLayerIndex(layer->map, layer->tileindex);

  /* -------------------------------------------------------------------- */
  /*      If we are using a local shapefile as our tileindex (that is     */
  /*      to say, the tileindex name is not of another layer), then we    */
  /*      just install a backtics style filter on the raster layer.       */
  /*      This is propogated to the "working layer" created for the       */
  /*      tileindex by code in mapraster.c.                               */
  /* -------------------------------------------------------------------- */
  if( tilelayerindex == -1 )
    return msLayerMakeBackticsTimeFilter( layer, timestring, timefield );

  /* -------------------------------------------------------------------- */
  /*      Otherwise we invoke the tileindex layers SetTimeFilter          */
  /*      method.                                                         */
  /* -------------------------------------------------------------------- */
  if ( msCheckParentPointer(layer->map,"map")==MS_FAILURE )
    return MS_FAILURE;
  return msLayerSetTimeFilter( layer->GET_LAYER(map,tilelayerindex),
                               timestring, timefield );
}

/************************************************************************/
/*                msRASTERLayerInitializeVirtualTable()                 */
/************************************************************************/

int
msRASTERLayerInitializeVirtualTable(layerObj *layer)
{
  assert(layer != NULL);
  assert(layer->vtable != NULL);

  layer->vtable->LayerInitItemInfo = msRASTERLayerInitItemInfo;
  layer->vtable->LayerFreeItemInfo = msRASTERLayerFreeItemInfo;
  layer->vtable->LayerOpen = msRASTERLayerOpen;
  layer->vtable->LayerIsOpen = msRASTERLayerIsOpen;
  layer->vtable->LayerWhichShapes = msRASTERLayerWhichShapes;
  layer->vtable->LayerNextShape = msRASTERLayerNextShape;
  layer->vtable->LayerGetShape = msRASTERLayerGetShape;
  /* layer->vtable->LayerGetShapeCount, use default */
  layer->vtable->LayerClose = msRASTERLayerClose;
  layer->vtable->LayerGetItems = msRASTERLayerGetItems;
  layer->vtable->LayerGetExtent = msRASTERLayerGetExtent;
  /* layer->vtable->LayerGetAutoStyle, use default */
  /* layer->vtable->LayerApplyFilterToLayer, use default */
  /* layer->vtable->LayerCloseConnection = msRASTERLayerClose; */
  /* we use backtics for proper tileindex shapefile functioning */
  layer->vtable->LayerSetTimeFilter = msRASTERLayerSetTimeFilter;
  /* layer->vtable->LayerCreateItems, use default */
  /* layer->vtable->LayerGetNumFeatures, use default */

  return MS_SUCCESS;
}

