Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: b8e6e0cbe0
Fetching contributors…

Cannot retrieve contributors at this time

529 lines (440 sloc) 17.173 kB
/******************************************************************************
* $Id$
*
* Project: MapServer
* Purpose: MapServer Tile Access API
* Author: Paul Ramsey <pramsey@cleverelephant.ca>
*
******************************************************************************
* Copyright (c) 2008, Paul Ramsey
*
* 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 "maptile.h"
#include "mapproject.h"
#ifdef USE_TILE_API
static void msTileResetMetatileLevel(mapObj *map)
{
hashTableObj *meta = &(map->web.metadata);
const char *zero = "0";
const char *value = NULL;
/* Is the tile_metatile_levetl set... */
if((value = msLookupHashTable(meta, "tile_metatile_level")) != NULL) {
msRemoveHashTable(meta, "tile_metatile_level");
msInsertHashTable(meta, "tile_metatile_level", zero);
}
/* No tile_metatile_level value. */
else {
msInsertHashTable(meta, "tile_metatile_level", zero);
}
}
#endif
/************************************************************************
* msTileGetGMapCoords *
************************************************************************/
static int msTileGetGMapCoords(const char *coordstring, int *x, int *y, int *zoom)
{
int num_coords = 0;
char **coords = NULL;
if( coordstring ) {
coords = msStringSplit(coordstring, ' ', &(num_coords));
if( num_coords != 3 ) {
msSetError(MS_WEBERR, "Invalid number of tile coordinates (should be three).", "msTileSetup()");
return MS_FAILURE;
}
} else {
msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
return MS_FAILURE;
}
if( x )
*x = strtol(coords[0], NULL, 10);
if( y )
*y = strtol(coords[1], NULL, 10);
if( zoom )
*zoom = strtol(coords[2], NULL, 10);
return MS_SUCCESS;
}
/************************************************************************
* msTileSetParams *
************************************************************************/
static void msTileGetParams(mapObj *map, tileParams *params)
{
const char *value = NULL;
hashTableObj *meta = &(map->web.metadata);
params->tile_size = SPHEREMERC_IMAGE_SIZE;
/* Check for tile buffer, set to buffer==0 as default */
if((value = msLookupHashTable(meta, "tile_map_edge_buffer")) != NULL) {
params->map_edge_buffer = atoi(value);
if(map->debug)
msDebug("msTileSetParams(): tile_map_edge_buffer = %d\n", params->map_edge_buffer);
} else
params->map_edge_buffer = 0;
/* Check for metatile size, set to tile==metatile as default */
if((value = msLookupHashTable(meta, "tile_metatile_level")) != NULL) {
params->metatile_level = atoi(value);
/* Quietly force metatile_level to be sane */
if( params->metatile_level < 0 )
params->metatile_level = 0;
if( params->metatile_level > 2 )
params->metatile_level = 2;
if(map->debug)
msDebug("msTileSetParams(): tile_metatile_level = %d\n", params->metatile_level);
} else
params->metatile_level = 0;
}
/************************************************************************
* msTileExtractSubTile *
* *
************************************************************************/
static imageObj* msTileExtractSubTile(const mapservObj *msObj, const imageObj *img)
{
int width, mini, minj;
int zoom = 2;
imageObj* imgOut = NULL;
tileParams params;
rendererVTableObj *renderer;
rasterBufferObj imgBuffer;
if( !MS_RENDERER_PLUGIN(msObj->map->outputformat)
|| msObj->map->outputformat->renderer != img->format->renderer ||
! MS_MAP_RENDERER(msObj->map)->supports_pixel_buffer ) {
msSetError(MS_MISCERR,"unsupported or mixed renderers","msTileExtractSubTile()");
return NULL;
}
renderer = MS_MAP_RENDERER(msObj->map);
if (renderer->getRasterBufferHandle((imageObj*)img,&imgBuffer) != MS_SUCCESS) {
return NULL;
}
/*
** Load the metatiling information from the map file.
*/
msTileGetParams(msObj->map, &params);
/*
** Initialize values for the metatile clip area.
*/
width = img->width - 2*params.map_edge_buffer;
mini = params.map_edge_buffer;
minj = params.map_edge_buffer;
if( msObj->TileMode == TILE_GMAP ) {
int x, y, zoom;
if( msObj->TileCoords ) {
if( msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE )
return NULL;
} else {
msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
return NULL;
}
if(msObj->map->debug)
msDebug("msTileExtractSubTile(): gmaps coords (x: %d, y: %d)\n",x,y);
/*
** The bottom N bits of the coordinates give us the subtile
** location relative to the metatile.
*/
x = (0xffff ^ (0xffff << params.metatile_level)) & x;
y = (0xffff ^ (0xffff << params.metatile_level)) & y;
if(msObj->map->debug)
msDebug("msTileExtractSubTile(): gmaps image coords (x: %d, y: %d)\n",x,y);
mini = mini + x * params.tile_size;
minj = minj + y * params.tile_size;
} else if( msObj->TileMode == TILE_VE ) {
int tsize;
int i = 0;
char j = 0;
if( (int)strlen( msObj->TileCoords ) - params.metatile_level < 0 ) {
return(NULL);
}
/*
** Process the last elements of the VE coordinate string to place the
** requested tile in the context of the metatile
*/
for( i = strlen( msObj->TileCoords ) - params.metatile_level;
i < strlen( msObj->TileCoords );
i++ ) {
j = msObj->TileCoords[i];
tsize = width / zoom;
if( j == '1' || j == '3' ) mini += tsize;
if( j == '2' || j == '3' ) minj += tsize;
zoom *= 2;
}
} else {
return(NULL); /* Huh? Should have a mode. */
}
imgOut = msImageCreate(params.tile_size, params.tile_size, msObj->map->outputformat, NULL, NULL, msObj->map->resolution, msObj->map->defresolution, NULL);
if( imgOut == NULL ) {
return NULL;
}
if(msObj->map->debug)
msDebug("msTileExtractSubTile(): extracting (%d x %d) tile, top corner (%d, %d)\n",params.tile_size,params.tile_size,mini,minj);
renderer->mergeRasterBuffer(imgOut,&imgBuffer,1.0,mini, minj,0, 0,params.tile_size, params.tile_size);
return imgOut;
}
/************************************************************************
* msTileSetup *
* *
* Called from mapserv.c, this is where the fun begins *
* Set up projections and test the parameters for legality. *
************************************************************************/
int msTileSetup(mapservObj* msObj)
{
#ifdef USE_TILE_API
char *outProjStr = NULL;
tileParams params;
/*
** Load the metatiling information from the map file.
*/
msTileGetParams(msObj->map, &params);
/*
** Ensure all the LAYERs have a projection.
*/
if( msMapSetLayerProjections(msObj->map) != 0 ) {
return(MS_FAILURE);
}
/*
** Set the projection string for this mode.
*/
if( msObj->TileMode == TILE_GMAP || msObj->TileMode == TILE_VE ) {
outProjStr = SPHEREMERC_PROJ4;
} else {
return MS_FAILURE; /* Huh? No mode? */
}
if( msLoadProjectionString(&(msObj->map->projection), outProjStr) != 0 ) {
msSetError(MS_CGIERR, "Unable to load projection string.", "msTileSetup()");
return MS_FAILURE;
}
/*
** Set up the output extents for this tilemode and tile coordinates
*/
if( msObj->TileMode == TILE_GMAP ) {
int x, y, zoom;
double zoomfactor;
if( msObj->TileCoords ) {
if( msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE )
return MS_FAILURE;
} else {
msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
return MS_FAILURE;
}
if( params.metatile_level >= zoom ) {
msTileResetMetatileLevel(msObj->map);
}
zoomfactor = pow(2.0, (double)zoom);
/*
** Check the input request for sanity.
*/
if( x >= zoomfactor || y >= zoomfactor ) {
msSetError(MS_CGIERR, "GMap tile coordinates are too large for supplied zoom.", "msTileSetup()");
return(MS_FAILURE);
}
if( x < 0 || y < 0 ) {
msSetError(MS_CGIERR, "GMap tile coordinates should not be less than zero.", "msTileSetup()");
return(MS_FAILURE);
}
} else if ( msObj->TileMode == TILE_VE ) {
if( strspn( msObj->TileCoords, "0123" ) < strlen( msObj->TileCoords ) ) {
msSetError(MS_CGIERR, "VE tile name should only include characters 0, 1, 2 and 3.", "msTileSetup()");
return(MS_FAILURE);
}
if( params.metatile_level >= strlen(msObj->TileCoords) ) {
msTileResetMetatileLevel(msObj->map);
}
} else {
return(MS_FAILURE); /* Huh? Should have a mode. */
}
return MS_SUCCESS;
#else
msSetError(MS_CGIERR, "Tile API is not available.", "msTileSetup()");
return(MS_FAILURE);
#endif
}
/************************************************************************
* msTileSetExtent *
* *
* Based on the input parameters, set the output extent for this *
* tile. *
************************************************************************/
int msTileSetExtent(mapservObj* msObj)
{
#ifdef USE_TILE_API
mapObj *map = msObj->map;
double dx, dy, buffer;
tileParams params;
/* Read the tile-mode map file parameters */
msTileGetParams(msObj->map, &params);
if( msObj->TileMode == TILE_GMAP ) {
int x, y, zoom;
double zoomfactor, tilesize, xmin, xmax, ymin, ymax;
if( msObj->TileCoords ) {
if( msTileGetGMapCoords(msObj->TileCoords, &x, &y, &zoom) == MS_FAILURE )
return MS_FAILURE;
} else {
msSetError(MS_WEBERR, "Tile parameter not set.", "msTileSetup()");
return MS_FAILURE;
}
if(map->debug)
msDebug("msTileSetExtent(): gmaps coords (x: %d, y: %d, z: %d)\n",x,y,zoom);
/*
** If we are metatiling, adjust the zoom level appropriately,
** then scale back the x/y coordinates to match the new level.
*/
if( params.metatile_level > 0 ) {
zoom = zoom - params.metatile_level;
x = x >> params.metatile_level;
y = y >> params.metatile_level;
}
if(map->debug)
msDebug("msTileSetExtent(): gmaps metacoords (x: %d, y: %d, z: %d)\n",x,y,zoom);
zoomfactor = pow(2.0, (double)zoom);
/*
** Calculate the ground extents of the tile request.
*/
/* printf("X: %i Y: %i Z: %i\n",x,y,zoom); */
tilesize = SPHEREMERC_GROUND_SIZE / zoomfactor;
xmin = (x * tilesize) - (SPHEREMERC_GROUND_SIZE / 2.0);
xmax = ((x + 1) * tilesize) - (SPHEREMERC_GROUND_SIZE / 2.0);
ymin = (SPHEREMERC_GROUND_SIZE / 2.0) - ((y + 1) * tilesize);
ymax = (SPHEREMERC_GROUND_SIZE / 2.0) - (y * tilesize);
map->extent.minx = xmin;
map->extent.maxx = xmax;
map->extent.miny = ymin;
map->extent.maxy = ymax;
} else if( msObj->TileMode == TILE_VE ) {
double minx = SPHEREMERC_GROUND_SIZE / -2.0;
double miny = SPHEREMERC_GROUND_SIZE / -2.0;
double maxx = SPHEREMERC_GROUND_SIZE / 2.0;
double maxy = SPHEREMERC_GROUND_SIZE / 2.0;
double zoom = 2.0;
double tsize;
int i = 0;
char j = 0;
/*
** Walk down the VE URL string, adjusting the extent each time.
** For meta-tiling cases, we stop early, to draw a larger image.
*/
for( i = 0; i < strlen( msObj->TileCoords ) - params.metatile_level; i++ ) {
j = msObj->TileCoords[i];
tsize = SPHEREMERC_GROUND_SIZE / zoom;
if( j == '1' || j == '3' ) minx += tsize;
if( j == '0' || j == '2' ) maxx -= tsize;
if( j == '2' || j == '3' ) maxy -= tsize;
if( j == '0' || j == '1' ) miny += tsize;
zoom *= 2.0;
}
map->extent.minx = minx;
map->extent.maxx = maxx;
map->extent.miny = miny;
map->extent.maxy = maxy;
} else {
return(MS_FAILURE); /* Huh? Should have a mode. */
}
/*
** Set the output tile size.
*/
msObj->ImgCols = SPHEREMERC_IMAGE_SIZE << params.metatile_level;
msObj->ImgRows = SPHEREMERC_IMAGE_SIZE << params.metatile_level;
map->width = SPHEREMERC_IMAGE_SIZE << params.metatile_level;
map->height = SPHEREMERC_IMAGE_SIZE << params.metatile_level;
if(map->debug)
msDebug("msTileSetExtent(): base image size (%d x %d)\n",map->width,map->height);
/*
** Add the gutters
** First calculate ground units in the buffer at current extent
*/
buffer = params.map_edge_buffer * (map->extent.maxx - map->extent.minx) / (double)map->width;
/*
** Then adjust the map extents out by that amount
*/
map->extent.minx -= buffer;
map->extent.maxx += buffer;
map->extent.miny -= buffer;
map->extent.maxy += buffer;
/*
** Finally adjust the map image size by the pixel buffer
*/
map->width += 2 * params.map_edge_buffer;
map->height += 2 * params.map_edge_buffer;
msObj->ImgCols += 2 * params.map_edge_buffer;
msObj->ImgRows += 2 * params.map_edge_buffer;
if(map->debug)
msDebug("msTileSetExtent(): buffered image size (%d x %d)\n",map->width,map->height);
/*
** Adjust the extents inwards by 1/2 pixel so they are from
** center-of-pixel to center-of-pixel, instead of edge-to-edge.
** This is the way mapserver does it.
*/
dx = (map->extent.maxx - map->extent.minx) / map->width;
map->extent.minx += dx*0.5;
map->extent.maxx -= dx*0.5;
dy = (map->extent.maxy - map->extent.miny) / map->height;
map->extent.miny += dy*0.5;
map->extent.maxy -= dy*0.5;
/*
** Ensure the labelcache buffer is greater than the tile buffer.
*/
if( params.map_edge_buffer > 0 ) {
const char *value;
hashTableObj *meta = &(map->web.metadata);
char tilebufferstr[64];
/* Write the tile buffer to a string */
snprintf(tilebufferstr, sizeof(tilebufferstr), "-%d", params.map_edge_buffer);
/* Hm, the labelcache buffer is set... */
if((value = msLookupHashTable(meta, "labelcache_map_edge_buffer")) != NULL) {
/* If it's too small, replace with a bigger one */
if( params.map_edge_buffer > abs(atoi(value)) ) {
msRemoveHashTable(meta, "labelcache_map_edge_buffer");
msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
}
}
/* No labelcache buffer value? Then we use the tile buffer. */
else {
msInsertHashTable(meta, "labelcache_map_edge_buffer", tilebufferstr);
}
}
if(map->debug) {
msDebug( "msTileSetExtent (%f, %f) (%f, %f)\n", map->extent.minx, map->extent.miny, map->extent.maxx, map->extent.maxy);
}
return MS_SUCCESS;
#else
msSetError(MS_CGIERR, "Tile API is not available.", "msTileSetExtent()");
return(MS_FAILURE);
#endif
}
/************************************************************************
* msDrawTile *
* *
* Draw the tile once with gutters, metatiling and buffers, then *
* clip out the final tile. *
* WARNING: Call msTileSetExtent() first or this will be a pointless *
* fucnction call. *
************************************************************************/
imageObj* msTileDraw(mapservObj *msObj)
{
imageObj *img;
tileParams params;
msTileGetParams(msObj->map, &params);
img = msDrawMap(msObj->map, MS_FALSE);
if( img == NULL )
return NULL;
if( params.metatile_level > 0 || params.map_edge_buffer > 0 ) {
imageObj *tmp = msTileExtractSubTile(msObj, img);
msFreeImage(img);
if( tmp == NULL )
return NULL;
img = tmp;
}
return img;
}
Jump to Line
Something went wrong with that request. Please try again.