benchmarking
2
.idea/wxbox.iml
generated
|
@ -20,6 +20,8 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/crates/ar2/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/nommer/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/ar2/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/grib2/benches" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/crates/tiler/benches" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
|
|
3249
Cargo.lock
generated
|
@ -4,16 +4,11 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1"
|
||||
nexrad-decode = "0.1.1"
|
||||
nexrad-data = "0.2"
|
||||
serde = { version = "1", features = ["derive"]}
|
||||
toml = "0.8"
|
||||
thiserror = "2"
|
||||
wxbox-nommer = { path = "../nommer" }
|
||||
bzip2 = { version = "0.5", default-features = false, features = ["libbz2-rs-sys"] }
|
||||
rayon = { version = "1" }
|
||||
bincode = { version = "2", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5" }
|
||||
|
|
BIN
crates/ar2/benches/KGWX20250518_165333_V06
Normal file
|
@ -1,14 +1,29 @@
|
|||
use std::{env, fs};
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use wxbox_ar2::parse;
|
||||
|
||||
const KCRP20170825_235733_V06: (&[u8], &str) = (include_bytes!("KCRP20170825_235733_V06"), "KCRP20170825_235733_V06");
|
||||
const KGWX20250518_165333_V06: (&[u8], &str) = (include_bytes!("KGWX20250518_165333_V06"), "KGWX20250518_165333_V06");
|
||||
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let f = fs::read("/home/core/dl/KGWX20250518_165333_V06").unwrap();
|
||||
c.bench_function("parse KGWX", |b| b.iter(|| {
|
||||
parse(black_box(f.clone()))
|
||||
}));
|
||||
let mut group = c.benchmark_group("parse");
|
||||
|
||||
for (test_file_bytes, name) in [KCRP20170825_235733_V06, KGWX20250518_165333_V06].iter() {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(name),
|
||||
test_file_bytes,
|
||||
|b, bytes| {
|
||||
b.iter(|| {
|
||||
parse(bytes.to_vec()).unwrap();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
|
|
491
crates/ar2/flamegraph.svg
Normal file
After Width: | Height: | Size: 566 KiB |
|
@ -3,9 +3,10 @@ use nexrad_decode::messages::MessageContents;
|
|||
use nexrad_decode::messages::digital_radar_data::{GenericDataBlock, RadialStatus};
|
||||
use nexrad_decode::result::Error;
|
||||
use std::fmt::Debug;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub mod sites;
|
||||
pub mod parser;
|
||||
|
||||
|
||||
pub struct Scan {
|
||||
pub coverage_pattern_number: u16,
|
||||
|
@ -85,25 +86,41 @@ pub enum MomentValue {
|
|||
pub fn parse(input: Vec<u8>) -> nexrad_data::result::Result<Scan> {
|
||||
let file = File::new(input);
|
||||
|
||||
let mut vcp = None;
|
||||
let mut vcp: Option<u16> = None;
|
||||
let mut radials = vec![];
|
||||
|
||||
for mut record in file.records() {
|
||||
if record.compressed() {
|
||||
record = record.decompress()?;
|
||||
}
|
||||
let messages = record.messages()?;
|
||||
for message in messages {
|
||||
let contents = message.into_contents();
|
||||
if let MessageContents::DigitalRadarData(radar_data_message) = contents {
|
||||
if vcp.is_none() {
|
||||
if let Some(volume_block) = &radar_data_message.volume_data_block {
|
||||
vcp = Some(volume_block.volume_coverage_pattern_number);
|
||||
}
|
||||
}
|
||||
radials.push(into_radial(*radar_data_message)?);
|
||||
let mut radial_chunks: Vec<nexrad_data::result::Result<(Vec<Radial>, Option<u16>)>> = file.records()
|
||||
.par_iter_mut()
|
||||
.map(|record| {
|
||||
let mut vcp = None;
|
||||
let mut radials = vec![];
|
||||
if record.compressed() {
|
||||
*record = record.decompress()?;
|
||||
}
|
||||
let messages = record.messages()?;
|
||||
|
||||
for message in messages {
|
||||
let contents = message.contents();
|
||||
if let MessageContents::DigitalRadarData(radar_data_message) = contents {
|
||||
if vcp.is_none() {
|
||||
if let Some(volume_block) = &radar_data_message.volume_data_block {
|
||||
vcp = Some(volume_block.volume_coverage_pattern_number);
|
||||
}
|
||||
}
|
||||
radials.push(into_radial(*radar_data_message.clone())?)
|
||||
}
|
||||
}
|
||||
|
||||
Ok((radials, vcp))
|
||||
})
|
||||
.collect();
|
||||
|
||||
for radial_chunk in radial_chunks {
|
||||
let (mut radial_chunk, this_vcp) = radial_chunk?;
|
||||
if vcp.is_none() && this_vcp.is_some() {
|
||||
vcp = this_vcp;
|
||||
}
|
||||
radials.append(&mut radial_chunk);
|
||||
}
|
||||
|
||||
Ok(Scan {
|
||||
|
@ -188,6 +205,4 @@ impl Sweep {
|
|||
|
||||
sweeps
|
||||
}
|
||||
}
|
||||
|
||||
pub const DATA_BYTES: &[u8] = include_bytes!("../KCRP20170825_235733_V06");
|
||||
}
|
|
@ -2,7 +2,6 @@ use std::{env, fs};
|
|||
use std::fs::File;
|
||||
use std::time::Instant;
|
||||
use wxbox_ar2::parse;
|
||||
use wxbox_ar2::parser::Ar2Parser;
|
||||
|
||||
fn main() {
|
||||
let f = fs::read(env::args().nth(1).unwrap()).unwrap();
|
||||
|
|
Before Width: | Height: | Size: 6.8 KiB |
|
@ -1,30 +0,0 @@
|
|||
[package]
|
||||
name = "wxbox-client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
eframe = { version = "0.31", features = ["persistence"] }
|
||||
egui = "0.31"
|
||||
poll-promise = "0.3"
|
||||
image = "0.25"
|
||||
rand = "0.9"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
egui_extras = { version = "0.31", features = ["serde"] }
|
||||
egui_demo_lib = { version = "0.31", features = ["serde"] }
|
||||
egui-phosphor = "0.9"
|
||||
walkers = { version = "0.36", git = "https://github.com/c0repwn3r/walkers", branch = "core/wxbox", features = ["serde"] }
|
||||
#walkers = { version = "0.36", path = "../../../walkers/walkers", features = ["serde"] }
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
lru = "0.13"
|
||||
futures = "0.3"
|
||||
egui_flex = "0.3"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tracing-web = "0.1"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
console_error_panic_hook = "0.1"
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 314 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"name": "wxbox",
|
||||
"short_name": "wxbox",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./assets/icon-256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/maskable_icon_x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icon-1024.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"lang": "en-US",
|
||||
"id": "/index.html",
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"background_color": "white",
|
||||
"theme_color": "white"
|
||||
}
|
Before Width: | Height: | Size: 128 KiB |
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
var cacheName = 'egui-template-pwa';
|
||||
*/
|
||||
/*
|
||||
var filesToCache = [
|
||||
'./',
|
||||
'./index.html',
|
||||
'./eframe_template.js',
|
||||
'./eframe_template_bg.wasm',
|
||||
];
|
||||
*/
|
||||
/* Start the service worker and cache all of the app's content *//*
|
||||
self.addEventListener('install', function (e) {
|
||||
e.waitUntil(
|
||||
caches.open(cacheName).then(function (cache) {
|
||||
return cache.addAll(filesToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
*/
|
||||
/* Serve cached content when offline *//*
|
||||
self.addEventListener('fetch', function (e) {
|
||||
e.respondWith(
|
||||
caches.match(e.request).then(function (response) {
|
||||
return response || fetch(e.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
*/
|
|
@ -1,154 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<!-- Disable zooming: -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<head>
|
||||
<!-- change this to your project name -->
|
||||
<title>wxbox</title>
|
||||
|
||||
<!-- config for our rust wasm binary. go to https://trunkrs.dev/assets/#rust for more customization -->
|
||||
<link data-trunk rel="rust" data-wasm-opt="2" />
|
||||
<!-- this is the base url relative to which other urls will be constructed. trunk will insert this from the public-url option -->
|
||||
<base data-trunk-public-url />
|
||||
|
||||
<link data-trunk rel="icon" href="assets/favicon.ico">
|
||||
|
||||
|
||||
<link data-trunk rel="copy-file" href="assets/sw.js"/>
|
||||
<link data-trunk rel="copy-file" href="assets/manifest.json"/>
|
||||
<link data-trunk rel="copy-file" href="assets/icon-1024.png" data-target-path="assets"/>
|
||||
<link data-trunk rel="copy-file" href="assets/icon-256.png" data-target-path="assets"/>
|
||||
<link data-trunk rel="copy-file" href="assets/icon_ios_touch_192.png" data-target-path="assets"/>
|
||||
<link data-trunk rel="copy-file" href="assets/maskable_icon_x512.png" data-target-path="assets"/>
|
||||
|
||||
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" href="assets/icon_ios_touch_192.png">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
|
||||
|
||||
<style>
|
||||
html {
|
||||
/* Remove touch delay: */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Light mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #fcfcfd;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
/* Dark mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #111113;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow canvas to fill entire web page: */
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Make canvas fill entire document: */
|
||||
canvas {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;/*
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
height: calc(100% - 26px);
|
||||
}
|
||||
@supports not (-webkit-touch-callout: none) {
|
||||
height: 100%;
|
||||
}*/
|
||||
height:100%;
|
||||
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Loading animation from https://loading.io/css/ */
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- The WASM code will resize the canvas dynamically -->
|
||||
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
|
||||
<canvas id="the_canvas_id"></canvas>
|
||||
|
||||
<!-- the loading spinner will be removed in main.rs -->
|
||||
<div class="centered" id="loading_text">
|
||||
<p style="font-size:16px">
|
||||
Loading…
|
||||
</p>
|
||||
<div class="lds-dual-ring"></div>
|
||||
</div>
|
||||
|
||||
<!--Register Service Worker. this will cache the wasm / js scripts for offline use (for PWA functionality). -->
|
||||
<!-- Force refresh (Ctrl + F5) to load the latest files instead of cached files -->
|
||||
<script>
|
||||
// We disable caching during development so that we always view the latest version.
|
||||
if ('serviceWorker' in navigator && window.location.hash !== "#dev") {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('sw.js');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<!-- Powered by egui: https://github.com/emilk/egui/ -->
|
|
@ -1,101 +0,0 @@
|
|||
use crate::map::sources::baselayers::BASELAYER_OSM;
|
||||
use crate::map::sources::{ActiveLayer, LayerId};
|
||||
use crate::map::tiles::LayerManager;
|
||||
use crate::ui::shell::footer::footer;
|
||||
use crate::ui::shell::left_bar::left_bar;
|
||||
use crate::ui::shell::right_bar::right_bar;
|
||||
use crate::ui::shell::top_bar::top_bar;
|
||||
use crate::ui::tokens::DESIGN_TOKENS;
|
||||
use eframe::{Frame, Storage};
|
||||
use egui::util::History;
|
||||
use egui::Context;
|
||||
use egui_demo_lib::{View, WidgetGallery};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use walkers::{HttpTiles, MapMemory, Position, Tiles};
|
||||
use crate::ui::{Breakpoint, CtxExt};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct App {
|
||||
pub(crate) frame_time_history: History<f32>,
|
||||
pub add_layer_open: bool,
|
||||
pub selected_layer: Option<LayerId>,
|
||||
pub map_memory: MapMemory,
|
||||
pub layer_manager: LayerManager,
|
||||
pub position: Position,
|
||||
pub menu_open: bool
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
DESIGN_TOKENS.apply(&cc.egui_ctx);
|
||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||
|
||||
if let Some(storage) = cc.storage {
|
||||
if let Some(stored) = eframe::get_value(storage, eframe::APP_KEY) {
|
||||
let mut stored: Self = stored;
|
||||
stored.frame_time_history = History::new(1..100, 0.5);
|
||||
stored.layer_manager.ctx = Some(cc.egui_ctx.clone());
|
||||
stored.layer_manager.registered_sources = HashMap::default();
|
||||
stored.layer_manager.register_builtin_layers();
|
||||
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
|
||||
let mut layer_manager = LayerManager::default();
|
||||
layer_manager.register_builtin_layers();
|
||||
layer_manager.active_layers.push(ActiveLayer {
|
||||
source_id: BASELAYER_OSM.source_id,
|
||||
layer_id: LayerId::new(),
|
||||
visible: true,
|
||||
});
|
||||
layer_manager.ctx = Some(cc.egui_ctx.clone());
|
||||
Self {
|
||||
frame_time_history: History::new(1..100, 0.5),
|
||||
add_layer_open: false,
|
||||
selected_layer: None,
|
||||
map_memory: MapMemory::default(),
|
||||
layer_manager,
|
||||
position: Position::new(35.227085, -80.843124),
|
||||
menu_open: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn save(&mut self, storage: &mut dyn Storage) {
|
||||
eframe::set_value(storage, eframe::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn update<'a>(&'a mut self, ctx: &'a Context, frame: &'a mut Frame) {
|
||||
if let Some(seconds) = frame.info().cpu_usage {
|
||||
self.frame_time_history.add(ctx.input(|i| i.time), seconds);
|
||||
}
|
||||
|
||||
top_bar(ctx);
|
||||
|
||||
let show_main_menu_in_footer = ctx.breakpoint() < Breakpoint::Xl4;
|
||||
|
||||
footer(ctx, self, show_main_menu_in_footer);
|
||||
|
||||
if !show_main_menu_in_footer {
|
||||
left_bar(
|
||||
ctx,
|
||||
&mut self.add_layer_open,
|
||||
&mut self.layer_manager,
|
||||
&mut self.selected_layer,
|
||||
);
|
||||
}
|
||||
//right_bar(ctx, self);
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.add(walkers::Map::new(
|
||||
Some(&mut self.layer_manager),
|
||||
&mut self.map_memory,
|
||||
self.position,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub mod app;
|
||||
pub mod map;
|
||||
pub mod ui;
|
|
@ -1,85 +0,0 @@
|
|||
use egui::Theme;
|
||||
use tracing::debug;
|
||||
use wxbox_client::ui::tokens::DESIGN_TOKENS;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> eframe::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default(),
|
||||
multisampling: 8,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
debug!("{:?}", DESIGN_TOKENS.get_color("colors.bg", Theme::Dark));
|
||||
|
||||
eframe::run_native(
|
||||
"wxbox",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(wxbox_client::app::App::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::fmt::format::Pretty;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_web::{performance_layer, MakeWebConsoleWriter};
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(false) // not supported in browsers
|
||||
.without_time() // std::time doesn't exist in wasm
|
||||
.with_writer(MakeWebConsoleWriter::new().with_max_level(Level::DEBUG)); // wgpu spams the console, and this is slow as hell
|
||||
|
||||
let perf_layer = performance_layer() // enable performance tracing
|
||||
.with_details_from_fields(Pretty::default()); // ... with pretty fields
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(perf_layer)
|
||||
.init(); // register the logger
|
||||
|
||||
use eframe::wasm_bindgen::JsCast as _;
|
||||
|
||||
let web_options = eframe::WebOptions::default();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
let document = web_sys::window()
|
||||
.expect("No window")
|
||||
.document()
|
||||
.expect("No document");
|
||||
|
||||
let canvas = document
|
||||
.get_element_by_id("the_canvas_id")
|
||||
.expect("Failed to find the_canvas_id")
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.expect("the_canvas_id was not a HtmlCanvasElement");
|
||||
|
||||
let start_result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
web_options,
|
||||
Box::new(|cc| Ok(Box::new(wxbox_client::app::App::new(cc)))),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove the loading text and spinner:
|
||||
if let Some(loading_text) = document.get_element_by_id("loading_text") {
|
||||
match start_result {
|
||||
Ok(_) => {
|
||||
loading_text.remove();
|
||||
}
|
||||
Err(e) => {
|
||||
loading_text.set_inner_html(
|
||||
"<p> The app has crashed. See the developer console for details. </p>",
|
||||
);
|
||||
panic!("Failed to start eframe: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use crate::map::tiles::LayerManager;
|
||||
use walkers::sources::Attribution;
|
||||
use walkers::{TextureWithUv, TileId, Tiles};
|
||||
|
||||
pub mod sources;
|
||||
pub mod tiles;
|
|
@ -1,16 +0,0 @@
|
|||
use crate::map::sources::{LayerSource, LayerTypeHint};
|
||||
use std::convert::Into;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static BASELAYER_OSM: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0x572dd260332d5f7f,
|
||||
tile_url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png".into(),
|
||||
display_name: "OpenStreetMap".into(),
|
||||
short_name: "OpenStreetMap".into(),
|
||||
type_hint: LayerTypeHint::Baselayer,
|
||||
location: "Worldwide".into(),
|
||||
source: "OpenStreetMap contributors".into(),
|
||||
source_link: "https://openstreetmap.org".into(),
|
||||
tile_size: 256,
|
||||
max_zoom: 19,
|
||||
});
|
|
@ -1,110 +0,0 @@
|
|||
use crate::map::sources::{LayerSource, LayerTypeHint};
|
||||
use std::convert::Into;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub static NOAA_MRMS_MERGED_CREF_QC_CONUS: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0xC10FCD0566B7F952,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_composite_reflectivity_qc_CONUS/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged Composite Reflectivity QCd".into(),
|
||||
short_name: "Composite Reflectivity (M-CONUS-QCd)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Continental US".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static NOAA_MRMS_MERGED_CREF_QC_ALASKA: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0x779F656117545E91,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_composite_reflectivity_qc_ALASKA/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged Composite Reflectivity QCd".into(),
|
||||
short_name: "Composite Reflectivity (M-ALASKA-QCd)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Alaska".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static NOAA_MRMS_MERGED_CREF_QC_CARIB: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0xA7076E0145BC4BDD,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_composite_reflectivity_qc_CARIB/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged Composite Reflectivity QCd".into(),
|
||||
short_name: "Composite Reflectivity (M-CARIB-QCd)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Caribbean".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static NOAA_MRMS_MERGED_CREF_QC_GUAM: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0xB277C4B00469BD02,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_composite_reflectivity_qc_GUAM/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged Composite Reflectivity QCd".into(),
|
||||
short_name: "Composite Reflectivity (M-GUAM-QCd)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Guam".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static NOAA_MRMS_MERGED_CREF_QC_HAWAII: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0x238710F2CD275445,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_composite_reflectivity_qc_HAWAII/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged Composite Reflectivity QCd".into(),
|
||||
short_name: "Composite Reflectivity (M-HAWAII-QCd)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Hawaii".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static NOAA_MRMS_MERGED_RHOHV_3KM_CONUS: LazyLock<LayerSource> =
|
||||
LazyLock::new(|| LayerSource {
|
||||
source_id: 0x0EFAF68CB6E30B8F,
|
||||
tile_url: format!(
|
||||
"{}/grib2/noaa_mrms_merged_rhohv_3km_CONUS/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "Merged RhoHV @ 3km".into(),
|
||||
short_name: "RhoHV @ 3km (CONUS)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "Continental US".into(),
|
||||
source: "NOAA / MRMS".into(),
|
||||
source_link: "https://mrms.nssl.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
||||
pub static ROC_KCRP_TEST: LazyLock<LayerSource> = LazyLock::new(|| LayerSource {
|
||||
source_id: 0x0EFAF68CB6E30B8E,
|
||||
tile_url: format!(
|
||||
"{}/nexrad/kcrp_ref_test/{{z}}/{{x}}/{{y}}@2x.png",
|
||||
env!("TILER_BASE_URL")
|
||||
),
|
||||
display_name: "KCRP SSR Test".into(),
|
||||
short_name: "KCRP SSR TEST (KCRP)".into(),
|
||||
type_hint: LayerTypeHint::RadarData,
|
||||
location: "KCRP".into(),
|
||||
source: "NOAA / ROC".into(),
|
||||
source_link: "https://roc.noaa.gov".into(),
|
||||
tile_size: 512,
|
||||
max_zoom: 19,
|
||||
});
|
|
@ -1,84 +0,0 @@
|
|||
use crate::map::tiles::SourceId;
|
||||
use image::DynamicImage;
|
||||
use poll_promise::Promise;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
pub mod baselayers;
|
||||
pub mod datalayers;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct LayerSource {
|
||||
pub source_id: u64,
|
||||
pub tile_url: String,
|
||||
pub display_name: String,
|
||||
pub short_name: String,
|
||||
pub location: String,
|
||||
pub source: String,
|
||||
pub source_link: String,
|
||||
pub type_hint: LayerTypeHint,
|
||||
pub tile_size: u32,
|
||||
pub max_zoom: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||
pub enum LayerTypeHint {
|
||||
Baselayer,
|
||||
RadarData,
|
||||
}
|
||||
impl LayerTypeHint {
|
||||
pub fn icon(&self) -> &'static str {
|
||||
match self {
|
||||
LayerTypeHint::Baselayer => egui_phosphor::regular::MAP_TRIFOLD,
|
||||
LayerTypeHint::RadarData => egui_phosphor::regular::TARGET,
|
||||
}
|
||||
}
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
LayerTypeHint::Baselayer => "Baselayer",
|
||||
LayerTypeHint::RadarData => "Radar data",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tile {
|
||||
pub promise: Promise<Result<DynamicImage, String>>,
|
||||
}
|
||||
impl Debug for Tile {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
if self.promise.ready().is_some() {
|
||||
"<loaded texture>"
|
||||
} else {
|
||||
"<texture promise>"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ActiveLayer {
|
||||
pub source_id: SourceId,
|
||||
pub layer_id: LayerId,
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LayerId(u32);
|
||||
impl LayerId {
|
||||
pub fn new() -> Self {
|
||||
Self(rand::random())
|
||||
}
|
||||
}
|
||||
impl Debug for LayerId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:04x}", self.0)
|
||||
}
|
||||
}
|
||||
impl From<LayerId> for egui::Id {
|
||||
fn from(id: LayerId) -> Self {
|
||||
Self::new(id)
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
use crate::map::sources::baselayers::*;
|
||||
use crate::map::sources::datalayers::*;
|
||||
use crate::map::sources::{ActiveLayer, LayerSource, Tile};
|
||||
use eframe::emath::{pos2, Rect};
|
||||
use futures::channel::mpsc::TrySendError;
|
||||
use image::{ColorType, DynamicImage};
|
||||
use lru::LruCache;
|
||||
use poll_promise::Promise;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::PartialEq;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{debug, error, trace};
|
||||
use walkers::sources::{Attribution, TileSource};
|
||||
use walkers::{HttpTiles, Texture, TextureWithUv, TileId, Tiles};
|
||||
|
||||
// type aliases to make the tile map less hellish
|
||||
pub type ZoomLevel = usize;
|
||||
pub type XCoord = usize;
|
||||
pub type YCoord = usize;
|
||||
pub type SourceId = u64;
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct LayerManager {
|
||||
#[serde(skip)]
|
||||
pub ctx: Option<egui::Context>,
|
||||
pub registered_sources: HashMap<SourceId, LayerSource>,
|
||||
pub active_layers: Vec<ActiveLayer>,
|
||||
#[serde(skip)]
|
||||
pub individual_sources: HashMap<SourceId, WHttpTiles>,
|
||||
}
|
||||
|
||||
pub struct WHttpTiles(pub Rc<RefCell<HttpTiles>>);
|
||||
impl Debug for WHttpTiles {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "<WHttpTiles>")
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerManager {
|
||||
pub fn register_layer(&mut self, layer: &LayerSource) {
|
||||
self.registered_sources
|
||||
.insert(layer.source_id, layer.clone());
|
||||
}
|
||||
pub fn unregister_layer(&mut self, layer_id: &SourceId) {
|
||||
self.registered_sources.remove(layer_id);
|
||||
self.active_layers.retain(|u| u.source_id != *layer_id);
|
||||
}
|
||||
pub fn register_builtin_layers(&mut self) {
|
||||
let layers = [
|
||||
// -- Base layers --
|
||||
&*BASELAYER_OSM, // OpenStreetMap
|
||||
// -- NOAA MRMS - Merged Composite Reflectivity - QCd
|
||||
&*NOAA_MRMS_MERGED_CREF_QC_CONUS, // Continental United States
|
||||
&*NOAA_MRMS_MERGED_CREF_QC_HAWAII, // Hawaii
|
||||
&*NOAA_MRMS_MERGED_CREF_QC_ALASKA, // Alaska FSS
|
||||
&*NOAA_MRMS_MERGED_CREF_QC_CARIB, // Caribbean
|
||||
&*NOAA_MRMS_MERGED_CREF_QC_GUAM, // Guam
|
||||
// -- NOAA MRMS - Merged RhoHV (Differential Reflectivity) @ 3km
|
||||
&*NOAA_MRMS_MERGED_RHOHV_3KM_CONUS,
|
||||
&*ROC_KCRP_TEST,
|
||||
];
|
||||
|
||||
for layer in layers {
|
||||
self.register_layer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_unregistered_layers(&mut self) {
|
||||
// remove any
|
||||
self.active_layers
|
||||
.retain(|u| self.registered_sources.contains_key(&u.source_id));
|
||||
}
|
||||
}
|
||||
|
||||
impl TileSource for LayerSource {
|
||||
fn tile_url(&self, tile_id: TileId) -> String {
|
||||
self.tile_url
|
||||
.replace("{z}", &tile_id.zoom.to_string())
|
||||
.replace("{x}", &tile_id.x.to_string())
|
||||
.replace("{y}", &tile_id.y.to_string())
|
||||
}
|
||||
|
||||
fn attribution(&self) -> Attribution {
|
||||
Attribution {
|
||||
text: self.source.clone().leak(),
|
||||
url: self.source_link.clone().leak(),
|
||||
logo_light: None,
|
||||
logo_dark: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_size(&self) -> u32 {
|
||||
self.tile_size
|
||||
}
|
||||
|
||||
fn max_zoom(&self) -> u8 {
|
||||
self.max_zoom
|
||||
}
|
||||
}
|
||||
|
||||
impl Tiles for LayerManager {
|
||||
fn at(&mut self, tile_id: TileId) -> Vec<TextureWithUv> {
|
||||
let mut layers = vec![];
|
||||
for layer in self.active_layers.iter().rev().filter(|u| u.visible) {
|
||||
let layer = self
|
||||
.individual_sources
|
||||
.entry(layer.source_id)
|
||||
.or_insert_with(|| {
|
||||
WHttpTiles(Rc::new(RefCell::new(HttpTiles::new(
|
||||
self.registered_sources
|
||||
.get(&layer.source_id)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
self.ctx.as_ref().unwrap().clone(),
|
||||
))))
|
||||
});
|
||||
let tiles_here = layer.0.borrow_mut().at(tile_id);
|
||||
layers.extend(tiles_here);
|
||||
}
|
||||
layers
|
||||
}
|
||||
|
||||
fn attribution(&self) -> Attribution {
|
||||
Attribution {
|
||||
text: "our sources",
|
||||
url: "https://weather.ax/credits",
|
||||
logo_light: None,
|
||||
logo_dark: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_size(&self) -> u32 {
|
||||
256
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|