File Coverage

blib/lib/Image/WorldMap.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Image::WorldMap;
2 1     1   837 use strict;
  1         2  
  1         37  
3 1     1   7 use warnings;
  1         2  
  1         32  
4 1     1   7 use Carp;
  1         1  
  1         79  
5 1     1   418 use Image::Imlib2;
  0            
  0            
6             use Image::WorldMap::Label;
7             use vars qw($VERSION);
8             $VERSION = '0.15';
9              
10             # Class method, creates a new map
11             sub new {
12             my ( $class, $filename, $label ) = @_;
13              
14             my $self = {};
15              
16             my $image = Image::Imlib2->load($filename);
17             if ( not defined $image ) {
18             croak("Image::WorldMap: unable to load $filename");
19             return;
20             }
21             my $w = $image->get_width;
22             my $h = $image->get_height;
23             $image->add_font_path("../");
24             $image->add_font_path("examples/");
25              
26             $self->{IMAGE} = $image;
27             $self->{LABELS} = [];
28             $self->{LABEL} = $label;
29             $self->{W} = $w;
30             $self->{H} = $h;
31             bless $self, $class;
32              
33             if ( defined $label ) {
34              
35             # Determine the label offset for the current font
36             $image->load_font($label);
37             my $testlabel
38             = Image::WorldMap::Label->new( 0, 0,
39             "This is a testy little label",
40             $self->{IMAGE} );
41             my ( $w, $h )
42             = $testlabel->_boundingbox( $image,
43             "This is a testy little label" );
44             $Image::WorldMap::Label::YOFFSET = -int( $h / 2 );
45             $Image::WorldMap::Label::XOFFSET = 4;
46             }
47              
48             return $self;
49             }
50              
51             sub add {
52             my ( $self, $longitude, $latitude, $label, $dot_colour ) = @_;
53              
54             my ( $w, $h ) = ( $self->{W}, $self->{H} );
55             $w /= 2;
56              
57             my $x = $longitude;
58             my $y = $latitude;
59              
60             $x = $x * $w / 180;
61             $y = $y * $h / 180;
62             $y = -$y;
63             $x += $w;
64             $y += ( $h / 2 );
65              
66             # print "Adding: $label at $longitude, $latitude ($x, $y)\n";
67              
68             # If we're not showing labels, delete the label
69             undef $label unless $self->{LABEL};
70              
71             my $newlabel = Image::WorldMap::Label->new( int($x), int($y), $label,
72             $self->{IMAGE}, $dot_colour );
73             push @{ $self->{LABELS} }, $newlabel;
74             }
75              
76             sub draw {
77             my ( $self, $filename ) = @_;
78              
79             my $t_changes = 0;
80             my $t = 0.95;
81             my $nlabels = @{ $self->{LABELS} };
82             my $changed = 0;
83             my $changed_successfully = 0;
84             my $steps = 0;
85              
86             my @labels = ( @{ $self->{LABELS} } );
87             my $overlaps = $self->_number_of_overlaps;
88              
89             # warn "Initial overlaps: $overlaps\n";
90              
91             while (1) {
92              
93             last if $overlaps == 0;
94              
95             _fisher_yates_shuffle( \@labels );
96              
97             foreach my $l1 (@labels) {
98              
99             last if $overlaps == 0;
100              
101             my ( $l1x, $l1y, $l1w, $l1h )
102             = ( $l1->{X}, $l1->{Y}, $l1->{LABELW}, $l1->{LABELH} );
103              
104             my ( $oldlabelx, $oldlabely ) = ( $l1->{LABELX}, $l1->{LABELY} );
105             my $old_overlaps_single = $self->_number_of_overlaps_single($l1)
106             || 0;
107             my $mode = int( rand(8) );
108             if ( $mode == 0 ) {
109              
110             # right
111             $l1->{LABELX} = $l1x + $Image::WorldMap::Label::XOFFSET;
112             $l1->{LABELY} = $l1y + $Image::WorldMap::Label::YOFFSET;
113             } elsif ( $mode == 1 ) {
114              
115             # left
116             $l1->{LABELX}
117             = $l1x - $l1w - $Image::WorldMap::Label::XOFFSET;
118             $l1->{LABELY} = $l1y + $Image::WorldMap::Label::YOFFSET;
119             } elsif ( $mode == 2 ) {
120              
121             # top
122             $l1->{LABELX} = $l1x - $l1w / 2;
123             $l1->{LABELY} = $l1y - $l1h;
124             } elsif ( $mode == 3 ) {
125              
126             # bottom
127             $l1->{LABELX} = $l1x - $l1w / 2;
128             $l1->{LABELY} = $l1y;
129             } elsif ( $mode == 4 ) {
130              
131             # top right
132             $l1->{LABELX} = $l1x;
133             $l1->{LABELY} = $l1y - $l1h;
134             } elsif ( $mode == 5 ) {
135              
136             # top left
137             $l1->{LABELX} = $l1x - $l1w;
138             $l1->{LABELY} = $l1y - $l1h;
139             } elsif ( $mode == 6 ) {
140              
141             # bottom right
142             $l1->{LABELX} = $l1x;
143             $l1->{LABELY} = $l1y;
144             } elsif ( $mode == 7 ) {
145              
146             # bottom left
147             $l1->{LABELX} = $l1x - $l1w;
148             $l1->{LABELY} = $l1y;
149             }
150              
151             my $overlaps_single = $self->_number_of_overlaps_single($l1) || 0;
152             my $de = $overlaps_single - $old_overlaps_single;
153              
154             $steps++;
155              
156             if ( $de <= 0 ) {
157             if ( $de == 0 ) {
158             } else {
159             $changed_successfully++;
160             $changed++;
161              
162             # warn " Moved " . $l1->{TEXT} . " $de\n";
163             }
164             $overlaps += $overlaps_single - $old_overlaps_single;
165             } elsif ( $de > 0 ) {
166             my $p = 1 - exp( -$de / $t );
167              
168             # warn "T $t, p $p\n";
169             if ( rand(1) < $p ) {
170              
171             # move label back
172             $l1->{LABELX} = $oldlabelx;
173             $l1->{LABELY} = $oldlabely;
174             } else {
175              
176             # warn " Moved " . $l1->{TEXT} . " $de (worse)\n";
177             $changed++;
178             $overlaps += $overlaps_single - $old_overlaps_single;
179             }
180             }
181             }
182              
183             # warn "Overlaps: $overlaps\n";
184              
185             if ( $steps > $nlabels * 20 && $changed == 0 ) {
186              
187             # warn "No changes\n";
188             last;
189             }
190              
191             if ( $changed_successfully > $nlabels * 5
192             || $changed > $nlabels * 20 )
193             {
194             $t *= 0.9;
195             $t_changes++;
196             $changed = 0;
197             $changed_successfully = 0;
198             $steps = 0;
199              
200             # warn "T $t, overlaps $overlaps\n";
201             }
202             last if $t_changes == 50;
203             }
204              
205             my $image = $self->{IMAGE};
206              
207             # Grey out label background
208             # foreach my $l1 (@{$self->{LABELS}}) {
209             # my($l1x, $l1y, $l1w, $l1h) =
210             # ($l1->labelx, $l1->labely, $l1->labelwidth, $l1->labelheight);
211             # $image->set_color(255, 255, 255, 32);
212             # $image->fill_rectangle($l1x, $l1y, $l1w, $l1h);
213             # }
214             map { $_->draw_dot($image) } @{ $self->{LABELS} };
215             map { $_->draw_label($image) } @{ $self->{LABELS} };
216              
217             $image->save($filename);
218             }
219              
220             sub _draw_oldish {
221             my ( $self, $filename ) = @_;
222              
223             my @labels = ( @{ $self->{LABELS} } );
224             my $overlaps = $self->_number_of_overlaps;
225              
226             foreach ( 1 .. 20 ) {
227             foreach my $l1 (@labels) {
228             my ( $l1x, $l1y, $l1w, $l1h )
229             = ( $l1->{X}, $l1->{Y}, $l1->{LABELW}, $l1->{LABELH} );
230              
231             my ( $oldlabelx, $oldlabely ) = ( $l1->{LABELX}, $l1->{LABELY} );
232             my $old_overlaps_single = $self->_number_of_overlaps_single($l1);
233             my $mode = int( rand(4) );
234             if ( $mode == 0 ) {
235             $l1->{LABELX} = $l1x + $Image::WorldMap::Label::XOFFSET;
236             $l1->{LABELY} = $l1y + $Image::WorldMap::Label::YOFFSET;
237             } elsif ( $mode == 1 ) {
238             $l1->{LABELX}
239             = $l1x - $l1w - $Image::WorldMap::Label::XOFFSET;
240             $l1->{LABELY} = $l1y + $Image::WorldMap::Label::YOFFSET;
241             } elsif ( $mode == 2 ) {
242             $l1->{LABELX} = $l1x - $l1w / 2;
243             $l1->{LABELY} = $l1y - $l1h;
244             } elsif ( $mode == 3 ) {
245             $l1->{LABELX} = $l1x - $l1w / 2;
246             $l1->{LABELY} = $l1y;
247             }
248              
249             my $overlaps_single = $self->_number_of_overlaps_single($l1);
250             if ( $overlaps_single > $old_overlaps_single ) {
251             $l1->{LABELX} = $oldlabelx;
252             $l1->{LABELY} = $oldlabely;
253             } else {
254             $overlaps += $overlaps_single - $old_overlaps_single;
255             }
256             }
257              
258             warn "Overlaps: $overlaps\n";
259             last if $overlaps == 0;
260             }
261              
262             my $image = $self->{IMAGE};
263              
264             # foreach my $l1 (@{$self->{LABELS}}) {
265             # my($l1x, $l1y, $l1w, $l1h) =
266             # ($l1->labelx, $l1->labely, $l1->labelwidth, $l1->labelheight);
267             # $image->set_color(255, 255, 255, 32);
268             # $image->fill_rectangle($l1x, $l1y, $l1w, $l1h);
269             # }
270             map { $_->draw_dot($image) } @{ $self->{LABELS} };
271             map { $_->draw_label($image) } @{ $self->{LABELS} };
272              
273             $image->save($filename);
274             }
275              
276             sub _number_of_overlaps_single {
277             my ( $self, $l1 ) = @_;
278              
279             my $overlaps = 0;
280             my @labels = ( @{ $self->{LABELS} } );
281              
282             my $l1text = $l1->{TEXT};
283             my ( $l1x, $l1y, $l1w, $l1h )
284             = ( $l1->{LABELX}, $l1->{LABELY}, $l1->{LABELW}, $l1->{LABELH} );
285             return unless $l1text;
286             foreach my $l2 (@labels) {
287             next if $l1 eq $l2;
288             my $l2text = $l2->{TEXT};
289             next unless $l2text;
290              
291             # warn "Comparing $l1text against $l2text...\n";
292             my ( $l2x, $l2y, $l2w, $l2h )
293             = ( $l2->{LABELX}, $l2->{LABELY}, $l2->{LABELW}, $l2->{LABELH} );
294             my $x = $l1x > $l2x ? $l1x : $l2x;
295             my $y = $l1y > $l2y ? $l1y : $l2y;
296             my $w
297             = ( $l1x + $l1w < $l2x + $l2w ? $l1x + $l1w : $l2x + $l2w ) - $x;
298             my $h
299             = ( $l1y + $l1h < $l2y + $l2h ? $l1y + $l1h : $l2y + $l2h ) - $y;
300             if ( $w > 0 && $h > 0 ) {
301             $overlaps++;
302             }
303             }
304             return $overlaps;
305             }
306              
307             sub _number_of_overlaps {
308             my ($self) = @_;
309             my %seen;
310              
311             my $overlaps = 0;
312             my @labels = ( @{ $self->{LABELS} } );
313              
314             foreach my $l1 (@labels) {
315             my ( $l1x, $l1y, $l1w, $l1h, $l1text ) = (
316             $l1->{LABELX}, $l1->{LABELY}, $l1->{LABELW},
317             $l1->{LABELH}, $l1->{TEXT}
318             );
319             next unless $l1text;
320             foreach my $l2 (@labels) {
321             next if $seen{$l1}{$l2}++;
322             next if $seen{$l2}{$l1}++;
323             next if $l1 eq $l2;
324             my $l2text = $l2->{TEXT};
325             next unless $l2text;
326              
327             # warn "Comparing $l1text against $l2text...\n";
328             my ( $l2x, $l2y, $l2w, $l2h )
329             = ( $l2->{LABELX}, $l2->{LABELY}, $l2->{LABELW},
330             $l2->{LABELH} );
331             my $x = $l1x > $l2x ? $l1x : $l2x;
332             my $y = $l1y > $l2y ? $l1y : $l2y;
333             my $w = ( $l1x + $l1w < $l2x + $l2w ? $l1x + $l1w : $l2x + $l2w )
334             - $x;
335             my $h = ( $l1y + $l1h < $l2y + $l2h ? $l1y + $l1h : $l2y + $l2h )
336             - $y;
337              
338             # warn "Overlap: $w x $h\n";
339             if ( $w > 0 && $h > 0 ) {
340              
341             # warn "Overlaps!\n";
342             $overlaps++;
343             }
344              
345             }
346             }
347             return $overlaps;
348             }
349              
350             # fisher_yates_shuffle( \@array ) :
351             # generate a random permutation of @array in place
352             sub _fisher_yates_shuffle {
353             my $array = shift;
354             my $i;
355             for ( $i = @$array; --$i; ) {
356             my $j = int rand( $i + 1 );
357             @$array[ $i, $j ] = @$array[ $j, $i ];
358             }
359             }
360              
361             __END__