mapradar 0.4.1

Turn addresses into coordinates and find nearby amenities using Google Maps API
Documentation
use clap::{Parser, Subcommand};
use colored::*;
use mapradar::client::MapradarClient;
use mapradar::models::{SearchQuery, ServiceType, TravelParameters};
use std::process;

#[derive(Parser)]
#[command(name = "mapradar")]
#[command(about = "CLI for Mapradar Location Intelligence", long_about = None)]
struct Cli {
    #[arg(short, long, env = "MAPRADAR_API_KEY")]
    api_key: String,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Geocode an address to coordinates
    Geocode { address: String },

    /// Reverse geocode coordinates to an address
    Reverse { latitude: f64, longitude: f64 },

    /// Find nearby amenities
    Nearby {
        #[arg(short, long, alias = "addr")]
        address: Option<String>,

        #[arg(long, alias = "lat")]
        latitude: Option<f64>,

        #[arg(long, alias = "lng", alias = "lon")]
        longitude: Option<f64>,

        /// Radius in meters (default 1000)
        #[arg(short, long, default_value_t = 1000.0)]
        radius: f64,

        /// Type of amenity (bank, hospital, school, etc.)
        #[arg(short, long, default_value = "bank")]
        r#type: String,

        /// Maximum number of results to return per service
        #[arg(short, long, alias = "limit", default_value_t = 10)]
        max_results: usize,
    },

    /// Calculate travel distance between two points
    Distance {
        #[arg(long, help = "Origin address")]
        origin_addr: Option<String>,

        #[arg(long, help = "Origin latitude")]
        origin_lat: Option<f64>,

        #[arg(long, help = "Origin longitude")]
        origin_lng: Option<f64>,

        #[arg(long, help = "Destination address")]
        dest_addr: Option<String>,

        #[arg(long, help = "Destination latitude")]
        dest_lat: Option<f64>,

        #[arg(long, help = "Destination longitude")]
        dest_lng: Option<f64>,

        #[arg(
            long,
            help = "Mode of travel (drive, walk, bicycle, motorcycle, transit)",
            default_value = "drive"
        )]
        mode: String,
    },
}

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let cli = Cli::parse();
    let client = MapradarClient::new(cli.api_key);

    match cli.command {
        Commands::Geocode { address } => match client.geocode_async(&address).await {
            Ok(loc) => println!("{}", serde_json::to_string_pretty(&loc).unwrap()),
            Err(err) => {
                eprintln!("{} {}", "Error:".red().bold(), err);
                process::exit(1);
            }
        },
        Commands::Reverse {
            latitude,
            longitude,
        } => match client.reverse_geocode_async(latitude, longitude).await {
            Ok(address) => println!("{}", serde_json::to_string_pretty(&address).unwrap()),
            Err(err) => {
                eprintln!("{} {}", "Error:".red().bold(), err);
                process::exit(1);
            }
        },
        Commands::Nearby {
            address,
            latitude,
            longitude,
            radius,
            r#type,
            max_results,
        } => {
            let service_types = r#type
                .split(",")
                .map(|type_str| match type_str.trim() {
                    "bank" => ServiceType::Bank,
                    "hospital" => ServiceType::Hospital,
                    "school" => ServiceType::School,
                    "restaurant" => ServiceType::Restaurant,
                    "bus-stop" => ServiceType::BusStop,
                    "market" => ServiceType::Market,
                    "mall" => ServiceType::Mall,
                    "fuel-station" => ServiceType::FuelStation,
                    "train-station" => ServiceType::TrainStation,
                    "taxi-stand" => ServiceType::TaxiStand,
                    "landmark" => ServiceType::Landmark,
                    "pharmacy" => ServiceType::Pharmacy,
                    _ => ServiceType::Landmark, // Default fallback
                })
                .collect::<Vec<ServiceType>>();

            let query = if let Some(latitude_val) = latitude {
                if let Some(longitude_val) = longitude {
                    SearchQuery::from_coordinates(latitude_val, longitude_val)
                } else {
                    eprintln!(
                        "{} Longitude is required when latitude is provided",
                        "Error:".red().bold()
                    );
                    process::exit(1);
                }
            } else {
                if let Some(address_val) = address {
                    SearchQuery::from_address(address_val)
                } else {
                    eprintln!(
                        "{} Either address or coordinates must be provided",
                        "Error:".red().bold()
                    );
                    process::exit(1);
                }
            };

            match client
                .fetch_intelligence_async(query, service_types, radius / 1000.0, max_results)
                .await
            {
                Ok(intel) => println!("{}", serde_json::to_string_pretty(&intel).unwrap()),
                Err(err) => {
                    eprintln!("{} {}", "Error:".red().bold(), err);
                    process::exit(1);
                }
            }
        }
        Commands::Distance {
            origin_addr,
            origin_lat,
            origin_lng,
            dest_addr,
            dest_lat,
            dest_lng,
            mode,
        } => {
            let api_mode = match mode.to_lowercase().as_str() {
                "walk" | "walking" => "WALK",
                "bicycle" => "BICYCLE",
                "motorcycle" | "bike" | "okada" | "keke" => "TWO_WHEELER",
                "transit" | "train" | "bus" | "brt" | "danfo" => "TRANSIT",
                _ => "DRIVE",
            };

            let params = TravelParameters {
                origin_latitude: origin_lat,
                origin_longitude: origin_lng,
                origin_address: origin_addr,
                destination_latitude: dest_lat,
                destination_longitude: dest_lng,
                destination_address: dest_addr,
                travel_mode: Some(api_mode.to_string()),
            };

            match client.calculate_travel_distance_async(params).await {
                Ok(dist) => println!("{} {:.2} km", "Distance:".green().bold(), dist),
                Err(err) => {
                    eprintln!("{} {}", "Error:".red().bold(), err);
                    process::exit(1);
                }
            }
        }
    }
}