File Coverage

blib/lib/Imager/Bing/MapLayer/Level.pm
Criterion Covered Total %
statement 121 198 61.1
branch 16 58 27.5
condition 10 30 33.3
subroutine 35 45 77.7
pod 7 7 100.0
total 189 338 55.9


line stmt bran cond sub pod time code
1             package Imager::Bing::MapLayer::Level;
2              
3 3     3   121265 use v5.10.1;
  3         13  
  3         159  
4              
5 3     3   1214 use Moose;
  3         607277  
  3         30  
6             with 'Imager::Bing::MapLayer::Role::TileClass';
7             with 'Imager::Bing::MapLayer::Role::FileHandling';
8             with 'Imager::Bing::MapLayer::Role::Centroid';
9             with 'Imager::Bing::MapLayer::Role::Misc';
10              
11 3     3   30027 use Carp qw/ confess /;
  3         7  
  3         285  
12 3     3   19 use Class::MOP::Method;
  3         6  
  3         76  
13 3     3   1169 use Const::Fast;
  3         1189  
  3         27  
14 3     3   5305 use Imager;
  3         194102  
  3         30  
15 3     3   246 use List::Util qw/ min max /;
  3         9  
  3         275  
16 3     3   22 use Moose::Util::TypeConstraints;
  3         6  
  3         49  
17 3     3   9320 use MooseX::StrictConstructor;
  3         31588  
  3         30  
18 3     3   22137 use POSIX::2008 qw/ round /;
  3         2072  
  3         451  
19              
20 3         641 use Imager::Bing::MapLayer::Utils qw/
21             $MIN_ZOOM_LEVEL $MAX_ZOOM_LEVEL $TILE_WIDTH $TILE_HEIGHT
22             width_at_level bounding_box pixel_to_tile_coords tile_coords_to_quad_key
23             optimize_points get_ground_resolution
24 3     3   852 /;
  3         7  
25              
26 3     3   2438 use Imager::Bing::MapLayer::Image;
  3         9  
  3         153  
27 3     3   2163 use Imager::Bing::MapLayer::Tile;
  3         14  
  3         237  
28              
29 3     3   31 use version 0.77; our $VERSION = version->declare('v0.1.8');
  3         92  
  3         26  
30              
31             =head1 NAME
32              
33             Imager::Bing::MapLayer::Level - zoom levels for Bing Maps
34              
35             =head1 SYNOPSIS
36              
37             my $level = Imager::Bing::MapLayer::Level->new(
38             level => $level, # zoom level
39             base_dir => $dir, # base directory (default '.')
40             overwrite => 1, # overwrite existing (default)
41             autosave => 1, # save on exit (default)
42             in_memory => 0, # keep tiles in memory (default false)
43             combine => 'darken', # tile combination method (default)
44             );
45              
46             $level->polygon(
47             points => $points, # listref to [ lat, lon ] points
48             fill => Imager::Fill->new( ... ), #
49             );
50              
51             =head1 DESCRIPTION
52              
53             This module is for internal use by L<Imager::Bing::MapLayer>.
54              
55             =begin :internal
56              
57             This module supports drawing on specific zoom levels.
58              
59             =head1 ATTRIBUTES
60              
61             =head2 C<level>
62              
63             The zoom level.
64              
65             =cut
66              
67             has 'level' => (
68             is => 'ro',
69             isa => subtype(
70             as 'Int',
71             where { ( $_ >= $MIN_ZOOM_LEVEL ) && ( $_ <= $MAX_ZOOM_LEVEL ) }
72             ),
73             );
74              
75             =head2 C<tiles>
76              
77             A hash reference of C<Imager::Bing::MapLayer::Tile> objects.
78              
79             The keys are tile coordinates of the form C<$tile_x . $; . $tile_y>.
80              
81             =cut
82              
83             has 'tiles' => (
84             is => 'ro',
85             isa => 'HashRef[Imager::Bing::MapLayer::Tile]',
86             default => sub { return {} },
87             init_arg => undef,
88             );
89              
90             =head2 C<timeouts>
91              
92             =cut
93              
94             # TODO - the last-modified value should be saved with each tile?
95              
96             has 'timeouts' => (
97             is => 'ro',
98             isa => 'HashRef[Int]',
99             default => sub { return {} },
100             );
101              
102             =head2 C<_last_cleanup_time>
103              
104             The time that the last tile cleanup was run.
105              
106             =cut
107              
108             has '_last_cleanup_time' => (
109             is => 'rw',
110             isa => 'Int',
111             lazy => 1,
112             default => sub { return time; },
113             );
114              
115             =head2 C<_max_buffer_breadth>
116              
117             The maximum width and height of the temporary L<Imager> image.
118              
119             Generally, you do not need to be concerned with this parameter, unless
120             you get C<malloc> errors when rendering tiles.
121              
122             =cut
123              
124             has '_max_buffer_breadth' => (
125             is => 'ro',
126             isa => 'Int',
127             default => 1024 * 4, #
128             );
129              
130             =head1 METHODS
131              
132             =head2 C<width>
133              
134             The width of the layer.
135              
136             =cut
137              
138             sub width {
139 0     0 1 0 my ($self) = @_;
140 0         0 return width_at_level( $self->level );
141             }
142              
143             =head2 C<height>
144              
145             The height of the layer.
146              
147             =cut
148              
149             sub height {
150 0     0 1 0 my ($self) = @_;
151 0         0 return width_at_level( $self->level );
152             }
153              
154             =head2 C<latlon_to_pixel>
155              
156             my ($x, $y) = $level->latlon_to_pixel($latitude, $longitude);
157              
158             Translates a latitude and longitude coordinate into a pixel on the
159             zoom level.
160              
161             =cut
162              
163             sub latlon_to_pixel {
164 13     13 1 39 my ( $self, @latlon ) = @_;
165 13         569 return Imager::Bing::MapLayer::Utils::latlon_to_pixel( $self->level,
166             @latlon );
167             }
168              
169             =head2 C<_translate_points>
170              
171             This is a utility method for translating C<points> parameters from
172             L<Imager> methods.
173              
174             At lower zoom levels, these are "optimized" by removing duplicate
175             adjacent points.
176              
177             =cut
178              
179             sub _translate_points {
180 0     0   0 my ( $self, $points ) = @_;
181 0         0 return optimize_points(
182 0         0 [ map { [ $self->latlon_to_pixel( @{$_} ) ] } @{$points} ] );
  0         0  
  0         0  
183             }
184              
185             =head2 C<_translate_coords>
186              
187             This is a utility method for translating C<box> parameters from
188             L<Imager> methods.
189              
190             =cut
191              
192             sub _translate_coords {
193 0     0   0 my ( $self, $points ) = @_;
194 3     3   1676 no warnings 'once';
  3         14  
  3         7659  
195 0         0 return [ pairmap { ( $self->latlon_to_pixel( $a, $b ) ) } @{$points} ];
  0         0  
  0         0  
196             }
197              
198             =head2 C<_translate_radius>
199              
200             my $pixels = $level->_translate_radius( $meters, $min_pixels);
201              
202             This method translates the C<r> parameter for cirlces and arcs from
203             meters into pixels.
204              
205             If the C<$min_pixels> parameter is given, then the radius will be no
206             smaller than the given number of pixels. (This is useful to ensure
207             that small circles show up on lower zoom levels.)
208              
209             =cut
210              
211             sub _translate_radius {
212 0     0   0 my ( $self, $r, $min_r ) = @_;
213              
214 0   0     0 return max(
215             round(
216             $r / get_ground_resolution(
217             $self->level, $self->centroid_latitude
218             )
219             ),
220             $min_r // 0
221             );
222             }
223              
224             # This is a hash that says which utility method to use for translating
225             # point arguments for Imager methods.
226              
227             const my %ARG_TO_METHOD => (
228             points => '_translate_points',
229             box => '_translate_coords', # TODO - this does not seem to work
230             r => '_translate_radius',
231             );
232              
233             =head2 C<_translate_point_arguments>
234              
235             This is an I<internal> utility method for translating coordinate
236             parameters from L<Imager> methods.
237              
238             =cut
239              
240             sub _translate_point_arguments {
241 13     13   64 my ( $self, %args ) = @_;
242              
243 13         26 my %i_args;
244              
245 13         63 foreach my $arg ( keys %ARG_TO_METHOD ) {
246              
247 39 50       252 if ( my $method = $self->can( $ARG_TO_METHOD{$arg} ) ) {
248              
249 39 50       132 $i_args{$arg}
250             = $self->$method( $args{$arg}, $args{"-min_${arg}"} )
251             if ( exists $args{$arg} );
252              
253             }
254              
255             }
256              
257             # Ideally, we could translate x and y separately, using the
258             # centroid_longitude and centroid_latitude as defaults for the
259             # missing coordinate. But this does not seem to work properly.
260             # So we translate them together.
261              
262             # TODO - clean up this code.
263              
264 13         48 foreach my $suffix ( '', qw/ 1 2 min max / ) {
265              
266 65         126 my $x = $args{ 'x' . $suffix };
267 65         107 my $y = $args{ 'y' . $suffix };
268              
269             # If either the x or y parameter is missing, then it won't be
270             # translated.
271              
272 65 100 66     235 if ( ( defined $x ) && ( defined $y ) ) {
273              
274 13 50 33     81 if ( ( ref $x ) || ( ref $y ) ) {
275              
276 0 0       0 if ($suffix) {
277              
278 0         0 confess
279             sprintf(
280             "x%s and y%s as coordinate lists are not supported");
281              
282             } else {
283              
284             # If there are a pair of x,y coordinate lists,
285             # then we just reassemble them into a 'points'
286             # parameter and translate that.
287              
288             # Note that this is based on how Imager treats
289             # these.
290              
291             # TODO - rewrite this code
292              
293 0 0       0 my @xs = ( ref $x ) ? @{$x} : ($x);
  0         0  
294 0 0       0 my @ys = ( ref $y ) ? @{$y} : ($y);
  0         0  
295              
296 0         0 my $last_x = shift @xs;
297 0         0 my $last_y = shift @ys;
298              
299 0         0 my @points = ( [ $last_y, $last_x ] );
300              
301 0   0     0 while ( @xs || @ys ) {
302              
303 0   0     0 my $this_x = ( shift @xs ) // $last_x;
304 0   0     0 my $this_y = ( shift @ys ) // $last_y;
305              
306 0         0 push @points, [ $this_y, $this_x ];
307              
308 0         0 ( $last_x, $last_y ) = ( $this_x, $this_y );
309              
310             }
311              
312 0         0 $i_args{points} = $self->_translate_points( \@points );
313              
314             }
315              
316             } else {
317              
318 13         68 my ( $pixel_x, $pixel_y ) = $self->latlon_to_pixel( $y, $x );
319 13         50 $i_args{ 'x' . $suffix } = $pixel_x;
320 13         54 $i_args{ 'y' . $suffix } = $pixel_y;
321              
322             }
323              
324             }
325              
326             }
327              
328 13         73 return %i_args;
329             }
330              
331             =head2 C<_tile_coords_to_internal_key>
332              
333             my $key = $level->_tile_coords_to_internal_key($tile_x, $tile_y);
334              
335             This is an I<internal method> for generating a key for the L</tiles>
336             and L</timeouts>.
337              
338             We join the tile coordinates into a small key to use for this, instead
339             of generating a quad key (which requires more work, and is only needed
340             for creating a new tile).
341              
342             =cut
343              
344             sub _tile_coords_to_internal_key {
345 13     13   25 my ( $self, $tile_x, $tile_y ) = @_;
346 13         69 return join( $;, $tile_x, $tile_y );
347             }
348              
349             =head2 C<_internal_key_to_tile_coords>
350              
351              
352             my ($tile_x, $tile_y) = $level->_internal_key_to_tile_coords($key);
353              
354             This is an I<internal> method for determining tile coordinates from a
355             key. It is the inverse of L</_tile_coords_to_internal_key>.
356              
357             =cut
358              
359             sub _internal_key_to_tile_coords {
360 0     0   0 my ( $self, $key ) = @_;
361 0         0 return ( split $;, $key );
362             }
363              
364             =head2 C<_load_tile>
365              
366             my $tile = $level->_load_tile($tile_x, $tile_y, $overwrite);
367              
368             This is an I<internal> method that loads a tile for this level, if it
369             exists. Otherwise it creates a new tile.
370              
371             =cut
372              
373             sub _load_tile {
374 13     13   26 my ( $self, $tile_x, $tile_y, $overwrite ) = @_;
375              
376 13         615 my $class = $self->tile_class;
377              
378 13         547 return $class->new(
379             quad_key => tile_coords_to_quad_key( $self->level, $tile_x, $tile_y ),
380             base_dir => $self->base_dir,
381             overwrite => $overwrite,
382             autosave => $self->autosave,
383             );
384              
385             }
386              
387             =head2 C<_cleanup_tiles>
388              
389             $level->_cleanup_tiles();
390              
391             This is an I<internal> method that removes tiles from memory that have
392             not been drawn to within the L</in_memory> timeout.
393              
394             =cut
395              
396             sub _cleanup_tiles {
397 14     14   677 my ($self) = @_;
398              
399 14 50       705 return unless $self->in_memory;
400              
401             # TODO: add an optional free memory parameter that tries to delete
402             # tiles until enough memory is freed.
403              
404 0         0 my $time = time;
405              
406 0 0       0 if ( ( $self->_last_cleanup_time + $self->in_memory ) < $time ) {
407              
408 0         0 my $tiles = $self->tiles;
409 0         0 my $timeouts = $self->timeouts;
410              
411 0         0 foreach my $key (
  0         0  
412 0         0 sort { $timeouts->{$a} <=> $timeouts->{$b} }
413             keys %{$timeouts}
414             )
415             {
416              
417 0 0       0 next unless $tiles->{$key};
418              
419 0 0       0 last if $timeouts->{$key} > $time;
420              
421             # For some reason, ignoring save when
422             # $self->autosave is true does not seem to
423             # consistently save the tile. So we always save
424             # it.
425              
426 0         0 $tiles->{$key}->save;
427              
428 0         0 $tiles->{$key} = undef;
429              
430 0         0 delete $timeouts->{$key};
431              
432             }
433              
434             }
435              
436 0         0 $self->_last_cleanup_time($time);
437             }
438              
439             =head2 C<_make_imager_wrapper_method>
440              
441             This is an I<internal> function generates wrapper methods for a tile's
442             L<Imager::Draw> methods.
443              
444             Basically, it calculates the bounding box for whatever is to be drawn, and creates a
445             L<Imager::Bing::MapLayer::Image> "pseudo-tile" to draw on.
446              
447             It then composes pieces from the pseudo tile onto the actual tile
448             (using the L</combine> method>).
449              
450             =cut
451              
452             sub _make_imager_wrapper_method {
453 36     36   71 my ( $class, $opts ) = @_;
454              
455 36   100     118 $opts->{args} //= [];
456 36   50     89 $opts->{name} //= "undef"; # to catch missing method names
457              
458             $class->meta->add_method(
459              
460             $opts->{name} => sub {
461              
462 13     13   97 my ( $self, %args ) = @_;
        13      
        13      
        13      
        13      
        13      
        13      
        13      
        13      
        13      
        13      
        13      
        13      
463              
464             return
465             if (
466 13 50 33     697 ( $args{'-min_level'} // $MIN_ZOOM_LEVEL ) > $self->level );
467             return
468             if (
469 13 50 33     675 ( $args{'-max_level'} // $MAX_ZOOM_LEVEL ) < $self->level );
470              
471 13         88 my %imager_args = $self->_translate_point_arguments(%args);
472              
473 13         35 foreach my $arg ( @{ $opts->{args} } ) {
  13         63  
474 12 50       184 $imager_args{$arg} = $args{$arg} if ( exists $args{$arg} );
475             }
476              
477             # Clean up old tiles before allocating new ones.
478              
479 13         59 $self->_cleanup_tiles();
480              
481 13         77 my ( $left, $top, $right, $bottom ) = bounding_box(%imager_args);
482              
483 13         51 my ( $width, $height )
484             = ( 1 + $right - $left, 1 + $bottom - $top );
485              
486             # We create a temporary image and draw on it. We then
487             # compose the appropriate pieces of that image on each
488             # tile. This is faster than drawing the image on every
489             # tile, for complex polylines and polygons like geographic
490             # boundaries.
491              
492             # But we cannot allocate too-large a temporary image, so
493             # we still need to draw them in pieces.
494              
495             # TODO: use Sys::MemInfo qw/ freemem / to check free
496             # memory, and adjust the temporary tile size
497             # accordingly. (Assume memory req is $width * height * 4)
498              
499             # TODO - get* methods should be handled differently.
500              
501 13         29 my ( $this_left, $this_top ) = ( $left, $top );
502              
503 13         38 while ( $this_left <= $right ) {
504              
505 13         687 my $this_width = min( 1 + $right - $this_left,
506             $self->_max_buffer_breadth );
507              
508 13         37 my $this_right = $this_left + $this_width - 1;
509              
510 13         35 while ( $this_top <= $bottom ) {
511              
512 13         593 my $this_height = min( 1 + $bottom - $this_top,
513             $self->_max_buffer_breadth );
514              
515 13         28 my $this_bottom = $this_top + $this_height - 1;
516              
517             # Note: we cannot catch malloc errors if the image
518             # is too large. Perl will just exit. See
519             # L<perldiag> for more information.
520              
521 13         127 my $image = Imager::Bing::MapLayer::Image->new(
522             pixel_origin => [ $this_left, $this_top ],
523             width => $this_width,
524             height => $this_height,
525             );
526              
527 13 50       10751 unless ($image) {
528              
529 0         0 confess
530             sprintf(
531             "unable to create image for (%d , %d) (%d , %d) at level %d: %s",
532             $this_left, $this_top, $this_right, $this_bottom,
533             $self->level, $_ );
534              
535             }
536              
537 13 50       86 if ( my $method = $image->can( $opts->{name} ) ) {
538              
539 13         71 my $result = $image->$method(%imager_args);
540              
541             # Now get the tile boundaries
542              
543 13         20892 my ( $tile_left, $tile_top )
544             = pixel_to_tile_coords( $this_left, $this_top );
545 13         43 my ( $tile_right, $tile_bottom )
546             = pixel_to_tile_coords( $this_right,
547             $this_bottom );
548              
549 13         666 my $tiles = $self->tiles;
550 13         578 my $timeouts = $self->timeouts;
551              
552 13         59 for (
553             my $tile_y = $tile_top;
554             $tile_y <= $tile_bottom;
555             $tile_y++
556             )
557             {
558              
559 13         52 for (
560             my $tile_x = $tile_left;
561             $tile_x <= $tile_right;
562             $tile_x++
563             )
564             {
565              
566 13         64 my $key
567             = $self->_tile_coords_to_internal_key(
568             $tile_x, $tile_y );
569              
570 13 50       61 unless ( defined $tiles->{$key} ) {
571              
572 13 50 66     672 my $overwrite
573             = ( exists $tiles->{$key}
574             && $self->in_memory )
575             ? 0
576             : $self->overwrite;
577              
578 13         63 $tiles->{$key}
579             = $self->_load_tile( $tile_x, $tile_y,
580             $overwrite );
581              
582 13         15556 $timeouts->{$key}
583             = time() + $self->in_memory;
584             }
585              
586 13 50       55 if ( my $tile = $tiles->{$key} ) {
587              
588 13         601 my $crop_left
589             = max( $this_left, $tile->left );
590 13         594 my $crop_top
591             = max( $this_top, $tile->top );
592              
593 13         595 my $crop_width = 1 + min(
594             $this_right - $crop_left,
595             $tile->right - $crop_left
596             );
597              
598 13         608 my $crop_height = 1 + min(
599             $this_bottom - $crop_top,
600             $tile->bottom - $crop_top
601             );
602              
603 13 50       87 my $crop = $image->crop(
604             left => $crop_left,
605             top => $crop_top,
606             width => $crop_width,
607             height => $crop_height,
608             ) or confess $image->errstr;
609              
610 13         2105 $tile->compose(
611             src => $crop,
612             left => $crop_left,
613             top => $crop_top,
614             width => $crop->getwidth,
615             height => $crop->getheight,
616             combine => $self->combine,
617             );
618              
619             # force garbage collection
620 13         1080 $crop = undef;
621              
622 13 50       78 if ( $self->in_memory ) {
623              
624 0         0 $timeouts->{$key}
625             = time() + $self->in_memory;
626              
627             } else {
628              
629             # See comments about regarding
630             # autosave consistency.
631              
632 13         64 $tile->save;
633              
634 13         13820 $tiles->{$key} = undef;
635              
636             }
637              
638             }
639              
640             }
641             }
642              
643 13         3714 $image = undef; # force garbage collection
644             }
645              
646             else {
647              
648 0         0 confess sprintf( "invalid method name: %s",
649             $opts->{name} );
650              
651             }
652              
653 13         64 $this_top += $this_height;
654              
655             }
656              
657 13         2232 $this_left += $this_width;
658             }
659              
660             },
661 36         126 );
662              
663             }
664              
665             __PACKAGE__->_make_imager_wrapper_method( { name => 'radial_circle', } );
666              
667             __PACKAGE__->_make_imager_wrapper_method( { name => 'getpixel', } );
668              
669             __PACKAGE__->_make_imager_wrapper_method(
670             { name => 'setpixel',
671             args => [qw/ color /],
672             }
673             );
674              
675             __PACKAGE__->_make_imager_wrapper_method(
676             { name => 'line',
677             args => [qw/ color endp aa antialias /],
678             }
679             );
680              
681             __PACKAGE__->_make_imager_wrapper_method(
682             { name => 'box',
683             args => [qw/ color filled fill /],
684             }
685             );
686              
687             __PACKAGE__->_make_imager_wrapper_method(
688             { name => 'polyline',
689             args => [qw/ color aa antialias /],
690             }
691             );
692              
693             __PACKAGE__->_make_imager_wrapper_method(
694             { name => 'polygon',
695             args => [qw/ color fill /],
696             }
697             );
698              
699             __PACKAGE__->_make_imager_wrapper_method(
700             { name => 'arc',
701             args => [qw/ d1 d2 color fill aa filled /],
702             }
703             );
704              
705             __PACKAGE__->_make_imager_wrapper_method(
706             { name => 'circle',
707             args => [qw/ color fill aa filled /],
708             }
709             );
710              
711             __PACKAGE__->_make_imager_wrapper_method(
712             { name => 'flood_fill',
713             args => [qw/ color border fill /],
714             }
715             );
716              
717             __PACKAGE__->_make_imager_wrapper_method(
718             { name => 'string',
719             args => [
720             qw/ string font aa align channel color size sizew utf8 vlayout text /
721             ],
722             }
723             );
724              
725             __PACKAGE__->_make_imager_wrapper_method(
726             { name => 'align_string',
727             args => [
728             qw/ string font aa valign halign channel color size sizew utf8 vlayout text /
729             ],
730             }
731             );
732              
733             # TODO/FIXME - generic method with callbacks to apply a function to a
734             # all tiles on a level?
735              
736             =head2 C<filter>
737              
738             Apply a L<Imager::Filter> to every tile in the level.
739              
740             Only tiles that have been drawn to will have filters applied to them.
741              
742             =cut
743              
744             sub filter {
745 0     0 1   my ( $self, %args ) = @_;
746              
747 0           foreach my $key ( keys %{ $self->tiles } ) {
  0            
748              
749 0           my $tile = $self->tiles->{$key};
750              
751 0 0         unless ($tile) { # assume $self->in_memory
752              
753 0           my ( $tile_x, $tile_y )
754             = $self->_internal_key_to_tile_coords($key);
755              
756             # We assume that a tile should not be overwritten
757              
758 0 0         my $overwrite = $self->in_memory ? 0 : $self->overwrite;
759              
760 0           $tile = $self->_load_tile( $tile_x, $tile_y, $overwrite );
761              
762             }
763              
764 0 0         if ($tile) {
765              
766 0 0         $tile->image->filter(%args)
767             or confess $tile->image->errstr;
768              
769             # See comments abouve regarding autosave consistency.
770              
771 0           $tile->save;
772              
773             }
774             }
775              
776             }
777              
778             =head2 C<colourise>
779              
780             =head2 C<colorize>
781              
782             $level->colourise();
783              
784             Runs the C<colourise> method on tiles.
785              
786             This method is intended to be run for after rendering on the level is
787             completed, i.e. for post-processing of heatmap tiles.
788              
789             =cut
790              
791             sub colourise {
792 0     0 1   my ( $self, %args ) = @_;
793              
794 0           foreach my $key ( keys %{ $self->tiles } ) {
  0            
795              
796 0           my $tile = $self->tiles->{$key};
797              
798 0 0         unless ($tile) { # assume $self->in_memory
799              
800 0           my ( $tile_x, $tile_y )
801             = $self->_internal_key_to_tile_coords($key);
802              
803             # We assume that a tile should not be overwritten
804              
805 0 0         my $overwrite = $self->in_memory ? 0 : $self->overwrite;
806              
807 0           $tile = $self->_load_tile( $tile_x, $tile_y, $overwrite );
808              
809             }
810              
811 0 0         if ($tile) {
812              
813 0           $tile->colourise(%args);
814              
815             # See comments about regarding autosave consistency.
816              
817 0           $tile->save;
818              
819             }
820             }
821              
822             }
823              
824             sub colorize {
825 0     0 1   my ( $self, %args ) = @_;
826 0           $self->colourise(%args);
827             }
828              
829             =head2 C<save>
830              
831             $level->save();
832              
833             Saves the titles.
834              
835             If L<in_memory> is non-zero, tiles that have timed out are removed
836             from memory.
837              
838             =cut
839              
840             sub save {
841 0     0 1   my ( $self, @args ) = @_;
842              
843 0           $self->_cleanup_tiles();
844              
845 0           foreach my $tile ( values %{ $self->tiles } ) {
  0            
846 0 0         $tile->save(@args) if ($tile);
847             }
848             }
849              
850             =end :internal
851              
852             =cut
853              
854 3     3   24 use namespace::autoclean;
  3         7  
  3         28  
855              
856             1;