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

Last change on this file since 15889 was 15839, checked in by apmon, 11 years ago

[renderd] Add the ability to explicitly expire renderd tiles from proxys via HTCP

For large setups of mod_tile, and renderd, it may be beneficial to have a reverse proxy infront of the main tile server.
However when using a fast updating db e.g. running off the minutly diffs, the proxy may either deliver stale content, or the
cache expiry has to be set very short, which limits the effectiveness of the proxy. Instead, this patch adds an explicit
mechanism to renderd to signal to the proxy which tiles have been rerendered and thus should be discarded from the cache.

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