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