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 )