3 ##############################################################################
4 ## Plots user supplied lat/longs on a user chosen map.
6 ## plot-latlong reads lat/long values from stdin and writes a PNG to
7 ## stdout. The input should contain one pair of lat/long values per line,
8 ## with the values separated by whitespace. Blank lines and lines starting
9 ## with the pound character ('#') are ignored.
11 ## Command line options are
13 ## -m <map-name> specifies the name of a map to use (default: the first
14 ## map listed in .mapinfo: 'World'); see .mapinfo for the valid map names;
15 ## see the file CONFIG for a description of the .mapinfo format
17 ## -s <point-size> specifies the size of the points to draw (default: 1);
18 ## points are filled squares, and the size is the width in pixels
20 ## -c causes the pixel coordinates of each lat/long to be printed to stderr;
21 ## the coordinates (0, 0) are at the upper left corner, and values increase
22 ## to the right and down
24 ## -i <map-info-file> specifies an alternate location for .mapinfo (other
25 ## than in the current directory or $HOME).
27 ##----------------------------------------------------------------------------
29 ## The code for handling the Alber/Lambert map projection is derived from
30 ## GTrace v1.0.0beta (http://www.caida.org/tools/visualization/gtrace),
31 ## which was written by Ram Periakaruppan. The included set of maps are also
32 ## derived from the GTrace distribution. GTrace redistributed these maps
33 ## with the permission of VisualRoute (http://www.visualroute.com),
34 ## the original source of the maps.
36 ##----------------------------------------------------------------------------
38 ## Copyright (C) 2003,2004,2005 The Regents of the University of California.
39 ## All Rights Reserved.
41 ## Permission to use, copy, modify and distribute any part of this
42 ## plot-latlong software package for educational, research and non-profit
43 ## purposes, without fee, and without a written agreement is hereby
44 ## granted, provided that the above copyright notice, this paragraph
45 ## and the following paragraphs appear in all copies.
47 ## Those desiring to incorporate this into commercial products or use
48 ## for commercial purposes should contact the Technology Transfer
49 ## Office, University of California, San Diego, 9500 Gilman Drive, La
50 ## Jolla, CA 92093-0910, Ph: (858) 534-5815, FAX: (858) 534-7345.
52 ## IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY
53 ## PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
54 ## DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS
55 ## SOFTWARE, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF
56 ## THE POSSIBILITY OF SUCH DAMAGE.
58 ## THE SOFTWARE PROVIDED HEREIN IS ON AN "AS IS" BASIS, AND THE
59 ## UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
60 ## SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. THE UNIVERSITY
61 ## OF CALIFORNIA MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES
62 ## OF ANY KIND, EITHER IMPLIED OR EXPRESS, INCLUDING, BUT NOT LIMITED
63 ## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
64 ## PARTICULAR PURPOSE, OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE
65 ## ANY PATENT, TRADEMARK OR OTHER RIGHTS.
67 ## The plot-latlong software is developed by the plot-latlong Team at the
68 ## University of California, San Diego under the Cooperative Association
69 ## for Internet Data Analysis (CAIDA) Program. Support for this work is
70 ## provided by the National Communications System (NCS) via NSF grant
71 ## ANI-0221172, entitled "Routing and Peering Analysis for Enhancing
72 ## Internet Performance and Security."
74 ##############################################################################
80 use vars qw($opt_m $opt_s $opt_c $opt_i);
84 die "usage: cat datapoints | plot-latlong [-m <map-name>] [-s <point-size>] [-c] [-i <map-info-file>] >output.png\n";
87 if (not getopts('m:s:ci:'))
95 my $point_size = $opt_s || 1;
98 my $map_directory; # directory containing map images
100 my @mapinfo_locations = (".mapinfo", "$ENV{HOME}/.mapinfo");
102 unshift @mapinfo_locations, $opt_i;
104 load_configuration(@mapinfo_locations);
106 my $selected_map = $opt_m || $first_map;
108 # lat/long of the upper-left and lower-right corners of the map image
114 # parameters for linear projection: pixels per degree
118 # parameters for Alber projection (supplied by user):
120 my $R; # radius of sphere
121 #my $phi_1; # standard parallel
122 #my $phi_2; # standard parallel
123 #my $phi_0; # origin latitude
124 #my $lambda_0; # origin longitude
125 my $false_easting; # the false easting amount
126 my $false_northing; # the false northing amount
128 # values for Alber projection calculated from user parameters:
137 my $conversion_fn; # pointer to function for converting lat/long to (x, y)
139 my $map = load_map($selected_map);
141 #############################################################################
143 my $green = $map->colorAllocate(64, 192, 64);
144 my $red = $map->colorAllocate(220, 64, 64);
152 # REQUIRE: -90 <= $lat <= 90
153 my ($lat, $long) = split /\s+/;
154 my $adjusted_long = $long;
155 if ($long < $map_top_long) {
156 $adjusted_long += 360;
157 } elsif ($long > $map_bottom_long) {
158 $adjusted_long -= 360;
160 my ($x, $y) = &$conversion_fn($lat, $adjusted_long);
162 print STDERR "$lat $long $x $y\n" if $DEBUG || $opt_c;
164 if ($point_size == 1)
166 $map->setPixel($x, $y, $red);
170 my $half = int($point_size / 2);
171 my $left_x = $x - $half;
172 my $top_y = $y - $half;
173 $map->filledRectangle($left_x, $top_y,
174 $left_x + $point_size - 1, $top_y + $point_size - 1,
179 print $map->png if not $DEBUG;
181 #############################################################################
182 #############################################################################
184 sub convert_latlong_to_xy_linear
186 my ($lat, $long) = @_;
188 my $lat_rel = $map_top_lat - $lat;
189 my $long_rel = $long - $map_top_long;
191 return ($long_rel * $map_long_scale, $lat_rel * $map_lat_scale);
194 #############################################################################
197 # See the publication "Map Projections Used by the U.S. Geological
198 # Survey Bulletin 1532" for details about this projection.
200 # However, the present coder does not know exactly which projection this
201 # function corresponds to in the USGS Bulletin.
205 return int($number + .5 * ($number <=> 0));
208 sub convert_latlong_to_xy_alber
210 my ($lat, $long) = @_;
212 my $phi = ($lat * $PI) / 180.0;
213 my $lambda = ($long * $PI) / 180.0;
215 my $p = ($R * sqrt($C - 2.0 * $N * sin($phi))) / $N;
216 my $p_0 = ($R * sqrt($C - 2.0 * $N * sin($PHI_0))) / $N;
217 my $theta = $N * ($lambda - $LAMBDA_0);
219 my $x = $false_easting + round($p * sin($theta));
220 my $y = $false_northing - round($p_0 - $p * cos($theta));
225 ############################################################################
231 if (not exists $configuration{"MAP $name"})
233 die "ERROR: Map '$name' not found in map configuration file.\n";
236 print("OPEN: $name ", $configuration{"MAP $name"}, "\n") if $DEBUG;
239 ($filename, $map_top_lat, $map_top_long, $map_bottom_lat, $map_bottom_long)
240 = split(/\s+/, $configuration{"MAP $name"});
242 my $path = "$map_directory/$filename";
243 my $retval = new GD::Image($path)
244 or die "ERROR: Couldn't open map image '$path': $!\n";
246 my ($map_width, $map_height) = $retval->getBounds();
248 print("DIM: $map_width $map_height\n") if $DEBUG;
250 $map_lat_scale = $map_height / ($map_top_lat - $map_bottom_lat);
251 $map_long_scale = $map_width / ($map_bottom_long - $map_top_long);
253 print("SCALE: $map_lat_scale $map_long_scale\n") if $DEBUG;
255 # -- projections -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
257 if (not exists $configuration{"PROJECTION $name"})
259 $conversion_fn = \&convert_latlong_to_xy_linear;
263 my @F = split(/\s+/, $configuration{"PROJECTION $name"});
264 if ($F[0] eq "ALBER")
266 ($R, $false_easting, $false_northing) = @F[1,6,7];
267 my ($phi_1, $phi_2, $phi_0, $lambda_0) = @F[2..5];
269 $PHI_1 = ($phi_1 * $PI) / 180.0;
270 $PHI_2 = ($phi_2 * $PI) / 180.0;
271 $PHI_0 = ($phi_0 * $PI) / 180.0;
272 $LAMBDA_0 = ($lambda_0 * $PI) / 180.0;
274 $N = (sin($PHI_1) + sin($PHI_2)) / 2.0;
275 $C = cos($PHI_1) ** 2 + 2.0 * $N * sin($PHI_1);
277 $conversion_fn = \&convert_latlong_to_xy_alber;
281 die "INTERNAL ERROR: unknown projection '$F[0]'\n";
288 #############################################################################
290 sub load_configuration
292 foreach my $filename (@_)
296 $map_directory = dirname($filename) . "/.mapimages";
297 load_configuration_aux($filename);
302 die "ERROR: Couldn't find map configuration file; looked for: @_\n";
305 sub load_configuration_aux
309 open CONFIG, "$filename"
310 or die "ERROR: Couldn't open map configuration file '$filename': $!\n";
322 my ($top_lat, $top_long, $bot_lat, $bot_long) = @F[3..6];
323 if (!check_coordinate($top_lat)
324 || !check_coordinate($top_long)
325 || !check_coordinate($bot_lat)
326 || !check_coordinate($bot_long))
328 die "ERROR: Line $.: boundary coordinates are malformed in map configuration file.\n";
331 if ($top_lat < $bot_lat || $top_long > $bot_long)
333 die "ERROR: Line $.: boundary coordinates have wrong relations in map configuration file.\n";
336 $first_map = $F[1] if not defined $first_map;
337 $configuration{"MAP $F[1]"} = join(" ", @F[2..6]);
341 elsif ($F[0] eq "PROJECTION")
345 if ($F[2] eq "ALBER")
349 foreach my $x (@F[3..9])
351 if (not check_number($x))
353 die "ERROR: Line $.: projection parameters are malformed in map configuration file.\n";
357 $configuration{"PROJECTION $F[1]"} = join(" ", @F[2..9]);
363 die "ERROR: Line $.: unknown map projection '$F[2]' in map configuration file.\n";
369 die "ERROR: Line $.: unknown key '$F[0]' in map configuration file.\n";
372 die "ERROR: Line $. of map configuration file is malformed.\n";
377 #############################################################################
383 return $x =~ /^(\-?)\d+(\.\d+)?$/;
389 return check_coordinate(@_);