source: subversion/applications/utils/planetdiff/planetdiff.c @ 30195

Last change on this file since 30195 was 11715, checked in by jonb, 11 years ago

Update planetdiff to ignore differences in attribute ordering and equivalent representations of floating point lat/lon values. These tend to differ between the weekly planet dump and osmosis

  • Property svn:keywords set to Revision
File size: 22.5 KB
Line 
1/*
2#-----------------------------------------------------------------------------
3# planetdiff - Calculates the differences between 2 planet.osm
4# Roughly equivalent to 'diff' but optimised for planet.osm files
5#
6# Use:
7#      planetdiff planetA.osm planetB.osm > delta-AB.xml
8#
9#      planetpatch planetA.osm delta-AB.xml > planetB.osm
10#
11# One of the input files may come from STDIN by specifying - as the filename
12# Files can be gzip or bzip2 compressed.
13#
14#-----------------------------------------------------------------------------
15# Written by Jon Burgess, Copyright 2007,2008
16#
17# This program is free software; you can redistribute it and/or
18# modify it under the terms of the GNU General Public License
19# as published by the Free Software Foundation; either version 2
20# of the License, or (at your option) any later version.
21#
22# This program is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
26#
27# You should have received a copy of the GNU General Public License
28# along with this program; if not, write to the Free Software
29# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
30#-----------------------------------------------------------------------------
31*/
32
33/* TODO:
34 * - Empty!
35 *
36 * DONE:
37 * Escape key/values on output (&"'<>)
38 * write patch tool
39 * Error if file not in required sequence (node/way/relation with increasing ID)
40 * Warn if generator is not planet.rb since others tend to violate ordering above
41 * Generate run-time stats on stderr
42 * Allow direct reading of .gz and .bz2 files
43 * Perform UTF8sanitize on input
44 */
45
46// define VERBOSE if you want run time stats
47#undef VERBOSE
48
49#include <stdio.h>
50#include <unistd.h>
51#include <stdlib.h>
52#include <string.h>
53#include <assert.h>
54
55#include <libxml/xmlstring.h>
56#include <libxml/xmlreader.h>
57
58#include "keyvals.h"
59#include "input.h"
60#include "sanitizer.h"
61
62/* Since {node,way,relation} elements are not nested we can
63 * accumulates the attributes, tags and nodes in global data.
64 * This is maintained independently for planetA and planetB
65 */
66enum type { invalid, node, way, relation, eof } current[2];
67static int osm_id[2];
68static struct keyval tags[2], nds[2], attrs[2];
69static struct keyval member_nodes[2], member_ways[2], member_rels[2];
70static xmlTextReaderPtr reader[2];
71static const char *files[2];
72// Strict mode is considers timestamp and key ordering changes as significant, otherwise they are ignored
73static const int strict = 0;
74// Do we run UTF8sanitizer on input, this should only be necessary if you are using very old planet dumps.
75static const int sanitize = 0;
76
77static int compareTypeID() {
78    // Compare object types
79    if (current[0] < current[1])
80        return -1;
81    if (current[0] > current[1])
82        return +1;
83
84    if (osm_id[0] < osm_id[1])
85        return -1;
86    if (osm_id[0] > osm_id[1])
87        return +1;
88
89    return 0;
90}
91
92static int compareKeyval(struct keyval kv[2])
93{
94    struct keyval *p[2];
95    int r;
96
97    p[0] = kv[0].next;
98    p[1] = kv[1].next;
99#define END(x) (p[x] == &kv[x])
100
101    while(1) {
102        if (END(0) && END(1))
103            return 0;
104
105        // Ignore user, uid and version since we don't want to emit these for the new planet which add these
106        if (!END(0) && (!strcmp(p[0]->key, "user") || !strcmp(p[0]->key, "uid") || !strcmp(p[0]->key, "version"))) {
107            p[0] = p[0]->next;
108            continue;
109        }
110        if (!END(1) && (!strcmp(p[1]->key, "user") || !strcmp(p[1]->key, "uid") || !strcmp(p[1]->key, "version"))) {
111            p[1] = p[1]->next;
112            continue;
113        }
114
115        if (END(0))
116            return -1;
117        if (END(1))
118            return +1;
119
120        r = strcmp(p[0]->key, p[1]->key);
121        if (r) return r;
122
123        // Ignore the value of any timstemap= for comparison
124        // This skips formatting and other uninteresting timestamp-only updates
125        // Note: this triggers on both k="timestamp" as well as timestamp=...
126        if (strict || strcmp(p[0]->key, "timestamp")) {
127            r = strcmp(p[0]->value, p[1]->value);
128#if 0
129            if (r) return r;
130#else
131            if (r) {
132              // Osmosis drops trailing 0's on floats, test for equal value
133              if (!strcmp(p[0]->key, "lat") || !strcmp(p[0]->key, "lon")) {
134                double a = strtod(p[0]->value, NULL);
135                double b = strtod(p[1]->value, NULL);
136                if (a != b)
137                  return a-b; 
138              } else
139                return r;
140            }
141#endif
142        }
143
144        p[0] = p[0]->next;
145        p[1] = p[1]->next;
146    }
147    return 0;
148}
149
150int compareKV(const void *hA, const void *hB)
151{
152    struct keyval * const *a = hA;
153    struct keyval * const *b = hB;
154    int c;
155
156    c = strcmp((*a)->key, (*b)->key);
157    if (c) return c;
158
159    return strcmp((*a)->value, (*b)->value);
160}
161
162void sortList(struct keyval *in, struct keyval *out)
163{
164    struct keyval **t, *p;
165    int len = countList(in);
166    int i;
167
168    initList(out);
169    if (!len)
170        return;
171
172    t = calloc(len, sizeof(*in));
173    assert(t);
174
175    for (i=0, p = in->next; i<len; i++, p = p->next)
176        t[i] = p;
177
178    qsort(t, len, sizeof(*t), compareKV);
179
180    for (i=0; i<len; i++)
181        addItem(out, t[i]->key, t[i]->value, 0);
182
183    free(t);
184}
185
186static int compareLists(struct keyval kv[2])
187{
188    // Strict mode requires all lists to be in a consistent order
189    // non-strict treats the 2 sets of tags below as equivalent
190    //
191    // <k='A' v='1' />
192    // <k='B' v='2' />
193    //
194    // <k='B' v='2' />
195    // <k='A' v='1' />
196
197    struct keyval clone[2];
198    int r;
199
200    if (strict)
201        return compareKeyval(kv);
202
203    if (!listHasData(&kv[0]) && !listHasData(&kv[1]))
204        return 0;
205
206    sortList(&kv[0], &clone[0]);
207    sortList(&kv[1], &clone[1]);
208    r = compareKeyval(clone);
209    resetList(&clone[0]);
210    resetList(&clone[1]);
211
212    return r;
213}
214
215
216static int compareOther(void)
217{
218    int c;
219
220    c = compareLists(attrs);
221    if (c) return c;
222
223    c = compareLists(tags);
224    if (c) return c;
225
226    c = compareKeyval(nds);
227    if (c) return c;
228
229    c = compareLists(member_nodes);
230    if (c) return c;
231
232    c = compareLists(member_ways);
233    if (c) return c;
234
235    c = compareLists(member_rels);
236    if (c) return c;
237
238    return 0;
239}
240
241static void collectAttributes(int i)
242{
243    int ret;
244    const xmlChar *name, *value;
245    while ((ret = xmlTextReaderMoveToNextAttribute(reader[i])) == 1) {
246        name  = xmlTextReaderConstName(reader[i]);
247        value = xmlTextReaderConstValue(reader[i]);
248        addItem(&attrs[i], (char *)name, (char *)value, 0);
249        //fprintf(stderr, "%s = %s\n", (char *)name, (char *)value);
250    }
251    if (ret != 0) {
252        fprintf(stderr, "%s : failed to parse attributes\n", files[i]);
253        exit(1);
254    }
255}
256
257static void StartElement(int i, const xmlChar *name)
258{
259    xmlChar *xk, *xv, *xid, *xgen, *xrole, *xtype;
260    static int warn = 1;
261
262    if (xmlStrEqual(name, BAD_CAST "node")) {
263        current[i] = node;
264        collectAttributes(i);
265    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
266        xk = xmlTextReaderGetAttribute(reader[i], BAD_CAST "k");
267        xv = xmlTextReaderGetAttribute(reader[i], BAD_CAST "v");
268        addItem(&tags[i], (char *)xk, (char *)xv, 0);
269        xmlFree(xk);
270        xmlFree(xv);
271    } else if (xmlStrEqual(name, BAD_CAST "member")) {
272        xtype = xmlTextReaderGetAttribute(reader[i], BAD_CAST "type");
273        xid   = xmlTextReaderGetAttribute(reader[i], BAD_CAST "ref");
274        xrole = xmlTextReaderGetAttribute(reader[i], BAD_CAST "role");
275        if (xmlStrEqual(xtype, BAD_CAST "node"))
276            addItem(&member_nodes[i], (char *)xid, (char *)xrole, 0);
277        else if (xmlStrEqual(xtype, BAD_CAST "way"))
278            addItem(&member_ways[i], (char *)xid, (char *)xrole, 0);
279        else if (xmlStrEqual(xtype, BAD_CAST "relation"))
280            addItem(&member_rels[i], (char *)xid, (char *)xrole, 0);
281        else
282            fprintf(stderr, "Unknown type: <member type='%s' ...\n", (char *)xtype);
283        xmlFree(xtype);
284        xmlFree(xid);
285        xmlFree(xrole);
286    } else if (xmlStrEqual(name, BAD_CAST "way")) {
287        current[i] = way;
288        collectAttributes(i);
289    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
290        xid  = xmlTextReaderGetAttribute(reader[i], BAD_CAST "ref");
291        addItem(&nds[i], "ref", (char *)xid, 0);
292        xmlFree(xid);
293    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
294        current[i] = relation;
295        collectAttributes(i);
296    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
297        if (warn) {
298            xgen = xmlTextReaderGetAttribute(reader[i], BAD_CAST "generator");
299            if (!xmlStrEqual(xgen, BAD_CAST "OpenStreetMap planet.rb") && !xmlStrEqual(xgen, BAD_CAST "OpenStreetMap planet.c")) {
300                fprintf(stderr, "Warning: The input file %s was generated by: %s\n", files[i], xgen ? (char *)xgen : "unknown");
301                fprintf(stderr, "this tool relies on the planet.osm format of the format of the planet.osm file\n");
302                fprintf(stderr, "generated by planet.rb of planet.openstreetmap.org and may not work with other osm files.\n\n");
303                warn = 0;
304            }
305            xmlFree(xgen);
306        }
307    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
308        // Ignored
309    } else {
310        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
311    }
312}
313
314static int EndElement(int i, const xmlChar *name)
315{
316    // Return 0 for closing tag of node/way/relation
317    int ret;
318    if (xmlStrEqual(name, BAD_CAST "node")) {
319        ret = 0;
320    } else if (xmlStrEqual(name, BAD_CAST "way")) {
321        ret = 0;
322    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
323        ret = 1;
324    } else if (xmlStrEqual(name, BAD_CAST "member")) {
325        ret = 1;
326    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
327        ret = 1;
328    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
329        ret = 0;
330    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
331        ret = 1; // maybe should be 0
332    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
333        ret = 1;
334    } else {
335        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
336        ret = 1;
337    }
338    return ret;
339}
340
341// Process an XML node, returns 0 for the closing tag for a node/segment/way
342static int processNode(int i)
343{
344    int ret = 1;
345    int empty;
346    xmlChar *name = xmlTextReaderName(reader[i]);
347    if (name == NULL)
348        name = xmlStrdup(BAD_CAST "--");
349       
350    switch(xmlTextReaderNodeType(reader[i])) {
351        case XML_READER_TYPE_ELEMENT:
352            empty = xmlTextReaderIsEmptyElement(reader[i]);
353            StartElement(i, name);     
354            if (!empty)
355                break;
356            /* Drop through for self closing tags since these generate no end_element */
357        case XML_READER_TYPE_END_ELEMENT:
358            ret = EndElement(i, name);
359            break;
360        case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
361            /* Ignore */
362            break;
363        default:
364            fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader[i]));
365            break;
366    }
367    xmlFree(name);
368    return ret;
369}
370
371static const char *getName(int i)
372{
373    switch (current[i]) {
374        case node: return "node";
375        case way: return "way";
376        case relation: return "relation";
377        case invalid: return "invalid";
378        default:
379            break;
380    }
381    fprintf(stderr, "Unhandled type %d\n", current[i]);
382    exit(5);
383}
384
385// Reads in a complete OSM object, e.g. <node> to <node/>
386static void getobject(int i)
387{
388    int ret = 1;
389    const char *id;
390    enum type last_type = current[i];
391    const char *last_name = getName(i);
392    int last_id = osm_id[i];
393    //static int count;
394
395    // Delete data for previous object
396    resetList(&attrs[i]);
397    resetList(&nds[i]);
398    resetList(&tags[i]);
399    resetList(&member_nodes[i]);
400    resetList(&member_ways[i]);
401    resetList(&member_rels[i]);
402
403    current[i] = eof;
404    if (!reader[i])
405        return; // EOF
406
407    while(ret == 1) {
408        ret = xmlTextReaderRead(reader[i]);
409        if (ret == 0) {
410            //fprintf(stderr, "EOF %s\n", files[i]);
411            xmlFreeTextReader(reader[i]);
412            reader[i] = NULL;
413            return;
414        }
415        if (ret != 1) {
416            fprintf(stderr, "Error parsing file %s\n", files[i]);
417            exit(3);
418        }
419
420        ret = processNode(i);
421    }
422    // Retrieve osm_id for node/way/relation
423    id = getItem(&attrs[i], "id");
424    osm_id[i] = id ? atoi(id) : 0;
425    //fprintf(stderr, "%d: object %d, id=%d\n", i, current[i], osm_id[i]);
426
427    // Perform sanity checking on element sequence. The output of planet.rb conforms to this and
428    // the current diff/patch algorithm relies on these properties.
429    if (current[i] < last_type) {
430        fprintf(stderr, "Error: <%s> seen after <%s>. The file must be strictly ordered node/way/relation.\n", getName(i), last_name);
431        fprintf(stderr, "The planet.osm generated by the planet export (planet.rb) is consistent with this.\n");
432        exit(8);
433    }
434    if ((current[i] == last_type) && (osm_id[i] < last_id)) {
435        fprintf(stderr, "Error: <%s id=%d> seen after <%s id=%d>. The IDs must be in increasing order.\n", getName(i), osm_id[i], getName(i), last_id);
436        fprintf(stderr, "The planet.osm generated by the planet export (planet.rb) is consistent with this.\n");
437        exit(9);
438    }
439#ifdef VERBOSE
440    // Some run-time stats, only count first stream
441    if (i == 0) {
442        count++;
443        if (current[i] != last_type) {
444            count = 0;
445            fprintf(stderr, "\n");
446        }
447        if ((count % 10000) == 0)
448            fprintf(stderr, "\rProcessing: %s(%dk)", getName(i), count/1000);
449    }
450#endif
451}
452
453
454static void displayNode(int i, const char *indent)
455{
456    struct keyval *p;
457
458    printf("%s<%s", indent,getName(i));
459
460    while ((p = popItem(&attrs[i])) != NULL) {
461        printf(" %s=\"%s\"", p->key, p->value);
462        freeItem(p);
463    }
464
465    if (listHasData(&tags[i]) || listHasData(&nds[i]) || listHasData(&member_nodes[i]) ||
466        listHasData(&member_ways[i]) || listHasData(&member_rels[i])) {
467        printf(">\n");
468
469        while ((p = popItem(&nds[i])) != NULL) {
470            printf("%s  <nd ref=\"%s\" />\n", indent, p->value);
471            freeItem(p);
472        }
473        while ((p = popItem(&member_nodes[i])) != NULL) {
474            printf("%s  <member type=\"node\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
475            freeItem(p);
476        }
477        while ((p = popItem(&member_ways[i])) != NULL) {
478            printf("%s  <member type=\"way\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
479            freeItem(p);
480        }
481        while ((p = popItem(&member_rels[i])) != NULL) {
482            printf("%s  <member type=\"relation\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
483            freeItem(p);
484        }
485        while ((p = popItem(&tags[i])) != NULL) {
486            printf("%s  <tag k=\"%s\" v=\"%s\" />\n", indent, p->key, p->value);
487            freeItem(p);
488        }
489        printf("%s</%s>\n", indent,getName(i));
490    } else {
491        printf("/>\n");
492    }
493}
494
495static void displayDiff(int i)
496{
497    const char *operation = i ? "add" : "delete";
498    printf("  <%s>\n", operation);
499    displayNode(i, "    ");
500    printf("  </%s>\n", operation);
501}
502
503static void displayPatch(int i)
504{
505    displayNode(i, "  ");
506}
507
508
509static void process(void)
510{
511    int c;
512
513    getobject(0);
514    getobject(1);
515
516    while (reader[0] || reader[1]) {
517        c = compareTypeID();
518        if (c == 0) {
519            // Matching object type and ID, generate diff if content differs
520            if (compareOther()) {
521                displayDiff(0);
522                displayDiff(1);
523            }
524            getobject(0);
525            getobject(1);
526        } else if (c < 0) {
527            // Object in stream 0 is missing in 1, generate diff to remove old content 0
528            displayDiff(0);
529            getobject(0);
530        } else {
531            // Object in stream 0 is ahead of 1, generate diff to add content in 1
532            displayDiff(1);
533            getobject(1);
534        }
535    }
536}
537
538
539
540static int diffFiles(void)
541{
542    int i;
543
544    for(i=0; i<2; i++) {
545        if (sanitize)
546            reader[i] = sanitizerOpen(files[i]);
547        else
548            reader[i] = inputUTF8(files[i]);
549        if (!reader[i]) {
550            fprintf(stderr, "Unable to open %s\n", files[i]);
551            return 1;
552        }
553    }
554
555    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
556    printf("<planetdiff version=\"0.5\" generator=\"OpenStreetMap planetdiff\" from=\"%s\" to=\"%s\">\n", files[0], files[1]);
557    process();
558    printf("</planetdiff>\n");
559
560    return 0;
561}
562
563
564static void applyPatch(int mode)
565{
566    int c;
567    //fprintf(stdout,"%s: %d id=%d\n", (mode > 0)? "add" : "delete", current[1], osm_id[1]);
568
569    // Stream input until we get to or pass the matching type+id
570    while ((c = compareTypeID()) < 0) {
571        if (current[0] != invalid)
572            displayPatch(0);
573        getobject(0);
574    }
575    if (mode == -1) {
576        if ((c == 0) && (compareOther() == 0)) {
577            // Perfect match, remove this from input by fetching next object
578            getobject(0);
579            return;
580        }
581        fprintf(stderr, "Error remove for <%s id=%d> does not match input file. %s\n",
582                getName(1), osm_id[1], (c==0)?"Object content differs.":"Object ID missing.");
583        exit(6);
584    } else if (mode == +1) {
585        if (c > 0) {
586            // Found next input following the insert, emit the new content but not input (yet)
587            displayPatch(1);
588            return;
589        }
590        fprintf(stderr, "Error adding <%s id=%d> input file already contains this object\n", getName(1), osm_id[1]);
591        exit(6);
592    }
593}
594
595static void processPatchNode(void)
596{
597    int i = 1; // Patch file is always stream 1
598    int empty;
599    int mode = 0; // -1 = remove, +1 = add;
600    xmlChar *name = xmlTextReaderName(reader[i]);
601    if (name == NULL)
602        name = xmlStrdup(BAD_CAST "--");
603       
604    switch(xmlTextReaderNodeType(reader[i])) {
605        case XML_READER_TYPE_ELEMENT:
606            empty = xmlTextReaderIsEmptyElement(reader[i]);
607            if (xmlStrEqual(name, BAD_CAST "add"))
608                mode = +1;
609            else if (xmlStrEqual(name, BAD_CAST "delete"))
610                mode = -1;
611            else if (xmlStrEqual(name, BAD_CAST "planetdiff")) {
612                mode = 0;
613                break;
614            } else {
615                fprintf(stderr, "Unexpected node: <%s>\n", (char *)name);
616                if (xmlStrEqual(name, BAD_CAST "osm"))
617                    fprintf(stderr, "It looks like %s is an osm file not a planetdiff\n", files[i]);
618                exit(1);
619            }
620            // Retrieve the object to add/remove from the patch file and apply
621            if (!empty) {
622                getobject(1);
623                applyPatch(mode);
624                break;
625            }
626            /* Drop through for self closing tags since these generate no end_element */
627        case XML_READER_TYPE_END_ELEMENT:
628            mode = 0;
629            break;
630        case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
631            /* Ignore */
632            break;
633        default:
634            fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader[i]));
635            break;
636    }
637    xmlFree(name);
638}
639
640
641static int patchFiles(void)
642{
643    int ret = 0;
644    int i;
645    // files[0] = planet.osm (input)
646    // files[1] = delta.xml (patch)
647    for(i=0; i<2; i++) {
648        if (sanitize)
649            reader[i] = sanitizerOpen(files[i]);
650        else
651            reader[i] = inputUTF8(files[i]);
652        if (!reader[i]) {
653            fprintf(stderr, "Unable to open %s\n", files[i]);
654            return 1;
655        }
656    }
657
658    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
659    printf("<osm version=\"0.5\" generator=\"OpenStreetMap planet.rb\">\n");
660    // Ensure generator string length matches original planet.rb
661    // to allow 'cmp -l' to be run on output to verify correctness
662    //printf("<osm version=\"0.5\" generator=\"OSM planetpatch tool---\">\n");
663
664    ret = xmlTextReaderRead(reader[1]);
665
666    while (ret == 1) {
667        processPatchNode();
668        ret = xmlTextReaderRead(reader[1]);
669    }
670
671    if (ret != 0) {
672        fprintf(stderr, "%s : failed to parse patch\n", files[1]);
673        return ret;
674    }
675
676    // Displaying any trailer from input
677    while ((current[0] != eof) && reader[0]) {
678        displayPatch(0);
679        getobject(0);
680    }
681
682    printf("</osm>\n");
683
684    xmlFreeTextReader(reader[0]);
685    xmlFreeTextReader(reader[1]);
686    return 0;
687}
688
689
690static void usageDiff(const char *arg0)
691{
692    fprintf(stderr, "Usage:\n\t%s planet1.osm planet2.osm > planet.diff\n\nGenerates a difference file between the two input planet.osm files.\nThis binary is based on SVN revision: $Rev: 11715 $.\n", arg0);
693    exit(1);
694}
695
696static int mainDiff(int argc, char *argv[], const char *name)
697{
698    int i;
699
700    if (argc != 3) {
701        usageDiff(name);
702        exit(1);
703    }
704
705    for (i=0; i<2; i++) {
706        files[i] = argv[i+1];
707        initList(&tags[i]);
708        initList(&nds[i]);
709        initList(&attrs[i]);
710        initList(&member_nodes[i]);
711        initList(&member_ways[i]);
712        initList(&member_rels[i]);
713    }
714
715    if (diffFiles() != 0)
716        exit(2);
717
718    return 0;
719}
720
721static void usagePatch(const char *arg0)
722{
723    fprintf(stderr, "Usage:\n\t%s planet.osm delta.xml > out.osm\n\n", arg0);
724    fprintf(stderr, "Applies the differences listed in delta.xml to the input planet.osm\n");
725    exit(1);
726}
727
728static int mainPatch(int argc, char *argv[], const char *name)
729{
730    int i;
731
732    if (argc != 3) {
733        usagePatch(name);
734        exit(1);
735    }
736
737    for (i=0; i<2; i++) {
738        files[i] = argv[i+1];
739        initList(&tags[i]);
740        initList(&nds[i]);
741        initList(&attrs[i]);
742        initList(&member_nodes[i]);
743        initList(&member_ways[i]);
744        initList(&member_rels[i]);
745    }
746
747    if (patchFiles() != 0)
748        exit(2);
749
750    return 0;
751}
752
753int main(int argc, char *argv[])
754{
755    int r;
756    const char *name;
757    LIBXML_TEST_VERSION
758
759    name = strrchr(argv[0], '/');
760
761    if (!name)
762        name = strrchr(argv[0], '\\');
763
764    if (name)
765        name++;
766    else
767        name = argv[0];
768
769    if (!strcmp(name, "planetdiff"))
770            r = mainDiff(argc, argv, name);
771    else if (!strcmp(name, "planetpatch"))
772            r = mainPatch(argc, argv, name);
773    else {
774        fprintf(stderr, "Usage error - should be called as planetdiff or planetpatch (not '%s')\n", name);
775        exit(1);
776    }
777
778    xmlCleanupParser();
779    xmlMemoryDump();
780#ifdef VERBOSE
781    fprintf(stderr, "\n");
782#endif
783    return r;
784}
Note: See TracBrowser for help on using the repository browser.