My girlfriend needed to convert a list of What3Words addresses back into coordinates for her Masters so I wrote this little tool that converts What3Words addresses to latitude/longitude coordinates and vice versa.
The API
I started by visiting what3words.com and doing a cheeky ‘f12’ to find out what publicly accessible API endpoint they’re using for their webpage.

Using curl to confirm:
curl -i -H "Accept: application/json" "https://mapapi.what3words.com/api/convert-to-coordinates?words=prices.slippery.traps&format=json"
Returns this json with our desired coordinates
{
"country": "FR",
"square": {
"southwest": { "lng": 2.294453, "lat": 48.858344 },
"northeast": { "lng": 2.294494, "lat": 48.858371 }
},
"nearestPlace": "Paris",
"coordinates": { "lng": 2.294473, "lat": 48.858358 },
"words": "prices.slippery.traps",
"language": "en",
"map": "https:\/\/w3w.co\/prices.slippery.traps"
}
Confirmed the 2 API endpoints I was after
- Words –> Coords:
https://mapapi.what3words.com/api/convert-to-coordinates - Coords –> Words:
https://mapapi.what3words.com/api/convert-to-3wa
Implementation
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
struct ApiResponse {
coordinates: Coordinates,
}
struct Coordinates {
lat: f64,
lng: f64,
}
fn lookup_words(client: &Client, words: &str) -> (f64, f64) {
let url = format!(
"https://mapapi.what3words.com/api/convert-to-coordinates?words={}&format=json",
words
);
let response = client
.get(&url)
.header("Accept", "application/json")
.send()
.expect("Failed to send request");
let body = response.text().expect("Failed to get response body");
let api_response: ApiResponse =
serde_json::from_str(&body).expect("Failed to parse JSON response");
(api_response.coordinates.lat, api_response.coordinates.lng)
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let (input, output_to_file, output_path) = match args.len() {
2 => (args[1].clone(), false, String::new()),
3 => (args[1].clone(), true, args[2].clone()),
_ => {
eprintln!("Usage: {} <input> [output_file]", args[0]);
eprintln!(" <input> can be:");
eprintln!(" - A file path (one what3words per line)");
eprintln!(" - A single set of three words (e.g. prices.slippery.traps)");
eprintln!(" [output_file] is optional - if omitted, output goes to stdout");
std::process::exit(1);
}
};
let client = Client::new();
let words_list: Vec<String> =
if input.contains('.') && !input.contains('/') && !input.contains('\\') {
vec![input.clone()]
} else {
let input_file = File::open(&input).expect("Failed to open input file");
let reader = BufReader::new(input_file);
reader
.lines()
.map(|l| l.unwrap().trim().to_string())
.filter(|w| !w.is_empty())
.collect()
};
let mut output_file =
output_to_file.then(|| File::create(&output_path).expect("Failed to create output file"));
for words in words_list {
let (lat, lng) = lookup_words(&client, &words);
let output_line = format!("{},{},{}", words, lat, lng);
if let Some(ref mut file) = output_file {
file.write_all(format!("{}\n", output_line).as_bytes())
.expect("Failed to write to output file");
} else {
println!("{}", output_line);
}
}
if output_to_file {
println!("Output written to {}", output_path);
}
}
Up to date code available at https://github.com/AlexFigar/3W2Coords
Installation
git clone https://github.com/AlexFigar/3W2Coords.git
./build.sh
Or manually:
cargo build --release
The compiled binary will be at: target/release/3W2Coords
Usage
# Single what3words to coordinates
./3W2Coords prices.slippery.traps
# Coordinates to what3words (reverse lookup)
./3W2Coords "48.858358,2.294473" --reverse
# File with multiple locations
./3W2Coords test.txt
# Specify output file
./3W2Coords test.txt output.txt
# Output format options
./3W2Coords prices.slippery.traps -f text # default: words,lat,lng
./3W2Coords prices.slippery.traps -f csv # comma-separated
./3W2Coords prices.slippery.traps -f json # JSON array
Options
-f, --format <FORMAT> Output format: text, csv, json [default: text]
-r, --reverse Coordinate to 3 words (reverse lookup)
-h, --help Print help
-V, --version Print version
Output Format
text/csv (default):
words,lat,lng
prices.slippery.traps,48.858358,2.294473
json:
[{"lat":48.858358,"lng":2.294473,"words":"prices.slippery.traps"}]