Gtk2-Ex-Geo

 view release on metacpan or  search on metacpan

lib/Gtk2/Ex/Geo/Overlay.pm  view on Meta::CPAN

## @class Gtk2::Ex::Geo::Overlay
# @todo Implement select linestring
# @brief A geocanvas widget
# @author Copyright (c) Ari Jolma
# @author This library is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself, either Perl version 5.8.5 or,
# at your option, any later version of Perl 5 you may have available.
package Gtk2::Ex::Geo::Overlay;

use strict;
use POSIX;
use Scalar::Util qw(blessed);
use Carp;
use Glib qw/TRUE FALSE/;
use Geo::OGC::Geometry;

use vars qw / $EDIT_SNAP_DISTANCE /;

our $VERSION = '0.62'; # same as Geo.pm

=pod

=head1 NAME

Gtk2::Ex::Geo::Overlay - A Gtk2 widget for a visual overlay of geospatial data

The <a href="http://geoinformatics.aalto.fi/doc/Geoinformatica/html/">
documentation of Gtk2::Ex::Geo</a> is written in doxygen format.

=cut

$EDIT_SNAP_DISTANCE = 5;

use Glib::Object::Subclass
    Gtk2::ScrolledWindow::,
    signals => {
	update_layers => {},  # sent just before the layers are rendered
	new_selection => {},  # sent after the user has changed the selection
	drawing_changed => {},# sent after the user has changed the drawing
	zoomed_in => {},      # deprecated
	extent_changed => {}, # deprecated
	motion_notify => {},  # the mouse has a new location on the map
	map_updated => {},    # deprecated
	pixmap_ready => {},   # sent just after pixmap is ready, but selection and drawing 
	                      # haven't been rendered, connect to this for annotations
    },
    properties => 
    [
     Glib::ParamSpec->double 
     (
      'zoom_factor', 'Zoom factor', 
      'Zoom multiplier when user presses + or -',
      0.1, 1000, 1.2, [qw/readable writable/]
      ),
     Glib::ParamSpec->double 
     (
      'step', 'Step', 
      'One step when scrolling is window width/height divided by step',
      1, 100, 8, [qw/readable writable/]
      ),
     ]
    ;

## @ignore
sub INIT_INSTANCE {
    my $self = shift;

    $self->{image} = Gtk2::Image->new;
    $self->{image}->set_size_request(0, 0);
    $self->{image}->signal_connect(size_allocate => \&size_allocate, $self);

    $self->{event_box} = Gtk2::EventBox->new;

    $self->{event_box}->add($self->{image});

    $self->{event_box}->signal_connect(button_press_event => \&button_press_event, $self);
    $self->{event_box}->signal_connect(button_release_event => \&button_release_event, $self);

    $self->{event_box}->add_events('pointer-motion-mask');
    $self->{event_box}->signal_connect(motion_notify_event => \&motion_notify, $self);

    $self->signal_connect(key_press_event => \&key_press_event, $self);
    $self->signal_connect(key_release_event => \&key_release_event, $self);

    $self->{selecting} = '';

lib/Gtk2/Ex/Geo/Overlay.pm  view on Meta::CPAN

	} else {
	    @size = @s;
	}
    }
    $self->zoom_to(@size) if @size;
}

## @ignore
sub value_changed {
    my(undef, $self) = @_;
    push @{$self->{zoom_stack}}, [@{$self->{offset}}, $self->{pixel_size}];
    $self->{offset} = [$self->get_hadjustment()->value(), $self->get_vadjustment()->value()];
    $self->signal_emit('extent-changed'); 
    $self->render();
    return 1;
}

## @method get_focus()
# @deprecated use get_viewport_of_selection or get_viewport
# @returns the visible area or the selection, if one exists, as ($minx, $miny, $maxx, $maxy).
sub get_focus {
    my($self) = @_;
    if ($self->{selection}) {
	my $e = $self->{selection}->Envelope;
	my $ll = $e->PointN(1);
	my $ur = $e->PointN(3);
	return ($ll->X, $ll->Y, $ur->X, $ur->Y);
    } else {
	my $minX = $self->{minX}+$self->{offset}[0]*$self->{pixel_size};
	my $maxY = $self->{maxY}-$self->{offset}[1]*$self->{pixel_size};
	return ($minX, $maxY-$self->{viewport_size}->[1]*$self->{pixel_size},
		$minX+$self->{viewport_size}->[0]*$self->{pixel_size}, $maxY);
    }
}

{
    package Gtk2::Ex::Geo::PseudoOverlay;
    sub round {
	return int($_[0] + .5 * ($_[0] <=> 0));
    }
    sub new {
	my($class, $minX, $maxY, $pixel_size) = @_;	
	my $self = { minX => $minX,
		     maxY => $maxY,
		     pixel_size => $pixel_size
	};
	bless($self, $class); 
    }
    sub point2pixmap_pixel {
	my($self, @p) = @_;
	return (round(($p[0] - $self->{minX})/$self->{pixel_size} - 0.5),
		round(($self->{maxY} - $p[1])/$self->{pixel_size} - 0.5));
    }
    package Gtk2::Ex::Geo::Canvas;
    our @ISA = qw(Gtk2::Gdk::Pixbuf);
 
    sub new {
	my($class, $layers, 
	   $minX, $maxY, $pixel_size, $w_offset, $h_offset,
	   $width, $height,
	   $bg_r, $bg_g, $bg_b, $bg_a, $overlay) = @_;
	
	return unless defined $minX;

	$overlay = Gtk2::Ex::Geo::PseudoOverlay->new($minX, $maxY, $pixel_size) unless $overlay;
	
	my @viewport = ($minX+$pixel_size*$w_offset, 0, 0, $maxY-$pixel_size*$h_offset);
	$viewport[2] = $viewport[0]+$pixel_size*$width;
	$viewport[1] = $viewport[3]-$pixel_size*$height;
	
	my $pb = &Gtk2::Ex::Geo::gtk2_ex_geo_pixbuf_create($width, $height,
							   $viewport[0], $viewport[3],
							   $pixel_size, 
							   $bg_r, $bg_g, $bg_b, $bg_a);
	
	my $surface = &Gtk2::Ex::Geo::gtk2_ex_geo_pixbuf_get_cairo_surface($pb);
	my $cr = Cairo::Context->create($surface);
	
	for my $layer (@$layers) {
	    $layer->render($pb, $cr, $overlay, \@viewport);
	}
	
	undef $cr;
	undef $surface;
	my $self = &Gtk2::Ex::Geo::gtk2_ex_geo_pixbuf_get_pixbuf($pb);
	&Gtk2::Ex::Geo::gtk2_ex_geo_pixbuf_destroy($pb); # does not delete the real pixbuf
	
	bless($self, $class); 
    }
}

package Gtk2::Ex::Geo::Overlay;

## @method render(%params)
# @brief Render the layers on the canvas.
# Each layer's render method is called:
# @code
# $layer->render($pixbuf_struct, $cairo_context, $self, \@viewport);
# @endcode
# If named parameter filename is set, the generated pixbuf is saved to it:
# @code
# $pixbuf->save($params{filename}, $params{type});
# @endcode
# The generated pixmap that is shown is annotated with selection and
# user defined annotation function.
sub render {
    my $self = shift;
    my %opt = @_;

    my $size = $self->{viewport_size};
    return unless $size->[0];

    $self->signal_emit('update-layers');

    my @tmp = ($self->{minX}, $self->{maxY}, $self->{pixel_size}, @{$self->{offset}});
    $self->{pixbuf} = Gtk2::Ex::Geo::Canvas->new
	($self->{layers}, @tmp, @{$size}, @{$self->{bg_color}}[0..3], $self);

    return unless $self->{pixbuf};

    if ($opt{filename}) {
	my $filename = $opt{filename};
	delete $opt{filename};
	my $type = $opt{type};
	delete $opt{type};
	# other options...
	$self->{pixbuf}->save($filename, $type);
	return;
    }

    $self->update_image();

    $self->{old_hadj} = $self->get_hscrollbar->get_adjustment; # prevents a warning
    $self->get_hscrollbar->set_adjustment
	(Gtk2::Adjustment->new($self->{offset}[0], 0, $self->{canvas_size}[0], $size->[0]/20,
			       $size->[0], $size->[0]));

    $self->{old_vadj} = $self->get_vscrollbar->get_adjustment; # prevents a warning
    $self->get_vscrollbar->set_adjustment
	(Gtk2::Adjustment->new($self->{offset}[1], 0, $self->{canvas_size}[1], $size->[1]/20,
			       $size->[1], $size->[1]));

    $self->signal_emit ('map-updated');

}

## @method render_geometry($gc, $geom)
# @brief Render a geometry on the overlay.
#
# @note this should be called annotate or made detect the context (gdk vs cairo)
# Call update_image after you are finished with drawing on the pixmap.
# @param gc A gdk graphics context (Gtk2::Gdk::GC object)
# @param geom A Geo::OGC::Geometry object.
sub render_geometry {
    my($self, $gc, $geom, %param) = @_;
    if ($geom->isa('Geo::OGC::GeometryCollection')) 
    {
	for my $g ($geom->NumGeometries) {
	    $self->render_geometry($gc, $g, %param);
	}
	return;
    } 
    elsif ($geom->isa('Geo::OGC::Point')) 
    {
	my @p = $self->point2pixmap_pixel($geom->X, $geom->Y);
	$self->{pixmap}->draw_line($gc, $p[0]-4, $p[1], $p[0]+4, $p[1]);
	$self->{pixmap}->draw_line($gc, $p[0], $p[1]-4, $p[0], $p[1]+4);
    } 
    elsif ($geom->isa('Geo::OGC::LineString')) 
    {
	my @points;
	for my $p ($geom->NumPoints) {
	    push @points, $self->point2pixmap_pixel($p->X, $p->Y);
	}
	$self->{pixmap}->draw_lines($gc, @points);
	if ($param{enhance_vertices}) {
	    my $pm = $self->{pixmap};
	    for (my $i = 0; $i < $#points; $i+=2) {
		my $x = $points[$i];
		my $y = $points[$i+1];
		$pm->draw_line($gc, $x-4, $y, $x+4, $y);
		$pm->draw_line($gc, $x, $y-4, $x, $y+4);
	    }
	}
    }
    elsif ($geom->isa('Geo::OGC::Polygon')) 
    {
	$self->render_geometry($gc, $geom->ExteriorRing, %param);
	for my $i (0..$geom->NumInteriorRing-1) {
	    $self->render_geometry($gc, $geom->InteriorRingN($i), %param);
	}
    }
}

## @method update_image($annotations, $user_param)
# @param annotations A subroutine for user annotations. Called like
# this: $annotations->($overlay, $pixmap, $gc, $user_param).
# @param user_param User parameter for the annotations.
# @brief Updates the image on the screen to show the changes in pixmap.
sub update_image {
    my($self, $annotations, $user_param) = @_;
    return unless $self->{pixbuf};
    $self->{image}->set_from_pixbuf(undef);
    $self->{pixmap} = $self->{pixbuf}->render_pixmap_and_mask(0);
    my $gc = Gtk2::Gdk::GC->new($self->{pixmap});
    $self->{pixmap}->draw_line($gc, 0, 0, 0, 0); # strange bug, the first line is not drawn
    $self->signal_emit('pixmap_ready');
    my $first = 1;
    if ($self->{drawing}) {
	$gc->set_rgb_fg_color(Gtk2::Gdk::Color->new(@{$self->{drawing_color}}));
	my $style = 'GDK_LINE_SOLID';
	$gc->set_line_attributes(2, $style, 'GDK_CAP_NOT_LAST', 'GDK_JOIN_MITER');
	$self->render_geometry($gc, $self->{drawing}, enhance_vertices => 1);
    }
    if ($self->{selection} and $self->{show_selection}) {
	$gc->set_rgb_fg_color(Gtk2::Gdk::Color->new(@{$self->{selection_color}}));
	my $style = $self->{selection_style};
	$style = 'GDK_LINE_SOLID';
	$gc->set_line_attributes(2, $style, 'GDK_CAP_NOT_LAST', 'GDK_JOIN_MITER');
	$self->render_geometry($gc, $self->{selection});
    }
    $annotations->($self, $self->{pixmap}, $gc, $user_param) if $annotations;
    $self->{image}->set_from_pixmap($self->{pixmap}, undef);
}

## @method zoom($w_offset, $h_offset, $pixel_size)
# @brief Select a part of the world into the visible area.
sub zoom {
    my($self, $w_offset, $h_offset, $pixel_size, $zoomed_in, $not_to_stack) = @_;

    push @{$self->{zoom_stack}}, [@{$self->{offset}}, $self->{pixel_size}] unless $not_to_stack;

    $self->{offset} = [$w_offset, $h_offset];
    
    # sanity check
    $pixel_size = 1 if $pixel_size <= 0;
    $self->{pixel_size} = $pixel_size;

    my $w = ($self->{maxX}-$self->{minX})/$self->{pixel_size};
    my $h = ($self->{maxY}-$self->{minY})/$self->{pixel_size};

    $self->{canvas_size} = [$w, $h];

    $self->render();
    if ($zoomed_in) {
	$self->signal_emit('zoomed-in');
    } else {
	$self->signal_emit('extent-changed');
    }
}

## @ignore
sub _zoom { 
    my($self, $in, $event, $center_x, $center_y, $zoomed_in) = @_;

    return unless $self->{layers} and @{$self->{layers}};



( run in 2.218 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )