source: subversion/applications/utils/mod_tile/gen_tile.cpp @ 28454

Last change on this file since 28454 was 28134, checked in by ldp, 8 years ago

+add mapnik/config_error.hpp. Fixes #4303.

File size: 17.8 KB
Line 
1#include <mapnik/version.hpp>
2#include <mapnik/map.hpp>
3#include <mapnik/datasource_cache.hpp>
4#include <mapnik/agg_renderer.hpp>
5#include <mapnik/filter_factory.hpp>
6#include <mapnik/color_factory.hpp>
7#include <mapnik/load_map.hpp>
8#include <mapnik/image_util.hpp>
9#include <mapnik/config_error.hpp>
10
11#include <iostream>
12#include <fstream>
13#include <sys/stat.h>
14#include <sys/types.h>
15#include <sys/time.h>
16#include <dirent.h>
17#include <syslog.h>
18#include <unistd.h>
19#include <pthread.h>
20
21#include "gen_tile.h"
22#include "render_config.h"
23#include "daemon.h"
24#include "protocol.h"
25#include "dir_utils.h"
26#include "store.h"
27
28#ifdef HTCP_EXPIRE_CACHE
29#include <sys/socket.h>
30#include <netdb.h>
31#endif
32
33#if MAPNIK_VERSION >= 800
34#include <mapnik/box2d.hpp>
35#define Image32 image_32
36#define ImageData32 image_data_32
37#define Envelope box2d
38#define zoomToBox zoom_to_box
39#else
40#include <mapnik/envelope.hpp>
41#endif
42
43
44using namespace mapnik;
45#ifndef DEG_TO_RAD
46#define DEG_TO_RAD (M_PI/180)
47#endif
48#ifndef RAD_TO_DEG
49#define RAD_TO_DEG (180/M_PI)
50#endif
51
52#ifdef METATILE
53#define RENDER_SIZE (256 * (METATILE + 1))
54#else
55#define RENDER_SIZE (512)
56#endif
57
58static const int minZoom = 0;
59static const int maxZoom = MAX_ZOOM;
60
61typedef struct {
62    char xmlname[XMLCONFIG_MAX];
63    char xmlfile[PATH_MAX];
64    char tile_dir[PATH_MAX];
65    Map map;
66    projection prj;
67    char xmluri[PATH_MAX];
68    char host[PATH_MAX];
69    char htcphost[PATH_MAX];
70    int htcpsock;
71    int ok;
72} xmlmapconfig;
73
74
75class SphericalProjection
76{
77    double *Ac, *Bc, *Cc, *zc;
78
79    public:
80        SphericalProjection(int levels=MAX_ZOOM) {
81            Ac = new double[levels];
82            Bc = new double[levels];
83            Cc = new double[levels];
84            zc = new double[levels];
85            int d, c = 256;
86            for (d=0; d<levels; d++) {
87                int e = c/2;
88                Bc[d] = c/360.0;
89                Cc[d] = c/(2 * M_PI);
90                zc[d] = e;
91                Ac[d] = c;
92                c *=2;
93            }
94        }
95
96        void fromLLtoPixel(double &x, double &y, int zoom) {
97            double d = zc[zoom];
98            double f = minmax(sin(DEG_TO_RAD * y),-0.9999,0.9999);
99            x = round(d + x * Bc[zoom]);
100            y = round(d + 0.5*log((1+f)/(1-f))*-Cc[zoom]);
101        }
102        void fromPixelToLL(double &x, double &y, int zoom) {
103            double e = zc[zoom];
104            double g = (y - e)/-Cc[zoom];
105            x = (x - e)/Bc[zoom];
106            y = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * M_PI);
107        }
108
109    private:
110        double minmax(double a, double b, double c)
111        {
112            #define MIN(x,y) ((x)<(y)?(x):(y))
113            #define MAX(x,y) ((x)>(y)?(x):(y))
114            a = MAX(a,b);
115            a = MIN(a,c);
116            return a;
117        }
118};
119
120static SphericalProjection tiling(maxZoom+1);
121
122static void load_fonts(const char *font_dir, int recurse)
123{
124    DIR *fonts = opendir(font_dir);
125    struct dirent *entry;
126    char path[PATH_MAX]; // FIXME: Eats lots of stack space when recursive
127
128    if (!fonts) {
129        syslog(LOG_CRIT, "Unable to open font directory: %s", font_dir);
130        return;
131    }
132
133    while ((entry = readdir(fonts))) {
134        struct stat b;
135        char *p;
136
137        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
138            continue;
139        snprintf(path, sizeof(path), "%s/%s", font_dir, entry->d_name);
140        if (stat(path, &b))
141            continue;
142        if (S_ISDIR(b.st_mode)) {
143            if (recurse)
144                load_fonts(path, recurse);
145            continue;
146        }
147        p = strrchr(path, '.');
148        if (p && !strcmp(p, ".ttf")) {
149            syslog(LOG_DEBUG, "DEBUG: Loading font: %s", path);
150            freetype_engine::register_font(path);
151        }
152    }
153    closedir(fonts);
154}
155
156#ifdef HTCP_EXPIRE_CACHE
157
158/**
159 * This function sends a HTCP cache clr request for a given
160 * URL.
161 * RFC for HTCP can be found at http://www.htcp.org/
162 */
163void cache_expire_url(int sock, char * url) {
164    char * buf;
165
166    if (sock < 0) {
167            return;
168    }
169
170    int idx = 0;
171    int url_len;
172
173    url_len = strlen(url);
174    buf = (char *) malloc(12 + 22 + url_len);
175    if (!buf) {
176        return;
177    }
178
179    idx = 0;
180
181    //16 bit: Overall length of the datagram packet, including this header
182    *((uint16_t *) (&buf[idx])) = htons(12 + 22 + url_len);
183    idx += 2;
184
185    //HTCP version. Currently at 0.0
186    buf[idx++] = 0; //Major version
187    buf[idx++] = 0; //Minor version
188
189    //Length of HTCP data, including this field
190    *((uint16_t *) (&buf[idx])) = htons(8 + 22 + url_len);
191    idx += 2;
192
193    //HTCP opcode CLR=4
194    buf[idx++] = 4;
195    //Reserved
196    buf[idx++] = 0;
197
198    //32 bit transaction id;
199    *((uint32_t *) (&buf[idx])) = htonl(255);
200    idx += 4;
201
202    buf[idx++] = 0;
203    buf[idx++] = 0; //HTCP reason
204
205    //Length of the Method string
206    *((uint16_t *) (&buf[idx])) = htons(4);
207    idx += 2;
208
209    ///Method string
210    memcpy(&buf[idx], "HEAD", 4);
211    idx += 4;
212
213    //Length of the url string
214    *((uint16_t *) (&buf[idx])) = htons(url_len);
215    idx += 2;
216
217    //Url string
218    memcpy(&buf[idx], url, url_len);
219    idx += url_len;
220
221    //Length of version string
222    *((uint16_t *) (&buf[idx])) = htons(8);
223    idx += 2;
224
225    //version string
226    memcpy(&buf[idx], "HTTP/1.1", 8);
227    idx += 8;
228
229    //Length of request headers. Currently 0 as we don't have any headers to send
230    *((uint16_t *) (&buf[idx])) = htons(0);
231
232    if (send(sock, (void *) buf, (12 + 22 + url_len), 0) < (12 + 22 + url_len)) {
233        syslog(LOG_ERR, "Failed to send HTCP purge for %s\n", url);
234    };
235
236    free(buf);
237}
238
239void cache_expire(int sock, char * host, char * uri, int x, int y, int z) {
240
241    if (sock < 0) {
242        return;
243    }
244    char * url = (char *)malloc(1024);
245    sprintf(url,"http://%s%s%i/%i/%i.png", host, uri, z,x,y);
246    cache_expire_url(sock, url);
247    free(url);
248}
249
250int init_cache_expire(char * htcphost) {
251    struct addrinfo hints;
252    struct addrinfo *result, *rp;
253    int sfd, s;
254
255    /* Obtain address(es) matching host/port */
256
257    memset(&hints, 0, sizeof(struct addrinfo));
258    hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
259    hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
260    hints.ai_flags = 0;
261    hints.ai_protocol = 0; /* Any protocol */
262
263    s = getaddrinfo(htcphost, HTCP_EXPIRE_CACHE_PORT, &hints, &result);
264    if (s != 0) {
265        syslog(LOG_ERR, "Failed to lookup HTCP cache host: %s", gai_strerror(s));
266        return -1;
267    }
268
269    /* getaddrinfo() returns a list of address structures.
270     Try each address until we successfully connect(2).
271     If socket(2) (or connect(2)) fails, we (close the socket
272     and) try the next address. */
273
274    for (rp = result; rp != NULL; rp = rp->ai_next) {
275        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
276        if (sfd == -1)
277            continue;
278
279        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
280            break; /* Success */
281
282        close(sfd);
283    }
284
285    if (rp == NULL) { /* No address succeeded */
286        syslog(LOG_ERR, "Failed to create HTCP cache socket");
287        return -1;
288    }
289
290    freeaddrinfo(result); /* No longer needed */
291
292    return sfd;
293
294}
295#endif
296
297
298
299#ifdef METATILE
300
301class metaTile {
302    public:
303        metaTile(const std::string &xmlconfig, int x, int y, int z):
304            x_(x), y_(y), z_(z), xmlconfig_(xmlconfig)
305        {
306            clear();
307        }
308
309        void clear()
310        {
311            for (int x = 0; x < METATILE; x++)
312                for (int y = 0; y < METATILE; y++)
313                    tile[x][y] = "";
314        }
315
316        void set(int x, int y, const std::string &data)
317        {
318            tile[x][y] = data;
319        }
320
321        const std::string get(int x, int y)
322        {
323            return tile[x][y];
324        }
325
326        // Returns the offset within the meta-tile index table
327        int xyz_to_meta_offset(int x, int y, int z)
328        {
329            unsigned char mask = METATILE - 1;
330            return (x & mask) * METATILE + (y & mask);
331        }
332
333        void save(const char *tile_dir)
334        {
335            int ox, oy, limit;
336            size_t offset;
337            struct meta_layout m;
338            char meta_path[PATH_MAX];
339            struct entry offsets[METATILE * METATILE];
340
341            memset(&m, 0, sizeof(m));
342            memset(&offsets, 0, sizeof(offsets));
343
344            xyz_to_meta(meta_path, sizeof(meta_path), tile_dir, xmlconfig_.c_str(), x_, y_, z_);
345            std::stringstream ss;
346            ss << std::string(meta_path) << "." << pthread_self();
347            std::string tmp(ss.str());
348
349            if (mkdirp(tmp.c_str()))
350              throw std::runtime_error("An error when creating tile directory");
351
352            try {
353              std::ofstream file;
354              file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
355              file.open(tmp.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
356
357              // Create and write header
358              m.count = METATILE * METATILE;
359              memcpy(m.magic, META_MAGIC, strlen(META_MAGIC));
360              m.x = x_;
361              m.y = y_;
362              m.z = z_;
363              file.write((const char *)&m, sizeof(m));
364             
365              offset = header_size;
366              limit = (1 << z_);
367              limit = MIN(limit, METATILE);
368             
369              // Generate offset table
370              for (ox=0; ox < limit; ox++) {
371                for (oy=0; oy < limit; oy++) {
372                  int mt = xyz_to_meta_offset(x_ + ox, y_ + oy, z_);
373                  offsets[mt].offset = offset;
374                  offsets[mt].size   = tile[ox][oy].size();
375                  offset += offsets[mt].size;
376                }
377              }
378              file.write((const char *)&offsets, sizeof(offsets));
379             
380              // Write tiles
381              for (ox=0; ox < limit; ox++) {
382                for (oy=0; oy < limit; oy++) {
383                  file.write((const char *)tile[ox][oy].data(), tile[ox][oy].size());
384                }
385              }
386             
387              file.close();
388             
389              rename(tmp.c_str(), meta_path);
390            } catch (std::ios_base::failure) {
391              // Remove the temporary file
392              unlink(tmp.c_str());
393              throw std::runtime_error("An error occured when writing a metatile\n");
394            }
395            //printf("Produced .meta: %s\n", meta_path);
396        }
397
398#ifdef HTCP_EXPIRE_CACHE
399        void expire_tiles(int sock, char * host, char * uri) {
400            if (sock < 0) {
401                return;
402            }
403            syslog(LOG_INFO, "Purging metatile via HTCP cache expiry");
404            int ox, oy;
405            int limit = (1 << z_);
406            limit = MIN(limit, METATILE);
407
408            // Generate offset table
409            for (ox=0; ox < limit; ox++) {
410                for (oy=0; oy < limit; oy++) {
411                    cache_expire(sock, host, uri, (x_ + ox), (y_ + oy), z_);
412                }
413            }
414        }
415#endif
416        int x_, y_, z_;
417        std::string xmlconfig_;
418        std::string tile[METATILE][METATILE];
419        static const int header_size = sizeof(struct meta_layout) + (sizeof(struct entry) * (METATILE * METATILE));
420};
421
422
423static enum protoCmd render(Map &m, char *xmlname, projection &prj, int x, int y, int z, unsigned int size, metaTile &tiles)
424{
425    int render_size = 256 * size;
426    double p0x = x * 256;
427    double p0y = (y + size) * 256;
428    double p1x = (x + size) * 256;
429    double p1y = y * 256;
430
431    //std::cout << "META TILE " << z << " " << x << "-" << x+size-1 << " " << y << "-" << y+size-1 << "\n";
432
433    tiling.fromPixelToLL(p0x, p0y, z);
434    tiling.fromPixelToLL(p1x, p1y, z);
435
436    prj.forward(p0x, p0y);
437    prj.forward(p1x, p1y);
438
439    Envelope<double> bbox(p0x, p0y, p1x,p1y);
440    m.resize(render_size, render_size);
441    m.zoomToBox(bbox);
442    m.set_buffer_size(128);
443    //m.zoom(size+1);
444
445    Image32 buf(render_size, render_size);
446    agg_renderer<Image32> ren(m,buf);
447    ren.apply();
448
449    // Split the meta tile into an NxN grid of tiles
450    unsigned int xx, yy;
451    for (yy = 0; yy < size; yy++) {
452        for (xx = 0; xx < size; xx++) {
453            image_view<ImageData32> vw(xx * 256, yy * 256, 256, 256, buf.data());
454            tiles.set(xx, yy, save_to_string(vw, "png256"));
455        }
456    }
457//    std::cout << "DONE TILE " << xmlname << " " << z << " " << x << "-" << x+size-1 << " " << y << "-" << y+size-1 << "\n";
458    syslog(LOG_DEBUG, "DEBUG: DONE TILE %s %d %d-%d %d-%d", xmlname, z, x, x+size-1, y, y+size-1);
459    return cmdDone; // OK
460}
461#else
462static enum protoCmd render(Map &m, const char *tile_dir, char *xmlname, projection &prj, int x, int y, int z)
463{
464    char filename[PATH_MAX];
465    char tmp[PATH_MAX];
466    double p0x = x * 256.0;
467    double p0y = (y + 1) * 256.0;
468    double p1x = (x + 1) * 256.0;
469    double p1y = y * 256.0;
470
471    tiling.fromPixelToLL(p0x, p0y, z);
472    tiling.fromPixelToLL(p1x, p1y, z);
473
474    prj.forward(p0x, p0y);
475    prj.forward(p1x, p1y);
476
477    Envelope<double> bbox(p0x, p0y, p1x,p1y);
478    bbox.width(bbox.width() * 2);
479    bbox.height(bbox.height() * 2);
480    m.zoomToBox(bbox);
481
482    Image32 buf(RENDER_SIZE, RENDER_SIZE);
483    agg_renderer<Image32> ren(m,buf);
484    ren.apply();
485
486    xyz_to_path(filename, sizeof(filename), tile_dir, xmlname, x, y, z);
487    if (mkdirp(filename))
488        return cmdNotDone;
489    snprintf(tmp, sizeof(tmp), "%s.tmp", filename);
490
491    image_view<ImageData32> vw(128, 128, 256, 256, buf.data());
492    //std::cout << "Render " << z << " " << x << " " << y << " " << filename << "\n";
493    save_to_file(vw, tmp, "png256");
494    if (rename(tmp, filename)) {
495        perror(tmp);
496        return cmdNotDone;
497    }
498
499    return cmdDone; // OK
500}
501#endif
502
503
504void render_init(const char *plugins_dir, const char* font_dir, int font_dir_recurse)
505{
506    syslog(LOG_INFO, "Renderd is using mapnik version %i.%i.%i", (MAPNIK_VERSION / 100000), ((MAPNIK_VERSION / 100) % 1000), (MAPNIK_VERSION % 100));
507    datasource_cache::instance()->register_datasources(plugins_dir);
508    load_fonts(font_dir, font_dir_recurse);
509}
510
511void *render_thread(void * arg)
512{
513    xmlconfigitem * parentxmlconfig = (xmlconfigitem *)arg;
514    xmlmapconfig maps[XMLCONFIGS_MAX];
515    int i,iMaxConfigs;
516
517    for (iMaxConfigs = 0; iMaxConfigs < XMLCONFIGS_MAX; ++iMaxConfigs) {
518        if (parentxmlconfig[iMaxConfigs].xmlname[0] == 0 || parentxmlconfig[iMaxConfigs].xmlfile[0] == 0) break;
519        strcpy(maps[iMaxConfigs].xmlname, parentxmlconfig[iMaxConfigs].xmlname);
520        strcpy(maps[iMaxConfigs].xmlfile, parentxmlconfig[iMaxConfigs].xmlfile);
521        strcpy(maps[iMaxConfigs].tile_dir, parentxmlconfig[iMaxConfigs].tile_dir);
522        maps[iMaxConfigs].map = Map(RENDER_SIZE, RENDER_SIZE);
523        maps[iMaxConfigs].ok = 1;
524        try {
525          load_map(maps[iMaxConfigs].map, maps[iMaxConfigs].xmlfile);
526        } catch (mapnik::config_error &ex) {
527          syslog(LOG_ERR, "An error occurred while loading the map layer '%s': %s", maps[iMaxConfigs].xmlname, ex.what());
528          maps[iMaxConfigs].ok = 0;
529        }
530        maps[iMaxConfigs].prj = projection(maps[iMaxConfigs].map.srs());
531#ifdef HTCP_EXPIRE_CACHE
532        strcpy(maps[iMaxConfigs].xmluri, parentxmlconfig[iMaxConfigs].xmluri);
533        strcpy(maps[iMaxConfigs].host, parentxmlconfig[iMaxConfigs].host);
534        strcpy(maps[iMaxConfigs].htcphost, parentxmlconfig[iMaxConfigs].htcpip);
535        if (strlen(maps[iMaxConfigs].htcphost) > 0) {
536            maps[iMaxConfigs].htcpsock = init_cache_expire(
537                    maps[iMaxConfigs].htcphost);
538            if (maps[iMaxConfigs].htcpsock > 0) {
539                syslog(LOG_INFO, "Successfully opened socket for HTCP cache expiry");
540            } else {
541                syslog(LOG_ERR, "Failed to opened socket for HTCP cache expiry");
542            }
543        } else {
544            maps[iMaxConfigs].htcpsock = -1;
545        }
546#endif
547    }
548
549    while (1) {
550        enum protoCmd ret;
551        struct item *item = fetch_request();
552        if (item) {
553            struct protocol *req = &item->req;
554#ifdef METATILE
555            // At very low zoom the whole world may be smaller than METATILE
556            unsigned int size = MIN(METATILE, 1 << req->z);
557            for (i = 0; i < iMaxConfigs; ++i) {
558                if (!strcmp(maps[i].xmlname, req->xmlname)) {
559                    metaTile tiles(req->xmlname, item->mx, item->my, req->z);
560                   
561                    if (maps[i].ok) {
562                        timeval tim;
563                        gettimeofday(&tim, NULL);
564                        long t1=tim.tv_sec*1000+(tim.tv_usec/1000);
565
566                        ret = render(maps[i].map, req->xmlname, maps[i].prj, item->mx, item->my, req->z, size, tiles);
567
568                        gettimeofday(&tim, NULL);
569                        long t2=tim.tv_sec*1000+(tim.tv_usec/1000);
570                        syslog(LOG_DEBUG, "DEBUG: DONE TILE %s %d %d-%d %d-%d in %.3lf seconds", 
571                               req->xmlname, req->z, item->mx, item->mx+size-1, item->my, item->my+size-1, (t2 - t1)/1000.0);
572                        statsRenderFinish(req->z, t2 - t1);
573                    } else {
574                        syslog(LOG_ERR, "Received request for map layer '%s' which failed to load", req->xmlname);
575                        ret = cmdNotDone;
576                    }
577
578                    if (ret == cmdDone) {
579                      try {
580                        tiles.save(maps[i].tile_dir);
581                      } catch (...) {
582                        // Treat any error as fatal and request end of processing
583                        syslog(LOG_ERR, "Received error when writing metatile to disk, requesting exit.");
584                        ret = cmdNotDone;
585                        request_exit();
586                      }
587#ifdef HTCP_EXPIRE_CACHE
588                        tiles.expire_tiles(maps[i].htcpsock,maps[i].host,maps[i].xmluri);
589#endif
590                    }
591#else
592                    ret = render(maps[i].map, maps[i].tile_dir, req->xmlname, maps[i].prj, req->x, req->y, req->z);
593#ifdef HTCP_EXPIRE_CACHE
594                    cache_expire(maps[i].htcpsock,maps[i].host, maps[i].xmluri, req->x,req->y,req->z);
595#endif
596#endif
597                    send_response(item, ret);
598                    break;
599               }
600            }
601            if (i == iMaxConfigs){
602                syslog(LOG_ERR, "No map for: %s", req->xmlname);
603            }
604        } else {
605            sleep(1); // TODO: Use an event to indicate there are new requests
606        }
607    }
608    return NULL;
609}
610
Note: See TracBrowser for help on using the repository browser.