Places

Plus codes

Plus codes (Open Location Codes), the addresses Google Maps gives places without a street: from a latitude and longitude, back to their area, shortened near a town and recovered from the short form.

Version 1.0.0 · 5 functions · 0 crates

#geo#maps#addresses#delivery

Functions

What use plus_codes.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use plus_code_encode, plus_code_decode from plus_codes (Quillo 0.6).

plus_code_encode(lat: float, lon: float, length: int) → stror an error

The plus code of a point with length digits: 10 (about 14 × 14 m, the usual), 11 (3 × 3 m) up to 15; 2, 4, 6 or 8 for larger areas, padded with 0.

plus_code_decode(code: str) → anyor an error

The area of a full plus code: { lat, lon, south, west, north, east, length }, its centre, its edges and its number of digits.

plus_code_shorten(code: str, lat: float, lon: float) → stror an error

A full code without the digits a nearby place makes plain: "9G8F+6X" for a code near (lat, lon), the centre of a town. The code is given back whole when the place is too far.

plus_code_recover(short_code: str, lat: float, lon: float) → stror an error

The full code of a short one ("9G8F+6X") given a point near it, such as the town it was written with: the nearest code that ends so.

plus_code_kind(code: str) → str

What code is: "full", "short" or "invalid".

Get it

$ quillo install plus_codes               # plus_codes.qwer into this folder$ quillo install plus_codes --example     # and its example, plus_codes.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/plus_codes.qwer$ curl -O https://quillolang.org/plugins/files/plus_codes.qwe

Then, in your program, all of it or only what you call:

main.qwe
use plus_codes.qweruse plus_code_encode, plus_code_decode from plus_codes      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

plus_codes.qwe
// Deliveries to places without a street address: the driver gets a plus code, the customer can type a short one.////   quillo run plus_codes.qwe      then open http://localhost:8080/codesuse "plus_codes.qwer"route GET /codes {    // Google's office in Mountain View, and the reference codes of the Open Location Code tests.    let office = plus_code_encode(37.4220041, -122.0862462, 10)?    {        office: office,        office_precise: plus_code_encode(37.4220041, -122.0862462, 11)?,        area: plus_code_decode("7FG49QCJ+2V")?,        region: plus_code_encode(20.3700625, 2.7821875, 4)?,        near: plus_code_shorten("9C3W9QCJ+2VX", 51.3701125, -1.217765625)?,        further: plus_code_shorten("9C3W9QCJ+2VX", 51.3708675, -1.217765625)?,        recovered: plus_code_recover("9QCJ+2VX", 51.3708675, -1.217765625)?,        kinds: [plus_code_kind("9C3W9QCJ+2VX"), plus_code_kind("QCJ+2VX"), plus_code_kind("9C3W9QCJ"), plus_code_kind("CJ+2VX")],    }}// /where?code=CJ+2VX&lat=51.37&lon=-1.21: a code from a form, short or full, as a map position.route GET /where ? { code: str, lat: float = 51.37, lon: float = -1.21 } {    let full = plus_code_recover(code, lat, lon)?    merge({ code: full }, plus_code_decode(full)?)}
$ quillo run plus_codes.qwe$ curl 'localhost:8080/codes'

Source

plus_codes.qwer
//! Plus codes (Open Location Codes), Google Maps' addresses for places without a street address: "8FVC9G8F+6X" from a latitude and longitude, back to its area, shortened near a town ("9G8F+6X Zurich") and recovered.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};const ALPHABET: &[u8; 20] = b"23456789CFGHJMPQRVWX";const SEP: char = '+';const SEP_POS: usize = 8;const PAD: char = '0';const PAIR_LEN: usize = 10;const MAX_DIGITS: usize = 15;const PAIR_PRECISION: i64 = 8000;const FINAL_LAT: i64 = 8000 * 3125; // 5^5 grid rowsconst FINAL_LNG: i64 = 8000 * 1024; // 4^5 grid columnsconst PAIR_RES: [f64; 5] = [20.0, 1.0, 0.05, 0.0025, 0.000125];fn index(c: char) -> Option<i64> {    let u = c.to_ascii_uppercase() as u8;    ALPHABET.iter().position(|&a| a == u).map(|i| i as i64)}fn lat_precision(len: usize) -> f64 {    if len <= PAIR_LEN { 20f64.powi((len as i32) / -2 + 2) } else { 20f64.powi(-3) / 5f64.powi(len as i32 - PAIR_LEN as i32) }}fn encode(lat: f64, lng: f64, len: usize) -> String {    let mut lat = lat.clamp(-90.0, 90.0);    let mut lng = lng;    while lng < -180.0 { lng += 360.0 }    while lng >= 180.0 { lng -= 360.0 }    if lat == 90.0 {        lat -= lat_precision(len);    }    let mut lat_v = (((lat + 90.0) * FINAL_LAT as f64 * 1e6).round() / 1e6).floor() as i64;    let mut lng_v = (((lng + 180.0) * FINAL_LNG as f64 * 1e6).round() / 1e6).floor() as i64;    let mut rev: Vec<char> = Vec::new();    if len > PAIR_LEN {        for _ in 0..5 {            let d = (lat_v % 5) * 4 + (lng_v % 4);            rev.push(ALPHABET[d as usize] as char);            lat_v /= 5;            lng_v /= 4;        }    } else {        lat_v /= 3125;        lng_v /= 1024;    }    for _ in 0..PAIR_LEN / 2 {        rev.push(ALPHABET[(lng_v % 20) as usize] as char);        rev.push(ALPHABET[(lat_v % 20) as usize] as char);        lat_v /= 20;        lng_v /= 20;    }    let digits: String = rev.into_iter().rev().collect();    let code = format!("{}{}{}", &digits[..SEP_POS], SEP, &digits[SEP_POS..]);    if len >= SEP_POS {        code[..len + 1].to_string()    } else {        format!("{}{}{}", &code[..len], PAD.to_string().repeat(SEP_POS - len), SEP)    }}fn valid(code: &str) -> bool {    let code = code.trim();    if code.is_empty() || !code.is_ascii() {        return false;    }    let seps: Vec<usize> = code.match_indices(SEP).map(|(i, _)| i).collect();    if seps.len() != 1 || seps[0] > SEP_POS || seps[0] % 2 == 1 {        return false;    }    let sep = seps[0];    if let Some(p) = code.find(PAD) {        if p == 0 || sep < SEP_POS {            return false;        }        let pads = &code[p..sep];        if pads.len() % 2 == 1 || pads.chars().any(|c| c != PAD) || code.len() > sep + 1 {            return false;        }        if p % 2 == 1 {            return false;        }    }    if code.len() - sep - 1 == 1 {        return false;    }    code.chars().filter(|&c| c != SEP && c != PAD).all(|c| index(c).is_some())}fn is_short(code: &str) -> bool {    valid(code) && code.find(SEP).map(|i| i < SEP_POS).unwrap_or(false)}fn is_full(code: &str) -> bool {    if !valid(code) || is_short(code) {        return false;    }    let c: Vec<char> = code.chars().collect();    if index(c[0]).unwrap_or(99) * 20 >= 180 {        return false;    }    if c.len() > 1 && index(c[1]).unwrap_or(99) * 20 >= 360 {        return false;    }    true}struct Area { south: f64, west: f64, north: f64, east: f64, len: usize }fn decode(code: &str) -> Result<Area, String> {    if !is_full(code) {        return Err(format!("`{}` is not a full plus code", code));    }    let clean: Vec<char> = code.trim().chars().filter(|&c| c != SEP && c != PAD).map(|c| c.to_ascii_uppercase()).collect();    let mut nlat = -90 * PAIR_PRECISION;    let mut nlng = -180 * PAIR_PRECISION;    let (mut glat, mut glng) = (0i64, 0i64);    let digits = clean.len().min(PAIR_LEN);    let mut pv: i64 = 160_000;    let mut i = 0;    while i < digits {        nlat += index(clean[i]).unwrap() * pv;        nlng += index(clean[i + 1]).unwrap() * pv;        if i < digits - 2 {            pv /= 20;        }        i += 2;    }    let mut lat_p = pv as f64 / PAIR_PRECISION as f64;    let mut lng_p = pv as f64 / PAIR_PRECISION as f64;    if clean.len() > PAIR_LEN {        let (mut rowpv, mut colpv) = (625i64, 256i64);        let digits = clean.len().min(MAX_DIGITS);        for i in PAIR_LEN..digits {            let d = index(clean[i]).unwrap();            glat += (d / 4) * rowpv;            glng += (d % 4) * colpv;            if i < digits - 1 {                rowpv /= 5;                colpv /= 4;            }        }        lat_p = rowpv as f64 / FINAL_LAT as f64;        lng_p = colpv as f64 / FINAL_LNG as f64;    }    let lat = nlat as f64 / PAIR_PRECISION as f64 + glat as f64 / FINAL_LAT as f64;    let lng = nlng as f64 / PAIR_PRECISION as f64 + glng as f64 / FINAL_LNG as f64;    let r = |v: f64| (v * 1e14).round() / 1e14;    Ok(Area { south: r(lat), west: r(lng), north: r(lat + lat_p), east: r(lng + lng_p), len: clean.len().min(MAX_DIGITS) })}/// The plus code of a point with `length` digits: 10 (about 14 × 14 m, the usual), 11 (3 × 3 m) up to 15; 2, 4, 6 or 8 for larger areas, padded with 0.pub fn plus_code_encode(lat: f64, lon: f64, length: i64) -> Result<String, String> {    if length < 2 || (length < PAIR_LEN as i64 && length % 2 == 1) || length > MAX_DIGITS as i64 {        return Err(format!("a plus code has 2, 4, 6, 8 or 10 to 15 digits, not {}", length));    }    if !lat.is_finite() || !lon.is_finite() {        return Err("the latitude and longitude must be numbers".into());    }    Ok(encode(lat, lon, length as usize))}/// The area of a full plus code: `{ lat, lon, south, west, north, east, length }`, its centre, its edges and its number of digits.pub fn plus_code_decode(code: &str) -> Result<Value, String> {    let a = decode(code)?;    let lat = ((a.south + a.north) / 2.0).min(90.0);    let lon = ((a.west + a.east) / 2.0).min(180.0);    Ok(json!({ "lat": (lat * 1e10).round() / 1e10, "lon": (lon * 1e10).round() / 1e10, "south": a.south, "west": a.west, "north": a.north, "east": a.east, "length": a.len }))}/// A full code without the digits a nearby place makes plain: `"9G8F+6X"` for a code near (`lat`, `lon`), the centre of a town. The code is given back whole when the place is too far.pub fn plus_code_shorten(code: &str, lat: f64, lon: f64) -> Result<String, String> {    if !is_full(code) {        return Err(format!("`{}` is not a full plus code", code));    }    if code.contains(PAD) {        return Err(format!("`{}` is padded and cannot be shortened", code));    }    let a = decode(code)?;    if a.len < 8 {        return Err(format!("`{}` is too short to shorten", code));    }    let up = code.trim().to_ascii_uppercase();    let range = (((a.south + a.north) / 2.0) - lat.clamp(-90.0, 90.0)).abs().max((((a.west + a.east) / 2.0) - lon).abs());    for i in (1..=3).rev() {        if range < PAIR_RES[i] * 0.3 {            return Ok(up[(i + 1) * 2..].to_string());        }    }    Ok(up)}/// The full code of a short one (`"9G8F+6X"`) given a point near it, such as the town it was written with: the nearest code that ends so.pub fn plus_code_recover(short_code: &str, lat: f64, lon: f64) -> Result<String, String> {    let code = short_code.trim().to_ascii_uppercase();    if is_full(&code) {        return Ok(code);    }    if !is_short(&code) {        return Err(format!("`{}` is not a plus code", short_code));    }    let lat = lat.clamp(-90.0, 90.0);    let mut lon = lon;    while lon < -180.0 { lon += 360.0 }    while lon >= 180.0 { lon -= 360.0 }    let pad = SEP_POS - code.find(SEP).unwrap();    let res = 20f64.powf(2.0 - pad as f64 / 2.0);    let half = res / 2.0;    let full = format!("{}{}", &encode(lat, lon, PAIR_LEN)[..pad], code);    let a = decode(&full)?;    let mut clat = (a.south + a.north) / 2.0;    let mut clng = (a.west + a.east) / 2.0;    if lat + half < clat && clat - res >= -90.0 {        clat -= res;    } else if lat - half > clat && clat + res <= 90.0 {        clat += res;    }    if lon + half < clng {        clng -= res;    } else if lon - half > clng {        clng += res;    }    Ok(encode(clat, clng, a.len))}/// What `code` is: `"full"`, `"short"` or `"invalid"`.pub fn plus_code_kind(code: &str) -> String {    if is_full(code) { "full" } else if is_short(code) { "short" } else { "invalid" }.to_string()}
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On Plus codes: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.