aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tileserver/base64.c272
-rw-r--r--tileserver/base64.h21
-rw-r--r--tileserver/netstring.c44
-rw-r--r--tileserver/netstring.h22
-rw-r--r--tileserver/tileserver.c416
-rw-r--r--tileserver/tileset.c203
-rw-r--r--tileserver/tileset.h30
7 files changed, 1008 insertions, 0 deletions
diff --git a/tileserver/base64.c b/tileserver/base64.c
new file mode 100644
index 000000000..08723e31c
--- /dev/null
+++ b/tileserver/base64.c
@@ -0,0 +1,272 @@
+/*
+ * base64.c:
+ * Base64 and "base64ish" encoding and decoding.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ */
+
+static const char rcsid[] = "$Id: base64.c,v 1.1 2006-09-20 10:25:14 chris Exp $";
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+static const char b64chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "+/=";
+
+static const char b64ishchars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-_=";
+
+/* base64_encode IN INLEN OUT B64ISH NOPAD
+ * Encode INLEN bytes at IN into the buffer at OUT. The total number of bytes
+ * written is recorded in *OUTLEN. OUT must have space for at least
+ * 1 + 4 * (INLEN + 3) / 3 + 1 bytes. Returns a pointer to OUT. This function
+ * always succeeds. If B64ISH is true, the alternate "base64ish" alphabet is
+ * used instead of the standard one. If NOPAD is true, "=" padding is not added
+ * at the end of the transformed buffer. */
+char *base64_encode(const void *in, const size_t inlen, char *out,
+ const bool b64ish, const bool nopad) {
+ const char *alphabet;
+ const uint8_t *b;
+ char *p;
+ size_t i;
+
+ alphabet = b64ish ? b64ishchars : b64chars;
+ b = (const uint8_t*)in;
+
+ for (i = 0, p = out; i < inlen; i += 3) {
+ uint8_t bb[3] = {0};
+ unsigned j;
+ size_t n, k;
+
+ n = inlen - i;
+ if (n > 3) n = 3;
+ for (k = 0; k < n; ++k) bb[k] = b[i + k];
+
+ j = bb[0] >> 2;
+ *(p++) = alphabet[j];
+
+ j = ((bb[0] & 3) << 4) | (bb[1] >> 4);
+ *(p++) = alphabet[j];
+
+ if (n == 1) {
+ if (!nopad) {
+ *(p++) = '=';
+ *(p++) = '=';
+ }
+ break;
+ }
+
+ j = ((bb[1] & 0xf) << 2) | (bb[2] >> 6);
+ *(p++) = alphabet[j];
+ if (n == 2) {
+ if (!nopad)
+ *(p++) = '=';
+ break;
+ }
+
+ j = bb[2] & 0x3f;
+ *(p++) = alphabet[j];
+ }
+
+ *p = 0;
+
+ return out;
+}
+
+/* base64_decode IN OUT OUTLEN B64ISH
+ * Decode the string at IN into OUT. If B64ISH is true, the alternate
+ * "base64ish" alphabet is used instead of the standard one. Returns the number
+ * of characters consumed and saves the number of output bytes decoded in
+ * *OUTLEN; the number of characters consumed will be smaller than the length
+ * of the input string if an invalid character was encountered in IN. OUT must
+ * have space for at least 3 * (INLEN / 4) bytes of output. */
+size_t base64_decode(const char *in, void *out, size_t *outlen,
+ const bool b64ish) {
+ const char *alphabet;
+ uint8_t *b;
+ size_t inlen = 0, consumed = 0, len = 0, i;
+
+ inlen = strlen(in);
+ alphabet = b64ish ? b64ishchars : b64chars;
+ b = (uint8_t*)out;
+
+ for (i = 0; i < inlen; i += 4) {
+ char bb[5] = "====";
+ size_t n, j;
+ const char *p;
+
+ n = inlen - i;
+ if (n > 4) n = 4;
+ memcpy(bb, in + i, n);
+
+ if (!(p = strchr(alphabet, bb[0])))
+ break;
+ j = p - alphabet;
+ b[len] = (uint8_t)(j << 2);
+ ++consumed;
+
+ if (!(p = strchr(alphabet, bb[1])))
+ break;
+ j = p - alphabet;
+ b[len++] |= (uint8_t)(j >> 4);
+ b[len] = (uint8_t)(j << 4);
+ ++consumed;
+
+ if ('=' == bb[2]) {
+ ++consumed;
+ if ('=' == *p) ++consumed; /* potentially skip last char */
+ break;
+ } else if (!(p = strchr(alphabet, bb[2])))
+ break;
+ j = p - alphabet;
+ b[len++] |= (uint8_t)(j >> 2);
+ b[len] = (uint8_t)(j << 6);
+ ++consumed;
+
+ if ('=' == bb[3]) {
+ ++consumed;
+ break;
+ } else if (!(p = strchr(alphabet, bb[3])))
+ break;
+ j = p - alphabet;
+ b[len++] |= (uint8_t)j;
+ ++consumed;
+ }
+
+ *outlen = len;
+ return consumed;
+}
+
+#ifdef BASE64_TEST_PROGRAM
+
+/*
+ * Small test program -- reads base64-encoded or raw data on standard input,
+ * and writes on standard output the decoded/encoded version. Driven by
+ * base64test.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define err(...) \
+ do { \
+ fprintf(stderr, "base64test: "); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ } while (0)
+#define die(...) do { err(__VA_ARGS__); exit(1); } while (0)
+
+struct datum {
+ void *buf;
+ size_t len;
+};
+
+static struct datum *netstring_read(FILE *fp) {
+ unsigned int len = 0;
+ int c;
+ struct datum *d;
+
+#define FAIL(what) \
+ do { \
+ if (feof(fp)) \
+ die("%s: Premature EOF", what); \
+ else \
+ die("%s: %s", what, strerror(errno)); \
+ } while (0)
+
+ while (EOF != (c = getc(fp))) {
+ if (isdigit(c))
+ len = 10 * len + c - '0';
+ else if (c == ':')
+ break;
+ else
+ die("bad character '%c' in netstring length", c);
+ }
+
+ if (feof(fp) || ferror(fp))
+ FAIL("while reading netstring length");
+
+ if (!(d = malloc((sizeof *d) + len + 1)))
+ die("malloc: %s", strerror(errno));
+ d->buf = (char*)d + (sizeof *d);
+ d->len = len;
+ ((char*)d->buf)[len] = 0; /* ensure NUL-terminated */
+
+ if (d->len != fread(d->buf, 1, d->len, fp))
+ FAIL("while reading netstring data");
+
+ if (EOF == (c = getc(fp))) {
+ if (feof(fp))
+ die("while reading netstring trailer: Premature EOF");
+ else
+ die("while reading netstring trailer: %s", strerror(errno));
+ }
+
+ return d;
+}
+
+void netstring_write(FILE *fp, const struct datum *d) {
+ fprintf(fp, "%u:", (unsigned)d->len);
+ if (d->len != fwrite(d->buf, 1, d->len, fp))
+ die("while writing netstring value: %s", strerror(errno));
+ if (1 != fprintf(fp, ","))
+ die("while writing netstring trailer: %s", strerror(errno));
+}
+
+/* main ARGC ARGV
+ * Entry point. */
+int main(int argc, char *argv[]) {
+ while (1) {
+ int c;
+ struct datum *d, d2;
+ size_t l;
+
+ if (EOF == (c = getc(stdin)))
+ die("premature EOF reading command character");
+ else if ('X' == c)
+ break;
+
+ d = netstring_read(stdin);
+
+ switch (c) {
+ case 'B': /* base64 */
+ case 'b': /* base64ish */
+ if (!(d2.buf = malloc(d->len)))
+ die("malloc: %s", strerror(errno));
+ l = base64_decode(d->buf, d2.buf, &d2.len, c == 'b');
+ netstring_write(stdout, &d2);
+ free(d2.buf);
+ break;
+
+ case 'R': /* to base64 */
+ case 'r': /* to base64ish */
+ if (!(d2.buf = malloc(1 + 4 * (1 + d->len / 3))))
+ die("malloc: %s", strerror(errno));
+ base64_encode(d->buf, d->len, d2.buf, c == 'r', 0);
+ d2.len = strlen((char*)d2.buf);
+ netstring_write(stdout, &d2);
+ free(d2.buf);
+ break;
+
+ default:
+ die("bad command character '%c'", c);
+ }
+
+ free(d);
+ fflush(stdout);
+ }
+ return 0;
+}
+
+#endif /* BASE64_TEST_PROGRAM */
diff --git a/tileserver/base64.h b/tileserver/base64.h
new file mode 100644
index 000000000..13acd356c
--- /dev/null
+++ b/tileserver/base64.h
@@ -0,0 +1,21 @@
+/*
+ * base64.h:
+ * Base64 and "base64ish" encoding and decoding.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ * $Id: base64.h,v 1.1 2006-09-20 10:25:14 chris Exp $
+ *
+ */
+
+#ifndef __BASE64_H_ /* include guard */
+#define __BASE64_H_
+
+/* base64.c */
+char *base64_encode(const void *in, const size_t inlen, char *out,
+ const bool b64ish, const boolnopad);
+size_t base64_decode(const char *in, void *out, size_t *outlen,
+ const bool b64ish);
+
+#endif /* __BASE64_H_ */
diff --git a/tileserver/netstring.c b/tileserver/netstring.c
new file mode 100644
index 000000000..b30ca5a67
--- /dev/null
+++ b/tileserver/netstring.c
@@ -0,0 +1,44 @@
+/*
+ * netstring.c:
+ * Write netstrings to strings.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ */
+
+static const char rcsid[] = "$Id: netstring.c,v 1.1 2006-09-20 10:25:14 chris Exp $";
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+
+/* netstring_write OUT BUFFER LEN
+ * Write the LEN-byte BUFFER to OUT as a netstring, returning the number of
+ * bytes. If OUT is NULL, returns the number of bytes required. */
+size_t netstring_write(char *out, const void *buf, const size_t len) {
+ size_t l = 0;
+ char dummy[32];
+ l += sprintf(out ? out : dummy, "%u:", (unsigned)len);
+ if (out) memcpy(p, buf, len);
+ l += len;
+ if (out) buf[l] = ',';
+ ++l;
+ return l;
+}
+
+/* netstring_write_string OUT STRING
+ * Write the NUL-terminated STRING to OUT as a netstring, returning the number
+ * of bytes used. If OUT is NULL, return the number of bytes required. */
+size_t netstring_write_string(char *out, const char *str) {
+ return netstring_write(out, str, strlen(str));
+}
+
+/* netstring_write_string OUT I
+ * Write I to OUT as a decimal integer formatted as a netstring, returning the
+ * number of bytes used. If OUT is NULL, return the number of bytes required. */
+size_t netstring_write_int(char *out, const int i) {
+ char str[32];
+ sprintf(str, "%d", i);
+ return netstring_write_string(out, str);
+}
diff --git a/tileserver/netstring.h b/tileserver/netstring.h
new file mode 100644
index 000000000..83d5aabfc
--- /dev/null
+++ b/tileserver/netstring.h
@@ -0,0 +1,22 @@
+/*
+ * netstring.h:
+ * Write netstrings into strings.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ * $Id: netstring.h,v 1.1 2006-09-20 10:25:14 chris Exp $
+ *
+ */
+
+#ifndef __NETSTRING_H_ /* include guard */
+#define __NETSTRING_H_
+
+#include <sys/types.h>
+
+/* netstring.c */
+size_t netstring_write(char *out, const void *buf, const size_t len);
+size_t netstring_write_string(char *out, const char *str);
+size_t netstring_write_int(char *out, const int i);
+
+#endif /* __NETSTRING_H_ */
diff --git a/tileserver/tileserver.c b/tileserver/tileserver.c
new file mode 100644
index 000000000..f64ff209f
--- /dev/null
+++ b/tileserver/tileserver.c
@@ -0,0 +1,416 @@
+/*
+ * tileserver.c:
+ * Serve map tiles and information about map tiles.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ */
+
+static const char rcsid[] = "$Id: tileserver.c,v 1.1 2006-09-20 10:25:14 chris Exp $";
+
+/*
+ * This is slightly complicated by the fact that we indirect tile references
+ * via hashes of the tiles themselves. We support the following queries:
+ *
+ * http://host/path/tileserver/TILESET/HASH
+ * to get an individual tile image in TILESET identified by HASH;
+ * http://host/path/tileserver/TILESET/E,N/FORMAT
+ * to get the identity of the tile at (E, N) in TILESET in the given
+ * FORMAT;
+ * http://host/path/tileserver/TILESET/W-E,S-N/FORMAT
+ * to get the identities of the tiles in the block with SW corner (W, S)
+ * and NE corner (E, N) in the given FORMAT.
+ *
+ * What FORMATS should we support? RABX and JSON are the obvious ones I guess.
+ */
+
+#include "tileset.h"
+
+/* tileset_path
+ * The configured path to tilesets. */
+char *tileset_path;
+
+#define HTTP_BAD_REQUEST 400
+#define HTTP_UNAUTHORIZED 401
+#define HTTP_FORBIDDEN 403
+#define HTTP_NOT_FOUND 404
+#define HTTP_INTERNAL_SERVER_ERROR 500
+#define HTTP_NOT_IMPLEMENTED 501
+#define HTTP_SERVICE_UNAVAILABLE 503
+
+/* err FORMAT [ARG ...]
+ * Write an error message to standard error. */
+#define err(...) \
+ do { \
+ fprintf(stderr, "tileserver: "); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ } while (0)
+
+/* die FORMAT [ARG ...]
+ * Write an error message to standard error and exit unsuccessfully. */
+#define die(...) do { err(__VA_ARGS__); exit(1); } while (0)
+
+/* error STATUS TEXT
+ * Send an error to the client with the given HTTP STATUS and TEXT. */
+void error(int status, const char *s) {
+ if (status < 100 || status > 999)
+ status = 500;
+ printf(
+ "Status: %03d\r\n"
+ "Content-Type: text/plain; charset=us-ascii\r\n"
+ "Content-Length: %u\r\n"
+ "\r\n"
+ "%s\n",
+ status,
+ strlen(s) + 1);
+}
+
+/* struct request
+ * Definition of a request we handle. */
+struct request {
+ char *r_tileset;
+ enum {
+ FN_GET_TILE = 0,
+ FN_GET_TILEIDS
+ } r_function;
+
+ uint8_t r_tilehash[TILEHASH_LEN];
+
+ int r_west, r_east, r_south, r_north;
+ enum {
+ F_RABX,
+ F_JSON,
+ F_TEXT
+ } r_format;
+
+ char *r_buf;
+};
+
+/* request_parse PATHINFO
+ * Parse a request from PATHINFO. Returns a request on success or NULL on
+ * failure. */
+struct request *request_parse(const char *path_info) {
+ const char *p, *q;
+ struct request *R = NULL, Rz = {0};
+
+ /* Some trivial syntax checks. */
+ if (!*path_info || *path_info == '/' || !strchr(path_info, '/'))
+ return NULL;
+
+
+ /*
+ * TILESET/HASH
+ * TILESET/E,N/FORMAT
+ * TILESET/W-E,S-N/FORMAT
+ */
+
+ /* Tileset name consists of alphanumerics and hyphen. */
+ p = path_info + strspn(path_info,
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "-");
+
+ if (*p != '/')
+ return NULL;
+
+ R = xmalloc(sizeof *R);
+ *R = Rz;
+ R->r_buf = xmalloc(strlen(path_info) + 1);
+ R->r_tileset = R->r_buf;
+
+ strncpy(R->r_tileset, path_info, p - path_info);
+ R->r_tileset[p - path_info] = 0;
+
+ ++p;
+
+ /* Mode. */
+ if ((q = strchr(p, '/'))) {
+ /* Tile IDs request. */
+ R->r_function = FN_GET_TILEIDS;
+
+ /* Identify format requested. */
+ ++q;
+ if (!strcmp(q, "RABX"))
+ R->r_format = F_RABX;
+ else if (!strcmp(q, "JSON"))
+ R->r_format = F_JSON;
+ else if (!strcmp(q, "text"))
+ R->r_format = F_TEXT;
+ else
+ goto fail;
+
+ if (4 == sscanf(p, "%d-%d,%d-%d",
+ &R->r_west, &R->r_east, &R->r_south, &R->r_north)) {
+ if (R->r_west < 0 || R->r_south < 0
+ || R->r_east < R->r_west || R->r_north < R->r_south)
+ goto fail;
+ else
+ return R;
+ } else if (2 == sscanf(p, "%d,%d", &R->r_west, &R->r_south)) {
+ R->r_east = R->r_west;
+ R->r_north = R->r_south;
+ if (R->r_west < 0 || R->r_south < 0)
+ goto fail;
+ else
+ return R;
+ }
+ } else {
+ size_t l;
+
+ /* Tile request. */
+ R->r_function = FN_GET_TILE;
+ if (strlen(p) != TILEID_LEN_B64)
+ goto fail;
+
+ /* Decode it. Really this is "base64ish", so that we don't have to
+ * deal with '+' or '/' in the URL. */
+ base64_decode(p, R->r_tilehash, &l, 1);
+ if (l != TILEID_LEN)
+ goto fail;
+
+ return R;
+ }
+
+fail:
+ request_free(R);
+ return NULL;
+}
+
+void request_free(struct request *R) {
+ if (!R) return;
+ xfree(R->r_buf);
+ xfree(R);
+}
+
+void handle_request(void) {
+ char *path_info;
+ struct request *R;
+ static char *path;
+ static size_t pathlen;
+ size_t l;
+ tileset T;
+ time_t when;
+ struct tm tm;
+ char timebuf[32];
+
+ /* All requests are given via PATH_INFO. */
+ if (!(path_info = getenv("PATH_INFO"))) {
+ error(400, "No request path supplied");
+ return;
+ }
+
+ if (!(R = request_parse(path_info))) {
+ error(400, "Bad request");
+ return;
+ }
+
+ /* So we have a valid request. */
+ l = strlen(R->r_tileset) + strlen(tileset_path) + 2;
+ if (pathlen < l)
+ pathlen = xrealloc(path, pathlen = l);
+ sprintf(path, "%s/%s", tileset_path, R->r_tileset);
+
+ if (!(T = tileset_open(path))) {
+ error(404, "Tileset not found");
+ /* XXX assumption about the nature of the error */
+ return;
+ }
+
+ if (FN_GET_TILE == R->r_function) {
+ /*
+ * Send a single tile image to the client.
+ */
+ void *buf;
+ size_t len;
+
+ if ((buf = tileset_get_tile(T, T->r_tileid, &len))) {
+ printf(
+ "Content-Type: image/png\r\n"
+ "Content-Length: %u\r\n"
+ "\r\n");
+ fwrite(stdout, 1, buf, len);
+ xfree(buf);
+ } else
+ error(404, "Tile not found");
+ /* XXX error assumption */
+ } else if (FN_GET_TILEIDS = R->r_function) {
+ /*
+ * Send one or more tile IDs to the client, in some useful format.
+ */
+ unsigned x, y;
+ static char *buf;
+ static size_t buflen, n;
+ unsigned rows, cols;
+ char *p;
+
+ rows = R->r_north + 1 - R->r_south;
+ cols = R->r_east + 1 - R->r_west;
+ n = cols * rows;
+ if (buflen < n * TILEID_LEN_B64 + 256)
+ buf = xrealloc(buf, buflen = n * TILEID_LEN_B64 + 256);
+
+ /* Send start of array in whatever format. */
+ p = buf;
+ switch (R->r_format) {
+ case F_RABX:
+ /* Format as array of arrays. */
+ *(p++) = 'L';
+ p += netstring_write_int(p, (int)rows);
+ break;
+
+ case F_JSON:
+ /* Ditto. */
+ *(p++) = '[';
+ break;
+
+ case F_TEXT:
+ /* Space and LF separated matrix so no special leader. */
+ break;
+ }
+
+ /* Iterate over tile IDs. */
+ for (y = R->r_south; y <= R->r_north; ++y) {
+ switch (R->r_format) {
+ case F_RABX:
+ *(p++) = 'L';
+ p += netstring_write_int(p, (int)cols);
+
+ case F_JSON:
+ *(p++) = '[';
+
+ case F_TEXT:
+ break; /* nothing */
+ }
+
+ for (x = R->r_west; x <= R->r_east; ++x) {
+ uint8_t id[TILEID_LEN];
+ char idb64[TILEID_LEN_B64 + 1];
+ bool isnull = 0;
+ size_t l;
+
+ if (!(tileset_get_tileid(T, x, y, id)))
+ isnull = 1;
+ else
+ base64_encode(id, TILEID_LEN, idb64, 1, 1);
+
+ if (p + 256 > buflen) {
+ size_t n;
+ n = p - buf;
+ buf = xrealloc(buf, buflen *= 2);
+ p = buf + n;
+ }
+
+ switch (R->r_format) {
+ case F_RABX:
+ if (isnull) {
+ *(p++) = 'T';
+ p += netstring_write(p, idb64);
+ } else
+ *(p++) = 'N';
+ break;
+
+ case F_JSON:
+ if (isnull) {
+ strcpy(p, "null");
+ p += 4;
+ } else {
+ *(p++) = '"';
+ strcpy(p, idb64);
+ p += TILEID_LEN_B64;
+ *(p++) = '"';
+ }
+ if (x < R->r_east)
+ *(p++) = ',';
+ break;
+
+ case F_TEXT:
+ if (isnull)
+ *(p++) = '-';
+ else {
+ strcpy(p, idb64);
+ p += TILEID_LEN_B64;
+ }
+ if (x < R->r_east)
+ *(p++) = ' ';
+ break;
+ }
+ }
+
+ switch (R->r_format) {
+ case F_RABX:
+ break; /* no row terminator */
+
+ case F_JSON:
+ *(p++) = ']';
+ if (y < R->r_north)
+ *(p++) = ',';
+ break;
+
+ case F_TEXT:
+ *(p++) = '\n';
+ break;
+ }
+ }
+
+ /* Array terminator. */
+ switch (R->r_format) {
+ case F_RABX:
+ break;
+
+ case F_JSON:
+ *(p++) = ']';
+ break;
+
+ case F_TEXT:
+ *(p++) = '\n';
+ break;
+ }
+ *(p++) = 0;
+
+ /* Actually send it. */
+ printf("Content-Type: ");
+ switch (R->r_format) {
+ case F_RABX:
+ printf("application/octet-stream");
+ break;
+
+ case F_JSON:
+ /* Not really clear what CT to use here but Yahoo use
+ * "text/javascript" and presumably they've done more testing
+ * than us.... */
+ printf("text/javascript");
+ break;
+
+ case F_TEXT:
+ printf("text/plain; charset=us-ascii");
+ break;
+ }
+ printf("\r\n"
+ "Content-Length: %u\r\n"
+ "\r\n",
+ (unsigned)(p - buf));
+
+ fwrite(buf, 1, p - buf, stdout);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ struct stat st;
+
+ if (2 != argc) {
+ die("single argument is path to tile sets");
+ }
+ tileset_path = argv[1];
+ if (-1 == stat(tileset_path, &st))
+ die("%s: stat: %s", tileset_path, strerror(errno));
+ else if (!S_ISDIR(st.st_mode))
+ die("%s: Not a directory", tileset_path);
+
+ while (FCGI_Accept() >= 0)
+ handle_request();
+
+ return 0;
+}
diff --git a/tileserver/tileset.c b/tileserver/tileset.c
new file mode 100644
index 000000000..b4e91d37d
--- /dev/null
+++ b/tileserver/tileset.c
@@ -0,0 +1,203 @@
+/*
+ * tileset.c:
+ * Interface to an individual tile set.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ */
+
+static const char rcsid[] = "$Id: tileset.c,v 1.1 2006-09-20 10:25:14 chris Exp $";
+
+/*
+ * Tile sets are stored in directory trees which contain indices of tile
+ * locations to tile IDs, packed archives of tile images, and indices of where
+ * each tile image lives in the corresponding packed file. Tile IDs are SHA1
+ * digests of the tile contents, enabling efficient storage in the presence of
+ * repeated tiles.
+ *
+ * Note that this doesn't have attractive properties for locality of reference.
+ * That will need fixing if performance under the current scheme is not
+ * acceptable.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "cdb.h"
+#include "tileset.h"
+
+#define TILEID_LEN 20
+
+struct tileset {
+ char *t_path, *t_pathbuf;
+ cdb t_tileid_idx;
+ /* Tile IDs are stored in the tile ID database in N-by-N square blocks,
+ * so that the tile ID for (X, Y) is obtained by getting the block for
+ * (X / N, Y / N) and looking up (X, Y) within it. The blocking factor is
+ * (hopefully!) chosen to be about a disk block in size. It is stored in
+ * the tile ID database. Rather than allocating a specific tile ID for a
+ * tile which is not present, each information block is preceded by a
+ * coverage bitmap. */
+ unsigned t_blocking;
+ bool t_first;
+ unsigned t_x, t_y;
+ cdb_datum t_block;
+ /* File pointer open on an image file and the name of the file it's open
+ * on. */
+ char *t_imgfile;
+ FILE *t_fpimg;
+};
+
+
+/* tileset_open PATH
+ * Open the tileset at PATH, returning a tileset object on success or NULL on
+ * failure. */
+tileset tileset_open(const char *path) {
+ struct tileset *T, Tz = {0};
+ static char *path;
+ static size_t pathlen;
+ size_t l;
+ cdb_datum d = NULL;
+ char *s;
+ int i;
+
+ T = xmalloc(sizeof *T);
+ *T = Tz;
+
+ T->t_first = 1;
+ T->t_path = xstrdup(path);
+ T->t_pathbuf = xmalloc(strlen(path) + sizeof "/a/b/c/index.cdb");
+
+ /* Open the tile ID index. */
+ sprintf(T->t_pathbuf, "%s/index.cdb", T->t_path);
+ if (!(T->t_tileid_idx = cdb_open(T->t_pathbuf)))
+ goto fail;
+
+ /* get blocking factor */
+ if (!(d = cdb_get_string(T->t_tileid_idx, "blocking")))
+ goto fail;
+ s = (char*)d->cd_buf;
+ if (!(i = atoi(s)) || i < 0)
+ goto fail;
+ T->t_blocking = (unsigned)i;
+
+ cdb_datum_free(d);
+
+ return T;
+
+fail:
+ tileset_free(T);
+ if (d) cdb_datum_free(d);
+ return NULL;
+}
+
+/* tileset_close T
+ * Free resources associated with T. */
+void tileset_close(tileset T) {
+ if (!T) return;
+ cdb_close(T->t_tileid_idx);
+ xfree(T->t_path);
+ xfree(T->t_pathbuf);
+ xfree(T);
+}
+
+static size_t blockmap_bitmap_len(const unsigned blocking) {
+ return (blocking * blocking + 8) / 8;
+}
+
+static size_t blockmap_len(const unsigned blocking) {
+ size_t l;
+ /* Bitmap of null tiles. */
+ l = blockmap_bitmap_len(blocking);
+ /* Tile IDs themselves */
+ l += blocking * blocking * TILEID_LEN;
+ return l;
+}
+
+/* tileset_get_tileid T X Y ID
+ * Write into ID the tile ID of the tile at (X, Y) in T, returning true on
+ * success or false on failure. */
+bool tileset_get_tileid(tileset T, const unsigned x, const unsigned y,
+ uint8_t *id) {
+ unsigned x2, y2, off, off0;
+ uint8_t *b;
+
+ if (T->t_first || T->t_x != x || T->t_y != y) {
+ /* Grab block from database. */
+ char buf[32];
+
+ T->t_first = 0;
+ if (T->t_block) cdb_datum_free(T->t_block);
+
+ sprintf(buf, "%u,%u", x / T->t_blocking, y / T->t_blocking);
+ T->t_block = cdb_get_string(T->t_tileid_idx, buf);
+ if (!T->block)
+ return 0;
+ }
+
+ if (T->t_block->cd_len != blockmap_len(T->t_blocking))
+ return 0;
+ /* XXX also report bogus ID block */
+
+ b = (uint8_t*)T->t_block->cd_buf;
+
+ x2 = x % T->t_blocking;
+ y2 = y % T->t_blocking;
+ off = (x2 + y2 * T->t_blocking);
+
+ /* For a tile not present the corresponding bit in the bitmap is set. */
+ if (b[off >> 3] & (1 << (off & 7)))
+ return 0;
+
+ off0 = blockmap_bitmap_len(T->t_blocking);
+ memcpy(id, b + off0 + off * TILEID_LEN, TILEID_LEN);
+
+ return 1;
+}
+
+/* tileset_get_tile T ID LEN
+ * Retrieve the tile identified by ID, writing its length into *LEN and
+ * returning a malloced buffer containing its contents on success, or returning
+ * NULL on failure. */
+void *tileset_get_tile(tileset T, const uint8_t *id, size_t *len) {
+ cdb idx = NULL;
+ cdb_datum d = NULL;
+ void *ret = NULL;
+ unsigned off, len;
+ FILE *fp = NULL;
+
+ sprintf(T->t_pathbuf, "%s/%x/%x/%x/index.cdb",
+ T->t_path, (unsigned)(id[0] >> 4),
+ (unsigned)(id[0] & 0xf), (unsigned)(id[1] >> 4));
+ if (!(idx = cdb_open(T->t_pathbuf)))
+ return NULL;
+ /* also maybe report bogus index */
+
+ if (!(d = cdb_get_string(idx, T->t_pathbuf)))
+ goto fail;
+
+ if (2 != sscanf((char*)d->cd_buf, "%x:%x", &off, &len))
+ goto fail;
+
+ sprintf(T->t_pathbuf, "%s/%x/%x/%x/tiles",
+ T->t_path, (unsigned)(id[0] >> 4),
+ (unsigned)(id[0] & 0xf), (unsigned)(id[1] >> 4));
+ if (!(fp = fopen(T->t_pathbuf, "rb")))
+ goto fail;
+ else if (-1 == fseek(fp, off, SEEK_SET))
+ goto fail;
+
+ ret = xmalloc(len);
+ if (len != fread(ret, 1, len, fp)) {
+ xfree(ret);
+ goto fail;
+ }
+
+fail:
+ if (idx) cdb_close(idx);
+ if (d) cdb_datum_free(d);
+ if (fp) fclose(fp);
+ return ret;
+}
diff --git a/tileserver/tileset.h b/tileserver/tileset.h
new file mode 100644
index 000000000..d14a5ed30
--- /dev/null
+++ b/tileserver/tileset.h
@@ -0,0 +1,30 @@
+/*
+ * tileset.h:
+ * Interface to an individual tile set.
+ *
+ * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
+ * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
+ *
+ * $Id: tileset.h,v 1.1 2006-09-20 10:25:14 chris Exp $
+ *
+ */
+
+#ifndef __TILESET_H_ /* include guard */
+#define __TILESET_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define TILEID_LEN 20
+#define TILEID_LEN_B64 27
+
+typedef struct tileset *tileset;
+
+/* tileset.c */
+tileset tileset_open(const char *path);
+void tileset_close(tileset T);
+bool tileset_get_tileid(tileset T, const unsigned x, const unsigned y,
+ uint8_t *id);
+void *tileset_get_tile(tileset T, const uint8_t *id, size_t *len);
+
+#endif /* __TILESET_H_ */