├── .gitignore ├── xml2csv.pl ├── README.md ├── stats.pl ├── poi.pl ├── wiki.pl └── EDetect.pm /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.gz 3 | *.txt 4 | -------------------------------------------------------------------------------- /xml2csv.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | 5 | my @data; # 0:changeset_id, 1:uid, 2:date, 3:num_changes, 4:num_comments, 5:created_by 6 | while(<>) { 7 | if (//g; 18 | $data[5] =~ s/&/&/g; 19 | $data[5] =~ s/"/'/g; 20 | $data[5] =~ s/,/;/; 21 | } 22 | if (/<\/changeset>/ || //) { 23 | print join(',', @data)."\n"; 24 | undef @data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calculating Editor Usage Stats 2 | 3 | This set of scripts was made for updating the [Editor Usage Stats](https://wiki.openstreetmap.org/wiki/Editor_usage_stats) page 4 | on the OpenStreetMap Wiki. It shows number of changesets, edits and unique users for every popular OSM editor. 5 | 6 | ## Usage 7 | 8 | Download a changeset bz2 file from [planet.osm.org](https://planet.openstreetmap.org/). Then run: 9 | 10 | curl -L | bzcat | ./xml2csv.pl | gzip > changesets.csv.gz 11 | 12 | > (Script xml2csv.pl take approximately 30mn on a regular computer) 13 | 14 | After that use `stats.pl` for calculating stats for a given year or month. By default it 15 | prints statistics for the current year. Specify an optional parameter for filtering dates: 16 | 17 | gzip -dc changesets.csv.gz | ./stats.pl 2015 > 2015.lst 18 | 19 | > (Script stats.pl take approximately 2mn on a regular computer) 20 | 21 | For getting stats for year 2015. You can get stats for a specific month by using e.g. `2016-07` as a parameter. 22 | 23 | ## Other Scripts 24 | 25 | *Wiki.pl* prepares a wiki page for publishing to [Editor Usage Stats](https://wiki.openstreetmap.org/wiki/Editor_usage_stats). 26 | 27 | *Poi.pl* downloads daily replication diffs for a year and prepares statistics on how many 28 | POI were added or modified with major editors. 29 | 30 | ## Author and License 31 | 32 | These scrips were written by Ilya Zverev and published under WTFPL. 33 | -------------------------------------------------------------------------------- /stats.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use File::Basename; 5 | use lib dirname (__FILE__); 6 | use EDetect qw(editor); 7 | 8 | sub nf { 9 | my $n = shift; 10 | $n =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1 /g; 11 | return $n; 12 | } 13 | 14 | my @gmt = gmtime; 15 | my $date = $ARGV[0] || $gmt[5] + 1900; 16 | shift; 17 | 18 | my %changesets; 19 | my %users; 20 | my %edits; 21 | my $sum_changesets = 0; 22 | my %all_users; 23 | my $sum_edits = 0; 24 | my %other; 25 | 26 | while(<>) { 27 | chomp; 28 | # 0:changeset_id, 1:uid, 2:date, 3:num_changes, 4:num_comments, 5:created_by 29 | my @data = split /,/; 30 | next if substr($data[2], 0, length($date)) ne $date; 31 | my $editor = editor($data[5] || ''); 32 | $other{$data[5]} += 1 if $editor eq 'Other'; 33 | $changesets{$editor}++; 34 | $sum_changesets++; 35 | $edits{$editor} += $data[3] || 0; 36 | $sum_edits += $data[3] || 0; 37 | $users{$editor} = {} if !exists($users{$editor}); 38 | $users{$editor}->{$data[1]} = (); 39 | $all_users{$data[1]} = (); 40 | } 41 | 42 | print "By Changesets:\n"; 43 | printf "%s: %s (%.1f%%)\n", $_, nf($changesets{$_}), 100.0*$changesets{$_}/$sum_changesets for sort { $changesets{$b} <=> $changesets{$a} } keys %changesets; 44 | 45 | print "\nBy Users:\n"; 46 | printf "%s: %s (%.1f%%)\n", $_, nf(scalar keys %{$users{$_}}), 100.0*scalar(keys %{$users{$_}})/scalar(keys %all_users) for sort { keys %{$users{$b}} <=> keys %{$users{$a}} } keys %users; 47 | 48 | print "\nBy Edits:\n"; 49 | printf "%s: %s (%.1f%%)\n", $_, nf($edits{$_}), 100.0*$edits{$_}/$sum_edits for sort { $edits{$b} <=> $edits{$a} } keys %edits; 50 | 51 | print "\nOther Editors:\n"; 52 | printf "%d %s\n", $other{$_}, $_ for sort { $other{$b} <=> $other{$a} } keys %other; 53 | -------------------------------------------------------------------------------- /poi.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use File::Basename; 5 | use lib dirname (__FILE__); 6 | use EDetect qw(editor); 7 | use IO::Uncompress::Gunzip; 8 | use XML::LibXML::Reader qw( XML_READER_TYPE_ELEMENT XML_READER_TYPE_END_ELEMENT ); 9 | 10 | my $year = 2016; 11 | my @editors = ('JOSM', 'iD', 'Potlatch 2', 'MAPS.ME', 'Other'); 12 | my %heditors = map { $_ => undef } @editors; 13 | my %poi_keys = map { $_ => undef } qw(amenity tourism historic shop craft emergency office); 14 | my $curl = 'curl'; 15 | my $replication_base = 'https://planet.openstreetmap.org/replication/day'; 16 | 17 | # Read last replication state index 18 | 19 | my $state; 20 | { 21 | local $/ = undef; 22 | open FH, "$curl -s $replication_base/state.txt|" or die "Failed to open state.txt: $!"; 23 | my $state_str = ; 24 | close FH; 25 | die "I don't see a state in state.txt" if $state_str !~ /sequenceNumber=(\d+)/; 26 | $state = $1; 27 | } 28 | $state -= 20; # Go back in time 20 days, since the list of changesets is obsolete 29 | 30 | # Build a hash for changeset editors 31 | 32 | print STDERR "Reading editors for changesets\n"; 33 | my %changesets; 34 | while(<>) { 35 | chomp; 36 | # 0:changeset_id, 1:uid, 2:date, 3:num_changes, 4:num_comments, 5:created_by 37 | my @data = split /,/; 38 | next if substr($data[2], 0, 4) < $year; 39 | my $editor = editor($data[5] || ''); 40 | $changesets{$data[0]} = exists $heditors{$editor} ? $editor : 'Other'; 41 | } 42 | 43 | # Download day replication diffs until we're out of year 44 | 45 | my %results; 46 | my $found_year = 1; 47 | sub process_osc; 48 | while($found_year) { 49 | my $osc_url = $replication_base.sprintf("/%03d/%03d/%03d.osc.gz", int($state/1000000), int($state/1000)%1000, $state%1000); 50 | print STDERR "Downloading $osc_url\n"; 51 | open FH, "$curl -s $osc_url|" or die "Failed to open: $!"; 52 | $found_year = process_osc(new IO::Uncompress::Gunzip(*FH)); 53 | close FH; 54 | $state--; 55 | } 56 | 57 | # Print results 58 | 59 | print 'Date,'.(join ',', map { "$_ new,$_ mod" } @editors)."\n"; 60 | for my $date (sort keys %results) { 61 | my @row = ('what', $date); 62 | for my $ed (@editors) { 63 | my $count = exists $results{$date}->{$ed} ? $results{$date}->{$ed} : [0, 0]; 64 | push @row, @$count; 65 | } 66 | print join(',', @row)."\n"; 67 | } 68 | 69 | sub register_poi { 70 | my ($time, $changeset, $isnew) = @_; 71 | return if length($time) < 10; 72 | return if !exists $changesets{$changeset}; 73 | my $k = substr($time, 0, 10); 74 | my $editor = $changesets{$changeset}; 75 | $results{$k} = {} if !exists $results{$k}; 76 | $results{$k}->{$editor} = [0, 0] if !exists $results{$k}->{$editor}; 77 | $results{$k}->{$editor}->[$isnew ? 0 : 1]++; 78 | } 79 | 80 | sub process_osc { 81 | my $handle = shift; 82 | my $r = XML::LibXML::Reader->new(IO => $handle); 83 | my $state = 0; # 0 = root, 1 = create, 2 = modify 84 | my $found_year = 0; 85 | my $poic = 0; 86 | while ($r->read) { 87 | if ($r->nodeType == XML_READER_TYPE_ELEMENT) { 88 | if ($r->name eq 'create') { 89 | $state = 1; 90 | } elsif ($r->name eq 'modify') { 91 | $state = 2; 92 | } elsif ($state && ($r->name eq 'node' || $r->name eq 'way')) { 93 | my $type = $r->name; 94 | my $changeset = $r->getAttribute('changeset'); 95 | my $time = $r->getAttribute('timestamp'); 96 | my $timeok = substr($time, 0, 4) >= $year; 97 | $found_year = 1 if $timeok; 98 | my $ispoi = 0; 99 | while ($r->read) { 100 | last if $r->nodeType == XML_READER_TYPE_END_ELEMENT && $r->name eq $type; 101 | if ($r->nodeType == XML_READER_TYPE_ELEMENT && $r->name eq 'tag') { 102 | my $k = $r->getAttribute('k'); 103 | $ispoi = 1 if exists $poi_keys{$k}; 104 | } 105 | } 106 | register_poi($time, $changeset, $state == 1) if $ispoi && $timeok; 107 | } 108 | } elsif( $r->nodeType == XML_READER_TYPE_END_ELEMENT ) { 109 | $state = 0; 110 | } 111 | } 112 | return $found_year; 113 | } 114 | -------------------------------------------------------------------------------- /wiki.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use File::Basename; 5 | use lib dirname (__FILE__); 6 | use EDetect qw(editor editor_wikilink); 7 | use Data::Dumper; 8 | 9 | my @gmt = gmtime; 10 | my @years = (2009..$gmt[5]+1900); 11 | 12 | sub nf { 13 | my ($value, $total) = @_; 14 | my $fmt = $value; 15 | $fmt =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1 /g; 16 | my $percent = $total ? 100.0 * $value / $total : 0.0; 17 | my $perc_str = $percent < 0.005 ? ' (~0%)' : sprintf $percent > 0.19 ? ' (%.1f%%)' : ' (%.2f%%)', $percent; 18 | return sprintf '%011d %11s%s', $value, $value ? $fmt : '-', 19 | ($value && $total ? $perc_str : ''); 20 | } 21 | 22 | sub extract { 23 | my ($dataref, $year, $editor, $extract_func) = @_; 24 | my $value = $editor ? $dataref->{$year}->{$editor} : $dataref->{$year}; 25 | return $extract_func ? $extract_func->($value) : $value; 26 | } 27 | 28 | sub print_editor { 29 | my ($name, $dataref, $totalref, $extract_func) = @_; 30 | print "|-\n"; 31 | print '| style="text-align:left" | '.editor_wikilink($name)."\n"; 32 | for my $year (@years) { 33 | my $value = 0; 34 | my $total = 0; 35 | if (exists $dataref->{$year}->{$name}) { 36 | $value = extract($dataref, $year, $name, $extract_func); 37 | $total = extract($totalref, $year, 0, $extract_func); 38 | } 39 | print '| '.nf($value, $total)."\n"; 40 | } 41 | } 42 | 43 | sub sort_editors { 44 | my ($dataref, $threshold, $allref, $extract_func) = @_; 45 | my $year = $years[-1]; 46 | my %values; 47 | $values{$_} = 0 for keys %{$allref}; 48 | $values{$_} = extract($dataref, $year, $_, $extract_func) for keys %{$dataref->{$year}}; 49 | $values{editor('-')} = -1; 50 | $values{editor('')} = -2; 51 | my %counts; 52 | $counts{$_} = 0 for keys %values; 53 | 54 | # Filter out editors that didn't match the threshold 55 | for my $editor (keys %values) { 56 | next if $values{$editor} < 0; 57 | my $count = 0; 58 | $count += extract($dataref, $_, $editor, $extract_func) || 0 for @years; 59 | $counts{$editor} = $count; 60 | if ($count < $threshold) { 61 | delete $values{$editor} if $count < $threshold; 62 | # Add its counters to 'Other' 63 | for my $y (@years) { 64 | # A hack to deal with users' list 65 | if ($extract_func) { 66 | $dataref->{$y}->{'Other'}->{$_} = () for keys %{$dataref->{$y}->{$editor}}; 67 | } else { 68 | $dataref->{$y}->{'Other'} += $dataref->{$y}->{$editor} || 0; 69 | } 70 | } 71 | } 72 | } 73 | return sort { $values{$b} <=> $values{$a} || $counts{$b} <=> $counts{$a} || $a cmp $b } keys %values; 74 | } 75 | 76 | sub cnt_uid { 77 | my $h = shift; 78 | return scalar keys %{$h}; 79 | } 80 | 81 | sub table_header { 82 | my $title = shift; 83 | print "==== $title ====\n\n"; 84 | print '{| class="wikitable sortable" style="text-align:right"'."\n"; 85 | print "|+ style=\"padding-bottom:.5em\" | $title\n"; 86 | print "|-\n"; 87 | print '! editor '; 88 | print " !! $_" for @years; 89 | print "\n"; 90 | } 91 | 92 | # Nested hashes: year => editor => count / list of uids 93 | my %changesets = map { $_ => {} } @years; 94 | my %users = map { $_ => {} } @years; 95 | my %edits = map { $_ => {} } @years; 96 | my %all_users = map { $_ => {} } @years; 97 | my %sum_changesets = map { $_ => 0 } @years; 98 | my %sum_edits = map { $_ => 0 } @years; 99 | my %all_editors; 100 | my %other_editors; 101 | my %other_editors_u; 102 | 103 | while(<>) { 104 | chomp; 105 | # 0:changeset_id, 1:uid, 2:date, 3:num_changes, 4:num_comments, 5:created_by 106 | my @data = split /,/; 107 | my $year = substr($data[2], 0, 4); 108 | next if !exists $changesets{$year}; 109 | my $editor = editor($data[5] || ''); 110 | $all_editors{$editor} = (); 111 | 112 | if ($editor eq 'Other') { 113 | $other_editors{$data[5]} += 1 + $data[3]; 114 | $other_editors_u{$data[5]} = {} if !exists $other_editors_u{$data[5]}; 115 | $other_editors_u{$data[5]}->{$data[1]} = (); 116 | } 117 | 118 | $changesets{$year}->{$editor}++; 119 | $sum_changesets{$year}++; 120 | $edits{$year}->{$editor} += $data[3] || 0; 121 | $sum_edits{$year} += $data[3] || 0; 122 | $users{$year}->{$editor} = {} if !exists $users{$year}->{$editor}; 123 | $users{$year}->{$editor}->{$data[1]} = (); 124 | $all_users{$year}->{$data[1]} = (); 125 | } 126 | 127 | #table_header('by number of changesets'); 128 | #print_editor($_, \%changesets, \%sum_changesets) for sort_editors(\%changesets, 10000, \%all_editors); 129 | #print "|-\n|}\n\n"; 130 | 131 | table_header('by number of users (distinct uids)'); 132 | print_editor($_, \%users, \%all_users, \&cnt_uid) for sort_editors(\%users, 500, \%all_editors, \&cnt_uid); 133 | print "|-\n|}\n\n"; 134 | 135 | table_header('by number of edits'); 136 | print_editor($_, \%edits, \%sum_edits) for sort_editors(\%edits, 300_000, \%all_editors); 137 | print "|-\n|}\n\n"; 138 | 139 | print "== Other Editors by users ==\n\n"; 140 | $other_editors_u{$_} = cnt_uid($other_editors_u{$_}) for keys %other_editors_u; 141 | for (sort { $other_editors_u{$b} <=> $other_editors_u{$a} } keys %other_editors_u) { 142 | printf "* %d %s\n", $other_editors_u{$_}, $_ if $other_editors_u{$_} >= 5; 143 | } 144 | print "== Other Editors by edits ==\n\n"; 145 | for (sort { $other_editors{$b} <=> $other_editors{$a} } keys %other_editors) { 146 | printf "* %d %s\n", $other_editors{$_}, $_ if $other_editors{$_} >= 1000; 147 | } 148 | -------------------------------------------------------------------------------- /EDetect.pm: -------------------------------------------------------------------------------- 1 | package EDetect; 2 | use strict; 3 | use warnings; 4 | 5 | use Exporter qw(import); 6 | 7 | our @EXPORT_OK = qw(editor editor_wikilink); 8 | 9 | sub editor { 10 | my $cr = shift; 11 | return 'Not Specified' if $cr eq ''; 12 | return 'JOSM Reverter' if $cr =~ /reverter_plugin\/\d{5}/; 13 | return 'JOSM' if $cr =~ /^(?:.+;)?JOSM/; 14 | return 'iD' if $cr =~ /^iD/; 15 | return 'Potlatch 0.x/1.x' if $cr =~ /^Potlatch [01]/; 16 | return 'Potlatch 2' if $cr =~ /^Potlatch 2/; 17 | return 'Potlatch 3' if $cr =~ /^Potlatch$/; 18 | return 'Merkaartor' if $cr =~ /^Merkaartor/; 19 | return 'Vespucci' if $cr =~ /^Vespucci/; 20 | return 'Go Map!!' if $cr =~ /^Go (?:Map|Kaart)/; 21 | return 'Pushpin' if $cr =~ /^Pushpin/; 22 | return 'OsmAnd' if $cr =~ /^Osm[Aa]nd/; 23 | return 'MAPS.ME' if $cr =~ /^MAPS\.ME/; 24 | return 'rosemary' if $cr =~ /^rosemary/; 25 | return 'rosemary' if $cr =~ /^wheelmap\.org/; 26 | return 'bulk_upload.py' if $cr =~ /^bulk_upload\.py/; 27 | return 'PythonOsmApi' if $cr =~ /PythonOsmApi/; 28 | return 'OSMPOIEditor' if $cr =~ /^POI\+/; 29 | return 'OSMPOIEditor' if $cr =~ /^OSMPOIEditor/; 30 | return 'Level0' if $cr =~ /^Level0/; 31 | return 'Osmose Editor' if $cr =~ /^Osmose Editor/; 32 | return 'RawEdit' if $cr =~ /^RawEdit/; 33 | return 'RawEdit' if $cr =~ /^Osmose Raw Editor/; 34 | return 'OsmHydrant' if $cr =~ /^OsmHydrant/; 35 | return 'iLOE' if $cr =~ /^iLOE/; 36 | return 'YAPIS' if $cr =~ /^YAPIS/; 37 | return 'Services_OSM' if $cr =~ /^Services_OpenStreetMap/; 38 | return 'ArcGIS' if $cr =~ /^ArcGIS Editor/; 39 | return 'osmapis' if $cr =~ /^osmapis/; 40 | return 'osmapi' if $cr =~ /^osmapi(?:\/.+)?$/; 41 | return 'FreieTonne' if $cr =~ /^FreieTonne/; 42 | return 'osmtools' if $cr =~ /^osmtools\//; 43 | return 'Redaction bot' if $cr =~ /^Redaction bot/; 44 | return 'upload.py' if $cr =~ /^(?:.*\/)?upload\.py/; 45 | return 'bulk_upload.py' if $cr =~ /^bulk_upload\.py/; 46 | return 'bulk_upload_sax.py' if $cr =~ /^bulk_upload_sax\.py/; 47 | return 'OpenMaps' if $cr =~ /^OpenMaps /; 48 | return 'Mapzen' if $cr =~ /^Mapzen /; 49 | return 'WebDRI' if $cr =~ /^WebDRI/; 50 | return 'KMLManager' if $cr =~ /^KMLManager/; 51 | return 'osmitter' if $cr =~ /^osmitter/; 52 | return 'COFFEEDEX 2002' if $cr =~ /^COFFEEDEX 2002/; 53 | return 'FindvejBot' if $cr =~ /^FindvejBot/; 54 | return 'Roy' if $cr =~ /^Roy /; 55 | return 'OSMPhpLib'if $cr =~ /^OSMPhpLib/; 56 | return 'Fulcrum' if $cr =~ /^Fulcrum /; 57 | return 'OSMapTuner' if $cr =~ /^OSMapTuner/; 58 | return 'QGIS' if $cr =~ /^QGIS /; 59 | return 'OpenSeaMap-Editor' if $cr =~ /^OpenSeaMap-Editor/; 60 | return 'BigTinCan' if $cr =~ /^BigTinCan /; 61 | return 'meta' if $cr =~ /^meta$/; 62 | return 'SviMik' if $cr =~ /^SviMik/; 63 | return 'My Opening Hours' if $cr =~ /^My Opening Hours/; 64 | return 'Sputnik.Ru.Adminka' if $cr =~ /^Sputnik\.Ru\.Adminka/; 65 | return 'OpenBeerMap' if $cr =~ /^OpenBeerMap/; 66 | return 'Kort' if $cr =~ /^Kort /; 67 | return 'Nomino' if $cr =~ /Nomino /; 68 | return 'Route4U' if $cr =~ /^Route4U/; 69 | return 'Ubiflow' if $cr =~ /^Ubiflow/; 70 | return 'streetkeysmv' if $cr =~ /^streetkeysmv/; 71 | return 'Grass and Green' if $cr =~ /^Grass_and_Green/; 72 | return 'fix.loggingroads.org' if $cr =~ /^fix\.loggingroads\.org/; 73 | return 'MapStalt Mini' if $cr =~ /^MapStalt Mini/; 74 | return 'osmupload.py' if $cr =~ /^osmupload\.py/; 75 | return "Jeff's Uploader" if $cr =~ /^Jeff's Uploader/; 76 | return 'AND node cleaner/retagger' if $cr =~ /^AND /; 77 | return 'MyUploader' if $cr =~ /^MyUploader/; 78 | return 'Tayberry' if $cr =~ /^Tayberry/; 79 | return 'GNOME Maps' if $cr =~ /^gnome-maps/; 80 | return 'StreetComplete' if $cr =~ /^StreetComplete /; 81 | return 'StreetComplete EE' if $cr =~ /^StreetComplete_ee /; 82 | return 'FireYak' if $cr =~ /^FireYak/; 83 | return 'MapContrib' if $cr =~ /^MapContrib/; 84 | return 'Geocropping' if $cr =~ /^Geocropping/; 85 | return 'RevertUI' if $cr =~ /^RevertUI/; 86 | return 'OSM ↔ Wikidata' if $cr =~ /osm\.wikidata\.link/; 87 | return 'OSM Contributor' if $cr =~ /^OSM Contributor/; 88 | return 'Jungle Bus' if $cr =~ /^Jungle Bus/; 89 | return 'IsraelHiking' if $cr =~ /^IsraelHiking\.osm/; 90 | return 'RocketData' if $cr =~ /^rocketdata\.io/; 91 | return 'Pic4Review' if $cr =~ /^Pic4Review/; 92 | return 'Tracks Editor' if $cr =~ /^Tracks Editor/; 93 | return 'Data4All' if $cr =~ /^Data4All/; 94 | return 'CityZen' if $cr =~ /^CityZen/; 95 | return 'Osm2go' if $cr =~ /^osm2go /; 96 | return 'AutoAWS' if $cr =~ /^autoAWS/; 97 | return 'DEVK Versicherung' if $cr =~ /^DEVK Versicherung/; 98 | return 'osm for ruby' if $cr =~ /^osm for ruby/; 99 | return 'osmlinzaddr.py' if $cr =~ /osmlinzaddr\.py/; 100 | return 'FixKarlsruheSchema' if $cr =~ /^FixKarlsruheSchema/; 101 | return 'GpsMid' if $cr =~ /^GpsMid_/; 102 | return 'AndNav2' if $cr =~ /^andnav\.org/; 103 | return 'Deriviste' if $cr =~ /^Deriviste/; 104 | return 'Brick' if $cr =~ /^Brick/; 105 | return 'SketchOSM' if $cr =~ /^SketchOSM/; 106 | return 'OSMyBiz' if $cr =~ /^OSMyBiz/; 107 | return 'Osm Go!' if $cr =~ /^Osm Go!/; 108 | return 'Parking Lanes' if $cr =~ /^PLanes/; 109 | return 'Rapid' if $cr =~ /^Rapi[dD] /; 110 | return 'TrashApp' if $cr =~ /^TrashApp/; 111 | return 'OsmInEdit' if $cr =~ /^OsmInEdit/; 112 | return 'MapComplete' if $cr =~ /^MapComplete/; 113 | return 'MapRoulette' if $cr =~ /^MapRoulette/; 114 | return 'Healthsites.io' if $cr =~ /^Healthsites\.io/; 115 | return 'OsmPipeline' if $cr =~ /^OsmPipeline/; 116 | return 'HTTPS All The Things' if $cr =~ /^https_all_the_things/; 117 | return 'addr2osm' if $cr =~ /^addr2osm/; 118 | return 'peundemerg.ro' if $cr =~ /^peundemerg.ro/; 119 | return 'Organic Maps' if $cr =~ /^O(rganic )?Maps /; 120 | return 'ProjetDuMois.fr' if $cr =~ /^ProjetDuMois/; 121 | return 'posiki_python_script' if $cr =~ /^posiki_python_script/; 122 | return 'Centaur Mapper' if $cr =~ /^Centaur Mapper/; 123 | return 'simple_revert' if $cr =~ /^(?:simple_revert|restore-version)\.py/; 124 | return 'POI Collector' if $cr =~ /^POI Collector v/; 125 | return 'Locus Map POI' if $cr =~ /^LoPoi /; 126 | return 'Mundi App' if $cr =~ /^Mundi App/; 127 | return 'OpenRecycleMap' if $cr =~ /^OpenRecycleMap/; 128 | return 'LINZ Import' if $cr =~ /^LINZ \w+ Import/; 129 | return 'Every Door' if $cr =~ /^Every Door /; 130 | return 'Mapa AED' if $cr =~ /aed\.openstreetmap\.org\.pl/; 131 | return 'Map builder' if $cr =~ /^Map builder /; 132 | return 'refill.bz.it' if $cr =~ /^Refill Südtirol \/ Alto Adige$/; 133 | return 'OpenAEDMap' if $cr =~ /openaedmap\.org/; 134 | return 'OpenAEDMap' if $cr =~ /^AED Map for/; 135 | return 'OpenAEDMap' if $cr =~ /openaedmap-backend /; 136 | return 'osm-revert' if $cr =~ /^osm-revert /; 137 | return 'OpenStop' if $cr =~ /^OpenStop/; 138 | return 'OSM Tags Editor' if $cr =~ /^Osm\.Org Tags Editor/; 139 | return 'Relatify' if $cr =~ /^osm-relatify /; 140 | return 'CoMaps' if $cr =~ /^CoMaps /; 141 | return 'DeFlock' if $cr =~ /^DeFlock /; 142 | return 'OsmAPP' if $cr =~ /^OsmAPP /; 143 | return 'Other'; 144 | } 145 | 146 | my %wikinames = ( 147 | 'Potlatch 0.x/1.x' => 'Potlatch 1', 148 | 'Pushpin' => 'Pushpin OSM', 149 | 'PythonOsmApi' => 'Osmapi', 150 | 'OSMPOIEditor' => 'POI+', 151 | 'rosemary' => 'Rosemary', 152 | 'ArcGIS' => 'ArcGIS Editor for OSM', 153 | 'upload.py' => 'Upload.py', 154 | 'bulk_upload.py' => 'Bulk_upload.py', 155 | 'bulk_upload_sax.py' => 'Bulk_upload_sax.py', 156 | 'OpenMaps' => 'OpenMaps (IZE)', 157 | 'Kort' => 'Kort Game', 158 | 'Mapzen' => 'Mapzen POI Collector', 159 | 'iLOE' => 'ILOE', 160 | 'osmitter' => 'Osmitter', 161 | 'QGIS' => 'QGIS OSM Plugin', 162 | 'Services_OSM' => 'PHP', 163 | 'Osmose Editor' => 'Osmose#Osmose_integrated_tags_editor', 164 | 'Redaction bot' => 'OSMF Redaction Bot', 165 | 'OpenSeaMap-Editor' => 'OpenSeaMap#Editor', 166 | 'AND node cleaner/retagger' => 'AND Data', 167 | 'FixKarlsruheSchema' => 'Xybot#So_what_does_the_FixKarlsruheSchema_ruleset_do_exactly', 168 | 'Jungle Bus' => 'Jungle Bus mobile app', 169 | 'HTTPS All The Things' => 'Automated Edits/b-jazz-bot', 170 | 'OsmPipeline' => 'Import/Maine E911 Addresses', 171 | 'LINZ Import' => 'Import/New Zealand Street Addresses (2021)', 172 | 'OSM ↔ Wikidata' => 'OSM ↔ Wikidata matcher', 173 | 'JOSM Reverter' => 'JOSM/Plugins/Reverter', 174 | 'OpenStop' => 'DE:OpenStop', 175 | 'StreetComplete EE' => 'SCEE', 176 | 'OSM Tags Editor' => 'OpenStreetMap Tags Editor' 177 | ); 178 | 179 | my @wiki_self = ( 180 | 'JOSM', 'iD', 'Potlatch 2', 'Merkaartor', 'Vespucci', 'Go Map!!', 'MAPS.ME', 181 | 'OsmAnd', 'Level0', 'OsmHydrant', 'RawEdit', 'Nomino', 'My Opening Hours', 182 | 'FreieTonne', 'MapStalt Mini', 'OSMapTuner', 'MapContrib', 'StreetComplete', 183 | 'OSM Contributor', 'Tracks Editor', 'Data4All', 'CityZen', 'Potlatch 3', 184 | 'Osm2go', 'AutoAWS', 'GpsMid', 'Deriviste', 'AndNav2', 'OSMyBiz', 'Osm Go!', 185 | 'Rapid', 'OsmInEdit', 'GNOME Maps', 'MapRoulette', 'MapComplete', 'Organic Maps', 186 | 'Every Door', 'Pic4Review', 'posiki_python_script', 'RevertUI', 'osm-revert', 187 | 'Relatify', 'CoMaps', 'OsmAPP' 188 | ); 189 | $wikinames{$_} = '' for @wiki_self; 190 | 191 | my %websites = ( 192 | 'Geocropping' => 'https://geocropping.xsalto.com/guide.html', 193 | 'IsraelHiking' => 'https://israelhiking.osm.org.il/', 194 | 'RocketData' => 'https://rocketdata.io/', 195 | 'DEVK Versicherung' => 'https://www.devk.de/', 196 | 'osmlinzaddr.py' => 'https://git.nzoss.org.nz/ewblen/osmlinzaddr', 197 | 'Brick' => 'https://edit.osmbuildings.org/', 198 | 'SketchOSM' => 'https://mindsightstudios.com/sketchosm/', 199 | 'Parking Lanes' => 'https://zlant.github.io/parking-lanes/', 200 | 'TrashApp' => 'https://trashapp.cc/', 201 | 'Healthsites.io' => 'https://healthsites.io/', 202 | 'addr2osm' => 'https://github.com/NKAmapper/addr2osm', 203 | 'peundemerg.ro' => 'https://forum.peundemerg.ro/index.php?topic=836.0', 204 | 'ProjetDuMois.fr' => 'https://projetdumois.fr/projects/2021-05_laboratory', 205 | 'OpenRecycleMap' => 'https://openrecyclemap.org/', 206 | 'Locus Map POI' => 'https://www.vastuf.com/projects/lopoi/', 207 | 'Mapa AED' => 'https://aed.openstreetmap.org.pl/', 208 | 'osmtools' => 'https://github.com/woodpeck/osm-revert-scripts', 209 | 'Map builder' => 'https://www.bing.com/mapbuilder/', 210 | 'refill.bz.it' => 'https://www.refill.bz.it/', 211 | 'OpenAEDMap' => 'https://openaedmap.org/', 212 | 'DeFlock' => 'https://deflock.me/app' 213 | ); 214 | 215 | sub editor_wikilink { 216 | my $e = shift; 217 | if (exists $wikinames{$e}) { 218 | return "[[".($wikinames{$e} ? $wikinames{$e}.'|' : '')."$e]]"; 219 | } elsif (exists $websites{$e}) { 220 | return "[$websites{$e} $e]"; 221 | } 222 | return $e; 223 | } 224 | 225 | 1; 226 | --------------------------------------------------------------------------------