source: subversion/applications/utils/sanitize/sanitize.pl @ 29923

Last change on this file since 29923 was 5531, checked in by joerg, 12 years ago

.../*.pl: remove @INC lines to reduce modules taken from svn instead of installed system

  • Property svn:executable set to *
File size: 18.2 KB
RevLine 
[1400]1#!/usr/bin/perl
2# Copyright (C) 2006, and GNU GPL'd, by Joerg Ostertag
3
4my $VERSION ="check_osm.pl (c) Joerg Ostertag
5Initial Version (Sept,2006) by Joerg Ostertag <sanitize-openstreetmap\@ostertag.name>
6Version 0.01
7";
8
9BEGIN {
10    my $dir = $0;
11    $dir =~s,[^/]+/[^/]+$,,;
[5078]12    unshift(@INC,"$dir/perl_lib");
13    unshift(@INC,"../perl_lib");
[1400]14}
15
16
17use strict;
18use warnings;
19
20use File::Basename;
21use File::Copy;
22use File::Path;
23use Getopt::Long;
24use IO::File;
25use POSIX qw(ceil floor);
26use Pod::Usage;
27
28use Utils::File;
29use Utils::Math;
30use Utils::Debug;
31use Utils::LWP::Utils;
32use Geo::OSM::Write;
33use Data::Dumper;
34use XML::Parser;
35
36sub read_osm_file($); # {}
37sub check_Data(); #{ }
38
39my ($man,$help);
40
41our ($lat_min,$lat_max,$lon_min,$lon_max) = (0,0,0,0);
42
43our $osm_file ='-';     # The complete osm Filename (including path) for reading
44our $osm_out_file ="-"; # if needed, the pure filename of the osm output File
45our $do_redeem_lonesome_nodes = 0;
46
47our $OSM;
48
49# Set defaults and get options from command line
50Getopt::Long::Configure('no_ignore_case');
51GetOptions ( 
52             'debug+'        => \$DEBUG,     
53             'd+'            => \$DEBUG,     
54             'verbose+'      => \$VERBOSE,
55             'MAN'           => \$man, 
56             'man'           => \$man, 
57             'h|help|x'      => \$help, 
58
59             'osm-file=s'    => \$osm_file,
60             'osm-out-file'  => \$osm_out_file,
61             'redeem-lonesome-nodes' => \$do_redeem_lonesome_nodes,
62             )
63    or pod2usage(1);
64
65pod2usage(1) if $help;
66pod2usage(-verbose=>2) if $man;
67
68if ( $VERBOSE || $DEBUG ) {
69    print STDERR "\nChecking\n";
70}
71# Empty out global Variables for next loop
72$OSM->{nodes}    = {};
73$OSM->{segments} = {};
74$OSM->{ways}     = {};
75$OSM->{tool} = "OpenStreetMap Sanitizer";
76#$OSM->{tool} = "JOSM";
77
78my $start_time=time();
79
80read_osm_file( $osm_file );
81
82#  check openstreetmap data in Memory
83check_Data();
84
85#print STDERR Dumper(\$OSM);
86Geo::OSM::Write::write_osm_file($osm_out_file,$OSM);
87
88if ( $VERBOSE || $DEBUG ) {
89    printf STDERR "Check Completed for $osm_file in %.0f sec\n\n",time()-$start_time;
90}
91
92
93exit 0;
94
95##################################################################
96# Functions
97##################################################################
98
99our ($MainAttr,$Tags, $WaySegments);
100
101# Function is called whenever an XML tag is started
102#----------------------------------------------
103sub DoStart() {
104    my ($expat, $name, %attr) = @_;
105   
106    if($name eq "node"){
107        $Tags={};
108        %{$MainAttr} = %attr;
109    }
110    if($name eq "segment"){
111        $Tags={};
112        %{$MainAttr} = %attr;
113    }
114    if($name eq "way"){
115        $Tags={};
116        $WaySegments=[];
117        %{$MainAttr} = %attr;
118    }
119    if($name eq "tag"){
120        # TODO: protect against id,from,to,lat,long,etc. being used as tags
121        $Tags->{$attr{"k"}} = $attr{"v"};
122    }
123    if($name eq "seg"){
124        my $id = $attr{"id"};
125        push(@{$WaySegments}, $id);
126    }
127}
128
129# Function is called whenever an XML tag is ended
130#----------------------------------------------
131sub DoEnd(){
132    my ($expat, $element) = @_;
133    my $id = $MainAttr->{"id"};
134    if($element eq "node"){
135        my $node={};
136        %{$node} = %{$MainAttr};
137        $node->{tag}=$Tags;
138        $OSM->{nodes}->{$id} = $node;
139        #print "Node:".join(",",keys(%{$Tags}))."\n" if(scalar(keys(%{$Tags}))>0);
140    }
141
142    if($element eq "segment"){
143        my $segment={};
144        %{$segment} = %{$MainAttr};
145        $segment->{tag} = $Tags;
146        $OSM->{segments}->{$id}=$segment;
147    }
148
149    if($element eq "way"){
150        my $way={};
151        $way->{seg} = $WaySegments;
152        $way->{tag} = $Tags;
153        $OSM->{ways}->{$id} = $way;
154    }
155
156}
157
158# Function is called whenever text is encountered in the XML file
159#----------------------------------------------
160sub DoChar(){
161    my ($expat, $String) = @_;
162}
163
164############################################
165# -----------------------------------------------------------------------------
166sub read_osm_file($) { # Insert Streets from osm File
167    my $file_name = shift;
168
169    my $start_time=time();
170
171    print "Read OSM Data\n" if $VERBOSE || $DEBUG;
172
173    print STDERR "Parsing file: $file_name\n" if $DEBUG;
174    my $p = XML::Parser->new( Handlers => {
175        Start => \&DoStart, 
176        End => \&DoEnd, 
177        Char => \&DoChar},
178                              ErrorContext => 10,
179                              );
180   
181    my $fh = data_open($file_name);
182    die "Cannot open OSM File $file_name\n" unless $fh;
183    eval {
184        $p->parse($fh);
185    };
186    print "\n" if $DEBUG || $VERBOSE;
187    if ( $VERBOSE) {
188        printf "Read and parsed $file_name in %.0f sec\n",time()-$start_time;
189    }
190    if ( $@ ) {
191        warn "$@Error while parsing\n $file_name\n";
192        return;
193    }
194    if (not $p) {
195        warn "WARNING: Could not parse osm data\n";
196        return;
197    }
198
199
200    return;
201}
202
203
204# Distance between 2 points
205sub p2p_distance($$){
206    my $p1 = shift;
207    my $p2 = shift;
208    my $delta_lat=abs( $p1->{lat} - $p2->{lat} );
209    my $delta_lon=abs( $p1->{lon} - $p2->{lon} );
210    my $dist = sqrt($delta_lat*$delta_lat+$delta_lon*$delta_lon)*40000/360;
211    return $dist;
212}
213
214
215sub adjust_bounding_box($$$){
216    my $bbox = shift;
217    my $lat = shift;
218    my $lon = shift;
219
220    for my $type ( qw(lat_min lat_max lon_min lon_max lat lon )) {
221        next if defined ($bbox->{$type});
222        if ( $type =~m/min/ ) {
223            $bbox->{$type} = 1000;
224        } else {
225            $bbox->{$type} = -1000;
226        }
227    }
228    # remember lat/lon Min/Max
229    $bbox->{lat_min}= min($bbox->{lat_min},$lat);
230    $bbox->{lat_max}= max($bbox->{lat_max},$lat);
231    $bbox->{lon_min}= min($bbox->{lon_min},$lon);
232    $bbox->{lon_max}= max($bbox->{lon_max},$lon);
233    $bbox->{lat}= ($bbox->{lat_min}+$bbox->{lat_max})/2;
234    $bbox->{lon}= ($bbox->{lon_min}+$bbox->{lon_max})/2;
235}
236
237sub adjust_bounding_box_for_segment($$){
238    my $bbox = shift;
239    my $segment = shift;
240    $segment = $OSM->{segments}->{$segment}
241    if $segment =~ m/^\d+$/;
242    my $node_from = $segment->{from};
243    my $node_to   = $segment->{to};
244    my $lat1 = $OSM->{nodes}->{$node_from}->{lat};
245    my $lon1 = $OSM->{nodes}->{$node_from}->{lon};
246    my $lat2 = $OSM->{nodes}->{$node_to}->{lat};
247    my $lon2 = $OSM->{nodes}->{$node_to}->{lon};
248    adjust_bounding_box($bbox,$lat1,$lon1);
249    adjust_bounding_box($bbox,$lat2,$lon2);
250}
251
252# ------------------------------------------------------------------
253sub tag_string($){
254    my $obj = shift;
255    my $tags      = $obj->{tag};
256    my $tags_string = join(" ",map{" $_:$tags->{$_} "}keys %$tags);
257    my $erg ='';
258    $erg = " Tags[$tags_string] " if $tags_string;
259    return $erg;
260}
261
262# ------------------------------------------------------------------
263# Create Link to a Node/segment/way
264sub link_to_obj($){
265    my $obj = shift;
266
267    my $bbox = { lat_min => 1000, lat_max => -1000,
268                 lon_min => 1000, lon_max => -1000};
269
270    my $type = "unknown type ";
271    my $obj_txt = "";
272    if ( defined($obj->{lat})) {          # Node
273        $type = "Node";
274        $obj_txt = "Node ".$obj->{id}." (".$obj->{lat}.",".$obj->{lon}.")";
275        adjust_bounding_box($bbox,$obj->{lat},$obj->{lon});
276    } elsif ( defined($obj->{from})) {    # Segment
277        $type = "Segment";
278        $obj_txt = " ";
279        #Segment ".$obj->{id}." (Node ".$obj->{from}." --> Node ".$obj->{to}.")";
280        adjust_bounding_box_for_segment($bbox,$obj);
281    } elsif ( defined($obj->{seg})) {     # Way
282        $type = "Way";
283        $obj_txt = "Way: $obj->{id}<br>\n";
284        $obj_txt .= "Segments[";
285        for my $seg_id ( @{$obj->{seg}} ) {
286            $obj_txt .= "$seg_id,";
287            next unless $seg_id;
288            next unless defined $OSM->{segments}->{$seg_id};
289            adjust_bounding_box_for_segment($bbox,$seg_id);
290        }
291        $obj_txt .= "]";
292    } else {
293        print "link_to_obj: unrecognized type for  ".Dumper(\$obj);
294    };
295
296    my $erg='';
297    my $lat = ($bbox->{lat_min} + $bbox->{lat_max})/2;
298    my $lon = ($bbox->{lon_min} + $bbox->{lon_max})/2;
299    my $osm_link = "<A target=\"map\" href=\"http://www.openstreetmap.org/index.html?";
300
301    if ( $obj_txt ){
302        $erg .= $obj_txt;
303    } else {
304        $erg .= "$type: ".Dumper(\$obj);
305    };
306    $erg .= "<font size=-4>".tag_string($obj)."</font>";
307    $erg .= "\n";
308    if ( $type ne "Node" ) {
309        $erg .= "<td>".link_download_josm($bbox). "</td>";
310        #$erg .= "Bounding Box:\n";
311        #$erg .= sprintf( "(%6.5f,%6.5f) "  ,$bbox->{lat_min},$bbox->{lon_min});
312        #$erg .= sprintf( "(%6.5f,%6.5f)\n" ,$bbox->{lat_max},$bbox->{lon_max});
313    }
314
315
316    $erg .= "<td>";
317    $erg .= "Map($type) with Zoom: ";
318    for my $zoom ( qw( 10 15 20 24 )) {
319        $erg .= $osm_link . "lat=$lat&lon=$lon&zoom=$zoom\">$zoom</a> ";
320    }
321    $erg .= "</td>";
322    return $erg;
323}
324
325# ------------------------------------------------------------------
326# Link to OSM view of Node
327sub link_node($){
328    my $node = shift;
329    $node = $OSM->{nodes}->{$node} if $node =~ m/^\d+$/;
330
331    my $osm_link = "<A target=\"map\" href=\"http://www.openstreetmap.org/index.html?";
332    return $osm_link . "lat=$node->{lat}&lon=$node->{lon}&zoom=23\">";
333}
334
335sub link_download_josm($){
336    my $bbox = shift;
337    my $box = "$bbox->{lat_min},$bbox->{lon_min},$bbox->{lat_max},$bbox->{lon_max}";
338    my $erg .= "<font size=-3>josm&nbsp;--download=";
339    $erg .= sprintf( "%6.5f,%6.5f,"  ,$bbox->{lat_min}-0.001,$bbox->{lon_min}+0.001);
340    $erg .= sprintf( "%6.5f,%6.5f\n" ,$bbox->{lat_max}-0.001,$bbox->{lon_max}+0.001);
341    $erg .= "</font>";
342    return $erg; 
343}
344
345# ------------------------------------------------------------------
346sub check_osm_segments() { # Insert Streets from osm variables into mysql-db for gpsdrive
347    my $start_time=time();
348
349    if ( $VERBOSE || $DEBUG ) {
350        print STDERR "------------ Check Segments\n";
351    }
352
353    my $max_dist=0;
354    my $sum_dist=0;
355    my $min_dist=99999999999999;
356
357    my $counted_segmtnts=0;
358    for my $seg_id (  keys %{$OSM->{segments}} ) {
359        my $segment = $OSM->{segments}->{$seg_id};
360        my $node_from = $segment->{from};
361        my $node_to   = $segment->{to};
362
363
364        print "Undefined Node From for Segment $seg_id:".Dumper(\$segment)."\n"
365            unless $node_from;
366        print "Undefined Node To   for Segment $seg_id:".Dumper(\$segment)."\n"
367            unless $node_to;
368        $OSM->{nodes}->{$node_from}->{connections}++;
369        $OSM->{nodes}->{$node_to}->{connections}++;
370
371        my $lat1 = $OSM->{nodes}->{$node_from}->{lat};
372        my $lon1 = $OSM->{nodes}->{$node_from}->{lon};
373        my $lat2 = $OSM->{nodes}->{$node_to}->{lat};
374        my $lon2 = $OSM->{nodes}->{$node_to}->{lon};
375        my $dist = p2p_distance($OSM->{nodes}->{$node_from},$OSM->{nodes}->{$node_to});
376        $segment->{tag}->{distance}=$dist;
377
378        if ( $node_from &&  $node_to && 
379             $node_from == $node_to ) {
380            $segment->{tag}->{error} .= "from == to";
381            next;
382        }
383
384        if ( $lat1 == $lat2 && $lon1 == $lon2 ) {
385            $segment->{tag}->{error} .= "no distance";
386            next;
387        } 
388       
389        if ( $dist > 8 ) {
390            $segment->{tag}->{error} .= "long distance $dist Km,";
391        } 
392        my $dist_meters = $dist*1000;
393        if ( $dist_meters < 10.1 ) {
394            $segment->{tag}->{error} .= sprintf("small distance %.2f Km,",$dist);
395        } 
396    }
397
398    if ( $VERBOSE) {
399        printf "Checked OSM Segments in %.0f sec\n",time()-$start_time;
400    }
401}
402
403# ----------------------------------------------------------------------------------------
404sub check_osm_nodes() { 
405    my $start_time=time();
406   
407    if ( $VERBOSE || $DEBUG ) {
408        print STDERR "------------ Check Nodes\n";
409    }
410    my $node_positions={};
411    for my $node_id (  keys %{$OSM->{nodes}} ) {
412        my $node = $OSM->{nodes}->{$node_id};
413        my $lat = $node->{lat};
414        my $lon = $node->{lon};
415        push(@{$node_positions->{"$lat,$lon"}},$node_id);
416        if (abs($lat)>90) {
417            print "Node $node_id: lat: $lat out of range\n";
418        }
419        if (abs($lon)>180) {
420            print "Node $node_id: lon: $lon out of range\n";
421        }
422        if ( !$node->{connections} ) {
423            my $tag_string = tag_string($node);
424            $tag_string =~ s/Tags\[\s*//g;
425            $tag_string =~ s/\s*\]\s*//g;
426            $tag_string =~ s/class:(node|trackpoint)//g;
427            $tag_string =~ s/created_by:JOSM//g;
[1545]428            $tag_string =~ s/converted_by:Track2osm//g;
[1400]429            $tag_string =~ s/editor:osmpedit-svn//g;
430            $tag_string =~ s/editor:osmpedit//g;
431            $tag_string =~ s/\s*//g;
432            if ($tag_string =~ m/class:viewpoint/ ) {
433            } elsif ($tag_string =~ m/class:village/ ) {
434            } elsif ($tag_string =~ m/class:point of interest/ ) {
435            } elsif ($tag_string) {
436                if (0){
437                    print "Node $node_id is not connected, but has Tag: ";
438                    print tag_string($node)." ";
439                    print " ($tag_string) ";
440                    print "\n";
441                }
442            } else {
443                $node->{tag}->{error} .= "node-no-connections";
444                if ( $do_redeem_lonesome_nodes ) {
445                    delete $OSM->{nodes}->{$node_id};
446                }
447            }
448        }
449    }
450
451    my $count;
452    for my $position ( keys %{$node_positions} ) {
453        my $nodes = $node_positions->{$position};
454        my $node0=$OSM->{nodes}->{$nodes->[0]};
455        my $node_lat = $node0->{lat};
456        my $node_lon = $node0->{lon};
457        my $lat = floor($node_lat/5)*5;
458        my $lon = floor($node_lon/5)*5;
459        $count->{"node-$lat-$lon"}->{points}++;
460        if ( @{$nodes} > 1 ) {
461            $count->{"node-$lat-$lon"}->{err}++;
462            my $link = link_node($node0);
463            #html_out("node","<td>");
464            for my $node ( @{$nodes} ) {
465                #xml_out( "node-duplicate",$node);
466                #html_out("node",
467                #sprintf( "%s ( %d Seg, %s )<br>", $node, $OSM->{nodes}->{$node}->{connections}||0 ,
468                #tag_string($OSM->{nodes}->{$node})
469                #));
470            }
471            #html_out("node","</td></tr>");
472        }
473    }
474
475    if ( $VERBOSE) {
476        printf "Checked OSM Segments in %.0f sec\n",time()-$start_time;
477    }
478}
479
480# ----------------------------------------------------------------------------------------
481sub check_osm_ways() { 
482    my $start_time=time();
483
484    if ( $VERBOSE || $DEBUG ) {
485        print STDERR "------------ Check Ways\n";
486    }
487
488    # ---------------------- Undefined segments in way
489    for my $way_id ( keys %{$OSM->{ways}} ) {
490        my $way = $OSM->{ways}->{$way_id};
491       
492        for my $seg_id ( @{$way->{seg}} ) {
493            if ( ! defined $OSM->{segments}->{$seg_id} ){
494                $way->{tag}->{error} .= "way-undef-segment $seg_id";
495                next;
496            }
497            $OSM->{segments}->{$seg_id}->{referenced_by_way} ++;
498        }
499    }
500
501    # ---------------------- No Segments in way
502    my $segments_per_way={};
503    my $distance_per_way={};
504    #html_out("way","<h2>No Segments in way</h2>\n<ul>");
505    my $way_tags={};
506    for my $way_id (  keys %{$OSM->{ways}} ) {
507        my $way = $OSM->{ways}->{$way_id};
508
509        for my $k ( keys %{$way->{tag}} ) {
510            $way_tags->{$k}->{count}++;
511            $way_tags->{$k}->{values}->{$way->{tag}->{$k}} ++;
512        }
513
514        if ( ! defined ( $way->{seg} )) {
515            #html_out("way","<li>Way  $way_id has no segments\n".
516            #link_to_obj($way)
517            #);
518            next;
519        }
520        if ( ! defined ( $way->{tag} )) {
521            #html_out("way-no-tags","<li>Way  $way_id has no tags\n".
522            #link_to_obj($way)
523            #);
524        } else {
525            my $tag=$way->{tag};
526            if ( ! defined ( $tag->{name} )) {
527                #html_out("way-no-name","<li>Way $way_id has no name\n".
528                #link_to_obj($way)
529                #);
530            }
531        }
532        my $count = scalar @{$way->{seg}};
533        $count = int($count/10)*10;
534        #$count = "$count - ".($count+9);
535        $segments_per_way->{$count} ++;
536
537        my $distance=0;
538        for my $seg_id ( @{$way->{seg}} ) {
539            next unless defined ($OSM->{segments}->{$seg_id});
540            $distance +=  ($OSM->{segments}->{$seg_id}->{distance}||0);
541        }
542       
543        $distance = int($distance/10)*10;
544        #$distance = "$distance - ".($distance+9);
545        $distance_per_way->{$distance}++;
546    }
547    #html_out("way","</ul>");
548
549    # --------------------------
550    #html_out("statistics-ways", "<h3>Number of Segments in Ways</h3>");
551    #html_out("statistics-ways", "<table>");
552    #html_out("statistics-ways", "<tr><th></th> <th></th> </tr>");
553    for my $number_of_segments ( sort { $a <=> $b }  keys %{$segments_per_way} ) {
554        my $number_of_ways = $segments_per_way->{$number_of_segments};
555        #html_out("statistics-ways", "<tr>");
556        #html_out("statistics-ways", "<td>"."$number_of_ways  </td><td>ways with </td>");
557        #html_out("statistics-ways", "<td>$number_of_segments - ".($number_of_segments+9)." </td><td> Segments </td>\n");
558        #html_out("statistics-ways", "<tr>");
559    }
560    #html_out("statistics-ways", "</table>");
561
562}
563
564
565
566
567# *****************************************************************************
568sub check_Data(){
569    # Checks and statistics
570    check_osm_segments();
571    check_osm_nodes();
572    check_osm_ways();
573
574}
575
576
577##################################################################
578# Usage/manual
579
580__END__
581
582=head1 NAME
583
584B<check_osm.pl> Version 0.01
585
586=head1 DESCRIPTION
587
588B<sanitize.pl> is a program to check osm data and make sanitizing suggestions.
589Data from Openstreetmap
590
591This Programm is completely experimental, but for some cases it
592can already be used.
593
594So: Have Fun, improve it and send me fixes :-))
595
596=head1 SYNOPSIS
597
598B<Common usages:>
599
600sanitize.pl [-d] [-v] [-h] [--redeem-lonesome-nodes] [--osm-file=file.osm]  [--osm-out-file=out-file.osm]
601
602=head1 OPTIONS
603
604=over 2
605
606=item B<--man> Complete documentation
607
608Complete documentation
609
610=item B<--osm-file=path/data.osm>
611
612Select the "path/data.osm" file to use for the checks
613
614=item B<--redeem-lonesome-nodes>
615
616Redeem lonesome Nodes. This means, if the node is not
617connected to anywhere and has no tags it is deleted.
618
619=item B<--osm-file=file.osm>
620
621Input Filename. Default is standardin.
622
623=item B<--osm-out-file=out-file.osm>
624
625Output Filename. Default is standardout.
626
627=head1 JOSM
628
629I you put a File named ~/.josm/external_tools with the following
630content you can access it with the tools menu inside josm.
631
[1545]632 <tools>
[1400]633 <group name="sanitizer">
634  <tool
635    name="Tweety's OSM sanitizer (All)"
636    exec="/usr/bin/perl /home/tweety/bin/sanitize.pl"
637    in="selection"
638    flags="include_references,include_backreferences"
639    out="replace">
640  </tool>
641  <tool
642    name="Tweety's OSM Sanitizer (Selection)"
643    exec="/usr/bin/perl /home/tweety/bin/sanitize.pl"
644    in="selection"
645    flags="include_references,include_backreferences"
646    out="replace">
647  </tool>
648  <tool
649    name="Tweety's OSM Sanitizer (Selection,redeem-lonesome-nodes)"
650    exec="/usr/bin/perl /home/tweety/bin/sanitize.pl --redeem-lonesome-nodes"
651    in="selection"
652    flags="include_references,include_backreferences"
653    out="replace">
654  </tool>
655 </group>
[1545]656 </tools>
657=back
[1400]658
659
[1545]660=head1 COPYRIGHT
661
662Copyright 2006, Jörg Ostertag
663
664This program is free software; you can redistribute it and/or
665modify it under the terms of the GNU General Public License
666as published by the Free Software Foundation; either version 2
667of the License, or (at your option) any later version.
668
669This program is distributed in the hope that it will be useful,
670but WITHOUT ANY WARRANTY; without even the implied warranty of
671MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
672GNU General Public License for more details.
673
674You should have received a copy of the GNU General Public License
675along with this program; if not, write to the Free Software
676Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
677
678=head1 AUTHOR
679
680Jörg Ostertag (sanitize-for-openstreetmap@ostertag.name)
681
682=head1 SEE ALSO
683
684http://www.openstreetmap.org/
685
686=cut
Note: See TracBrowser for help on using the repository browser.