+=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
+
+[ [ latitude0, longitude0 ], [ latitude1, longitude1 ] ... ]
+
+=cut
+
+sub vertices_json {
+ my $self = shift;
+ my @vertices = map { [ $_->latitude, $_->longitude ] } $self->deploy_zone_vertex;
+ 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
+
+=item process_batch_import JOB, PARAMS
+
+=cut
+
+sub process_batch_import {
+ eval {
+ use FS::deploy_zone_block;
+ use FS::deploy_zone_vertex;
+ };
+ my $job = shift;
+ my $param = shift;
+ if (!ref($param)) {
+ $param = thaw(decode_base64($param));
+ }
+
+ # even if creating a new zone, the deploy_zone object should already
+ # be inserted by this point
+ my $zonenum = $param->{zonenum}
+ or die "zonenum required";
+ my $zone = FS::deploy_zone->by_key($zonenum)
+ or die "deploy_zone #$zonenum not found";
+ my $opt;
+ if ( $zone->zonetype eq 'B' ) {
+ $opt = { 'table' => 'deploy_zone_block',
+ 'params' => [ 'zonenum', 'censusyear' ],
+ 'formats' => { 'plain' => [ 'censusblock' ] },
+ 'default_csv' => 1,
+ };
+ $job->update_statustext('1,Inserting census blocks');
+ } elsif ( $zone->zonetype eq 'P' ) {
+ $opt = { 'table' => 'deploy_zone_vertex',
+ 'params' => [ 'zonenum' ],
+ 'formats' => { 'plain' => [ 'latitude', 'longitude' ] },
+ 'default_csv' => 1,
+ };
+ } else {
+ die "don't know how to import to zonetype ".$zone->zonetype;
+ }
+
+ FS::Record::process_batch_import( $job, $opt, $param );
+
+}
+
+=item process_block_lookup JOB, ZONENUM
+
+Look up all the census blocks in the zone's footprint, and insert them.
+This will replace any existing block list.
+
+=cut
+
+sub process_block_lookup {
+ my $job = shift;
+ my $param = shift;
+ if (!ref($param)) {
+ $param = thaw(decode_base64($param));
+ }
+ my $zonenum = $param->{zonenum};
+ my $zone = FS::deploy_zone->by_key($zonenum)
+ or die "zone $zonenum not found\n";
+
+ # wipe the existing list of blocks
+ my $error = $zone->process_o2m(
+ 'table' => 'deploy_zone_block',
+ 'num_col' => 'zonenum',
+ 'fields' => 'zonenum',
+ 'params' => {},
+ );
+ die $error if $error;
+
+ $job->update_statustext('0,querying census database') if $job;
+
+ # negotiate the rugged jungle trails of the ArcGIS REST protocol:
+ # 1. unlike most places, longitude first.
+ my @zone_vertices = map { [ $_->longitude, $_->latitude ] }
+ $zone->deploy_zone_vertex;
+
+ return if scalar(@zone_vertices) < 3; # then don't bother
+
+ # 2. package this as "rings", inside a JSON geometry object
+ # 3. announce loudly and frequently that we are using spatial reference
+ # 4326, "true GPS coordinates"
+ my $geometry = encode_json({
+ 'rings' => [ \@zone_vertices ],
+ 'wkid' => 4326,
+ });
+
+ my %query = (
+ f => 'json', # duh
+ geometry => $geometry,
+ geometryType => 'esriGeometryPolygon', # as opposed to a bounding box
+ inSR => 4326,
+ outSR => 4326,
+ spatialRel => 'esriSpatialRelIntersects', # the test to perform
+ outFields => 'OID,GEOID',
+ returnGeometry => 'false',
+ orderByFields => 'OID',
+ );
+ my $url = 'https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/Tracts_Blocks/MapServer/12/query';
+ my $ua = LWP::UserAgent->new;
+
+ # first find out how many of these we're dealing with
+ my $response = $ua->request(
+ POST $url, Content => [
+ %query,
+ returnCountOnly => 1,
+ ]
+ );
+ die $response->status_line unless $response->is_success;
+ my $data = decode_json($response->content);
+ # their error messages are mostly useless, but don't just blindly continue
+ die $data->{error}{message} if $data->{error};
+
+ my $count = $data->{count};
+ my $inserted = 0;
+
+ #warn "Census block lookup: $count\n";
+
+ # we have to do our own pagination on this, because the census bureau
+ # doesn't support resultOffset (maybe they don't have ArcGIS 10.3 yet).
+ # that's why we're ordering by OID, it's globally unique
+ my $last_oid = 0;
+ my $done = 0;
+ while (!$done) {
+ $response = $ua->request(
+ POST $url, Content => [
+ %query,
+ where => "OID>$last_oid",
+ ]
+ );
+ die $response->status_line unless $response->is_success;
+ $data = decode_json($response->content);
+ die $data->{error}{message} if $data->{error};
+ last unless scalar @{$data->{features}}; #Nothing to insert
+
+ foreach my $feature (@{ $data->{features} }) {
+ my $geoid = $feature->{attributes}{GEOID}; # the prize
+ my $block = FS::deploy_zone_block->new({
+ zonenum => $zonenum,
+ censusblock => $geoid
+ });
+ $error = $block->insert;
+ die "$error (inserting census block $geoid)" if $error;
+
+ $inserted++;
+ if ($job and $inserted % 100 == 0) {
+ my $percent = sprintf('%.0f', $inserted / $count * 100);
+ $job->update_statustext("$percent,creating block records");
+ }
+ }
+
+ #warn "Inserted $inserted records\n";
+ $last_oid = $data->{features}[-1]{attributes}{OID};
+ $done = 1 unless $data->{exceededTransferLimit};
+ }
+
+ $zone->set('censusyear', $CENSUS_YEAR);
+ $error = $zone->replace;
+ warn "$error (updating zone census year)" if $error; # whatever, continue
+
+ return;
+}
+