ndslive-math 1.0.0

Coordinate math, packed tile IDs, and Morton (Z-order) codes for NDS.Live geographic tiling.
Documentation
// SPDX-License-Identifier: BSD-3-Clause
//
// Targeted tests for public-API paths not exercised by the parity vectors:
// error/Display formatting, alternate constructors, dimensions, bbox-from-tiles,
// normalization wrap branches, equality, and out-of-range Morton wrapping.

use ndslive_math::{bounding_box_from_tile_ids, MortonCode, PackedTileId, TileIdError, Wgs84};

#[test]
fn tile_id_error_display() {
    let e1: TileIdError = PackedTileId::from_tile_index(0, 16).unwrap_err();
    assert!(format!("{e1}").contains("Invalid level"));

    let e2 = PackedTileId::from_tile_index(99, 0).unwrap_err(); // max morton at level 0 is 1
    assert!(format!("{e2}").contains("morton"));

    let e3 = PackedTileId::new(0).unwrap_err(); // below the minimum packed tile id
    assert!(format!("{e3}").contains("must be >="));
}

#[test]
fn constructors() {
    // new() with a valid signed value (level 13, positive) and a level-15
    // negative value.
    let positive = PackedTileId::from_tile_index(12345, 13).unwrap().value();
    assert!(PackedTileId::new(positive).is_ok());
    let level15 = PackedTileId::from_tile_index(0, 15).unwrap().value();
    assert!(level15 < 0);
    assert!(PackedTileId::new(level15).is_ok());

    // from_i64 with an unsigned representation >= 2^32 (masked to low 32 bits).
    let t = PackedTileId::from_i64((1i64 << 32) + (1i64 << 16)).unwrap();
    assert_eq!(t.level(), 0);

    // from_morton_and_level rejects an invalid level.
    let m = MortonCode::from_nds_coordinates(0, 0);
    assert!(PackedTileId::from_morton_and_level(m, 16).is_err());
}

#[test]
fn dimensions_and_displays() {
    let tile = PackedTileId::from_tile_index(12345, 13).unwrap();
    let (w, h) = tile.dimensions_in_meters();
    assert!(w > 0.0 && h > 0.0);

    assert!(format!("{tile}").contains("PackedTileId(value="));
    assert!(format!("{}", MortonCode::from_nds_coordinates(1, 1)).contains("MortonCode(value="));
}

#[test]
fn bbox_from_multiple_tiles() {
    // Empty -> None.
    assert!(bounding_box_from_tile_ids(&[]).is_none());

    // Several tiles -> the min/max-update loop runs over all four directions.
    let tiles = ndslive_math::get_tile_ids_for_bounding_box(0, 0, 1 << 28, 1 << 28, 3);
    assert!(tiles.len() >= 3);
    let mid = tiles[tiles.len() / 2];
    let ordered = [mid, tiles[0], tiles[tiles.len() - 1]];
    let (min_x, min_y, max_x, max_y) = bounding_box_from_tile_ids(&ordered).unwrap();
    assert!(min_x <= max_x && min_y <= max_y);
}

#[test]
fn wgs84_normalization_wrap_and_eq() {
    // Longitudes that need wrapping into [-180, 180) exercise both wrap branches.
    assert!((Wgs84::new(270.0, 0.0).lon - (-90.0)).abs() < 1e-9);
    assert!((Wgs84::new(-270.0, 0.0).lon - 90.0).abs() < 1e-9);

    // PartialEq (and the is_close_rel helper) + Display.
    let a = Wgs84::new(13.404954, 52.520008);
    let b = Wgs84::new(13.404954, 52.520008);
    assert_eq!(a, b);
    assert_ne!(a, Wgs84::new(11.585, 48.137));
    assert!(format!("{a}").contains("Wgs84(lon="));
}

#[test]
fn morton_out_of_range_y_wraps() {
    // y outside [-2^30, 2^30) exercises the wrap loops (x is full i32, so its
    // wrap loops are unreachable from this API).
    let (_, y) = MortonCode::from_nds_coordinates(0, 1 << 30).to_nds_coordinates();
    assert_eq!(y, -(1 << 30));
    let _ = MortonCode::from_nds_coordinates(0, -(1 << 30) - 1).to_nds_coordinates();
}