OSM Object Problem Scanner
- Rust 100%
OOPS — OSM Object Problem Scanner
A modular Rust toolkit for analyzing OpenStreetMap data quality issues with extensible analyzers and spatial indexing.
Features
- Command Line Interface: Full-featured CLI with multiple output formats
- Data Ingestion: Overpass API queries and PBF file parsing
- Spatial Analysis: Fast intersection detection and spatial queries using the geo ecosystem
- Plugin Architecture: Extensible analyzer system with registry
- Real Data Validation: Integration tested with Salt Lake City OSM data
- Multiple Output Formats: JSON, CSV, and human-readable summary formats
- Streaming Parsers: Memory-efficient processing of large datasets
Quick Start
Installation
git clone https://github.com/mvexel/oops
cd oops
cargo build --release
Command Line Usage
# List available analyzers
cargo run --bin oops-cli -- list
# Analyze a PBF file
cargo run --bin oops-cli -- analyze --file data.osm.pbf --verbose
# Different output formats
cargo run --bin oops-cli -- analyze --file data.osm.pbf --format summary
cargo run --bin oops-cli -- analyze --file data.osm.pbf --format csv
cargo run --bin oops-cli -- analyze --file data.osm.pbf --format json
# Run specific analyzer
cargo run --bin oops-cli -- analyze --file data.osm.pbf --analyzer missing_maxspeed
Basic Usage
Analyze a PBF file
useoops_ingest::{PbfReader,PbfFilter};#[tokio::main]asyncfn main()-> Result<(),Box<dynstd::error::Error>>{letreader=PbfReader::new();letfilter=PbfFilter::new().nodes_only().with_tag("amenity",Some("restaurant")).limit(100);letosm_data=reader.read_file("data.osm.pbf",Some(filter)).await?;println!("Found {} restaurants",osm_data.nodes.len());Ok(())}Query Overpass API
useoops_ingest::{OverpassClient,BoundingBox};#[tokio::main]asyncfn main()-> Result<(),Box<dynstd::error::Error>>{letclient=OverpassClient::new();letbbox=BoundingBox::new(-111.9,40.7,-111.8,40.8);// Salt Lake City area
letosm_data=client.fetch_in_bbox(&bbox,r#"node["amenity"="restaurant"]"#).await?;println!("Found {} restaurants in bbox",osm_data.nodes.len());Ok(())}Custom Analysis
useoops_core::{Analyzer,AnalysisQuery,AnalysisResult,BoundingBox};struct RestaurantAnalyzer;implAnalyzerforRestaurantAnalyzer{fn name(&self)-> &str {"restaurant_analyzer"}fn analyze(&self,query: &AnalysisQuery,data: &OsmData)-> Vec<AnalysisResult>{letmutresults=Vec::new();fornodeindata.nodes_in_bbox(&query.area){ifletSome("restaurant")=node.tags.get("amenity").map(|s|s.as_str()){letmutresult=AnalysisResult::new(node.id,ElementType::Node);if!node.tags.contains_key("opening_hours"){result.add_finding(Finding{category: ProblemCategory::Missing,severity: Severity::Medium,message: "Missing opening hours".into(),recommendation: Some("Add opening_hours tag".into()),context: HashMap::new(),});}results.push(result);}}results}}Architecture
oops/
├── oops-core/ # Core data structures and analysis framework
├── oops-ingest/ # Data ingestion (Overpass API, PBF files)
├── oops-analyzers/ # Built-in quality analyzers
└── oops-cli/ # Command line interface
Core Concepts
- OsmData: Spatially-indexed container for OSM elements
- Analyzer: Trait for implementing quality checks
- AnalysisQuery: Spatial query with context and filters
- AnalysisResult: Generic result format with severity levels and findings
Sample Data
The repository includes Salt Lake City sample data in sample_data/:
slc_small.osm.pbf- Small test dataset (~664KB)slc_large.osm.pbf- Larger dataset (~5.8MB)
Development
Running Tests
# Unit tests
cargo test
# Integration tests with sample data
cargo test -- --ignored
# Specific test with output
cargo test test_pbf_reader_slc_small -- --ignored --nocapture
Project Structure
- oops-core: Fundamental data types (
OsmNode,OsmWay,OsmRelation,OsmData) + analyzer framework - oops-ingest: Data loading from Overpass API and PBF files
- oops-analyzers: Quality analysis implementations using composable architecture
- oops-cli: Command-line interface with multiple output formats
Plugin System
OOPS has a plugin system so you can develop your own custom analyzers:
Creating Custom Analyzers
useoops_core::analysis::*;// Create a composable analyzer for missing opening hours on restaurants
pubfn build_restaurant_opening_hours_analyzer()-> Analyzer{Analyzer::new("restaurant_opening_hours","1.0.0","Finds restaurants missing opening hours",// Select restaurant nodes
Box::new(NodeSelector::new().has_tag("amenity","restaurant")),// No additional context needed for this simple analyzer
Box::new(NoContextProvider::new()),// Detect missing opening_hours tag
Box::new(MissingTagDetector::new("opening_hours").with_severity(Severity::Medium).with_message_template("Restaurant missing opening hours").with_recommendation_template("Add opening_hours tag to indicate when the restaurant is open")))}Using the Plugin Registry
useoops_core::AnalyzerRegistry;// Create registry and register analyzers
letmutregistry=AnalyzerRegistry::new();registry.register(MyAnalyzer);registry.register(RestaurantAnalyzer);registry.register(AccessibilityAnalyzer);// List available analyzers
println!("Available analyzers: {:?}",registry.list());// Get analyzer info
ifletSome(info)=registry.get_info("my_analyzer"){println!("{} v{}: {}",info.name,info.version,info.description);}// Run specific analyzer
letresults=registry.analyze("my_analyzer",&query,&data)?;// Run all analyzers
letall_results=registry.analyze_all(&query,&data);Built-in Analyzers
The oops-analyzers crate provides several built-in analyzers to identify common data quality issues.
missing_maxspeed
- Purpose: Identifies major roads that are missing a
maxspeedtag. - How it works:
- Selector: It selects OSM
wayelements that are tagged withhighwayvalues such asmotorway,trunk,primary,secondary, ortertiary. - Detector: For each selected way, it checks for the existence of the
maxspeedtag. - Finding: If a
maxspeedtag is not present, it generates a finding, flagging the way as incomplete.
- Selector: It selects OSM
pedestrian_completeness
- Purpose: Analyzes the coverage of sidewalks along roads to assess pedestrian infrastructure completeness.
- How it works:
- Selector: It selects OSM
wayelements representing roads where pedestrians are common (e.g.,highway=residential,highway=tertiary). - Context Provider: It uses a spatial context provider to find nearby OSM ways within a certain buffer distance of the selected road.
- Detector: It analyzes the nearby ways to determine if they are sidewalks (
highway=footwayandfootway=sidewalk) and assesses whether the road has adequate sidewalk coverage. - Finding: If sidewalk coverage is insufficient, it generates a finding.
- Selector: It selects OSM
Plugin Examples
See oops-core/examples/plugin_system_demo.rs for complete examples of the composable architecture with:
- Missing maxspeed analyzer: Uses generic components with custom detector
- Pedestrian completeness analyzer: Uses spatial context for sidewalk analysis
Run the demo:
cd oops-core && cargo run --example plugin_system_demo
Testing with Sample Data
The project includes real OpenStreetMap data for testing:
# Test PBF parsing
cargo test test_pbf_reader_slc_small -- --ignored --nocapture
# Test streaming parsing
cargo test test_pbf_streaming_slc -- --ignored --nocapture
# Test Overpass API (requires internet)
cargo test test_fetch_restaurants_integration -- --ignored --nocapture
# Run analyzer registry integration
cargo test test_analyzer_registry_integration
Contributing
- Follow existing code patterns and conventions
- Add tests for new functionality
- Use TDD approach when possible
- Document public APIs
- Test with sample data before submitting
License
[Add your license here]
Dependencies
geo- Geospatial operationsrstar- Spatial indexingosmpbf- PBF file parsingreqwest- HTTP client for Overpass APIserde- Serializationtokio- Async runtime