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

Last change on this file since 29256 was 29256, checked in by apmon, 7 years ago

Renderd seg faulted if the database connection was reset

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