use std::sync::Arc;
use oxigis_render::mvt::VectorTile;
use oxigis_render::{MapView, TileId};
use oxigis_ui::print::{PrintRequest, PrintTileLayer};
use oxigis_ui::{BoxedTileProvider, TileLayerSource};
pub(crate) struct ComposedStack {
pub rgb: Vec<u8>,
pub vector: Vec<Vec<(TileId, Arc<VectorTile>)>>,
pub missing_raster: usize,
pub missing_vector: usize,
}
pub(crate) fn compose_stack(
request: &PrintRequest,
ctx: &egui::Context,
compose: &MapView,
required: &[TileId],
) -> Result<ComposedStack, String> {
let basemap = crate::build_tile_provider(&request.basemap, ctx)
.ok_or_else(|| "no tile provider could be built for the export".to_string())?
.provider;
let mut missing_raster = await_raster(&basemap, required, "basemap");
let mut rgb = oxigis_ui::print::compose_map_rgb(compose, &mut |tile| basemap.tile(tile));
let mut vector = Vec::with_capacity(request.stack.len());
let mut missing_vector = 0_usize;
for entry in &request.stack {
match &entry.source {
TileLayerSource::Vector(config) => {
let (tiles, missing) = fetch_vector(config, ctx, compose, required);
missing_vector += missing;
vector.push(tiles);
}
_ => {
vector.push(Vec::new());
let Some(provider) = raster_provider(entry, ctx) else {
continue;
};
missing_raster += await_raster(&provider, required, "stack layer");
oxigis_ui::print::overlay_map_rgb(compose, &mut rgb, entry.opacity, &mut |tile| {
provider.tile(tile)
});
}
}
}
Ok(ComposedStack {
rgb,
vector,
missing_raster,
missing_vector,
})
}
fn raster_provider(entry: &PrintTileLayer, ctx: &egui::Context) -> Option<BoxedTileProvider> {
let built = match &entry.source {
TileLayerSource::Cog(config) => crate::build_cog_provider(config, None, ctx),
TileLayerSource::RasterArchive(config) => {
crate::build_archive_provider(config, None, ctx, None, None)
}
TileLayerSource::Xyz(config) => crate::build_tile_provider(config, ctx),
TileLayerSource::Vector(_) => None,
};
if built.is_none() {
tracing::warn!(
"OxiGIS desktop: a tiled layer's provider could not be built for the export; \
the page is printed without it",
);
}
built.map(|built| built.provider)
}
fn await_raster(provider: &BoxedTileProvider, required: &[TileId], what: &str) -> usize {
let deadline = std::time::Instant::now() + crate::tile_budget(required.len());
loop {
let missing = required
.iter()
.filter(|tile| provider.tile(**tile).is_none())
.count();
if missing == 0 {
return 0;
}
if std::time::Instant::now() >= deadline {
tracing::warn!(
missing,
total = required.len(),
layer = what,
"OxiGIS desktop: PDF export proceeding with missing tiles",
);
return missing;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
fn fetch_vector(
config: &oxigis_ui::VectorTileConfig,
ctx: &egui::Context,
compose: &MapView,
required: &[TileId],
) -> (Vec<(TileId, Arc<VectorTile>)>, usize) {
let Some(installed) = crate::build_vector_source(config, ctx, None, None) else {
tracing::warn!(
"OxiGIS desktop: a vector tile layer's source could not be built for the export; \
the page is printed without it",
);
return (Vec::new(), 0);
};
let source = installed.source;
let deadline = std::time::Instant::now() + crate::tile_budget(required.len());
let mut missing = 0;
loop {
let _ = source.begin_frame(*compose);
let absent = required
.iter()
.filter(|tile| {
let absent = source.decoded(**tile).is_none();
if absent {
let _ = source.mesh(**tile);
}
absent
})
.count();
if absent == 0 {
break;
}
if std::time::Instant::now() >= deadline {
tracing::warn!(
missing = absent,
total = required.len(),
"OxiGIS desktop: PDF export proceeding with missing vector tiles",
);
missing = absent;
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
let tiles = required
.iter()
.filter_map(|tile| source.decoded(*tile).map(|decoded| (*tile, decoded)))
.collect();
(tiles, missing)
}