source: subversion/applications/utils/mod_mapnik_wms/wms.cpp @ 30595

Last change on this file since 30595 was 22440, checked in by frederik, 9 years ago

typo

File size: 36.4 KB
Line 
1/**
2 * Mapnik WMS module for Apache
3 *
4 * This is the C++ code where Mapnik is actually called to do something.
5 *
6 */
7
8#define BOOST_SPIRIT_THREADSAFE
9
10/* for jpeg compression */
11#define BUFFER_SIZE 4096
12#define QUALITY 90
13
14extern "C"
15{
16#include <png.h>
17#include <gd.h>
18#include <gdfonts.h>
19#include <httpd.h>
20#include <http_log.h>
21#include <http_protocol.h>
22#include <apr_strings.h>
23#include <apr_pools.h>
24#include <db.h>
25#include <proj_api.h>
26#include <jpeglib.h>
27}
28
29#include <mapnik/map.hpp>
30#include <mapnik/datasource_cache.hpp>
31#include <mapnik/font_engine_freetype.hpp>
32#include <mapnik/agg_renderer.hpp>
33#include <mapnik/filter_factory.hpp>
34#include <mapnik/color_factory.hpp>
35#include <mapnik/image_util.hpp>
36#include <mapnik/config_error.hpp>
37#include <mapnik/load_map.hpp>
38#include <mapnik/octree.hpp>
39
40#include "wms.h"
41
42#include <iostream>
43#include <vector>
44
45#include <sys/types.h>
46#include <sys/stat.h>
47#include <fcntl.h>
48#include <pthread.h>
49
50#include "logbuffer.h"
51
52
53extern "C" 
54{
55struct wms_cfg *get_wms_cfg(request_rec *r);
56bool load_configured_map(server_rec *s, struct wms_cfg *cfg)
57{
58    //cfg->mapnik_map = (mapnik::Map *) apr_pcalloc(p, sizeof(mapnik::Map));
59    try 
60    {
61        cfg->mapnik_map = new mapnik::Map();
62        load_map(*((mapnik::Map *)cfg->mapnik_map), cfg->map);
63        ((mapnik::Map *) cfg->mapnik_map)->setAspectFixMode(mapnik::Map::ADJUST_CANVAS_HEIGHT);
64    }
65    catch (const mapnik::config_error & ex)
66    {
67        ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s,
68             "error initializing map: %s.", ex.what());
69        std::clog << "error loading map: " << ex.what() << std::endl;
70        delete (mapnik::Map *) cfg->mapnik_map;
71        cfg->mapnik_map = NULL;
72        return false;
73    }
74    return true;
75};
76
77int wms_initialize(server_rec *s, struct wms_cfg *cfg, apr_pool_t *p)
78{
79    if (!cfg->datasource_count)
80    {
81        return HTTP_INTERNAL_SERVER_ERROR;
82    }
83
84    for (int i=0; i<cfg->datasource_count; i++)
85        mapnik::datasource_cache::instance()->register_datasources(cfg->datasource[i]);
86
87    if (!cfg->font_count)
88    {
89        return HTTP_INTERNAL_SERVER_ERROR;
90    }
91
92    for (int i=0; i<cfg->font_count; i++)
93    {
94        mapnik::freetype_engine::register_font(cfg->font[i]);
95    }
96
97    if (!cfg->map)
98    {
99        return HTTP_INTERNAL_SERVER_ERROR;
100    }
101
102    if (!cfg->url)
103    {
104        return HTTP_INTERNAL_SERVER_ERROR;
105    }
106    if (!cfg->title)
107    {
108        return HTTP_INTERNAL_SERVER_ERROR;
109    }
110
111    cfg->initialized = 1;
112
113    if (cfg->debug)
114    {
115        // will create map later
116        std::clog << "debug mode, load map file later" << std::endl;
117        return OK;
118    }
119
120    if (!load_configured_map(s, cfg))
121        return HTTP_INTERNAL_SERVER_ERROR;
122
123    std::clog << "init complete" << std::endl;
124    return OK;
125}
126} /* extern C */
127
128
129/**
130 * Callback for libpng functions.
131 */
132void user_flush_data(png_structp png_ptr)
133{
134    /* no-op */
135}
136
137/**
138 * Used as a data sink for libpng functions. Sends PNG data to Apache.
139 */
140void user_write_data(png_structp png_ptr,
141               png_bytep data, png_size_t length)
142{
143    request_rec *r = (request_rec *) png_get_io_ptr(png_ptr);
144    unsigned int offset = 0;
145    while(1)
146    {
147        int written = ap_rwrite(data + offset, length, r);
148        /* FIXME if this should somehow constantly return 0 we'll loop forever. */
149        if (written < 0) return;
150        if (written + offset == length) return;
151        offset += written;
152    }
153}
154
155/**
156 * Used as a data sink for libgd functions. Sends PNG data to Apache.
157 */
158static int gd_png_sink(void *ctx, const char *data, int length)
159{
160    request_rec *r = (request_rec *) ctx;
161    return ap_rwrite(data, length, r);
162}
163
164/**
165 * Callbacks for jpeg library
166 */
167typedef struct
168{
169     struct jpeg_destination_mgr pub;
170     request_rec *out;
171     JOCTET * buffer;
172} dest_mgr;
173
174inline void init_destination(j_compress_ptr cinfo)
175{
176  dest_mgr * dest = reinterpret_cast<dest_mgr*>(cinfo->dest);
177  dest->buffer = (JOCTET*) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
178                                                       BUFFER_SIZE * sizeof(JOCTET));
179  dest->pub.next_output_byte = dest->buffer;
180  dest->pub.free_in_buffer = BUFFER_SIZE;
181}
182
183inline boolean empty_output_buffer(j_compress_ptr cinfo)
184{
185    dest_mgr *dest = reinterpret_cast<dest_mgr*>(cinfo->dest);
186    unsigned int offset = 0;
187    while(1)
188    {
189        int written = ap_rwrite(dest->buffer + offset, BUFFER_SIZE, dest->out);
190        if (written < 0) return false;
191        if (written + offset == BUFFER_SIZE) break;
192        offset += written;
193    }
194    dest->pub.next_output_byte = dest->buffer;
195    dest->pub.free_in_buffer = BUFFER_SIZE;
196    return true;
197}
198
199inline void term_destination( j_compress_ptr cinfo)
200{
201    dest_mgr *dest = reinterpret_cast<dest_mgr*>(cinfo->dest);
202    size_t size  = BUFFER_SIZE - dest->pub.free_in_buffer;
203    if (size > 0)
204    {
205        unsigned int offset = 0;
206        while(1)
207        {
208            int written = ap_rwrite(dest->buffer + offset, size, dest->out);
209            if (written < 0) return;
210            if (written + offset == size) break;
211            offset += written;
212        }
213    }
214}
215
216/**
217 * Decodes URI, overwrites original buffer.
218 */
219void decode_uri_inplace(char *uri)
220{
221    char *c, *d, code[3] = {0, 0, 0};
222
223    for (c = uri, d = uri; *c; c++)
224    {
225        if ((*c=='%') && isxdigit(*(c+1)) && isxdigit(*(c+2)))
226        {
227            strncpy(code, c+1, 2); 
228            *d++ = (char) strtol(code, 0, 16);
229            c+=2;
230        } 
231        else 
232        {
233            *d++ = *c;
234        }
235    }
236    *d = 0;
237}
238
239/**
240 * Sends a HTTP error return code and logs the error.
241 */
242int http_error(request_rec *r, int code, const char *fmt, ...)
243{
244    va_list ap;
245    char msg[1024]; 
246    va_start(ap, fmt);
247    vsnprintf(msg, 1023, fmt, ap);
248    msg[1023]=0;
249    std::clog << msg << std::endl;
250    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
251    ap_set_content_type(r, "text/plain");
252    ap_rputs(msg, r);
253    return code;
254}
255
256/**
257 * Sends a WMS error message in the format requested by the client.
258 * This is either an XML document, or an image. The HTTP return code
259 * is 200 OK.
260 */
261int wms_error(request_rec *r, const char *code, const char *fmt, ...)
262{
263    va_list ap;
264    char msg[1024]; 
265    va_start(ap, fmt);
266    vsnprintf(msg, 1023, fmt, ap);
267    msg[1023]=0;
268    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
269    std::clog << msg << std::endl;
270
271    char *args = r->args ? apr_pstrdup(r->pool, r->args) : 0;
272    char *amp;
273    char *current = args;
274    char *equals;
275    bool end = (args == 0);
276
277    const char *exceptions = "";
278    const char *width = 0;
279    const char *height = 0;
280
281    /* parse URL parameters into variables */
282    while (!end)
283    {
284        amp = index(current, '&');
285        if (amp == 0) { amp = current + strlen(current); end = true; }
286        *amp = 0;
287        equals = index(current, '=');
288        if (equals > current)
289        {
290            *equals++ = 0;
291            decode_uri_inplace(current);
292            decode_uri_inplace(equals);
293
294            if (!strcasecmp(current, "WIDTH")) width = equals;
295            else if (!strcasecmp(current, "HEIGHT")) height = equals;
296            else if (!strcasecmp(current, "EXCEPTIONS")) exceptions = equals;
297        }
298        current = amp + 1;
299    }
300
301    if (!strcmp(exceptions, "application/vnd.ogc.se_xml") || !width || !height)
302    {
303        /* XML error message was requested. */
304        ap_set_content_type(r, exceptions);
305        ap_rprintf(r, "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>\n"
306            "<!DOCTYPE ServiceExceptionReport SYSTEM 'http://www.digitalearth.gov/wmt/xml/exception_1_1_0.dtd'>\n"
307            "<ServiceExceptionReport version='1.1.0'>\n"
308            "<ServiceException code='%s'>\n%s\n</ServiceException>\n"
309            "</ServiceExceptionReport>", code, msg);
310    }
311    else if (!strcmp(exceptions, "application/vnd.ogc.se_inimage"))
312    {
313        /* Image error message was requested. We use libgd to create one. */
314        int n_width = atoi(width);
315        int n_height = atoi(height);
316        if (n_width > 0 && n_width < 9999 && n_height > 0 && n_height < 9999)
317        {
318            gdImagePtr img = gdImageCreate(n_width, n_height);
319            (void) gdImageColorAllocate(img, 255, 255, 255);
320            int black = gdImageColorAllocate(img, 0, 0, 0);
321            gdImageString(img, gdFontGetSmall(), 0, 0, (unsigned char *) msg, black);
322            gdSink sink;
323            sink.context = (void *) r;
324            sink.sink = gd_png_sink;
325            ap_set_content_type(r, "image/png");
326            gdImagePngToSink(img, &sink);
327        }
328        else
329        {
330            return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "Cannot satisfy requested exception type (%s)", exceptions);
331        }
332    }
333    else if (!strcmp(exceptions, "application/vnd.ogc.se_blank"))
334    {
335        /* Empty image in error was requested. */
336        int n_width = atoi(width);
337        int n_height = atoi(height);
338        if (n_width > 0 && n_width < 9999 && n_height > 0 && n_height < 9999)
339        {
340            gdImagePtr img = gdImageCreate(n_width, n_height);
341            gdImageColorAllocate(img, 255, 255, 255);
342            gdSink sink;
343            sink.context = (void *) r;
344            sink.sink = gd_png_sink;
345            ap_set_content_type(r, "image/png");
346            gdImagePngToSink(img, &sink);
347        }
348        else
349        {
350            return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "Cannot satisfy requested exception type (%s)", exceptions);
351        }
352    }
353    else
354    {
355        return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "Invalid exception type (%s)", exceptions);
356    }
357    return OK;
358}
359
360/* From Mapnik. */
361void reduce_8 (mapnik::ImageData32 const& in, mapnik::ImageData8 &out, mapnik::octree<mapnik::rgb> &tree)
362{
363    unsigned width = in.width();
364    unsigned height = in.height();
365    for (unsigned y = 0; y < height; ++y)
366    {
367        mapnik::ImageData32::pixel_type const * row = in.getRow(y);
368        mapnik::ImageData8::pixel_type  * row_out = out.getRow(y);
369        for (unsigned x = 0; x < width; ++x)
370        {
371            unsigned val = row[x];
372            mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff);
373            uint8_t index = tree.quantize(c);
374            row_out[x] = index;
375        }
376    }
377}
378     
379/* From Mapnik. */
380void reduce_4 (mapnik::ImageData32 const& in, mapnik::ImageData8 &out, mapnik::octree<mapnik::rgb> &tree)
381{
382    unsigned width = in.width();
383    unsigned height = in.height();
384
385    for (unsigned y = 0; y < height; ++y)
386    {
387        mapnik::ImageData32::pixel_type const * row = in.getRow(y);
388        mapnik::ImageData8::pixel_type  * row_out = out.getRow(y);
389
390        for (unsigned x = 0; x < width; ++x)
391        {
392            unsigned val = row[x];
393            mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff);
394            uint8_t index = tree.quantize(c);
395            if (x%2 >  0) index = index<<4;
396            row_out[x>>1] |= index; 
397        }
398    }
399}
400   
401/* From Mapnik. */
402void reduce_1(mapnik::ImageData32 const&, mapnik::ImageData8 & out, mapnik::octree<mapnik::rgb> &)
403{
404    out.set(0); // only one color!
405}
406
407/**
408 * Generates PNG and sends it to the client.
409 * Based on PNG writer code from Mapnik.
410 */
411void send_png_response(request_rec *r, mapnik::Image32 buf, unsigned int height, bool smallpng)
412{
413    mapnik::ImageData32 image = buf.data();
414    int depth = 32;
415    std::vector<mapnik::rgb> palette;
416    mapnik::ImageData8 *reduced;
417    unsigned width = image.width();
418
419    if (smallpng)
420    {
421        mapnik::octree<mapnik::rgb> tree(256);
422        for (unsigned y = 0; y < height; ++y)
423        {
424            mapnik::ImageData32::pixel_type const * row = image.getRow(y);
425            for (unsigned x = 0; x < width; ++x)
426            {
427                unsigned val = row[x];
428                tree.insert(mapnik::rgb((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff));
429            }
430        }
431
432        tree.create_palette(palette);
433        assert(palette.size() <= 256);
434
435        if (palette.size() > 16 )
436        {
437            // >16 && <=256 colors -> write 8-bit color depth
438            reduced = new mapnik::ImageData8(width,height);   
439            reduce_8(image,*reduced,tree);
440            depth = 8;
441        }
442        else if (palette.size() == 1) 
443        {
444            // 1 color image ->  write 1-bit color depth PNG
445            reduced = new mapnik::ImageData8(width,height);   
446            width  = (int(0.125*width) + 7)&~7;
447            reduce_1(image,*reduced,tree); 
448            depth = 1;
449        }
450        else 
451        {
452            // <=16 colors -> write 4-bit color depth PNG
453            reduced = new mapnik::ImageData8(width,height);   
454            width = (int(0.5*width) + 3)&~3;
455            reduce_4(image,*reduced,tree);
456            depth = 4;
457        }
458    }
459
460    png_voidp error_ptr=0;
461    png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr,0, 0);
462
463    if (!png_ptr) return;
464#if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10200) && defined(PNG_MMX_CODE_SUPPORTED)
465      png_uint_32 mask, flags;
466      flags = png_get_asm_flags(png_ptr);
467      mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE);
468      png_set_asm_flags(png_ptr, flags | mask);
469#endif
470    png_set_filter (png_ptr, 0, PNG_FILTER_NONE);
471    png_infop info_ptr = png_create_info_struct(png_ptr);
472    if (!info_ptr)
473    {
474        png_destroy_write_struct(&png_ptr,(png_infopp)0);
475        return;
476    }
477    if (setjmp(png_jmpbuf(png_ptr)))
478    {
479        png_destroy_write_struct(&png_ptr, &info_ptr);
480        return;
481    }
482    png_set_write_fn(png_ptr,
483            (void *) r, user_write_data, user_flush_data);
484
485    std::clog << "png preparation complete" << std::endl;
486    if (smallpng)
487    {
488        png_set_IHDR(png_ptr, info_ptr,width,height,depth,
489                PNG_COLOR_TYPE_PALETTE,PNG_INTERLACE_NONE,
490                PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
491        png_set_PLTE(png_ptr,info_ptr,reinterpret_cast<png_color*>(&palette[0]),palette.size());
492    }
493    else
494    {
495        png_set_IHDR(png_ptr, info_ptr,width,height,8,
496                PNG_COLOR_TYPE_RGB_ALPHA,PNG_INTERLACE_NONE,
497                PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
498    }
499    png_write_info(png_ptr, info_ptr);
500
501    for (unsigned int i = 0; i < height; i++)
502    {
503        // this is a primitive way of making sure that the client gets exatly the number of
504        // rows it asked for, even if the generated image should be larger or smaller. This
505        // method drops rows, or duplicates them, which makes for bad image quality if the
506        // client asked for a "wrong" size. proper rescaling would be better
507        unsigned int src_row = (int) (i * image.height() * 1.0 / height + 0.5);
508        png_write_row(png_ptr,
509            (smallpng) ? (png_bytep)reduced->getRow(src_row)
510                       : (png_bytep)image.getRow(src_row));
511    }
512
513    png_write_end(png_ptr, info_ptr);
514    png_destroy_write_struct(&png_ptr, &info_ptr);
515    if (smallpng) delete reduced;
516}
517
518/**
519 * Generates JPEG and sends it to the client.
520 * Based on JPEG writer code from Mapnik.
521 */
522void send_jpeg_response(request_rec *r, mapnik::Image32 buf, unsigned int height)
523{
524    mapnik::ImageData32 image = buf.data();
525    struct jpeg_compress_struct cinfo;
526    struct jpeg_error_mgr jerr;
527
528    int iwidth=image.width();
529    int iheight=image.height();
530
531    cinfo.err = jpeg_std_error(&jerr);
532    jpeg_create_compress(&cinfo);
533
534    cinfo.dest = (struct jpeg_destination_mgr *)(*cinfo.mem->alloc_small)
535        ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof(dest_mgr));
536    dest_mgr * dest = (dest_mgr*) cinfo.dest;
537    dest->pub.init_destination = init_destination;
538    dest->pub.empty_output_buffer = empty_output_buffer;
539    dest->pub.term_destination = term_destination;
540    dest->out = r;
541
542    //jpeg_stdio_dest(&cinfo, fp);
543    cinfo.image_width = iwidth;
544    cinfo.image_height = height;
545    cinfo.input_components = 3;
546    cinfo.in_color_space = JCS_RGB;
547    jpeg_set_defaults(&cinfo);
548    jpeg_set_quality(&cinfo, QUALITY,1);
549    jpeg_start_compress(&cinfo, 1);
550    JSAMPROW row_pointer[1];
551    JSAMPLE* row=reinterpret_cast<JSAMPLE*>( ::operator new (sizeof(JSAMPLE) * iwidth*3));
552    for (unsigned int i = 0; i < height; i++)
553    {
554        // this is a primitive way of making sure that the client gets exatly the number of
555        // rows it asked for, even if the generated image should be larger or smaller. This
556        // method drops rows, or duplicates them, which makes for bad image quality if the
557        // client asked for a "wrong" size. proper rescaling would be better
558
559        unsigned int src_row = (int) (i * iheight * 1.0 / height + 0.5);
560        const unsigned* imageRow=image.getRow(src_row);
561        int index=0;
562        for (int j=0; j<iwidth; j++)
563        {
564            row[index++]=(imageRow[j])&0xff;
565            row[index++]=(imageRow[j]>>8)&0xff;
566            row[index++]=(imageRow[j]>>16)&0xff;
567        }
568        row_pointer[0] = &row[0];
569        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
570    }
571    ::operator delete(row);
572
573    jpeg_finish_compress(&cinfo);
574    jpeg_destroy_compress(&cinfo);
575}
576
577/**
578 * Creates a GetCapabilties response and sends it to the client.
579 */
580int wms_getcap(request_rec *r)
581{
582    struct wms_cfg *config = get_wms_cfg(r);
583
584    std::string srs_list;
585    std::string bbox_list;
586
587    char buffer[1024];
588    sprintf(buffer, "   <LatLonBoundingBox minx='%f' miny='%f' maxx='%f' maxy='%f' />\n", 
589        config->minx, config->miny, config->maxx, config->maxy);
590    bbox_list.append(buffer);
591    sprintf(buffer, "   <BoundingBox SRS='EPSG:4326' minx='%f' miny='%f' maxx='%f' maxy='%f' />\n", 
592        config->minx, config->miny, config->maxx, config->maxy);
593    bbox_list.append(buffer);
594
595    for (int i=0; i<config->srs_count; i++)
596    {
597        sprintf(buffer, "+init=%s", config->srs[i]);
598        for (char *j = buffer; *j; j++) *j=tolower(*j);
599        projPJ pj = pj_init_plus(buffer);
600        if (!pj)
601        {
602            std::clog << "error while initializing projection '" << config->srs[i] << "': '" << pj_strerrno(pj_errno) << "', dropped" << std::endl;
603            continue;
604        }
605        projUV uv;
606        uv.u = config->minx;
607        uv.v = config->miny;
608        if (strcmp(config->srs[i], "EPSG:4326"))
609        {
610            uv.u *= DEG_TO_RAD;
611            uv.v *= DEG_TO_RAD;
612            uv = pj_fwd(uv, pj);
613        }
614
615        srs_list.append("    <SRS>");
616        srs_list.append(config->srs[i]);
617        srs_list.append("</SRS>\n");
618
619    /*
620     * add bounding boxes in layer coordinates. this seems not to be required,
621     * plus it makes things a bit difficult for those SRSes which give "nan"
622     * values for large extents.
623     
624        sprintf(buffer, "   <BoundingBox SRS='%s' minx='%f' miny='%f' ", config->srs[i], uv.u, uv.v);
625        bbox_list.append(buffer);
626
627        uv.u = config->maxx;
628        uv.v = config->maxy;
629        if (strcmp(config->srs[i], "EPSG:4326"))
630        {
631            uv.u *= DEG_TO_RAD;
632            uv.v *= DEG_TO_RAD;
633            uv = pj_fwd(uv, pj);
634        }
635        pj_free(pj);
636        sprintf(buffer, "maxx='%f' maxy='%f' />\n", uv.u, uv.v);
637        bbox_list.append(buffer);
638    */
639    }
640
641    ap_set_content_type(r, "application/vnd.ogc.wms_xml");
642    ap_rprintf(r, 
643        "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>\n"
644        "<!DOCTYPE WMT_MS_Capabilities SYSTEM 'http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd'\n"
645        " [\n"
646        " <!ELEMENT VendorSpecificCapabilities EMPTY>\n"
647        " ]>  <!-- end of DOCTYPE declaration -->\n"
648        "\n"
649        "<WMT_MS_Capabilities version='1.1.1'>\n"
650        "\n"
651        "<Service>\n"
652        "  <Name>OGC:WMS</Name>\n"
653        "  <Title>%s</Title>\n"
654        "  <OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='%s%s?'/>\n"
655        "</Service>\n"
656        "\n"
657        "<Capability>\n"
658        "  <Request>\n"
659        "    <GetCapabilities>\n"
660        "      <Format>application/vnd.ogc.wms_xml</Format>\n"
661        "      <DCPType>\n"
662        "        <HTTP>\n"
663        "          <Get><OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='%s%s?'/></Get>\n"
664        "        </HTTP>\n"
665        "      </DCPType>\n"
666        "    </GetCapabilities>\n"
667        "    <GetMap>\n"
668        "      <Format>image/png</Format>\n"
669        "      <Format>image/png8</Format>\n"
670        "      <Format>image/jpeg</Format>\n"
671        "      <DCPType>\n"
672        "        <HTTP>\n"
673        "          <Get><OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='%s%s?'/></Get>\n"
674        "        </HTTP>\n"
675        "      </DCPType>\n"
676        "    </GetMap>\n"
677        "  </Request>\n"
678        "  <Exception>\n"
679        "    <Format>application/vnd.ogc.se_xml</Format>\n"
680        "    <Format>application/vnd.ogc.se_inimage</Format>\n"
681        "    <Format>application/vnd.ogc.se_blank</Format>\n"
682        "  </Exception>\n"
683        "  <UserDefinedSymbolization SupportSLD='0' UserLayer='0' UserStyle='0' RemoteWFS='0'/>\n", 
684                config->title, config->url, r->uri, config->url, r->uri, config->url, r->uri);
685
686    // FIXME more if this should be configurable.
687    ap_rprintf(r, 
688        "  <Layer>\n"
689        "    <Name>%s</Name>\n"
690        "    <Title>%s</Title>\n"
691        "%s\n"
692        "%s\n"
693        "    <Attribution>\n"
694        "        <Title>www.openstreetmap.org/CC-BY-SA2.0</Title>\n"
695        "        <OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='http://www.openstreetmap.org/'/>\n"
696        "    </Attribution>\n"
697        "    <Layer queryable='0' opaque='1' cascaded='0'>\n"
698        "        <Name>%s</Name>\n"
699        "        <Title>%s</Title>\n"
700        "        <Abstract>Full OSM Mapnik rendering.</Abstract>\n"
701        "%s\n"
702        "%s\n",
703            config->top_layer_name, config->top_layer_title, srs_list.c_str(), bbox_list.c_str(), 
704            config->top_layer_name, config->top_layer_title, srs_list.c_str(), bbox_list.c_str());
705
706    if (config->include_sub_layers)
707    {
708        /* TODO - add auto-generated set of layers from Mapnik map file, should look like so:
709
710        ap_rprintf(r,
711        "        <Layer queryable='0' opaque='1' cascaded='0'><Name>places</Name><Title>places</Title></Layer>\n");
712
713        */
714    }
715    ap_rprintf(r, 
716        "    </Layer>\n"
717        "  </Layer>\n"
718        "</Capability>\n"
719        "</WMT_MS_Capabilities>");
720    return OK;
721}
722
723/**
724 * Handles the GetMap request.
725 */
726int wms_getmap(request_rec *r)
727{
728    int rv = OK;
729    struct wms_cfg *config = get_wms_cfg(r);
730
731    const char *layers = 0;
732    char *srs = 0;
733    const char *bbox = 0;
734    const char *width = 0;
735    const char *height = 0;
736    const char *styles = 0;
737    const char *format = 0;
738    const char *transparent = 0;
739    const char *bgcolor = 0;
740    const char *exceptions = "application/vnd.ogc.se_xml";
741
742    char *args = r->args ? apr_pstrdup(r->pool, r->args) : 0;
743    char *amp;
744    char *current = args;
745    char *equals;
746    bool end = (args == 0);
747
748    /*
749     * in debug mode, the map is loaded/parsed for each request. that makes
750     * it easier to make changes (no apache restart required)
751     */
752
753    if ((config->debug) && (!load_configured_map(r->server, config)))
754    {
755        return wms_error(r, "InvalidDimensionValue", "error parsing map file");
756    }
757
758    if (!config->mapnik_map)
759    {
760        return wms_error(r, "InvalidDimensionValue", "error parsing map file");
761    }
762
763    /* parse URL parameters into variables */
764    while (!end)
765    {
766        amp = index(current, '&');
767        if (amp == 0) { amp = current + strlen(current); end = true; }
768        *amp = 0;
769        equals = index(current, '=');
770        if (equals > current)
771        {
772            *equals++ = 0;
773            decode_uri_inplace(current);
774            decode_uri_inplace(equals);
775
776            if (!strcasecmp(current, "LAYERS")) layers = equals;
777            else if (!strcasecmp(current, "SRS")) srs = equals;
778            else if (!strcasecmp(current, "BBOX")) bbox = equals;
779            else if (!strcasecmp(current, "WIDTH")) width = equals;
780            else if (!strcasecmp(current, "HEIGHT")) height = equals;
781            else if (!strcasecmp(current, "STYLES")) styles = equals;
782            else if (!strcasecmp(current, "FORMAT")) format = equals;
783            else if (!strcasecmp(current, "TRANSPARENT")) transparent = equals;
784            else if (!strcasecmp(current, "BGCOLOR")) bgcolor = equals;
785            else if (!strcasecmp(current, "EXCEPTIONS")) exceptions = equals;
786        }
787        current = amp + 1;
788    }
789
790    if (!layers) return wms_error(r, "MissingDimensionValue", "required parameter 'layers' not set");
791    if (!srs) return wms_error(r, "MissingDimensionValue", "required parameter 'srs' not set");
792    if (!bbox) return wms_error(r, "MissingDimensionValue", "required parameter 'bbox' not set");
793    if (!width) return wms_error(r, "MissingDimensionValue", "required parameter 'width' not set");
794    if (!height) return wms_error(r, "MissingDimensionValue", "required parameter 'height' not set");
795    if (!styles) return wms_error(r, "MissingDimensionValue", "required parameter 'styles' not set");
796    if (!format) return wms_error(r, "MissingDimensionValue", "required parameter 'format' not set");
797
798    int n_width = atoi(width);
799    int n_height = atoi(height);
800
801    if (n_width < 1 || n_width > 9999)
802    {
803        return wms_error(r, "InvalidDimensionValue", "requested width (%d) is not in range 1...9999", n_width);
804    }
805    if (n_height < 1 || n_height > 9999)
806    {
807        return wms_error(r, "InvalidDimensionValue", "requested height (%d) is not in range 1...9999", n_height);
808    }
809
810    double bboxvals[4];
811    int bboxcnt = 0;
812    char *dup = apr_pstrdup(r->pool, bbox);
813    char *tok = strtok(dup, ",");
814    while(tok)
815    {
816        if (bboxcnt<4) bboxvals[bboxcnt] = strtod(tok, NULL);
817        bboxcnt++;
818        tok = strtok(NULL, ",");
819    }
820    if (bboxcnt != 4)
821    {
822        return wms_error(r, "InvalidDimensionValue", "Invalid BBOX parameter ('%s'). Must contain four comma-separated values.", bbox);
823    }
824
825    /*
826     * commented out due to client brokenness
827     *
828    if (bboxvals[0] > bboxvals[2] ||
829        bboxvals[1] > bboxvals[3] ||
830        bboxvals[0] < -180 ||
831        bboxvals[2] < -180 ||
832        bboxvals[1] < -90 ||
833        bboxvals[3] < -90 ||
834        bboxvals[0] > 180 ||
835        bboxvals[2] > 180 ||
836        bboxvals[1] > 90 ||
837        bboxvals[3] > 90)
838    {
839        return wms_error(r, "InvalidDimensionValue", "Invalid BBOX parameter ('%s'). Must describe an area on earth, with minlon,minlat,maxlon,maxlat", bbox);
840    }
841    */
842
843    /** check if given SRS is supported by configuration */
844    bool srs_ok = false;
845
846    for (int i=0; i<config->srs_count; i++)
847    {
848        if (!strcmp(config->srs[i], srs))
849        {
850            srs_ok= true;
851            break;
852        }
853    }
854    if (!srs_ok)
855    {
856        return wms_error(r, "InvalidSRS", "The given SRS ('%s') is not supported by this WMS service.", srs);
857    }
858
859    /*
860     * Layer selection is currently disabled. We always return all Mapnik
861     * layers. But this could be used to let the client select individual layers.
862
863    // split up layers into a proper C++ set for easy access
864    std::set<std::string> layermap;
865    dup = apr_pstrdup(r->pool, layers);
866    const char *token = strtok(dup, ",");
867    while(token)
868    {
869        // if one of the layers requested is the "top" layer
870        // then kill layer selection and return everything.
871        if  (!strcmp(token, config->top_layer_name))
872        {
873            layermap.clear();
874            break;
875        }
876        layermap.insert(token);
877        token = strtok(NULL, ",");
878    }
879    */
880
881    FILE *f = fopen(config->logfile, "a");
882    std::streambuf *old = NULL;
883    logbuffer *o = NULL;
884    if (f)
885    {
886        o = new logbuffer(f);
887        old = std::clog.rdbuf();
888        std::clog.rdbuf(o);
889    }
890
891    std::clog << "NEW REQUEST: " << r->the_request << std::endl;
892
893    char *type;
894    char *customer_id;
895
896#ifdef USE_KEY_DATABASE
897    /*
898     * See README for what the key database is about. It is basically
899     * an access control scheme where clients have to give a certain
900     * key in the URL to be granted access.
901     */
902
903    if (config->key_db_file)
904    {
905        std::clog << "checking key " << r->uri << std::endl;
906        char *map_name = apr_pstrdup(r->pool, r->uri+1);
907        char *user_key = index(map_name, '/');
908        if (!user_key) 
909        {
910            return http_error(r, HTTP_FORBIDDEN, "No key in URL", exceptions);
911        }
912   
913        *(user_key++) = 0;
914
915        DB *dbp;
916        DBT key, data;
917        memset(&key, 0, sizeof(key));
918        memset(&data, 0, sizeof(data));
919        int ret;
920
921        if ((ret = db_create(&dbp, NULL, 0)) != 0) 
922        {
923            std::clog << "db_create returns error: " << db_strerror(ret) << std::endl;
924            return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "database error", exceptions);
925        }
926
927        if ((ret = dbp->open(dbp,
928            NULL, config->key_db_file, NULL, DB_UNKNOWN, DB_RDONLY, 0)) != 0) {
929            dbp->err(dbp, ret, "%s", config->key_db_file);
930            std::clog << "db_open returns error: " << db_strerror(ret) << std::endl;
931            return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "database error", exceptions);
932        }
933        key.data = user_key;
934        key.size = strlen(user_key);
935
936        if ((ret = dbp->get(dbp, NULL, &key, &data, 0)) != 0)
937        {
938            std::clog << "db_get returns error for key '" << user_key << "': " << db_strerror(ret) << std::endl;
939            return http_error(r, HTTP_FORBIDDEN, "Key not known", exceptions);
940        }
941
942        char *colon = index((char *)data.data, ':');
943        if (!colon) return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "Bad db content", exceptions);
944        *(colon++)=0;
945        char *colon2 = index(colon, ':');
946        if (!colon2) return http_error(r, HTTP_INTERNAL_SERVER_ERROR, "Bad db content", exceptions);
947        *(colon2++) = 0;
948        type = apr_pstrdup(r->pool, (char *) data.data);
949        customer_id = apr_pstrdup(r->pool, colon2);
950       
951        char *token = strtok(colon, ",");
952        bool found = false;
953        while (token)
954        {
955            if (!strcmp(token, map_name))
956            {
957                found = true;
958                break;
959            }
960            token = strtok(NULL, ",");
961        }   
962       
963        if (!found)
964        {
965            std::clog << "requested map name '" << map_name << "' not in allowed list for key '" << user_key << "'" << std::endl;
966            return http_error(r, HTTP_FORBIDDEN, "Map not allowed", exceptions);
967        }
968       
969        std::clog << "user id " << customer_id << ", account type is '" << type << "'" << std::endl;
970
971        if (!strcmp(type, "demo"))
972        {
973            if (config->max_demo_width && n_width > config->max_demo_width)
974                return wms_error(r, "InvalidDimensionValue", 
975                    "requested width (%d) is not in demo range 1...%d", n_width, config->max_demo_width);
976            if (config->max_demo_height && n_height > config->max_demo_height)
977                return wms_error(r, "InvalidDimensionValue", 
978                    "requested height (%d) is not in demo range 1...%d", n_height, config->max_demo_height);
979        }
980    }
981#endif
982
983    using namespace mapnik;
984    Map mymap = *((Map *)config->mapnik_map);
985
986    /* If you have a flaky database connection you might want to set this to > 1.
987     * This is really a brute force way of handling problems. */
988    int attempts = 1;
989
990    while(attempts-- > 0)
991    {
992        try 
993        {
994            std::clog << "Configuring map parameters" << std::endl;
995            char init[256];
996            for (char *i = srs; *i; i++) *i=tolower(*i);
997            snprintf(init, 256, "+init=%s",srs);
998            mymap.set_srs(init);
999            mymap.zoomToBox(Envelope<double>(bboxvals[0], bboxvals[1], bboxvals[2], bboxvals[3]));
1000            mymap.resize(n_width, n_height);
1001
1002            /*
1003             * currently disabled. always render all layers.
1004
1005            // remove those layers that are not in the WMS "layers"
1006            // parameter. - unfortunately the map object doesn't
1007            // allow us to acces the layers non-const, otherweise
1008            // instead of copying the map object and removing layers,
1009            // we'd just set them invisible!
1010           
1011            std::vector<mapnik::Layer> ml = mymap.layers();
1012            if (layermap.size())
1013            {
1014                for (int i=ml.size()-1; i>=0; i--)
1015                {
1016                    if (layermap.find(ml[i].name()) == layermap.end())
1017                    {
1018                        mymap.removeLayer(i);
1019                    }
1020                }
1021            }
1022            */
1023
1024            Image32 buf(mymap.getWidth(),mymap.getHeight());
1025            agg_renderer<Image32> ren(mymap, buf);
1026
1027            /*
1028             * broken clients will request a width/height that does not match, so this log line
1029             * is worth looking out for. we will fix this to return the right image but quality
1030             * suffers.
1031             */
1032            std::clog << "Start rendering (computed height is " << mymap.getHeight() << ", requested is " << n_height << ")" << std::endl;
1033            ren.apply();
1034            std::clog << "Rendering complete" << std::endl;
1035           
1036            attempts = 0; // exit loop later
1037
1038            if (!strcmp(format, "image/png"))
1039            {
1040                ap_set_content_type(r, "image/png");
1041                std::clog << "Start streaming PNG response" << std::endl;
1042                send_png_response(r, buf, n_height, false);
1043                std::clog << "PNG response complete" << std::endl;
1044            }
1045            else if (!strcmp(format, "image/png8"))
1046            {
1047                ap_set_content_type(r, "image/png");
1048                std::clog << "Start streaming PNG response (palette image)" << std::endl;
1049                send_png_response(r, buf, n_height, true);
1050                std::clog << "PNG response complete" << std::endl;
1051            }
1052            else if (!strcmp(format, "image/jpeg"))
1053            {
1054                ap_set_content_type(r, "image/jpeg");
1055                std::clog << "Start streaming JPEG response" << std::endl;
1056                send_jpeg_response(r, buf, n_height);
1057                std::clog << "JPEG response complete" << std::endl;
1058            }
1059            else
1060            {
1061                rv = wms_error(r, "InvalidFormat", "Cannot deliver requested data format ('%s')", format);
1062            }
1063        }
1064        catch ( const mapnik::config_error & ex )
1065        {
1066            rv = http_error(r, HTTP_INTERNAL_SERVER_ERROR, "mapnik config exception: %s", ex.what());
1067        }
1068        catch ( const std::exception & ex )
1069        {
1070            rv = http_error(r, HTTP_INTERNAL_SERVER_ERROR, "standard exception (pid %d): %s", getpid(), ex.what());
1071        }
1072        catch ( ... )
1073        {
1074            rv = http_error(r, HTTP_INTERNAL_SERVER_ERROR, "other exception");
1075        }
1076        if (attempts) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "re-trying...");
1077    }
1078
1079    // reset clog stream
1080    if (old) 
1081    {
1082        std::clog.rdbuf(old);
1083        delete o;
1084        fclose(f);
1085    }
1086
1087    if (config->debug) delete (mapnik::Map *) config->mapnik_map;
1088
1089    return rv;
1090}
1091
1092extern "C" 
1093{
1094    /**
1095     * Called by the Apache hook. Parses request and delegates to
1096     * proper method.
1097     */
1098    int wms_handle(request_rec *r)
1099    {
1100        char *args = r->args ? apr_pstrdup(r->pool, r->args) : 0;
1101        char *amp;
1102        char *current = args;
1103        char *equals;
1104        bool end = (args == 0);
1105
1106        const char *request = 0;
1107        const char *service = 0;
1108        const char *version = 0;
1109
1110        /* parse URL parameters into variables */
1111        while (!end)
1112        {
1113            amp = index(current, '&');
1114            if (amp == 0) { amp = current + strlen(current); end = true; }
1115            *amp = 0;
1116            equals = index(current, '=');
1117            if (equals > current)
1118            {
1119                *equals++ = 0;
1120                decode_uri_inplace(current);
1121                decode_uri_inplace(equals);
1122
1123                if (!strcasecmp(current, "REQUEST")) request = equals;
1124                else if (!strcasecmp(current, "SERVICE")) service = equals;
1125                else if (!strcasecmp(current, "VERSION")) version = equals;
1126            }
1127            current = amp + 1;
1128        }
1129
1130        if (!request)
1131        {
1132            return wms_error(r, "MissingDimensionValue", "Required parameter 'request' not set.");
1133        }
1134        else if (!strcmp(request, "GetMap"))
1135        {
1136            return wms_getmap(r); 
1137        }
1138        else if (!strcmp(request, "GetCapabilities"))
1139        {
1140            return wms_getcap(r);
1141        }
1142        else
1143        {
1144            return wms_error(r, "Request type '%s' is not supported.", request);
1145        }
1146    }
1147}
Note: See TracBrowser for help on using the repository browser.