11use geo:: Point ;
2+ use std:: cmp;
23
34use codearea:: CodeArea ;
45
56use consts:: {
6- CODE_ALPHABET , ENCODING_BASE , GRID_COLUMNS , GRID_ROWS , LATITUDE_MAX , LONGITUDE_MAX ,
7- MAX_CODE_LENGTH , MIN_TRIMMABLE_CODE_LEN , PADDING_CHAR , PADDING_CHAR_STR , PAIR_CODE_LENGTH ,
8- PAIR_RESOLUTIONS , SEPARATOR , SEPARATOR_POSITION ,
7+ CODE_ALPHABET , ENCODING_BASE , GRID_CODE_LENGTH , GRID_COLUMNS , GRID_ROWS , LATITUDE_MAX ,
8+ LAT_INTEGER_MULTIPLIER , LNG_INTEGER_MULTIPLIER , LONGITUDE_MAX , MAX_CODE_LENGTH ,
9+ MIN_TRIMMABLE_CODE_LEN , PADDING_CHAR , PADDING_CHAR_STR , PAIR_CODE_LENGTH , PAIR_RESOLUTIONS ,
10+ SEPARATOR , SEPARATOR_POSITION ,
911} ;
1012
1113use private:: {
12- clip_latitude, code_value, compute_latitude_precision, narrow_region, normalize_longitude,
13- prefix_by_reference,
14+ clip_latitude, code_value, compute_latitude_precision, normalize_longitude, prefix_by_reference,
1415} ;
1516
1617/// Determines if a code is a valid Open Location Code.
@@ -104,48 +105,70 @@ pub fn is_full(_code: &str) -> bool {
104105/// 11 or 12 are probably the limit of useful codes.
105106pub fn encode ( pt : Point < f64 > , code_length : usize ) -> String {
106107 let mut lat = clip_latitude ( pt. lat ( ) ) ;
107- let mut lng = normalize_longitude ( pt. lng ( ) ) ;
108+ let lng = normalize_longitude ( pt. lng ( ) ) ;
108109
109- let mut trimmed_code_length = code_length;
110- if trimmed_code_length > MAX_CODE_LENGTH {
111- trimmed_code_length = MAX_CODE_LENGTH ;
112- }
110+ let trimmed_code_length = cmp:: min ( code_length, MAX_CODE_LENGTH ) ;
113111
114112 // Latitude 90 needs to be adjusted to be just less, so the returned code
115113 // can also be decoded.
116114 if lat > LATITUDE_MAX || ( LATITUDE_MAX - lat) < 1e-10f64 {
117115 lat -= compute_latitude_precision ( trimmed_code_length) ;
118116 }
119117
120- lat += LATITUDE_MAX ;
121- lng += LONGITUDE_MAX ;
118+ // Convert to integers.
119+ let mut lat_val =
120+ ( ( ( lat + LATITUDE_MAX ) * LAT_INTEGER_MULTIPLIER as f64 * 1e6 ) . round ( ) / 1e6f64 ) as i64 ;
121+ let mut lng_val =
122+ ( ( ( lng + LONGITUDE_MAX ) * LNG_INTEGER_MULTIPLIER as f64 * 1e6 ) . round ( ) / 1e6f64 ) as i64 ;
122123
123- let mut code = String :: with_capacity ( trimmed_code_length + 1 ) ;
124- let mut digit = 0 ;
125- while digit < trimmed_code_length {
126- narrow_region ( digit, & mut lat, & mut lng) ;
124+ // Compute the code digits. This largely ignores the requested length - it
125+ // generates either a 10 digit code, or a 15 digit code, and then truncates
126+ // it to the requested length.
127127
128- let lat_digit = lat as usize ;
129- let lng_digit = lng as usize ;
130- if digit < PAIR_CODE_LENGTH {
131- code. push ( CODE_ALPHABET [ lat_digit] ) ;
132- code. push ( CODE_ALPHABET [ lng_digit] ) ;
133- digit += 2 ;
134- } else {
135- code. push ( CODE_ALPHABET [ 4 * lat_digit + lng_digit] ) ;
136- digit += 1 ;
128+ // Build up the code digits in reverse order.
129+ let mut rev_code = String :: with_capacity ( trimmed_code_length + 1 ) ;
130+
131+ // First do the grid digits.
132+ if code_length > PAIR_CODE_LENGTH {
133+ for _i in 0 ..GRID_CODE_LENGTH {
134+ let lat_digit = lat_val % GRID_ROWS as i64 ;
135+ let lng_digit = lng_val % GRID_COLUMNS as i64 ;
136+ let ndx = ( lat_digit * GRID_COLUMNS as i64 + lng_digit) as usize ;
137+ rev_code. push ( CODE_ALPHABET [ ndx] ) ;
138+ lat_val /= GRID_ROWS as i64 ;
139+ lng_val /= GRID_COLUMNS as i64 ;
137140 }
138- lat -= lat_digit as f64 ;
139- lng -= lng_digit as f64 ;
140- if digit == SEPARATOR_POSITION {
141- code. push ( SEPARATOR ) ;
141+ } else {
142+ // Adjust latitude and longitude values to skip the grid digits.
143+ lat_val /= GRID_ROWS . pow ( GRID_CODE_LENGTH as u32 ) as i64 ;
144+ lng_val /= GRID_COLUMNS . pow ( GRID_CODE_LENGTH as u32 ) as i64 ;
145+ }
146+ // Compute the pair section of the code.
147+ for i in 0 ..PAIR_CODE_LENGTH / 2 {
148+ rev_code. push ( CODE_ALPHABET [ ( lng_val % ENCODING_BASE as i64 ) as usize ] ) ;
149+ lng_val /= ENCODING_BASE as i64 ;
150+ rev_code. push ( CODE_ALPHABET [ ( lat_val % ENCODING_BASE as i64 ) as usize ] ) ;
151+ lat_val /= ENCODING_BASE as i64 ;
152+ // If we are at the separator position, add the separator.
153+ if i == 0 {
154+ rev_code. push ( SEPARATOR ) ;
142155 }
143156 }
144- if digit < SEPARATOR_POSITION {
145- code. push_str ( PADDING_CHAR_STR . repeat ( SEPARATOR_POSITION - digit) . as_str ( ) ) ;
157+ let mut code: String ;
158+ // If we need to pad the code, replace some of the digits.
159+ if code_length < SEPARATOR_POSITION {
160+ code = rev_code. chars ( ) . rev ( ) . take ( code_length) . collect ( ) ;
161+ code. push_str (
162+ PADDING_CHAR_STR
163+ . repeat ( SEPARATOR_POSITION - code_length)
164+ . as_str ( ) ,
165+ ) ;
146166 code. push ( SEPARATOR ) ;
167+ } else {
168+ code = rev_code. chars ( ) . rev ( ) . take ( code_length + 1 ) . collect ( ) ;
147169 }
148- code
170+
171+ return code;
149172}
150173
151174/// Decodes an Open Location Code into the location coordinates.
@@ -165,35 +188,36 @@ pub fn decode(_code: &str) -> Result<CodeArea, String> {
165188 code = code. chars ( ) . take ( MAX_CODE_LENGTH ) . collect ( ) ;
166189 }
167190
168- let mut lat = -LATITUDE_MAX ;
169- let mut lng = -LONGITUDE_MAX ;
170- let mut lat_res = ENCODING_BASE * ENCODING_BASE ;
171- let mut lng_res = ENCODING_BASE * ENCODING_BASE ;
191+ // Work out the values as integers and convert to floating point at the end.
192+ let mut lat: i64 = -90 * LAT_INTEGER_MULTIPLIER ;
193+ let mut lng: i64 = -180 * LNG_INTEGER_MULTIPLIER ;
194+ let mut lat_place_val: i64 = LAT_INTEGER_MULTIPLIER * ENCODING_BASE . pow ( 2 ) as i64 ;
195+ let mut lng_place_val: i64 = LNG_INTEGER_MULTIPLIER * ENCODING_BASE . pow ( 2 ) as i64 ;
172196
173197 for ( idx, chr) in code. chars ( ) . enumerate ( ) {
174198 if idx < PAIR_CODE_LENGTH {
175199 if idx % 2 == 0 {
176- lat_res /= ENCODING_BASE ;
177- lat += lat_res * code_value ( chr) as f64 ;
200+ lat_place_val /= ENCODING_BASE as i64 ;
201+ lat += lat_place_val * code_value ( chr) as i64 ;
178202 } else {
179- lng_res /= ENCODING_BASE ;
180- lng += lng_res * code_value ( chr) as f64 ;
203+ lng_place_val /= ENCODING_BASE as i64 ;
204+ lng += lng_place_val * code_value ( chr) as i64 ;
181205 }
182- } else if idx < MAX_CODE_LENGTH {
183- lat_res /= GRID_ROWS ;
184- lng_res /= GRID_COLUMNS ;
185- lat += lat_res * ( code_value ( chr) as f64 / GRID_COLUMNS ) . trunc ( ) ;
186-
187- lng += lng_res * ( code_value ( chr) as f64 % GRID_COLUMNS ) ;
206+ } else {
207+ lat_place_val /= GRID_ROWS as i64 ;
208+ lng_place_val /= GRID_COLUMNS as i64 ;
209+ lat += lat_place_val * ( code_value ( chr) / GRID_COLUMNS ) as i64 ;
210+ lng += lng_place_val * ( code_value ( chr) % GRID_COLUMNS ) as i64 ;
188211 }
189212 }
190- Ok ( CodeArea :: new (
191- lat,
192- lng,
193- lat + lat_res,
194- lng + lng_res,
195- code. len ( ) ,
196- ) )
213+ // Convert to floating point values.
214+ let lat_lo: f64 = lat as f64 / LAT_INTEGER_MULTIPLIER as f64 ;
215+ let lng_lo: f64 = lng as f64 / LNG_INTEGER_MULTIPLIER as f64 ;
216+ let lat_hi: f64 =
217+ ( lat + lat_place_val) as f64 / ( ENCODING_BASE . pow ( 3 ) * GRID_ROWS . pow ( 5 ) ) as f64 ;
218+ let lng_hi: f64 =
219+ ( lng + lng_place_val) as f64 / ( ENCODING_BASE . pow ( 3 ) * GRID_COLUMNS . pow ( 5 ) ) as f64 ;
220+ Ok ( CodeArea :: new ( lat_lo, lng_lo, lat_hi, lng_hi, code. len ( ) ) )
197221}
198222
199223/// Remove characters from the start of an OLC code.
0 commit comments