source: subversion/applications/utils/planet.osm/C/planet.c @ 4729

Last change on this file since 4729 was 4729, checked in by jonb, 12 years ago

planet.c: prevent warning about _GNU_SOURCE being redefined

File size: 14.9 KB
Line 
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <stdarg.h>
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <sys/socket.h>
12#include <sys/un.h>
13#include <unistd.h>
14#include <fcntl.h>
15#include <errno.h>
16#include <limits.h>
17#include <time.h>
18#include <utime.h>
19
20
21#include <mysql.h>
22#include <mysqld_error.h>
23#include <signal.h>
24#include <stdarg.h>
25#include <sslopt-vars.h>
26#include <assert.h>
27
28#include "keyvals.h"
29
30#define INDENT "  "
31
32static char escape_tmp[1024];
33
34const char *xmlescape(const char *in)
35{ 
36    /* character escaping as per http://www.w3.org/TR/REC-xml/ */
37
38    /* WARNING: this funxtion uses a static buffer so do not rely on the result
39     * being constant if called more than once
40     */
41    bzero(escape_tmp, sizeof(escape_tmp));
42    while(*in) {
43        int len = strlen(escape_tmp);
44        int left = sizeof(escape_tmp) - len - 1;
45
46        if (left < 7)
47            break;
48
49        switch(*in) {
50            case  '&': strncat(escape_tmp, "&amp;",  left); break;
51            //case '\'': strncat(escape_tmp, "&apos;", left); break;
52            case  '<': strncat(escape_tmp, "&lt;",   left); break;
53            case  '>': strncat(escape_tmp, "&gt;",   left); break;
54            case  '"': strncat(escape_tmp, "&quot;", left); break;
55            default: escape_tmp[len] = *in;
56        }
57        in++;
58    }
59    return escape_tmp;
60}
61
62static void osm_tags(struct keyval *tags)
63{
64    struct keyval *p;
65
66    while ((p = popItem(tags)) != NULL) {
67        printf(INDENT INDENT "<tag k=\"%s\"", xmlescape(p->key));
68        printf(" v=\"%s\" />\n", xmlescape(p->value));
69        freeItem(p);
70    }
71
72   resetList(tags);
73}
74
75static void osm_node(int id, long double lat, long double lon, struct keyval *tags, const char *ts)
76{
77    if (listHasData(tags)) {
78        printf(INDENT "<node id=\"%d\" lat=\"%.7Lf\" lon=\"%.7Lf\" timestamp=\"%s\">\n", id, lat, lon, ts);
79        osm_tags(tags);
80        printf(INDENT "</node>\n");
81    } else {
82        printf(INDENT "<node id=\"%d\" lat=\"%.7Lf\" lon=\"%.7Lf\" timestamp=\"%s\"/>\n", id, lat, lon, ts);
83    }
84}
85
86static void osm_segment(int id, int from, int to, struct keyval *tags, const char *ts)
87{
88    if (listHasData(tags)) {
89        printf(INDENT "<segment id=\"%d\" from=\"%d\" to=\"%d\" timestamp=\"%s\">\n", id, from, to, ts);
90        osm_tags(tags);
91        printf(INDENT "</segment>\n");
92    } else {
93        printf(INDENT "<segment id=\"%d\" from=\"%d\" to=\"%d\" timestamp=\"%s\"/>\n", id, from, to, ts);
94    }
95}
96
97static void osm_way(int id, struct keyval *segs, struct keyval *tags, const char *ts)
98{
99    struct keyval *p;
100
101    if (listHasData(tags) || listHasData(segs)) {
102        printf(INDENT "<way id=\"%d\" timestamp=\"%s\">\n", id, ts);
103        while ((p = popItem(segs)) != NULL) {
104            printf(INDENT INDENT "<seg id=\"%s\" />\n", p->value);
105            freeItem(p);
106        }
107        osm_tags(tags);
108        printf(INDENT "</way>\n");
109    } else {
110        printf(INDENT "<way id=\"%d\" timestamp=\"%s\"/>\n", id, ts);
111    }
112}
113
114
115void read_tags(const char *str, struct keyval *tags)
116{
117   enum tagState { sKey, sValue, sDone, sEnd} s;
118   char *key, *value;
119   const char *p, *key_start, *value_start;
120
121   if (!str || !*str)
122    return;
123   // key=value;key=value;...
124   p = str;
125   key_start = p;
126   s = sKey;
127   value_start = key = value = NULL;
128   while(s != sEnd) {
129       switch(s) {
130           case sKey:
131               if (*p == '=') {
132                   key = strndup(key_start, p - key_start);
133                   s = sValue;
134                   value_start = p+1;
135               }
136               p++;
137               break;
138
139           case sValue:
140               if (!*p || *p == ';') {
141                   value = strndup(value_start, p - value_start);
142                   s = sDone;
143                   key_start = p+1;
144               }
145               if (*p) p++;
146               break;
147
148           case sDone:
149               //printf("%s=%s\n", key, value);
150               addItem(tags, key, value, 0);
151               free(key);
152               free(value);
153               s = *p ? sKey : sEnd;
154               break;
155
156           case sEnd:
157               break;
158       }
159   }
160}
161
162void parseDate(struct tm *tm, const char *str)
163{
164    time_t tmp;
165    // 2007-05-20 13:51:35
166    bzero(tm, sizeof(*tm));
167    int n = sscanf(str, "%d-%d-%d %d:%d:%d",
168                   &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, &tm->tm_min, &tm->tm_sec);
169
170    if (n !=6)
171        printf("failed to parse date string, got(%d): %s\n", n, str);
172 
173    tm->tm_year -= 1900;
174    tm->tm_mon  -= 1;
175    tm->tm_isdst = -1;
176
177    // Converting to/from time_t ensures the tm_isdst field gets set to indicate GMT/BST
178    // Rails stores the timestamps in the DB using UK localtime.
179    tmp = mktime(tm);
180    localtime_r(&tmp, tm);
181}
182
183const char *strTime(struct tm *tm)
184{
185    static char out[64]; // Not thread safe
186
187    //2000-01-04T12:02:09+00:00
188    snprintf(out, sizeof(out), "%d-%02d-%02dT%02d:%02d:%02d+0%c:00",
189             tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
190             tm->tm_isdst ? '1':'0');
191
192    return out;
193}
194
195void nodes(MYSQL *mysql)
196{
197    char query[255];
198    MYSQL_RES *res;
199    MYSQL_ROW row;
200    struct keyval tags;
201
202    initList(&tags);
203
204    snprintf(query, sizeof(query), "select id, latitude, longitude, timestamp, tags from current_nodes where visible = 1 order by id");
205
206    if ((mysql_query(mysql, query)) || !(res= mysql_use_result(mysql)))
207    {
208        fprintf(stderr,"Cannot query nodes: %s\n", mysql_error(mysql));
209        exit(1);
210    }
211
212    while ((row= mysql_fetch_row(res))) {
213        long int id;
214        long double latitude,longitude;
215        const char *tag_str;
216        struct tm date;
217
218        assert(mysql_num_fields(res) == 5);
219
220        id = strtol(row[0], NULL, 10);
221#ifdef SCHEMA_V6
222        latitude  = strtol(row[1], NULL, 10) / 10000000.0;
223        longitude = strtol(row[2], NULL, 10) / 10000000.0;
224#else
225        latitude  = strtold(row[1], NULL);
226        longitude = strtold(row[2], NULL);
227#endif
228        parseDate(&date, row[3]);
229        tag_str = row[4];
230        read_tags(tag_str, &tags);
231
232        osm_node(id, latitude, longitude, &tags, strTime(&date));
233    }
234
235    mysql_free_result(res);
236}
237
238void segments(MYSQL *mysql)
239{
240    char query[255];
241    MYSQL_RES *res;
242    MYSQL_ROW row;
243    struct keyval tags;
244
245    initList(&tags);
246
247    snprintf(query, sizeof(query), "select id, node_a, node_b, timestamp, tags from current_segments where visible = 1 order by id");
248
249    if ((mysql_query(mysql, query)) || !(res= mysql_use_result(mysql)))
250    {
251        fprintf(stderr,"Cannot query segments: %s\n", mysql_error(mysql));
252        exit(1);
253    }
254
255    while ((row= mysql_fetch_row(res))) {
256        long int id, node_a, node_b;
257        const char *tag_str;
258        struct tm date;
259
260        assert(mysql_num_fields(res) == 5);
261
262        id     = strtol(row[0], NULL, 10);
263        node_a = strtol(row[1], NULL, 10);
264        node_b = strtol(row[2], NULL, 10);
265        parseDate(&date, row[3]);
266        tag_str = row[4];
267
268        read_tags(tag_str, &tags);
269        osm_segment(id, node_a, node_b, &tags, strTime(&date));
270    }
271
272    mysql_free_result(res);
273}
274
275#define TAG_CACHE (1000)
276
277struct tCache {
278    int id;
279    struct keyval tags;
280};
281
282static struct tCache cache[TAG_CACHE+1];
283static MYSQL_STMT *tags_stmt;
284
285void tags_init(MYSQL *mysql)
286{
287    int i;
288    const char *query = "SELECT id, k, v FROM current_way_tags WHERE id >= ? ORDER BY id LIMIT 1000"; // == TAG_CACHE
289    MYSQL_RES *prepare_meta_result;
290    tags_stmt = mysql_stmt_init(mysql);
291    assert(tags_stmt);
292    if (mysql_stmt_prepare(tags_stmt, query, strlen(query))) {
293        fprintf(stderr,"Cannot setup prepared query for current_way_tags: %s\n", mysql_error(mysql));
294        exit(1);
295    }
296    assert(mysql_stmt_param_count(tags_stmt) == 1);
297    prepare_meta_result = mysql_stmt_result_metadata(tags_stmt);
298    assert(prepare_meta_result);
299    assert(mysql_num_fields(prepare_meta_result) == 3);
300    mysql_free_result(prepare_meta_result);
301
302    for (i=0; i< TAG_CACHE; i++)
303        initList(&cache[i].tags);
304}
305
306void tags_exit(void)
307{
308    mysql_stmt_close(tags_stmt);
309    tags_stmt = NULL;
310}
311
312void refill_tags(MYSQL *mysql, const int id)
313{
314    unsigned long length[3];
315    my_bool       is_null[3];
316    my_bool       error[3];
317    MYSQL_BIND tags_bind_param[1];
318    MYSQL_BIND tags_bind_res[3];
319    char key[256], value[256];
320    int i, row_id, last_id, cache_slot;
321
322    for (i=0; i<TAG_CACHE; i++) {
323        if (!cache[i].id)
324            break;
325        resetList(&cache[i].tags);
326        cache[i].id = 0;
327    }
328
329    memset(tags_bind_param, 0, sizeof(tags_bind_param));
330    tags_bind_param[0].buffer_type= MYSQL_TYPE_LONG;
331    tags_bind_param[0].buffer= (char *)&id;
332    tags_bind_param[0].is_null= 0;
333    tags_bind_param[0].length= 0;
334
335    if (mysql_stmt_bind_param(tags_stmt, tags_bind_param)) {
336        fprintf(stderr, " mysql_stmt_bind_param() failed\n");
337        fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
338        exit(0);
339    }
340
341    if (mysql_stmt_execute(tags_stmt))
342    {
343        fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");
344        fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
345        exit(0);
346    }
347
348    memset(tags_bind_res, 0, sizeof(tags_bind_res));
349
350    tags_bind_res[0].buffer_type= MYSQL_TYPE_LONG;
351    tags_bind_res[0].buffer= (char *)&row_id;
352    tags_bind_res[0].is_null= &is_null[0];
353    tags_bind_res[0].length= &length[0];
354    tags_bind_res[0].error= &error[0];
355
356    tags_bind_res[1].buffer_type= MYSQL_TYPE_VAR_STRING;
357    tags_bind_res[1].buffer_length= sizeof(key);
358    tags_bind_res[1].buffer= key;
359    tags_bind_res[1].is_null= &is_null[0];
360    tags_bind_res[1].length= &length[0];
361    tags_bind_res[1].error= &error[0];
362
363    tags_bind_res[2].buffer_type= MYSQL_TYPE_VAR_STRING;
364    tags_bind_res[2].buffer_length= sizeof(value);
365    tags_bind_res[2].buffer= value;
366    tags_bind_res[2].is_null= &is_null[1];
367    tags_bind_res[2].length= &length[1];
368    tags_bind_res[2].error= &error[1];
369
370
371    if (mysql_stmt_bind_result(tags_stmt, tags_bind_res))
372    {
373        fprintf(stderr, " mysql_stmt_bind_result() failed\n");
374        fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
375        exit(0);
376    }
377
378    if (mysql_stmt_store_result(tags_stmt))
379    {
380        fprintf(stderr, " mysql_stmt_store_result() failed\n");
381        fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
382        exit(0);
383    }
384
385    cache_slot = 0;
386    last_id = 0;
387    while (!mysql_stmt_fetch(tags_stmt)) {
388        if (last_id != row_id) {
389            if (last_id)
390               cache_slot++;
391            cache[cache_slot].id = row_id;
392            last_id = row_id;
393        }
394        addItem(&cache[cache_slot].tags, key, value, 0);
395    }
396    // We need to clean out final slot since it may be truncated, unless
397    // we only got a single slot filled then we hit the end of the table
398    // which we assume _is_ complete
399    if (cache_slot) {
400        resetList(&cache[cache_slot].tags);
401        cache[cache_slot].id = 0;
402    } else {
403        // This algorithm can not cope with > TAG_CACHE on a single way
404        assert(countList(&cache[cache_slot].tags) != TAG_CACHE);
405    }
406}
407
408static int cache_off;
409
410struct keyval *get_way_tags(MYSQL *mysql, const int id)
411{
412    if (!cache[cache_off].id) {
413        refill_tags(mysql, id);
414        cache_off = 0;
415    }
416
417    if (cache[cache_off].id == id)
418        return &cache[cache_off++].tags;
419
420    assert(cache[cache_off].id > id);
421    return NULL;
422}
423
424void ways(MYSQL *ways_mysql, MYSQL *segs_mysql, MYSQL *tags_mysql)
425{
426    char ways_query[255], segs_query[255];
427    MYSQL_RES *ways_res, *segs_res;
428    MYSQL_ROW ways_row, segs_row;
429    struct keyval *tags, segs;
430
431    initList(&segs);
432
433    snprintf(ways_query, sizeof(ways_query),
434             "select id, timestamp from current_ways where visible = 1 order by id");
435    snprintf(segs_query, sizeof(segs_query),
436             "select id, segment_id from current_way_segments ORDER BY id, sequence_id");
437
438    if ((mysql_query(ways_mysql, ways_query)) || !(ways_res= mysql_use_result(ways_mysql)))
439    {
440        fprintf(stderr,"Cannot query current_ways: %s\n", mysql_error(ways_mysql));
441        exit(1);
442    }
443    if ((mysql_query(segs_mysql, segs_query)) || !(segs_res= mysql_use_result(segs_mysql)))
444    {
445        fprintf(stderr,"Cannot query current_way_segments: %s\n", mysql_error(segs_mysql));
446        exit(1);
447    }
448
449    tags_init(tags_mysql);
450
451    ways_row = mysql_fetch_row(ways_res);
452    segs_row = mysql_fetch_row(segs_res);
453
454    while (ways_row) {
455        int way_id     = strtol(ways_row[0], NULL, 10);
456        // Terminating way_seg_id is necessary to ensure final way is generated.
457        int way_seg_id = segs_row ? strtol(segs_row[0], NULL, 10): INT_MAX;
458
459        if (way_id < way_seg_id) {
460            // no more segments in this way
461            struct tm date;
462            parseDate(&date, ways_row[1]);
463            tags = get_way_tags(tags_mysql, way_id);
464            osm_way(way_id, &segs, tags, strTime(&date));
465            // fetch new way
466            ways_row= mysql_fetch_row(ways_res);
467            assert(mysql_num_fields(ways_res) == 2);
468        } else if (way_id > way_seg_id) {
469            // we have entries in current_way_segs for a missing way, discard!
470            // fetch next way_seg
471            segs_row = mysql_fetch_row(segs_res);
472            assert(mysql_num_fields(segs_res) == 2);
473        } else {
474            // in step, add current segment and fetch the next one
475            addItem(&segs, "", segs_row[1], 0);
476            segs_row = mysql_fetch_row(segs_res);
477            assert(mysql_num_fields(segs_res) == 2);
478        }
479    }
480
481    mysql_free_result(ways_res);
482    mysql_free_result(segs_res);
483    tags_exit();
484}
485
486int main(int argc, char **argv)
487{
488    // 3 MySQL connections are required to fetch way data from multiple tables
489#define NUM_CONN (3)
490    MYSQL mysql[NUM_CONN];
491    int i;
492    const char *set_timeout = "SET SESSION net_write_timeout=600";
493
494    // Database timestamps use UK localtime
495    setenv("TZ", ":GB", 1);
496
497    for (i=0; i<NUM_CONN; i++) {
498        mysql_init(&mysql[i]);
499
500        if (mysql_options(&mysql[i], MYSQL_SET_CHARSET_NAME , "utf8")) {
501            fprintf(stderr, "set options failed\n");
502            exit(1);
503        }
504
505        if (!(mysql_real_connect(&mysql[i],"","openstreetmap","openstreetmap","openstreetmap",MYSQL_PORT,NULL,0)))
506        {
507            fprintf(stderr,"%s: %s\n",argv[0],mysql_error(&mysql[i]));
508            exit(1);
509        }
510
511        if (mysql_query(mysql, set_timeout)) {
512            fprintf(stderr,"FAILED %s: %s\n", set_timeout, mysql_error(mysql));
513            exit(1);
514        }
515    }
516    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
517    printf("<osm version=\"0.3\" generator=\"OpenStreetMap planet.c\">\n");
518    printf("  <bound box=\"-90,-180,90,180\" origin=\"http://www.openstreetmap.org/api/0.4\" />\n");
519
520    nodes(&mysql[0]);
521    segments(&mysql[0]);
522    ways(&mysql[0], &mysql[1], &mysql[2]);
523
524    printf("</osm>\n");
525
526    for (i=0; i<NUM_CONN; i++)
527        mysql_close(&mysql[i]);
528
529    return 0;
530}
Note: See TracBrowser for help on using the repository browser.