File Coverage

blib/lib/GD/Map.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package GD::Map;
2              
3 1     1   25289 use strict;
  1         2  
  1         41  
4 1     1   403 use GD;
  0            
  0            
5             use Data::Dumper;
6             use Digest::MD5 qw(md5_hex);
7              
8             our $VERSION = '1.00';
9              
10             sub new {
11             my %opts = @_;
12              
13             # what do we need?
14             # well, the path of the basemaps (required)
15             # and the output path (required)
16              
17             die "basemap_path not defined" unless($opts{basemap_path});
18             die "output_path not defined" unless($opts{output_path});
19              
20             die "could not find basemap_path [$opts{basemap_path}]" unless(-d $opts{basemap_path});
21            
22             unless(-d $opts{output_path}) {
23             # try and create it
24             mkdir $opts{output_path}, 0775;
25             die "could not find output_path [$opts{output_path}]" unless(-d $opts{ouput_path});
26             }
27              
28             my $m = {
29             output_path => _pathstr($opts{output_path}),
30             basemap_path => _pathstr($opts{basemap_path}),
31             verbose => $opts{verbose},
32             };
33             bless $m, __PACKAGE__;
34              
35             return $m;
36             }
37              
38             sub dump {
39             my $m = shift;
40              
41             print Data::Dumper->Dump([$m]);
42             }
43              
44             sub add_data {
45             my $m = shift;
46             my %data = @_;
47              
48             push @{$m->{data}}, { %data };
49             }
50              
51             sub add_object {
52             my $m = shift;
53             my %opts = @_;
54              
55             die "id not defined" unless($opts{id});
56             my %tmp;
57             $tmp{id} = $opts{id};
58             foreach my $t (qw(line circle text rectangle dot image)) {
59             $tmp{type} = $t if ($opts{type} eq $t);
60             }
61             die "type [$opts{type}] not defined or is not valid" unless($tmp{type});
62              
63             # transfer some other options to the tmp variable
64             foreach my $t (qw(color filled fillcolor)) {
65             $tmp{$t} = $opts{$t} if ($opts{$t});
66             }
67              
68             # make sure this object type does not yet exist
69             foreach my $or (@{$m->{objects}}) {
70             die "$opts{id} object is already defined"
71             if ($or->{id} eq $opts{id});
72             }
73              
74             # otherwise push it into the object types
75             push @{$m->{objects}}, { %tmp };
76             }
77              
78             sub create_basemap {
79             my $m = shift;
80             my %opts = @_;
81              
82             # We need
83              
84             # map name
85             die "map_name not defined" unless($opts{map_name});
86              
87             # data_path (location of CIA map data)
88             die "data_path not defined" unless($opts{data_path});
89              
90             die "could not find data_path [$opts{data_path}]" unless(-d $opts{data_path});
91             $opts{data_path} = _pathstr($opts{data_path});
92              
93             my ($my,$ny,$mx,$nx,$scale);
94             if ($opts{scale}) {
95             # scale and min lat/long and max lat/long
96              
97             # we need to shrink by 25% when done
98             # to get that "soft" look
99             $scale = $opts{scale}*4;
100              
101             # make sure we have min and max lat and long
102             foreach my $mm (qw(min max)) {
103             foreach my $ll (qw(lat long)) {
104             die unless(defined($opts{"${mm}_${ll}"}));
105             }
106             }
107             print "Setting size from scale information\n" if ($m->{verbose});
108             $my = $opts{max_lat}*$scale;
109             $ny = $opts{min_lat}*$scale;
110             $mx = $opts{max_long}*$scale;
111             $nx = $opts{min_long}*$scale;
112             print "max_y = $my, min_y = $ny, max_x = $mx, min_x = $nx\n" if ($m->{verbose});
113             } else {
114             # height and width in pixels and either a start and end lat and start long
115             # or a start and end long and start lat
116              
117             die "scale not provided";
118             }
119              
120             # we also need a background color (default to white)
121             # and a line color (default to black)
122              
123             my @xy;
124             my @seg;
125              
126             # open each of the data files
127             opendir D, $opts{data_path};
128             while (my $f = readdir D) {
129             next unless(-f "$opts{data_path}$f");
130             open F, "$opts{data_path}$f";
131             print "Reading datafile $opts{data_path}$f\n" if ($m->{verbose});
132             while (my $l = ) {
133             if ($l =~ m/^segment/) {
134             push @xy, [ @seg ] if (scalar @seg);
135             undef @seg;
136             next;
137             } else {
138             $l =~ s/^\s+//;
139             my ($y,$x) = split /\s+/, $l;
140             next if ($y > $opts{max_lat});
141             next if ($y < $opts{min_lat});
142             next if ($x > $opts{min_long}*-1);
143             next if ($x < $opts{max_long}*-1);
144             $x = int($x*$scale*-1);
145             $y = int($y*$scale);
146             push @seg, "$x,$y";
147             }
148             }
149            
150             push @xy, [ @seg ] if (scalar @seg);
151             close F;
152             }
153             close D;
154              
155             die "No data information loaded" unless(scalar @xy);
156              
157             my $width = $mx-$nx;
158             my $height = $my-$ny;
159             print "Setting up GD Image ($width x $height)\n" if ($m->{verbose});
160             my $im = new GD::Image($width,$height);
161             my $fg = $im->colorAllocate(100,100,100);
162             my $bg = $im->colorAllocate(255,255,255);
163             $im->setThickness(2);
164             $im->fill(1,1,$bg);
165              
166             print "Drawing basemap\n" if ($m->{verbose});
167             foreach my $seg (@xy) {
168             my ($lx,$ly);
169             foreach my $xy (@{$seg}) {
170             my ($x,$y) = split ',', $xy;
171             $x = $mx-$x;
172             $y = $my-$y;
173             if (defined($lx)) {
174             if ($x < $width/10 && $lx > $width-($width/10)) {
175             1;
176             } else {
177             $im->line($x,$y,$lx,$ly,$fg);
178             }
179             }
180             $lx = $x;
181             $ly = $y;
182             }
183             }
184              
185             my $outputf = "$m->{basemap_path}$opts{map_name}.png";
186             print "Writing $outputf\n" if ($m->{verbose});
187             open F, ">$outputf";
188             binmode F;
189             print F $im->png;
190             close F;
191              
192             # make sure the file was written
193             unless (-s $outputf) {
194             die "$outputf was not created (maybe a permission problem?)"
195             }
196              
197             # update data file
198             _read_basemap_data($m);
199             $m->{basemap}->{$opts{map_name}}{scale} = $opts{scale};
200             $m->{basemap}->{$opts{map_name}}{min_long} = $opts{min_long};
201             $m->{basemap}->{$opts{map_name}}{max_long} = $opts{max_long};
202             $m->{basemap}->{$opts{map_name}}{min_lat} = $opts{min_lat};
203             $m->{basemap}->{$opts{map_name}}{max_lat} = $opts{max_lat};
204             _write_basemap_data($m);
205             }
206              
207             sub set_basemap {
208             my $m = shift;
209             my $map_name = shift;
210              
211             # load the basemap config file
212             _read_basemap_data($m);
213              
214             # then check and see if we can find the basemap information in the file
215             die "Sorry, $map_name is not a valid basemap"
216             unless(defined($m->{basemap}{$map_name}));
217              
218             # and then see if we can find the file itself
219             die "Sorry, could not find $m->{basemap_path}$map_name"
220             unless(-s "$m->{basemap_path}$map_name.png");
221              
222             $m->{use_basemap} = $map_name;
223              
224             }
225              
226             sub map_scale {
227             my $m = shift;
228              
229             die "Sorry, basemap not defined" unless($m->{use_basemap});
230             die "Sorry, no scale for basemap $m->{use_basemap}"
231             unless(defined($m->{basemap}{$m->{use_basemap}}{scale}));
232              
233             return $m->{basemap}{$m->{use_basemap}}{scale};
234             }
235              
236             sub draw {
237             my $m = shift;
238             my $d = shift;
239            
240             $m->{data} = $d if (defined($d));
241             die "No data to draw" unless(scalar @{$m->{data}});
242              
243             die "Please set_basemap before calling draw" unless($m->{use_basemap});
244             my $b = $m->{basemap}{$m->{use_basemap}};
245              
246             $m->{map_width} = int(($b->{max_long}-$b->{min_long})*$b->{scale});
247             $m->{map_height} = int(($b->{max_lat}-$b->{min_lat})*$b->{scale});
248             my $max_x = $m->{map_width};
249             my $max_y = $m->{map_height};
250              
251             # create an md5 of the $m->object which we will use as the unique file name
252             $m->{filename} = md5_hex(Data::Dumper->Dump([$m]));
253              
254             #$m->dump();
255              
256             print "Creating GD Image\n" if ($m->{verbose});
257             my $im = new GD::Image->new("$m->{basemap_path}$m->{use_basemap}.png");
258              
259             # loop through the objects and then draw all of them in the correct order
260             # so they get layered depending on how the objects where created
261             foreach my $or (@{$m->{objects}}) {
262             print "Drawing object $or->{id} [$or->{type}]\n" if ($m->{verbose});
263              
264             # allocate object colors
265             foreach my $t (qw(color fillcolor)) {
266             if (defined($or->{$t})) {
267             my ($r,$g,$b) = split ',', $or->{$t};
268             $or->{$t} = $im->colorAllocate($r,$g,$b);
269             }
270             }
271              
272             foreach my $dr (@{$m->{data}}) {
273             next unless($dr->{id} eq $or->{id});
274             if ($or->{type} eq "line") {
275             my ($x1,$y1,$x2,$y2) = _latlong_to_xy($m,$dr);
276             print "Drawing line from $x1,$y1 to $x2,$y2\n" if ($m->{verbose});
277             $im->line($x1,$y1,$x2,$y2,$or->{color});
278             } elsif ($or->{type} eq "dot") {
279             my $size = $dr->{size} || 4;
280             my ($x1,$y1) = _latlong_to_xy($m,$dr);
281             print "Drawing dot at $x1,$y1 size $size\n" if ($m->{verbose});
282             $im->filledArc($x1,$y1,$size,$size,0,360,$or->{color});
283             } elsif ($or->{type} eq "circle") {
284             my $size = $dr->{size} || 4;
285             my ($x1,$y1) = _latlong_to_xy($m,$dr);
286             print "Drawing circle at $x1,$y1 size $size\n" if ($m->{verbose});
287             $im->arc($x1,$y1,$size,$size,0,360,$or->{color});
288             } elsif ($or->{type} eq "image") {
289             die "Could not find file $dr->{image_path}"
290             unless(-f $dr->{image_path});
291            
292             foreach my $t (qw(image_height image_width)) {
293             die "Invalid or missing $t" unless($dr->{$t});
294             }
295              
296             my $img = new GD::Image->new($dr->{image_path});
297             my $h = $dr->{image_height};
298             my $w = $dr->{image_width};
299             my $dx = int($h/2);
300             my $dy = int($w/2);
301             my ($x1,$y1) = _latlong_to_xy($m,$dr);
302             $im->copy($img,$x1-$dx,$y1-$dy,0,0,$h,$w);
303             }
304             }
305             }
306              
307             open(IMG, ">$m->{output_path}$m->{filename}.png");
308             binmode IMG;
309             print IMG $im->png;
310             close IMG;
311              
312             die "Map file did not get created correctly"
313             unless(-s "$m->{output_path}$m->{filename}.png");
314              
315             print "$m->{output_path}$m->{filename}.png created\n" if ($m->{verbose});
316             }
317              
318             ############################
319             # PRIVATE FUNCTIONS
320             ############################
321             sub _latlong_to_xy {
322             my $m = shift;
323             my $data = shift;
324              
325             my $b = $m->{basemap}{$m->{use_basemap}};
326              
327             my $x1 = int(($b->{max_long}-$data->{start_long})*$b->{scale});
328             my $y1 = int(($b->{max_lat}-$data->{start_lat})*$b->{scale});
329             my $x2 = int(($b->{max_long}-$data->{end_long})*$b->{scale})
330             if (defined($data->{end_long}));
331             my $y2 = int(($b->{max_lat}-$data->{end_lat})*$b->{scale})
332             if (defined($data->{end_lat}));
333              
334             return ($x1,$y1,$x2,$y2);
335             }
336              
337             sub _read_basemap_data {
338             my $m = shift;
339              
340             $m->{basemap} = {};
341             return unless(-s "$m->{basemap_path}mapdata.conf");
342             open F, "$m->{basemap_path}mapdata.conf";
343             my $data;
344             while (my $l = ) {
345             $data .= $l;
346             }
347             close F;
348              
349             eval $data;
350             }
351              
352             sub _write_basemap_data {
353             my $m = shift;
354              
355             open F, ">$m->{basemap_path}mapdata.conf";
356             print F Data::Dumper->Dump([\%{$$m{basemap}}],['$$m{basemap}']);
357             close F;
358              
359             unless (-s "$m->{basemap_path}mapdata.conf") {
360             die "$m->{basemap_path}mapdata.conf was not created (maybe a permission problem?)"
361             }
362             }
363              
364             sub _pathstr {
365             my $path = shift;
366              
367             return (($path =~ m/\/$/) ? $path : "$path/");
368             }
369              
370             # Preloaded methods go here.
371              
372             # Autoload methods go after =cut, and are processed by the autosplit program.
373              
374             1;
375             __END__