mirror of
https://github.com/openstreetmap/mod_tile.git
synced 2025-07-25 15:04:30 +00:00
mod_tile: Apache module and rendering daemon for serving OSM tiles
This commit is contained in:
51
Makefile
Normal file
51
Makefile
Normal file
@ -0,0 +1,51 @@
|
||||
builddir = .
|
||||
top_dir:=$(shell /usr/sbin/apxs -q exp_installbuilddir)
|
||||
top_dir:=$(shell /usr/bin/dirname ${top_dir})
|
||||
|
||||
top_srcdir = ${top_dir}
|
||||
top_builddir = ${top_dir}
|
||||
|
||||
include ${top_builddir}/build/special.mk
|
||||
|
||||
CXX := g++
|
||||
|
||||
APXS = apxs
|
||||
APACHECTL = apachectl
|
||||
EXTRA_CFLAGS = -I$(builddir)
|
||||
|
||||
EXTRA_CPPFLAGS += -g -O2 -Wall
|
||||
EXTRA_LDFLAGS += $(shell pkg-config --libs libagg)
|
||||
|
||||
all: local-shared-build renderd
|
||||
|
||||
clean:
|
||||
rm -f *.o *.lo *.slo *.la .libs/*
|
||||
rm -f renderd
|
||||
|
||||
RENDER_CPPFLAGS += -g -O2 -Wall
|
||||
RENDER_CPPFLAGS += -I/usr/local/include/mapnik
|
||||
RENDER_CPPFLAGS += $(shell pkg-config --cflags freetype2)
|
||||
RENDER_CPPFLAGS += $(shell Magick++-config --cxxflags --cppflags)
|
||||
RENDER_CPPFLAGS += $(shell pkg-config --cflags libagg)
|
||||
|
||||
RENDER_LDFLAGS += -g
|
||||
RENDER_LDFLAGS += -lmapnik -L/usr/local/lib64
|
||||
RENDER_LDFLAGS += $(shell pkg-config --libs freetype2)
|
||||
RENDER_LDFLAGS += $(shell Magick++-config --ldflags --libs)
|
||||
RENDER_LDFLAGS += $(shell pkg-config --libs libagg)
|
||||
|
||||
renderd: daemon.c gen_tile.cpp
|
||||
$(CXX) -o $@ $^ $(RENDER_LDFLAGS) $(RENDER_CPPFLAGS)
|
||||
|
||||
|
||||
MYSQL_CFLAGS += -g -O2 -Wall
|
||||
MYSQL_CFLAGS += $(shell mysql_config --cflags)
|
||||
|
||||
MYSQL_LDFLAGS += $(shell mysql_config --libs)
|
||||
|
||||
mysql2file: mysql2file.c
|
||||
$(CC) $(MYSQL_CFLAGS) $(MYSQL_LDFLAGS) -o $@ $^
|
||||
|
||||
# Not sure why this is not created automatically
|
||||
.deps:
|
||||
touch .deps
|
413
daemon.c
Normal file
413
daemon.c
Normal file
@ -0,0 +1,413 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
|
||||
#include "gen_tile.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#define QUEUE_MAX (10)
|
||||
#define MAX_CONNECTIONS (10)
|
||||
|
||||
#define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
#define FD_INVALID (-1)
|
||||
#define REQ_LIMIT (10)
|
||||
#define DIRTY_LIMIT (1000 * 1000)
|
||||
#define NUM_THREADS (4)
|
||||
|
||||
static pthread_t render_threads[NUM_THREADS];
|
||||
static struct sigaction sigPipeAction;
|
||||
|
||||
|
||||
void pipe_handler(__attribute__((unused)) int sigNum)
|
||||
{
|
||||
// Needed in case the client closes the connection
|
||||
// before we send a response.
|
||||
// FIXME: is fprintf really safe in signal handler?
|
||||
//fprintf(stderr, "Caught SIGPIPE\n");
|
||||
}
|
||||
|
||||
// Build parent directories for the specified file name
|
||||
// Note: the part following the trailing / is ignored
|
||||
// e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b
|
||||
static int mkdirp(const char *path) {
|
||||
struct stat s;
|
||||
char tmp[PATH_MAX];
|
||||
char *p;
|
||||
|
||||
strncpy(tmp, path, sizeof(tmp));
|
||||
|
||||
// Look for parent directory
|
||||
p = strrchr(tmp, '/');
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
*p = '\0';
|
||||
|
||||
if (!stat(tmp, &s))
|
||||
return !S_ISDIR(s.st_mode);
|
||||
*p = '/';
|
||||
// Walk up the path making sure each element is a directory
|
||||
p = tmp;
|
||||
if (!*p)
|
||||
return 0;
|
||||
p++; // Ignore leading /
|
||||
while (*p) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (!stat(tmp, &s)) {
|
||||
if (!S_ISDIR(s.st_mode))
|
||||
return 1;
|
||||
} else if (mkdir(tmp, 0777))
|
||||
return 1;
|
||||
*p = '/';
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct item reqHead, dirtyHead, renderHead;
|
||||
static int reqNum, dirtyNum;
|
||||
static pthread_mutex_t qLock;
|
||||
|
||||
struct item *fetch_request(void)
|
||||
{
|
||||
struct item *item = NULL;
|
||||
|
||||
pthread_mutex_lock(&qLock);
|
||||
|
||||
if (reqNum) {
|
||||
item = reqHead.next;
|
||||
reqNum--;
|
||||
} else if (dirtyNum) {
|
||||
item = dirtyHead.next;
|
||||
dirtyNum--;
|
||||
}
|
||||
if (item) {
|
||||
item->next->prev = item->prev;
|
||||
item->prev->next = item->next;
|
||||
|
||||
item->prev = &renderHead;
|
||||
item->next = renderHead.next;
|
||||
renderHead.next->prev = item;
|
||||
renderHead.next = item;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&qLock);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void delete_request(struct item *item)
|
||||
{
|
||||
pthread_mutex_lock(&qLock);
|
||||
|
||||
item->next->prev = item->prev;
|
||||
item->prev->next = item->next;
|
||||
|
||||
pthread_mutex_unlock(&qLock);
|
||||
free(item);
|
||||
}
|
||||
|
||||
void clear_requests(int fd)
|
||||
{
|
||||
struct item *item;
|
||||
|
||||
pthread_mutex_lock(&qLock);
|
||||
item = reqHead.next;
|
||||
while (item != &reqHead) {
|
||||
if (item->fd == fd)
|
||||
item->fd = FD_INVALID;
|
||||
item = item->next;
|
||||
}
|
||||
item = renderHead.next;
|
||||
while (item != &renderHead) {
|
||||
if (item->fd == fd)
|
||||
item->fd = FD_INVALID;
|
||||
item = item->next;
|
||||
}
|
||||
pthread_mutex_unlock(&qLock);
|
||||
}
|
||||
|
||||
void send_response(struct item *item, enum protoCmd rsp)
|
||||
{
|
||||
struct protocol *req = &item->req;
|
||||
int ret;
|
||||
|
||||
pthread_mutex_lock(&qLock);
|
||||
|
||||
if ((item->fd != FD_INVALID) && (req->cmd == cmdRender)) {
|
||||
req->cmd = rsp;
|
||||
//fprintf(stderr, "Sending message %d to %d\n", rsp, item->fd);
|
||||
ret = send(item->fd, req, sizeof(*req), 0);
|
||||
if (ret != sizeof(*req))
|
||||
perror("send error during send_done");
|
||||
}
|
||||
pthread_mutex_unlock(&qLock);
|
||||
}
|
||||
|
||||
static inline const char *cmdStr(enum protoCmd c)
|
||||
{
|
||||
switch (c) {
|
||||
case cmdIgnore: return "Ignore";
|
||||
case cmdRender: return "Render";
|
||||
case cmdDirty: return "Dirty";
|
||||
case cmdDone: return "Done";
|
||||
case cmdNotDone: return "NotDone";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
int pending(struct item *test)
|
||||
{
|
||||
// check all queues and render list to see if this request already queued
|
||||
// call with qLock held
|
||||
struct item *item;
|
||||
|
||||
item = reqHead.next;
|
||||
while (item != &reqHead) {
|
||||
if ((item->req.x == test->req.x) && (item->req.y == test->req.y) && (item->req.z == test->req.z))
|
||||
return 1;
|
||||
item = item->next;
|
||||
}
|
||||
item = dirtyHead.next;
|
||||
while (item != &dirtyHead) {
|
||||
if ((item->req.x == test->req.x) && (item->req.y == test->req.y) && (item->req.z == test->req.z))
|
||||
return 1;
|
||||
item = item->next;
|
||||
}
|
||||
item = renderHead.next;
|
||||
while (item != &renderHead) {
|
||||
if ((item->req.x == test->req.x) && (item->req.y == test->req.y) && (item->req.z == test->req.z))
|
||||
return 1;
|
||||
item = item->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum protoCmd rx_request(const struct protocol *req, int fd)
|
||||
{
|
||||
struct item *list = NULL, *item;
|
||||
|
||||
if (req->ver != 1) {
|
||||
fprintf(stderr, "Bad protocol version %d\n", req->ver);
|
||||
return cmdIgnore;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s z(%d), x(%d), y(%d), path(%s)\n",
|
||||
cmdStr(req->cmd), req->z, req->x, req->y, req->path);
|
||||
|
||||
if ((req->cmd != cmdRender) && (req->cmd != cmdDirty))
|
||||
return cmdIgnore;
|
||||
|
||||
if (mkdirp(req->path))
|
||||
return cmdNotDone;
|
||||
|
||||
item = (struct item *)malloc(sizeof(*item));
|
||||
if (!item) {
|
||||
fprintf(stderr, "malloc failed\n");
|
||||
return cmdNotDone;
|
||||
}
|
||||
item->req = *req;
|
||||
|
||||
pthread_mutex_lock(&qLock);
|
||||
|
||||
if (pending(item)) {
|
||||
pthread_mutex_unlock(&qLock);
|
||||
free(item);
|
||||
return cmdNotDone; // No way to wait on a pending tile
|
||||
}
|
||||
|
||||
if ((req->cmd == cmdRender) && (reqNum < REQ_LIMIT)) {
|
||||
list = &reqHead;
|
||||
reqNum++;
|
||||
item->fd = fd;
|
||||
} else if (dirtyNum < DIRTY_LIMIT) {
|
||||
list = &dirtyHead;
|
||||
dirtyNum++;
|
||||
item->fd = FD_INVALID; // No response after render
|
||||
}
|
||||
|
||||
if (list) {
|
||||
item->next = list;
|
||||
item->prev = list->prev;
|
||||
item->prev->next = item;
|
||||
list->prev = item;
|
||||
} else
|
||||
free(item);
|
||||
|
||||
pthread_mutex_unlock(&qLock);
|
||||
|
||||
return (list == &reqHead)?cmdIgnore:cmdNotDone;
|
||||
}
|
||||
|
||||
|
||||
void process_loop(int listen_fd)
|
||||
{
|
||||
int num_connections = 0;
|
||||
int connections[MAX_CONNECTIONS];
|
||||
|
||||
bzero(connections, sizeof(connections));
|
||||
|
||||
while (1) {
|
||||
struct sockaddr_un in_addr;
|
||||
socklen_t in_addrlen = sizeof(in_addr);
|
||||
fd_set rd;
|
||||
int incoming, num, nfds, i;
|
||||
|
||||
FD_ZERO(&rd);
|
||||
FD_SET(listen_fd, &rd);
|
||||
nfds = listen_fd+1;
|
||||
|
||||
for (i=0; i<num_connections; i++) {
|
||||
FD_SET(connections[i], &rd);
|
||||
nfds = MAX(nfds, connections[i]+1);
|
||||
}
|
||||
|
||||
num = select(nfds, &rd, NULL, NULL, NULL);
|
||||
if (num == -1)
|
||||
perror("select()");
|
||||
else if (num) {
|
||||
//printf("Data is available now on %d fds\n", num);
|
||||
if (FD_ISSET(listen_fd, &rd)) {
|
||||
num--;
|
||||
incoming = accept(listen_fd, (struct sockaddr *) &in_addr, &in_addrlen);
|
||||
if (incoming < 0) {
|
||||
perror("accept()");
|
||||
break;
|
||||
}
|
||||
if (num_connections == MAX_CONNECTIONS) {
|
||||
fprintf(stderr, "Connection limit(%d) reached. Dropping connection\n", MAX_CONNECTIONS);
|
||||
close(incoming);
|
||||
} else {
|
||||
connections[num_connections++] = incoming;
|
||||
fprintf(stderr, "Got incoming connection, fd %d, number %d\n", incoming, num_connections);
|
||||
}
|
||||
}
|
||||
for (i=0; num && (i<num_connections); i++) {
|
||||
int fd = connections[i];
|
||||
if (FD_ISSET(fd, &rd)) {
|
||||
struct protocol cmd;
|
||||
int ret;
|
||||
|
||||
//fprintf(stderr, "New command from fd %d, number %d, to go %d\n", fd, i, num);
|
||||
// TODO: to get highest performance we should loop here until we get EAGAIN
|
||||
ret = recv(fd, &cmd, sizeof(cmd), MSG_DONTWAIT);
|
||||
if (ret == sizeof(cmd)) {
|
||||
enum protoCmd rsp = rx_request(&cmd, fd);
|
||||
|
||||
switch(rsp) {
|
||||
case cmdNotDone:
|
||||
cmd.cmd = rsp;
|
||||
fprintf(stderr, "Sending NotDone response(%d)\n", rsp);
|
||||
ret = send(fd, &cmd, sizeof(cmd), 0);
|
||||
if (ret != sizeof(cmd))
|
||||
perror("response send error");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (!ret) {
|
||||
int j;
|
||||
|
||||
num_connections--;
|
||||
fprintf(stderr, "Connection %d, fd %d closed, now %d left\n", i, fd, num_connections);
|
||||
for (j=i; j < num_connections; j++)
|
||||
connections[j] = connections[j+1];
|
||||
clear_requests(fd);
|
||||
close(fd);
|
||||
} else {
|
||||
fprintf(stderr, "Recv Error on fd %d\n", fd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
fprintf(stderr, "Select timeout\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const char *spath = RENDER_SOCKET;
|
||||
int fd, i;
|
||||
struct sockaddr_un addr;
|
||||
mode_t old;
|
||||
|
||||
fprintf(stderr, "Rendering daemon\n");
|
||||
|
||||
pthread_mutex_init(&qLock, NULL);
|
||||
reqHead.next = reqHead.prev = &reqHead;
|
||||
dirtyHead.next = dirtyHead.prev = &dirtyHead;
|
||||
renderHead.next = renderHead.prev = &renderHead;
|
||||
|
||||
fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "failed to create unix sozket\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
bzero(&addr, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
|
||||
|
||||
unlink(addr.sun_path);
|
||||
|
||||
old = umask(0); // Need daemon socket to be writeable by apache
|
||||
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr, "socket bind failed for: %s\n", spath);
|
||||
close(fd);
|
||||
exit(3);
|
||||
}
|
||||
umask(old);
|
||||
|
||||
if (listen(fd, QUEUE_MAX) < 0) {
|
||||
fprintf(stderr, "socket listen failed for %d\n", QUEUE_MAX);
|
||||
close(fd);
|
||||
exit(4);
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (fcntl(fd, F_SETFD, O_RDWR | O_NONBLOCK) < 0) {
|
||||
fprintf(stderr, "setting socket non-block failed\n");
|
||||
close(fd);
|
||||
exit(5);
|
||||
}
|
||||
#endif
|
||||
|
||||
//sigPipeAction.sa_handler = pipe_handler;
|
||||
sigPipeAction.sa_handler = SIG_IGN;
|
||||
if (sigaction(SIGPIPE, &sigPipeAction, NULL) < 0) {
|
||||
fprintf(stderr, "failed to register signal handler\n");
|
||||
close(fd);
|
||||
exit(6);
|
||||
}
|
||||
|
||||
render_init();
|
||||
|
||||
for(i=0; i<NUM_THREADS; i++) {
|
||||
if (pthread_create(&render_threads[i], NULL, render_thread, NULL)) {
|
||||
fprintf(stderr, "error spawning render thread\n");
|
||||
close(fd);
|
||||
exit(7);
|
||||
}
|
||||
}
|
||||
process_loop(fd);
|
||||
|
||||
unlink(spath);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
199
gen_tile.cpp
Normal file
199
gen_tile.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
#include <mapnik/map.hpp>
|
||||
#include <mapnik/datasource_cache.hpp>
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
#include <mapnik/filter_factory.hpp>
|
||||
#include <mapnik/color_factory.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/load_map.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
|
||||
#include <Magick++.h>
|
||||
#include <iostream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "gen_tile.h"
|
||||
#include "protocol.h"
|
||||
|
||||
using namespace mapnik;
|
||||
|
||||
#define DEG_TO_RAD (M_PIl/180)
|
||||
#define RAD_TO_DEG (180/M_PIl)
|
||||
|
||||
static const int minZoom = 0;
|
||||
static const int maxZoom = 18;
|
||||
static const char *mapfile = "/home/jburgess/osm/svn.openstreetmap.org/applications/rendering/mapnik/osm-jb-merc.xml";
|
||||
|
||||
|
||||
static void postProcess(const char *path)
|
||||
{
|
||||
// Convert the 32bit RGBA image to one with indexed colours
|
||||
// TODO: Ideally this would work on the Mapnik Image32 instead of requiring the intermediate image
|
||||
// Or have a post-process thread with queueing
|
||||
|
||||
char tmp[PATH_MAX];
|
||||
Magick::Image image;
|
||||
snprintf(tmp, sizeof(tmp), "%s.tmp", path);
|
||||
|
||||
try {
|
||||
image.read(path);
|
||||
|
||||
image.matte(0);
|
||||
image.quantizeDither(0);
|
||||
image.quantizeColors(255);
|
||||
image.quantize();
|
||||
image.modulusDepth(8);
|
||||
image.write(tmp);
|
||||
rename(tmp, path);
|
||||
}
|
||||
catch( Magick::Exception &error_ ) {
|
||||
std::cerr << "Caught exception: " << error_.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static double minmax(double a, double b, double c)
|
||||
{
|
||||
#define MIN(x,y) ((x)<(y)?(x):(y))
|
||||
#define MAX(x,y) ((x)>(y)?(x):(y))
|
||||
a = MAX(a,b);
|
||||
a = MIN(a,c);
|
||||
return a;
|
||||
}
|
||||
|
||||
class GoogleProjection
|
||||
{
|
||||
double *Ac, *Bc, *Cc, *zc;
|
||||
|
||||
public:
|
||||
GoogleProjection(int levels=18) {
|
||||
Ac = new double[levels];
|
||||
Bc = new double[levels];
|
||||
Cc = new double[levels];
|
||||
zc = new double[levels];
|
||||
int d, c = 256;
|
||||
for (d=0; d<levels; d++) {
|
||||
int e = c/2;
|
||||
Bc[d] = c/360.0;
|
||||
Cc[d] = c/(2 * M_PIl);
|
||||
zc[d] = e;
|
||||
Ac[d] = c;
|
||||
c *=2;
|
||||
}
|
||||
}
|
||||
|
||||
void fromLLtoPixel(double &x, double &y, int zoom) {
|
||||
double d = zc[zoom];
|
||||
double f = minmax(sin(DEG_TO_RAD * y),-0.9999,0.9999);
|
||||
x = round(d + x * Bc[zoom]);
|
||||
y = round(d + 0.5*log((1+f)/(1-f))*-Cc[zoom]);
|
||||
}
|
||||
void fromPixelToLL(double &x, double &y, int zoom) {
|
||||
double e = zc[zoom];
|
||||
double g = (y - e)/-Cc[zoom];
|
||||
x = (x - e)/Bc[zoom];
|
||||
y = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * M_PIl);
|
||||
}
|
||||
};
|
||||
|
||||
static void load_fonts(const char *font_dir, int recurse)
|
||||
{
|
||||
DIR *fonts = opendir(font_dir);
|
||||
struct dirent *entry;
|
||||
char path[PATH_MAX]; // FIXME: Eats lots of stack space when recursive
|
||||
|
||||
if (!fonts) {
|
||||
fprintf(stderr, "Unable to open font directory: %s\n", font_dir);
|
||||
return;
|
||||
}
|
||||
|
||||
while ((entry = readdir(fonts))) {
|
||||
struct stat b;
|
||||
char *p;
|
||||
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
snprintf(path, sizeof(path), "%s/%s", font_dir, entry->d_name);
|
||||
if (stat(path, &b))
|
||||
continue;
|
||||
if (S_ISDIR(b.st_mode)) {
|
||||
if (recurse)
|
||||
load_fonts(path, recurse);
|
||||
continue;
|
||||
}
|
||||
p = strrchr(path, '.');
|
||||
if (p && !strcmp(p, ".ttf")) {
|
||||
//fprintf(stderr, "Loading font: %s\n", path);
|
||||
freetype_engine::register_font(path);
|
||||
}
|
||||
}
|
||||
closedir(fonts);
|
||||
}
|
||||
|
||||
static GoogleProjection gprj(maxZoom+1);
|
||||
static projection prj("+proj=merc +datum=WGS84");
|
||||
|
||||
static enum protoCmd render(Map &m, Image32 &buf, int x, int y, int z, const char *filename)
|
||||
{
|
||||
double p0x = x * 256.0;
|
||||
double p0y = (y + 1) * 256.0;
|
||||
double p1x = (x + 1) * 256.0;
|
||||
double p1y = y * 256.0;
|
||||
|
||||
gprj.fromPixelToLL(p0x, p0y, z);
|
||||
gprj.fromPixelToLL(p1x, p1y, z);
|
||||
|
||||
prj.forward(p0x, p0y);
|
||||
prj.forward(p1x, p1y);
|
||||
|
||||
Envelope<double> bbox(p0x, p0y, p1x,p1y);
|
||||
bbox.width(bbox.width() * 2);
|
||||
bbox.height(bbox.height() * 2);
|
||||
m.zoomToBox(bbox);
|
||||
|
||||
agg_renderer<Image32> ren(m,buf, 128,128);
|
||||
ren.apply();
|
||||
buf.saveToFile(filename,"png");
|
||||
|
||||
return cmdDone; // OK
|
||||
}
|
||||
|
||||
pthread_mutex_t map_lock;
|
||||
|
||||
void render_init(void)
|
||||
{
|
||||
// TODO: Make these module options
|
||||
datasource_cache::instance()->register_datasources("/usr/local/lib64/mapnik/input");
|
||||
//load_fonts("/usr/share/fonts", 1);
|
||||
load_fonts("/usr/local/lib64/mapnik/fonts", 0);
|
||||
pthread_mutex_init(&map_lock, NULL);
|
||||
}
|
||||
|
||||
|
||||
void *render_thread(__attribute__((unused)) void *unused)
|
||||
{
|
||||
Map m(2 * 256, 2 * 256);
|
||||
Image32 buf(256, 256);
|
||||
|
||||
load_map(m,mapfile);
|
||||
|
||||
while (1) {
|
||||
enum protoCmd ret;
|
||||
struct item *item = fetch_request();
|
||||
if (item) {
|
||||
struct protocol *req = &item->req;
|
||||
//pthread_mutex_lock(&map_lock);
|
||||
ret = render(m, buf, req->x, req->y, req->z, req->path);
|
||||
//pthread_mutex_unlock(&map_lock);
|
||||
postProcess(req->path);
|
||||
send_response(item, ret);
|
||||
delete_request(item);
|
||||
} else
|
||||
sleep(1); // TODO: Use an event to indicate there are new requests
|
||||
}
|
||||
return NULL;
|
||||
}
|
28
gen_tile.h
Normal file
28
gen_tile.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef GEN_TILE_H
|
||||
#define GEN_TILE_H
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct item {
|
||||
struct item *next;
|
||||
struct item *prev;
|
||||
struct protocol req;
|
||||
int fd;
|
||||
};
|
||||
|
||||
//int render(Map &m, int x, int y, int z, const char *filename);
|
||||
void *render_thread(void *unused);
|
||||
struct item *fetch_request(void);
|
||||
void delete_request(struct item *item);
|
||||
void send_response(struct item *item, enum protoCmd);
|
||||
void render_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
460
mod_tile.c
Normal file
460
mod_tile.c
Normal file
@ -0,0 +1,460 @@
|
||||
#include "apr.h"
|
||||
#include "apr_strings.h"
|
||||
#include "apr_thread_proc.h" /* for RLIMIT stuff */
|
||||
#include "apr_optional.h"
|
||||
#include "apr_buckets.h"
|
||||
#include "apr_lib.h"
|
||||
#include "apr_poll.h"
|
||||
|
||||
#define APR_WANT_STRFUNC
|
||||
#define APR_WANT_MEMFUNC
|
||||
#include "apr_want.h"
|
||||
|
||||
#include "util_filter.h"
|
||||
#include "ap_config.h"
|
||||
#include "httpd.h"
|
||||
#include "http_config.h"
|
||||
#include "http_request.h"
|
||||
#include "http_core.h"
|
||||
#include "http_protocol.h"
|
||||
#include "http_main.h"
|
||||
#include "http_log.h"
|
||||
#include "util_script.h"
|
||||
#include "ap_mpm.h"
|
||||
#include "mod_core.h"
|
||||
#include "mod_cgi.h"
|
||||
|
||||
module AP_MODULE_DECLARE_DATA tile_module;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "gen_tile.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#define MAX_ZOOM 18
|
||||
// MAX_SIZE is the biggest file which we will return to the user
|
||||
#define MAX_SIZE (1 * 1024 * 1024)
|
||||
// IMG_PATH must have blank.png etc.
|
||||
#define WWW_ROOT "/var/www/html"
|
||||
#define IMG_PATH "/img"
|
||||
// TILE_PATH must have tile z directory z(0..18)/x/y.png
|
||||
#define TILE_PATH "/osm_tiles2"
|
||||
//#define TILE_PATH "/tile"
|
||||
// MAX_LOAD_OLD: if tile is out of date, don't re-render it if past this load threshold (users gets old tile)
|
||||
#define MAX_LOAD_OLD 5
|
||||
// MAX_LOAD_OLD: if tile is missing, don't render it if past this load threshold (user gets 404 error)
|
||||
#define MAX_LOAD_MISSING 10
|
||||
// MAX_LOAD_ANY: give up serving any data if beyond this load (user gets 404 error)
|
||||
#define MAX_LOAD_ANY 100
|
||||
// Maximum tile age in seconds
|
||||
// TODO: this mechanism should really be a hard cutoff on planet update time.
|
||||
#define MAX_AGE (48 * 60 * 60)
|
||||
|
||||
// Typical interval between planet imports, used as basis for tile expiry times
|
||||
#define PLANET_INTERVAL (7 * 24 * 60 * 60)
|
||||
|
||||
// Planet import should touch this file when complete
|
||||
#define PLANET_TIMESTAMP "/tmp/planet-import-complete"
|
||||
|
||||
// Timeout before giving for a tile to be rendered
|
||||
#define REQUEST_TIMEOUT (3)
|
||||
#define FD_INVALID (-1)
|
||||
|
||||
|
||||
#define MIN(x,y) ((x)<(y)?(x):(y))
|
||||
#define MAX(x,y) ((x)>(y)?(x):(y))
|
||||
|
||||
|
||||
enum tileState { tileMissing, tileOld, tileCurrent };
|
||||
|
||||
static int error_message(request_rec *r, const char *format, ...)
|
||||
__attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
static int error_message(request_rec *r, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int len;
|
||||
char *msg;
|
||||
|
||||
len = vasprintf(&msg, format, ap);
|
||||
|
||||
if (msg) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", msg);
|
||||
r->content_type = "text/plain";
|
||||
if (!r->header_only)
|
||||
ap_rputs(msg, r);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
int socket_init(request_rec *r)
|
||||
{
|
||||
const char *spath = RENDER_SOCKET;
|
||||
int fd;
|
||||
struct sockaddr_un addr;
|
||||
|
||||
//fprintf(stderr, "Starting rendering client\n");
|
||||
|
||||
fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to create unix socket");
|
||||
return FD_INVALID;
|
||||
}
|
||||
|
||||
bzero(&addr, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, spath, sizeof(addr.sun_path));
|
||||
|
||||
if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "socket connect failed for: %s", spath);
|
||||
close(fd);
|
||||
return FD_INVALID;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
int request_tile(request_rec *r, int x, int y, int z, const char *filename, int dirtyOnly)
|
||||
{
|
||||
struct protocol cmd;
|
||||
//struct pollfd fds[1];
|
||||
static int fd = FD_INVALID;
|
||||
int ret = 0;
|
||||
|
||||
if (fd == FD_INVALID) {
|
||||
fd = socket_init(r);
|
||||
|
||||
if (fd == FD_INVALID) {
|
||||
//fprintf(stderr, "Failed to connect to renderer\n");
|
||||
return 0;
|
||||
} else {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Connected to renderer");
|
||||
}
|
||||
}
|
||||
|
||||
bzero(&cmd, sizeof(cmd));
|
||||
|
||||
cmd.ver = PROTO_VER;
|
||||
cmd.cmd = dirtyOnly ? cmdDirty : cmdRender;
|
||||
cmd.z = z;
|
||||
cmd.x = x;
|
||||
cmd.y = y;
|
||||
strcpy(cmd.path, filename);
|
||||
|
||||
//fprintf(stderr, "Requesting tile(%d,%d,%d)\n", z,x,y);
|
||||
ret = send(fd, &cmd, sizeof(cmd), 0);
|
||||
if (ret != sizeof(cmd)) {
|
||||
if (errno == EPIPE) {
|
||||
close(fd);
|
||||
fd = FD_INVALID;
|
||||
}
|
||||
|
||||
//perror("send error");
|
||||
return 0;
|
||||
}
|
||||
if (!dirtyOnly) {
|
||||
struct timeval tv = { REQUEST_TIMEOUT, 0 };
|
||||
fd_set rx;
|
||||
int s;
|
||||
|
||||
while (1) {
|
||||
FD_ZERO(&rx);
|
||||
FD_SET(fd, &rx);
|
||||
s = select(fd+1, &rx, NULL, NULL, &tv);
|
||||
if (s == 1) {
|
||||
bzero(&cmd, sizeof(cmd));
|
||||
ret = recv(fd, &cmd, sizeof(cmd), 0);
|
||||
if (ret != sizeof(cmd)) {
|
||||
if (errno == EPIPE) {
|
||||
close(fd);
|
||||
fd = FD_INVALID;
|
||||
}
|
||||
//perror("recv error");
|
||||
break;
|
||||
}
|
||||
//fprintf(stderr, "Completed tile(%d,%d,%d)\n", z,x,y);
|
||||
if (cmd.x == x && cmd.y == y && cmd.z == z) {
|
||||
if (cmd.cmd == cmdDone)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
} else if (s == 0) {
|
||||
break;
|
||||
} else {
|
||||
if (errno == EPIPE) {
|
||||
close(fd);
|
||||
fd = FD_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int getPlanetTime(request_rec *r)
|
||||
{
|
||||
static time_t last_check;
|
||||
static time_t planet_timestamp;
|
||||
time_t now = time(NULL);
|
||||
struct stat buf;
|
||||
|
||||
// Only check for updates periodically
|
||||
if (now < last_check + 300)
|
||||
return planet_timestamp;
|
||||
|
||||
last_check = now;
|
||||
if (stat(PLANET_TIMESTAMP, &buf)) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Planet timestamp file " PLANET_TIMESTAMP " is missing");
|
||||
// Make something up
|
||||
planet_timestamp = now - 3 * 24 * 60 * 60;
|
||||
} else {
|
||||
if (buf.st_mtime != planet_timestamp) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Planet file updated at %s", ctime(&buf.st_mtime));
|
||||
planet_timestamp = buf.st_mtime;
|
||||
}
|
||||
}
|
||||
return planet_timestamp;
|
||||
}
|
||||
|
||||
enum tileState tile_state(request_rec *r, const char *filename)
|
||||
{
|
||||
// FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
|
||||
struct stat buf;
|
||||
|
||||
if (stat(filename, &buf))
|
||||
return tileMissing;
|
||||
|
||||
if (buf.st_mtime < getPlanetTime(r))
|
||||
return tileOld;
|
||||
|
||||
return tileCurrent;
|
||||
}
|
||||
|
||||
static apr_status_t expires_filter(ap_filter_t *f, apr_bucket_brigade *b)
|
||||
{
|
||||
request_rec *r = f->r;
|
||||
apr_time_t expires, holdoff, nextPlanet;
|
||||
apr_table_t *t = r->headers_out;
|
||||
enum tileState state = tile_state(r, r->filename);
|
||||
char *timestr;
|
||||
|
||||
/* Append expiry headers ... */
|
||||
|
||||
//ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "expires(%s), uri(%s), filename(%s), path_info(%s)\n",
|
||||
// r->handler, r->uri, r->filename, r->path_info);
|
||||
|
||||
// current tiles will expire after next planet dump is due
|
||||
// or after 1 hour if the planet dump is late or tile is due for re-render
|
||||
nextPlanet = (state == tileCurrent) ? apr_time_from_sec(getPlanetTime(r) + PLANET_INTERVAL) : 0;
|
||||
holdoff = r->request_time + apr_time_from_sec(60 * 60);
|
||||
expires = MAX(holdoff, nextPlanet);
|
||||
|
||||
apr_table_mergen(t, "Cache-Control",
|
||||
apr_psprintf(r->pool, "max-age=%" APR_TIME_T_FMT,
|
||||
apr_time_sec(expires - r->request_time)));
|
||||
timestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
|
||||
apr_rfc822_date(timestr, expires);
|
||||
apr_table_setn(t, "Expires", timestr);
|
||||
|
||||
ap_remove_output_filter(f);
|
||||
return ap_pass_brigade(f->next, b);
|
||||
}
|
||||
|
||||
static int serve_blank(request_rec *r)
|
||||
{
|
||||
// Redirect request to blank tile
|
||||
r->method = apr_pstrdup(r->pool, "GET");
|
||||
r->method_number = M_GET;
|
||||
apr_table_unset(r->headers_in, "Content-Length");
|
||||
ap_internal_redirect_handler(IMG_PATH "/blank-000000.png", r);
|
||||
return OK;
|
||||
}
|
||||
|
||||
int get_load_avg(request_rec *r)
|
||||
{
|
||||
FILE *loadavg = fopen("/proc/loadavg", "r");
|
||||
int avg = 1000;
|
||||
|
||||
if (!loadavg) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to read /proc/loadavg");
|
||||
return 1000;
|
||||
}
|
||||
if (fscanf(loadavg, "%d", &avg) != 1) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "failed to parse /proc/loadavg");
|
||||
fclose(loadavg);
|
||||
return 1000;
|
||||
}
|
||||
fclose(loadavg);
|
||||
|
||||
return avg;
|
||||
}
|
||||
|
||||
static int tile_dirty(request_rec *r, int x, int y, int z, const char *path)
|
||||
{
|
||||
request_tile(r, x,y,z,path, 1);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static int get_tile(request_rec *r, int x, int y, int z, const char *path)
|
||||
{
|
||||
int avg = get_load_avg(r);
|
||||
enum tileState state;
|
||||
|
||||
if (avg > MAX_LOAD_ANY) {
|
||||
// we're too busy to send anything now
|
||||
return error_message(r, "error: Load above MAX_LOAD_ANY threshold %d > %d", avg, MAX_LOAD_ANY);
|
||||
}
|
||||
|
||||
state = tile_state(r, path);
|
||||
|
||||
// Note: We rely on the default Apache handler to return the files from the filesystem
|
||||
// hence we return DECLINED in order to return the tile to the client
|
||||
// or OK if we want to send something else.
|
||||
|
||||
switch (state) {
|
||||
case tileCurrent:
|
||||
return DECLINED;
|
||||
break;
|
||||
case tileOld:
|
||||
if (avg > MAX_LOAD_OLD) {
|
||||
// Too much load to render it now, mark dirty but return old tile
|
||||
tile_dirty(r, x, y, z, path);
|
||||
return DECLINED;
|
||||
}
|
||||
break;
|
||||
case tileMissing:
|
||||
if (avg > MAX_LOAD_MISSING) {
|
||||
tile_dirty(r, x, y, z, path);
|
||||
return error_message(r, "error: File missing and load above MAX_LOAD_MISSING threshold %d > %d", avg, MAX_LOAD_MISSING);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (request_tile(r, x,y,z,path, 0)) {
|
||||
// Need to make apache try accessing this tile again (since it may have been missing)
|
||||
// TODO: Instead of redirect, maybe we can update fileinfo for new tile, but is this sufficient?
|
||||
apr_table_unset(r->headers_in, "Content-Length");
|
||||
ap_internal_redirect_handler(r->uri, r);
|
||||
return OK;
|
||||
}
|
||||
return error_message(r, "rendering failed for %s", path);
|
||||
}
|
||||
|
||||
static int tile_status(request_rec *r, int x, int y, int z, const char *path)
|
||||
{
|
||||
// FIXME: Apache already has most, if not all, this info recorded in r->fileinfo, use this instead!
|
||||
struct stat buf;
|
||||
time_t now;
|
||||
int old;
|
||||
char MtimeStr[32]; // At least 26 according to man ctime_r
|
||||
char AtimeStr[32]; // At least 26 according to man ctime_r
|
||||
char *p;
|
||||
|
||||
if (stat(path, &buf))
|
||||
return error_message(r, "Unable to find a tile at %s", path);
|
||||
|
||||
now = time(NULL);
|
||||
old = (buf.st_mtime < now - MAX_AGE);
|
||||
|
||||
MtimeStr[0] = '\0';
|
||||
ctime_r(&buf.st_mtime, MtimeStr);
|
||||
AtimeStr[0] = '\0';
|
||||
ctime_r(&buf.st_atime, AtimeStr);
|
||||
|
||||
if ((p = strrchr(MtimeStr, '\n')))
|
||||
*p = '\0';
|
||||
if ((p = strrchr(AtimeStr, '\n')))
|
||||
*p = '\0';
|
||||
|
||||
return error_message(r, "Tile is %s. Last rendered at %s. Last accessed at %s", old ? "due to be rendered" : "clean", MtimeStr, AtimeStr);
|
||||
}
|
||||
|
||||
|
||||
static int tile_handler(request_rec *r)
|
||||
{
|
||||
int x, y, z, n, limit;
|
||||
char option[11];
|
||||
int oob;
|
||||
char path[PATH_MAX];
|
||||
|
||||
option[0] = '\0';
|
||||
|
||||
if(strcmp(r->handler, "tile"))
|
||||
return DECLINED;
|
||||
|
||||
//ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "handler(%s), uri(%s), filename(%s), path_info(%s)",
|
||||
// r->handler, r->uri, r->filename, r->path_info);
|
||||
|
||||
/* URI = .../<z>/<x>/<y>.png[/option] */
|
||||
n = sscanf(r->uri, TILE_PATH "/%d/%d/%d.png/%10s", &z, &x, &y, option);
|
||||
if (n < 3) {
|
||||
//return error_message(r, "unable to process: %s", r->path_info);
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
// Generate the tile filename.
|
||||
// This may differ from r->filename in some cases (e.g. if a parent directory is missing)
|
||||
snprintf(path, PATH_MAX, WWW_ROOT TILE_PATH "/%d/%d/%d.png", z, x, y);
|
||||
|
||||
// Validate tile co-ordinates
|
||||
oob = (z < 0 || z > MAX_ZOOM);
|
||||
if (!oob) {
|
||||
// valid x/y for tiles are 0 ... 2^zoom-1
|
||||
limit = (1 << z) - 1;
|
||||
oob = (x < 0 || x > limit || y < 0 || y > limit);
|
||||
}
|
||||
|
||||
if (n == 3) {
|
||||
ap_add_output_filter("MOD_TILE", NULL, r, r->connection);
|
||||
return oob ? serve_blank(r) : get_tile(r, x, y, z, path);
|
||||
}
|
||||
|
||||
if (n == 4) {
|
||||
if (oob)
|
||||
return error_message(r, "The tile co-ordinates that you specified are invalid");
|
||||
if (!strcmp(option, "status"))
|
||||
return tile_status(r, x, y, z, path);
|
||||
if (!strcmp(option, "dirty"))
|
||||
return tile_dirty(r, x, y, z, path);
|
||||
return error_message(r, "Unknown option");
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
static void register_hooks(__attribute__((unused)) apr_pool_t *p)
|
||||
{
|
||||
ap_register_output_filter("MOD_TILE", expires_filter, NULL, APR_HOOK_MIDDLE);
|
||||
ap_hook_handler(tile_handler, NULL, NULL, APR_HOOK_FIRST);
|
||||
}
|
||||
|
||||
module AP_MODULE_DECLARE_DATA tile_module =
|
||||
{
|
||||
STANDARD20_MODULE_STUFF,
|
||||
NULL, /* dir config creater */
|
||||
NULL, /* dir merger --- default is to override */
|
||||
NULL, /* server config */
|
||||
NULL, /* merge server config */
|
||||
NULL, /* command apr_table_t */
|
||||
register_hooks /* register hooks */
|
||||
};
|
9
mod_tile.conf
Normal file
9
mod_tile.conf
Normal file
@ -0,0 +1,9 @@
|
||||
# This is the Apache server configuration file for providing OSM tile support
|
||||
# through mod_tile
|
||||
#
|
||||
|
||||
LoadModule tile_module modules/mod_tile.so
|
||||
|
||||
<Directory /var/www/html/osm_tiles2/>
|
||||
SetHandler tile
|
||||
</Directory>
|
13
modules.mk
Normal file
13
modules.mk
Normal file
@ -0,0 +1,13 @@
|
||||
#
|
||||
# this is used/needed by the APACHE2 build system
|
||||
#
|
||||
|
||||
MOD_TILE = mod_tile
|
||||
|
||||
mod_tile.la: ${MOD_TILE:=.slo}
|
||||
$(SH_LINK) -rpath $(libexecdir) -module -avoid-version ${MOD_TILE:=.lo}
|
||||
|
||||
DISTCLEAN_TARGETS = modules.mk
|
||||
|
||||
shared = mod_tile.la
|
||||
|
162
mysql2file.c
Normal file
162
mysql2file.c
Normal file
@ -0,0 +1,162 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <time.h>
|
||||
#include <utime.h>
|
||||
|
||||
|
||||
#include <mysql.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <sslopt-vars.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define WWW_ROOT "/var/www/html"
|
||||
// TILE_PATH must have tile z directory z(0..18)/x/y.png
|
||||
#define TILE_PATH "/osm_tiles2"
|
||||
|
||||
|
||||
// Build parent directories for the specified file name
|
||||
// Note: the part following the trailing / is ignored
|
||||
// e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b
|
||||
static int mkdirp(const char *path) {
|
||||
struct stat s;
|
||||
char tmp[PATH_MAX];
|
||||
char *p;
|
||||
|
||||
strncpy(tmp, path, sizeof(tmp));
|
||||
|
||||
// Look for parent directory
|
||||
p = strrchr(tmp, '/');
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
*p = '\0';
|
||||
|
||||
if (!stat(tmp, &s))
|
||||
return !S_ISDIR(s.st_mode);
|
||||
*p = '/';
|
||||
// Walk up the path making sure each element is a directory
|
||||
p = tmp;
|
||||
if (!*p)
|
||||
return 0;
|
||||
p++; // Ignore leading /
|
||||
while (*p) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (!stat(tmp, &s)) {
|
||||
if (!S_ISDIR(s.st_mode))
|
||||
return 1;
|
||||
} else if (mkdir(tmp, 0777))
|
||||
return 1;
|
||||
*p = '/';
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void parseDate(struct tm *tm, const char *str)
|
||||
{
|
||||
// 2007-05-20 13:51:35
|
||||
bzero(tm, sizeof(*tm));
|
||||
int n = sscanf(str, "%d-%d-%d %d:%d:%d",
|
||||
&tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, &tm->tm_min, &tm->tm_sec);
|
||||
|
||||
if (n !=6)
|
||||
printf("failed to parse date string, got(%d): %s\n", n, str);
|
||||
|
||||
tm->tm_year -= 1900;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
MYSQL mysql;
|
||||
char query[255];
|
||||
MYSQL_RES *res;
|
||||
MYSQL_ROW row;
|
||||
mysql_init(&mysql);
|
||||
|
||||
if (!(mysql_real_connect(&mysql,"","tile","tile","tile",MYSQL_PORT,NULL,0)))
|
||||
{
|
||||
fprintf(stderr,"%s: %s\n",argv[0],mysql_error(&mysql));
|
||||
exit(1);
|
||||
}
|
||||
mysql.reconnect= 1;
|
||||
|
||||
snprintf(query, sizeof(query), "SELECT x,y,z,data,created_at FROM tiles");
|
||||
|
||||
if ((mysql_query(&mysql, query)) || !(res= mysql_use_result(&mysql)))
|
||||
{
|
||||
fprintf(stderr,"Cannot query tiles: %s\n", mysql_error(&mysql));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while ((row= mysql_fetch_row(res)))
|
||||
{
|
||||
ulong *lengths= mysql_fetch_lengths(res);
|
||||
char path[PATH_MAX];
|
||||
unsigned long int x,y,z,length;
|
||||
time_t created_at;
|
||||
const char *data;
|
||||
struct tm date;
|
||||
int fd;
|
||||
struct utimbuf utb;
|
||||
|
||||
assert(mysql_num_fields(res) == 5);
|
||||
|
||||
//printf("x(%s) y(%s) z(%s) data_length(%lu): %s\n", row[0], row[1], row[2], lengths[3], row[4]);
|
||||
|
||||
x = strtoul(row[0], NULL, 10);
|
||||
y = strtoul(row[1], NULL, 10);
|
||||
z = strtoul(row[2], NULL, 10);
|
||||
data = row[3];
|
||||
length = lengths[3];
|
||||
parseDate(&date, row[4]);
|
||||
created_at = mktime(&date);
|
||||
|
||||
//printf("x(%lu) y(%lu) z(%lu) data_length(%lu): %s", x,y,z,length,ctime(&created_at));
|
||||
|
||||
if (!length) {
|
||||
printf("skipping empty tile x(%lu) y(%lu) z(%lu) data_length(%lu): %s", x,y,z,length,ctime(&created_at));
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(path, PATH_MAX, WWW_ROOT TILE_PATH "/%lu/%lu/%lu.png", z, x, y);
|
||||
printf("%s\n", path);
|
||||
mkdirp(path);
|
||||
|
||||
fd = open(path, O_CREAT | O_WRONLY, 0644);
|
||||
if (fd <0) {
|
||||
perror(path);
|
||||
exit(1);
|
||||
}
|
||||
if (write(fd, data, length) != length) {
|
||||
perror("writing tile");
|
||||
exit(2);
|
||||
}
|
||||
close(fd);
|
||||
utb.actime = created_at;
|
||||
utb.modtime = created_at;
|
||||
if (utime(path, &utb) < 0) {
|
||||
perror("utime");
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
printf ("Number of rows: %lu\n", (unsigned long) mysql_num_rows(res));
|
||||
mysql_free_result(res);
|
||||
|
||||
mysql_close(&mysql); /* Close & free connection */
|
||||
return 0;
|
||||
}
|
31
mysql2file.rb
Executable file
31
mysql2file.rb
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/ruby
|
||||
require 'mysql'
|
||||
require 'date'
|
||||
require 'time'
|
||||
require 'fileutils'
|
||||
|
||||
dbh = nil
|
||||
dbh = Mysql.real_connect('localhost', 'tile', 'tile', 'tile')
|
||||
dbh.query_with_result = false
|
||||
dbh.query("select x,y,z,data,created_at from tiles" )
|
||||
res = dbh.use_result
|
||||
|
||||
while row = res.fetch_hash do
|
||||
x = row['x']
|
||||
y = row['y']
|
||||
z = row['z']
|
||||
created_at = Time.parse(row['created_at'])
|
||||
|
||||
path = "/var/www/html/osm_tiles2/#{z}/#{x}"
|
||||
FileUtils.mkdir_p(path)
|
||||
|
||||
print "x(#{x}) y(#{y}) z(#{z}), created_at(#{created_at.to_i})\n"
|
||||
|
||||
f = File.new("#{path}/#{y}.png", "w")
|
||||
f.print row['data']
|
||||
f.close
|
||||
File.utime(created_at,created_at,"#{path}/#{y}.png")
|
||||
end
|
||||
puts "Number of rows returned: #{res.num_rows}"
|
||||
|
||||
res.free
|
36
protocol.h
Normal file
36
protocol.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Protocol between client and render daemon
|
||||
*
|
||||
* ver = 1;
|
||||
*
|
||||
* cmdRender(z,x,y), response: {cmdDone(z,x,y), cmdBusy(z,x,y)}
|
||||
* cmdDirty(z,x,y), no response
|
||||
*
|
||||
* A client may not bother waiting for a response if the render daemon is too slow
|
||||
* causing responses to get slightly out of step with requests.
|
||||
*/
|
||||
#define TILE_PATH_MAX (256)
|
||||
#define PROTO_VER (1)
|
||||
#define RENDER_SOCKET "/tmp/osm-renderd"
|
||||
|
||||
enum protoCmd { cmdIgnore, cmdRender, cmdDirty, cmdDone, cmdNotDone };
|
||||
|
||||
struct protocol {
|
||||
int ver;
|
||||
enum protoCmd cmd;
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
char path[TILE_PATH_MAX]; // FIXME: this is a really bad idea since it allows wrties to arbitrrary stuff
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
139
readme.txt
Normal file
139
readme.txt
Normal file
@ -0,0 +1,139 @@
|
||||
mod_tile
|
||||
========
|
||||
|
||||
A program to efficiently render and serve map tiles for
|
||||
www.openstreetmap.org map using Apache and Mapnik.
|
||||
|
||||
Note: This program is very much still in development
|
||||
it has numerous hard coded paths and options which need
|
||||
to be made user configurable options. You will not
|
||||
be able to use this program without modifying these to
|
||||
fit your local environment.
|
||||
|
||||
Requirements
|
||||
============
|
||||
OSM map data imported into PostgreSQL using osm2pgsql
|
||||
Mapnik renderer along with the OSM.xml file and map
|
||||
symbols, world_boundaries shapefiles. Apache with
|
||||
development headers for APR module development.
|
||||
|
||||
|
||||
Tile Rendering
|
||||
==============
|
||||
The rendering is implemented in a multithreaded process
|
||||
called renderd which opens a unix socket and listens for
|
||||
requests to render tiles. It uses Mapnik to render tiles
|
||||
using the rendering rules defined in osm-jb-merc.xml.
|
||||
|
||||
The render daemon implements a queueing mechanism which
|
||||
can render foreground requests (for new tiles being viewed)
|
||||
and background requests (updating tiles which have expired)
|
||||
|
||||
Tile serving
|
||||
============
|
||||
Tiles are served from the filesystem using Apache from
|
||||
/var/www/html/osm_tiles2/[Z]/[X]/[Y].png
|
||||
|
||||
where X,Y,Z are the standard OSM tile co-ordinates.
|
||||
|
||||
An Apache module called mod_tile enhances the regular
|
||||
Apache file serving mechanisms to provide:
|
||||
|
||||
1) Tile expiry. It estimates when the tile is next
|
||||
likely to be rendered and adds the approriate HTTP
|
||||
cache expiry headers
|
||||
|
||||
2) When tiles have expired it requests the rendering
|
||||
daemon to render (or re-render) the tile.
|
||||
|
||||
There is an attempt to make the mod_tile code aware of
|
||||
the load on the server so that it backs off the rendering
|
||||
if the machine is under heavy load.
|
||||
|
||||
Setup
|
||||
=====
|
||||
Make sure you've read and implemented the things in the
|
||||
requirements section. Edit the paths in the source to
|
||||
match your local setup. Compile the code with make, and
|
||||
then make install (as root, to copy the mod_tile to the
|
||||
apache module directory).
|
||||
|
||||
Create a new apache config file to load the module,
|
||||
e.g.
|
||||
/etc/httpd/conf.d/mod_tile.conf
|
||||
|
||||
--------------
|
||||
|
||||
LoadModule tile_module modules/mod_tile.so
|
||||
|
||||
<Directory /var/www/html/osm_tiles2/>
|
||||
SetHandler tile
|
||||
</Directory>
|
||||
|
||||
--------------
|
||||
|
||||
Create the directory /var/www/html/osm_tiles2/
|
||||
|
||||
Run the rendering daemon 'renderd'
|
||||
|
||||
Make sure the osm_tiles2 directory is writeable by the
|
||||
user running the renderd process.
|
||||
|
||||
Restart Aapche
|
||||
|
||||
Note: SELinux will prevent the mod_tile code from opening
|
||||
the unix-socket to the render daemon so must be disabled.
|
||||
|
||||
Try loading a tile in your browser, e.g.
|
||||
http://localhost/osm_tiles2/0/0/0.png
|
||||
|
||||
The render daemon should have produce a message like:
|
||||
|
||||
Got incoming connection, fd 7, number 1
|
||||
Render z(0), x(0), y(0), path(/var/www/html/osm_tiles2/0/0/0.png)
|
||||
|
||||
After a few seconds you should see a tile of the world
|
||||
in your browser window.
|
||||
|
||||
To get a complete slippy map you should install a copy
|
||||
of the OpenLayers based OSM slippy map and point this to
|
||||
fetch tiles from http://localhost/osm_tiles2
|
||||
|
||||
mysql2file
|
||||
==========
|
||||
This was written to export the existing OSM tiles from
|
||||
the Mysql database to the filesystem.
|
||||
|
||||
Bugs
|
||||
====
|
||||
Too many hard coded options (need to be come module options or command
|
||||
line options to renderd).
|
||||
mod_tile uses many non-APR routines. It probably only works in Linux.
|
||||
If rendering daemon dies then all queued rendering requests are lost.
|
||||
Code has not been thoroughly tested.
|
||||
|
||||
Performance
|
||||
===========
|
||||
The existing tile serving based on Apache + mod_ruby + cat_tile.rb
|
||||
+ Mysql manages to serve something in the region of 250 - 500 requests
|
||||
per second. Apache + mod_tile manages 2000+ per second. Both these
|
||||
figures are for tiles which have already been rendered.
|
||||
|
||||
Filesystem Issues
|
||||
=================
|
||||
The average tile size is currently somewhere in the region of 2.5kB.
|
||||
(Based on a 20GB MySQL DB which contains 8M tiles). Typically
|
||||
filesystems are not particularly efficient at storing large numbers
|
||||
of small files. They often take a minimum of 4kB on the disk.
|
||||
|
||||
Unfortunately if you reduce the block size to 1 or 2kB then this also
|
||||
has a significant impact on the maximum file system size and number of
|
||||
inodes available.
|
||||
|
||||
The simple z/x/y.png filesystem layout means that at high zoom levels
|
||||
there can be large numbers of files in a single directory
|
||||
|
||||
Zoom 18 = 2^18 = 256k files in a single directory.
|
||||
|
||||
If ext2/3 is being used then you really need to have directory indexing
|
||||
enabled.
|
Reference in New Issue
Block a user