Beispiel #1
0
mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_request_get_tile *req_tile)
{
  int expires = 0;
  mapcache_http_response *response;
  int i,is_empty=1 /* response image is initially empty */;
  char *timestr;
  mapcache_image *base=NULL;
  mapcache_image_format *format = NULL;

#ifdef DEBUG
  if(req_tile->ntiles ==0) {
    ctx->set_error(ctx,500,"BUG: get_tile called with 0 tiles");
    return NULL;
  }
#endif
  response = mapcache_http_response_create(ctx->pool);


  mapcache_prefetch_tiles(ctx,req_tile->tiles,req_tile->ntiles);
  if(GC_HAS_ERROR(ctx))
    return NULL;

  /* loop through tiles, and eventually merge them vertically together */
  for(i=0; i<req_tile->ntiles; i++) {
    mapcache_tile *tile = req_tile->tiles[i]; /* shortcut */
    if(tile->mtime && (tile->mtime < response->mtime || response->mtime == 0))
      response->mtime = tile->mtime;
    if(tile->expires && (tile->expires < expires || expires == 0)) {
      expires = tile->expires;
    }
    
    if(tile->nodata) {
      /* treat the special case where the cache explicitely stated that the
       tile was empty, and we don't have any vertical merging to do */
      if(tile->encoded_data && req_tile->ntiles == 1) {
        response->data = tile->encoded_data;
        /* we don't touch is_empty, as we have access to the encoded empty image, but the
         resulting tile is empty */
      }
      continue;
    }
    
    /* treat the most common case: 
     - we have a single tile request (i.e. isempty is true)
     - the cache returned the encoded image
     */
    if(is_empty && tile->encoded_data) {
      response->data = tile->encoded_data;
      /* just in case we also have the raw image data available, keep a ref to it
       if we need to merge another tile ontop of it*/
      if(tile->raw_image) {
        base = tile->raw_image;
      }
      is_empty = 0; /* we now know we might need to do some vertical merging */
      continue;
    }

    /* if we're here, either
     * - we need to merge the current tile onto the previous one(s), or
     * - we only have the tile's raw data available
     */

    if(!is_empty) {
      /* we have an existing tile, so we know we need to merge the current one into it */
      if(!base) {
        /* the existing tile has not been decoded yet, but we need the access to the raw pixels*/
        base = mapcache_imageio_decode(ctx, response->data);
        if(!base) return NULL;
      }
      response->data = NULL; /* the encoded data is now obsolete, as we will be merging the current tile */

      /* we need to access the current tile's pixel data */
      if(!tile->raw_image) {
        tile->raw_image = mapcache_imageio_decode(ctx,tile->encoded_data);
        if(!tile->raw_image) return NULL;
      }
      mapcache_image_merge(ctx, base, tile->raw_image);
    } else {
      /* we don't need to merge onto an existing tile and don't have access to the tile's encoded data.
       * 
       * we don't encode the tile's raw image data just yet because we might need to merge another one on top
       * of it later.
       */
      base = tile->raw_image;
      is_empty = 0;
    }
  }

  if(!response->data) {
    /* we need to encode the raw image data*/
    if(base) {
      if(req_tile->format) {
        format = req_tile->format;
      } else {
        format = req_tile->tiles[0]->tileset->format;
        if(!format) {
          format = ctx->config->default_image_format; /* this one is always defined */
        }
      }
      response->data = format->write(ctx, base, format);
      if(GC_HAS_ERROR(ctx)) {
        return NULL;
      }
    } else {
#ifdef DEBUG
      if(!is_empty) {
        ctx->set_error(ctx,500,"BUG: no image data to encode, but tile not marked as empty");
        return NULL;
      }
#endif
      unsigned char empty[5] = {'#',0,0,0,0};
      response->data = mapcache_empty_png_decode(ctx,empty,&is_empty); /* is_empty is unchanged and left to 1 */
      format = mapcache_configuration_get_image_format(ctx->config,"PNG8");
    }
  }
  
  /* compute the content-type */
  mapcache_image_format_type t = mapcache_imageio_header_sniff(ctx,response->data);
  if(t == GC_PNG)
    apr_table_set(response->headers,"Content-Type","image/png");
  else if(t == GC_JPEG)
    apr_table_set(response->headers,"Content-Type","image/jpeg");

  /* compute expiry headers */
  if(expires) {
    apr_time_t now = apr_time_now();
    apr_time_t additional = apr_time_from_sec(expires);
    apr_time_t texpires = now + additional;
    apr_table_set(response->headers, "Cache-Control",apr_psprintf(ctx->pool, "max-age=%d", expires));
    timestr = apr_palloc(ctx->pool, APR_RFC822_DATE_LEN);
    apr_rfc822_date(timestr, texpires);
    apr_table_setn(response->headers, "Expires", timestr);
  }

  return response;
}
Beispiel #2
0
void parseFormat(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
{
  char *name = NULL,  *type = NULL;
  mapcache_image_format *format = NULL;
  ezxml_t cur_node;
  name = (char*)ezxml_attr(node,"name");
  type = (char*)ezxml_attr(node,"type");
  if(!name || !strlen(name)) {
    ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <format>");
    return;
  }
  name = apr_pstrdup(ctx->pool, name);
  if(!type || !strlen(type)) {
    ctx->set_error(ctx, 400, "mandatory attribute \"type\" not found in <format>");
    return;
  }
  if(!strcmp(type,"PNG")) {
    int colors = -1;
    mapcache_compression_type compression = MAPCACHE_COMPRESSION_DEFAULT;
    if ((cur_node = ezxml_child(node,"compression")) != NULL) {
      if(!strcmp(cur_node->txt, "fast")) {
        compression = MAPCACHE_COMPRESSION_FAST;
      } else if(!strcmp(cur_node->txt, "best")) {
        compression = MAPCACHE_COMPRESSION_BEST;
      } else if(!strcmp(cur_node->txt, "none")) {
        compression = MAPCACHE_COMPRESSION_DISABLE;
      } else {
        ctx->set_error(ctx, 400, "unknown compression type %s for format \"%s\"", cur_node->txt, name);
        return;
      }
    }
    if ((cur_node = ezxml_child(node,"colors")) != NULL) {
      char *endptr;
      colors = (int)strtol(cur_node->txt,&endptr,10);
      if(*endptr != 0 || colors < 2 || colors > 256) {
        ctx->set_error(ctx, 400, "failed to parse colors \"%s\" for format \"%s\""
                       "(expecting an  integer between 2 and 256 "
                       "eg <colors>256</colors>",
                       cur_node->txt,name);
        return;
      }
    }

    if(colors == -1) {
      format = mapcache_imageio_create_png_format(ctx->pool,
               name,compression);
    } else {
      format = mapcache_imageio_create_png_q_format(ctx->pool,
               name,compression, colors);
    }
  } else if(!strcmp(type,"JPEG")) {
    int quality = 95;
    mapcache_photometric photometric = MAPCACHE_PHOTOMETRIC_YCBCR;
    if ((cur_node = ezxml_child(node,"quality")) != NULL) {
      char *endptr;
      quality = (int)strtol(cur_node->txt,&endptr,10);
      if(*endptr != 0 || quality < 1 || quality > 100) {
        ctx->set_error(ctx, 400, "failed to parse quality \"%s\" for format \"%s\""
                       "(expecting an  integer between 1 and 100 "
                       "eg <quality>90</quality>",
                       cur_node->txt,name);
        return;
      }
    }
    if ((cur_node = ezxml_child(node,"photometric")) != NULL) {
      if(!strcasecmp(cur_node->txt,"RGB"))
        photometric = MAPCACHE_PHOTOMETRIC_RGB;
      else if(!strcasecmp(cur_node->txt,"YCBCR"))
        photometric = MAPCACHE_PHOTOMETRIC_YCBCR;
      else {
        ctx->set_error(ctx,500,"failed to parse jpeg format %s photometric %s. expecting rgb or ycbcr",
                       name,cur_node->txt);
        return;
      }
    }
    format = mapcache_imageio_create_jpeg_format(ctx->pool,
             name,quality,photometric);
  } else if(!strcasecmp(type,"MIXED")) {
    mapcache_image_format *transparent=NULL, *opaque=NULL;
    unsigned int alpha_cutoff=255;
    if ((cur_node = ezxml_child(node,"transparent")) != NULL) {
      transparent = mapcache_configuration_get_image_format(config,cur_node->txt);
    }
    if(!transparent) {
      ctx->set_error(ctx,400, "mixed format %s references unknown transparent format %s"
                     "(order is important, format %s should appear first)",
                     name,cur_node->txt,cur_node->txt);
      return;
    }
    if ((cur_node = ezxml_child(node,"opaque")) != NULL) {
      opaque = mapcache_configuration_get_image_format(config,cur_node->txt);
    }
    if(!opaque) {
      ctx->set_error(ctx,400, "mixed format %s references unknown opaque format %s"
                     "(order is important, format %s should appear first)",
                     name,cur_node->txt,cur_node->txt);
      return;
    }
    if ((cur_node = ezxml_child(node,"alpha_cutoff")) != NULL) {
      alpha_cutoff = atoi(cur_node->txt);
    }
    format = mapcache_imageio_create_mixed_format(ctx->pool,name,transparent, opaque, alpha_cutoff);
  } else {
    ctx->set_error(ctx, 400, "unknown format type %s for format \"%s\"", type, name);
    return;
  }
  if(format == NULL) {
    ctx->set_error(ctx, 400, "failed to parse format \"%s\"", name);
    return;
  }


  mapcache_configuration_add_image_format(config,format,name);
  return;
}
void mapcache_configuration_parse_xml(mapcache_context *ctx, const char *filename, mapcache_cfg *config) {
   ezxml_t doc, node;
   const char *mode;
   doc = ezxml_parse_file(filename);
   if (doc == NULL) {
      ctx->set_error(ctx,400, "failed to parse file %s. Is it valid XML?", filename);
      goto cleanup;
   } else {
      const char *err = ezxml_error(doc);
      if(err && *err) {
         ctx->set_error(ctx,400, "failed to parse file %s: %s", filename, err);
         goto cleanup;
      }
   }

   if(strcmp(doc->name,"mapcache")) {
      ctx->set_error(ctx,400, "failed to parse file %s. first node is not <mapcache>", filename);
      goto cleanup;
   }
   mode = ezxml_attr(doc,"mode");
   if(mode) {
      if(!strcmp(mode,"combined_mirror")) {
         config->mode = MAPCACHE_MODE_MIRROR_COMBINED;
      } else if(!strcmp(mode,"split_mirror")) {
         config->mode = MAPCACHE_MODE_MIRROR_SPLIT;
      } else if(!strcmp(mode,"normal")) {
         config->mode = MAPCACHE_MODE_NORMAL;
      } else {
         ctx->set_error(ctx,400,"unknown mode \"%s\" for <mapcache>",mode);
         goto cleanup;
      }
   } else {
         config->mode = MAPCACHE_MODE_NORMAL;
   }

   for(node = ezxml_child(doc,"metadata"); node; node = node->next) {
      parseMetadata(ctx, node, config->metadata);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   for(node = ezxml_child(doc,"source"); node; node = node->next) {
      parseSource(ctx, node, config);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   for(node = ezxml_child(doc,"grid"); node; node = node->next) {
      parseGrid(ctx, node, config);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   for(node = ezxml_child(doc,"format"); node; node = node->next) {
      parseFormat(ctx, node, config);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   for(node = ezxml_child(doc,"cache"); node; node = node->next) {
      parseCache(ctx, node, config);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   for(node = ezxml_child(doc,"tileset"); node; node = node->next) {
      parseTileset(ctx, node, config);
      if(GC_HAS_ERROR(ctx)) goto cleanup;
   }

   if ((node = ezxml_child(doc,"service")) != NULL) {
      ezxml_t service_node;
      for(service_node = node; service_node; service_node = service_node->next) {
         char *enabled = (char*)ezxml_attr(service_node,"enabled");
         char *type = (char*)ezxml_attr(service_node,"type");
         if(!strcasecmp(enabled,"true")) {
            if (!strcasecmp(type,"wms")) {
               mapcache_service *new_service = mapcache_service_wms_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_WMS] = new_service;
            }
            else if (!strcasecmp(type,"tms")) {
               mapcache_service *new_service = mapcache_service_tms_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_TMS] = new_service;
            }
            else if (!strcasecmp(type,"wmts")) {
               mapcache_service *new_service = mapcache_service_wmts_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_WMTS] = new_service;
            }
            else if (!strcasecmp(type,"kml")) {
               mapcache_service *new_service = mapcache_service_kml_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_KML] = new_service;
            }
            else if (!strcasecmp(type,"gmaps")) {
               mapcache_service *new_service = mapcache_service_gmaps_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_GMAPS] = new_service;
            }
            else if (!strcasecmp(type,"ve")) {
               mapcache_service *new_service = mapcache_service_ve_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_VE] = new_service;
            }
            else if (!strcasecmp(type,"demo")) {
               mapcache_service *new_service = mapcache_service_demo_create(ctx);
               if(new_service->configuration_parse_xml) {
                  new_service->configuration_parse_xml(ctx,service_node,new_service,config);
               }
               config->services[MAPCACHE_SERVICE_DEMO] = new_service;
            } else {
               ctx->set_error(ctx,400,"unknown <service> type %s",type);
            }
            if(GC_HAS_ERROR(ctx)) goto cleanup;
         }
      }
   }
   else if ((node = ezxml_child(doc,"services")) != NULL) {
      ctx->log(ctx,MAPCACHE_WARN,"<services> tag is deprecated, use <service type=\"wms\" enabled=\"true|false\">");
      parseServices(ctx, node, config);
   } else {
      ctx->set_error(ctx, 400, "no <services> configured");
   }
   if(GC_HAS_ERROR(ctx)) goto cleanup;


   node = ezxml_child(doc,"default_format");
   if(!node)
      node = ezxml_child(doc,"merge_format");
   if (node) {
      mapcache_image_format *format = mapcache_configuration_get_image_format(config,node->txt);
      if(!format) {
         ctx->set_error(ctx, 400, "default_format tag references format %s but it is not configured",
               node->txt);
         goto cleanup;
      }
      config->default_image_format = format;
   }

   if ((node = ezxml_child(doc,"errors")) != NULL) {
      if(!strcmp(node->txt,"log")) {
         config->reporting = MAPCACHE_REPORT_LOG;
      } else if(!strcmp(node->txt,"report")) {
         config->reporting = MAPCACHE_REPORT_MSG;
      } else if(!strcmp(node->txt,"empty_img")) {
         config->reporting = MAPCACHE_REPORT_EMPTY_IMG;
         mapcache_image_create_empty(ctx, config);
         if(GC_HAS_ERROR(ctx)) goto cleanup;
      } else if(!strcmp(node->txt, "report_img")) {
         config->reporting = MAPCACHE_REPORT_ERROR_IMG;
         ctx->set_error(ctx,501,"<errors>: report_img not implemented");
         goto cleanup;
      } else {
         ctx->set_error(ctx,400,"<errors>: unknown value %s (allowed are log, report, empty_img, report_img)",
               node->txt);
         goto cleanup;
      }
   }

   if((node = ezxml_child(doc,"lock_dir")) != NULL) {
      config->lockdir = apr_pstrdup(ctx->pool, node->txt);
   } else {
      config->lockdir = apr_pstrdup(ctx->pool,"/tmp");
   }

   if((node = ezxml_child(doc,"lock_retry")) != NULL) {
      char *endptr;
      config->lock_retry_interval = (unsigned int)strtol(node->txt,&endptr,10);
      if(*endptr != 0 || config->lock_retry_interval < 0) {
         ctx->set_error(ctx, 400, "failed to parse lock_retry microseconds \"%s\". Expecting a positive integer",
               node->txt);
         return;
      }
   }
   
   if((node = ezxml_child(doc,"threaded_fetching")) != NULL) {
      if(!strcasecmp(node->txt,"true")) {
         config->threaded_fetching = 1;
      } else if(strcasecmp(node->txt,"false")) {
         ctx->set_error(ctx, 400, "failed to parse threaded_fetching \"%s\". Expecting true or false",node->txt);
         return;
      }
   }

   if((node = ezxml_child(doc,"log_level")) != NULL) {
      if(!strcasecmp(node->txt,"debug")) {
         config->loglevel = MAPCACHE_DEBUG;
      } else if(!strcasecmp(node->txt,"info")) {
         config->loglevel = MAPCACHE_INFO;
      } else if(!strcasecmp(node->txt,"notice")) {
         config->loglevel = MAPCACHE_NOTICE;
      } else if(!strcasecmp(node->txt,"warn")) {
         config->loglevel = MAPCACHE_WARN;
      } else if(!strcasecmp(node->txt,"error")) {
         config->loglevel = MAPCACHE_ERROR;
      } else if(!strcasecmp(node->txt,"crit")) {
         config->loglevel = MAPCACHE_CRIT;
      } else if(!strcasecmp(node->txt,"alert")) {
         config->loglevel = MAPCACHE_ALERT;
      } else if(!strcasecmp(node->txt,"emerg")) {
         config->loglevel = MAPCACHE_EMERG;
      } else {
         ctx->set_error(ctx,500,"failed to parse <log_level> \"%s\". Expecting debug, info, notice, warn, error, crit, alert or emerg",node->txt);
         return;
      }
   }
   if((node = ezxml_child(doc,"auto_reload")) != NULL) {
      if(!strcasecmp(node->txt,"true")) {
         config->autoreload = 1;
      } else if(!strcasecmp(node->txt,"false")) {
         config->autoreload = 0;
      } else {
         ctx->set_error(ctx,500,"failed to parse <auto_reload> \"%s\". Expecting true or false",node->txt);
         return;
      }
   }


cleanup:
   ezxml_free(doc);
   return;
}
Beispiel #4
0
mapcache_cfg* mapcache_configuration_create(apr_pool_t *pool)
{
  mapcache_grid *grid;
  int i;
  double wgs84_resolutions[18]= {
    0.703125000000000,
    0.351562500000000,
    0.175781250000000,
    8.78906250000000e-2,
    4.39453125000000e-2,
    2.19726562500000e-2,
    1.09863281250000e-2,
    5.49316406250000e-3,
    2.74658203125000e-3,
    1.37329101562500e-3,
    6.86645507812500e-4,
    3.43322753906250e-4,
    1.71661376953125e-4,
    8.58306884765625e-5,
    4.29153442382812e-5,
    2.14576721191406e-5,
    1.07288360595703e-5,
    5.36441802978516e-6
  };

  double google_resolutions[19] = {
    156543.0339280410,
    78271.51696402048,
    39135.75848201023,
    19567.87924100512,
    9783.939620502561,
    4891.969810251280,
    2445.984905125640,
    1222.992452562820,
    611.4962262814100,
    305.7481131407048,
    152.8740565703525,
    76.43702828517624,
    38.21851414258813,
    19.10925707129406,
    9.554628535647032,
    4.777314267823516,
    2.388657133911758,
    1.194328566955879,
    0.5971642834779395
  };




  mapcache_extent wgs84_extent= {-180,-90,180,90};
  mapcache_extent google_extent= {-20037508.3427892480,-20037508.3427892480,20037508.3427892480,20037508.3427892480};
  double unitwidth,unitheight;

  mapcache_cfg *cfg = (mapcache_cfg*)apr_pcalloc(pool, sizeof(mapcache_cfg));
  cfg->caches = apr_hash_make(pool);
  cfg->sources = apr_hash_make(pool);
  cfg->tilesets = apr_hash_make(pool);
  cfg->grids = apr_hash_make(pool);
  cfg->image_formats = apr_hash_make(pool);
  cfg->metadata = apr_table_make(pool,3);

  mapcache_configuration_add_image_format(cfg,
          mapcache_imageio_create_png_format(pool,"PNG",MAPCACHE_COMPRESSION_FAST),
          "PNG");
  mapcache_configuration_add_image_format(cfg,
          mapcache_imageio_create_png_q_format(pool,"PNG8",MAPCACHE_COMPRESSION_FAST,256),
          "PNG8");
  mapcache_configuration_add_image_format(cfg,
          mapcache_imageio_create_jpeg_format(pool,"JPEG",90,MAPCACHE_PHOTOMETRIC_YCBCR,MAPCACHE_OPTIMIZE_YES),
          "JPEG");
  mapcache_configuration_add_image_format(cfg,
          mapcache_imageio_create_mixed_format(pool,"mixed",
                    mapcache_configuration_get_image_format(cfg,"PNG"),
                    mapcache_configuration_get_image_format(cfg,"JPEG"), 255),
          "mixed");
  cfg->default_image_format = mapcache_configuration_get_image_format(cfg,"mixed");
  cfg->reporting = MAPCACHE_REPORT_MSG;

  grid = mapcache_grid_create(pool);
  grid->name = apr_pstrdup(pool,"WGS84");
  apr_table_add(grid->metadata,"title","GoogleCRS84Quad");
  apr_table_add(grid->metadata,"wellKnownScaleSet","urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad");
  apr_table_add(grid->metadata,"profile","global-geodetic");
  grid->srs = apr_pstrdup(pool,"EPSG:4326");
  grid->unit = MAPCACHE_UNIT_DEGREES;
  grid->tile_sx = grid->tile_sy = 256;
  grid->nlevels = 18;
  grid->extent = wgs84_extent;
  grid->levels = (mapcache_grid_level**)apr_pcalloc(pool,
                 grid->nlevels*sizeof(mapcache_grid_level*));
  for(i=0; i<grid->nlevels; i++) {
    mapcache_grid_level *level = (mapcache_grid_level*)apr_pcalloc(pool,sizeof(mapcache_grid_level));
    level->resolution = wgs84_resolutions[i];
    unitheight = grid->tile_sy * level->resolution;
    unitwidth = grid->tile_sx * level->resolution;

    level->maxy = ceil((grid->extent.maxy-grid->extent.miny - 0.01* unitheight)/unitheight);
    level->maxx = ceil((grid->extent.maxx-grid->extent.minx - 0.01* unitwidth)/unitwidth);
    grid->levels[i] = level;
  }
  mapcache_configuration_add_grid(cfg,grid,"WGS84");

  grid = mapcache_grid_create(pool);
  grid->name = apr_pstrdup(pool,"GoogleMapsCompatible");
  grid->srs = apr_pstrdup(pool,"EPSG:3857");
  APR_ARRAY_PUSH(grid->srs_aliases,char*) = apr_pstrdup(pool,"EPSG:900913");
  apr_table_add(grid->metadata,"title","GoogleMapsCompatible");
  apr_table_add(grid->metadata,"profile","global-mercator");
  apr_table_add(grid->metadata,"wellKnownScaleSet","urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible");
  grid->tile_sx = grid->tile_sy = 256;
  grid->nlevels = 19;
  grid->unit = MAPCACHE_UNIT_METERS;
  grid->extent = google_extent;
  grid->levels = (mapcache_grid_level**)apr_pcalloc(pool,
                 grid->nlevels*sizeof(mapcache_grid_level*));
  for(i=0; i<grid->nlevels; i++) {
    mapcache_grid_level *level = (mapcache_grid_level*)apr_pcalloc(pool,sizeof(mapcache_grid_level));
    level->resolution = google_resolutions[i];
    unitheight = grid->tile_sy * level->resolution;
    unitwidth = grid->tile_sx * level->resolution;

    level->maxy = ceil((grid->extent.maxy-grid->extent.miny - 0.01* unitheight)/unitheight);
    level->maxx = ceil((grid->extent.maxx-grid->extent.minx - 0.01* unitwidth)/unitwidth);
    grid->levels[i] = level;
  }
  mapcache_configuration_add_grid(cfg,grid,"GoogleMapsCompatible");

  grid = mapcache_grid_create(pool);
  grid->name = apr_pstrdup(pool,"g");
  grid->srs = apr_pstrdup(pool,"EPSG:900913");
  APR_ARRAY_PUSH(grid->srs_aliases,char*) = apr_pstrdup(pool,"EPSG:3857");
  apr_table_add(grid->metadata,"title","GoogleMapsCompatible");
  apr_table_add(grid->metadata,"profile","global-mercator");
  apr_table_add(grid->metadata,"wellKnownScaleSet","urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible");
  grid->tile_sx = grid->tile_sy = 256;
  grid->nlevels = 19;
  grid->unit = MAPCACHE_UNIT_METERS;
  grid->extent = google_extent;
  grid->levels = (mapcache_grid_level**)apr_pcalloc(pool,
                 grid->nlevels*sizeof(mapcache_grid_level*));
  for(i=0; i<grid->nlevels; i++) {
    mapcache_grid_level *level = (mapcache_grid_level*)apr_pcalloc(pool,sizeof(mapcache_grid_level));
    level->resolution = google_resolutions[i];
    unitheight = grid->tile_sy * level->resolution;
    unitwidth = grid->tile_sx * level->resolution;

    level->maxy = ceil((grid->extent.maxy-grid->extent.miny - 0.01* unitheight)/unitheight);
    level->maxx = ceil((grid->extent.maxx-grid->extent.minx - 0.01* unitwidth)/unitwidth);
    grid->levels[i] = level;
  }
  mapcache_configuration_add_grid(cfg,grid,"g");


  cfg->loglevel = MAPCACHE_WARN;
  cfg->autoreload = 0;

  return cfg;
}
void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config) {
   char *name = NULL;
   mapcache_tileset *tileset = NULL;
   ezxml_t cur_node;
   char* value;
   int havewgs84bbox=0;
   if(config->mode == MAPCACHE_MODE_NORMAL) {
      name = (char*)ezxml_attr(node,"name");
   } else {
      name = "mirror";
   }
   if(!name || !strlen(name)) {
      ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <tileset>");
      return;
   }
   else {
      name = apr_pstrdup(ctx->pool, name);
      /* check we don't already have a cache defined with this name */
      if(mapcache_configuration_get_tileset(config, name)) {
         ctx->set_error(ctx, 400, "duplicate tileset with name \"%s\"",name);
         return;
      }
   }
   tileset = mapcache_tileset_create(ctx);
   tileset->name = name;
   
   if ((cur_node = ezxml_child(node,"metadata")) != NULL) {
      parseMetadata(ctx, cur_node, tileset->metadata);
      GC_CHECK_ERROR(ctx);
   }
   
   
   if ((value = (char*)apr_table_get(tileset->metadata,"wgs84boundingbox")) != NULL) {
      double *values;
      int nvalues;
      value = apr_pstrdup(ctx->pool,value);
      if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, value, NULL, &values, &nvalues) ||
            nvalues != 4) {
         ctx->set_error(ctx, 400, "failed to parse extent array %s."
               "(expecting 4 space separated numbers, got %d (%f %f %f %f)"
               "eg <wgs84bbox>-180 -90 180 90</wgs84bbox>",
               value,nvalues,values[0],values[1],values[2],values[3]);
         return;
      }
      tileset->wgs84bbox[0] = values[0];
      tileset->wgs84bbox[1] = values[1];
      tileset->wgs84bbox[2] = values[2];
      tileset->wgs84bbox[3] = values[3];
      havewgs84bbox = 1;
   }

   for(cur_node = ezxml_child(node,"grid"); cur_node; cur_node = cur_node->next) {
      int i;
      mapcache_grid *grid;
      mapcache_grid_link *gridlink;
      char *restrictedExtent = NULL, *sTolerance = NULL;
      double *extent;
      int tolerance;

      if (tileset->grid_links == NULL) {
         tileset->grid_links = apr_array_make(ctx->pool,1,sizeof(mapcache_grid_link*));
      }
      grid = mapcache_configuration_get_grid(config, cur_node->txt);
      if(!grid) {
         ctx->set_error(ctx, 400, "tileset \"%s\" references grid \"%s\","
               " but it is not configured", name, cur_node->txt);
         return;
      }
      gridlink = apr_pcalloc(ctx->pool,sizeof(mapcache_grid_link));
      gridlink->grid = grid;
      gridlink->minz = 0;
      gridlink->maxz = grid->nlevels;
      gridlink->grid_limits = apr_pcalloc(ctx->pool,grid->nlevels*sizeof(int*));
      for(i=0;i<grid->nlevels;i++) {
         gridlink->grid_limits[i] = apr_pcalloc(ctx->pool,4*sizeof(int));
      }

      restrictedExtent = (char*)ezxml_attr(cur_node,"restricted_extent");
      if(restrictedExtent) {
         int nvalues;
         restrictedExtent = apr_pstrdup(ctx->pool,restrictedExtent);
         if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, restrictedExtent, NULL, &gridlink->restricted_extent, &nvalues) ||
               nvalues != 4) {
            ctx->set_error(ctx, 400, "failed to parse extent array %s."
                  "(expecting 4 space separated numbers, "
                  "eg <grid restricted_extent=\"-180 -90 180 90\">foo</grid>",
                  restrictedExtent);
            return;
         }
         extent = gridlink->restricted_extent;
      } else {
         extent = grid->extent;
      }

      tolerance = 5;
      sTolerance = (char*)ezxml_attr(cur_node,"tolerance");
      if(sTolerance) {
         char *endptr;
         tolerance = (int)strtol(sTolerance,&endptr,10);
         if(*endptr != 0 || tolerance < 0) {
            ctx->set_error(ctx, 400, "failed to parse grid tolerance %s (expecting a positive integer)",
                  sTolerance);  
            return;
         }
      }
      

      mapcache_grid_compute_limits(grid,extent,gridlink->grid_limits,tolerance);
      
      sTolerance = (char*)ezxml_attr(cur_node,"minzoom");
      if(sTolerance) {
         char *endptr;
         tolerance = (int)strtol(sTolerance,&endptr,10);
         if(*endptr != 0 || tolerance < 0) {
            ctx->set_error(ctx, 400, "failed to parse grid minzoom %s (expecting a positive integer)",
                  sTolerance);  
            return;
         }
         gridlink->minz = tolerance;
      }
      
      sTolerance = (char*)ezxml_attr(cur_node,"maxzoom");
      if(sTolerance) {
         char *endptr;
         tolerance = (int)strtol(sTolerance,&endptr,10);
         if(*endptr != 0 || tolerance < 0) {
            ctx->set_error(ctx, 400, "failed to parse grid maxzoom %s (expecting a positive integer)",
                  sTolerance);  
            return;
         }
         gridlink->maxz = tolerance + 1;
      }

      if(gridlink->minz<0 || gridlink->maxz>grid->nlevels || gridlink->minz>=gridlink->maxz) {
            ctx->set_error(ctx, 400, "invalid grid maxzoom/minzoom %d/%d", gridlink->minz,gridlink->maxz);
            return;
      }

      
      
      /* compute wgs84 bbox if it wasn't supplied already */
      if(!havewgs84bbox && !strcasecmp(grid->srs,"EPSG:4326")) {
         tileset->wgs84bbox[0] = extent[0];
         tileset->wgs84bbox[1] = extent[1];
         tileset->wgs84bbox[2] = extent[2];
         tileset->wgs84bbox[3] = extent[3];
      }
      APR_ARRAY_PUSH(tileset->grid_links,mapcache_grid_link*) = gridlink;
   }

   if ((cur_node = ezxml_child(node,"dimensions")) != NULL) {
      parseDimensions(ctx, cur_node, tileset);
      GC_CHECK_ERROR(ctx);
   }


   if ((cur_node = ezxml_child(node,"cache")) != NULL) {
         mapcache_cache *cache = mapcache_configuration_get_cache(config, cur_node->txt);
         if(!cache) {
            ctx->set_error(ctx, 400, "tileset \"%s\" references cache \"%s\","
                  " but it is not configured", name, cur_node->txt);
            return;
         }
         tileset->cache = cache;
   }

   if ((cur_node = ezxml_child(node,"source")) != NULL) {
         mapcache_source *source = mapcache_configuration_get_source(config, cur_node->txt);
         if(!source) {
            ctx->set_error(ctx, 400, "tileset \"%s\" references source \"%s\","
                  " but it is not configured", name, cur_node->txt);
            return;
         }
         tileset->source = source;
   }

   if ((cur_node = ezxml_child(node,"metatile")) != NULL) {
     int *values, nvalues;
      value = apr_pstrdup(ctx->pool,cur_node->txt);
         
         if(MAPCACHE_SUCCESS != mapcache_util_extract_int_list(ctx, cur_node->txt, NULL,
                  &values, &nvalues) ||
               nvalues != 2) {
            ctx->set_error(ctx, 400, "failed to parse metatile dimension %s."
                  "(expecting 2 space separated integers, "
                  "eg <metatile>5 5</metatile>",
                  cur_node->txt);
            return;
         }
         tileset->metasize_x = values[0];
         tileset->metasize_y = values[1];
   }
      

   if ((cur_node = ezxml_child(node,"watermark")) != NULL) {
         if(!*cur_node->txt) {
             ctx->set_error(ctx,400, "watermark config entry empty");
             return;
         }
         mapcache_tileset_add_watermark(ctx,tileset,cur_node->txt);
         GC_CHECK_ERROR(ctx);
   }
      

   if ((cur_node = ezxml_child(node,"expires")) != NULL) {
         char *endptr;
         tileset->expires = (int)strtol(cur_node->txt,&endptr,10);
         if(*endptr != 0) {
            ctx->set_error(ctx, 400, "failed to parse expires %s."
                  "(expecting an  integer, "
                  "eg <expires>3600</expires>",
                  cur_node->txt);  
            return;
         }
   }
   if ((cur_node = ezxml_child(node,"auto_expire")) != NULL) {
      char *endptr;
      tileset->auto_expire = (int)strtol(cur_node->txt,&endptr,10);
      if(*endptr != 0) {
         ctx->set_error(ctx, 400, "failed to parse auto_expire %s."
               "(expecting an  integer, "
               "eg <auto_expire>3600</auto_expire>",
               cur_node->txt);  
         return;
      }
   }

   if ((cur_node = ezxml_child(node,"metabuffer")) != NULL) {
         char *endptr;
         tileset->metabuffer = (int)strtol(cur_node->txt,&endptr,10);
         if(*endptr != 0) {
            ctx->set_error(ctx, 400, "failed to parse metabuffer %s."
                  "(expecting an  integer, "
                  "eg <metabuffer>1</metabuffer>",
                  cur_node->txt);
            return;
         }
   }
      
   if ((cur_node = ezxml_child(node,"format")) != NULL) {
         mapcache_image_format *format = mapcache_configuration_get_image_format(config,cur_node->txt);
         if(!format) {
            ctx->set_error(ctx, 400, "tileset \"%s\" references format \"%s\","
                  " but it is not configured",name,cur_node->txt);
            return;
         }
         tileset->format = format;
   }

   mapcache_tileset_configuration_check(ctx,tileset);
   GC_CHECK_ERROR(ctx);
   mapcache_configuration_add_tileset(config,tileset,name);
   return;
}