quadkey 0.1.0

Quadkey conversion utlities
Documentation
const CHAR_CODE_ZERO: usize = 48;

#[derive(Debug, PartialEq)]
pub struct Tile {
    pub x: usize,
    pub y: usize,
    pub z: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct QuadKeyParseError;

/// Convert a tile to a binary quadkey
///
/// # Examples
/// 
/// ```
/// let qk = tile_to_u64(29, 50, 7)
/// // 0b0010110101100100000000000000000000000000000000000000000000000111
/// ```
///
pub fn tile_to_u64(x: usize, y: usize, zoom: usize) -> u64 {
    let mut qk: u64 = 0;
    let mut i = zoom;
    while i > 0 {
        let mask = 1 << (i - 1);
        let location = 64 - ((zoom - i + 1) * 2) + 1;

        if (x & mask) != 0 {
            qk = qk | 1 << (location - 1);
        }
        if (y & mask) != 0 {
            qk = qk | 1 << location;
        }
        i = i - 1;
    }

    qk = qk | (zoom as u64);
    return qk;
}

/// Convert a binary quadkey into a tile
///
/// # Examples
/// 
/// ```
/// let tile = u64_to_tile(0b0010110101100100000000000000000000000000000000000000000000000111)
/// // Tile { x: 29, y: 50, z: 7 }
/// ```
///
pub fn u64_to_tile(qk: u64) -> Tile {
    let zoom = u64_zoom_level(qk) as usize;
    let mut x = 0;
    let mut y = 0;
    let mut i = zoom;
    while i > 0 {
        let mask = 1 << (i - 1);

        let location = 64 - ((zoom - i + 1) * 2) + 1;
        if qk & (1 << (location - 1)) != 0 {
            x = x | mask
        }
        if qk & (1 << location) != 0 {
            y = y | mask
        }
        i = i - 1;
    }

    return Tile { x, y, z: zoom };
}

/// Get the zoom level from a binary quadkey
///
/// # Examples
/// 
/// ```
/// let zoom = u64_zoom_level(0b0010110101100100000000000000000000000000000000000000000000000111)
/// // 7
/// ```
///
#[inline]
pub fn u64_zoom_level(bin_qk: u64) -> u64 {
    return bin_qk & 31;
}

/// Convert a tile into a quadkey string
///
/// # Examples
/// 
/// ```
/// let qk = tile_to_str(11, 3, 8) 
/// // "00001033"
/// ```
///
pub fn tile_to_str(x: usize, y: usize, z: usize) -> String {
    let mut res = vec!['0'; z];

    let mut i = z;
    while i > 0 {
        let mut digit = '0' as u8;
        let mask = 1 << (i - 1);
        if (x & mask) != 0 {
            digit = digit + 1;
        }
        if (y & mask) != 0 {
            digit = digit + 2;
        }

        res[z - i] = digit as char;

        i = i - 1;
    }
    return res.into_iter().collect();
}

/// Convert a quadkey string into a tile
///
/// # Examples
/// 
/// ```
/// let tile = str_to_tile("00001033") 
/// // Tile { x:11, y:3, z:8 } 
/// ```
///
pub fn str_to_tile(qk: String) -> Result<Tile, QuadKeyParseError> {
    let mut x = 0;
    let mut y = 0;
    let z = qk.len();

    let bytes = qk.as_bytes();
    let mut i = z;
    while i > 0 {
        let mask = 1 << (i - 1);

        let q = (bytes[z - i] as usize) - CHAR_CODE_ZERO;
        match q {
            0 => (),
            1 => x = x | mask,
            2 => y = y | mask,
            3 => {
                x |= mask;
                y |= mask;
            }
            _ => return Err(QuadKeyParseError {}),
        }
        i = i - 1;
    }

    return Ok(Tile { x, y, z });
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_converts_tile_to_string() {
        assert_eq!(crate::tile_to_str(0, 0, 0), "");
        assert_eq!(crate::tile_to_str(1, 1, 1), "3");
        assert_eq!(crate::tile_to_str(11, 3, 8), "00001033");
    }

    #[test]
    fn it_converts_from_string() {
        assert_eq!(
            crate::str_to_tile(String::from("00001033")).unwrap(),
            crate::Tile { x: 11, y: 3, z: 8 }
        );
    }

    #[test]
    fn it_round_trips_from_string() {
        let tile = crate::Tile { x: 11, y: 3, z: 8 };
        let tile_qk = crate::tile_to_str(tile.x, tile.y, tile.z);
        assert_eq!(crate::str_to_tile(tile_qk).unwrap(), tile);
    }

    #[test]
    fn it_get_binary_zoom_level() {
        assert_eq!(crate::u64_zoom_level(1), 1);
        assert_eq!(crate::u64_zoom_level(2), 2);
        assert_eq!(crate::u64_zoom_level(4), 4);
        assert_eq!(crate::u64_zoom_level(7), 7);
        assert_eq!(crate::u64_zoom_level(8), 8);
        assert_eq!(crate::u64_zoom_level(24), 24);
        assert_eq!(crate::u64_zoom_level(20024), 24);
    }

    #[test]
    fn it_converts_tile_to_u64() {
        assert_eq!(
            crate::tile_to_u64(1, 1, 1),
            0b1100000000000000000000000000000000000000000000000000000000000001
        );
        assert_eq!(
            crate::tile_to_u64(29, 50, 7),
            0b0010110101100100000000000000000000000000000000000000000000000111
        );
        assert_eq!(
            crate::tile_to_u64(35210, 21493, 16),
            0b110001001001011111010100110011000000000000000000000000000010000
        );
    }

    #[test]
    fn it_converts_from_u64() {
        assert_eq!(
            crate::u64_to_tile(0b1100000000000000000000000000000000000000000000000000000000000001),
            crate::Tile { x: 1, y: 1, z: 1 }
        );
        assert_eq!(
            crate::u64_to_tile(0b0010110101100100000000000000000000000000000000000000000000000111),
            crate::Tile { x: 29, y: 50, z: 7 }
        );
        assert_eq!(
            crate::u64_to_tile(0b110001001001011111010100110011000000000000000000000000000010000),
            crate::Tile {
                x: 35210,
                y: 21493,
                z: 16
            }
        );
    }
}