1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// TODO This doesn't really belong in gameplay/freeform

use anyhow::Result;
use serde::Deserialize;

use abstutil::Timer;
use map_gui::tools::{find_exe, FilePicker, RunCommand};
use map_model::Map;
use synthpop::{ExternalPerson, Scenario};
use widgetry::tools::PopupMsg;
use widgetry::EventCtx;

use crate::app::Transition;
use crate::sandbox::gameplay::GameplayMode;
use crate::sandbox::SandboxMode;

pub fn import_grid2demand(ctx: &mut EventCtx) -> Transition {
    Transition::Push(FilePicker::new_state(
        ctx,
        None,
        Box::new(|ctx, app, maybe_path| {
            if let Ok(Some(path)) = maybe_path {
                Transition::Replace(RunCommand::new_state(
                    ctx,
                    true,
                    vec![
                        find_exe("cli"),
                        "import-grid2-demand".to_string(),
                        format!("--map={}", app.primary.map.get_name().path()),
                        format!("--input={}", path),
                    ],
                    Box::new(|_, app, success, _| {
                        if success {
                            // Clear out the cached scenario. If we repeatedly use this import, the
                            // scenario name is always the same, but the file is changing.
                            app.primary.scenario = None;
                            Transition::Replace(SandboxMode::simple_new(
                                app,
                                GameplayMode::PlayScenario(
                                    app.primary.map.get_name().clone(),
                                    "grid2demand".to_string(),
                                    Vec::new(),
                                ),
                            ))
                        } else {
                            // The popup already explained the failure
                            Transition::Keep
                        }
                    }),
                ))
            } else {
                // The user didn't pick a file, so stay on the scenario picker
                Transition::Pop
            }
        }),
    ))
}

pub fn import_json(ctx: &mut EventCtx) -> Transition {
    Transition::Push(FilePicker::new_state(
        ctx,
        None,
        Box::new(|ctx, app, maybe_path| {
            if let Ok(Some(path)) = maybe_path {
                let result = ctx.loading_screen("import JSON scenario", |_, timer| {
                    import_json_scenario(&app.primary.map, path, timer)
                });
                match result {
                    Ok(scenario_name) => {
                        // Clear out the cached scenario. If we repeatedly use this import, the
                        // scenario name is always the same, but the file is changing.
                        app.primary.scenario = None;
                        Transition::Replace(SandboxMode::simple_new(
                            app,
                            GameplayMode::PlayScenario(
                                app.primary.map.get_name().clone(),
                                scenario_name,
                                Vec::new(),
                            ),
                        ))
                    }
                    Err(err) => Transition::Replace(PopupMsg::new_state(
                        ctx,
                        "Error",
                        vec![err.to_string()],
                    )),
                }
            } else {
                // The user didn't pick a file, so stay on the scenario picker
                Transition::Pop
            }
        }),
    ))
}

// This works the same as importer/src/bin/import_traffic.rs. We should decide how to share
// behavior between UI and CLI tools.
fn import_json_scenario(map: &Map, input: String, timer: &mut Timer) -> Result<String> {
    let skip_problems = true;
    let input: Input = abstio::maybe_read_json(input, timer)?;

    let mut s = Scenario::empty(map, &input.scenario_name);
    // Include all buses/trains
    s.only_seed_buses = None;
    s.people = ExternalPerson::import(map, input.people, skip_problems)?;
    // Always clean up people with no-op trips (going between the same buildings)
    s = s.remove_weird_schedules(true);
    s.save();
    Ok(s.scenario_name)
}

#[derive(Deserialize)]
struct Input {
    scenario_name: String,
    people: Vec<ExternalPerson>,
}