If one were to enter the words: 'softly affiliate activation' at the what3words.com site, the response would be a location in Walt Disney World. The latitude and longitude for that spot is 28.3852 -81.5638. Using that service enables anyone to specify any place on the Earth with three words.

Three word location is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Task: Provide a similar solution - Display a location on the Earth with three words derived from a latitude longitude pair.

For: latitude = 28.3852 longitude = -81.5638 Display: W18497 W11324 W01322

Implementation:

Build an array of 28126, 6 character words in the form W00000 thru W28125.

Convert latitude and longitude into positive integers.

Build a 43 bit integer containing latitude (21 bits) and longitude (22 bits).

Isolate most significant 15 bits for word 1 index.

Isolate next 14 bits for word 2 index.

Isolate next 14 bits for word 3 index.

Fetch each word from the word array.

Display the words.

Reverse the procedure and display the original latitude and longitude.

Extra credit: Find a way to populate the word array with actual words.


-- Three-word location
-- J. Carter     2023 May
-- Uses the PragmAda Reusable Components (https://github.com/jrcarter/PragmARC)

with Ada.Text_IO;
with PragmARC.Images;

procedure Three_Word is
   type U43 is mod 2 ** 43;
   subtype Word is String (1 .. 6);
   
   function Image is new PragmARC.Images.Modular_Image (Number => U43);
   function Image is new PragmARC.Images.Float_Image (Number => Float);
   
   Lat  : constant String :=  "28.3852";
   Long : constant String := "-81.5638";
   
   LL_Mix : U43 := U43 ( (Float'Value (Lat) + 90.0) * 10_000.0) * 2 ** 22 + U43 ( (Float'Value (Long) + 180.0) * 10_000.0);
   W3n    : U43 := LL_Mix rem 2 ** 14;             -- Number for 3rd word
   W2n    : U43 := (LL_Mix / 2 ** 14) rem 2 ** 14; --    "    "  2nd   "
   W1n    : U43 := LL_Mix / 2 ** 28;               --    "    "  1st   "
   
   W1 : constant Word := 'W' & Image (W1n, Width => 5, Zero_Filled => True); -- 1st word
   W2 : constant Word := 'W' & Image (W2n, Width => 5, Zero_Filled => True); -- 2nd   "
   W3 : constant Word := 'W' & Image (W3n, Width => 5, Zero_Filled => True); -- 3rd   "
begin -- Three_Word
   Ada.Text_IO.Put (Item => Lat & ", " & Long & " => " & W1 & ' ' & W2 & ' ' & W3 & " => ");
   
   -- Reverse the process
   W1n := U43'Value (W1 (2 .. 6) );
   W2n := U43'Value (W2 (2 .. 6) );
   W3n := U43'Value (W3 (2 .. 6) );
   LL_Mix := W1n * 2 ** 28 + W2n * 2 ** 14 + W3n;
   Ada.Text_IO.Put_Line  (Item => Image (Float (LL_Mix / 2 ** 22) / 10_000.0 - 90.0, Fore => 0, Aft => 4, Exp => 0) & ", " &
                                  Image (Float (LL_Mix rem 2 ** 22) / 10_000.0 - 180.0, Fore => 0, Aft => 4, Exp => 0) );
end Three_Word;
Output:
28.3852, -81.5638 => W18497 W11324 W01322 => 28.3852, -81.5638
Translation of: Lua

... but using arithmetic instead of bit shifting and masking.

BEGIN # three word location, translation of the Lua sample #

    PROC to word = ( INT w )STRING:
         BEGIN
            STRING word := "W" + whole( w, -5 );
            FOR i FROM LWB word TO UPB word DO IF word[ i ] = " " THEN word[ i ] := "0" FI OD;
            word
         END # to word # ;

    PROC from word = ( STRING word )INT:
         BEGIN
            INT result := 0;
            FOR i FROM LWB word + 1 TO UPB word DO result *:= 10 +:= ( ABS word[ i ] - ABS "0" ) OD;
            result
         END # from number # ;

    INT two to 14 = 2 ^ 14, two to 22 = 2 ^ 22, INT two to 28 = 2 ^ 28;

    PROC to three words = ( REAL lat, lon, REF STRING word1, word2, word3 )VOID:
         BEGIN
            # convert from lat and lon to positive integers #
            INT ilat = ENTIER( lat * 10000 ) +  900000
              , ilon = ENTIER( lon * 10000 ) + 1800000
              ;
            # build 43 bit number comprising 21 bits (lat) and 22 bits (lon) #
            LONG INT latlon = ( LENG ilat * two to 22 ) + LENG ilon;

            # isolate relevant bits #
            INT w1 = SHORTEN ( latlon OVER two to 28 ) MOD ABS 16r8000
              , w2 = SHORTEN ( latlon OVER two to 14 ) MOD ABS 16r4000
              , w3 = SHORTEN ( latlon                  MOD ABS 16r4000 )
              ;
            # convert to word format #
            word1 := to word( w1 );
            word2 := to word( w2 );
            word3 := to word( w3 )

         END # to three words # ;

    PROC from three words = ( STRING word1, word2, word3, REF REAL lat, lon )VOID:
         BEGIN
            INT w1 = from word( word1 )
              , w2 = from word( word2 )
              , w3 = from word( word3 )
              ;
            LONG INT latlon = ( LENG w1 * two to 28 ) + ( LENG w2 * two to 14 ) + LENG w3;
            INT      ilat   = SHORTEN ( latlon OVER two to 22 )
                   , ilon   = SHORTEN ( latlon MOD  ABS 16r400000 )
                   ;
            lat := ( ilat -  900000 ) / 10000;
            lon := ( ilon - 1800000 ) / 10000

         END # from three wordfs # ;

    BEGIN

        PROC show coordinates = ( REAL lat, lon )VOID:
             BEGIN
                print( ( "  latitude = ",  fixed( lat, -8, 4 ) ) );
                print( ( ", longitude = ", fixed( lon, -8, 4 ), newline ) )
             END # show coordinates # ;

        REAL lat = 28.3852, lon = -81.5638;

        print( ( "Starting figures:", newline ) );
        show coordinates( lat, lon );
        print( ( newline ) );

        # latitude and longitude to words #

        STRING w1s := "", w2s := "", w3s := "";

        to three words( lat, lon, w1s, w2s, w3s );

        print( ( "Three word location is:", newline ) );
        print( ( "  ", w1s, " ", w2s, " ", w3s, newline ) );
        print( ( newline ) );

        # words to latitude and longitude #

        REAL rlat := -1, rlon := -1;
        from three words( w1s, w2s, w3s, rlat, rlon );

        print( ( "After reversing the procedure:", newline ) );
        show coordinates( rlat, rlon );
        IF rlat /= lat OR rlon /= lon THEN print( ( "**** not as expected", newline ) ) FI
    END
END
Output:
Starting figures:
  latitude =  28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude =  28.3852, longitude = -81.5638

Index words

When the words are index-based as in the task description, it's not necessary to generate all 28126.

on locationToWords({latitude, longitude})
    -- "Convert" the coordinates to positive integers by adding enough degrees to ensure positive results,
    -- multiplying by enough to left shift by four decimal places, and rounding.
    set intLat to ((latitude + 90) * 10000) as integer
    set intLong to ((longitude + 180) * 10000) as integer
    -- Derive a 15-bit and two 14-bit values from the two results' 43 bits.
    set output to {intLat div 64, intLat mod 64 * 256 + intLong div 16384, intLong mod 16384}
    -- Coerce the three values to text "words" beginning with "W" and any necessary leading zeros.
    repeat with thisIndex in output
        set thisIndex's contents to "W" & text 2 thru 6 of ((100000 + thisIndex) as text)
    end repeat
    
    return output
end locationToWords

on wordsToLocation(threeWords)
    set indices to {}
    repeat with thisWord in threeWords
        set end of indices to (text 2 thru -1 of thisWord) as integer
    end repeat
    set intLat to (beginning of indices) * 64 + (item 2 of indices) div 256 mod 64
    set intLong to (item 2 of indices) mod 256 * 16384 + (end of indices)
    
    return {intLat / 10000 - 90, intLong / 10000 - 180}
end wordsToLocation

-- Task code:
local location, threeWords, checkLocation
set location to {28.3852, -81.5638}
set threeWords to locationToWords(location)
set checkLocation to wordsToLocation(threeWords)
return {location, threeWords, checkLocation}
Output:
{{28.3852, -81.5638}, {"W18497", "W11324", "W01322"}, {28.3852, -81.5638}}

Actual words

on locationToWords({latitude, longitude}, listOfWords)
    script o
        property wordList : listOfWords
    end script
    
    set intLat to ((latitude + 90) * 10000) as integer
    set intLong to ((longitude + 180) * 10000) as integer
    set output to {intLat div 64, intLat mod 64 * 256 + intLong div 16384, intLong mod 16384}
    repeat with thisIndex in output
        set thisIndex's contents to item (thisIndex + 1) of o's wordList -- AppleScript indices are 1-based.
    end repeat
    
    return output
end locationToWords

on wordsToLocation(threeWords, listOfWords)
    script o
        property wordList : listOfWords
    end script
    
    set indices to {}
    repeat with thisWord in threeWords
        set thisWord to thisWord's contents
        set i to 1
        repeat until (item i of o's wordList is thisWord)
            set i to i + 1
            if (i > 28126) then error "wordsToLocation() handler: The word “" & thisWord & "” isn't in the word list."
        end repeat
        set end of indices to i - 1 -- Converted to 0-based index.
    end repeat
    set intLat to (beginning of indices) * 64 + (item 2 of indices) div 256 mod 64
    set intLong to (item 2 of indices) mod 256 * 16384 + (end of indices)
    
    return {intLat / 10000 - 90, intLong / 10000 - 180}
end wordsToLocation

-- Task code:
local o, location, threeWords, checkLocation
-- Use the words in unixdict.txt. It only has 25110 of them by AppleScript's count,
-- so make up the shortfall with invented plurals and third-persons-singular.
script
    property wordList : words of (read file ((path to desktop as text) & "unixdict.txt") as «class utf8»)
    property additionalWords : {}
end script
set o to result
repeat with i from 1 to (28126 - (count o's wordList))
    tell item i of o's wordList
        if (it ends with "s") then
            set end of o's additionalWords to it & "es"
        else
            set end of o's additionalWords to it & "s"
        end if
    end tell
end repeat
set o's wordList to o's wordList & o's additionalWords

set location to {28.3852, -81.5638}
set threeWords to locationToWords(location, o's wordList)
set checkLocation to wordsToLocation(threeWords, o's wordList)
return {location, threeWords, checkLocation}
Output:
{{28.3852, -81.5638}, {"quote", "hygiene", "aristotelean"}, {28.3852, -81.5638}}

Conversion based on Wren
WordList From link suggested by Symsyn

URLDownloadToFile, http://www-personal.umich.edu/~jlawler/wordlist, % A_Temp "\wordlist.txt"
FileRead, wordList, % A_Temp "\wordlist.txt"

LL := [28.3852, -81.5638]
num := LL2num(LL)
words := LL2words(wordList, LL)
LL2 := words2LL(wordList, words)

MsgBox, 262144, , % result := "
(
LL = " LL.1 ", " LL.2 "
LL2num = " num.1 ", " num.2 ", " num.3 "
LL2words = " words.1 ", " words.2 ", " words.3 "
words2LL = " LL2.1 ", " LL2.2 "
)"
return
;-----------------------------------------------
LL2words(wordList, LL){    ; Latitude/Longitude to 3 words
    num := LL2num(LL)
    wli := wordList(wordList).1
    return [wli[num.1], wli[num.2], wli[num.3]]
}
;-----------------------------------------------
words2LL(wordList, w){    ; 3 words to Latitude/Longitude
    iow := wordList(wordList).2
    LL := num2LL([iow[w.1], iow[w.2], iow[w.3]])
    return [ll.1, ll.2]
}
;-----------------------------------------------
wordList(wordList){        ; word list to two arrays
    wli:=[], iow:=[]    ; word list index, index of word
    for i, word in StrSplit(wordList, "`n", "`r")
        if (word ~= "^[a-z]+$") && (StrLen(word) <= 8) && (StrLen(word) > 3)
            wli.Push(word), iow[word] := wli.MaxIndex()
    return [wli, iow]
}
;-----------------------------------------------
LL2num(LL){                ; Latitude/Longitude to 3 numbers
    ilat := LL.1*10000 + 900000
    ilon := LL.2*10000 + 1800000
    latlon := (ilat << 22) + ilon
    return [(latlon >> 28) & 0x7fff, (latlon >> 14) & 0x3fff, latlon & 0x3fff]
}
;-----------------------------------------------
num2LL(w){                ; 3 numbers to Latitude/Longitude
    latlon := (w.1 << 28) | (w.2 << 14) | w.3
    ilat := latlon >> 22
    ilon := latlon & 0x3fffff
    return [(ilat-900000)/10000, (ilon-1800000)/10000]
}
;-----------------------------------------------
Output:
LL = 28.3852, -81.5638
LL2num = 18497, 11324, 1322
LL2words = malleus, fasten, analytic
words2LL = 28.385200, -81.563800

C

Translation of: Go
#include <stdio.h>
#include <stdlib.h>

typedef long long int64;
 
void to_word(char *ws, int64 w) {
    sprintf(ws, "W%05lld", w);
}

int64 from_word(char *ws) {
    return atoi(++ws);
}

int main() {
    double lat, lon;
    int64 latlon, ilat, ilon, w1, w2, w3;
    char w1s[7], w2s[7], w3s[7];
    printf("Starting figures:\n");
    lat = 28.3852;
    lon = -81.5638;
    printf("  latitude = %0.4f, longitude = %0.4f\n", lat, lon);
 
    // convert lat and lon to positive integers
    ilat = (int64)(lat*10000 + 900000);
    ilon = (int64)(lon*10000 + 1800000);
 
    // build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
    latlon = (ilat << 22) + ilon;

    // isolate relevant bits
    w1 = (latlon >> 28) & 0x7fff;
    w2 = (latlon >> 14) & 0x3fff;
    w3 = latlon & 0x3fff;

    // convert to word format
    to_word(w1s, w1);
    to_word(w2s, w2);
    to_word(w3s, w3);
 
    // and print the results
    printf("\nThree word location is:\n");
    printf("  %s %s %s\n", w1s, w2s, w3s);

    /* now reverse the procedure */
    w1 = from_word(w1s);
    w2 = from_word(w2s);
    w3 = from_word(w3s);

    latlon = (w1 << 28) | (w2 << 14) | w3;
    ilat = latlon >> 22;
    ilon = latlon & 0x3fffff;
    lat = (double)(ilat-900000) / 10000;
    lon = (double)(ilon-1800000) / 10000;

    // and print the results
    printf("\nAfter reversing the procedure:\n");
    printf("  latitude = %0.4f, longitude = %0.4f\n", lat, lon);
    return 0;
}
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638

Test coordinates copied from Perl solution.

module TWL
  abstract class WordList
    abstract def from_number (n)
    abstract def from_word (w)

    def [] (w_or_n)
      if w_or_n.is_a?(Int)
        from_number(w_or_n)
      elsif w_or_n.is_a?(String)
        from_word(w_or_n)
      else
        raise "invalid argument"
      end
    end

    Standard = StandardWordList.new
  end

  class StandardWordList < WordList
    def from_number (n)
      sprintf "W%05d", n
    end
    def from_word (w)
      w =~ /^W(\d{5})$/ && $1.to_i || raise "invalid word"
    end
  end

  class FileWordList < WordList
    @words : Array(String)
    def initialize (fn, min_length = 6, max_length = 6)
      @words = File.open(fn) do |f|
        f.each_line.select {|w| min_length <= w.size <= max_length }
          .first(28126).to_a.sort!
      end
    end
    def from_number (n)
      @words[n]
    end
    def from_word (w)
      idx = @words.bsearch_index {|word, _| word >= w }
      return idx if idx && @words[idx] == w
      raise "invalid word #{w}"
    end
  end

  def self.from_latitude (lat, lon, wlist = WordList::Standard)
    ilat = (lat * 10000).to_i64 + 900000
    ilon = (lon * 10000).to_i64 + 1800000
    n = (ilat << 22) + ilon
    i1 = n >> 28
    i2 = (n >> 14) & 0x3fff
    i3 = n & 0x3fff

    { wlist[i1], wlist[i2], wlist[i3] }
  end

  def self.from_words (word1, word2, word3, wlist = WordList::Standard)
    i1 = wlist[word1].to_i64
    i2 = wlist[word2].to_i64
    i3 = wlist[word3].to_i64
    n = (i1 << 28) + (i2 << 14) + i3
    ilat = n >> 22
    ilon = n & 0x3fffff
    lat = (ilat - 900000) / 10000
    lon = (ilon - 1800000) / 10000
    { lat, lon}
  end
end

word_list = TWL::FileWordList.new "words_alpha.txt"

[{ 28.3852,    -81.5638,   "Task location" },
 { 51.4337,     -0.2141,   "Wimbledon"},
 { 21.2596,   -157.8117,   "Diamond Head crater"},
 {-55.9652,    -67.2256,   "Monumento Cabo De Hornos"},
 { 71.170924,   25.782998, "Nordkapp, Norway"},
 { 45.762983,    4.834520, "Café Perl, Lyon"},
 { 48.391541, -124.736731, "Cape Flattery Lighthouse, Tatoosh Island"}
].each do |lat, lon, desc|
  puts "Coordinates: #{lat}, #{lon}  (#{desc})"
  words1 = TWL.from_latitude(lat, lon)
  words2 = TWL.from_latitude(lat, lon, word_list)
  print "  To 3-word: ", words1.join(" "), " ~ ", words2.join(" "), "\n"
  latlon1 = TWL.from_words *words1
  latlon2 = TWL.from_words *words2, word_list
  print "From 3-words (1): ", latlon1.join(","), "\n"
  print "From 3-words (2): ", latlon2.join(","), "\n"
  puts
end
Output:
Coordinates: 28.3852, -81.5638  (Task location)
  To 3-word: W18497 W11324 W01322 ~ ovally goshen apinch
From 3-words (1): 28.3852,-81.5638
From 3-words (2): 28.3852,-81.5638

Coordinates: 51.4337, -0.2141  (Wimbledon)
  To 3-word: W22099 W00365 W12003 ~ rubens adores harris
From 3-words (1): 51.4337,-0.2141
From 3-words (2): 51.4337,-0.2141

Coordinates: 21.2596, -157.8117  (Diamond Head crater)
  To 3-word: W17384 W05133 W08891 ~ natrix chende enzyms
From 3-words (1): 21.2596,-157.8117
From 3-words (2): 21.2596,-157.8117

Coordinates: -55.9652, -67.2256  (Monumento Cabo De Hornos)
  To 3-word: W05317 W15428 W13632 ~ chowse looeys jimjam
From 3-words (1): -55.9652,-67.2256
From 3-words (2): -55.9652,-67.2256

Coordinates: 71.170924, 25.782998  (Nordkapp, Norway)
  To 3-word: W25182 W15741 W09829 ~ sundew madhab flaked
From 3-words (1): 71.1709,25.7829
From 3-words (2): 71.1709,25.7829

Coordinates: 45.762983, 4.83452  (Café Perl, Lyon)
  To 3-word: W21212 W15728 W13337 ~ reflee macupi isidae
From 3-words (1): 45.7629,4.8345
From 3-words (2): 45.7629,4.8345

Coordinates: 48.391541, -124.736731  (Cape Flattery Lighthouse, Tatoosh Island)
  To 3-word: W21623 W11041 W11961 ~ retuck givens happer
From 3-words (1): 48.3915,-124.7367
From 3-words (2): 48.3915,-124.7367
Translation of: Go
program Three_word_location;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TThreeWordLocation = array of string;

  TGlobalPosition = record
  private
    FLatitude: Double;
    FLongitude: Double;
    FWs: TThreeWordLocation;
    class function toWord(w: int64): string; static;
    class function fromWord(ws: string): int64; static;
    procedure SetLatitude(const Value: Double);
    procedure SetLongitude(const Value: Double);
    procedure Recalculate;
    function GetTWLocationAsStr: string;
  public
    constructor Create(_lat, _lon: Double); overload;
    constructor Create(Ws: TThreeWordLocation); overload;
    procedure Assign(Ws: TThreeWordLocation);
    property Latitude: Double read FLatitude write SetLatitude;
    property Longitude: Double read FLongitude write SetLongitude;
    property TWLocation: TThreeWordLocation read FWs;
    property TWLocationAsStr: string read GetTWLocationAsStr;
  end;

{ TGlobalPosition }

constructor TGlobalPosition.Create(_lat, _lon: Double);
begin
  FLatitude := _lat;
  FLongitude := _lon;
  Recalculate;
end;

constructor TGlobalPosition.Create(ws: TThreeWordLocation);
begin
  Assign(ws);
end;

class function TGlobalPosition.fromWord(ws: string): int64;
begin
  Result := StrToInt(ws.Substring(1));
end;

function TGlobalPosition.GetTWLocationAsStr: string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to 2 do
    Result := Result + ' ' + FWs[i];
  Result := Result.Trim;
end;

procedure TGlobalPosition.Recalculate;
var
  i: Integer;
  w: array[0..2] of int64;
  ilat, ilon, latlon: Int64;
begin
  SetLength(FWs, 3);

  // convert lat and lon to positive integers
  ilat := Round(FLatitude * 10000 + 900000);
  ilon := Round(FLongitude * 10000 + 1800000);

   // build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
  latlon := (ilat shl 22) + ilon;

  // isolate relevant bits
  w[0] := (latlon shr 28) and $7fff;
  w[1] := (latlon shr 14) and $3fff;
  w[2] := latlon and $3fff;

  // convert to word format
  for i := 0 to 2 do
    FWs[i] := toWord(w[i]);
end;

procedure TGlobalPosition.SetLatitude(const Value: Double);
begin
  FLatitude := Value;
  Recalculate;
end;

procedure TGlobalPosition.SetLongitude(const Value: Double);
begin
  FLongitude := Value;
  Recalculate;
end;

class function TGlobalPosition.toWord(w: int64): string;
begin
  result := format('W%.5d', [w]);
end;

procedure TGlobalPosition.Assign(Ws: TThreeWordLocation);
var
  i: Integer;
  w: array[0..2] of int64;
  ilat, ilon, latlon: Int64;
begin
  SetLength(FWs, 3);
  for i := 0 to 2 do
  begin
    FWs[i] := Ws[i];
    w[i] := fromWord(Ws[i]);
  end;

  latlon := (w[0] shl 28) or (w[1] shl 14) or w[2];
  ilat := latlon shr 22;
  ilon := latlon and $3fffff;
  FLatitude := (ilat - 900000) / 10000;
  FLongitude := (ilon - 1800000) / 10000;
end;

var
  pos: TGlobalPosition;

begin
  pos.Create(28.3852, -81.5638);

  Writeln('Starting figures:');
  Writeln(Format('  latitude = %0.4f, longitude = %0.4f', [pos.Latitude, pos.Longitude]));

  Writeln(#10'Three word location is:');
  Writeln('  ', pos.TWLocationAsStr);

  Writeln(#10'After reversing the procedure:');

  // pos.Create(['W18497','W11324','W01322']);
  pos.Create(pos.TWLocation);
  Writeln(Format('  latitude = %0.4f, longitude = %0.4f', [pos.Latitude, pos.Longitude]));

  Readln;
end.
Output:
Starting figures:
  latitude = 28,3852, longitude = -81,5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28,3852, longitude = -81,5638
Translation of: Nim
func$ to_word w .
   w$ = w
   return "W" & substr "00000" 1 (5 - len w$) & w$
.
func from_word w$ .
   return number substr w$ 2 5
.
numfmt 0 4
lat = 28.3852
long = -81.5638
print "Latitude: " & lat
print "Longitude: " & long
ilat = floor (lat * 10000 + 900000)
ilong = floor (long * 10000 + 1800000)
#
latlong = bitshift ilat 22 + ilong
w1 = bitand bitshift latlong -28 0x7fff
w2 = bitand bitshift latlong -14 0x3fff
w3 = bitand latlong 0x3fff
w1$ = to_word w1
w2$ = to_word w2
w3$ = to_word w3
#
print ""
print "Three words: " & w1$ & " " & w2$ & " " & w3$
print ""
#
w1 = from_word w1$
w2 = from_word w2$
w3 = from_word w3$
latlong = bitshift w1 28 + bitshift w2 14 + w3
ilat = bitshift latlong -22
ilong = bitand latlong 0x3fffff
lat = (ilat - 900000) / 10000
long = (ilong - 1800000) / 10000
#
print "Latitude: " & lat
print "Longitude: " & long
Output:
Latitude: 28.3852
Longitude: -81.5638

Three words: W18497 W11324 W01322

Latitude: 28.3852
Longitude: -81.5638
Translation of: Nim
Print "Starting figures:"
Dim As Double lat = 28.3852, longi = -81.5638
Print Using "  latitude = &, longitude = &"; lat; longi

' Convert "lat" and "long" to positive integers.
Dim As Integer ilat = Cint(lat * 10000 + 900000)
Dim As Integer ilong = Cint(longi * 10000 + 1800000)

' Build 43 bit int comprising 21 bits (lat) and 22 bits (lon).
Dim As Double latlong = ilat Shl 22 + ilong

' Isolate relevant bits.
Dim As Integer w1 = latlong Shr 28 And &H7fff
Dim As Integer w2 = latlong Shr 14 And &H3fff
Dim As Integer w3 = latlong And &H3fff

' Convert to word format.
Dim As String*5 w1s = String(5, "0"), w2s = String(5, "0"), w3s = String(5, "0")
Mid(w1s, 6-Len(Str(w1))) = Str(w1)
Mid(w2s, 6-Len(Str(w2))) = Str(w2)
Mid(w3s, 6-Len(Str(w3))) = Str(w3)

' Print the results.
Print !"\nThree word location is:"
Print Using "  W\   \ W\   \ W\   \"; w1s; w2s; w3s
 
latlong = w1 Shl 28 Or w2 Shl 14 Or w3
ilat = latlong Shr 22
ilong = latlong And &H3fffff
lat = (ilat - 900000) / 10000
longi = (ilong - 1800000) / 10000
 
' Print the results.
Print !"\nAfter reversing the procedure:"
Print Using "  latitude = &, longitude = &"; lat; longi
Sleep
Output:
Igual que la entrada de Nim
Translation of: Wren

Though no need for big integers as we have int64 built in.

package main

import (
    "fmt"
    "strconv"
)

func toWord(w int64) string { return fmt.Sprintf("W%05d", w) }

func fromWord(ws string) int64 {
    var u, _ = strconv.ParseUint(ws[1:], 10, 64)
    return int64(u)
}

func main() {
    fmt.Println("Starting figures:")
    lat := 28.3852
    lon := -81.5638
    fmt.Printf("  latitude = %0.4f, longitude = %0.4f\n", lat, lon)

    // convert lat and lon to positive integers
    ilat := int64(lat*10000 + 900000)
    ilon := int64(lon*10000 + 1800000)

    // build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
    latlon := (ilat << 22) + ilon

    // isolate relevant bits
    w1 := (latlon >> 28) & 0x7fff
    w2 := (latlon >> 14) & 0x3fff
    w3 := latlon & 0x3fff

    // convert to word format
    w1s := toWord(w1)
    w2s := toWord(w2)
    w3s := toWord(w3)

    // and print the results
    fmt.Println("\nThree word location is:")
    fmt.Printf("  %s %s %s\n", w1s, w2s, w3s)

    /* now reverse the procedure */
    w1 = fromWord(w1s)
    w2 = fromWord(w2s)
    w3 = fromWord(w3s)

    latlon = (w1 << 28) | (w2 << 14) | w3
    ilat = latlon >> 22
    ilon = latlon & 0x3fffff
    lat = float64(ilat-900000) / 10000
    lon = float64(ilon-1800000) / 10000

    // and print the results
    fmt.Println("\nAfter reversing the procedure:")
    fmt.Printf("  latitude = %0.4f, longitude = %0.4f\n", lat, lon)
}
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638

J

To take full advantage of the bit space, I think we should use 11650.8444 for the multiplier (2^21 - log2 180), but that's not what was asked for here.

wloc=: {{ ;:inv wordlist{~ (15 14 14#i.3)#./.;(21 22#&.>2) #:&.> <.0.5+10000*90 180+y }}
colw=: {{ _90 _180+1e_4*21({.,&#.}.);(15 14 14#&.>2)#:&.>wordlist i.;:y }}

With wordlist=: ('W',}.@":)&.> 1e5+i.3e4

   wloc 28.3852 _81.5638
W18497 W11324 W01322
   colw wloc 28.3852 _81.5638
28.3852 _81.5638

With wordlist=: cutLF CR-.~fread 'wordlist' based on the file 'wordlist' from http://www-personal.umich.edu/~jlawler/wordlist

   wloc 28.3852 _81.5638
diplotene chamomile aeroplanist
   colw wloc 28.3852 _81.5638
28.3852 _81.5638


Works with: Java version LTS version 17
Translation of: Go
public class Main {
    public static String toWord(long w) {
        return String.format("W%05d", w);
    }

    public static long fromWord(String ws) {
        return Long.parseLong(ws.substring(1));
    }

    public static void main(String[] args) {
        System.out.println("Starting figures:");
        double lat = 28.3852;
        double lon = -81.5638;
        System.out.printf("  latitude = %.4f, longitude = %.4f%n", lat, lon);

        // Convert lat and lon to positive integers
        long ilat = (long)(lat * 10000 + 900000);
        long ilon = (long)(lon * 10000 + 1800000);

        // Build 43-bit long comprising 21 bits (lat) and 22 bits (lon)
        long latlon = (ilat << 22) + ilon;

        // Isolate relevant bits
        long w1 = (latlon >> 28) & 0x7fff;
        long w2 = (latlon >> 14) & 0x3fff;
        long w3 = latlon & 0x3fff;

        // Convert to word format
        String w1s = toWord(w1);
        String w2s = toWord(w2);
        String w3s = toWord(w3);

        // Print the results
        System.out.println("\nThree word location is:");
        System.out.printf("  %s %s %s%n", w1s, w2s, w3s);

        // Reverse the procedure
        w1 = fromWord(w1s);
        w2 = fromWord(w2s);
        w3 = fromWord(w3s);

        latlon = (w1 << 28) | (w2 << 14) | w3;
        ilat = latlon >> 22;
        ilon = latlon & 0x3fffff;

        lat = (ilat - 900000) / 10000.0;
        lon = (ilon - 1800000) / 10000.0;

        // Print the results
        System.out.println("\nAfter reversing the procedure:");
        System.out.printf("  latitude = %.4f, longitude = %.4f%n", lat, lon);
    }
}
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638


Works with: NodeJS 16.14.2
Translation of: Java
function toWord(w) {
    return `W${w.toString().padStart(5, '0')}`;
}

function fromWord(ws) {
    return parseInt(ws.substring(1), 10);
}

function main() {
    console.log("Starting figures:");
    let lat = 28.3852;
    let lon = -81.5638;
    console.log(`  latitude = ${lat.toFixed(4)}, longitude = ${lon.toFixed(4)}`);

    // Convert lat and lon to positive integers
    let ilat = Math.floor(lat * 10000 + 900000);
    let ilon = Math.floor(lon * 10000 + 1800000);

    // Build 43-bit integer comprising 21 bits (lat) and 22 bits (lon)
    // JavaScript uses 64-bit floats for all numbers, but bitwise operations are 32-bit.
    // To handle 43 bits, we use BigInt.
    let latlon = (BigInt(ilat) << 22n) + BigInt(ilon);

    // Isolate relevant bits
    let w1 = Number((latlon >> 28n) & 0x7fffn);
    let w2 = Number((latlon >> 14n) & 0x3fffn);
    let w3 = Number(latlon & 0x3fffn);

    // Convert to word format
    let w1s = toWord(w1);
    let w2s = toWord(w2);
    let w3s = toWord(w3);

    // Print the results
    console.log("\nThree word location is:");
    console.log(`  ${w1s} ${w2s} ${w3s}`);

    // Reverse the procedure
    w1 = fromWord(w1s);
    w2 = fromWord(w2s);
    w3 = fromWord(w3s);

    latlon = (BigInt(w1) << 28n) | (BigInt(w2) << 14n) | BigInt(w3);
    ilat = Number(latlon >> 22n);
    ilon = Number(latlon & 0x3fffffn);

    lat = (ilat - 900000) / 10000;
    lon = (ilon - 1800000) / 10000;

    // Print the results
    console.log("\nAfter reversing the procedure:");
    console.log(`  latitude = ${lat.toFixed(4)}, longitude = ${lon.toFixed(4)}`);
}

main();
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638


Adapted from Wren

Works with jq, the C implementation of jq

Works with gojq, the Go implementation of jq

## Generic functions

# If $j is 0, then an error condition is raised;
# otherwise, assuming infinite-precision integer arithmetic,
# if the input and $j are integers, then the result will be an integer.
def idivide($j):
  . as $i
  | ($i % $j) as $mod
  | ($i - $mod) / $j ;

# From bitwise.jq
# integer to stream of 0s and 1s, least significant bit first
def bitwise:
  recurse( if . >= 2 then idivide(2) else empty end) | . % 2;

# inverse of `bitwise`
def stream_to_integer(stream):
  reduce stream as $c ( {power:1 , ans: 0};
      .ans += ($c * .power) | .power *= 2 )
    | .ans;

# Convert the $j least-significant bits of the input integer to an integer
def to_int($j):
  stream_to_integer(limit($j; bitwise));

# Take advantage of gojq's support for infinite-precision integer arithmetic:
def power($b): . as $in | reduce range(0;$b) as $i (1; . * $in);

# Input is assumed to be a non-negative integer
def rightshift($n):
  reduce range(0;$n) as $i (.; idivide(2)) ;

def lpad($len; $fill):
  tostring
  | ($len - length) as $l
  | if $l <= 0 then .
    else ($fill * $l)[:$l] + .
    end;

## Functions to convert to and from the 'W00000' format 
def toWord: "W\(lpad(5; "0"))";

def fromWord: .[1:] | tonumber;

# Latitude should be presented as a number in [-90, 90]
# and longitude as a number in [-180, 180].
def task($lat; $lon):
  # convert lat and lon to positive integers
  (($lat * 10000 | trunc) + 900000 ) as $ilat
  | (($lon * 10000 | trunc) + 1800000) as $ilon
   
  # build 43 bit integer comprising 21 bits (lat) and 22 bits (lon)
  | ($ilat * (2 | power(22)) + $ilon) as $latlon
  # isolate relevant bits
  | ($latlon | rightshift(28) | to_int(15) | toWord) as $w1
  | ($latlon | rightshift(14) | to_int(14) | toWord) as $w2
  | ($latlon | to_int(14) | toWord) as $w3
  | "Starting figures:",
    "  latitude = \($lat), longitude = \($lon)",
    "\nThree word location is:",
    ([$w1, $w2, $w3] | join(" ")),

     # now reverse the procedure
     ({}
      | .latlon = (  ($w1 | fromWord) * (2 | power(28))
                   + ($w2 | fromWord) * (2 | power(14))
                   + ($w3 | fromWord ) )
      | .ilat = (.latlon | rightshift(22))
      | .ilon = (.latlon | to_int(22))
      | .lat = ((.ilat - 900000) / 10000)
      | .lon = ((.ilon - 1800000) / 10000)
      | "\nAfter reversing the procedure:",
        "  latitude = \(.lat), longitude = \(.lon)" ) 
;

task(28.3852; -81.5638)
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638

Direct translation from the SymSyn example given by the task creator, though note that idiomatic Julia would usually code this as two small encode() and decode() functions.

# Three Word Location - convert latitude and longitude to three words
LAT = 28.3852
LON = -81.5638

# build word array W00000 ... W28125
wordarray = ["W" * string(x, pad=5) for x in 0:28125]

# make latitude and longitude positive integers
ILAT = Int(LAT * 10000 + 900000)
ILON = Int(LON * 10000 + 1800000)

# build 43 bit integer containing latitude (21 bits) and longitude (22 bits)
LATLON = (ILAT << 22) + ILON

# isolate most significant 15 bits for word 1 index
# next 14 bits for word 2 index
# next 14 bits for word 3 index
W1 = (LATLON >> 28) & 0x7fff
W2 = (LATLON >> 14) & 0x3fff
W3 = LATLON & 0x3fff

# fetch each word from word array
w1 = wordarray[W1 + 1]
w2 = wordarray[W2 + 1]
w3 = wordarray[W3 + 1]

# display words
println("$w1 $w2 $w3")


# reverse the procedure

# look up each word
(w1index, w2index, w3index) = indexin([w1, w2, w3], wordarray) .- 1

# build the latlon integer from the word indexes
latlon = (w1index << 28) | (w2index << 14) | w3index


# isolate the latitude and longitude
ilon =  latlon & 0xfffff
ilat = latlon >> 22

# convert back to floating point values
lon = (ilon - 1800000) / 10000
lat = (ilat - 900000) / 10000

# display values
println("latitude = $lat longitude = $lon")
Output:
W18497 W11324 W01322
latitude = 28.3852 longitude = -81.5638

Idiomatic version with scrambling

using Random

const LAT = 28.3852
const LON = -81.5638

# build word array W00000 ... W28125
const wordarray = ["W" * string(x, pad=5) for x in 0:28125]

function threewordencode(lat, lon, seed=0) # returns vector of 3 strings
    arr = wordarray
    if seed != 0
        rng = MersenneTwister(seed)
        arr = shuffle(rng, deepcopy(wordarray))
    end
    i = (Int(lat * 10000 + 900000) << 22) | Int(lon * 10000 + 1800000)
    return map(x -> arr[x + 1], [(i >> 28) & 0x7fff, (i >> 14) & 0x3fff, i & 0x3fff])
end

function threeworddecode(w1, w2, w3, seed=0) # returns pair of Float64
    arr = wordarray
    if seed != 0
        rng = MersenneTwister(seed)
        arr = shuffle(rng, deepcopy(wordarray))
    end
    (i1, i2, i3) = indexin([w1, w2, w3], arr) .- 1
    latlon = (i1 << 28) | (i2 << 14) | i3
    ilon, ilat = latlon & 0xfffff, latlon >> 22
    return  (ilon - 1800000) / 10000, (ilat - 900000) / 10000
end

words = threewordencode(LAT, LON)
println(join(words, " "))

lat, lon = threeworddecode(words...)
println("latitude = $lat longitude = $lon")

println("\nWith scramble using key 12345678:")
words = threewordencode(LAT, LON, 12345678)
println(join(words, " "))
lat, lon = threeworddecode(words..., 12345678)
println("latitude = $lat longitude = $lon")
Output:
W18497 W11324 W01322
latitude = -81.5638 longitude = 28.3852

With scramble using key 12345678:
W20242 W23427 W16215
latitude = -81.5638 longitude = 28.3852
Translation of: Go
fun toWord(w: Long): String {
    return "W%05d".format(w)
}

fun fromWord(ws: String): Long {
    return ws.substring(1).toUInt().toLong()
}

fun main() {
    println("Starting figures:")
    var lat = 28.3852
    var lon = -81.5638
    println("  latitude = %.4f, longitude = %.4f".format(lat, lon))
    println()

    // convert lat and lon to positive integers
    var ilat = (lat * 10000 + 900000).toLong()
    var ilon = (lon * 10000 + 1800000).toLong()

    // build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
    var latlon = (ilat shl 22) + ilon

    // isolate relevant bits
    var w1 = (latlon shr 28) and 0x7fff
    var w2 = (latlon shr 14) and 0x3fff
    var w3 = latlon and 0x3fff

    // convert to word format
    val w1s = toWord(w1)
    val w2s = toWord(w2)
    val w3s = toWord(w3)

    // and print the results
    println("Three word location is:")
    println("  $w1s $w2s $w3s")
    println()

    /* now reverse the procedure */
    w1 = fromWord(w1s)
    w2 = fromWord(w2s)
    w3 = fromWord(w3s)

    latlon = (w1 shl 28) or (w2 shl 14) or w3
    ilat = latlon shr 22
    ilon = latlon and 0x3fffff
    lat = (ilat - 900000).toDouble() / 10000
    lon = (ilon - 1800000).toDouble() / 10000

    // and print the results
    println("After reversing the procedure:")
    println("  latitude = %.4f, longitude = %.4f".format(lat, lon))
}
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Translation of: C
function toWord(w)
    return string.format("W%05d", w)
end

function fromWord(ws)
    return tonumber(string.sub(ws, 2, -1))
end

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

print("Starting figures:")
lat = 28.3852
lon = -81.5638
print(string.format("  latitude = %0.4f, longitude = %0.4f", lat, lon))
print()

-- convert from lat and lon to positive integers
ilat = lat * 10000 +  900000
ilon = lon * 10000 + 1800000

-- build 43 bit number comprising 21 bits (lat) and 22 bits (lon)
latlon = math.floor((ilat << 22) + ilon)

-- isloate relevant bits
w1 = (latlon >> 28) & 0x7fff
w2 = (latlon >> 14) & 0x3fff
w3 =  latlon        & 0x3fff

-- convert to word format
w1s = toWord(w1)
w2s = toWord(w2)
w3s = toWord(w3)

-- and print the results
print("Three word location is:")
print("  " .. w1s .. " " .. w2s .. " " .. w3s)
print()

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

-- now reverse the procedure
w1 = fromWord(w1s)
w2 = fromWord(w2s)
w3 = fromWord(w3s)

latlon = (w1 << 28) | (w2 << 14) | w3
ilat = latlon >> 22
ilon = latlon & 0x3fffff
lat = (ilat -  900000) / 10000
lon = (ilon - 1800000) / 10000

-- and print the results
print("After reversing the procedure:")
print(string.format("  latitude = %0.4f, longitude = %0.4f", lat, lon))
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Module ThreeWordLocation {
	locale 1033 ' using . for decimal point
	toWord=lambda (a)->"W"+str$(a,"00000")
	
	' shift for 63 bit positive value (Binary.Shift() works for 32bit)
	bit.shift=lambda (a as long long, b)->if(b>=0->a * 2&&^b, a div 2&&^-b)
	
	' binary.and for low long from a long long value
	bit.and=lambda -> {
		structure HiLow {
			{ab as long long} 'union with 2 longs
			a as long, b as long
		} ' all values are unsigned so for long long we give >=0 of type decimal
		HiLow z ' make a z and put as closure to lambda
		=lambda z (z|ab, b)->binary.and(z|a, b)
	}() ' execute outer lambda
	lat = 28.3852
	long = -81.5638
	print "Starting figures:"
	print "  latitude = "; lat;", longitude = "; long
	ilat = floor(lat * 10000 + 900000)
	ilong = floor(long * 10000 + 1800000)
	latlong = bit.shift(ilat, 22) + ilong
	w1= bit.and(bit.shift(latlong, -28), 0x7fff)
	w2 = bit.and(bit.shift(latlong, -14), 0x3fff)
	w3 = bit.and(latlong, 0x3fff)
	print
	print "Three word location is:"
	print "  ";toWord(w1);" ";toWord(w2);" ";toWord(w3)
	latlong = bit.shift(w1, 28) + bit.shift(w2, 14) + w3
	ilat = bit.shift(latlong, -22)
	ilong = bit.and(latlong, 0x3fffff)
	lat = (ilat - 900000) / 10000
	long = (ilong - 1800000) / 10000
	print
	print "After reversing the procedure:"
	print "  latitude = "; lat;", longitude = "; long
}
ThreeWordLocation
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
toThreeWords[{lat_, lon_}] := Module[{latInteger, lonInteger, bits},
   latInteger = Round[lat*10000] + 900000;
   lonInteger = Round[lon*10000] + 1800000;
   bits = Join[IntegerDigits[latInteger, 2, 21], IntegerDigits[lonInteger, 2, 22]];
   Map["W" <> IntegerString[FromDigits[#, 2], 10, 5] &, TakeList[bits, {15, 14, 14}]]];

fromThreeWords[words_] := Module[{bits, lat, lon},
   bits = Join @@ MapThread[IntegerDigits[ToExpression[StringDrop[#, 1]], 2, #2] &,
          {words, {15, 14, 14}}];
   lat = (FromDigits[Take[bits, 21], 2] - 900000)/10000.;
   lon = (FromDigits[Take[bits, -22], 2] - 1800000)/10000.;
   {lat, lon}];

threeWords = toThreeWords[{28.3852, -81.5638}];
{lat, lon} = fromThreeWords[threeWords];

Print[StringRiffle[threeWords, " "]];
Print["latitude = ", lat, "  longitude = ", lon];
Output:
W18497 W11324 W01322
latitude = 28.3852  longitude = -81.5638
Translation of: Go
import strformat, strutils

func toWord(w: int64): string = &"W{w:05}"

func fromWord(ws: string): int64 = ws[1..5].parseInt()

echo "Starting figures:"
var
  lat = 28.3852
  long = -81.5638
echo &"  latitude = {lat:0.4f}, longitude = {long:0.4f}"

# Convert "lat" and "long" to positive integers.
var
  ilat = int64(lat * 10_000 + 900_000)
  ilong = int64(long * 10_000 + 1_800_000)

# Build 43 bit int comprising 21 bits (lat) and 22 bits (lon).
var latlong = ilat shl 22 + ilong

# Isolate relevant bits.
var
  w1 = latlong shr 28 and 0x7fff
  w2 = latlong shr 14 and 0x3fff
  w3 = latlong and 0x3fff

# Convert to word format.
let
  w1s = w1.toWord
  w2s = w2.toWord
  w3s = w3.toWord

# Print the results.
echo "\nThree word location is:"
echo &"  {w1s} {w2s} {w3s}"

# Reverse the procedure.
w1 = w1s.fromWord
w2 = w2s.fromWord
w3 = w3s.fromWord

latlong = w1 shl 28 or w2 shl 14 or w3
ilat = latlong shr 22
ilong = latlong and 0x3fffff
lat = float(ilat - 900_000) / 10_000
long = float(ilong - 1_800_000) / 10_000

# Print the results.
echo "\nAfter reversing the procedure:"
echo &"  latitude = {lat:0.4f}, longitude = {long:0.4f}"
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Translation of: Raku
use strict;
use warnings;
use feature 'say';
use bignum; # without this, round-trip results not exact

use Math::AnyNum 'polymod';

# SYNTHETICS HANDLING

my @synth;
push @synth, join '', @$_ for map { [split /:/] } glob '{b,d,f,h,j,k,l,m,n,p,r,s,t,w,y,z}:{a,e,i,o,u}';
my(%htnys,$c); $htnys{$_} = $c++ for @synth;
my $exp  = @synth;
my $prec = 10_000;

sub bin2dec { unpack('N', pack('B32', substr('0' x 32 . shift, -32))) }

sub synth { join '', reverse @synth[polymod(shift() + int(rand 18) * 28126, $exp, $exp) ] }

sub thnys {
    my @n = @htnys{ shift() =~ /(..)(..)(..)/ };  # NB notation on hash slice: % -> @
    ($n[2] + $n[1]*$exp + $n[0]*$exp**2) % 28126
}

# ENCODE / DECODE

sub w_encode {
    my($lat, $lon, $f) = @_;
    $f = \&synth unless $f;
    my @words;
    my $bits = sprintf '%021b%022b', int(($lat+90)*$prec), int(($lon+180)*$prec);
    push @words, &$f(bin2dec($_)) for $bits =~ / (.{15}) (.{14}) (.{14}) /x;
    @words
}

sub w_decode {
    my($w, $f) = @_;
    $f = \&thnys unless $f;
    my $s = '%015b';
    my $bin = sprintf($s, &$f($$w[0])) . substr(sprintf($s, &$f($$w[1])), 1) . substr(sprintf($s, &$f($$w[2])), 1);
    (bin2dec(substr($bin,0,21))/$prec - 90), (bin2dec(substr($bin,21))/$prec - 180)
}

# TESTING

for ([ 51.4337,     -0.2141,   'Wimbledon'],
     [ 21.2596,   -157.8117,   'Diamond Head crater'],
     [-55.9652,    -67.2256,   'Monumento Cabo De Hornos'],
     [ 71.170924,   25.782998, 'Nordkapp, Norway'],
     [ 45.762983,    4.834520, 'Café Perl, Lyon'],
     [ 48.391541, -124.736731, 'Cape Flattery Lighthouse, Tatoosh Island'],
    ) {
    my($lat, $lon, $description) = @$_;
    my @words = w_encode $lat, $lon;
    my @index = w_encode $lat, $lon, sub { shift };
    printf "Coordinates: %s, %s (%s)\n   To Index: %s\n  To 3-word: %s\nFrom 3-word: %s, %s\n From Index: %s, %s\n\n",
      $lat, $lon, $description, join(' ',@index), join(' ',@words), w_decode(\@words), w_decode(\@index, sub { shift() });
}
Output:
Coordinates: 51.4337, -0.2141 (Wimbledon)
   To Index: 22099 365 12003
  To 3-word: yotema ritomi rahiku
From 3-word: 51.4337, -0.2141
 From Index: 51.4337, -0.2141

Coordinates: 21.2596, -157.8117 (Diamond Head crater)
   To Index: 17384 5133 8891
  To 3-word: hayibi batufo jokube
From 3-word: 21.2596, -157.8117
 From Index: 21.2596, -157.8117

Coordinates: -55.9652, -67.2256 (Monumento Cabo De Hornos)
   To Index: 5317 15428 13632
  To 3-word: fubeha zidura nerupe
From 3-word: -55.9652, -67.2256
 From Index: -55.9652, -67.2256

Coordinates: 71.170924, 25.782998 (Nordkapp, Norway)
   To Index: 25182 15741 9829
  To 3-word: zorenu jaboda kiyika
From 3-word: 71.1709, 25.7829
 From Index: 71.1709, 25.7829

Coordinates: 45.762983, 4.83452 (Café Perl, Lyon)
   To Index: 21212 15728 13337
  To 3-word: ludefu bimepo demojo
From 3-word: 45.7629, 4.8345
 From Index: 45.7629, 4.8345

Coordinates: 48.391541, -124.736731 (Cape Flattery Lighthouse, Tatoosh Island)
   To Index: 21623 11041 11960
  To 3-word: kakofo radaki habuho
From 3-word: 48.3915, -124.7368
 From Index: 48.3915, -124.7368
Translation of: Go
-- demo\rosetta\Three_word_location.exw
with javascript_semantics
 
function toWord(integer w)
    return sprintf("W%05d", w)
end function
 
function fromWord(string ws)
    sequence r = scanf(ws,"W%05d")
    integer res = r[1][1]
    return res
end function
 
printf(1,"Starting figures:\n")
atom lat =  28.3852,
     lon = -81.5638
printf(1,"  latitude = %0.4f, longitude = %0.4f\n", {lat, lon})
 
-- convert lat and lon to positive integers
integer ilat := floor((lat+90)*10000),
        ilon := floor((lon+180)*10000)
 
-- build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
-- (std phix atoms have 53/64 bits of precision on 32/64 bit, both plenty)
atom latlon := (ilat << 22) + ilon
 
-- isolate relevant bits
integer w1 = (latlon >> 28) && 0x7fff,
        w2 = (latlon >> 14) && 0x3fff,
        w3 = latlon && 0x3fff
 
-- convert to word format
string w1s = toWord(w1),
       w2s = toWord(w2),
       w3s = toWord(w3)
 
-- and print the results
printf(1,"\nThree word location is:\n")
printf(1,"  %s %s %s\n", {w1s, w2s, w3s})
 
-- now reverse the procedure
w1 = fromWord(w1s)
w2 = fromWord(w2s)
w3 = fromWord(w3s)
 
-- NB: or_bits (likewise ||), being expressly 32-bit, is NOT appropriate here...
latlon = (w1 << 28) + (w2 << 14) + w3
ilat = latlon >> 22
ilon = latlon && 0x3fffff
lat = ilat/10000 - 90
lon = ilon/10000 - 180
 
-- and print the results
printf(1,"\nAfter reversing the procedure:\n")
printf(1,"  latitude = %0.4f, longitude = %0.4f\n", {lat, lon})
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Translation of: Wren
Library: Pluto-fmt

No need for bigint as 64 bit integer bitwise operations (which we have) suffice for this task.

local fmt  = require "fmt"

-- Functions to convert to and from the word format 'W00000'.
local to_word   = |w| -> string.format("W%05d", w)
local from_word = |w| -> tonumber(w:sub(2))

-- Set latitude and longitude and print them.
print("Starting figures:")
local lat = 28.3852
local lon = -81.5638
fmt.print("  latitude = %0.4f, longitude = %0.4f", lat, lon)

-- Convert lat and lon to positive integers.
local ilat = lat * 10000 + 900000
local ilon = lon * 10000 + 1800000

-- Build 43 bit integer comprising 21 bits (lat) and 22 bits (lon)
local latlon = (ilat << 22) + ilon

-- Isolate relevant bits.
local w1 = (latlon >> 28) & 0x7fff
local w2 = (latlon >> 14) & 0x3fff
local w3 = latlon & 0x3fff

-- Convert to word format.
w1 = to_word(w1)
w2 = to_word(w2)
w3 = to_word(w3)

-- Print the results.
print("\nThree word location is:")
fmt.print("  %s %s %s", w1, w2, w3)

-- Now reverse the procedure.
w1 = from_word(w1)
w2 = from_word(w2)
w3 = from_word(w3)
latlon = (w1 << 28) | (w2 << 14) | w3
ilat = latlon >> 22
ilon = latlon & 0x3fffff
lat = (ilat - 900000) / 10000
lon = (ilon - 1800000) / 10000

-- Print the results.
print("\nAfter reversing the procedure:")
fmt.print("  latitude = %0.4f, longitude = %0.4f", lat, lon)
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Translation of: Kotlin
def to_word(w: int):
    return "W%05d" % w

def from_word(ws: str):
    return int(ws[1:])

def main():
    print("Starting figures:")

    lat = 28.3852
    lon = -81.5638

    print(f" latitude = {lat:.4f}, longitude = {lon:.4f}")
    print()

    ilat = int(lat * 10000 + 900000)
    ilon = int(lon * 10000 + 1800000)

    latlon = (ilat << 22) + ilon

    w1 = (latlon >> 28) & 0x7fff
    w2 = (latlon >> 14) & 0x3fff
    w3 = latlon & 0x3fff

    w1s = to_word(w1)
    w2s = to_word(w2)
    w3s = to_word(w3)

    print("Three word location is: ")
    print(' ', w1s, w2s, w3s)
    print()

    w1 = from_word(w1s)
    w2 = from_word(w2s)
    w3 = from_word(w3s)

    latlon = (w1 << 28) | (w2 << 14) | w3
    ilat = latlon >> 22
    ilon = latlon & 0x3fffff

    lat = (ilat - 900000) / 10000
    lon = (ilon - 1800000) / 10000

    print("After reversing the procedure: ")
    print(f" latitude {lat:.4f}, longitude = {lon:.4f}")

if __name__ == '__main__':
    main()
Output:
Starting figures:
 latitude = 28.3852, longitude = -81.5638

Three word location is: 
  W18497 W11324 W01322

After reversing the procedure: 
 latitude 28.3852, longitude = -81.5638
Works with: Rakudo version 2020.07

The task

In large part due to the complete lack of specification, reference implementation, or guidance from the task creator, came up with my own bespoke synthetic word list.

Words always consist of a series of consonant/vowel pairs. Uses a cut down alphabet to reduce possible confusion from overlapping pronunciation.

Some letters with overlapping pronunciation are removed: c: confusable with k or s, g: overlaps with j, x: overlaps with z, q: just because, v: similar to w and we have way more than enough characters anyway.

As it is, with this alphabet we can form 512000 different 6 character "words"; 28126 is a drop in the bucket. To spread out the the words a bit, add a bit of randomness. 28126 fits into 512000 18 and a bit times. Add a random multiple of 28126 to the encoder then modulus it back out on decode. Will get different results on different runs.

We don't bother to pre-calculate and store the words, just generate them on the fly.

Official pronunciation guide:

  • a - long a (say may day)
  • e - long e (he me see)
  • i - long i (hi sigh die)
  • o - long o (go so low)
  • u - long u (due boo moo)
# SYNTHETICS HANDLING
my @synth = flat < b d f h j k l m n p r s t w y z > X~ < a e i o u >;
my %htnys = @synth.antipairs;
my $exp   = @synth.elems;

sub synth (Int $v) { @synth[($v + (^18).pick * 28126).polymod($exp xx *).reverse || 0].join }

sub thnys (Str $v) { (sum %htnys{$v.comb(2).reverse} Z* 1, $exp, $exp**2) % 28126 }


# ENCODE / DECODE
sub w-encode ( Rat(Real) $lat, Rat(Real) $lon, :&f = &synth ) {
    $_ = (($lat +  90) * 10000).round.fmt('%021b') ~ (($lon + 180) * 10000).round.fmt('%022b');
    (:2(.substr(0,15)), :2(.substr(15,14)),:2(.substr(29)))».&f
}

sub w-decode ( *@words, :&f = &thnys ) {
    my $bin = (@words».&f Z, <0 1 1>).map({.[0].fmt('%015b').substr(.[1])}).join;
    (:2($bin.substr(0,21))/10000) - 90, (:2($bin.substr(21))/10000) - 180
}


# TESTING
for 51.4337,  -0.2141, # Wimbledon
    21.2596,-157.8117, # Diamond Head crater
   -55.9652, -67.2256, # Monumento Cabo De Hornos
    59.3586,  24.7447, # Lake Raku
    29.2021,  81.5324, # Village Raku
    -7.1662,  53.9470, # The Indian ocean, south west of Seychelles
    28.3852, -81.5638  # Walt Disney World
  -> $lat, $lon {
    my @words = w-encode $lat, $lon;
    my @index = w-encode $lat, $lon, :f( { $_ } );
    printf "Coordinates: %s, %s\n   To Index: %s\n  To 3-word: %s\nFrom 3-word: %s, %s\n From Index: %s, %s\n\n",
      $lat, $lon, @index.Str, @words.Str, w-decode(@words), w-decode @index, :f( { $_ } );
}
Output:
Coordinates: 51.4337, -0.2141
   To Index: 22099 365 12003
  To 3-word: zofobe fohujo habute
From 3-word: 51.4337, -0.2141
 From Index: 51.4337, -0.2141

Coordinates: 21.2596, -157.8117
   To Index: 17384 5133 8891
  To 3-word: nijemo zanaza fupawu
From 3-word: 21.2596, -157.8117
 From Index: 21.2596, -157.8117

Coordinates: -55.9652, -67.2256
   To Index: 5317 15428 13632
  To 3-word: zanohu julaso husese
From 3-word: -55.9652, -67.2256
 From Index: -55.9652, -67.2256

Coordinates: 59.3586, 24.7447
   To Index: 23337 4732 15831
  To 3-word: kapupi hokame supoku
From 3-word: 59.3586, 24.7447
 From Index: 59.3586, 24.7447

Coordinates: 29.2021, 81.5324
   To Index: 18625 5535 10268
  To 3-word: dijule nutuza nefini
From 3-word: 29.2021, 81.5324
 From Index: 29.2021, 81.5324

Coordinates: -7.1662, 53.947
   To Index: 12942 12942 12942
  To 3-word: rakudo rakudo rakudo
From 3-word: -7.1662, 53.947
 From Index: -7.1662, 53.947

Coordinates: 28.3852, -81.5638
   To Index: 18497 11324 1322
  To 3-word: tabesa nekaso bupodo
From 3-word: 28.3852, -81.5638
 From Index: 28.3852, -81.5638

(Ok, I admit I manipulated that second to last one, but it is a correct and valid 3-word location in this implementation. There is less than 1 chance in 5000 that it will produce that specific word group though.)

A thought experiment

A little thought experiment... Latitude, longitude to four decimal places is accurate to about 11.1 meters at the equator, smaller the further from the equator you get. What would it take to support five decimal places? (Accurate to 1.11 meters.)

360 * 100000 == 36000000; ceiling 36000000.log(2) == 26;

So we need 26 bits to cover 360.00000; half of that for 180.00000, or 26 bits + 25 bits == 51 bits. 51 / 3 == 17. 2**17 == 131072 indices. The previous synthetics routine provides much more than enough.

How many sylabics will we need to minimally cover it?

∛131072 == 50.7968...

So at least 51. The synthetics routine provide sylabics in blocks of 5, so we would need at least 11 consonants.

Capriciously and somewhat randomly cutting down the list we arrive at this.

10 times better accuracy in the same three, 6-letter word space.

# SYNTHETICS HANDLING
my @synth = flat < b d f j k n p r s t w > X~ < a e i o u >;
my %htnys = @synth.antipairs;
my $exp   = @synth.elems;
my $prec  = 100_000;


sub synth (Int $v) { @synth[$v.polymod($exp xx *).reverse || 0].join }

sub thnys (Str $v) { sum %htnys{$v.comb(2).reverse} Z× 1, $exp, $exp² }


# ENCODE / DECODE
sub w-encode ( Rat(Real) $lat, Rat(Real) $lon, :&f = &synth ) {
    $_ = (($lat +  90) × $prec).round.fmt('%025b') ~ (($lon + 180) × $prec).round.fmt('%026b');
    (:2(.substr(0,17)), :2(.substr(17,17)), :2(.substr(34)))».&f
}

sub w-decode ( *@words, :&f = &thnys ) {
    my $bin = @words».&f.map({.fmt('%017b')}).join;
    (:2($bin.substr(0,25))/$prec) - 90, (:2($bin.substr(25))/$prec) - 180
}


# TESTING
for 51.43372,  -0.21412, # Wimbledon center court
    21.25976,-157.81173, # Diamond Head summit
   -55.96525, -67.22557, # Monumento Cabo De Hornos
    28.3852,  -81.5638,  # Walt Disney World
    89.99999, 179.99999, # test some
   -89.99999,-179.99999  # extremes
  -> $lat, $lon {
    my @words = w-encode $lat, $lon;
    printf "Coordinates: %s, %s\n   To Index: %s\n  To 3-word: %s\nFrom 3-word: %s, %s\n\n",
      $lat, $lon, w-encode($lat, $lon, :f({$_})).Str, @words.Str, w-decode(@words);
}
Output:
Coordinates: 51.43372, -0.21412
   To Index: 55247 71817 21724
  To 3-word: jofuni kosasi diduwu
From 3-word: 51.43372, -0.21412

Coordinates: 21.25976, -157.81173
   To Index: 43460 110608 121675
  To 3-word: fukafa repebo safija
From 3-word: 21.25976, -157.81173

Coordinates: -55.96525, -67.22557
   To Index: 13294 108118 5251
  To 3-word: bukeru rasaso besane
From 3-word: -55.96525, -67.22557

Coordinates: 28.3852, -81.5638
   To Index: 46244 28747 13220
  To 3-word: jajasu duniri bukaka
From 3-word: 28.3852, -81.5638

Coordinates: 89.99999, 179.99999
   To Index: 70312 65298 86271
  To 3-word: kofoki kepifo nonope
From 3-word: 89.99999, 179.99999

Coordinates: -89.99999, -179.99999
   To Index: 0 512 1
  To 3-word: ba duji be
From 3-word: -89.99999, -179.99999
Translation of: Python
Rebol [
    title: "Rosetta code: Three word location"
    file:  %Three_word_location.r3
    url:   https://rosettacode.org/wiki/Three_word_location
]

w3w-encode: func [
    "Encodes an integer as a W-prefixed 5-digit word"
    w [integer!]
][
    ajoin ["W" pad/with w -5 #"0"]
]
w3w-decode: func [
    "Decodes a W-prefixed word back to an integer"
    ws [string!]
][
    to integer! skip ws 1
]

latlon-to-words: function [
    "Encodes a lat/lon coordinate pair into three W-prefixed words"
    lat [decimal!] lon [decimal!]
][
    ilat: to integer! lat * 10000 + 900000  ;; shift to positive range 0..1800000
    ilon: to integer! lon * 10000 + 1800000 ;; shift to positive range 0..3600000
    latlon: (ilat << 22) + ilon             ;; pack into a single 43-bit integer
    w1:   (latlon >> 28) & 0#7FFF           ;; bits 42-28 (15 bits)
    w2:   (latlon >> 14) & 0#3FFF           ;; bits 27-14 (14 bits)
    w3:    latlon        & 0#3FFF           ;; bits 13-0  (14 bits)
    reduce [w3w-encode w1  w3w-encode w2  w3w-encode w3]
]

words-to-latlon: function [
    "Decodes three W-prefixed words back into a lat/lon coordinate pair"
    w1s [string!] w2s [string!] w3s [string!]
][
    latlon: ((w3w-decode w1s) << 28) | ((w3w-decode w2s) << 14) | w3w-decode w3s
    ilat: latlon >> 22
    ilon: latlon & 0#3FFFFF
    reduce [
        ilat - 900000  / 10000.0
        ilon - 1800000 / 10000.0
    ]
]

;; --- main ---

lat:  28.3852
lon: -81.5638

print as-yellow "Starting figures:"
print [" latitude =" round/to lat 0.0001 " longitude =" round/to lon 0.0001]

set [w1s w2s w3s] latlon-to-words lat lon
print as-yellow "^/Three word location is:"
print ["" w1s w2s w3s]

set [lat lon] words-to-latlon w1s w2s w3s
print as-yellow "^/After reversing the procedure:"
print [" latitude =" round/to lat 0.0001 " longitude =" round/to lon 0.0001]
Output:
Starting figures:
 latitude = 28.3852  longitude = -81.5638

Three word location is:
 W18497 W11324 W01322

After reversing the procedure:
 latitude = 28.3852  longitude = -81.5638
Works with: RPL version HP-49C
« 43 STWS DEC
  { 90 180 } ADD 10000 * IP R→I  
  EVAL SWAP 4194304 * + R→B
  1 2 START
     #3FFFh AND LASTARG 1 + / 
  NEXT
  3 →LIST REVLIST
  100000 ADD
  1 « →STR 4 OVER SIZE 1 - SUB "W" SWAP + » DOLIST
» 'LL→W' STO    @ ( { latitude longitude } → { "word1" .. "word3" )

« « 2 OVER SIZE SUB STR→ » MAP
  DUP 1 GET
  2 3 FOR j
     16384 * OVER j GET +
  NEXT
  NIP R→B #400000h / LASTARG 1 - AND 
  2 →LIST B→R 10000 / { 90 180 } - 
» 'W→LL' STO    @ ( → { "word1" .. "word3" → { latitude longitude } )
{ 28.3852 -81.5638 } LL→W
DUP W→LL
Output:
2:  { "W18497" "W11324" "W01322" }
1:  { 28.3852 -81.5638 }
| Three Word Location - convert latitude and longitude to three words

lat : 28.3852
lon : -81.5638

| build word array W00000 ... W28125
 
 i
 if i <= 28125
    ~ i $r
    #$r szr
    'W00000' $t
    (6-szr) szr 
    szr #$t
    + $r $t
    + $t $wordarray 
    + i
    goif
 endif

| make latitude and longitude positive integers

 {lat * 10000 + 900000} ilat
 {lon * 10000 + 1800000} ilon

| build 43 bit integer containing latitude (21 bits) and longitude (22 bits)

 ilat latlon
 shl latlon 22
 + ilon latlon

| isolate most significant 15 bits for word 1 index
| next 14 bits for word 2 index
| next 14 bits for word 3 index

 latlon:42:15 w1
 latlon:27:14 w2
 latlon:13:14 w3

| fetch each word from word array  

  (w1*6+1) w1
  $wordarray.w1 $w1 6
  (w2*6+1) w2
  $wordarray.w2 $w2 6
  (w3*6+1) w3
  $wordarray.w3 $w3 6

| display words

 "$w1 ' ' $w2 ' ' $w3" []


| reverse the procedure


| look up each word
  
 call bsearch 0 28125 $w1
 result w1index

 call bsearch 0 28125 $w2
 result w2index

 call bsearch 0 28125 $w3
 result w3index

| build the latlon integer from the word indexes

 w1index latlon
 shl latlon 14
 + w2index latlon
 shl latlon 14
 + w3index latlon

| isolate the latitude and longitude

 latlon:21:22 ilon
 latlon:42:21 ilat

| convert back to floating point values
 
 {(ilon - 1800000) / 10000} lon
 {(ilat - 900000) / 10000} lat

| display values

 "'latitude = ' lat ' longitude = ' lon" [] 

 stop

bsearch
 param L H $word
 if L <= H 
    ((L + H) shr 1) M
    (M*6+1) I
    $wordarray.I $w 6
    if $w > $word 
       - 1 M H
    else
       if $w < $word 
          + 1 M L
       else      
          return M
       endif
    endif
    goif
 endif
 return -1
Output:
W18497 W11324 W01322
latitude =          28.3852 longitude =         -81.5638

Using Real Words

| Three Word Location - convert latitude and longitude to three words
 

lat : 28.3852
lon : -81.5638

| Build real word array of the first 28126 words of 8 
| or less characters from list of 69905 words sorted alphabetically 
| at http://www-personal.umich.edu/~jlawler/wordlist.
| There are 36282 words of 8 or less characters here.
 

 opentext 'LargeWordList.txt' wf
 if ioresult <> 0
    stop
 endif
 if i <= 28125
    [wf] $s
    if ioresult <> 0
       go closefile
    endif
    #$s wsz
    if wsz <= 8
       + $s $wordarray
       (8-wsz) wsz
       + '        ' $wordarray wsz
       + i
    endif
    goif
 endif
closefile close wf

| make latitude and longitude positive integers

 {lat * 10000 + 900000} ilat
 {lon * 10000 + 1800000} ilon

| build 43 bit integer containing latitude (21 bits) and longitude (22 bits)

 ilat latlon
 shl latlon 22
 + ilon latlon

| isolate most significant 15 bits for word 1 index
| next 14 bits for word 2 index
| next 14 bits for word 3 index

 latlon:42:15 w1
 latlon:27:14 w2
 latlon:13:14 w3

| fetch each word from word array  

  (w1*8+1) w1
  $wordarray.w1 $w1 8
  (w2*8+1) w2
  $wordarray.w2 $w2 8
  (w3*8+1) w3
  $wordarray.w3 $w3 8

| display words

 "$w1 ' ' $w2 ' ' $w3" []


| reverse the procedure


| look up each word
  
 call bsearch 0 28125 $w1
 result w1index

 call bsearch 0 28125 $w2
 result w2index

 call bsearch 0 28125 $w3
 result w3index

| build the latlon integer from the word indexes

 w1index latlon
 shl latlon 14
 + w2index latlon
 shl latlon 14
 + w3index latlon

| isolate the latitude and longitude

 latlon:21:22 ilon
 latlon:42:21 ilat

| convert back to floating point values
 
 {(ilon - 1800000) / 10000} lon
 {(ilat - 900000) / 10000} lat

| display values

 "'latitude = ' lat ' longitude = ' lon" [] 

 stop

bsearch
 param L H $word
 if L <= H 
    ((L + H) shr 1) M
    (M*8+1) I
    $wordarray.I $w 8
    if $w > $word 
       - 1 M H
    else
       if $w < $word 
          + 1 M L
       else      
          return M
       endif
    endif
    goif
 endif
 return -1
Output:
ling     everyone amoral  
latitude =          28.3852 longitude =         -81.5638
Translation of: Wren
Works with: Uiua version 0.12.0-dev.1

Uses a subset of unixdict.txt to populate the word index.

W ← ▽⊸≡(>5◇⧻)⊜□≠@\n.&fras "unixdict.txt" # read file as string
# Calculate 'Three Word' indexes from lat, long.
# w1 w2 w3 ? lat long
LLtoWix ← setinv(
  +×ⁿ22 2 ⊃(+9e5|+18e5◌)∩(⁅×1e4) # combine to one int.
  ⊃(°⋯↙¯15|°⋯↙¯14↘¯15|°⋯↘¯28)⋯   # split into three
| ++⊃(×ⁿ28 2|×ⁿ14 2◌|⋅⋅∘)
  ∩(÷1e4)⊃(-9e5|-18e5◌)∩°⋯⊃(↘22|↙22)⋯
)
ItoW ← ≡(□⊡:W)
&p $"Lat/Long = (_, _)",, 28.38521 ¯81.56379
&p $"Sample three words: _"ItoW ⊂⊟⊃(⊙⊙∘|⊙⊙∘) LLtoWix
&p $"And back again. (_, _)"°LLtoWix
Output:
Lat/Long = (28.38521, -81.56379)
Sample three words: {⌜whittle⌟ ⌜misanthrope⌟ ⌜autograph⌟}
And back again. (28.3852, -81.5638)
Translation of: Go
import strconv
 
fn to_word(w i64) string { return 'W${w:05}' }
 
fn from_word(ws string) i64 {
    u, _ := strconv.common_parse_uint2(ws[1..], 10, 64)
    return i64(u)
}
 
fn main() {
    println("Starting figures:")
    mut lat := 28.3852
    mut lon := -81.5638
    println("  latitude = ${lat:.4}, longitude = ${lon:.4}")
 
    // convert lat and lon to positive integers
    mut ilat := i64(lat*10000 + 900000)
    mut ilon := i64(lon*10000 + 1800000)
 
    // build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
    mut latlon := (ilat << 22) + ilon
 
    // isolate relevant bits
    mut w1 := (latlon >> 28) & 0x7fff
    mut w2 := (latlon >> 14) & 0x3fff
    mut w3 := latlon & 0x3fff
 
    // convert to word format
    w1s := to_word(w1)
    w2s := to_word(w2)
    w3s := to_word(w3)
 
    // and print the results
    println("\nThree word location is:")
    println("  $w1s $w2s $w3s")
 
    /* now reverse the procedure */
    w1 = from_word(w1s)
    w2 = from_word(w2s)
    w3 = from_word(w3s)
 
    latlon = (w1 << 28) | (w2 << 14) | w3
    ilat = latlon >> 22
    ilon = latlon & 0x3fffff
    lat = f64(ilat-900000) / 10000
    lon = f64(ilon-1800000) / 10000
 
    // and print the results
    println("\nAfter reversing the procedure:")
    println("  latitude = ${lat:.4}, longitude = ${lon:.4}")
}
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638
Library: Wren-fmt
Library: Wren-big

This just follows the steps in the task description though I couldn't see any point in creating a 28,126 element array when two simple functions will do.

Note that bitwise operations are limited to 32-bit unsigned integers in Wren which isn't big enough here so we use BigInts instead.

import "./fmt" for Fmt
import "./big" for BigInt

// functions to convert to and from the word format 'W00000'
var toWord   = Fn.new { |w| Fmt.swrite("W$05d", w) }
var fromWord = Fn.new { |w| Num.fromString(w[1..-1]) }

// set latitude and longitude and print them
System.print("Starting figures:")
var lat = 28.3852
var lon = -81.5638
Fmt.print("  latitude = $0.4f, longitude = $0.4f", lat, lon)

// convert lat and lon to positive BigInts
var ilat = BigInt.new(lat * 10000 + 900000)
var ilon = BigInt.new(lon * 10000 + 1800000)

// build 43 bit BigInt comprising 21 bits (lat) and 22 bits (lon)
var latlon = (ilat << 22) + ilon

// isolate relevant bits and convert back to 'normal' ints
var w1 = ((latlon >> 28) & 0x7fff).toSmall
var w2 = ((latlon >> 14) & 0x3fff).toSmall
var w3 = (latlon & 0x3fff).toSmall

// convert to word format
w1 = toWord.call(w1)
w2 = toWord.call(w2)
w3 = toWord.call(w3)

// and print the results
System.print("\nThree word location is:")
Fmt.print("  $s $s $s", w1, w2, w3)

/* now reverse the procedure */
w1 = BigInt.new(fromWord.call(w1))
w2 = BigInt.new(fromWord.call(w2))
w3 = BigInt.new(fromWord.call(w3))
latlon = (w1 << 28) | (w2 << 14) | w3
ilat = (latlon >> 22).toSmall
ilon = (latlon & 0x3fffff).toSmall
lat = (ilat - 900000) / 10000
lon = (ilon - 1800000) / 10000

// and print the results
System.print("\nAfter reversing the procedure:")
Fmt.print("  latitude = $0.4f, longitude = $0.4f", lat, lon)
Output:
Starting figures:
  latitude = 28.3852, longitude = -81.5638

Three word location is:
  W18497 W11324 W01322

After reversing the procedure:
  latitude = 28.3852, longitude = -81.5638