DBIO-PostgreSQL-PostGIS

 view release on metacpan or  search on metacpan

lib/DBIO/PostgreSQL/PostGIS/Geometry.pm  view on Meta::CPAN

package DBIO::PostgreSQL::PostGIS::Geometry;
# ABSTRACT: Lightweight PostGIS geometry/geography value object

use strict;
use warnings;

use DBIO::Exception ();
use DBIO::PostgreSQL::PostGIS::Codec::WKT::Parser  ();
use DBIO::PostgreSQL::PostGIS::Codec::WKT::Builder ();
use DBIO::PostgreSQL::PostGIS::Codec::WKB::Decoder ();


sub new {
  my ($class, %args) = @_;
  my $self = {
    srid          => $args{srid},
    wkt           => $args{wkt},
    ewkb_hex      => $args{ewkb_hex},
    geometry_type => $args{geometry_type},
    coordinates   => $args{coordinates},
  };
  return bless $self, $class;
}


sub from_wkt {
  my ($class, $wkt, %args) = @_;
  DBIO::Exception->throw("from_wkt requires a WKT string") unless defined $wkt;
  my $parsed = DBIO::PostgreSQL::PostGIS::Codec::WKT::Parser->parse($wkt);
  return $class->new(
    wkt           => $wkt,
    srid          => $args{srid},
    geometry_type => $parsed ? $parsed->{type} : undef,
    coordinates   => $parsed ? $parsed->{coords} : undef,
  );
}


sub from_ewkt {
  my ($class, $ewkt) = @_;
  DBIO::Exception->throw("from_ewkt requires an EWKT string") unless defined $ewkt;
  if ($ewkt =~ /\ASRID=(\d+);(.+)\z/s) {
    return $class->new(srid => $1, wkt => $2);
  }
  return $class->new(wkt => $ewkt);
}


sub from_ewkb_hex {
  my ($class, $hex) = @_;
  my $decoded = eval {
    DBIO::PostgreSQL::PostGIS::Codec::WKB::Decoder->decode_hex($hex);
  };
  return undef unless $decoded && $decoded->{type} ne 'unknown';
  return $class->new(
    ewkb_hex      => $hex,
    srid          => $decoded->{srid},
    geometry_type => $decoded->{type},
    coordinates   => $decoded->{coords},
  );
}


sub point {
  my $class = shift;
  my @coords;
  push @coords, shift while @_ && !ref $_[0] && $_[0] =~ /\A-?[0-9.eE+\-]+\z/;
  my %args = @_;
  my $wkt = 'POINT(' . join(' ', @coords) . ')';
  return $class->new(wkt => $wkt, srid => $args{srid}, geometry_type => 'POINT');
}


sub from_lat_lon {
  my ($class, $lat, $lon) = @_;
  return $class->point($lon, $lat, srid => 4326);
}


sub linestring {
  my ($class, $coords, %args) = @_;
  my $wkt = 'LINESTRING(' . join(',', map { join ' ', @$_ } @$coords) . ')';
  return $class->new(wkt => $wkt, srid => $args{srid}, geometry_type => 'LINESTRING');
}


sub polygon {
  my ($class, $rings, %args) = @_;
  my $wkt = 'POLYGON('
    . join(',', map {
        '(' . join(',', map { join ' ', @$_ } @$_) . ')'
      } @$rings)
    . ')';
  return $class->new(wkt => $wkt, srid => $args{srid}, geometry_type => 'POLYGON');
}


sub bbox_polygon {
  my ($class, $xmin, $ymin, $xmax, $ymax, %args) = @_;
  return $class->polygon(
    [[ [$xmin,$ymin], [$xmax,$ymin], [$xmax,$ymax], [$xmin,$ymax], [$xmin,$ymin] ]],
    %args,
  );
}


sub from_geojson {
  my ($class, $gj, %args) = @_;
  my $srid = exists $args{srid} ? $args{srid} : 4326;
  my $type = $gj->{type}
    or DBIO::Exception->throw("from_geojson: missing 'type'");
  my $coords = $gj->{coordinates};
  my $wkt;
  if ($type eq 'Point') {
    $wkt = 'POINT(' . join(' ', @$coords) . ')';
  }
  elsif ($type eq 'LineString' or $type eq 'MultiPoint') {
    $wkt = uc($type =~ s/(?<=[a-z])(?=[A-Z])/ /gr) =~ s/ //gr;
    $wkt = ($type eq 'LineString' ? 'LINESTRING' : 'MULTIPOINT')

lib/DBIO/PostgreSQL/PostGIS/Geometry.pm  view on Meta::CPAN


DBIO::PostgreSQL::PostGIS::Geometry - Lightweight PostGIS geometry/geography value object

=head1 VERSION

version 0.900001

=head1 SYNOPSIS

  use DBIO::PostgreSQL::PostGIS::Geometry;

  # From WKT
  my $point = DBIO::PostgreSQL::PostGIS::Geometry->from_wkt(
    'POINT(13.4 52.5)', srid => 4326,
  );

  # From WKB hex (what PostGIS returns by default)
  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_ewkb_hex($hex);

  $point->srid;            # 4326
  $point->geometry_type;   # 'POINT'
  $point->wkt;             # 'POINT(13.4 52.5)'
  $point->ewkt;            # 'SRID=4326;POINT(13.4 52.5)'
  $point->coordinates;     # [13.4, 52.5]
  $point->x; $point->y;
  $point->is_empty;

  # Helpers
  $point->to_geojson;      # { type => 'Point', coordinates => [...] }
  $point->bbox;            # [xmin, ymin, xmax, ymax]

=head1 DESCRIPTION

A lightweight Perl-side representation of a PostGIS geometry or geography
value. Stores SRID + WKT (lazily parsed). Optional inflate path through
L<Geo::OGR> when installed for heavy spatial operations.

This object is what L<DBIO::PostgreSQL::PostGIS> inflates C<geometry>
and C<geography> column values to. On the way out (deflate) it serializes
back to EWKT for the database.

=head1 METHODS

=head2 from_wkt

  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_wkt($wkt, srid => 4326);

Constructs from a Well-Known Text representation.

=head2 from_ewkt

  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_ewkt('SRID=4326;POINT(0 0)');

Parses an Extended WKT string (PostGIS's C<SRID=N;WKT> form).

=head2 from_ewkb_hex

  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_ewkb_hex($hex);

Constructs from PostGIS's default hex-encoded EWKB output. Stores the
hex unparsed; geometry_type/coordinates are decoded lazily on demand.

=head2 point

  my $p = DBIO::PostgreSQL::PostGIS::Geometry->point($x, $y, srid => 4326);
  my $p = DBIO::PostgreSQL::PostGIS::Geometry->point($x, $y, $z, srid => 4326);

Constructs a POINT geometry. Accepts 2 or 3 numeric coords followed by
named options (currently just C<srid>).

=head2 from_lat_lon

  my $p = DBIO::PostgreSQL::PostGIS::Geometry->from_lat_lon($lat, $lon);

Convenience for building a 4326 POINT from latitude/longitude. Note the
order: lat first (the human convention), but the WKT/PostGIS axis order
is C<POINT(lon lat)> — this method swaps for you.

=head2 linestring

  my $l = DBIO::PostgreSQL::PostGIS::Geometry->linestring(
    [[0,0],[1,1],[2,0]], srid => 4326,
  );

=head2 polygon

  my $p = DBIO::PostgreSQL::PostGIS::Geometry->polygon(
    [ [[0,0],[10,0],[10,10],[0,10],[0,0]] ],   # outer ring + optional holes
    srid => 4326,
  );

=head2 bbox_polygon

  my $b = DBIO::PostgreSQL::PostGIS::Geometry->bbox_polygon(
    $xmin, $ymin, $xmax, $ymax, srid => 4326,
  );

Convenience for an axis-aligned rectangle as a closed POLYGON ring.

=head2 from_geojson

  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_geojson(\%geojson);
  my $g = DBIO::PostgreSQL::PostGIS::Geometry->from_geojson(\%geojson, srid => 4326);

Builds from a GeoJSON-shaped hashref. SRID defaults to 4326 (per the
GeoJSON spec) unless overridden.

=head2 srid

=head2 wkt

=head2 ewkb_hex

=head2 geometry_type

The geometry type as an upper-case string: C<POINT>, C<LINESTRING>,
C<POLYGON>, C<MULTIPOINT>, C<MULTILINESTRING>, C<MULTIPOLYGON>,
C<GEOMETRYCOLLECTION>. Lazily derived from WKT or EWKB.

=head2 ewkt



( run in 1.100 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )