|
| 1 | +#!/usr/bin/env perl |
| 2 | +# -*- perl -*- |
| 3 | + |
| 4 | +# |
| 5 | +# Author: Slaven Rezic |
| 6 | +# |
| 7 | +# Copyright (C) 2025 Slaven Rezic. All rights reserved. |
| 8 | +# This program is free software; you can redistribute it and/or |
| 9 | +# modify it under the same terms as Perl itself. |
| 10 | +# |
| 11 | +# WWW: https://github.com/eserte/bbbike |
| 12 | +# |
| 13 | + |
| 14 | +use strict; |
| 15 | +use warnings; |
| 16 | +use FindBin; |
| 17 | + |
| 18 | +use LWP::UserAgent; |
| 19 | +use JSON; |
| 20 | +use lib "$FindBin::RealBin/.."; |
| 21 | +use BBBikeYAML qw(LoadFile); |
| 22 | + |
| 23 | +use POSIX qw(floor strftime); |
| 24 | +use Math::Trig; |
| 25 | + |
| 26 | +sub latlon_to_tile { |
| 27 | + my ($lat, $lon, $zoom) = @_; |
| 28 | + my $lat_rad = deg2rad($lat); |
| 29 | + my $n = 2 ** $zoom; |
| 30 | + my $x_tile = floor(( $lon + 180.0 ) / 360.0 * $n); |
| 31 | + my $y_tile = floor((1 - (log(tan($lat_rad) + 1 / cos($lat_rad)) / pi())) / 2 * $n); |
| 32 | + return ($x_tile, $y_tile); |
| 33 | +} |
| 34 | + |
| 35 | +# like in mapillary-v4-fetch |
| 36 | +my $do_cache = 1; |
| 37 | +my $cache_time = 8 * 3600; # seconds |
| 38 | + |
| 39 | +my $conf_file = "$ENV{HOME}/.mapillary"; |
| 40 | +my $conf = LoadFile $conf_file; |
| 41 | +my $client_token = $conf->{client_token} || die "Can't get client_token from $conf_file"; |
| 42 | + |
| 43 | +my @tiles = (); |
| 44 | +my $z = 12; |
| 45 | + |
| 46 | +# XXX get from Geography::Berlin_DE or provide by option |
| 47 | +my $min_lon = 13.051179; |
| 48 | +my $max_lon = 13.764158; |
| 49 | +my $min_lat = 52.337621; |
| 50 | +my $max_lat = 52.689878; |
| 51 | + |
| 52 | +my $date_range = shift; |
| 53 | +my $date_from = ""; |
| 54 | +my $date_to = ""; |
| 55 | +if ($date_range) { |
| 56 | + if ($date_range =~ /^(\d{8})(?:-|\.\.)(\d{8})?$/) { |
| 57 | + $date_from = $1; |
| 58 | + $date_to = $2 if defined $2; |
| 59 | + } else { |
| 60 | + die "Wrong date range syntax, must be: YYYYMMDD- or YYYYMMDD-YYYYMMDD.\n"; |
| 61 | + } |
| 62 | +} |
| 63 | +if (!$date_from) { |
| 64 | + $date_from = strftime "%Y%m%d", localtime(time-86400); |
| 65 | + warn "INFO: date range not given, default to $date_from-$date_to\n"; |
| 66 | +} |
| 67 | +die "Too many arguments" if @ARGV; |
| 68 | + |
| 69 | +my ($min_x, $min_y) = latlon_to_tile($min_lat, $min_lon, $z); |
| 70 | +my ($max_x, $max_y) = latlon_to_tile($max_lat, $max_lon, $z); |
| 71 | + |
| 72 | +($min_x,$max_x) = ($max_x,$min_x) if $max_x < $min_x; |
| 73 | +($min_y,$max_y) = ($max_y,$min_y) if $max_y < $min_y; |
| 74 | + |
| 75 | +for my $x ($min_x .. $max_x) { |
| 76 | + for my $y ($min_y .. $max_y) { |
| 77 | + push @tiles, [$z,$x,$y]; |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +warn "INFO: we need to fetch and process " . scalar(@tiles) . " tile(s)...\n"; |
| 82 | + |
| 83 | +my $ua; |
| 84 | +if ($do_cache) { |
| 85 | + if (!eval { require LWP::UserAgent::WithCache; require HTTP::Date; 1 }) { |
| 86 | + die "Module missing, please install. Error: $@"; |
| 87 | + } |
| 88 | + # need to patch set_cache method |
| 89 | + my $orig_set_cache = \&LWP::UserAgent::WithCache::set_cache; |
| 90 | + { |
| 91 | + no warnings 'redefine'; |
| 92 | + *LWP::UserAgent::WithCache::set_cache = sub { |
| 93 | + my($self, $uri, $res) = @_; |
| 94 | + |
| 95 | + if ($res->header('X-Died')) { |
| 96 | + warn "X-Died header encountered, do not write to cache...\n"; |
| 97 | + return; |
| 98 | + } |
| 99 | + if ($res->header('Client-Aborted')) { |
| 100 | + warn "Client-Aborted header encountered, do not write to cache...\n"; |
| 101 | + return; |
| 102 | + } |
| 103 | + |
| 104 | + my $expires = time + $cache_time; |
| 105 | + my $expires_formatted = HTTP::Date::time2str($expires); |
| 106 | + $res->header('Expires', $expires_formatted); |
| 107 | + |
| 108 | + $orig_set_cache->($self, $uri, $res); |
| 109 | + }; |
| 110 | + } |
| 111 | + my %cache_opt = ( |
| 112 | + 'namespace' => 'lwp-cache', |
| 113 | + 'cache_root' => "$ENV{HOME}/.cache", |
| 114 | + 'default_expires_in' => $cache_time, |
| 115 | + ); |
| 116 | + $ua = LWP::UserAgent::WithCache->new(\%cache_opt); |
| 117 | +} else { |
| 118 | + $ua = LWP::UserAgent->new(keep_alive => 1); |
| 119 | +} |
| 120 | + |
| 121 | +$ua->agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"); |
| 122 | + |
| 123 | +print <<'EOF'; |
| 124 | +#: map: polar |
| 125 | +#: line_arrow: last |
| 126 | +#: |
| 127 | +EOF |
| 128 | + |
| 129 | +for my $tile (@tiles) { |
| 130 | + my ($z, $x, $y) = @$tile; |
| 131 | + my $file = "/tmp/tile_${z}_${x}_${y}.mvt"; |
| 132 | + warn "INFO: fetch and process $file...\n"; |
| 133 | + #if (!-s $file) { # always use cached version |
| 134 | + { |
| 135 | + my $url = "https://tiles.mapillary.com/maps/vtp/mly1_public/2/$z/$x/$y?access_token=$client_token"; |
| 136 | + my $response = $ua->get($url); |
| 137 | + if ($response->is_success) { |
| 138 | + my $content = $response->content; |
| 139 | + # XXX workaround for HTTP::Response bug https://github.com/libwww-perl/HTTP-Message/issues/48 |
| 140 | + if ($do_cache && $HTTP::Response::VERSION <= 7.00) { |
| 141 | + $content =~ s{\n$}{}; |
| 142 | + } |
| 143 | + open my $fh, '>', "$file~" or die $!; |
| 144 | + binmode $fh; |
| 145 | + print $fh $content; |
| 146 | + close $fh; |
| 147 | + rename "$file~", $file or die $!; |
| 148 | + } else { |
| 149 | + warn "ERROR: Failed for $url:\n" . $response->dump . "\n"; |
| 150 | + next; |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + # Call Python script |
| 155 | +# my $json = `python3 $FindBin::RealBin/parse_mvt_sequences.py $file "$date_from" "$date_to"`; |
| 156 | + my $json = `$^X $FindBin::RealBin/parse_mvt_sequences.pl $file "$date_from" "$date_to"`; |
| 157 | + my $results = eval { decode_json($json) }; |
| 158 | + if ($results && ref $results eq 'ARRAY') { |
| 159 | + for my $seq (@$results) { |
| 160 | + print "#: url: $seq->{url}\n"; |
| 161 | + print "start_captured_at=$seq->{start_captured_at} creator=$seq->{creator} make=$seq->{make} end_captured_at=$seq->{end_captured_at} start_id=$seq->{start_id} sequence=$seq->{sequence}\tX " . join(" ", map { "$_->[1],$_->[0]" } @{ $seq->{coordinates} }) . "\n"; |
| 162 | + } |
| 163 | + } |
| 164 | + # keep cached file! unlink $file; # Optional: cleanup |
| 165 | +} |
| 166 | + |
| 167 | +__END__ |
0 commit comments