1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#![no_std]
extern crate libm;
mod wordlist;
use libm::F64Ext;
use wordlist::WORDLIST;
const TILES_SIZES: &[f64] = &[5.625, 0.125, 0.00277777777, 0.00006172839, 0.00000308642];
const RASTER_SIZE: &[(usize, usize)] = &[(32, 64), (45, 45), (45, 45), (45, 45), (20, 20)];
fn get_word_idx<'a>(word: &'a str) -> Result<usize, Error<'a>> {
WORDLIST.iter()
.enumerate()
.find(|(_, w)| **w == word)
.map(|(idx, _)| idx)
.ok_or(Error::InvalidWord(word))
}
pub fn from_words<'a, I, S>(words: I) -> Result<(f64, f64), Error<'a>>
where I: Iterator<Item=&'a S> + ExactSizeIterator,
S: AsRef<str> + 'a + ?Sized
{
if words.len() > 5 {
return Err(Error::TooManyWords(words.len()))
}
let (lat, lng) = words.enumerate()
.map(|(idx, word)| {
let word_idx_limit = RASTER_SIZE[idx].0 * RASTER_SIZE[idx].1;
let word_idx = get_word_idx(word.as_ref())?;
if word_idx >= word_idx_limit {
return Err(Error::InvalidWord(word.as_ref()));
}
let lat_idx = word_idx / RASTER_SIZE[idx].1;
let long_idx = word_idx % RASTER_SIZE[idx].1;
Ok((
(lat_idx as f64) * TILES_SIZES[idx],
(long_idx as f64) * TILES_SIZES[idx],
))
})
.try_fold((-90f64, -180f64), |acc, coords| {
match coords {
Ok((lat, long)) => Ok((acc.0 + lat, acc.1 + long)),
Err(e) => Err(e)
}
})?;
Ok((lat, lng))
}
pub fn to_words(lat: f64, lng: f64) -> Result<[&'static str; 5], Error<'static>> {
if (-90f64..=90f64).contains(&lat) == false || (-180f64..=180f64).contains(&lng) == false {
return Err(Error::CoordinatesOutOfRange)
}
let mut lat = lat + 90f64;
let mut lng = ((lng - 180f64) % 360f64) + 360f64;
let mut code = [""; 5];
for (idx, code_word) in code.iter_mut().enumerate() {
let lat_idx_f64 = (lat / TILES_SIZES[idx]).floor();
let lng_idx_f64 = (lng / TILES_SIZES[idx]).floor();
lat -= lat_idx_f64 * TILES_SIZES[idx];
lng -= lng_idx_f64 * TILES_SIZES[idx];
let word_idx = (lat_idx_f64 as usize) * RASTER_SIZE[idx].1 + (lng_idx_f64 as usize);
*code_word = WORDLIST[word_idx];
}
Ok(code)
}
#[derive(Debug)]
pub enum Error<'a> {
InvalidWord(&'a str),
TooManyWords(usize),
CoordinatesOutOfRange
}
#[cfg(test)]
mod tests {
#[test]
fn test_words_to_coords() {
let words = &["slush", "battle", "damage", "dentist"][..];
let (lat, lng) = super::from_words(words.iter()).unwrap();
assert_eq!(lat, 51.02561728383f64);
assert_eq!(lng, 13.72333333297f64);
}
#[test]
fn test_coords_to_words() {
let code = super::to_words(51.02561728383f64, 13.72333333297f64).unwrap();
let expected = &["slush", "battle", "damage", "dentist"][..];
assert_eq!(expected, &code[..4]);
}
}