803ccdeaa1fd5d3bfe020916205d9b79ac095bab
[planetdump.git] / planet.c
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
32 static char escape_tmp[1024];
33
34 const 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
62 static 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
75 static 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
86 static 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
97 static 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
115 void 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
162 void 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
183 const 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
195 void 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
238 void 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
277 struct tCache {
278     int id;
279     struct keyval tags;
280 };
281
282 static struct tCache cache[TAG_CACHE+1];
283 static MYSQL_STMT *tags_stmt;
284
285 void 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
306 void tags_exit(void)
307 {
308     mysql_stmt_close(tags_stmt);
309     tags_stmt = NULL;
310 }
311
312 void 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
408 static int cache_off;
409
410 struct 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
424 void 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
486 int 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 }