summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2015-09-30 22:49:38 -0700
committerMark Wells <mark@freeside.biz>2015-09-30 22:50:00 -0700
commitf485dcdf05ee107645299197a9ab9d9dd3f22be3 (patch)
tree541d64f13a8ebd4a2d448295a68182a58de63fb2
parent9e99a610a2c69db3ab9f42728ab6dbf492a5496a (diff)
graphical selection of deployment zones and automatic block lookup, #30260
-rw-r--r--FS/FS/Schema.pm3
-rw-r--r--FS/FS/deploy_zone.pm187
-rw-r--r--FS/FS/deploy_zone_block.pm5
-rw-r--r--FS/FS/o2m_Common.pm18
-rw-r--r--FS/FS/part_pkg_fcc_option.pm2
-rw-r--r--httemplate/browse/deploy_zone.html6
-rw-r--r--httemplate/edit/deploy_zone-fixed.html85
-rw-r--r--httemplate/edit/deploy_zone-mobile.html21
-rw-r--r--httemplate/edit/process/deploy_zone-fixed.html35
-rw-r--r--httemplate/edit/process/deploy_zone-mobile.html19
-rw-r--r--httemplate/edit/process/elements/process.html21
-rw-r--r--httemplate/elements/polygon.html127
-rw-r--r--httemplate/elements/tr-polygon.html5
-rw-r--r--httemplate/misc/process/deploy_zone-block_lookup.cgi13
14 files changed, 450 insertions, 97 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 85fbbeb..486860f 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -7038,6 +7038,7 @@ sub tables_hashref {
'zonenum', 'serial', '', '', '', '',
'description', 'char', 'NULL', $char_d, '', '',
'agentnum', 'int', '', '', '', '',
+ 'censusyear', 'char', 'NULL', 4, '', '',
'dbaname', 'char', 'NULL', $char_d, '', '',
'zonetype', 'char', '', 1, '', '',
'technology', 'int', '', '', '', '',
@@ -7069,7 +7070,7 @@ sub tables_hashref {
'blocknum', 'serial', '', '', '', '',
'zonenum', 'int', '', '', '', '',
'censusblock', 'char', '', 15, '', '',
- 'censusyear', 'char', '', 4, '', '',
+ 'censusyear', 'char','NULL', 4, '', '',
],
'primary_key' => 'blocknum',
'unique' => [],
diff --git a/FS/FS/deploy_zone.pm b/FS/FS/deploy_zone.pm
index 38dd7dc..71129cf 100644
--- a/FS/FS/deploy_zone.pm
+++ b/FS/FS/deploy_zone.pm
@@ -6,6 +6,13 @@ use FS::Record qw( qsearch qsearchs dbh );
use Storable qw(thaw);
use MIME::Base64;
+use JSON qw(encode_json decode_json) ;
+use LWP::UserAgent;
+use HTTP::Request::Common;
+
+# update this in 2020, along with the URL for the TIGERweb service
+our $CENSUS_YEAR = 2010;
+
=head1 NAME
FS::deploy_zone - Object methods for deploy_zone records
@@ -48,6 +55,12 @@ Optional text describing the zone.
The agent that serves this zone.
+=item censusyear
+
+The census map year for which this zone was last updated. May be null for
+zones that contain no census blocks (mobile zones, or fixed zones that haven't
+had their block lists filled in yet).
+
=item dbaname
The name under which service is marketed in this zone. If null, will
@@ -58,6 +71,8 @@ default to the agent name.
The way the zone geography is defined: "B" for a list of census blocks
(used by the FCC for fixed broadband service), "P" for a polygon (for
mobile services). See L<FS::deploy_zone_block> and L<FS::deploy_zone_vertex>.
+Note that block-type zones are still allowed to have a vertex list, for
+use by the map editor.
=item technology
@@ -147,12 +162,16 @@ sub delete {
local $FS::UID::AutoCommit = 0;
# clean up linked records
my $self = shift;
- my $error = $self->process_o2m(
- 'table' => $self->element_table,
- 'num_col' => 'zonenum',
- 'fields' => 'zonenum',
- 'params' => {},
- ) || $self->SUPER::delete(@_);
+ my $error;
+ foreach (qw(deploy_zone_block deploy_zone_vertex)) {
+ $error ||= $self->process_o2m(
+ 'table' => $_,
+ 'num_col' => 'zonenum',
+ 'fields' => 'zonenum',
+ 'params' => {},
+ );
+ }
+ $error ||= $self->SUPER::delete(@_);
if ($error) {
dbh->rollback if $oldAutoCommit;
@@ -185,6 +204,7 @@ sub check {
$self->ut_numbern('zonenum')
|| $self->ut_text('description')
|| $self->ut_number('agentnum')
+ || $self->ut_numbern('censusyear')
|| $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
|| $self->ut_textn('dbaname')
|| $self->ut_enum('zonetype', [ 'B', 'P' ])
@@ -219,24 +239,6 @@ sub check {
$self->SUPER::check;
}
-=item element_table
-
-Returns the name of the table that contains the zone's elements (blocks or
-vertices).
-
-=cut
-
-sub element_table {
- my $self = shift;
- if ($self->zonetype eq 'B') {
- return 'deploy_zone_block';
- } elsif ( $self->zonetype eq 'P') {
- return 'deploy_zone_vertex';
- } else {
- die 'unknown zonetype';
- }
-}
-
=item deploy_zone_block
Returns the census block records in this zone, in order by census block
@@ -244,8 +246,7 @@ number. Only appropriate to block-type zones.
=item deploy_zone_vertex
-Returns the vertex records for this zone, in order by sequence number. Only
-appropriate to polygon-type zones.
+Returns the vertex records for this zone, in order by sequence number.
=cut
@@ -267,7 +268,19 @@ sub deploy_zone_vertex {
});
}
-=back
+=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);
+}
=head2 SUBROUTINES
@@ -315,7 +328,125 @@ sub process_batch_import {
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 = 'http://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};
+
+ 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;
+}
+
=head1 BUGS
=head1 SEE ALSO
diff --git a/FS/FS/deploy_zone_block.pm b/FS/FS/deploy_zone_block.pm
index 757af7e..2ac18e2 100644
--- a/FS/FS/deploy_zone_block.pm
+++ b/FS/FS/deploy_zone_block.pm
@@ -43,10 +43,6 @@ L<FS::deploy_zone> foreign key for the zone.
U.S. census block number (15 digits).
-=item censusyear
-
-The year of the census map where the block appeared or was last verified.
-
=back
=head1 METHODS
@@ -107,7 +103,6 @@ sub check {
$self->ut_numbern('blocknum')
|| $self->ut_number('zonenum')
|| $self->ut_number('censusblock')
- || $self->ut_number('censusyear')
;
return $error if $error;
diff --git a/FS/FS/o2m_Common.pm b/FS/FS/o2m_Common.pm
index 4f6d2e7..430f00b 100644
--- a/FS/FS/o2m_Common.pm
+++ b/FS/FS/o2m_Common.pm
@@ -35,11 +35,19 @@ Available options:
table (required) - Table into which the records are inserted.
-num_col (optional) - Column in table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
-
-params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form.
-
-fields (required) - Arrayref of field names for each record in table. Pulled from params as "pkeyNN_field" where pkey is table's primary key and NN is the entry's numeric identifier.
+fields (required) - Arrayref of the field names in the "many" table.
+
+params (required) - Hashref of keys and values, often passed as
+C<scalar($cgi->Vars)> from a form. This will be scanned for keys of the form
+"pkeyNN" (where pkey is the primary key column name, and NN is an integer).
+Each of these designates one record in the "many" table. The contents of
+that record will be taken from other parameters with the names
+"pkeyNN_myfield" (where myfield is one of the fields in the 'fields'
+array).
+
+num_col (optional) - Name of the foreign key column in the "many" table, which
+links to the primary key of the base table. If not specified, it is assumed
+this has the same name as in the base table.
=cut
diff --git a/FS/FS/part_pkg_fcc_option.pm b/FS/FS/part_pkg_fcc_option.pm
index 5c78e5f..3d821f5 100644
--- a/FS/FS/part_pkg_fcc_option.pm
+++ b/FS/FS/part_pkg_fcc_option.pm
@@ -148,7 +148,7 @@ tie our %spectrum_labels, 'Tie::IxHash', (
95 => 'Wireless Communications Service (WCS) Band',
96 => 'Broadband Radio Service/Educational Broadband Service Band',
97 => 'Satellite (e.g. L-band, Big LEO, Little LEO)',
- 98 => 'Unlicensed (including broadcast television “white spaces”) Bands',
+ 98 => 'Unlicensed (including broadcast television "white spaces") Bands',
99 => '600 MHz',
100 => 'H Block',
101 => 'Advanced Wireless Services (AWS) 3 Band',
diff --git a/httemplate/browse/deploy_zone.html b/httemplate/browse/deploy_zone.html
index 3bd9d07..02ebb8b 100644
--- a/httemplate/browse/deploy_zone.html
+++ b/httemplate/browse/deploy_zone.html
@@ -17,6 +17,7 @@
'Market',
'Advertised Mbps',
'Contractual Mbps',
+ 'Vertices',
'Census blocks',
],
fields => [ 'zonenum',
@@ -42,6 +43,9 @@
)
},
sub { my $self = shift;
+ FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+ },
+ sub { my $self = shift;
FS::deploy_zone_block->count('zonenum = '.$self->zonenum)
},
],
@@ -53,7 +57,7 @@
'(cir_speed_down, cir_speed_up)',
],
links => [ $link_fixed, $link_fixed, ],
- align => 'clllllr',
+ align => 'cllllrr',
nohtmlheader => 1,
disable_maxselect => 1,
disable_total => 1,
diff --git a/httemplate/edit/deploy_zone-fixed.html b/httemplate/edit/deploy_zone-fixed.html
index 90d1b66..b8d9f8b 100644
--- a/httemplate/edit/deploy_zone-fixed.html
+++ b/httemplate/edit/deploy_zone-fixed.html
@@ -54,29 +54,36 @@
value => 'Contractually guaranteed speed (Mbps)' },
'cir_speed_down',
'cir_speed_up',
-
- { type => 'tablebreak-tr-title', value => 'Census blocks'},
- { field => 'file',
- type => 'file-upload',
- },
- { field => 'format',
- type => 'hidden',
- value => 'plain',
- },
- { field => 'censusyear',
- type => 'select',
- options => [ '', qw( 2013 2012 2011 ) ],
- },
-
- { type => 'tablebreak-tr-title', value => '', },
- { field => 'blocknum',
- type => 'deploy_zone_block',
- o2m_table => 'deploy_zone_block',
- m2_label => ' ',
- m2_error_callback => $m2_error_callback,
- },
+ { type => 'tablebreak-tr-title', value => 'Footprint'},
+ { field => 'vertices',
+ type => 'polygon',
+ curr_value_callback => sub {
+ my ($cgi, $object) = @_;
+ $cgi->param('vertices') || $object->vertices_json;
+ },
+ }
+#
+# { type => 'tablebreak-tr-title', value => 'Census blocks'},
+# { field => 'file',
+# type => 'file-upload',
+# },
+# { field => 'format',
+# type => 'hidden',
+# value => 'plain',
+# },
+# { field => 'censusyear',
+# type => 'hidden',
+# options => [ '', qw( 2013 2012 2011 ) ],
+# },
+#
+# { type => 'tablebreak-tr-title', value => '', },
+# { field => 'blocknum',
+# type => 'deploy_zone_block',
+# o2m_table => 'deploy_zone_block',
+# m2_label => ' ',
+# m2_error_callback => $m2_error_callback,
+# },
],
-
&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -90,22 +97,22 @@ my $technology_labels = FS::part_pkg_fcc_option->technology_labels;
my $media_types = FS::part_pkg_fcc_option->media_types;
delete $media_types->{'Mobile Wireless'}; # cause this is the fixed zone page
-my $m2_error_callback = sub {
- my ($cgi, $deploy_zone) = @_;
- my @blocknums = grep {
- /^blocknum\d+/ and length($cgi->param($_.'_censusblock'))
- } $cgi->param;
-
- sort { $a->censusblock <=> $b->censusblock }
- map {
- my $k = $_;
- FS::deploy_zone_block->new({
- blocknum => scalar($cgi->param($k)),
- zonenum => $deploy_zone->zonenum,
- censusblock => scalar($cgi->param($k.'_censusblock')),
- censusyear => scalar($cgi->param($k.'_censusyear')),
- })
- } @blocknums;
-};
+#my $m2_error_callback = sub {
+# my ($cgi, $deploy_zone) = @_;
+# my @blocknums = grep {
+# /^blocknum\d+/ and length($cgi->param($_.'_censusblock'))
+# } $cgi->param;
+#
+# sort { $a->censusblock <=> $b->censusblock }
+# map {
+# my $k = $_;
+# FS::deploy_zone_block->new({
+# blocknum => scalar($cgi->param($k)),
+# zonenum => $deploy_zone->zonenum,
+# censusblock => scalar($cgi->param($k.'_censusblock')),
+# censusyear => scalar($cgi->param($k.'_censusyear')),
+# })
+# } @blocknums;
+#};
</%init>
diff --git a/httemplate/edit/deploy_zone-mobile.html b/httemplate/edit/deploy_zone-mobile.html
index d049cb0..8cec298 100644
--- a/httemplate/edit/deploy_zone-mobile.html
+++ b/httemplate/edit/deploy_zone-mobile.html
@@ -49,14 +49,21 @@
'adv_speed_down',
'adv_speed_up',
{ type => 'tablebreak-tr-title', value => 'Footprint'},
- { field => 'vertexnum',
- type => 'deploy_zone_vertex',
- o2m_table => 'deploy_zone_vertex',
- m2_label => ' ',
- m2_error_callback => $m2_error_callback,
- },
- ],
+ { field => 'vertices',
+ type => 'polygon',
+ curr_value_callback => sub {
+ my ($cgi, $object) = @_;
+ $cgi->param('vertices') || $object->vertices_json;
+ },
+ }
+# { field => 'vertexnum',
+# type => 'deploy_zone_vertex',
+# o2m_table => 'deploy_zone_vertex',
+# m2_label => ' ',
+# m2_error_callback => $m2_error_callback,
+# },
+ ],
&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
diff --git a/httemplate/edit/process/deploy_zone-fixed.html b/httemplate/edit/process/deploy_zone-fixed.html
index eae3a74..0033bbe 100644
--- a/httemplate/edit/process/deploy_zone-fixed.html
+++ b/httemplate/edit/process/deploy_zone-fixed.html
@@ -3,12 +3,31 @@
error_redirect => popurl(2).'deploy_zone-fixed.html',
table => 'deploy_zone',
viewall_dir => 'browse',
- process_o2m => {
- 'table' => 'deploy_zone_block',
- 'fields' => [qw( censusblock censusyear )]
- },
- process_upload => {
- 'process' => 'misc/process/deploy_zone-import.html',
- 'fields' => [qw( censusyear format )],
- },
+ precheck_callback => $precheck_callback,
+ process_o2m =>
+ { 'table' => 'deploy_zone_vertex',
+ 'fields' => [qw( latitude longitude )]
+ },
+ progress_init => [
+ 'PostForm',
+ [ 'zonenum' ],
+ $fsurl.'misc/process/deploy_zone-block_lookup.cgi',
+ $fsurl.'browse/deploy_zone.html',
+ ],
&>
+<%init>
+my $precheck_callback = sub {
+ # convert the vertex list into a process_o2m-style parameter list
+ if ( $cgi->param('vertices') ) {
+ my $vertices = decode_json($cgi->param('vertices'));
+ my $i = 0;
+ foreach (@$vertices) {
+ $cgi->param("vertexnum${i}", '');
+ $cgi->param("vertexnum${i}_latitude", $_->[0]);
+ $cgi->param("vertexnum${i}_longitude", $_->[1]);
+ $i++;
+ }
+ }
+ '';
+};
+</%init>
diff --git a/httemplate/edit/process/deploy_zone-mobile.html b/httemplate/edit/process/deploy_zone-mobile.html
index 7b8f911..d36d5d4 100644
--- a/httemplate/edit/process/deploy_zone-mobile.html
+++ b/httemplate/edit/process/deploy_zone-mobile.html
@@ -2,8 +2,25 @@
error_redirect => popurl(2).'deploy_zone-mobile.html',
table => 'deploy_zone',
viewall_dir => 'browse',
- process_o2m =>
+ precheck_callback => $precheck_callback,
+ process_o2m =>
{ 'table' => 'deploy_zone_vertex',
'fields' => [qw( latitude longitude )]
},
&>
+<%init>
+my $precheck_callback = sub {
+ # convert the vertex list into a process_o2m-style parameter list
+ if ( $cgi->param('vertices') ) {
+ my $vertices = decode_json($cgi->param('vertices'));
+ my $i = 0;
+ foreach (@$vertices) {
+ $cgi->param("vertexnum${i}", '');
+ $cgi->param("vertexnum${i}_latitude", $_->[0]);
+ $cgi->param("vertexnum${i}_longitude", $_->[1]);
+ $i++;
+ }
+ }
+ '';
+};
+</%init>
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 69bd605..a76f4be 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -160,7 +160,26 @@ process();
</script>
<& /elements/footer.html &>
-%} elsif ( $opt{'popup_reload'} ) {
+% } elsif ( $opt{'progress_init'} ) {
+% # some false laziness with the above
+% my ($form_name, $job_fields) = @{ $opt{'progress_init'} };
+<form name="<% $form_name %>">
+% foreach my $field (@$job_fields) {
+ <input type="hidden" name="<% $field %>" value="<% $cgi->param($field) |h %>">
+% }
+<& /elements/progress-init.html,
+ @{ $opt{'progress_init'} }
+&>
+<input type="submit" style="display:none">
+</form>
+<script>
+<&| /elements/onload.js &>
+process();
+</&>
+</script>
+<& /elements/footer.html &>
+
+% } elsif ( $opt{'popup_reload'} ) {
<% include('/elements/header-popup.html', $opt{'popup_reload'} ) %>
diff --git a/httemplate/elements/polygon.html b/httemplate/elements/polygon.html
new file mode 100644
index 0000000..c26e985
--- /dev/null
+++ b/httemplate/elements/polygon.html
@@ -0,0 +1,127 @@
+<%init>
+my %opt = @_;
+my $field = $opt{'field'};
+my $id = $opt{'id'} || $opt{'field'};
+my $div_id = "div_$id";
+
+my $vertices_json = $opt{'curr_value'} || '[]';
+</%init>
+<& hidden.html, %opt &>
+<div id="<% $div_id %>" style="height: 600px; width: 600px"></div>
+
+<script src="https://maps.googleapis.com/maps/api/js?libraries=drawing"></script>
+<script>
+var map;
+var drawingManager;
+
+function updateFormInput(event) {
+ var path = window.polygon.getPath();
+ var vertices = []; // array of arrays, geoJSON style
+ for (var i =0; i < path.getLength(); i++) {
+ var xy = path.getAt(i);
+ vertices[i] = [ xy.lat(), xy.lng() ];
+ }
+ console.log(vertices); //XXX
+ $('#<% $field %>').prop('value', JSON.stringify(vertices));
+}
+
+$(function() {
+ mapOptions = {
+ zoom: 4,
+ center: {lat: 39.40114, lng: -96.57127}, // continental U.S.
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ panControl: true,
+ scaleControl: true,
+ streetViewControl: false,
+ };
+ map = new google.maps.Map($('#<% $div_id %>')[0], mapOptions);
+
+ var polygonComplete = function(p) {
+ window.polygon = p;
+ if (drawingManager) {
+ drawingManager.setDrawingMode(null);
+ drawingManager.setOptions({ drawingControl: false });
+ }
+ // double click to delete a vertex (so long as it remains a polygon)
+ p.addListener('dblclick', function (mev) {
+ if (mev.vertex != null && window.polygon.getPath().length > 3) {
+ p.getPath().removeAt(mev.vertex);
+ }
+ });
+ // any time the polygon is modified, update the vertex list
+ p.getPath().addListener('set_at', updateFormInput);
+ p.getPath().addListener('insert_at', updateFormInput);
+ p.getPath().addListener('remove_at', updateFormInput);
+
+ // and also now
+ updateFormInput();
+ };
+
+ var polygonOptions = {
+ fillColor: '#0000a0',
+ fillOpacity: 0.2,
+ strokeColor: '#0000a0',
+ strokeWeight: 2,
+ clickable: false,
+ editable: true,
+ zIndex: 1,
+ map: map,
+ };
+
+ var vertex_array = <% $vertices_json %>;
+ if ( vertex_array.length > 2 ) {
+ // then we already have a polygon. make it acceptable to google maps,
+ // and also create a bounding box for it and fit the map to that.
+
+ var path = [];
+ var bounds = new google.maps.LatLngBounds();
+ for (var i = 0; i < vertex_array.length; i++) {
+ var xy = new google.maps.LatLng(vertex_array[i][0], vertex_array[i][1]);
+ path.push(xy);
+ bounds.extend(xy);
+ }
+
+ polygonOptions.paths = [ path ];
+ polygonComplete(new google.maps.Polygon(polygonOptions));
+ map.fitBounds(bounds);
+
+ } else {
+ // there are no vertices, or not enough to make a polygon, so
+ // enable drawing mode to create a new one
+
+ drawingManager = new google.maps.drawing.DrawingManager({
+ drawingMode: google.maps.drawing.OverlayType.POLYGON,
+ drawingControl: true,
+ drawingControlOptions: {
+ position: google.maps.ControlPosition.TOP_CENTER,
+ drawingModes: [
+ google.maps.drawing.OverlayType.POLYGON,
+ ]
+ },
+ polygonOptions: polygonOptions,
+ });
+
+ // after a single polygon is drawn: remember it, add a listener to let
+ // nodes be deleted, and exit drawing mode
+ drawingManager.addListener('polygoncomplete', polygonComplete);
+ drawingManager.setMap(map);
+
+ // center the map on the user (for lack of a better choice)
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position) {
+ var pos = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+
+ map.setCenter(pos);
+ map.setZoom(12);
+ });
+ } // on error, or if geolocation isn't available, do nothing
+ }
+
+});
+
+ </script>
+ </body>
+</html>
diff --git a/httemplate/elements/tr-polygon.html b/httemplate/elements/tr-polygon.html
new file mode 100644
index 0000000..6990d3d
--- /dev/null
+++ b/httemplate/elements/tr-polygon.html
@@ -0,0 +1,5 @@
+<tr>
+<td colspan=2>
+<& polygon.html, @_ &>
+</td>
+</tr>
diff --git a/httemplate/misc/process/deploy_zone-block_lookup.cgi b/httemplate/misc/process/deploy_zone-block_lookup.cgi
new file mode 100644
index 0000000..8f4eac7
--- /dev/null
+++ b/httemplate/misc/process/deploy_zone-block_lookup.cgi
@@ -0,0 +1,13 @@
+<% $server->process %>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right([
+ 'Edit FCC report configuration',
+ 'Edit FCC report configuration for all agents',
+ ]);
+
+my $server = FS::UI::Web::JSRPC->new(
+ 'FS::deploy_zone::process_block_lookup', $cgi
+);
+</%init>