Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gif timeserie #102

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ else(PNG_FOUND)
report_mandatory_not_found(PNG)
endif(PNG_FOUND)

find_package(GIF)
if(GIF_FOUND)
include_directories(${GIF_INCLUDE_DIR})
target_link_libraries(mapcache ${GIF_LIBRARY})
else(GIF_FOUND)
report_mandatory_not_found(GIF)
endif(GIF_FOUND)

find_package(JPEG)
if(JPEG_FOUND)
include_directories(${JPEG_INCLUDE_DIR})
Expand Down Expand Up @@ -263,6 +271,7 @@ endmacro()
message(STATUS "* Configured options for the mapcache library")
message(STATUS " * Mandatory components")
message(STATUS " * png: ${PNG_LIBRARY}")
message(STATUS " * gif: ${GIF_LIBRARY}")
message(STATUS " * jpeg: ${JPEG_LIBRARY}")
message(STATUS " * Curl: ${CURL_LIBRARY}")
message(STATUS " * Apr: ${APR_LIBRARY}")
Expand Down
36 changes: 33 additions & 3 deletions include/mapcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ typedef struct mapcache_image_format mapcache_image_format;
typedef struct mapcache_image_format_mixed mapcache_image_format_mixed;
typedef struct mapcache_image_format_png mapcache_image_format_png;
typedef struct mapcache_image_format_png_q mapcache_image_format_png_q;
typedef struct mapcache_image_format_gif mapcache_image_format_gif;
typedef struct mapcache_image_format_jpeg mapcache_image_format_jpeg;
typedef struct mapcache_cfg mapcache_cfg;
typedef struct mapcache_tileset mapcache_tileset;
Expand Down Expand Up @@ -843,7 +844,7 @@ void mapcache_service_dispatch_request(mapcache_context *ctx,
/** @{ */

typedef enum {
GC_UNKNOWN, GC_PNG, GC_JPEG
GC_UNKNOWN, GC_PNG, GC_JPEG, GC_GIF
} mapcache_image_format_type;

typedef enum {
Expand Down Expand Up @@ -1200,7 +1201,7 @@ struct mapcache_grid_link {
mapcache_extent *restricted_extent;
mapcache_extent_i *grid_limits;
int minz,maxz;

/**
* tiles above this zoom level will not be stored to the cache, but will be
* dynamically generated (either by reconstructing from lower level tiles, or
Expand Down Expand Up @@ -1506,6 +1507,7 @@ struct mapcache_image_format {
char *extension; /**< the extension to use when saving a file with this format */
char *mime_type;
mapcache_buffer * (*write)(mapcache_context *ctx, mapcache_image *image, mapcache_image_format * format);
mapcache_buffer * (*write_frames)(mapcache_context *ctx, mapcache_image *images, int numimages, mapcache_image_format * format, int delay);
/**< pointer to a function that returns a mapcache_buffer containing the given image encoded
* in the specified format
*/
Expand Down Expand Up @@ -1591,6 +1593,33 @@ mapcache_image_format* mapcache_imageio_create_png_q_format(apr_pool_t *pool, ch

/** @} */

typedef struct {
unsigned char b,g,r,a;
} rgbaPixel;

typedef struct {
unsigned char r,g,b;
} rgbPixel;

int _mapcache_imageio_quantize_image(mapcache_image *rb,
unsigned int *reqcolors, rgbaPixel *palette,
unsigned int *maxval,
rgbaPixel *forced_palette, int num_forced_palette_entries);
int _mapcache_imageio_classify(mapcache_image *rb, unsigned char *pixels,
rgbaPixel *palette, int numPaletteEntries);

struct mapcache_image_format_gif {
mapcache_image_format format;
mapcache_buffer * (*write_frames)(mapcache_context *ctx, mapcache_image *images, int numimages, mapcache_image_format * format, int delay);
int *animate;
};
mapcache_image_format* mapcache_imageio_create_gif_format(apr_pool_t *pool, char *name);
mapcache_buffer* _mapcache_imageio_gif_encode(mapcache_context *ctx, mapcache_image *img, mapcache_image_format *format);


mapcache_image_format* mapcache_imageio_create_jpeg_format(apr_pool_t *pool, char *name, int quality,
mapcache_photometric photometric);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do these private functions need to be exposed in the main header? I'd rather you created a specific file if you need to use quantization from multiple places

/**\defgroup imageio_jpg JPEG Image IO
* \ingroup imageio */
/** @{ */
Expand Down Expand Up @@ -1741,11 +1770,12 @@ apr_array_header_t* mapcache_timedimension_get_entries_for_value(mapcache_contex
struct mapcache_timedimension {
mapcache_timedimension_assembly_type assembly_type;
void (*configuration_parse_xml)(mapcache_context *context, mapcache_timedimension *dim, ezxml_t node);
apr_array_header_t* (*get_entries_for_interval)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset,
apr_array_header_t* (*get_entries_for_interval)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset,
mapcache_grid *grid, mapcache_extent *extent, time_t start, time_t end);
apr_array_header_t* (*get_all_entries)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset);
char *default_value;
char *key; /* TIME, hardcoded */
int delay;
};

#ifdef USE_SQLITE
Expand Down
3 changes: 3 additions & 0 deletions lib/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ mapcache_cfg* mapcache_configuration_create(apr_pool_t *pool)
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_gif_format(pool,"GIF"),
"GIF");
mapcache_configuration_add_image_format(cfg,
mapcache_imageio_create_jpeg_format(pool,"JPEG",90,MAPCACHE_PHOTOMETRIC_YCBCR),
"JPEG");
Expand Down
32 changes: 26 additions & 6 deletions lib/configuration_xml.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void parseMetadata(mapcache_context *ctx, ezxml_t node, apr_table_t *metadata)

void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *tileset) {
const char *attr = NULL;
int delay;
if(tileset->timedimension) {
ctx->set_error(ctx,400,"tileset \"%s\" can only have a single <timedimension>", tileset->name);
return;
Expand All @@ -66,7 +67,7 @@ void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *t
ctx->set_error(ctx,400,"unknown \"type\" attribute \"%s\" for %s's <timedimension>. Expecting one of (sqlite)",attr,tileset->name);
return;
}

} else {
ctx->set_error(ctx,400,"missing \"type\" attribute for %s's <timedimension>",tileset->name);
return;
Expand All @@ -77,14 +78,33 @@ void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *t
} else {
tileset->timedimension->key = apr_pstrdup(ctx->pool,"TIME");
}

attr = ezxml_attr(node,"default");
if(attr && *attr) {
tileset->timedimension->default_value = apr_pstrdup(ctx->pool,attr);
} else {
ctx->set_error(ctx,400,"no \"default\" attribute for <timedimension> %s",tileset->timedimension->key);
return;
}

attr = ezxml_attr(node, "animate");
if (attr && *attr){
if(!strcasecmp(attr, "true")) {
tileset->timedimension->assembly_type = MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE;
attr = ezxml_attr(node, "delay");
if (attr && *attr) {
delay = atoi(attr);
if (delay > 0)
tileset->timedimension->delay = delay;
else
ctx->set_error(ctx,400, "the \"delay \" attribute for <timedimension> %s must be an integer bigger than 0", tileset->timedimension->key);
}
}
} else {
ctx->set_error(ctx,400,"no \"animate\" attribute for <timedimension> %s", tileset->timedimension->key);
}


tileset->timedimension->configuration_parse_xml(ctx,tileset->timedimension,node);
}

Expand Down Expand Up @@ -701,7 +721,7 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
ctx->set_error(ctx, 400, "invalid grid maxzoom/minzoom %d/%d", gridlink->minz,gridlink->maxz);
return;
}

sTolerance = (char*)ezxml_attr(cur_node,"max-cached-zoom");
/* RFC97 implementation: check for a maximum zoomlevel to cache */
if(sTolerance) {
Expand All @@ -712,14 +732,14 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
sTolerance);
return;
}

if(tolerance > gridlink->maxz) {
ctx->set_error(ctx, 400, "failed to parse grid max-cached-zoom %s (max cached zoom is greater than grid's max zoom)",
sTolerance);
return;
}
gridlink->max_cached_zoom = tolerance;

/* default to reassembling */
gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_REASSEMBLE;
sTolerance = (char*)ezxml_attr(cur_node,"out-of-zoom-strategy");
Expand Down Expand Up @@ -750,7 +770,7 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
parseDimensions(ctx, cur_node, tileset);
GC_CHECK_ERROR(ctx);
}

if ((cur_node = ezxml_child(node,"timedimension")) != NULL) {
parseTimeDimension(ctx, cur_node, tileset);
GC_CHECK_ERROR(ctx);
Expand Down
69 changes: 62 additions & 7 deletions lib/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r
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 */
Expand All @@ -233,8 +233,8 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r
}
continue;
}
/* treat the most common case:

/* treat the most common case:
- we have a single tile request (i.e. isempty is true)
- the cache returned the encoded image
*/
Expand Down Expand Up @@ -271,7 +271,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r
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.
*/
Expand Down Expand Up @@ -307,7 +307,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r
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)
Expand Down Expand Up @@ -339,6 +339,7 @@ mapcache_map* mapcache_assemble_maps(mapcache_context *ctx, mapcache_map **maps,
int i;
maptiles = apr_pcalloc(ctx->pool,nmaps*sizeof(mapcache_tile**));
nmaptiles = apr_pcalloc(ctx->pool,nmaps*sizeof(int));

for(i=0; i<nmaps; i++) {
mapcache_tileset_get_map_tiles(ctx,maps[i]->tileset,maps[i]->grid_link,
&maps[i]->extent, maps[i]->width, maps[i]->height,
Expand Down Expand Up @@ -405,6 +406,50 @@ mapcache_map* mapcache_assemble_maps(mapcache_context *ctx, mapcache_map **maps,
return basemap;
}

mapcache_map* mapcache_assemble_animated_maps(mapcache_context *ctx, mapcache_map **maps, int nmaps, mapcache_mode mode)
{
int i, frame, frameidx = 0, nummaps = 0, currentframe = 0, numframes = 0;
mapcache_image *animatedframes = NULL;
mapcache_map *basemap = NULL;
mapcache_map **map_frames = apr_pcalloc(ctx->pool, nmaps*sizeof(mapcache_map*));

for(i=0; i<nmaps; i++) {
if(apr_table_get(maps[i]->dimensions, maps[i]->tileset->timedimension->key) != NULL) {
numframes++;
}
}

animatedframes = mapcache_image_create_with_data(ctx, maps[0]->width * numframes, maps[0]->height);

for(frame=0; frame<numframes; frame++) {
frameidx = 0;
nummaps = 0;
for(i=0; i<nmaps; i++) {
if(apr_table_get(maps[i]->dimensions, maps[i]->tileset->timedimension->key) != NULL) {
if(frameidx == currentframe) {
frameidx = 999999;
currentframe++;
} else {
frameidx++;
continue;
}
}
map_frames[nummaps] = maps[i];
nummaps++;
}

basemap = mapcache_assemble_maps(ctx, map_frames, nummaps, mode);

if(basemap->raw_image)
memcpy(animatedframes->data+frame*(animatedframes->w/numframes)*animatedframes->h*4,
basemap->raw_image->data, basemap->raw_image->w*basemap->raw_image->h*4);
if(GC_HAS_ERROR(ctx)) return NULL;
}
basemap->raw_image = animatedframes;

return basemap;
}

mapcache_http_response *mapcache_core_get_map(mapcache_context *ctx, mapcache_request_get_map *req_map)
{
mapcache_image_format *format = NULL;
Expand All @@ -429,7 +474,11 @@ mapcache_http_response *mapcache_core_get_map(mapcache_context *ctx, mapcache_re


if(req_map->getmap_strategy == MAPCACHE_GETMAP_ASSEMBLE) {
basemap = mapcache_assemble_maps(ctx, req_map->maps, req_map->nmaps, req_map->resample_mode);
if(req_map->maps[0]->tileset->timedimension &&
req_map->maps[0]->tileset->timedimension->assembly_type == MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE)
basemap = mapcache_assemble_animated_maps(ctx, req_map->maps, req_map->nmaps, req_map->resample_mode);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't pollute the core_get_map function with this switch, instead branch off to the specific assembly method inside mapcache_assemble_maps()

else
basemap = mapcache_assemble_maps(ctx, req_map->maps, req_map->nmaps, req_map->resample_mode);
if(GC_HAS_ERROR(ctx)) return NULL;
} else if(!ctx->config->non_blocking && req_map->getmap_strategy == MAPCACHE_GETMAP_FORWARD) {
int i;
Expand Down Expand Up @@ -469,7 +518,13 @@ mapcache_http_response *mapcache_core_get_map(mapcache_context *ctx, mapcache_re

if(basemap->raw_image) {
format = req_map->getmap_format; /* always defined, defaults to JPEG */
response->data = format->write(ctx,basemap->raw_image,format);
if(req_map->maps[0]->tileset->timedimension->assembly_type == MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE)
if (format->write_frames)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is ugly :( - I'd rather you change the format->write() prototype so it accepts multiple mapcache_images, and remove the write_frames prototype

response->data = format->write_frames(ctx, basemap->raw_image, req_map->nmaps, format, req_map->maps[0]->tileset->timedimension->delay);
else
ctx->set_error(ctx,500,"Asked for animated time dimension with a non-animated format");
else
response->data = format->write(ctx,basemap->raw_image,format);
if(GC_HAS_ERROR(ctx)) {
return NULL;
}
Expand Down
Loading