Add CLI to demo (#160)

* Introduce a CLI

* Fix headless rendering
This commit is contained in:
Max Ammann 2022-09-08 11:12:22 +02:00 committed by GitHub
parent 0b9404a518
commit 1934555013
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 184 additions and 114 deletions

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run demo (debug+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --features trace" />
<configuration default="false" name="Run headed demo (debug+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run demo (release+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --release --features trace" />
<configuration default="false" name="Run headed demo (release+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --release --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />

View File

@ -23,3 +23,5 @@ tracing = "0.1.35"
tracing-subscriber = { version = "0.3.14", optional = true }
tracing-tracy = { version = "0.8", optional = true }
tracy-client = { version = "0.12.7", optional = true }
clap = { version = "3.2.12", features = ["derive"] }

View File

@ -0,0 +1,16 @@
use maplibre::{
platform::{http_client::ReqwestHttpClient, schedule_method::TokioScheduleMethod},
MapBuilder,
};
use maplibre_winit::winit::WinitMapWindowConfig;
pub async fn run_headed() {
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.build()
.initialize()
.await
.run()
}

View File

@ -0,0 +1,55 @@
use maplibre::{
coords::{LatLon, WorldTileCoords},
error::Error,
headless::HeadlessMapWindowConfig,
platform::{http_client::ReqwestHttpClient, schedule_method::TokioScheduleMethod},
render::settings::{RendererSettings, TextureFormat},
util::grid::google_mercator,
window::WindowSize,
MapBuilder,
};
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
let mut map = MapBuilder::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(tile_size, tile_size).unwrap(),
})
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
let tile_limits = google_mercator().tile_limits(
extent_wgs84_to_merc(&Extent {
minx: min.longitude,
miny: min.latitude,
maxx: max.longitude,
maxy: max.latitude,
}),
0,
);
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
println!("Rendering {}", &coords);
map.map_schedule
.fetch_process(&coords)
.await
.expect("Failed to fetch and process!");
match map.map_schedule_mut().update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {}
}
e => eprintln!("{:?}", e),
};
}
}

View File

@ -1,31 +1,12 @@
use std::collections::HashSet;
use std::io::ErrorKind;
use maplibre::{
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
coords::{TileCoords, ViewRegion, WorldTileCoords, ZoomLevel},
error::Error,
headless::{utils::HeadlessPipelineProcessor, HeadlessMapWindowConfig},
io::{
pipeline::{PipelineContext, PipelineProcessor, Processable},
source_client::{HttpClient, HttpSourceClient},
tile_pipelines::build_vector_tile_pipeline,
tile_repository::StoredLayer,
RawLayer, TileRequest,
},
platform::{
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod,
},
render::{
settings::{RendererSettings, TextureFormat},
ShaderVertex,
},
style::source::TileAddressingScheme,
util::{grid::google_mercator, math::Aabb2},
window::{EventLoop, WindowSize},
MapBuilder,
};
use maplibre_winit::winit::WinitMapWindowConfig;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
use clap::{builder::ValueParser, Parser, Subcommand};
use maplibre::{coords::LatLon, platform::run_multithreaded};
use crate::{headed::run_headed, headless::run_headless};
mod headed;
mod headless;
#[cfg(feature = "trace")]
fn enable_tracing() {
@ -36,63 +17,46 @@ fn enable_tracing() {
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}
fn run_in_window() {
run_multithreaded(async {
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.build()
.initialize()
.await
.run()
})
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
fn run_headless() {
run_multithreaded(async {
let mut map = MapBuilder::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(1000, 1000).unwrap(),
})
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
let tile_limits = google_mercator().tile_limits(
extent_wgs84_to_merc(&Extent {
minx: 11.3475219363,
miny: 48.0345697188,
maxx: 11.7917815798,
maxy: 48.255861,
}),
0,
);
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
println!("Rendering {}", &coords);
map.map_schedule
.fetch_process(&coords)
.await
.expect("Failed to fetch and process!");
match map.map_schedule_mut().update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {}
fn parse_lat_long(env: &str) -> Result<LatLon, std::io::Error> {
let split = env.split(',').collect::<Vec<_>>();
if let (Some(latitude), Some(longitude)) = (split.get(0), split.get(1)) {
Ok(LatLon::new(
latitude.parse::<f64>().unwrap(),
longitude.parse::<f64>().unwrap(),
))
} else {
Err(std::io::Error::new(
ErrorKind::InvalidData,
"Failed to parse latitude and longitude.",
))
}
e => eprintln!("{:?}", e),
};
}
})
}
#[derive(Subcommand)]
enum Commands {
Headed {},
Headless {
#[clap(default_value_t = 400)]
tile_size: u32,
#[clap(
value_parser = ValueParser::new(parse_lat_long),
default_value_t = LatLon::new(48.0345697188, 11.3475219363)
)]
min: LatLon,
#[clap(
value_parser = ValueParser::new(parse_lat_long),
default_value_t = LatLon::new(48.255861, 11.7917815798)
)]
max: LatLon,
},
}
fn main() {
@ -101,6 +65,20 @@ fn main() {
#[cfg(feature = "trace")]
enable_tracing();
//run_headless();
run_in_window();
let cli = Cli::parse();
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Commands::Headed {} => {
run_multithreaded(async { run_headed().await });
}
Commands::Headless {
tile_size,
min,
max,
} => {
run_multithreaded(async { run_headless(*tile_size, *min, *max).await });
}
}
}

View File

@ -20,15 +20,14 @@ pub struct ViewState {
impl ViewState {
pub fn new<P: Into<cgmath::Rad<f64>>>(
window_size: &WindowSize,
position: WorldCoords,
zoom: Zoom,
center: LatLon,
pitch: f64,
fovy: P,
) -> Self {
let tile_center = TILE_SIZE / 2.0;
let fovy = fovy.into();
let height = tile_center / (fovy / 2.0).tan();
let position = WorldCoords::from_lat_lon(center, zoom);
let camera = Camera::new(
(position.x, position.y, height),

View File

@ -1,6 +1,10 @@
//! Provides utilities related to coordinates.
use std::{f64::consts::PI, fmt};
use std::{
f64::consts::PI,
fmt,
fmt::{Display, Formatter},
};
use cgmath::{num_traits::Pow, AbsDiffEq, Matrix4, Point3, Vector3};
@ -112,17 +116,32 @@ impl Into<u8> for ZoomLevel {
}
#[derive(Copy, Clone, Debug)]
pub struct LatLon(f64, f64);
pub struct LatLon {
pub latitude: f64,
pub longitude: f64,
}
impl LatLon {
pub fn new(latitude: f64, longitude: f64) -> Self {
LatLon(latitude, longitude)
LatLon {
latitude,
longitude,
}
}
}
impl Default for LatLon {
fn default() -> Self {
LatLon(0.0, 0.0)
LatLon {
latitude: 0.0,
longitude: 0.0,
}
}
}
impl Display for LatLon {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{},{}", self.latitude, self.longitude)
}
}
@ -477,10 +496,10 @@ impl WorldCoords {
pub fn from_lat_lon(lat_lon: LatLon, zoom: Zoom) -> WorldCoords {
let tile_size = TILE_SIZE * 2.0_f64.powf(zoom.0);
// Get x value
let x = (lat_lon.1 + 180.0) * (tile_size / 360.0);
let x = (lat_lon.longitude + 180.0) * (tile_size / 360.0);
// Convert from degrees to radians
let lat_rad = (lat_lon.0 * PI) / 180.0;
let lat_rad = (lat_lon.latitude * PI) / 180.0;
// get y value
let merc_n = f64::ln(f64::tan((PI / 4.0) + (lat_rad / 2.0)));

View File

@ -13,7 +13,7 @@ use wgpu::{BufferAsyncError, BufferSlice};
use crate::{
context::{MapContext, ViewState},
coords::{LatLon, ViewRegion, WorldTileCoords, Zoom},
coords::{LatLon, ViewRegion, WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
error::Error,
headless::utils::HeadlessPipelineProcessor,
io::{
@ -114,12 +114,9 @@ where
) -> Self {
let view_state = ViewState::new(
&window_size,
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
style
.center
.map(|center| LatLon::new(center[0], center[1]))
.unwrap_or_default(),
style.pitch.unwrap_or_default(),
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
Zoom::default(),
0.0,
cgmath::Deg(110.0),
);
let tile_repository = TileRepository::new();
@ -210,6 +207,8 @@ where
pool.clear();
}
self.map_context.tile_repository.clear();
while let Some(layer) = processor.layers.pop() {
self.map_context
.tile_repository

View File

@ -67,6 +67,10 @@ impl TileRepository {
}
}
pub fn clear(&mut self) {
self.tree.clear();
}
/// Inserts a tessellated layer into the quad tree at its world tile coords.
/// If the space is vacant, the tessellated layer is inserted into a new
/// [crate::io::tile_repository::CachedTile].

View File

@ -2,7 +2,7 @@ use std::{marker::PhantomData, mem};
use crate::{
context::{MapContext, ViewState},
coords::{LatLon, Zoom},
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
error::Error,
io::{
scheduler::Scheduler,
@ -52,16 +52,14 @@ where
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
) -> Self {
let view_state = ViewState::new(
&window_size,
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
style
let zoom = style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default();
let position = style
.center
.map(|center| LatLon::new(center[0], center[1]))
.unwrap_or_default(),
style.pitch.unwrap_or_default(),
cgmath::Deg(110.0),
);
.map(|center| WorldCoords::from_lat_lon(LatLon::new(center[0], center[1]), zoom))
.unwrap_or_default();
let pitch = style.pitch.unwrap_or_default();
let view_state = ViewState::new(&window_size, position, zoom, pitch, cgmath::Deg(110.0));
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();

View File

@ -18,7 +18,7 @@ pub struct Style {
pub metadata: HashMap<String, String>,
pub sources: HashMap<String, Source>,
pub layers: Vec<StyleLayer>,
pub center: Option<[f64; 2]>,
pub center: Option<[f64; 2]>, // TODO: Use LatLon type here
pub zoom: Option<f64>,
pub pitch: Option<f64>,
}