[freeside-commits] branch master updated. b3b6d0750030d08032756ad4a6969e193a65a928

Ivan Kohler ivan at freeside.biz
Fri Aug 26 12:23:44 PDT 2022


The branch, master has been updated
       via  b3b6d0750030d08032756ad4a6969e193a65a928 (commit)
      from  86fb4649060ed26420bb1931481885064e76abc5 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit b3b6d0750030d08032756ad4a6969e193a65a928
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 26 12:23:43 2022 -0700

    add shapefile, kmz and geojson export to deployment zones, RT#86460

diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 8dd72ac79..ebd40adda 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -85,6 +85,7 @@ if ( -e $addl_handler_use_file ) {
     die $@ if $@;
   }
   use Text::CSV_XS;
+  use Archive::Zip;
   use Spreadsheet::WriteExcel;
   use Spreadsheet::WriteExcel::Utility;
   use OLE::Storage_Lite;
@@ -120,7 +121,10 @@ if ( -e $addl_handler_use_file ) {
   use Locale::Currency::Format;
   use Number::Phone::Country qw( noexport );
   use Business::US::USPS::WebTools::AddressStandardization;
-  use Geo::GoogleEarth::Pluggable;
+  use Geo::GoogleEarth::Pluggable 0.16;
+  use Geo::Shapelib;
+  use Geo::JSON;
+  use Geo::JSON::FeatureCollection;
   use LWP::UserAgent;
   use Storable qw( nfreeze thaw );
   use FS;
diff --git a/FS/FS/deploy_zone.pm b/FS/FS/deploy_zone.pm
index 723b491c8..16ba5ddf3 100644
--- a/FS/FS/deploy_zone.pm
+++ b/FS/FS/deploy_zone.pm
@@ -10,9 +10,14 @@ use Cpanel::JSON::XS;
 use LWP::UserAgent;
 use HTTP::Request::Common;
 
+use Geo::JSON::Polygon;
+use Geo::JSON::Feature;
+
 # update this in 2020, along with the URL for the TIGERweb service
 our $CENSUS_YEAR = 2010;
 
+our $tech_label  = FS::part_pkg_fcc_option->technology_labels;
+
 =head1 NAME
 
 FS::deploy_zone - Object methods for deploy_zone records
@@ -275,6 +280,27 @@ sub deploy_zone_vertex {
   });
 }
 
+=item shapefile_add SHAPEFILE
+
+Adds this deployment zone to the supplied Geo::Shapelib shapefile.
+
+=cut
+
+sub shapefile_add {
+  my( $self, $shapefile ) = @_;
+
+  my @coordinates = map { [ $_->longitude, $_->latitude, 0, 0 ] }
+                      $self->deploy_zone_vertex;
+  push @coordinates, $coordinates[0];
+
+  push @{$shapefile->{Shapes}}, { 'Vertices' => \@coordinates };
+  push @{$shapefile->{ShapeRecords}}, [ $tech_label->{$self->technology},
+                                        $self->adv_speed_down,
+                                        $self->adv_speed_up,
+                                      ];
+  '';
+}
+
 =item vertices_json
 
 Returns the vertex list for this zone, as a JSON string of
@@ -289,6 +315,51 @@ sub vertices_json {
   encode_json(\@vertices);
 }
 
+=item geo_json_feature
+
+Returns this zone as a Geo::JSON::Feature object
+
+=cut
+
+sub geo_json_feature {
+  my $self = shift;
+
+  my @coordinates = map { [ $_->longitude, $_->latitude ] }
+                      $self->deploy_zone_vertex;
+  push @coordinates, $coordinates[0];
+
+  Geo::JSON::Feature->new({
+    geometry   => Geo::JSON::Polygon->new({ coordinates => [ \@coordinates ] }),
+    properties => { 'Technology' => $tech_label->{$self->technology},
+                    'Down'       => $self->adv_speed_down,
+                    'Up'         => $self->adv_speed_up,
+                  },
+  })
+}
+
+=item kml_add
+
+Adds this deployment zone to the supplied Geo::GoogleEarth::Pluggable object.
+
+=cut
+
+sub kml_polygon {
+  my( $self, $kml ) = @_;
+
+  my $name = $self->description. ' ('. $self->adv_speed_down. '/'.
+                                       $self->adv_speed_up. ')';
+
+  $kml->Polygon( 'name'        => $name,
+                 'coordinates' => [ [ #outerBoundary
+                                      map { [ $_->longitude, $_->latitude, 0 ] }
+                                        $self->deploy_zone_vertex
+                                    ],
+                                    #[ #innerBoundary
+                                    #]
+                                  ]
+               );
+}
+
 =head2 SUBROUTINES
 
 =over 4
diff --git a/debian/control b/debian/control
index 0f1e29fb9..479a150d1 100644
--- a/debian/control
+++ b/debian/control
@@ -78,7 +78,8 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,
  libipc-run-safehandles-perl,libpoe-perl,libsoap-lite-perl,libxmlrpc-lite-perl,
  libhtml-tableextract-perl,libhtml-element-extended-perl,libcam-pdf-perl,
  libnet-openssh-perl,libgd-barcode-perl,sam2p,libsys-sigaction-perl,
- libgeo-googleearth-pluggable-perl,libgeo-coder-googlev3-perl,libnet-snmp-perl,
+ libgeo-googleearth-pluggable-perl (>=0.16),libgeo-coder-googlev3-perl,
+ libnet-snmp-perl,
  libcrypt-openssl-rsa-perl,libregexp-common-perl,libnet-cidr-perl,
  libregexp-ipv6-perl,libhtml-quoted-perl,libtext-password-pronounceable-perl,
  libconvert-color-perl,liburi-perl,libhtml-rewriteattributes-perl,
@@ -107,7 +108,8 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,
  libspreadsheet-parsexlsx-perl, libunicode-truncate-perl (>= 0.303-1),
  libspreadsheet-xlsx-perl, libpod-simple-perl, libwebservice-northern911-perl,
  liblocale-codes-perl, liblocale-po-perl, libgeo-uscensus-geocoding-perl,
- libnet-sftp-foreign-perl, libpdf-webkit-perl
+ libnet-sftp-foreign-perl, libpdf-webkit-perl, libgeo-shapelib-perl,
+ libgeo-json-perl
 Conflicts: libparams-classify-perl (>= 0.013-6)
 Replaces: freeside (<<4)
 Breaks: freeside (<<4)
diff --git a/httemplate/browse/deploy_zone.html b/httemplate/browse/deploy_zone.html
index 5514d7db8..33724e59f 100644
--- a/httemplate/browse/deploy_zone.html
+++ b/httemplate/browse/deploy_zone.html
@@ -19,6 +19,21 @@
                         'Contractual Mbps',
                         'Vertices',
                         'Census blocks',
+                        'Shapefile',
+                        'KMZ',
+                        'GeoJSON',
+                     ],
+  footer          => [ '',
+                       'All fixed zones',
+                       '',
+                       '',
+                       '',
+                       '',
+                       '',
+                       '',
+                       '<A HREF="'. $fixed_shp.  '">download</A>',
+                       '<A HREF="'. $fixed_kmz.  '">download</A>',
+                       '<A HREF="'. $fixed_json. '">download</A>',
                      ],
   fields          => [  'zonenum',
                         'description',
@@ -48,6 +63,18 @@
                         sub { my $self = shift;
                               FS::deploy_zone_block->count('zonenum = '.$self->zonenum)
                             },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
                      ],
   sort_fields     => [ 'zonenum',
                        'description',
@@ -56,7 +83,7 @@
                        '(adv_speed_down, adv_speed_up)',
                        '(cir_speed_down, cir_speed_up)',
                      ],
-  links           => [  $link_fixed, $link_fixed, ],
+  links           => [  $link_fixed, $link_fixed, '', '', '', '', '', '', $link_shp, $link_kmz, $link_json, ],
   align           => 'cllllrrr',
   nohtmlheader    => 1,
   disable_maxselect => 1,
@@ -79,6 +106,9 @@
                         'Service Type',
                         'Advertised Mbps',
                         'Vertices', # number of vertices? not so useful
+                        'Shapefile',
+                        'KMZ',
+                        'GeoJSON',
                      ],
   fields          => [  'zonenum',
                         'description',
@@ -101,6 +131,18 @@
                         sub { my $self = shift;
                               FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
                             },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
+                        sub { my $self = shift;
+                              FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+                              ? 'download' : ''
+                            },
                      ],
   sort_fields     => [ 'zonenum',
                        'description',
@@ -109,7 +151,7 @@
                        '(is_voice is not null, is_broadband is not null)',
                        '(adv_speed_down, adv_speed_up)',
                      ],
-  links           => [  '', $link_mobile, ],
+  links           => [  $link_mobile, $link_mobile, '', '', '', '', '', '', $link_shp, $link_kmz, $link_json, ],
   align           => 'clllllr',
   nohtmlheader    => 1,
   disable_maxselect => 1,
@@ -120,15 +162,24 @@
 
 <& /elements/footer.html &>
 <%init>
+
 my $curuser = $FS::CurrentUser::CurrentUser;
 my $acl_edit = $curuser->access_right('Edit FCC report configuration');
 my $acl_edit_global = $curuser->access_right('Edit FCC report configuration for all agents');
 die "access denied"
   unless $acl_edit or $acl_edit_global;
 
-my $link_fixed = [ $p.'edit/deploy_zone-fixed.html?', 'zonenum' ];
-my $link_mobile= [ $p.'edit/deploy_zone-mobile.html?', 'zonenum' ];
+my $link_fixed  = [ $p.'edit/deploy_zone-fixed.html?', 'zonenum' ];
+my $link_mobile = [ $p.'edit/deploy_zone-mobile.html?', 'zonenum' ];
+my $link_shp    = [ $p.'view/deploy_zone-shp.cgi?', 'zonenum' ];
+my $link_kmz    = [ $p.'view/deploy_zone-kmz.cgi?', 'zonenum' ];
+my $link_json   = [ $p.'view/deploy_zone-geojson.cgi?', 'zonenum' ];
+
+my $fixed_shp    = $p.'view/deploy_zone-shp.cgi?zonetype=B';
+my $fixed_kmz    = $p.'view/deploy_zone-kmz.cgi?zonetype=B';
+my $fixed_json   = $p.'view/deploy_zone-geojson.cgi?zonetype=B';
+
+my $tech_label  = FS::part_pkg_fcc_option->technology_labels;
+my $spec_label  = FS::part_pkg_fcc_option->spectrum_labels;
 
-my $tech_label = FS::part_pkg_fcc_option->technology_labels;
-my $spec_label = FS::part_pkg_fcc_option->spectrum_labels;
 </%init>
diff --git a/httemplate/view/deploy_zone-geojson.cgi b/httemplate/view/deploy_zone-geojson.cgi
new file mode 100644
index 000000000..0c9d19353
--- /dev/null
+++ b/httemplate/view/deploy_zone-geojson.cgi
@@ -0,0 +1,42 @@
+<% $content %>\
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit FCC report configuration');
+my $acl_edit_global = $curuser->access_right('Edit FCC report configuration for all agents');
+die "access denied"
+  unless $acl_edit or $acl_edit_global;
+
+my($name, $content);
+
+my($query) = $cgi->keywords;
+if ( $query =~ /^(\d+)$/ || $cgi->param('zonenum') =~ /^(\d+$)/ ) {
+  my $zonenum = $1;
+  $name = $zonenum;
+  my $deploy_zone = qsearchs('deploy_zone', { 'zonenum' => $zonenum })
+    or die 'unknown zonenum';
+
+  $content = $deploy_zone->geo_json_feature->to_json;
+
+} elsif ( $cgi->param('zonetype') =~ /^(\w)$/ ) {
+  my $zonetype = $1;
+  $name = $zonetype;
+  my @deploy_zone = qsearch('deploy_zone', { 'zonetype' => $zonetype,
+                                             'disabled' => '',        });
+
+   my $fc = Geo::JSON::FeatureCollection->new({
+     features => [ map $_->geo_json_feature, @deploy_zone ],
+   });
+
+   $content = $fc->to_json;
+
+} else {
+  die "no zonenum or zonetype\n";
+}
+
+http_header('Content-Type'        => 'application/geo+json' );
+http_header('Content-Disposition' => "filename=$name.geojson" );
+http_header('Content-Length'      => length($content) );
+http_header('Cache-control'       => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/deploy_zone-kmz.cgi b/httemplate/view/deploy_zone-kmz.cgi
new file mode 100644
index 000000000..d2af171e5
--- /dev/null
+++ b/httemplate/view/deploy_zone-kmz.cgi
@@ -0,0 +1,42 @@
+<% $content %>\
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit FCC report configuration');
+my $acl_edit_global = $curuser->access_right('Edit FCC report configuration for all agents');
+die "access denied"
+  unless $acl_edit or $acl_edit_global;
+
+my $kml = Geo::GoogleEarth::Pluggable->new;
+
+my $name;
+
+my($query) = $cgi->keywords;
+if ( $query =~ /^(\d+)$/ || $cgi->param('zonenum') =~ /^(\d+$)/ ) {
+  my $zonenum = $1;
+  $name = $zonenum;
+  my $deploy_zone = qsearchs('deploy_zone', { 'zonenum' => $zonenum })
+    or die 'unknown zonenum';
+
+  $deploy_zone->kml_polygon($kml);
+
+} elsif ( $cgi->param('zonetype') =~ /^(\w)$/ ) {
+  my $zonetype = $1;
+  $name = $zonetype;
+  my @deploy_zone = qsearch('deploy_zone', { 'zonetype' => $zonetype,
+                                             'disabled' => '',        });
+
+  $_->kml_polygon($kml) foreach @deploy_zone;
+
+} else {
+  die "no zonenum or zonetype\n";
+}
+
+my $content = $kml->archive;
+
+http_header('Content-Type' => 'application/vnd.google-earth.kmz' ); #kmz
+http_header('Content-Disposition' => "filename=$name.kmz" );
+http_header('Content-Length'      => length($content) );
+http_header('Cache-control'       => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/deploy_zone-shp.cgi b/httemplate/view/deploy_zone-shp.cgi
new file mode 100644
index 000000000..4b5dfbbc6
--- /dev/null
+++ b/httemplate/view/deploy_zone-shp.cgi
@@ -0,0 +1,76 @@
+<% $content %>\
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $acl_edit = $curuser->access_right('Edit FCC report configuration');
+my $acl_edit_global = $curuser->access_right('Edit FCC report configuration for all agents');
+die "access denied"
+  unless $acl_edit or $acl_edit_global;
+
+my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+
+my %shapelib_opts = (
+  Shapetype  => Geo::Shapelib::POLYGON,
+  FieldNames => [ 'Tech', 'Down', 'Up' ],
+  FieldTypes => [ 'String:32', 'Double', 'Double' ],
+);
+
+my( $name, $shapefile );
+
+my($query) = $cgi->keywords;
+if ( $query =~ /^(\d+)$/ || $cgi->param('zonenum') =~ /^(\d+$)/ ) {
+  my $zonenum = $1;
+  $name = $zonenum;
+  my $deploy_zone = qsearchs('deploy_zone', { 'zonenum' => $zonenum })
+    or die 'unknown zonenum';
+
+  $shapefile = new Geo::Shapelib {
+    Name => "$dir/$zonenum-$$",
+    %shapelib_opts
+  };
+
+  $deploy_zone->shapefile_add($shapefile);
+
+} elsif ( $cgi->param('zonetype') =~ /^(\w)$/ ) {
+  my $zonetype = $1;
+  $name = $zonetype;
+  my @deploy_zone = qsearch('deploy_zone', { 'zonetype' => $zonetype,
+                                             'disabled' => '',        });
+
+  $shapefile = new Geo::Shapelib {
+    Name => "$dir/$zonetype-$$",
+    %shapelib_opts
+  };
+
+  $_->shapefile_add($shapefile) foreach @deploy_zone;
+
+} else {
+  die "no zonenum or zonetype\n";
+}
+
+$shapefile->set_bounds;
+
+$shapefile->save;
+
+#slurp up .shp .shx and .dbf files and put them in a zip.. return that
+#and delete the files
+
+my $content = '';
+open(my $fh, '>', \$content);
+
+my $zip = new Archive::Zip;
+$zip->addFile("$dir/$name-$$.$_", "$name.$_") foreach qw( shp shx dbf );
+unless ( $zip->writeToFileHandle($fh) == Archive::Zip::AZ_OK() ) {
+  die "failed to create .shz file\n";
+}
+close $fh;
+
+unlink("$dir/$name-$$.$_") foreach qw( shp shx dbf );
+
+#http_header('Content-Type'        => 'x-gis/x-shapefile' );
+http_header('Content-Type'        => 'archive/zip' );
+http_header('Content-Disposition' => "filename=$name.shz" );
+http_header('Content-Length'      => length($content) );
+http_header('Cache-control'       => 'max-age=60' );
+
+</%init>

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Mason.pm                          |  6 ++-
 FS/FS/deploy_zone.pm                    | 71 ++++++++++++++++++++++++++++++
 debian/control                          |  6 ++-
 httemplate/browse/deploy_zone.html      | 63 ++++++++++++++++++++++++---
 httemplate/view/deploy_zone-geojson.cgi | 42 ++++++++++++++++++
 httemplate/view/deploy_zone-kmz.cgi     | 42 ++++++++++++++++++
 httemplate/view/deploy_zone-shp.cgi     | 76 +++++++++++++++++++++++++++++++++
 7 files changed, 297 insertions(+), 9 deletions(-)
 create mode 100644 httemplate/view/deploy_zone-geojson.cgi
 create mode 100644 httemplate/view/deploy_zone-kmz.cgi
 create mode 100644 httemplate/view/deploy_zone-shp.cgi




More information about the freeside-commits mailing list