├── .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 |
--------------------------------------------------------------------------------