source: trunk/npemap.org.uk/scripts/postcodeine/postcodeine.c @ 268

Last change on this file since 268 was 268, checked in by David Sheldon, 14 years ago

Postcodeine code from Mr Lightfoot. Minor modifications.

File size: 17.6 KB
Line 
1/*
2 * postcodeine.c:
3 * Given a postcode prefix, plot a map of all the postcodes matching that
4 * prefix.
5 *
6 * Copyright (c) 2006 UK Citizens Online Democracy. All rights reserved.
7 * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
8 *
9 *     According to http://www.mysociety.org/?p=109 this is licenced under
10 *     the Affero GPL http://www.affero.org/oagpl.html
11 *
12 *     Original code: http://bitter.ukcod.org.uk/~chris/postcodeine/code/
13 *
14 * Changed 2006/11/01 David Sheldon to change the paths where it keeps data, and
15 *                     to change the input format it requires. Also removed the osni
16 *                     projection as we don't have NI postcodes and it was giving me
17 *                     proj errors. Also actually parse the IF_MODIFIED_SINCE header,
18 *                     and set the expires to 12 hours rather than 1 year.
19 * (sorry about my C).
20 */
21
22static const char rcsid[] = "$Id: postcodeine.c,v 1.5 2006/01/19 19:03:23 chris Exp chris $";
23#define _XOPEN_SOURCE
24
25
26#include <sys/types.h>
27
28#include <ctype.h>
29#include <dirent.h>
30#include <errno.h>
31#include <fcgi_stdio.h>
32#include <proj_api.h>
33#include <stdbool.h>
34#include <stdint.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38
39
40#include <time.h>
41#include <unistd.h>
42
43#include <sys/stat.h>
44
45#include "postcodeine.h"
46
47#define MIN_LON         -11.
48#define MAX_LON           2.
49#define MIN_LAT          49.5
50#define MAX_LAT          59.5
51
52#define POSTCODE_CACHE  "/tmp/postcodes.dat"
53#define POSTCODE_DIR    "/tmp/postcodes/"
54#define IMAGE_CACHE_DIR "/tmp"
55
56#define die(...)        do { fprintf(stderr, "postcodeine[%d]: ", (int)getpid()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(1); } while (0)
57
58struct image images[] = {
59        { 301,  400, NULL },
60        { 902, 1200, NULL },
61    };
62
63#define NIMAGES 2
64
65struct postcodepos {
66    char postcode[10];
67    double x, y;    /* float'd do */
68};
69
70/* clear_image I
71 * Clear I. */
72static void clear_image(struct image *I) {
73    memset((char*)I->data + I->h * sizeof(pixel*), 0, I->h * I->w * sizeof(pixel));
74}
75
76/* grid_to_map IRISH E N X Y
77 * Convert the grid position (E, N) to an (X, Y) coordinate on the map. If
78 * IRISH is true, the Irish grid is used; otherwise OSGB. */
79static void grid_to_map(const bool is_irish_grid, const int e, const int n, double *x, double *y) {
80    static const char *osgb_params[] = { "init=world:bng", NULL },
81                      *merc_params[] = { "proj=merc", "ellps=WGS84", NULL };
82    static projPJ osgb, merc;
83    projUV c1 = {0}, c2 = {0};
84    static double E0, N0, E1, N1;
85    if (!osgb) {
86        /* XXX these seem to abort on failure rather than returning an error */
87        if (!(osgb = pj_init(1, (char**)osgb_params)))
88            die("%s: pj_init1: %s", osgb_params[0], pj_strerrno(pj_errno));
89        if (!(merc = pj_init(2, (char**)merc_params)))
90            die("%s: pj_init3: %s %s", merc_params[0], merc_params[1], pj_strerrno(pj_errno));
91
92        /* Figure out bounds of the map. */
93        c1.u = MIN_LON * DEG_TO_RAD;
94        c1.v = MAX_LAT * DEG_TO_RAD;
95        c2 = pj_fwd(c1, merc);
96        E0 = c2.u;
97        N0 = c2.v;
98
99        c1.u = MAX_LON * DEG_TO_RAD;
100        c1.v = MIN_LAT * DEG_TO_RAD;
101        c2 = pj_fwd(c1, merc);
102        E1 = c2.u;
103        N1 = c2.v;
104    }
105
106    /* grid to lat/lon. */
107    c1.u = e;
108    c1.v = n;
109    c2 = pj_inv(c1, osgb);
110
111    /* XXX at this point we have a lat/lon registered to the Airy or modified
112     * Airy datum. We should transform it to the WGS84 datum using a Helmert
113     * transform or whatever, but for this scale it's not really worth the
114     * effort. */
115
116    /* lat/lon to Mercator eastings/northings. */
117    c1 = pj_fwd(c2, merc);
118
119    *x = (c1.u - E0) / (E1 - E0);
120    *y = (c1.v - N0) / (N1 - N0);
121}
122
123struct pcindex {
124    int first, last;
125};
126
127static int tobase36(const char c) {
128    if (tolower(c) >= 'a' && tolower(c) <= 'z')
129        return tolower(c) - 'a';
130    else if (c >= '0' && c <= '9')
131        return 26 + c - '0';
132    else
133        die("bad character '%c'", c);
134}
135
136static bool fread_all(void *buf, const size_t n, FILE *fp) {
137    return fread(buf, 1, n, fp) == n;
138}
139
140static bool fwrite_all(const void *buf, const size_t n, FILE *fp) {
141    return fwrite(buf, 1, n, fp) == n;
142}
143
144static struct postcodepos *read_postcodes_cached(const char *dir, size_t *npcs, struct pcindex i1[36], struct pcindex i2[36][36], struct pcindex i3[36][36][36]) {
145    FILE *fp;
146    struct postcodepos *pp;
147    int a, b, c;
148    struct pcindex ind;
149
150    if (!(fp = fopen(POSTCODE_CACHE, "r")))
151        return NULL;
152
153    fprintf(stderr, "postcodeine[%d]: reading cached postcode data\n", (int)getpid());
154
155    if (!fread_all(npcs, sizeof *npcs, fp))
156        die("%s: read: %s", POSTCODE_CACHE, strerror(errno));
157
158    pp = malloc(*npcs * sizeof *pp);
159
160    if (!fread_all(pp, *npcs * sizeof *pp, fp))
161        die("%s: read: %s", POSTCODE_CACHE, strerror(errno));
162
163    for (a = 0; a < 36; ++a) {
164        if (!fread_all(&ind, sizeof(struct pcindex), fp))
165            die("%s: read: %s", POSTCODE_CACHE, strerror(errno));
166        i1[a] = ind;
167        for (b = 0; b < 36; ++b) {
168            if (!fread_all(&ind, sizeof(struct pcindex), fp))
169                die("%s: read: %s", POSTCODE_CACHE, strerror(errno));
170            i2[a][b] = ind;
171            for (c = 0; c < 36; ++c) {
172                if (!fread_all(&ind, sizeof(struct pcindex), fp))
173                    die("%s: read: %s", POSTCODE_CACHE, strerror(errno));
174                i3[a][b][c] = ind;
175            }
176        }
177    }
178
179    fclose(fp);
180
181    return pp;
182}
183
184/* read_postcodes DIRECTORY
185 * Read CSV files containing postcodes and their positions from DIRECTORY. */
186static struct postcodepos *read_postcodes(const char *dir, size_t *npcs, struct pcindex i1[36], struct pcindex i2[36][36], struct pcindex i3[36][36][36]) {
187    DIR *D;
188    struct postcodepos *pp;
189    size_t n = 0, nalloc;
190    struct dirent *e;
191    char fn[256];
192    FILE *fp;
193    int a, b, c;
194    struct pcindex ind;
195
196    if ((pp = read_postcodes_cached(dir, npcs, i1, i2, i3)))
197        return pp;
198   
199    if (!(D = opendir(dir)))
200        die("%s: opendir: %s", dir, strerror(errno));
201    pp = malloc((nalloc = 100000) * sizeof *pp);
202    while ((e = readdir(D))) {
203        char *q;
204        char line[256];
205        int linenum;
206
207        /* Check that the filename ends ".csv" or ".CSV". */
208        if ((!(q = strstr(e->d_name, ".csv"))
209                    && !(q = strstr(e->d_name, ".CSV")))
210            || q[4])
211            continue;
212       
213        sprintf(fn, "%s/%s", dir, e->d_name);
214        if (!(fp = fopen(fn, "r")))
215            die("%s: open: %s", fn, strerror(errno));
216
217        linenum = 1;
218        while (fgets(line, sizeof line, fp)) {
219            char *pc, *p;
220            int i, j, E, N;
221
222            /*
223             * Example line:
224             * "AB119NB",395004,805097
225             */
226
227            pc = line + 1;
228            p = strchr(pc, '\"');
229            if (!p) die("%s:%d: bad data (no quoted postcode)", fn, linenum);
230            *(p++) = 0;
231
232            for (i = 0; i < 1; ++i) {
233                p = strchr(p, ',');
234                if (!p) die("%s:%d: bad data (not enough fields)", fn, linenum);
235                ++p;
236            }
237
238            if (2 != sscanf(p, "%d,%d,", &E, &N))
239                die("%s:%d: bad data (no coordinates)", fn, linenum);
240
241            /* Some postcodes have no valid coordinate data. */
242            if (!E && !N)
243                continue;
244
245            if (n == nalloc)
246                pp = realloc(pp, (nalloc += 1000000) * sizeof *pp);
247
248            for (i = 0, j = 0; pc[i]; ++i)
249                if (pc[i] != ' ')
250                    pp[n].postcode[j++] = pc[i];
251            pp[n].postcode[j] = 0;
252
253            if (pp[n].postcode[strspn(pp[n].postcode, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")])
254                die("%s:%d: postcode \"%s\" contains invalid characters", fn, linenum, pp[n].postcode);
255           
256            grid_to_map(pp[n].postcode[0] == 'B' && pp[n].postcode[1] == 'T', E, N, &pp[n].x, &pp[n].y);
257
258            if (n > 0 && strcmp(pp[n - 1].postcode, pp[n].postcode) >= 0)
259                die("%s:%d: postcode \"%s\" does not follow previous postcode \"%s\" lexically", fn, linenum, pp[n].postcode, pp[n - 1].postcode);
260
261//                                              puts(pp[n].postcode);
262            a = tobase36(pp[n].postcode[0]);
263            b = tobase36(pp[n].postcode[1]);
264            c = tobase36(pp[n].postcode[2]);
265
266            if (i1[a].first == -1) {
267                i1[a].first = (int)n;
268            }
269            i1[a].last = (int)n;
270           
271            if (i2[a][b].first == -1)
272                i2[a][b].first = (int)n;
273            i2[a][b].last = (int)n;
274
275            if (i3[a][b][c].first == -1)
276                i3[a][b][c].first = (int)n;
277            i3[a][b][c].last = (int)n;
278
279            ++linenum;
280            ++n;
281        }
282
283        if (ferror(fp))
284            die("%s: %s", fn, strerror(errno));
285
286        fclose(fp);
287    }
288
289    fprintf(stderr, "postcodeine[%d]: writing cached postcode data\n", (int)getpid());
290    sprintf(fn, "%s.%d", POSTCODE_CACHE, (int)getpid());
291    if (!(fp = fopen(fn, "w")))
292        die("%s: open: %s", fn, strerror(errno));
293
294    *npcs = n;
295    if (!fwrite_all(npcs, sizeof *npcs, fp)
296        || !fwrite_all(pp, n * sizeof *pp, fp))
297        die("%s: write: %s", fn, strerror(errno));
298
299    for (a = 0; a < 36; ++a) {
300        ind = i1[a];
301        if (!fwrite_all(&ind, sizeof(struct pcindex), fp))
302            die("%s: write: %s", fn, strerror(errno));
303        for (b = 0; b < 36; ++b) {
304            ind = i2[a][b];
305            if (!fwrite_all(&ind, sizeof(struct pcindex), fp))
306                die("%s: write: %s", fn, strerror(errno));
307            for (c = 0; c < 36; ++c) {
308                ind = i3[a][b][c];
309                if (!fwrite_all(&ind, sizeof(struct pcindex), fp))
310                    die("%s: write: %s", fn, strerror(errno));
311            }
312        }
313    }
314
315    fclose(fp);
316
317    if (-1 == rename(fn, POSTCODE_CACHE)) {
318        unlink(fn);
319        die("%s: rename: %s", fn, strerror(errno));
320    }
321
322    return pp;
323}
324
325static void error(const int code, const char *str) {
326    printf(
327        "Status: %d %s\r\n"
328        "Content-Type: text/plain\r\n"
329        "Content-Length: %d\r\n"
330        "\r\n"
331        "%s\n",
332        code, str, strlen(str) + 1, str);
333}
334
335int main(int argc, char *argv[]) {
336    int i, j, k;
337    struct postcodepos *pcs;
338    size_t npcs;
339    struct pcindex idx1[36], idx2[36][36], idx3[36][36][36];
340    extern bool writepng(const char *filename, const struct image *img);
341    time_t modified;
342    time_t e;
343                struct stat file_stat;
344    struct tm *E;
345    char expirydate[32];
346
347    time(&e);
348   
349    e +=  86400 / 2;
350    E = gmtime(&e);
351    strftime(expirydate, sizeof expirydate, "%a, %d %b %Y %H:%M:%S GMT", E);
352
353    /* Must do this here so that error-handling works. */
354    if (FCGI_Accept() < 0)
355        die("first FCGI_Accept() returned < 0");
356
357    fprintf(stderr, "postcodeine[%d]: starting up\n", (int)getpid());
358   
359    /* Allocate space for the images. */
360    for (i = 0; i < NIMAGES; ++i) {
361        int y;
362        images[i].data = malloc(images[i].h * sizeof(pixel*) + images[i].h * images[i].w * sizeof(pixel));
363        for (y = 0; y < images[i].h; ++y)
364            images[i].data[y] = (pixel*)((char*)images[i].data + images[i].h * sizeof(pixel*) + y * images[i].w * sizeof(pixel));
365    }
366   
367    /* Initialise the three indices. */
368    for (i = 0; i < 36; ++i) {
369        idx1[i].first = idx1[i].last = -1;
370        for (j = 0; j < 36; ++j) {
371            idx2[i][j].first = idx2[i][j].last = -1;
372            for (k = 0; k < 36; ++k)
373                idx3[i][j][k].first = idx3[i][j][k].last = -1;
374        }
375    }
376
377    pcs = read_postcodes(POSTCODE_DIR, &npcs, idx1, idx2, idx3);
378                stat(POSTCODE_CACHE, &file_stat);
379                modified = file_stat.st_mtime;
380   
381    do {
382        char *query;
383        int n0, n1;
384        struct image *I;
385        char fn1[256], fn2[256];
386        int c;
387        FILE *fp;
388        struct stat st;
389        float X = 0, Y = 0;
390                                struct tm mod_since;
391                                time_t cached_time;
392
393        if (getenv("HTTP_IF_MODIFIED_SINCE")) {
394                                                strptime(getenv("HTTP_IF_MODIFIED_SINCE"),"%a, %d %b %Y %H:%M:%S %Z", &mod_since);
395            cached_time = mktime(&mod_since);
396                                                if (cached_time > modified) {
397                                                        printf(
398                                                                        "Status: 304 Not Modified\r\n"
399                                                                        "Content-Type: image/png\r\n"
400                                                                        "\r\n");
401                                                        continue;
402                                          }
403        }
404
405        /* First character is "B" for big map or "S" for small map; remainder
406         * is postcode prefix. */
407        query = getenv("QUERY_STRING");
408
409        if (!query || !*query || !strchr("BSXZ", *query)) {
410            error(400, "Bad query");
411            continue;
412        }
413
414        if (*query == 'Z') {
415            /* List of postcode zones matching. */
416            struct postcodezone *Z;
417            size_t ql;
418            char zone[3] = {0};
419            extern struct postcodezone zones[]; /* in zones.c */
420            printf(
421                "Content-Type: text/html\r\n"       /* sort-of */
422                "Expires: %s\r\n"
423                "\r\n"
424                "<table style=\"width: 100%%;\">"
425                "<tr><th style=\"width: 4em;\"></th><th></th></tr>",
426                expirydate);
427            ql = 0;
428            if (query[1] && isalpha(query[1])) {
429                zone[0] = query[1];
430                ql = 1;
431                if (query[2]) {
432                    zone[1] = isalpha(query[2]) ? query[2] : 0;
433                    ql = 2;
434                }
435            }
436            for (Z = zones; Z->zone; ++Z) {
437                if (!query[1] || memcmp(Z->zone, zone, ql) == 0)
438                    printf("<tr><td><strong>%.*s</strong>%s</td><td>%s</td></tr>",
439                            (int)strlen(zone), Z->zone, Z->zone + strlen(zone), Z->name);
440            }
441            printf("</table>");
442            continue;
443        }
444
445        sprintf(fn2, "%s/%s.png", IMAGE_CACHE_DIR, query);
446        sprintf(fn1, "%s.%d", fn2, (int)getpid());
447
448        if (*query != 'X' && (fp = fopen(fn2, "r")))
449            goto showimage;
450
451        if (*query == 'B')
452            I = images + 1;
453        else if (*query == 'S')
454            I = images;
455        else
456            I = NULL;
457        ++query;
458       
459        if (query[strspn(query, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
460                                "abcdefghijklmnopqrstuvwxyz"
461                                "0123456789")])
462            n0 = n1 = -1;
463        else if (!*query) {
464            n0 = 0;
465            n1 = npcs;
466        } else {
467            int a = -1, b = -1, c = -1;
468            a = tobase36(query[0]);
469            if (query[1]) {
470                b = tobase36(query[1]);
471                if (query[2]) c = tobase36(query[2]);
472            }
473
474            if (c != -1) {
475                size_t l;
476                n0 = idx3[a][b][c].first;
477                n1 = idx3[a][b][c].last + 1;
478                l = strlen(query);
479
480                /* test on I so that the "mean coordinates" mode isn't an
481                 * oracle for postcode-to-place lookups. */
482                if (l > 3 && I) {
483                    /* XXX should bisect */
484                    while (strncmp(pcs[n0].postcode, query, l) && n0 < n1) ++n0;
485                    if (n0 == n1)
486                        n0 = n1 = -1;
487                    else {
488                        while (strncmp(pcs[n1].postcode, query, l) && n1 > n0) --n1;
489                        ++n1;
490                    }
491                    if (n0 == n1)
492                        n0 = n1 = -1;
493                }
494
495            } else if (b != -1) {
496                n0 = idx2[a][b].first;
497                n1 = idx2[a][b].last + 1;
498            } else {
499                n0 = idx1[a].first;
500                n1 = idx1[a].last + 1;
501            }
502        }
503
504        fprintf(stderr, "query = \"%s\"; from = %d; to = %d\n", query, n0, n1);
505
506        if (I) clear_image(I);
507        if (n0 != -1) {
508            /* Draw a little square on each bit of the image. */
509            int n, d = 1, N;
510            if (n1 - n0 > 100000)
511                d = 10;
512            else if (n1 - n0 > 10000)
513                d = 2;
514            for (n = n0, N = 0; n < n1; n += d) {
515                int x, y, i, j;
516                ++N;
517                if (!I) {
518                    X += pcs[n].x;
519                    Y += pcs[n].y;
520                    continue;
521                }
522                x = pcs[n].x * I->w;
523                y = pcs[n].y * I->h;
524                for (j = y - 1; j <= y + 1; ++j) {
525                    if (j < 0 || j >= I->h) continue;
526                    for (i = x - 1; i <= x + 1; ++i) {
527                        if (i < 0 || i >= I->w) continue;
528                        I->data[j][i] = 1;
529                    }
530                }
531            }
532            X /= N;
533            Y /= N;
534        }
535
536        if (!I) {
537            char buf[32];
538            sprintf(buf, "%.5f,%.5f", X, Y);
539            printf(
540                "Content-Type: text/plain\r\n"
541                "Content-Length: %d\r\n"
542                "Expires: %s\r\n"
543                "\r\n"
544                "%s",
545                strlen(buf), expirydate, buf);
546            continue;
547        }
548
549        /* Write a PNG file. */
550        --query;
551        writepng(fn1, I);
552        rename(fn1, fn2);
553     
554        if (!(fp = fopen(fn2, "r"))) {
555            error(500, strerror(errno));
556            continue;
557        }
558
559showimage:
560        if (-1 == fstat(fileno(fp), &st)) {
561            error(500, strerror(errno));
562            fclose(fp);
563            continue;
564        }
565
566        printf(
567            "Content-Type: image/png\r\n"
568            "Content-Length: %u\r\n"
569            "\r\n",
570            (unsigned)st.st_size);
571
572        while (EOF != (c = getc(fp)))
573            putc(c, stdout);
574
575        fclose(fp);
576    } while (FCGI_Accept() >= 0);
577
578    fprintf(stderr, "postcodeine[%d]: shutting down\n", (int)getpid());
579   
580    return 0;
581}
Note: See TracBrowser for help on using the repository browser.