#!/usr/bin/perl # PLSS point ids to addresses, for Evanston IL # Author: Dan Jacobson https://www.jidanni.org/ # Copyright: https://www.gnu.org/licenses/gpl.htm # Created: 2024-02-18T01:36:52+0000 # Last-Updated: 2024-03-11T03:28:29+0000 # Update #: 720 # =pod Actually why am I still forcing mile/80 granularity when it comes to giving address points? Any integer should be fine... =head1 "Off the deep end" with gdaltransform Holy moly, now going off the deep end, with gdaltransform. Our concept today is only give gdaltransform only the four GCPs relevant to the point in question (the place where we want to put our address tick.) We give it only the four surrounding section corners. =cut use strict; use warnings q(all); use PointId2Address; use Twsp2mi; use Data::Dumper; $Data::Dumper::Deepcopy++; $Data::Dumper::Sortkeys++; my %co; =head1 Evanston actually has three or more grids (if one looks at it from the PLSS.) =over =item T41N, south of Central St. Lots of Evanston. Chopped of before a full mile, at Central St. =item T42N, which we will define as Central St. and northwards Note the PLSS data has nicely created missing sections out of the Ouilmette Reservation, which we will thankfully use. =item Downtown Downtown is a part of T41N that we will apply a tilt to... =back =cut my $false_corners; if ( $ARGV[0] eq '--false-corners' ) { $false_corners++; shift; } my %grids; $grids{S} = { origin => { id => 'IL030410N0140E0_100240', # corner of Howard & Asbury address => [ -1300, 100 ] # just a random observation point }, num_per_mile => [ 1200, 800 ] # from City ordinance }; %{ $grids{N} } = %{ $grids{S} }; #deep copy # Must start using custom assignments due to distortion, visible on # the grids on the sides of the official city maps too # Highland is 3200w, so section line should be 3800w. # IL030420N0130E0_300100 W city limit 3800 W 2600 N # IL030420N0130E0_400100 Ewing 2800 W 2600 N # IL030420N0130E0_500100 Ashland 1500 W 2600 N $grids{N}{origin} = { id => 'IL030420N0130E0_500100', # corner of Central & Ashland address => [ -1500, 2600 ] # just a random observation point }; sub pid2sector { return $_[0] =~ /0420N/ ? 'N' : 'S'; #Add Downtown one day? } # Here we go. We are reading in the corners! while (<>) { chomp; my @F = split /,/; $co{ $F[2] } = [ @F[ 0, 1 ] ]; } =head1 Irregular shaped sections To top it all off, the study area (Evanston) includes irregular sections, ragged on the east at the lakeshore, and chopped on the north at Central St. Using any irregular section corners would distort our results! OK, we will build a plank into the lake of false teeth sections. Maybe Central St. isn't so bad... =cut if ($false_corners) { my %new_co; # It's the world of "false teeth"! We apply our fixes to the # lakefront broken edge sections... my @needed = qw/ IL030410N0140E0_100500 IL030410N0140E0_100600 IL030410N0140E0_200500 IL030410N0140E0_200600 IL030410N0140E0_300200 IL030410N0140E0_300300 IL030410N0140E0_300400 IL030420N0130E0_600100 IL030420N0130E0_600200 /; for ( # Order matters, build in rows, then in each row by columns eastward. sort { ( Twsp2mi::point_id2miles($a) )[1] <=> ( Twsp2mi::point_id2miles($b) )[1] || ( Twsp2mi::point_id2miles($a) )[0] <=> ( Twsp2mi::point_id2miles($b) )[0] } @needed ) { if ( $co{$_} ) { warn "Already know coordinates for $_. Skipping!"; next; } warn "Making supplemental $_"; my @id; $id[0] = $_; my @ll; my @comm = qw/echo/; for ( 1, 2 ) { $id[$_] = Twsp2mi::point_id_plus_miles( $id[0], -$_, 0 ); unless ( $ll[$_] = $co{ $id[$_] } ) { $id[$_] =~ s/0140E0_100/0130E0_700/; unless ( $ll[$_] = $co{ $id[$_] } ) { die "While computing\n$id[0], no known corner at:\n$id[$_]"; } } } my @G = qw/| geod +ellps=WGS84 +units=us-mi -f %.6f/; for ( 2, 1 ) { push @comm, reverse @{ $ll[$_] } } push @comm, @G, "-I"; my $res = qx(@comm) or die; my ( $az, $back_az, $dist ) = split /\s+/, $res; @comm = ( "echo", ( reverse @{ $ll[1] } ), $az, 1, @G ); $res = qx(@comm) or die; ( my ( $lat, $lon ), $back_az ) = split /\s+/, $res; @{ $new_co{$_} } = @{ $co{$_} } = ( $lon, $lat ); } for ( keys %new_co ) { print join ",", @{ $new_co{$_} }, $_; print $/; } exit; } my @targets = generate_sample_ticks(); sub generate_sample_ticks { my @ticks; # Example ticks for ( my $i = 400 ; $i <= 2400 ; $i += 300 ) { push @ticks, [ $i, -$i, 100 ]; #Howard St. ##[LABEL, X, Y] } for ( my $i = 700 ; $i <= 3600 ; $i += 300 ) { push @ticks, [ $i, -$i, 2100 ]; #Simpson St. } for ( my $i = 600 ; $i <= 3600 ; $i += 300 ) { push @ticks, [ $i, -$i, 2600 ]; #Central St. } for ( my $i = 300 ; $i <= 2599 ; $i += 200 ) { push @ticks, [ $i, -1300, $i ]; #Asbury Ave. S sector } for ( my $i = 2600 ; $i <= 2800 ; $i += 200 ) { push @ticks, [ $i, -1305, $i ]; #Asbury Ave. N sector } for ( my $i = 300 ; $i <= 1100 ; $i += 150 ) { my $k = 100 + $i; push @ticks, [ $k, -$k, $k ]; #West=North Diagonal } return @ticks; } my %tarr; for (@targets) { my ( $label, @addr ) = @$_; my $id = PointId2Address::addr2id( $grids{ addr_pair2sector(@addr) }, @addr ); $tarr{$id}{gov} = PointId2Address::id2governing_id($id); unless ( @{ $tarr{$id}{coordinates} } = govid_and_adr2coords( $tarr{$id}{gov}, @addr ) ) { warn "Skipping $label @addr"; next; } die "Cannot make $id coordinates" unless 2 == @{ $tarr{$id}{coordinates} }; print join ",", @{ $tarr{$id}{coordinates} }, $label; print $/; } sub govid_and_adr2coords { my ( $govid, @adr ) = @_; my @surrounding_ids = PointId2Address::surrounding_ids($govid); my @args; for (@surrounding_ids) { push @args, "-gcp"; push @args, id2addr_local($_); unless ( $co{$_} ) { my $mess = "@args"; for ($mess) { s/-gcp/\n$&/g; s/\s+\n/\n/g; s/^\n//; } warn "Gov $govid, adr @adr:\n$mess: No known corner of $_"; return undef; } push @args, @{ $co{$_} }; } my $result = qx!echo @adr | gdaltransform -output_xy @args!; die unless $result; #poor error checking chomp $result; return split / /, $result; } sub id2addr_local { my $id = $_[0]; my $sector = pid2sector($id); ##Hmm, accessing a global variable... $grids{$sector}{target} = { id => $id }; # Must wipe out previous {target}{miles} my @o = PointId2Address::id2addr_raw( $grids{$sector} ) or die; if ( $id =~ /600$/ ) { #Central street! $o[1] = 2600; } return @o; } =head1 addr_pair2sector() - determine via address what sector of Evanston this is. Lets call our sectors S, for south of Central St., N for Central St. and northward, and D for Downtown. =cut sub addr_pair2sector { my ( $x, $y, @rest ) = @_; if ( $y >= 2600 ) { return 'N'; } elsif (0) { return 'D' } #not ready yet else { return 'S' } }