Some checks failed
nigig-map / test (push) Has been cancelled
- Increased MAX_ELEMENTS_PER_TILE from 100,000 to 250,000 to handle Kenya MBTiles that contain 101k-140k elements per tile - Updated security limit test to use valid JSON with 260k elements - Added .forgejo/workflows/nigig-map.yml CI workflow to catch map-related regressions in tile parsing, style compilation, and tessellation Fixes runtime errors: - 'failed to triangulate local mbtile: too many elements (N > 100000)' - Tiles with 101k-140k elements now process successfully
367 lines
11 KiB
Rust
367 lines
11 KiB
Rust
//! Integration tests for the tile decoding pipeline
|
|
//!
|
|
//! These tests verify the end-to-end functionality of tile decoding,
|
|
//! from raw MVT/JSON data to renderable tile buffers.
|
|
|
|
use nigig_map::tile_decode::{
|
|
build_tile_buffers_from_body, mbtiles_tile_to_overpass_response,
|
|
};
|
|
use nigig_map::geometry::TileKey;
|
|
use nigig_map::style::CompiledMapTheme;
|
|
|
|
/// Test decoding a simple Overpass JSON response with nodes and ways
|
|
#[test]
|
|
fn test_decode_simple_overpass_response() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "node",
|
|
"id": 1,
|
|
"lat": 52.3676,
|
|
"lon": 4.9041,
|
|
"tags": {
|
|
"name": "Amsterdam Central"
|
|
}
|
|
},
|
|
{
|
|
"type": "way",
|
|
"id": 2,
|
|
"nodes": [1, 3, 4, 1],
|
|
"tags": {
|
|
"building": "yes",
|
|
"name": "Test Building"
|
|
}
|
|
},
|
|
{
|
|
"type": "node",
|
|
"id": 3,
|
|
"lat": 52.3680,
|
|
"lon": 4.9045
|
|
},
|
|
{
|
|
"type": "node",
|
|
"id": 4,
|
|
"lat": 52.3678,
|
|
"lon": 4.9048
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 8536, y: 5534 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Failed to decode: {:?}", result.err());
|
|
|
|
let buffers = result.unwrap();
|
|
assert!(buffers.feature_count > 0, "Should have decoded at least one feature");
|
|
assert!(!buffers.fill_vertices.is_empty() || !buffers.stroke_vertices.is_empty(),
|
|
"Should have generated geometry");
|
|
}
|
|
|
|
/// Test decoding with security limits (should reject oversized input)
|
|
#[test]
|
|
fn test_security_limit_json_size() {
|
|
// Create a JSON that exceeds the element count limit (250,000).
|
|
// Build valid JSON with comma-separated elements.
|
|
let mut elements = String::new();
|
|
for i in 0..260_000 {
|
|
if i > 0 { elements.push(','); }
|
|
elements.push_str(&format!(
|
|
r#"{{"type":"node","id":{},"lat":0.0,"lon":0.0}}"#,
|
|
i
|
|
));
|
|
}
|
|
let large_json = format!(r#"{{"elements":[{}]}}"#, elements);
|
|
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, &large_json, &theme);
|
|
assert!(result.is_err(), "Should reject oversized JSON");
|
|
assert!(result.unwrap_err().contains("too large") ||
|
|
result.unwrap_err().contains("too many"),
|
|
"Error should mention size limit");
|
|
}
|
|
|
|
/// Test decoding with invalid JSON
|
|
#[test]
|
|
fn test_decode_invalid_json() {
|
|
let invalid_json = r#"{"version": 0.6, "elements": [invalid}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, invalid_json, &theme);
|
|
assert!(result.is_err(), "Should reject invalid JSON");
|
|
}
|
|
|
|
/// Test decoding empty elements array
|
|
#[test]
|
|
fn test_decode_empty_elements() {
|
|
let json = r#"{"version": 0.6, "elements": []}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Should handle empty elements");
|
|
|
|
let buffers = result.unwrap();
|
|
assert_eq!(buffers.feature_count, 0, "Should have no features");
|
|
}
|
|
|
|
/// Test decoding various OSM feature types
|
|
#[test]
|
|
fn test_decode_various_feature_types() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "node",
|
|
"id": 1,
|
|
"lat": 52.3676,
|
|
"lon": 4.9041,
|
|
"tags": {"amenity": "restaurant", "name": "Test Restaurant"}
|
|
},
|
|
{
|
|
"type": "way",
|
|
"id": 2,
|
|
"nodes": [10, 11, 12, 10],
|
|
"tags": {"highway": "residential", "name": "Test Street"}
|
|
},
|
|
{
|
|
"type": "way",
|
|
"id": 3,
|
|
"nodes": [20, 21, 22, 23, 20],
|
|
"tags": {"natural": "water", "name": "Test Lake"}
|
|
},
|
|
{
|
|
"type": "node", "id": 10, "lat": 52.367, "lon": 4.904
|
|
},
|
|
{
|
|
"type": "node", "id": 11, "lat": 52.368, "lon": 4.905
|
|
},
|
|
{
|
|
"type": "node", "id": 12, "lat": 52.367, "lon": 4.905
|
|
},
|
|
{
|
|
"type": "node", "id": 20, "lat": 52.369, "lon": 4.906
|
|
},
|
|
{
|
|
"type": "node", "id": 21, "lat": 52.370, "lon": 4.907
|
|
},
|
|
{
|
|
"type": "node", "id": 22, "lat": 52.369, "lon": 4.908
|
|
},
|
|
{
|
|
"type": "node", "id": 23, "lat": 52.368, "lon": 4.907
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 8536, y: 5534 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Failed to decode: {:?}", result.err());
|
|
|
|
let buffers = result.unwrap();
|
|
assert!(buffers.feature_count >= 3, "Should decode restaurant, street, and lake");
|
|
|
|
// Should have POIs (restaurant)
|
|
assert!(!buffers.pois.is_empty(), "Should have POIs");
|
|
|
|
// Should have labels
|
|
assert!(!buffers.labels.is_empty(), "Should have labels");
|
|
}
|
|
|
|
/// Test decoding with different zoom levels
|
|
#[test]
|
|
fn test_decode_different_zoom_levels() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "way",
|
|
"id": 1,
|
|
"nodes": [10, 11, 12, 10],
|
|
"tags": {"building": "yes"}
|
|
},
|
|
{
|
|
"type": "node", "id": 10, "lat": 52.367, "lon": 4.904
|
|
},
|
|
{
|
|
"type": "node", "id": 11, "lat": 52.368, "lon": 4.905
|
|
},
|
|
{
|
|
"type": "node", "id": 12, "lat": 52.367, "lon": 4.905
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
// Test at different zoom levels
|
|
for zoom in [10, 12, 14, 16] {
|
|
let tile_key = TileKey { z: zoom, x: 0, y: 0 };
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Failed at zoom {}: {:?}", zoom, result.err());
|
|
}
|
|
}
|
|
|
|
/// Test that malformed way references are handled gracefully
|
|
#[test]
|
|
fn test_decode_malformed_way_references() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "way",
|
|
"id": 1,
|
|
"nodes": [999, 1000, 1001],
|
|
"tags": {"highway": "residential"}
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Should handle missing nodes gracefully");
|
|
|
|
let buffers = result.unwrap();
|
|
// Way should be skipped due to missing nodes
|
|
assert_eq!(buffers.feature_count, 0, "Should skip malformed way");
|
|
}
|
|
|
|
/// Test decoding with Unicode characters in tags
|
|
#[test]
|
|
fn test_decode_unicode_tags() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "node",
|
|
"id": 1,
|
|
"lat": 52.3676,
|
|
"lon": 4.9041,
|
|
"tags": {
|
|
"name": "阿姆斯特丹",
|
|
"name:ja": "アムステルダム",
|
|
"name:ar": "أمستردام"
|
|
}
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Should handle Unicode tags");
|
|
}
|
|
|
|
/// Test MBTiles to Overpass conversion
|
|
#[test]
|
|
fn test_mbtiles_to_overpass_conversion() {
|
|
// This test would require actual MVT binary data
|
|
// For now, we'll skip it or use a mock
|
|
// In a real implementation, we'd load a test MVT file
|
|
|
|
// Placeholder test
|
|
let tile_key = TileKey { z: 14, x: 0, y: 0 };
|
|
let mock_mvt_data = vec![]; // Would be actual MVT binary
|
|
|
|
if !mock_mvt_data.is_empty() {
|
|
let result = mbtiles_tile_to_overpass_response(tile_key, &mock_mvt_data);
|
|
assert!(result.is_ok(), "Should convert MVT to Overpass format");
|
|
}
|
|
}
|
|
|
|
/// Test that geometry is correctly tessellated
|
|
#[test]
|
|
fn test_geometry_tessellation() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "way",
|
|
"id": 1,
|
|
"nodes": [10, 11, 12, 13, 10],
|
|
"tags": {"building": "yes"}
|
|
},
|
|
{
|
|
"type": "node", "id": 10, "lat": 52.367, "lon": 4.904
|
|
},
|
|
{
|
|
"type": "node", "id": 11, "lat": 52.368, "lon": 4.904
|
|
},
|
|
{
|
|
"type": "node", "id": 12, "lat": 52.368, "lon": 4.905
|
|
},
|
|
{
|
|
"type": "node", "id": 13, "lat": 52.367, "lon": 4.905
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 8536, y: 5534 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Failed to decode: {:?}", result.err());
|
|
|
|
let buffers = result.unwrap();
|
|
|
|
// Building should generate fill geometry
|
|
assert!(!buffers.fill_vertices.is_empty(), "Should have fill vertices");
|
|
assert!(!buffers.fill_indices.is_empty(), "Should have fill indices");
|
|
|
|
// Vertices should be in groups of 7 (x, y, color, nx, ny, shape_id, zbias)
|
|
assert_eq!(buffers.fill_vertices.len() % 7, 0,
|
|
"Fill vertices should be properly formatted");
|
|
}
|
|
|
|
/// Test stroke geometry generation
|
|
#[test]
|
|
fn test_stroke_geometry_generation() {
|
|
let json = r#"{
|
|
"version": 0.6,
|
|
"elements": [
|
|
{
|
|
"type": "way",
|
|
"id": 1,
|
|
"nodes": [10, 11, 12],
|
|
"tags": {"highway": "primary"}
|
|
},
|
|
{
|
|
"type": "node", "id": 10, "lat": 52.367, "lon": 4.904
|
|
},
|
|
{
|
|
"type": "node", "id": 11, "lat": 52.368, "lon": 4.905
|
|
},
|
|
{
|
|
"type": "node", "id": 12, "lat": 52.369, "lon": 4.906
|
|
}
|
|
]
|
|
}"#;
|
|
|
|
let tile_key = TileKey { z: 14, x: 8536, y: 5534 };
|
|
let theme = CompiledMapTheme::default();
|
|
|
|
let result = build_tile_buffers_from_body(tile_key, json, &theme);
|
|
assert!(result.is_ok(), "Failed to decode: {:?}", result.err());
|
|
|
|
let buffers = result.unwrap();
|
|
|
|
// Highway should generate stroke geometry
|
|
assert!(!buffers.stroke_vertices.is_empty(), "Should have stroke vertices");
|
|
assert!(!buffers.stroke_indices.is_empty(), "Should have stroke indices");
|
|
|
|
// Vertices should be in groups of 7
|
|
assert_eq!(buffers.stroke_vertices.len() % 7, 0,
|
|
"Stroke vertices should be properly formatted");
|
|
}
|