diff options
-rw-r--r-- | tileserver/base64.c | 272 | ||||
-rw-r--r-- | tileserver/base64.h | 21 | ||||
-rw-r--r-- | tileserver/netstring.c | 44 | ||||
-rw-r--r-- | tileserver/netstring.h | 22 | ||||
-rw-r--r-- | tileserver/tileserver.c | 416 | ||||
-rw-r--r-- | tileserver/tileset.c | 203 | ||||
-rw-r--r-- | tileserver/tileset.h | 30 |
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_ */ |