Contents of /trunk/src/osm.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 28 - (show annotations)
Wed Dec 24 14:17:20 2008 UTC (15 years, 4 months ago) by achadwick
File MIME type: text/plain
File size: 55248 byte(s)
The "look at me still talking when there's mapping to do" update.

UI: Make display more responsive during long busy periods. Use Hildon banners
    if available, or temporary statusbar messages for non-Hildon. Some message
    wording changes.

Bugfix: Prevent some segfaults when tapping screen during a redraw by putting
    a grabby banner in the way.

Etc: Update credits.
1 /*
2 * Copyright (C) 2008 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of OSM2Go.
5 *
6 * OSM2Go is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OSM2Go is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #define OSM_STREAM_PARSER
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26
27 #define __USE_XOPEN
28 #include <time.h>
29
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32
33 #include "appdata.h"
34 #include "banner.h"
35
36 #ifndef LIBXML_TREE_ENABLED
37 #error "Tree not enabled in libxml"
38 #endif
39
40 /* determine where a node/way/relation read from the osm file */
41 /* is inserted into the internal database */
42 // #define OSM_SORT_ID
43 #define OSM_SORT_LAST
44 // #define OSM_SORT_FIRST
45
46 /* ------------------------- bounds handling --------------------- */
47
48 static void osm_bounds_free(bounds_t *bounds) {
49 free(bounds);
50 }
51
52 static void osm_bounds_dump(bounds_t *bounds) {
53 printf("\nBounds: %f->%f %f->%f\n",
54 bounds->ll_min.lat, bounds->ll_max.lat,
55 bounds->ll_min.lon, bounds->ll_max.lon);
56 }
57
58 #ifndef OSM_STREAM_PARSER
59 static bounds_t *osm_parse_osm_bounds(osm_t *osm,
60 xmlDocPtr doc, xmlNode *a_node) {
61 char *prop;
62
63 if(osm->bounds) {
64 errorf(NULL, "Doubly defined bounds");
65 return NULL;
66 }
67
68 bounds_t *bounds = g_new0(bounds_t, 1);
69
70 bounds->ll_min.lat = bounds->ll_min.lon = NAN;
71 bounds->ll_max.lat = bounds->ll_max.lon = NAN;
72
73 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"minlat"))) {
74 bounds->ll_min.lat = g_ascii_strtod(prop, NULL);
75 xmlFree(prop);
76 }
77
78 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"maxlat"))) {
79 bounds->ll_max.lat = g_ascii_strtod(prop, NULL);
80 xmlFree(prop);
81 }
82
83 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"minlon"))) {
84 bounds->ll_min.lon = g_ascii_strtod(prop, NULL);
85 xmlFree(prop);
86 }
87
88 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"maxlon"))) {
89 bounds->ll_max.lon = g_ascii_strtod(prop, NULL);
90 xmlFree(prop);
91 }
92
93 if(isnan(bounds->ll_min.lat) || isnan(bounds->ll_min.lon) ||
94 isnan(bounds->ll_max.lat) || isnan(bounds->ll_max.lon)) {
95 errorf(NULL, "Invalid coordinate in bounds (%f/%f/%f/%f)",
96 bounds->ll_min.lat, bounds->ll_min.lon,
97 bounds->ll_max.lat, bounds->ll_max.lon);
98
99 osm_bounds_free(bounds);
100 return NULL;
101 }
102
103
104 /* calculate map zone which will be used as a reference for all */
105 /* drawing/projection later on */
106 pos_t center = { (bounds->ll_max.lat + bounds->ll_min.lat)/2,
107 (bounds->ll_max.lon + bounds->ll_min.lon)/2 };
108
109 pos2lpos_center(&center, &bounds->center);
110
111 /* the scale is needed to accomodate for "streching" */
112 /* by the mercartor projection */
113 bounds->scale = cos(DEG2RAD(center.lat));
114
115 pos2lpos_center(&bounds->ll_min, &bounds->min);
116 bounds->min.x -= bounds->center.x;
117 bounds->min.y -= bounds->center.y;
118 bounds->min.x *= bounds->scale;
119 bounds->min.y *= bounds->scale;
120
121 pos2lpos_center(&bounds->ll_max, &bounds->max);
122 bounds->max.x -= bounds->center.x;
123 bounds->max.y -= bounds->center.y;
124 bounds->max.x *= bounds->scale;
125 bounds->max.y *= bounds->scale;
126
127 return bounds;
128 }
129 #endif
130
131 /* ------------------------- user handling --------------------- */
132
133 void osm_users_free(user_t *user) {
134 while(user) {
135 user_t *next = user->next;
136
137 if(user->name) g_free(user->name);
138 g_free(user);
139
140 user = next;
141 }
142 }
143
144 void osm_users_dump(user_t *user) {
145 printf("\nUser list:\n");
146 while(user) {
147 printf("Name: %s\n", user->name);
148 user = user->next;
149 }
150 }
151
152 static user_t *osm_user(osm_t *osm, char *name) {
153
154 /* search through user list */
155 user_t **user = &osm->user;
156 while(*user && strcasecmp((*user)->name, name) < 0)
157 user = &(*user)->next;
158
159 /* end of list or inexact match? create new user entry! */
160 if(!*user || strcasecmp((*user)->name, name)) {
161 user_t *new = g_new0(user_t, 1);
162 new->name = g_strdup(name);
163 new->next = *user;
164 *user = new;
165
166 return new;
167 }
168
169 return *user;
170 }
171
172 static
173 time_t convert_iso8601(const char *str) {
174 tzset();
175
176 struct tm ctime;
177 memset(&ctime, 0, sizeof(struct tm));
178 strptime(str, "%FT%T%z", &ctime);
179
180 return mktime(&ctime) - timezone;
181 }
182
183 /* -------------------- tag handling ----------------------- */
184
185 void osm_tag_free(tag_t *tag) {
186 if(tag->key) g_free(tag->key);
187 if(tag->value) g_free(tag->value);
188 g_free(tag);
189 }
190
191 void osm_tags_free(tag_t *tag) {
192 while(tag) {
193 tag_t *next = tag->next;
194 osm_tag_free(tag);
195 tag = next;
196 }
197 }
198
199 static void osm_tags_dump(tag_t *tag) {
200 while(tag) {
201 printf("Key/Val: %s/%s\n", tag->key, tag->value);
202 tag = tag->next;
203 }
204 }
205
206 tag_t *osm_parse_osm_tag(osm_t *osm, xmlDocPtr doc, xmlNode *a_node) {
207 xmlNode *cur_node = NULL;
208
209 /* allocate a new tag structure */
210 tag_t *tag = g_new0(tag_t, 1);
211
212 char *prop;
213 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"k"))) {
214 if(strlen(prop) > 0) tag->key = g_strdup(prop);
215 xmlFree(prop);
216 }
217
218 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"v"))) {
219 if(strlen(prop) > 0) tag->value = g_strdup(prop);
220 xmlFree(prop);
221 }
222
223 if(!tag->key || !tag->value) {
224 printf("incomplete tag key/value %s/%s\n", tag->key, tag->value);
225 osm_tags_free(tag);
226 return NULL;
227 }
228
229 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next)
230 if (cur_node->type == XML_ELEMENT_NODE)
231 printf("found unhandled osm/node/tag/%s\n", cur_node->name);
232
233 return tag;
234 }
235
236 gboolean osm_is_creator_tag(tag_t *tag) {
237 if(strcasecmp(tag->key, "created_by") == 0) return TRUE;
238 if(strcasecmp(tag->key, "source") == 0) return TRUE;
239
240 return FALSE;
241 }
242
243 gboolean osm_tag_key_and_value_present(tag_t *haystack, tag_t *tag) {
244 while(haystack) {
245 if((strcasecmp(haystack->key, tag->key) == 0) &&
246 (strcasecmp(haystack->value, tag->value) == 0))
247 return TRUE;
248
249 haystack = haystack->next;
250 }
251 return FALSE;
252 }
253
254 gboolean osm_tag_key_other_value_present(tag_t *haystack, tag_t *tag) {
255 while(haystack) {
256 if((strcasecmp(haystack->key, tag->key) == 0) &&
257 (strcasecmp(haystack->value, tag->value) != 0))
258 return TRUE;
259
260 haystack = haystack->next;
261 }
262 return FALSE;
263 }
264
265 gboolean osm_way_ends_with_node(way_t *way, node_t *node) {
266 /* and deleted way may even not contain any nodes at all */
267 /* so ignore it */
268 if(way->flags & OSM_FLAG_DELETED)
269 return FALSE;
270
271 /* any valid way must have at least two nodes */
272 g_assert(way->node_chain && way->node_chain->next);
273
274 node_chain_t *chain = way->node_chain;
275 if(chain->node == node) return TRUE;
276
277 while(chain->next) chain = chain->next;
278 if(chain->node == node) return TRUE;
279
280 return FALSE;
281 }
282
283 /* ------------------- node handling ------------------- */
284
285 void osm_node_free(icon_t **icon, node_t *node) {
286 if(node->icon_buf)
287 icon_free(icon, node->icon_buf);
288
289 /* there must not be anything left in this chain */
290 g_assert(!node->map_item_chain);
291
292 osm_tags_free(node->tag);
293 g_free(node);
294 }
295
296 static void osm_nodes_free(icon_t **icon, node_t *node) {
297 while(node) {
298 node_t *next = node->next;
299 osm_node_free(icon, node);
300 node = next;
301 }
302 }
303
304 void osm_node_dump(node_t *node) {
305 char buf[64];
306 struct tm tm;
307
308 printf("Id: %lu\n", node->id);
309 printf("User: %s\n", node->user?node->user->name:"<unspecified>");
310 printf("Visible: %s\n", node->visible?"yes":"no");
311
312 localtime_r(&node->time, &tm);
313 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", &tm);
314 printf("Time: %s\n", buf);
315 osm_tags_dump(node->tag);
316 }
317
318 void osm_nodes_dump(node_t *node) {
319 printf("\nNode list:\n");
320 while(node) {
321 osm_node_dump(node);
322 printf("\n");
323 node = node->next;
324 }
325 }
326
327 #ifndef OSM_STREAM_PARSER
328 static node_t *osm_parse_osm_node(osm_t *osm,
329 xmlDocPtr doc, xmlNode *a_node) {
330 xmlNode *cur_node = NULL;
331
332 /* allocate a new node structure */
333 node_t *node = g_new0(node_t, 1);
334 node->pos.lat = node->pos.lon = NAN;
335
336 char *prop;
337 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"id"))) {
338 node->id = strtoul(prop, NULL, 10);
339 xmlFree(prop);
340 }
341
342 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"lat"))) {
343 node->pos.lat = g_ascii_strtod(prop, NULL);
344 xmlFree(prop);
345 }
346
347 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"lon"))) {
348 node->pos.lon = g_ascii_strtod(prop, NULL);
349 xmlFree(prop);
350 }
351
352 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"user"))) {
353 node->user = osm_user(osm, prop);
354 xmlFree(prop);
355 }
356
357 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"visible"))) {
358 node->visible = (strcasecmp(prop, "true") == 0);
359 xmlFree(prop);
360 }
361
362 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"timestamp"))) {
363 node->time = convert_iso8601(prop);
364 xmlFree(prop);
365 }
366
367 /* scan for tags and attach a list of tags */
368 tag_t **tag = &node->tag;
369 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
370 if (cur_node->type == XML_ELEMENT_NODE) {
371 if(strcasecmp((char*)cur_node->name, "tag") == 0) {
372 /* attach tag to node */
373 *tag = osm_parse_osm_tag(osm, doc, cur_node);
374 if(*tag) tag = &((*tag)->next);
375 } else
376 printf("found unhandled osm/node/%s\n", cur_node->name);
377 }
378 }
379
380 pos2lpos(osm->bounds, &node->pos, &node->lpos);
381
382 return node;
383 }
384 #endif
385
386 /* ------------------- way handling ------------------- */
387
388 void osm_node_chain_free(node_chain_t *node_chain) {
389 while(node_chain) {
390 g_assert(node_chain->node->ways);
391
392 node_chain_t *next = node_chain->next;
393 node_chain->node->ways--;
394 g_free(node_chain);
395 node_chain = next;
396 }
397 }
398
399 void osm_way_free(way_t *way) {
400 // printf("freeing way #%ld\n", way->id);
401
402 osm_node_chain_free(way->node_chain);
403 osm_tags_free(way->tag);
404
405 /* there must not be anything left in this chain */
406 g_assert(!way->map_item_chain);
407
408 g_free(way);
409 }
410
411 static void osm_ways_free(way_t *way) {
412 while(way) {
413 way_t *next = way->next;
414 osm_way_free(way);
415 way = next;
416 }
417 }
418
419 void osm_way_append_node(way_t *way, node_t *node) {
420 node_chain_t **node_chain = &way->node_chain;
421
422 while(*node_chain)
423 node_chain = &((*node_chain)->next);
424
425 *node_chain = g_new0(node_chain_t, 1);
426 (*node_chain)->node = node;
427
428 node->ways++;
429 }
430
431 int osm_node_chain_length(node_chain_t *node_chain) {
432 int cnt = 0;
433 while(node_chain) {
434 cnt++;
435 node_chain = node_chain->next;
436 }
437
438 return cnt;
439 }
440
441 void osm_way_dump(way_t *way) {
442 char buf[64];
443 struct tm tm;
444
445 printf("Id: %lu\n", way->id);
446 printf("User: %s\n", way->user?way->user->name:"<unspecified>");
447 printf("Visible: %s\n", way->visible?"yes":"no");
448 node_chain_t *node_chain = way->node_chain;
449 while(node_chain) {
450 printf(" Node: %lu\n", node_chain->node->id);
451 node_chain = node_chain->next;
452 }
453
454 localtime_r(&way->time, &tm);
455 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", &tm);
456 printf("Time: %s\n", buf);
457 osm_tags_dump(way->tag);
458 }
459
460 void osm_ways_dump(way_t *way) {
461 printf("\nWay list:\n");
462 while(way) {
463 osm_way_dump(way);
464 printf("\n");
465 way = way->next;
466 }
467 }
468
469 node_chain_t *osm_parse_osm_way_nd(osm_t *osm,
470 xmlDocPtr doc, xmlNode *a_node) {
471 char *prop;
472
473 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"ref"))) {
474 item_id_t id = strtoul(prop, NULL, 10);
475 node_chain_t *node_chain = g_new0(node_chain_t, 1);
476
477 /* search matching node */
478 node_chain->node = osm->node;
479 while(node_chain->node && node_chain->node->id != id)
480 node_chain->node = node_chain->node->next;
481
482 if(!node_chain->node) printf("Node id %lu not found\n", id);
483
484 if(node_chain->node)
485 node_chain->node->ways++;
486
487 xmlFree(prop);
488
489 return node_chain;
490 }
491
492 return NULL;
493 }
494
495 #ifndef OSM_STREAM_PARSER
496 static way_t *osm_parse_osm_way(osm_t *osm,
497 xmlDocPtr doc, xmlNode *a_node) {
498 xmlNode *cur_node = NULL;
499
500 /* allocate a new way structure */
501 way_t *way = g_new0(way_t, 1);
502
503 char *prop;
504 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"id"))) {
505 way->id = strtoul(prop, NULL, 10);
506 xmlFree(prop);
507 }
508
509 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"user"))) {
510 way->user = osm_user(osm, prop);
511 xmlFree(prop);
512 }
513
514 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"visible"))) {
515 way->visible = (strcasecmp(prop, "true") == 0);
516 xmlFree(prop);
517 }
518
519 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"timestamp"))) {
520 way->time = convert_iso8601(prop);
521 xmlFree(prop);
522 }
523
524 /* scan for tags/nodes and attach their lists */
525 tag_t **tag = &way->tag;
526 node_chain_t **node_chain = &way->node_chain;
527
528 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
529 if (cur_node->type == XML_ELEMENT_NODE) {
530 if(strcasecmp((char*)cur_node->name, "tag") == 0) {
531 /* attach tag to node */
532 *tag = osm_parse_osm_tag(osm, doc, cur_node);
533 if(*tag) tag = &((*tag)->next);
534 } else if(strcasecmp((char*)cur_node->name, "nd") == 0) {
535 *node_chain = osm_parse_osm_way_nd(osm, doc, cur_node);
536 if(*node_chain)
537 node_chain = &((*node_chain)->next);
538 } else
539 printf("found unhandled osm/node/%s\n", cur_node->name);
540 }
541 }
542
543 return way;
544 }
545 #endif
546
547 /* ------------------- relation handling ------------------- */
548
549 void osm_member_free(member_t *member) {
550 if(member->role) g_free(member->role);
551 g_free(member);
552 }
553
554 void osm_members_free(member_t *member) {
555 while(member) {
556 member_t *next = member->next;
557 osm_member_free(member);
558 member = next;
559 }
560 }
561
562 static void osm_relations_free(relation_t *relation) {
563 while(relation) {
564 relation_t *next = relation->next;
565
566 osm_tags_free(relation->tag);
567 osm_members_free(relation->member);
568
569 g_free(relation);
570 relation = next;
571 }
572 }
573
574 void osm_relations_dump(relation_t *relation) {
575 printf("\nRelation list:\n");
576 while(relation) {
577 char buf[64];
578 struct tm tm;
579
580 printf("Id: %lu\n", relation->id);
581 printf("User: %s\n",
582 relation->user?relation->user->name:"<unspecified>");
583 printf("Visible: %s\n", relation->visible?"yes":"no");
584
585 member_t *member = relation->member;
586 while(member) {
587 switch(member->type) {
588 case ILLEGAL:
589 case NODE_ID:
590 case WAY_ID:
591 case RELATION_ID:
592 break;
593
594 case NODE:
595 if(member->node)
596 printf(" Member: Node, id = %lu, role = %s\n",
597 member->node->id, member->role);
598 break;
599
600 case WAY:
601 if(member->way)
602 printf(" Member: Way, id = %lu, role = %s\n",
603 member->way->id, member->role);
604 break;
605
606 case RELATION:
607 if(member->relation)
608 printf(" Member: Relation, id = %lu, role = %s\n",
609 member->relation->id, member->role);
610 break;
611 }
612
613 member = member->next;
614 }
615
616 localtime_r(&relation->time, &tm);
617 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", &tm);
618 printf("Time: %s\n", buf);
619 osm_tags_dump(relation->tag);
620
621 printf("\n");
622 relation = relation->next;
623 }
624 }
625
626 member_t *osm_parse_osm_relation_member(osm_t *osm,
627 xmlDocPtr doc, xmlNode *a_node) {
628 char *prop;
629 member_t *member = g_new0(member_t, 1);
630 member->type = ILLEGAL;
631
632 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"type"))) {
633 if(strcasecmp(prop, "way") == 0) member->type = WAY;
634 else if(strcasecmp(prop, "node") == 0) member->type = NODE;
635 else if(strcasecmp(prop, "relation") == 0) member->type = RELATION;
636 xmlFree(prop);
637 }
638
639 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"ref"))) {
640 item_id_t id = strtoul(prop, NULL, 10);
641
642 switch(member->type) {
643 case ILLEGAL:
644 printf("Unable to store illegal type\n");
645 break;
646
647 case WAY:
648 /* search matching way */
649 member->way = osm->way;
650 while(member->way && member->way->id != id)
651 member->way = member->way->next;
652
653 if(!member->way) {
654 member->type = WAY_ID;
655 member->id = id;
656 }
657 break;
658
659 case NODE:
660 /* search matching node */
661 member->node = osm->node;
662 while(member->node && member->node->id != id)
663 member->node = member->node->next;
664
665 if(!member->node) {
666 member->type = NODE_ID;
667 member->id = id;
668 }
669 break;
670
671 case RELATION:
672 /* search matching relation */
673 member->relation = osm->relation;
674 while(member->relation && member->relation->id != id)
675 member->relation = member->relation->next;
676
677 if(!member->relation) {
678 member->type = NODE_ID;
679 member->id = id;
680 }
681 break;
682
683 case WAY_ID:
684 case NODE_ID:
685 case RELATION_ID:
686 break;
687 }
688
689 xmlFree(prop);
690 }
691
692 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"role"))) {
693 if(strlen(prop) > 0) member->role = g_strdup(prop);
694 xmlFree(prop);
695 }
696
697 return member;
698 }
699
700 #ifndef OSM_STREAM_PARSER
701 static relation_t *osm_parse_osm_relation(osm_t *osm,
702 xmlDocPtr doc, xmlNode *a_node) {
703 xmlNode *cur_node = NULL;
704
705 /* allocate a new relation structure */
706 relation_t *relation = g_new0(relation_t, 1);
707
708 char *prop;
709 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"id"))) {
710 relation->id = strtoul(prop, NULL, 10);
711 xmlFree(prop);
712 }
713
714 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"user"))) {
715 relation->user = osm_user(osm, prop);
716 xmlFree(prop);
717 }
718
719 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"visible"))) {
720 relation->visible = (strcasecmp(prop, "true") == 0);
721 xmlFree(prop);
722 }
723
724 if((prop = (char*)xmlGetProp(a_node, (unsigned char*)"timestamp"))) {
725 relation->time = convert_iso8601(prop);
726 xmlFree(prop);
727 }
728
729 /* scan for tags and attach a list of tags */
730 tag_t **tag = &relation->tag;
731 member_t **member = &relation->member;
732
733 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
734 if (cur_node->type == XML_ELEMENT_NODE) {
735 if(strcasecmp((char*)cur_node->name, "tag") == 0) {
736 /* attach tag to node */
737 *tag = osm_parse_osm_tag(osm, doc, cur_node);
738 if(*tag) tag = &((*tag)->next);
739 } else if(strcasecmp((char*)cur_node->name, "member") == 0) {
740 *member = osm_parse_osm_relation_member(osm, doc, cur_node);
741 if(*member) member = &((*member)->next);
742 } else
743 printf("found unhandled osm/node/%s\n", cur_node->name);
744 }
745 }
746
747 return relation;
748 }
749
750 /* ----------------------- generic xml handling -------------------------- */
751
752 /* parse loc entry */
753 static void osm_parse_osm(osm_t *osm, xmlDocPtr doc, xmlNode * a_node) {
754 xmlNode *cur_node = NULL;
755
756 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
757 if (cur_node->type == XML_ELEMENT_NODE) {
758 if(strcasecmp((char*)cur_node->name, "bounds") == 0)
759 osm->bounds = osm_parse_osm_bounds(osm, doc, cur_node);
760 else if(strcasecmp((char*)cur_node->name, "node") == 0) {
761 /* parse node and attach it to chain */
762 node_t *new = osm_parse_osm_node(osm, doc, cur_node);
763 if(new) {
764 node_t **node = &osm->node;
765
766 #ifdef OSM_SORT_ID
767 /* search chain of nodes */
768 while(*node && ((*node)->id < new->id))
769 node = &(*node)->next;
770 #endif
771
772 #ifdef OSM_SORT_LAST
773 while(*node) node = &(*node)->next;
774 #endif
775
776 /* insert into chain */
777 new->next = *node;
778 *node = new;
779 }
780 } else if(strcasecmp((char*)cur_node->name, "way") == 0) {
781 /* parse way and attach it to chain */
782 way_t *new = osm_parse_osm_way(osm, doc, cur_node);
783 if(new) {
784 way_t **way = &osm->way;
785
786 #ifdef OSM_SORT_ID
787 /* insert into chain */
788 while(*way && ((*way)->id < new->id))
789 way = &(*way)->next;
790 #endif
791
792 #ifdef OSM_SORT_LAST
793 while(*way) way = &(*way)->next;
794 #endif
795
796 /* insert into chain */
797 new->next = *way;
798 *way = new;
799 }
800 } else if(strcasecmp((char*)cur_node->name, "relation") == 0) {
801 /* parse relation and attach it to chain */
802 relation_t *new = osm_parse_osm_relation(osm, doc, cur_node);
803 if(new) {
804 relation_t **relation = &osm->relation;
805
806 #ifdef OSM_SORT_ID
807 /* search chain of ways */
808 while(*relation && ((*relation)->id < new->id))
809 relation = &(*relation)->next;
810 #endif
811
812 #ifdef OSM_SORT_LAST
813 while(*relation) relation = &(*relation)->next;
814 #endif
815
816 /* insert into chain */
817 new->next = *relation;
818 *relation = new;
819 }
820 } else
821 printf("found unhandled osm/%s\n", cur_node->name);
822
823 }
824 }
825 }
826
827 /* parse root element and search for "osm" */
828 static osm_t *osm_parse_root(xmlDocPtr doc, xmlNode * a_node) {
829 osm_t *osm;
830 xmlNode *cur_node = NULL;
831
832 /* allocate memory to hold osm file description */
833 osm = g_new0(osm_t, 1);
834
835 for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
836 if (cur_node->type == XML_ELEMENT_NODE) {
837 /* parse osm osm file ... */
838 if(strcasecmp((char*)cur_node->name, "osm") == 0)
839 osm_parse_osm(osm, doc, cur_node);
840 else
841 printf("found unhandled %s\n", cur_node->name);
842 }
843 }
844
845 return osm;
846 }
847
848 static osm_t *osm_parse_doc(xmlDocPtr doc) {
849 osm_t *osm;
850
851 /* Get the root element node */
852 xmlNode *root_element = xmlDocGetRootElement(doc);
853
854 osm = osm_parse_root(doc, root_element);
855
856 /*free the document */
857 xmlFreeDoc(doc);
858
859 /*
860 * Free the global variables that may
861 * have been allocated by the parser.
862 */
863 xmlCleanupParser();
864
865 return osm;
866 }
867 #endif
868
869 /* ------------------ osm handling ----------------- */
870
871 void osm_free(icon_t **icon, osm_t *osm) {
872 if(!osm) return;
873
874 if(osm->bounds) osm_bounds_free(osm->bounds);
875 if(osm->user) osm_users_free(osm->user);
876 if(osm->way) osm_ways_free(osm->way);
877 if(osm->node) osm_nodes_free(icon, osm->node);
878 if(osm->relation) osm_relations_free(osm->relation);
879 g_free(osm);
880 }
881
882 void osm_dump(osm_t *osm) {
883 osm_bounds_dump(osm->bounds);
884 osm_users_dump(osm->user);
885 osm_nodes_dump(osm->node);
886 osm_ways_dump(osm->way);
887 osm_relations_dump(osm->relation);
888 }
889
890 #ifdef OSM_STREAM_PARSER
891 /* -------------------------- stream parser tests ------------------- */
892
893 #include <libxml/xmlreader.h>
894
895 static gint my_strcmp(const xmlChar *a, const xmlChar *b) {
896 if(!a && !b) return 0;
897 if(!a) return -1;
898 if(!b) return +1;
899 return strcmp((char*)a,(char*)b);
900 }
901
902 /* skip current element incl. everything below (mainly for testing) */
903 /* returns FALSE if something failed */
904 static gboolean skip_element(xmlTextReaderPtr reader) {
905 g_assert(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT);
906 const xmlChar *name = xmlTextReaderConstName(reader);
907 g_assert(name);
908 int depth = xmlTextReaderDepth(reader);
909
910 if(xmlTextReaderIsEmptyElement(reader))
911 return TRUE;
912
913 int ret = xmlTextReaderRead(reader);
914 while((ret == 1) &&
915 ((xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT) ||
916 (xmlTextReaderDepth(reader) > depth) ||
917 (my_strcmp(xmlTextReaderConstName(reader), name) != 0))) {
918 ret = xmlTextReaderRead(reader);
919 }
920 return(ret == 1);
921 }
922
923 /* parse bounds */
924 static bounds_t *process_bounds(xmlTextReaderPtr reader) {
925 char *prop = NULL;
926 bounds_t *bounds = g_new0(bounds_t, 1);
927
928 bounds->ll_min.lat = bounds->ll_min.lon = NAN;
929 bounds->ll_max.lat = bounds->ll_max.lon = NAN;
930
931 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "minlat"))) {
932 bounds->ll_min.lat = g_ascii_strtod(prop, NULL);
933 xmlFree(prop);
934 }
935
936 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "maxlat"))) {
937 bounds->ll_max.lat = g_ascii_strtod(prop, NULL);
938 xmlFree(prop);
939 }
940
941 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "minlon"))) {
942 bounds->ll_min.lon = g_ascii_strtod(prop, NULL);
943 xmlFree(prop);
944 }
945
946 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "maxlon"))) {
947 bounds->ll_max.lon = g_ascii_strtod(prop, NULL);
948 xmlFree(prop);
949 }
950
951 if(isnan(bounds->ll_min.lat) || isnan(bounds->ll_min.lon) ||
952 isnan(bounds->ll_max.lat) || isnan(bounds->ll_max.lon)) {
953 errorf(NULL, "Invalid coordinate in bounds (%f/%f/%f/%f)",
954 bounds->ll_min.lat, bounds->ll_min.lon,
955 bounds->ll_max.lat, bounds->ll_max.lon);
956
957 osm_bounds_free(bounds);
958 return NULL;
959 }
960
961 /* skip everything below */
962 skip_element(reader);
963
964 /* calculate map zone which will be used as a reference for all */
965 /* drawing/projection later on */
966 pos_t center = { (bounds->ll_max.lat + bounds->ll_min.lat)/2,
967 (bounds->ll_max.lon + bounds->ll_min.lon)/2 };
968
969 pos2lpos_center(&center, &bounds->center);
970
971 /* the scale is needed to accomodate for "streching" */
972 /* by the mercartor projection */
973 bounds->scale = cos(DEG2RAD(center.lat));
974
975 pos2lpos_center(&bounds->ll_min, &bounds->min);
976 bounds->min.x -= bounds->center.x;
977 bounds->min.y -= bounds->center.y;
978 bounds->min.x *= bounds->scale;
979 bounds->min.y *= bounds->scale;
980
981 pos2lpos_center(&bounds->ll_max, &bounds->max);
982 bounds->max.x -= bounds->center.x;
983 bounds->max.y -= bounds->center.y;
984 bounds->max.x *= bounds->scale;
985 bounds->max.y *= bounds->scale;
986
987 return bounds;
988 }
989
990 static tag_t *process_tag(xmlTextReaderPtr reader) {
991 /* allocate a new tag structure */
992 tag_t *tag = g_new0(tag_t, 1);
993
994 char *prop;
995 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "k"))) {
996 if(strlen(prop) > 0) tag->key = g_strdup(prop);
997 xmlFree(prop);
998 }
999
1000 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "v"))) {
1001 if(strlen(prop) > 0) tag->value = g_strdup(prop);
1002 xmlFree(prop);
1003 }
1004
1005 if(!tag->key || !tag->value) {
1006 printf("incomplete tag key/value %s/%s\n", tag->key, tag->value);
1007 osm_tags_free(tag);
1008 tag = NULL;
1009 }
1010
1011 skip_element(reader);
1012 return tag;
1013 }
1014
1015 static node_t *process_node(xmlTextReaderPtr reader, osm_t *osm) {
1016
1017 /* allocate a new node structure */
1018 node_t *node = g_new0(node_t, 1);
1019 node->pos.lat = node->pos.lon = NAN;
1020
1021 char *prop;
1022 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "id"))) {
1023 node->id = strtoul(prop, NULL, 10);
1024 xmlFree(prop);
1025 }
1026
1027 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "lat"))) {
1028 node->pos.lat = g_ascii_strtod(prop, NULL);
1029 xmlFree(prop);
1030 }
1031
1032 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "lon"))) {
1033 node->pos.lon = g_ascii_strtod(prop, NULL);
1034 xmlFree(prop);
1035 }
1036
1037 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "user"))) {
1038 node->user = osm_user(osm, prop);
1039 xmlFree(prop);
1040 }
1041
1042 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "visible"))) {
1043 node->visible = (strcasecmp(prop, "true") == 0);
1044 xmlFree(prop);
1045 }
1046
1047 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "timestamp"))) {
1048 node->time = convert_iso8601(prop);
1049 xmlFree(prop);
1050 }
1051
1052 pos2lpos(osm->bounds, &node->pos, &node->lpos);
1053
1054 /* just an empty element? then return the node as it is */
1055 if(xmlTextReaderIsEmptyElement(reader))
1056 return node;
1057
1058 /* parse tags if present */
1059 int depth = xmlTextReaderDepth(reader);
1060
1061 /* scan all elements on same level or its children */
1062 tag_t **tag = &node->tag;
1063 int ret = xmlTextReaderRead(reader);
1064 while((ret == 1) &&
1065 ((xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT) ||
1066 (xmlTextReaderDepth(reader) != depth))) {
1067
1068 if(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
1069 char *subname = (char*)xmlTextReaderConstName(reader);
1070 if(strcasecmp(subname, "tag") == 0) {
1071 *tag = process_tag(reader);
1072 if(*tag) tag = &(*tag)->next;
1073 } else
1074 skip_element(reader);
1075 }
1076
1077 ret = xmlTextReaderRead(reader);
1078 }
1079
1080 return node;
1081 }
1082
1083 static node_chain_t *process_nd(xmlTextReaderPtr reader, osm_t *osm) {
1084 char *prop;
1085
1086 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "ref"))) {
1087 item_id_t id = strtoul(prop, NULL, 10);
1088 node_chain_t *node_chain = g_new0(node_chain_t, 1);
1089
1090 /* search matching node */
1091 node_chain->node = osm->node;
1092 while(node_chain->node && node_chain->node->id != id)
1093 node_chain->node = node_chain->node->next;
1094
1095 if(!node_chain->node) printf("Node id %lu not found\n", id);
1096
1097 if(node_chain->node)
1098 node_chain->node->ways++;
1099
1100 xmlFree(prop);
1101
1102 skip_element(reader);
1103 return node_chain;
1104 }
1105
1106 skip_element(reader);
1107 return NULL;
1108 }
1109
1110 static way_t *process_way(xmlTextReaderPtr reader, osm_t *osm) {
1111 /* allocate a new way structure */
1112 way_t *way = g_new0(way_t, 1);
1113
1114 char *prop;
1115 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "id"))) {
1116 way->id = strtoul(prop, NULL, 10);
1117 xmlFree(prop);
1118 }
1119
1120 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "user"))) {
1121 way->user = osm_user(osm, prop);
1122 xmlFree(prop);
1123 }
1124
1125 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "visible"))) {
1126 way->visible = (strcasecmp(prop, "true") == 0);
1127 xmlFree(prop);
1128 }
1129
1130 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "timestamp"))) {
1131 way->time = convert_iso8601(prop);
1132 xmlFree(prop);
1133 }
1134
1135 /* just an empty element? then return the way as it is */
1136 /* (this should in fact never happen as this would be a way without nodes) */
1137 if(xmlTextReaderIsEmptyElement(reader))
1138 return way;
1139
1140 /* parse tags/nodes if present */
1141 int depth = xmlTextReaderDepth(reader);
1142
1143 /* scan all elements on same level or its children */
1144 tag_t **tag = &way->tag;
1145 node_chain_t **node_chain = &way->node_chain;
1146 int ret = xmlTextReaderRead(reader);
1147 while((ret == 1) &&
1148 ((xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT) ||
1149 (xmlTextReaderDepth(reader) != depth))) {
1150
1151 if(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
1152 char *subname = (char*)xmlTextReaderConstName(reader);
1153 if(strcasecmp(subname, "nd") == 0) {
1154 *node_chain = process_nd(reader, osm);
1155 if(*node_chain) node_chain = &(*node_chain)->next;
1156 } else if(strcasecmp(subname, "tag") == 0) {
1157 *tag = process_tag(reader);
1158 if(*tag) tag = &(*tag)->next;
1159 } else
1160 skip_element(reader);
1161 }
1162 ret = xmlTextReaderRead(reader);
1163 }
1164
1165 return way;
1166 }
1167
1168 static member_t *process_member(xmlTextReaderPtr reader, osm_t *osm) {
1169 char *prop;
1170 member_t *member = g_new0(member_t, 1);
1171 member->type = ILLEGAL;
1172
1173 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "type"))) {
1174 if(strcasecmp(prop, "way") == 0) member->type = WAY;
1175 else if(strcasecmp(prop, "node") == 0) member->type = NODE;
1176 else if(strcasecmp(prop, "relation") == 0) member->type = RELATION;
1177 xmlFree(prop);
1178 }
1179
1180 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "ref"))) {
1181 item_id_t id = strtoul(prop, NULL, 10);
1182
1183 switch(member->type) {
1184 case ILLEGAL:
1185 printf("Unable to store illegal type\n");
1186 break;
1187
1188 case WAY:
1189 /* search matching way */
1190 member->way = osm->way;
1191 while(member->way && member->way->id != id)
1192 member->way = member->way->next;
1193
1194 if(!member->way) {
1195 member->type = WAY_ID;
1196 member->id = id;
1197 }
1198 break;
1199
1200 case NODE:
1201 /* search matching node */
1202 member->node = osm->node;
1203 while(member->node && member->node->id != id)
1204 member->node = member->node->next;
1205
1206 if(!member->node) {
1207 member->type = NODE_ID;
1208 member->id = id;
1209 }
1210 break;
1211
1212 case RELATION:
1213 /* search matching relation */
1214 member->relation = osm->relation;
1215 while(member->relation && member->relation->id != id)
1216 member->relation = member->relation->next;
1217
1218 if(!member->relation) {
1219 member->type = NODE_ID;
1220 member->id = id;
1221 }
1222 break;
1223
1224 case WAY_ID:
1225 case NODE_ID:
1226 case RELATION_ID:
1227 break;
1228 }
1229
1230 xmlFree(prop);
1231 }
1232
1233 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "role"))) {
1234 if(strlen(prop) > 0) member->role = g_strdup(prop);
1235 xmlFree(prop);
1236 }
1237
1238 return member;
1239 }
1240
1241 static relation_t *process_relation(xmlTextReaderPtr reader, osm_t *osm) {
1242 /* allocate a new relation structure */
1243 relation_t *relation = g_new0(relation_t, 1);
1244
1245 char *prop;
1246 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "id"))) {
1247 relation->id = strtoul(prop, NULL, 10);
1248 xmlFree(prop);
1249 }
1250
1251 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "user"))) {
1252 relation->user = osm_user(osm, prop);
1253 xmlFree(prop);
1254 }
1255
1256 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "visible"))) {
1257 relation->visible = (strcasecmp(prop, "true") == 0);
1258 xmlFree(prop);
1259 }
1260
1261 if((prop = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "timestamp"))) {
1262 relation->time = convert_iso8601(prop);
1263 xmlFree(prop);
1264 }
1265
1266 /* just an empty element? then return the relation as it is */
1267 /* (this should in fact never happen as this would be a relation */
1268 /* without members) */
1269 if(xmlTextReaderIsEmptyElement(reader))
1270 return relation;
1271
1272 /* parse tags/member if present */
1273 int depth = xmlTextReaderDepth(reader);
1274
1275 /* scan all elements on same level or its children */
1276 tag_t **tag = &relation->tag;
1277 member_t **member = &relation->member;
1278 int ret = xmlTextReaderRead(reader);
1279 while((ret == 1) &&
1280 ((xmlTextReaderNodeType(reader) != XML_READER_TYPE_END_ELEMENT) ||
1281 (xmlTextReaderDepth(reader) != depth))) {
1282
1283 if(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT) {
1284 char *subname = (char*)xmlTextReaderConstName(reader);
1285 if(strcasecmp(subname, "nd") == 0) {
1286 *member = process_member(reader, osm);
1287 if(*member) member = &(*member)->next;
1288 } else if(strcasecmp(subname, "tag") == 0) {
1289 *tag = process_tag(reader);
1290 if(*tag) tag = &(*tag)->next;
1291 } else
1292 skip_element(reader);
1293 }
1294 ret = xmlTextReaderRead(reader);
1295 }
1296
1297 return relation;
1298 }
1299
1300 static osm_t *process_osm(xmlTextReaderPtr reader) {
1301 /* alloc osm structure */
1302 osm_t *osm = g_new0(osm_t, 1);
1303
1304 node_t **node = &osm->node;
1305 way_t **way = &osm->way;
1306 relation_t **relation = &osm->relation;
1307
1308 /* no attributes of interest */
1309
1310 const xmlChar *name = xmlTextReaderConstName(reader);
1311 g_assert(name);
1312
1313 /* read next node */
1314 int num_elems = 0;
1315 const int tick_every = 50; // Balance responsive appearance with performance.
1316 int ret = xmlTextReaderRead(reader);
1317 while(ret == 1) {
1318
1319 switch(xmlTextReaderNodeType(reader)) {
1320 case XML_READER_TYPE_ELEMENT:
1321
1322 g_assert(xmlTextReaderDepth(reader) == 1);
1323 char *name = (char*)xmlTextReaderConstName(reader);
1324 if(strcasecmp(name, "bounds") == 0) {
1325 osm->bounds = process_bounds(reader);
1326 } else if(strcasecmp(name, "node") == 0) {
1327 *node = process_node(reader, osm);
1328 if(*node) node = &(*node)->next;
1329 } else if(strcasecmp(name, "way") == 0) {
1330 *way = process_way(reader, osm);
1331 if(*way) way = &(*way)->next;
1332 } else if(strcasecmp(name, "relation") == 0) {
1333 *relation = process_relation(reader, osm);
1334 if(*relation) relation = &(*relation)->next;
1335 } else {
1336 printf("something unknown found\n");
1337 g_assert(0);
1338 skip_element(reader);
1339 }
1340 break;
1341
1342 case XML_READER_TYPE_END_ELEMENT:
1343 /* end element must be for the current element */
1344 g_assert(xmlTextReaderDepth(reader) == 0);
1345 return osm;
1346 break;
1347
1348 default:
1349 break;
1350 }
1351 ret = xmlTextReaderRead(reader);
1352
1353 if (num_elems++ > tick_every) {
1354 num_elems = 0;
1355 banner_busy_tick();
1356 }
1357 }
1358
1359 g_assert(0);
1360 return NULL;
1361 }
1362
1363 static osm_t *process_file(const char *filename) {
1364 osm_t *osm = NULL;
1365 xmlTextReaderPtr reader;
1366 int ret;
1367
1368 reader = xmlReaderForFile(filename, NULL, 0);
1369 if (reader != NULL) {
1370 ret = xmlTextReaderRead(reader);
1371 if(ret == 1) {
1372 char *name = (char*)xmlTextReaderConstName(reader);
1373 if(name && strcasecmp(name, "osm") == 0)
1374 osm = process_osm(reader);
1375 } else
1376 printf("file empty\n");
1377
1378 xmlFreeTextReader(reader);
1379 } else {
1380 fprintf(stderr, "Unable to open %s\n", filename);
1381 }
1382 return osm;
1383 }
1384
1385 /* ----------------------- end of stream parser tests ------------------- */
1386 #endif
1387
1388 #include <sys/time.h>
1389
1390 osm_t *osm_parse(char *filename) {
1391
1392 struct timeval start;
1393 gettimeofday(&start, NULL);
1394
1395 LIBXML_TEST_VERSION;
1396
1397 #ifdef OSM_STREAM_PARSER
1398 // use stream parser
1399 osm_t *osm = process_file(filename);
1400 xmlCleanupParser();
1401
1402 #else
1403 // parse into a tree
1404 /* parse the file and get the DOM */
1405 xmlDoc *doc = NULL;
1406 if ((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
1407 xmlErrorPtr errP = xmlGetLastError();
1408 errorf(NULL, "While parsing \"%s\":\n\n%s", filename, errP->message);
1409 return NULL;
1410 }
1411
1412 osm_t *osm = osm_parse_doc(doc);
1413 #endif
1414
1415 struct timeval end;
1416 gettimeofday(&end, NULL);
1417
1418 printf("total parse time: %ldms\n",
1419 (end.tv_usec - start.tv_usec)/1000 +
1420 (end.tv_sec - start.tv_sec)*1000);
1421
1422 return osm;
1423 }
1424
1425 gboolean osm_sanity_check(GtkWidget *parent, osm_t *osm) {
1426 if(!osm->bounds) {
1427 errorf(parent, _("Invalid data in OSM file:\n"
1428 "Boundary box missing!"));
1429 return FALSE;
1430 }
1431 if(!osm->node) {
1432 errorf(parent, _("Invalid data in OSM file:\n"
1433 "No drawable content found!"));
1434 return FALSE;
1435 }
1436 return TRUE;
1437 }
1438
1439 /* ------------------------- misc access functions -------------- */
1440
1441 char *osm_tag_get_by_key(tag_t *tag, char *key) {
1442 if(!tag || !key) return NULL;
1443
1444 while(tag) {
1445 if(strcasecmp(tag->key, key) == 0)
1446 return tag->value;
1447
1448 tag = tag->next;
1449 }
1450
1451 return NULL;
1452 }
1453
1454 char *osm_way_get_value(way_t *way, char *key) {
1455 tag_t *tag = way->tag;
1456
1457 while(tag) {
1458 if(strcasecmp(tag->key, key) == 0)
1459 return tag->value;
1460
1461 tag = tag->next;
1462 }
1463
1464 return NULL;
1465 }
1466
1467 char *osm_node_get_value(node_t *node, char *key) {
1468 tag_t *tag = node->tag;
1469
1470 while(tag) {
1471 if(strcasecmp(tag->key, key) == 0)
1472 return tag->value;
1473
1474 tag = tag->next;
1475 }
1476
1477 return NULL;
1478 }
1479
1480 gboolean osm_way_has_value(way_t *way, char *str) {
1481 tag_t *tag = way->tag;
1482
1483 while(tag) {
1484 if(tag->value && strcasecmp(tag->value, str) == 0)
1485 return TRUE;
1486
1487 tag = tag->next;
1488 }
1489 return FALSE;
1490 }
1491
1492 gboolean osm_node_has_value(node_t *node, char *str) {
1493 tag_t *tag = node->tag;
1494
1495 while(tag) {
1496 if(tag->value && strcasecmp(tag->value, str) == 0)
1497 return TRUE;
1498
1499 tag = tag->next;
1500 }
1501 return FALSE;
1502 }
1503
1504 gboolean osm_node_has_tag(node_t *node) {
1505 tag_t *tag = node->tag;
1506
1507 if(tag && strcasecmp(tag->key, "created_by") == 0)
1508 tag = tag->next;
1509
1510 return tag != NULL;
1511 }
1512
1513 /* return true if node is part of way */
1514 gboolean osm_node_in_way(way_t *way, node_t *node) {
1515 node_chain_t *node_chain = way->node_chain;
1516 while(node_chain) {
1517 if(node_chain->node == node)
1518 return TRUE;
1519
1520 node_chain = node_chain->next;
1521 }
1522 return FALSE;
1523 }
1524
1525 static void osm_generate_tags(tag_t *tag, xmlNodePtr node) {
1526 while(tag) {
1527 /* make sure "created_by" tag contains our id */
1528 if(strcasecmp(tag->key, "created_by") == 0) {
1529 g_free(tag->value);
1530 tag->value = g_strdup(PACKAGE " v" VERSION);
1531 }
1532
1533 xmlNodePtr tag_node = xmlNewChild(node, NULL, BAD_CAST "tag", NULL);
1534 xmlNewProp(tag_node, BAD_CAST "k", BAD_CAST tag->key);
1535 xmlNewProp(tag_node, BAD_CAST "v", BAD_CAST tag->value);
1536 tag = tag->next;
1537 }
1538 }
1539
1540 /* build xml representation for a way */
1541 char *osm_generate_xml(osm_t *osm, type_t type, void *item) {
1542 char str[32];
1543 xmlChar *result = NULL;
1544 int len = 0;
1545
1546 LIBXML_TEST_VERSION;
1547
1548 xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
1549 xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST "osm");
1550 xmlNewProp(root_node, BAD_CAST "version", BAD_CAST "0.5");
1551 xmlNewProp(root_node, BAD_CAST "generator", BAD_CAST PACKAGE " V" VERSION);
1552 xmlDocSetRootElement(doc, root_node);
1553
1554 switch(type) {
1555 case NODE:
1556 {
1557 node_t *node = (node_t*)item;
1558 xmlNodePtr node_node = xmlNewChild(root_node, NULL,
1559 BAD_CAST "node", NULL);
1560 /* new nodes don't have an id, but get one after the upload */
1561 if(!(node->flags & OSM_FLAG_NEW)) {
1562 snprintf(str, sizeof(str), "%u", (unsigned)node->id);
1563 xmlNewProp(node_node, BAD_CAST "id", BAD_CAST str);
1564 }
1565 g_ascii_dtostr(str, sizeof(str), node->pos.lat);
1566 xmlNewProp(node_node, BAD_CAST "lat", BAD_CAST str);
1567 g_ascii_dtostr(str, sizeof(str), node->pos.lon);
1568 xmlNewProp(node_node, BAD_CAST "lon", BAD_CAST str);
1569 osm_generate_tags(node->tag, node_node);
1570 }
1571 break;
1572
1573 case WAY:
1574 {
1575 way_t *way = (way_t*)item;
1576 xmlNodePtr way_node = xmlNewChild(root_node, NULL, BAD_CAST "way", NULL);
1577 snprintf(str, sizeof(str), "%u", (unsigned)way->id);
1578 xmlNewProp(way_node, BAD_CAST "id", BAD_CAST str);
1579
1580 node_chain_t *node_chain = way->node_chain;
1581 while(node_chain) {
1582 xmlNodePtr nd_node = xmlNewChild(way_node, NULL, BAD_CAST "nd", NULL);
1583 char *str = g_strdup_printf("%ld", node_chain->node->id);
1584 xmlNewProp(nd_node, BAD_CAST "ref", BAD_CAST str);
1585 g_free(str);
1586 node_chain = node_chain->next;
1587 }
1588
1589 osm_generate_tags(way->tag, way_node);
1590 }
1591 break;
1592
1593 case RELATION:
1594 {
1595 relation_t *relation = (relation_t*)item;
1596 xmlNodePtr rel_node = xmlNewChild(root_node, NULL,
1597 BAD_CAST "relation", NULL);
1598 snprintf(str, sizeof(str), "%u", (unsigned)relation->id);
1599 xmlNewProp(rel_node, BAD_CAST "id", BAD_CAST str);
1600
1601 member_t *member = relation->member;
1602 while(member) {
1603 xmlNodePtr m_node = xmlNewChild(rel_node,NULL,BAD_CAST "member", NULL);
1604 char *str = NULL;
1605
1606 switch(member->type) {
1607 case NODE:
1608 xmlNewProp(m_node, BAD_CAST "type", BAD_CAST "node");
1609 str = g_strdup_printf("%ld", member->node->id);
1610 break;
1611
1612 case WAY:
1613 xmlNewProp(m_node, BAD_CAST "type", BAD_CAST "way");
1614 str = g_strdup_printf("%ld", member->way->id);
1615 break;
1616
1617 case RELATION:
1618 xmlNewProp(m_node, BAD_CAST "type", BAD_CAST "relation");
1619 str = g_strdup_printf("%ld", member->relation->id);
1620 break;
1621
1622 default:
1623 break;
1624 }
1625
1626 if(str) {
1627 xmlNewProp(m_node, BAD_CAST "ref", BAD_CAST str);
1628 g_free(str);
1629 }
1630
1631 if(member->role)
1632 xmlNewProp(m_node, BAD_CAST "role", BAD_CAST member->role);
1633 else
1634 xmlNewProp(m_node, BAD_CAST "role", BAD_CAST "");
1635
1636 member = member->next;
1637 }
1638 osm_generate_tags(relation->tag, rel_node);
1639 }
1640 break;
1641
1642 default:
1643 printf("neither NODE nor WAY nor RELATION\n");
1644 g_assert(0);
1645 break;
1646 }
1647
1648 xmlDocDumpFormatMemoryEnc(doc, &result, &len, "UTF-8", 1);
1649 xmlFreeDoc(doc);
1650 xmlCleanupParser();
1651
1652 // puts("xml encoding result:");
1653 // puts((char*)result);
1654
1655 return (char*)result;
1656 }
1657
1658 /* build xml representation for a node */
1659 char *osm_generate_xml_node(osm_t *osm, node_t *node) {
1660 return osm_generate_xml(osm, NODE, node);
1661 }
1662
1663 /* build xml representation for a way */
1664 char *osm_generate_xml_way(osm_t *osm, way_t *way) {
1665 return osm_generate_xml(osm, WAY, way);
1666 }
1667
1668 /* build xml representation for a relation */
1669 char *osm_generate_xml_relation(osm_t *osm, relation_t *relation) {
1670 return osm_generate_xml(osm, RELATION, relation);
1671 }
1672
1673 node_t *osm_get_node_by_id(osm_t *osm, item_id_t id) {
1674 node_t *node = osm->node;
1675 while(node) {
1676 if(node->id == id)
1677 return node;
1678
1679 node = node->next;
1680 }
1681 return NULL;
1682 }
1683
1684 way_t *osm_get_way_by_id(osm_t *osm, item_id_t id) {
1685 way_t *way = osm->way;
1686 while(way) {
1687 if(way->id == id)
1688 return way;
1689
1690 way = way->next;
1691 }
1692 return NULL;
1693 }
1694
1695 relation_t *osm_get_relation_by_id(osm_t *osm, item_id_t id) {
1696 relation_t *relation = osm->relation;
1697 while(relation) {
1698 if(relation->id == id)
1699 return relation;
1700
1701 relation = relation->next;
1702 }
1703
1704 return NULL;
1705 }
1706
1707 /* ---------- edit functions ------------- */
1708
1709 item_id_t osm_new_way_id(osm_t *osm) {
1710 item_id_t id = -1;
1711
1712 while(TRUE) {
1713 gboolean found = FALSE;
1714 way_t *way = osm->way;
1715 while(way) {
1716 if(way->id == id)
1717 found = TRUE;
1718
1719 way = way->next;
1720 }
1721
1722 /* no such id so far -> use it */
1723 if(!found) return id;
1724
1725 id--;
1726 }
1727 g_assert(0);
1728 return 0;
1729 }
1730
1731 item_id_t osm_new_node_id(osm_t *osm) {
1732 item_id_t id = -1;
1733
1734 while(TRUE) {
1735 gboolean found = FALSE;
1736 node_t *node = osm->node;
1737 while(node) {
1738 if(node->id == id)
1739 found = TRUE;
1740
1741 node = node->next;
1742 }
1743
1744 /* no such id so far -> use it */
1745 if(!found) return id;
1746
1747 id--;
1748 }
1749 g_assert(0);
1750 return 0;
1751 }
1752
1753 node_t *osm_node_new(osm_t *osm, gint x, gint y) {
1754 printf("Creating new node\n");
1755
1756 node_t *node = g_new0(node_t, 1);
1757 node->lpos.x = x;
1758 node->lpos.y = y;
1759 node->visible = TRUE;
1760 node->time = time(NULL);
1761
1762 /* add created_by tag */
1763 node->tag = g_new0(tag_t, 1);
1764 node->tag->key = g_strdup("created_by");
1765 node->tag->value = g_strdup(PACKAGE " v" VERSION);
1766
1767 /* convert screen position back to ll */
1768 lpos2pos(osm->bounds, &node->lpos, &node->pos);
1769
1770 printf(" new at %d %d (%f %f)\n",
1771 node->lpos.x, node->lpos.y, node->pos.lat, node->pos.lon);
1772
1773 return node;
1774 }
1775
1776
1777 void osm_node_attach(osm_t *osm, node_t *node) {
1778 printf("Attaching node\n");
1779
1780 node->id = osm_new_node_id(osm);
1781 node->flags = OSM_FLAG_NEW;
1782
1783 /* attach to end of node list */
1784 node_t **lnode = &osm->node;
1785 while(*lnode) lnode = &(*lnode)->next;
1786 *lnode = node;
1787 }
1788
1789 way_t *osm_way_new(void) {
1790 printf("Creating new way\n");
1791
1792 way_t *way = g_new0(way_t, 1);
1793 way->visible = TRUE;
1794 way->flags = OSM_FLAG_NEW;
1795 way->time = time(NULL);
1796
1797 /* add created_by tag */
1798 way->tag = g_new0(tag_t, 1);
1799 way->tag->key = g_strdup("created_by");
1800 way->tag->value = g_strdup(PACKAGE " v" VERSION);
1801
1802 return way;
1803 }
1804
1805 void osm_way_attach(osm_t *osm, way_t *way) {
1806 printf("Attaching way\n");
1807
1808 way->id = osm_new_way_id(osm);
1809 way->flags = OSM_FLAG_NEW;
1810
1811 /* attach to end of way list */
1812 way_t **lway = &osm->way;
1813 while(*lway) lway = &(*lway)->next;
1814 *lway = way;
1815 }
1816
1817 /* returns pointer to chain of ways affected by this deletion */
1818 way_chain_t *osm_node_delete(osm_t *osm, icon_t **icon,
1819 node_t *node, gboolean permanently,
1820 gboolean affect_ways) {
1821 way_chain_t *way_chain = NULL, **cur_way_chain = &way_chain;
1822
1823 /* new nodes aren't stored on the server and are just deleted permanently */
1824 if(node->flags & OSM_FLAG_NEW) {
1825 printf("About to delete NEW node #%ld -> force permanent delete\n",
1826 node->id);
1827 permanently = TRUE;
1828 }
1829
1830 /* first remove node from all ways using it */
1831 way_t *way = osm->way;
1832 while(way) {
1833 node_chain_t **chain = &(way->node_chain);
1834 gboolean modified = FALSE;
1835 while(*chain) {
1836 /* remove node from chain */
1837 if(node == (*chain)->node) {
1838 modified = TRUE;
1839 if(affect_ways) {
1840 node_chain_t *next = (*chain)->next;
1841 g_free(*chain);
1842 *chain = next;
1843 } else
1844 chain = &((*chain)->next);
1845 } else
1846 chain = &((*chain)->next);
1847 }
1848
1849 if(modified) {
1850 way->flags |= OSM_FLAG_DIRTY;
1851
1852 /* and add the way to the list of affected ways */
1853 *cur_way_chain = g_new0(way_chain_t, 1);
1854 (*cur_way_chain)->way = way;
1855 cur_way_chain = &((*cur_way_chain)->next);
1856 }
1857
1858 way = way->next;
1859 }
1860
1861 if(!permanently) {
1862 printf("mark node #%ld as deleted\n", node->id);
1863 node->flags |= OSM_FLAG_DELETED;
1864 } else {
1865 printf("permanently delete node #%ld\n", node->id);
1866
1867 /* remove it from the chain */
1868 node_t **cnode = &osm->node;
1869 int found = 0;
1870
1871 while(*cnode) {
1872 if(*cnode == node) {
1873 found++;
1874 *cnode = (*cnode)->next;
1875
1876 osm_node_free(icon, node);
1877 } else
1878 cnode = &((*cnode)->next);
1879 }
1880 g_assert(found == 1);
1881 }
1882
1883 return way_chain;
1884 }
1885
1886 guint osm_way_number_of_nodes(way_t *way) {
1887 guint nodes = 0;
1888 node_chain_t *chain = way->node_chain;
1889 while(chain) {
1890 nodes++;
1891 chain = chain->next;
1892 }
1893 return nodes;
1894 }
1895
1896 /* return all relations a node is in */
1897 relation_chain_t *osm_node_to_relation(osm_t *osm, node_t *node) {
1898 relation_chain_t *rel_chain = NULL, **cur_rel_chain = &rel_chain;
1899
1900 relation_t *relation = osm->relation;
1901 while(relation) {
1902 gboolean is_member = FALSE;
1903
1904 member_t *member = relation->member;
1905 while(member) {
1906 switch(member->type) {
1907 case NODE:
1908 /* nodes are checked directly */
1909 if(member->node == node)
1910 is_member = TRUE;
1911 break;
1912
1913 case WAY: {
1914 /* ways have to be checked for the nodes they consist of */
1915 node_chain_t *chain = member->way->node_chain;
1916 while(chain && !is_member) {
1917 if(chain->node == node)
1918 is_member = TRUE;
1919
1920 chain = chain->next;
1921 }
1922 } break;
1923
1924 default:
1925 break;
1926 }
1927 member = member->next;
1928 }
1929
1930 /* node is a member of this relation, so move it to the member chain */
1931 if(is_member) {
1932 *cur_rel_chain = g_new0(relation_chain_t, 1);
1933 (*cur_rel_chain)->relation = relation;
1934 cur_rel_chain = &((*cur_rel_chain)->next);
1935 }
1936
1937 relation = relation->next;
1938 }
1939
1940 return rel_chain;
1941 }
1942
1943 /* return all relations a way is in */
1944 relation_chain_t *osm_way_to_relation(osm_t *osm, way_t *way) {
1945 relation_chain_t *rel_chain = NULL, **cur_rel_chain = &rel_chain;
1946
1947 relation_t *relation = osm->relation;
1948 while(relation) {
1949 gboolean is_member = FALSE;
1950
1951 member_t *member = relation->member;
1952 while(member) {
1953 switch(member->type) {
1954 case WAY: {
1955 /* ways can be check directly */
1956 if(member->way == way)
1957 is_member = TRUE;
1958 } break;
1959
1960 default:
1961 break;
1962 }
1963 member = member->next;
1964 }
1965
1966 /* way is a member of this relation, so move it to the member chain */
1967 if(is_member) {
1968 *cur_rel_chain = g_new0(relation_chain_t, 1);
1969 (*cur_rel_chain)->relation = relation;
1970 cur_rel_chain = &((*cur_rel_chain)->next);
1971 }
1972
1973 relation = relation->next;
1974 }
1975
1976 return rel_chain;
1977 }
1978
1979 /* return all ways a node is in */
1980 way_chain_t *osm_node_to_way(osm_t *osm, node_t *node) {
1981 way_chain_t *chain = NULL, **cur_chain = &chain;
1982
1983 way_t *way = osm->way;
1984 while(way) {
1985 gboolean is_member = FALSE;
1986
1987 node_chain_t *node_chain = way->node_chain;
1988 while(node_chain) {
1989 if(node_chain->node == node)
1990 is_member = TRUE;
1991
1992 node_chain = node_chain->next;
1993 }
1994
1995 /* node is a member of this relation, so move it to the member chain */
1996 if(is_member) {
1997 *cur_chain = g_new0(way_chain_t, 1);
1998 (*cur_chain)->way = way;
1999 cur_chain = &((*cur_chain)->next);
2000 }
2001
2002 way = way->next;
2003 }
2004
2005 return chain;
2006 }
2007
2008 gboolean osm_position_within_bounds(osm_t *osm, gint x, gint y) {
2009 if((x < osm->bounds->min.x) || (x > osm->bounds->max.x)) return FALSE;
2010 if((y < osm->bounds->min.y) || (y > osm->bounds->max.y)) return FALSE;
2011 return TRUE;
2012 }
2013
2014 /* remove the given node from all relations. used if the node is to */
2015 /* be deleted */
2016 void osm_node_remove_from_relation(osm_t *osm, node_t *node) {
2017 relation_t *relation = osm->relation;
2018 printf("removing node #%ld from all relations:\n", node->id);
2019
2020 while(relation) {
2021 member_t **member = &relation->member;
2022 while(*member) {
2023 if(((*member)->type == NODE) &&
2024 ((*member)->node == node)) {
2025
2026 printf(" from relation #%ld\n", relation->id);
2027
2028 member_t *cur = *member;
2029 *member = (*member)->next;
2030 osm_member_free(cur);
2031
2032 relation->flags |= OSM_FLAG_DIRTY;
2033 } else
2034 member = &(*member)->next;
2035 }
2036 relation = relation->next;
2037 }
2038 }
2039
2040 /* remove the given way from all relations */
2041 void osm_way_remove_from_relation(osm_t *osm, way_t *way) {
2042 relation_t *relation = osm->relation;
2043 printf("removing way #%ld from all relations:\n", way->id);
2044
2045 while(relation) {
2046 member_t **member = &relation->member;
2047 while(*member) {
2048 if(((*member)->type == WAY) &&
2049 ((*member)->way == way)) {
2050
2051 printf(" from relation #%ld\n", relation->id);
2052
2053 member_t *cur = *member;
2054 *member = (*member)->next;
2055 osm_member_free(cur);
2056
2057 relation->flags |= OSM_FLAG_DIRTY;
2058 } else
2059 member = &(*member)->next;
2060 }
2061 relation = relation->next;
2062 }
2063 }
2064
2065 void osm_way_delete(osm_t *osm, icon_t **icon,
2066 way_t *way, gboolean permanently) {
2067
2068 /* new ways aren't stored on the server and are just deleted permanently */
2069 if(way->flags & OSM_FLAG_NEW) {
2070 printf("About to delete NEW way #%ld -> force permanent delete\n",
2071 way->id);
2072 permanently = TRUE;
2073 }
2074
2075 /* delete all nodes that aren't in other use now */
2076 node_chain_t **chain = &way->node_chain;
2077 while(*chain) {
2078
2079 (*chain)->node->ways--;
2080 printf("checking node #%ld (still used by %d)\n",
2081 (*chain)->node->id, (*chain)->node->ways);
2082
2083 /* this node must only be part of this way */
2084 if(!(*chain)->node->ways) {
2085 /* delete this node, but don't let this actually affect the */
2086 /* associated ways as the only such way is the oen we are currently */
2087 /* deleting */
2088 way_chain_t *way_chain =
2089 osm_node_delete(osm, icon, (*chain)->node, FALSE, FALSE);
2090 g_assert(way_chain);
2091 while(way_chain) {
2092 way_chain_t *way_next = way_chain->next;
2093 g_assert(way_chain->way == way);
2094 g_free(way_chain);
2095 way_chain = way_next;
2096 }
2097 }
2098
2099 node_chain_t *cur = (*chain);
2100 *chain = cur->next;
2101 g_free(cur);
2102 }
2103 way->node_chain = NULL;
2104
2105 if(!permanently) {
2106 printf("mark way #%ld as deleted\n", way->id);
2107 way->flags |= OSM_FLAG_DELETED;
2108 } else {
2109 printf("permanently delete way #%ld\n", way->id);
2110
2111 /* remove it from the chain */
2112 way_t **cway = &osm->way;
2113 int found = 0;
2114
2115 while(*cway) {
2116 if(*cway == way) {
2117 found++;
2118 *cway = (*cway)->next;
2119
2120 osm_way_free(way);
2121 } else
2122 cway = &((*cway)->next);
2123 }
2124 g_assert(found == 1);
2125 }
2126 }
2127
2128 void osm_way_revert(way_t *way) {
2129 node_chain_t *new = NULL;
2130
2131 /* walk old chain first to last */
2132 node_chain_t *old = way->node_chain;
2133 while(old) {
2134 node_chain_t *next = old->next;
2135
2136 /* and prepend each node to the new chain */
2137 old->next = new;
2138 new = old;
2139
2140 old = next;
2141 }
2142
2143 way->node_chain = new;
2144 }
2145
2146 node_t *osm_way_get_first_node(way_t *way) {
2147 node_chain_t *chain = way->node_chain;
2148 if(!chain) return NULL;
2149 return chain->node;
2150 }
2151
2152 node_t *osm_way_get_last_node(way_t *way) {
2153 node_chain_t *chain = way->node_chain;
2154
2155 while(chain && chain->next) chain=chain->next;
2156
2157 if(!chain) return NULL;
2158
2159 return chain->node;
2160 }
2161
2162 void osm_way_rotate(way_t *way, gint offset) {
2163 if(!offset) return;
2164
2165 /* needs at least two nodes to work properly */
2166 g_assert(way->node_chain);
2167 g_assert(way->node_chain->next);
2168
2169 while(offset--) {
2170 node_chain_t *chain = way->node_chain;
2171 chain->node->ways--; // reduce way count of old start/end node
2172
2173 /* move all nodes ahead one chain element ... */
2174 while(chain->next) {
2175 chain->node = chain->next->node;
2176 chain = chain->next;
2177 }
2178
2179 /* ... and make last one same as first one */
2180 chain->node = way->node_chain->node;
2181 chain->node->ways++; // increase way count of new start/end node
2182 }
2183 }
2184
2185 tag_t *osm_tags_copy(tag_t *src_tag, gboolean update_creator) {
2186 tag_t *new_tags = NULL;
2187 tag_t **dst_tag = &new_tags;
2188
2189 while(src_tag) {
2190 *dst_tag = g_new0(tag_t, 1);
2191 (*dst_tag)->key = g_strdup(src_tag->key);
2192 if(update_creator && (strcasecmp(src_tag->key, "created_by") == 0))
2193 (*dst_tag)->value = g_strdup(PACKAGE " v" VERSION);
2194 else
2195 (*dst_tag)->value = g_strdup(src_tag->value);
2196
2197 dst_tag = &(*dst_tag)->next;
2198 src_tag = src_tag->next;
2199 }
2200
2201 return new_tags;
2202 }
2203 // vim:et:ts=8:sw=2:sts=2:ai