The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Geo::GoogleEarth::Pluggable;
use strict;
use warnings;
use base qw{Geo::GoogleEarth::Pluggable::Folder}; 
use XML::LibXML::LazyBuilder qw{DOM E};
use Archive::Zip qw{COMPRESSION_DEFLATED};
use IO::Scalar qw{};

our $VERSION='0.15';

=head1 NAME

Geo::GoogleEarth::Pluggable - Generates GoogleEarth Documents

=head1 SYNOPSIS

  use Geo::GoogleEarth::Pluggable;
  my $document=Geo::GoogleEarth::Pluggable->new(%data); #is a special Folder...
  my $folder  =$document->Folder(%data);                #isa Geo::GoogleEarth::Pluggable::Folder
  my $point   =$document->Point(%data);                 #isa Geo::GoogleEarth::Pluggable::Point
  my $netlink =$document->NetworkLink(%data);           #isa Geo::GoogleEarth::Pluggable::NetworkLink
  my $lookat  =$document->LookAt(%data);                #isa Geo::GoogleEarth::Pluggable::LookAt
  my $style   =$document->Style(%data);                 #isa Geo::GoogleEarth::Pluggable::Style
  print $document->render;

KML CGI Example

  use Geo::GoogleEarth::Pluggable;
  my $document=Geo::GoogleEarth::Pluggable->new(name=>"KML Document");
  print $document->header,
        $document->render;

KMZ CGI Example

  use Geo::GoogleEarth::Pluggable;
  my $document=Geo::GoogleEarth::Pluggable->new(name=>"KMZ Document");
  print $document->header_kmz,
        $document->archive;

=head1 DESCRIPTION

Geo::GoogleEarth::Pluggable is a Perl object oriented interface that allows for the creation of XML documents that can be used with Google Earth.

Geo::GoogleEarth::Pluggable (aka Document) is a L<Geo::GoogleEarth::Pluggable::Folder> with a render method.

=head2 Object Inheritance Graph

  --- Constructor -+- Base --- Folder    --- Document
                   |        |
                   |        +- Placemark -+- Point
                   |        |             +- LineString
                   |        |             +- LinearRing
                   |        |
                   |        +- StyleBase -+- Style
                   |        |             +- StyleMap
                   |        |
                   |        +- NetworkLink
                   |
                   +- LookAt

=head2 Constructors that append to the parent folder object

Folder, NetworkLink, Point, LineString, LinearRing

=head2 Constructors that return objects for future use

LookAt(), Style(), StyleMap()

=head2 Wrappers (what makes it easy)

Style => IconStyle, LineStyle, PolyStyle, LabelStyle, ListStyle

Point => MultiPoint

=head1 USAGE

This is all of the code you need to generate a complete Google Earth document.

  use Geo::GoogleEarth::Pluggable;
  my $document=Geo::GoogleEarth::Pluggable->new;
  $document->Point(name=>"White House", lat=>38.897337, lon=>-77.036503);
  print $document->render;

=head1 CONSTRUCTOR

=head2 new

  my $document=Geo::GoogleEarth::Pluggable->new(name=>"My Name");

=head1 METHODS

=head2 type

Returns the object type.

  my $type=$folder->type;

=cut

sub type {"Document"};

=head2 document

Returns the document object.

All objects know to which document they belong even the document itself!

=cut

sub document {shift};

=head2 render

Returns an XML document with an XML declaration and a root name of "Document"

  print $document->render;

=cut

sub render {
  my $self=shift;
  my $d = DOM(E(kml=>{$self->xmlns}, $self->node));
  return $d->toString;
}

=head2 archive

Returns a KMZ formatted Zipped archive of the XML document

  print $document->archive;

=cut

sub archive {
  my $self=shift;
  my $azip=Archive::Zip->new;
  my $member=$azip->addString($self->render, "doc.kml");
  $member->desiredCompressionMethod(COMPRESSION_DEFLATED);
  #$member->desiredCompressionLevel(9); #RT60563, RT54827

  my $archive=q{};
  my $iosh=IO::Scalar->new( \$archive );
  $azip->writeToFileHandle($iosh);
  $iosh->close;
  return $archive;
}

=head2 xmlns

Add or update a namespace

  $document->xmlns->{"namespace"}=$url;

Delete a namespace

  delete($document->xmlns->{"xmlns:gx"});

Replace all namespaces

  $document->{"xmlns"}={namespace=>$url};

Reset to default namespaces

  delete($document->{"xmlns"});

=cut

sub xmlns {
  my $self=shift;
  unless (defined($self->{'xmlns'})) {
    $self->{'xmlns'}={
            'xmlns'      => "http://www.opengis.net/kml/2.2",
            'xmlns:gx'   => "http://www.google.com/kml/ext/2.2",
            'xmlns:kml'  => "http://www.opengis.net/kml/2.2",
            'xmlns:atom' => "http://www.w3.org/2005/Atom",
                     };
  }
  return wantarray ? %{$self->{'xmlns'}} : $self->{'xmlns'};
}

=head2 nextId

This method is in the document since all Styles and StyleMaps are in the document not folders.

  my $id=$document->nextId($type); #$type in "Style" or "StyleMap"

=cut

sub nextId {
  my $self=shift;
  my $type=shift || "Unknown";
  $self->{"nextId"}=0 unless defined $self->{"nextId"};
  return sprintf("%s-%s-%s", $type, "perl", $self->{"nextId"}++);
}

=head2 header, header_kml

Returns a header appropriate for a web application

  Content-type: application/vnd.google-earth.kml+xml
  Content-Disposition: attachment; filename=filename.xls

  $document->header                                                       #embedded in browser
  $document->header(filename=>"filename.xls")                             #download prompt
  $document->header(content_type=>"application/vnd.google-earth.kml+xml") #default content type

=cut

*header_kml=\&header;

sub header {
  my $self=shift;
  my %data=@_;
     $data{"content_type"}="application/vnd.google-earth.kml+xml"
       unless defined $data{"content_type"};
  my $header=sprintf("Content-type: %s\n", $data{"content_type"});
     $header.=sprintf(qq{Content-Disposition: attachment; filename="%s";\n},
                         $data{"filename"}) if defined $data{"filename"};
     $header.="\n";
  return $header;
}

=head2 header_kmz

Returns a header appropriate for a web application

  Content-type: application/vnd.google-earth.kml+xml
  Content-Disposition: attachment; filename=filename.xls

  $document->header_kmz                                                   #embedded in browser
  $document->header_kmz(filename=>"filename.xls")                         #download prompt
  $document->header_kmz(content_type=>"application/vnd.google-earth.kmz") #default content type

=cut

sub header_kmz {
  my $self=shift;
  my %data=@_;
  $data{"content_type"}||="application/vnd.google-earth.kmz";
  return $self->header(%data);
}

=head1 TODO

=over

=item Support for default Polygon and Line styles that are nicer than GoogleEarth's

=item Support for DateTime object in the constructor that is promoted to the LookAt object.

=item Create a L<GPS::Point> plugin (Promote tag as name and datetime to LookAt)

=back

=head1 BUGS

Please log on RT and send to the geo-perl email list.

=head1 LIMITATIONS

=head2 Not So Pretty XML

The XML produced by L<XML::LibXML> is not "pretty".  If you need pretty XML you must pass the output through xmllint or a simular product.

For example: 

  perl -MGeo::GoogleEarth::Pluggable -e "print Geo::GoogleEarth::Pluggable->new->render" | xmllint --format -

=head2 Write Only

This package can only write KML and KMZ files.  However, if you need to read KML files, please see the L<Geo::KML> package's C<from> method.

=head1 SUPPORT

DavisNetworks.com supports all Perl applications including this package.

=head1 AUTHOR

  Michael R. Davis (mrdvt92)
  CPAN ID: MRDVT

=head1 COPYRIGHT

This program is free software licensed under the...

  The BSD License

The full text of the license can be found in the LICENSE file included with this module.

=head1 SEE ALSO

L<Geo::KML>, L<XML::LibXML>, L<XML::LibXML::LazyBuilder>, L<Archive::Zip>, L<IO::Scalar>

=cut

1;