#!/usr/bin/perl ## North Dakota / South Dakota boundary monument calculations, ## for https://www.jidanni.org/geo/borders/n_s_dakota/index.html ## Author: Dan Jacobson https://www.jidanni.org/ ## Copyright: https://www.gnu.org/licenses/gpl.html ## Created: 2023-02-16T03:24:18+0000 ## Last-Updated: 2023-12-28T22:34:05+0000 ## Update #: 333 # Extra parentheses added so the reader can see the logic. use strict; use warnings q(all); my $BURKLE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXY"; my $adams1 = 293; my $Usage = "Input should look like lines of the following. One per line: 1) Monument miles 2) Burkle addresses, or corners 3) PLSS section,township and range 4) South Dakota corners 5) Adams County North Dakota corners 6) PLSS point IDs e.g., ND051290N1070W0_500100 (SN__0 format is only on output.) All formatted as per the /.../ regular expressions in the program. Example: $0 <) { chomp; my $line = $_; s/^\s+//; s/#.*//; next unless length; # Skip blank lines. print "$line:"; #Repeat input in output s/½/.5/; if (/^ \d+ (?: \.\d+ )? $/x) { # The user gave us miles, # I'll compute all these (avenue, address, PLSS...) # independently, as one day one might want to separate them # back out. $_ is inputted monument miles. { # Tell them the Burkle avenue my $avenue = $_ - 184; my $nwse; if ( $avenue > 0 ) { $nwse = "SW"; } elsif ( $avenue < 0 ) { $nwse = "SE"; $avenue = -$avenue; } else { $nwse = "S"; } if ($avenue) { $avenue =~ s/\.\d+/miles_to_burkle_minor_street($&)/e; } else { $avenue = "Center"; } printf "\n\t%s AVE & %s ST %s;", $avenue, 102, $nwse; } { # Tell them the Burkle address, my $house_number = ( $_ - 184 ) * 100 + 1; # North side of 102nd Street are odd numbers. Yes, 99s in # SE address quadrant, but we always want the SE section # corner. printf "\n\t%d %d ST %s;", abs $house_number, 102, # Street number always the same, since we go due west. $house_number < 0 ? "SE" : "SW"; } { # Tell them the South Dakota avenue printf "\n\t%.2f AVE & %s ST South Dakota;", south_dakota( $_, 0 ), 100; # One day also tell them the South Dakota address. # Wait, gosh, they look like our %.2f strings (but with a dot.) # Except those could be both odd and even. } { if ( $_ >= 262 && $_ <= 310 ) { # If in Adams County ND, ## 294=2SW, 293=1S, 292=2SE #Bend in 1st S. ignored. my $adams = $_ - $adams1; my $nwse; if ( $adams < 0 ) { $nwse = "SE" } elsif ( $adams > 0 ) { $nwse = "SW" } else { $nwse = "S" } $adams = 1 + abs $adams; ## # Tell them its street number printf "\n\t%s AVE & %s ST %s Adams Co. ND;", 5, #State line is always 5th Ave. $adams, $nwse; # One day could also tell them the Adams County address (?) } } { # tell them what (North Dakota) section corner that is: my @A = ( # SE, because R_W gets bigger westbound. /\.\d*/ ? "$& mi w of " : "", 36 - ( ( $_ + 2 ) % 6 ), # Section number. 129, # Township number, always the same, since we go due west. 47 + ( $_ + 2 ) / 6 ); # Range number. printf "\n\t%sSE cr S%d T%dN R%dW;", @A; printf "\n\t%sSE cr ND05%03d0N%03d0W0SN%02d0;" , #but not accepting back as input for round trip yet. $A[0], @A[ 2, 3 ], $A[1]; } { # tell them a PLSS point ID =bla Wow, super hard. Should use some ../../house_numbering/grids/utilities ideas This is wrong: printf "\n\tND051290N%03d0W0_%d%d0100;", #It took hours to figure this all out: int( 47 + ( $_ + 2 ) / 6 ) , # Range number. 7 - ( $_ + 2 ) % 6, #column in section #RIGHT abs( ( int $_ ) - $_ ) * 8; # half Section =cut } } # The user wants to know what mile a place is at, elsif ( /\b 5 \s+ AV\w* \s* & \s* ([\d.]+) \s* ST\w* \s+ (S[WE]?) \s+ Adams\b/ix ) { # The user gave us an Adams corner my $adams = $1 - 1; if ( $2 eq "SE" ) { $adams = -$adams } printf "\n\t%s", $adams + $adams1; } elsif (/([\d.]+) \s+ AV\w* \s* & \s* 100 \s* ST\w* \s+ South \s+ Dakota/ix) { # The user gave us a South Dakota corner printf "\n\t%s", south_dakota( $1, 1 ); } elsif ( /(\d+|center)([$BURKLE_ALPHABET]?) \s+ AV\w* \s* & \s* 102 \s* ST\w* \s+ ([SEW]+) \b/ix ) { # The user gave us a Burkle corner my ( $avenue, $letter, $nwse ) = ( $1, $2, $3 ); $avenue =~ s/center/0/i; if ($letter) { $avenue += burkle_minor_street_to_miles($letter); } if ( $nwse =~ /SE/i ) { $avenue = -$avenue } printf "\n\t%s", $avenue + 184; } elsif (/(\d+) \s+ 102 \s+ ST \s+ S([EW]) \b/ix) { # The user gave us a Burkle house address printf "\n\t%s", ( ( $1 * ( $2 eq 'E' ? -1 : 1 ) ) - 1 ) / 100 + 184; } elsif (/(.*)SE \s* c\w* \s+ S\D* \s* (\d+) \s+ T129N \s+ R(\d+)\s*W\b/ix) { # The user gave us a section corner. my ( $verbs, $section, $range ) = ( $1, $2, $3 ); my $miles = ( ( $range - 48 ) * 6 + 4 ) + ( 36 - $section ); #show logic if ($verbs) { if ( $verbs =~ /^([\d\.]+) \s+ mi\w* \s+ w\w* \s+ of /ix ) { $miles += $1; } #"(x).y miles west of" else { die "Fractional ($verbs) miles error."; } } printf "\n\t%s", $miles; } ## The user gave us a PLSS Point ID # Here we dissect a "Bureau of Land Management # Geographic Coordinate Database (GCDB) Publication # Point IDs", e.g., AZ230490N0130W0_240400 And "GCDB # NAMING CONVENTIONS Meridian codes, Township names, # Point IDs, SID Numbers" elsif (/\b(ND051290N...0W0_[1-7][04]0100)\b/) { my @v = unpack "xxxxxxxxxA3xxxxAAxxxx", $1; my $miles = ( $v[0] - 47 ) * 6 + 5 - $v[1] - $v[2] / 8; printf "\n\t%s", $miles; } else { die "$_: $Usage"; } print "\n"; } sub burkle_minor_street_to_miles { return ( 1 + index( $BURKLE_ALPHABET, uc $_[0] ) ) / ( 1 + length $BURKLE_ALPHABET ); } sub miles_to_burkle_minor_street { my @letters = split( //, $BURKLE_ALPHABET ); return $letters[ $_[0] * @letters ]; # Yes, will make "0M AVE", and no, cannot blame it on using quadrants. } sub south_dakota { my @a = ( [ 1, # Monument miles at Monument 1 360.566875 # Monument miles at Terminal Monument, 360 miles + 45.35 chains ], [ 480, # South Dakota avenue number at Monument 1 100.6233 # South Dakota avenue number at Terminal Monument. See index.html... # about how we are just glossing over the wrinkles in the South Dakota system. ] ); @a = reverse @a if $_[1]; return $a[1][0] + ( $a[1][1] - $a[1][0] ) / ( $a[0][1] - $a[0][0] ) * ( $_[0] - $a[0][0] ); }