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

Last change on this file since 22934 was 22934, checked in by apmon, 9 years ago

[renderd] Add a munin graph to show how much time renderd spends rendering tiles of various zoom levels

With this graph, it is hopefully possible to see where (i.e. towards which zoomlevel) most of the rendering time goes,
and thus might give some indication where optimisations in the style sheet are best focused at, if performance is an issue.
It useses purely wallclock time and thus does not account for individual resources.

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