diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be8788a..c8268a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,8 @@ jobs: run: | rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - 7z x ./shapefiles_v1.zip -y + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + 7z x ./shapefiles_v2.zip -y popd - uses: Swatinem/rust-cache@v2.9.1 @@ -140,8 +140,8 @@ jobs: rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - unzip -o ./shapefiles_v1.zip + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + unzip -o ./shapefiles_v2.zip popd sudo apt-get update -y sudo apt-get install -y protobuf-compiler @@ -234,8 +234,8 @@ jobs: rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - unzip -o ./shapefiles_v1.zip + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + unzip -o ./shapefiles_v2.zip popd sudo apt-get update -y sudo apt-get install -y protobuf-compiler @@ -305,8 +305,8 @@ jobs: rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - unzip -o ./shapefiles_v1.zip + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + unzip -o ./shapefiles_v2.zip popd sudo apt-get update -y sudo apt-get install -y protobuf-compiler diff --git a/.github/workflows/rendering.yml b/.github/workflows/rendering.yml index fc15614..5ea2589 100644 --- a/.github/workflows/rendering.yml +++ b/.github/workflows/rendering.yml @@ -35,8 +35,8 @@ jobs: rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - unzip -o ./shapefiles_v1.zip + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + unzip -o ./shapefiles_v2.zip popd - name: Get Rust toolchain diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index bd0bb3d..6370c9b 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -48,8 +48,8 @@ jobs: rc alias set yr32srv https://rustfs-s3.yr32.net eewbot-ci "${{ secrets.YR32RUSTFS_EEWBOT_CI_SECRET }}" pushd assets/shapefile/ - rc cp yr32srv/eewbot/shapefiles_v1.zip ./shapefiles_v1.zip - unzip -o ./shapefiles_v1.zip + rc cp yr32srv/eewbot/shapefiles_v2.zip ./shapefiles_v2.zip + unzip -o ./shapefiles_v2.zip popd sudo apt-get update -y sudo apt-get install -y protobuf-compiler diff --git a/asset-preprocessor/src/lib.rs b/asset-preprocessor/src/lib.rs index f5c81cf..99fe7e0 100644 --- a/asset-preprocessor/src/lib.rs +++ b/asset-preprocessor/src/lib.rs @@ -2,3 +2,4 @@ mod math; pub mod parse_lake_shapefile; pub mod parse_shapefile; pub mod parse_tsunami_shapefile; +pub mod parse_world_shapefile; diff --git a/asset-preprocessor/src/parse_world_shapefile.rs b/asset-preprocessor/src/parse_world_shapefile.rs new file mode 100644 index 0000000..fa0ef24 --- /dev/null +++ b/asset-preprocessor/src/parse_world_shapefile.rs @@ -0,0 +1,136 @@ +#![allow(clippy::type_complexity)] + +use std::collections::HashMap; + +use shapefile::{Shape, ShapeReader}; + +use crate::math::*; + +const WEB_MERCATOR_MAX_LATITUD: f32 = 85.051_13; + +struct VertexBuffer { + buffer: Vec<(Of32, Of32)>, + dict: HashMap<(Of32, Of32), usize>, +} + +impl VertexBuffer { + fn new() -> Self { + Self { + buffer: Default::default(), + dict: Default::default(), + } + } + + fn insert(&mut self, v: (Of32, Of32)) -> usize { + match self.dict.get(&v) { + Some(index) => *index, + None => { + self.buffer.push(v); + let index = self.buffer.len() - 1; + self.dict.insert(v, index); + index + } + } + } + + fn into_buffer(self) -> Vec<(f32, f32)> { + self.buffer.into_iter().map(|(x, y)| (x.0, y.0)).collect() + } +} + +/// 極点での発散を避けるため、球面Web Mercatorで一般的に使用される打ち切り緯度 ±85.05113° を、レンダラーの描画上限として採用する。 +/// 経度はそのまま返す。 +fn clamp_mercator_latitude(point: Point) -> Point { + let latitude = point + .latitude + .0 + .clamp(-WEB_MERCATOR_MAX_LATITUD, WEB_MERCATOR_MAX_LATITUD); + + Point::new(Of32::from(latitude), point.longitude) +} + +struct WorldRings { + rings: Vec, +} + +impl WorldRings { + fn from_shape(shape: Shape) -> Self { + let Shape::Polygon(polygon) = shape else { + panic!("world shapefile contains a non-Polygon shape"); + }; + + let rings: Vec<_> = polygon + .rings() + .iter() + .map(|ring| Ring::from(ring.points().to_vec())) + .collect(); + + Self { rings } + } +} + +struct Shapefile { + entries: Vec, +} + +impl Shapefile { + fn new() -> Self { + let shp_file = std::fs::File::open("../assets/shapefile/world/world.shp"); + + let Ok(shp_file) = shp_file else { + panic!( + r#"EEWBot Renderer requirements is not satisfied. + +World shape file is not found. + - assets/shapefile/world/world.shp + +Please follow: + https://github.com/EEWBot/eew-renderer/wiki#shapefile-%E5%85%A5%E6%89%8B%E5%85%88"# + ) + }; + + let mut shape_reader = ShapeReader::new(shp_file).unwrap(); + + let entries = shape_reader + .iter_shapes() + .map(|shape| shape.expect("Failed to read a shape from world shapefile")) + .map(WorldRings::from_shape) + .collect(); + + Self { entries } + } +} + +pub fn read() -> ( + Vec<(f32, f32)>, // vertices + Vec, // indices +) { + let shapefile = Shapefile::new(); + + let mut vertex_buffer = VertexBuffer::new(); + + let world_indices: Vec = shapefile + .entries + .iter() + .flat_map(|world_rings| &world_rings.rings) + .flat_map(|ring| ring.triangulate()) + .map(clamp_mercator_latitude) + .map(|point| { + u32::try_from(vertex_buffer.insert(point.into())) + .expect("world shapefile has too many vertices") + }) + .collect(); + + let world_vertices = vertex_buffer.into_buffer(); + + assert!( + !world_vertices.is_empty(), + "world shapefile produced no vertices" + ); + assert!( + !world_indices.is_empty(), + "world shapefile produced no indices" + ); + + (world_vertices, world_indices) +} diff --git a/assets/shapefile/world/README.md b/assets/shapefile/world/README.md new file mode 100644 index 0000000..1a4b15f --- /dev/null +++ b/assets/shapefile/world/README.md @@ -0,0 +1,2 @@ +https://github.com/EEWBot/eew-renderer/wiki に従い、以下のファイルを作成する + - `assets/shapefile/world/world.shp` diff --git a/renderer-assets/build.rs b/renderer-assets/build.rs index da7c4f0..76778ce 100644 --- a/renderer-assets/build.rs +++ b/renderer-assets/build.rs @@ -6,7 +6,9 @@ use const_gen::*; use ordered_float::NotNan; mod station_codes_parser; -use asset_preprocessor::{parse_lake_shapefile, parse_shapefile, parse_tsunami_shapefile}; +use asset_preprocessor::{ + parse_lake_shapefile, parse_shapefile, parse_tsunami_shapefile, parse_world_shapefile, +}; use renderer_types::{BoundingBox, GeoDegree, Vertex}; fn main() { @@ -15,6 +17,8 @@ fn main() { let (lake_vertices, lake_indices) = parse_lake_shapefile::read(); + let (world_vertices, world_indices) = parse_world_shapefile::read(); + let s = std::fs::read_to_string("../assets/intensity_stations.json").unwrap(); #[allow(non_snake_case)] @@ -77,6 +81,8 @@ fn main() { const_declaration!(SCALE_LEVEL_MAP = scale_level_map), const_declaration!(LAKE_VERTICES = lake_vertices), const_declaration!(LAKE_INDICES = lake_indices), + const_declaration!(WORLD_VERTICES = world_vertices), + const_declaration!(WORLD_INDICES = world_indices), const_declaration!(TSUNAMI_VERTICES = tsunami_vertices), const_declaration!(TSUNAMI_INDICES = tsunami_indices), const_declaration!(TSUNAMI_AREA_CODE_TO_INTERNAL_CODE = tsunami_area_code_to_internal_code), diff --git a/renderer-assets/src/lib.rs b/renderer-assets/src/lib.rs index 21ae8c8..44c50de 100644 --- a/renderer-assets/src/lib.rs +++ b/renderer-assets/src/lib.rs @@ -19,6 +19,11 @@ pub struct LakeGeometries { pub indices: &'static [u32], } +pub struct WorldGeometries { + pub vertices: &'static [(f32, f32)], + pub indices: &'static [u32], +} + pub struct TsunamiGeometries { pub vertices: &'static [(f32, f32, u16)], pub indices: &'static [u32], @@ -41,6 +46,13 @@ impl QueryInterface { } } + pub fn world_geometries() -> WorldGeometries { + WorldGeometries { + vertices: WORLD_VERTICES, + indices: WORLD_INDICES, + } + } + pub fn tsunami_geometries() -> TsunamiGeometries { TsunamiGeometries { vertices: TSUNAMI_VERTICES, diff --git a/renderer/src/frame_context.rs b/renderer/src/frame_context.rs index 65d427b..02b2b18 100644 --- a/renderer/src/frame_context.rs +++ b/renderer/src/frame_context.rs @@ -105,6 +105,27 @@ pub enum FramePayload { TsunamiSecond(TsunamiSecondPayload), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MapLayerConfig { + pub world: bool, + pub saibunkuiki_line: bool, +} + +impl FramePayload { + pub fn map_layers(&self) -> MapLayerConfig { + match self { + FramePayload::Earthquake(_) => MapLayerConfig { + world: true, + saibunkuiki_line: true, + }, + FramePayload::TsunamiFirst(_) | FramePayload::TsunamiSecond(_) => MapLayerConfig { + world: false, + saibunkuiki_line: false, + }, + } + } +} + #[derive(Debug)] pub struct FrameContext { pub payload: FramePayload, diff --git a/renderer/src/worker/drawer_map.rs b/renderer/src/worker/drawer_map.rs index 9c28540..9462d34 100644 --- a/renderer/src/worker/drawer_map.rs +++ b/renderer/src/worker/drawer_map.rs @@ -1,3 +1,4 @@ +use crate::frame_context::MapLayerConfig; use crate::worker::vertex::{BorderLineUniform, MapUniform}; use crate::worker::FrameContext; use glium::backend::Facade; @@ -6,7 +7,7 @@ use std::ops::DerefMut; pub fn draw( frame_context: &FrameContext, - has_saibunkuiki: bool, + layers: MapLayerConfig, ) { let theme = frame_context.theme; let params = frame_context.draw_parameters; @@ -16,6 +17,25 @@ pub fn draw( let offset = frame_context.offset.into(); let image_size: [f32; 2] = frame_context.image_size.to_f32().into(); + if layers.world { + resources + .shader + .map + .draw( + frame_context.surface.borrow_mut().deref_mut(), + &resources.world.vertex, + &resources.world.index, + &MapUniform { + aspect_ratio, + offset, + zoom: scale, + color: theme.ground_color, + }, + params, + ) + .unwrap(); + } + resources .shader .map @@ -54,7 +74,7 @@ pub fn draw( ) .unwrap(); - if has_saibunkuiki { + if layers.saibunkuiki_line { resources .shader .border_line diff --git a/renderer/src/worker/mod.rs b/renderer/src/worker/mod.rs index ee5083c..a500d3a 100644 --- a/renderer/src/worker/mod.rs +++ b/renderer/src/worker/mod.rs @@ -175,22 +175,24 @@ impl ApplicationHandler for App<'_> { clear_color[3], ); + let map_layers = request_frame_context.payload.map_layers(); + match &request_frame_context.payload { FramePayload::Earthquake(earthquake) => { - drawer_map::draw(&frame_context, true); + drawer_map::draw(&frame_context, map_layers); drawer_intensity_icon::draw_all(&frame_context, earthquake); drawer_epicenter::draw(&frame_context, earthquake); drawer_overlay::draw(&frame_context, earthquake); } FramePayload::TsunamiFirst(tsunami) => { - drawer_map::draw(&frame_context, false); + drawer_map::draw(&frame_context, map_layers); drawer_tsunami_line::draw(&frame_context, tsunami); drawer_tsunami_legends::draw(&frame_context, tsunami); drawer_epicenter::draw(&frame_context, tsunami); drawer_overlay::draw(&frame_context, tsunami); } FramePayload::TsunamiSecond(tsunami) => { - drawer_map::draw(&frame_context, false); + drawer_map::draw(&frame_context, map_layers); drawer_tsunami_legends::draw(&frame_context, tsunami); drawer_epicenter::draw(&frame_context, tsunami); drawer_overlay::draw(&frame_context, tsunami); diff --git a/renderer/src/worker/resources.rs b/renderer/src/worker/resources.rs index 2ea1c08..2791f29 100644 --- a/renderer/src/worker/resources.rs +++ b/renderer/src/worker/resources.rs @@ -15,6 +15,7 @@ pub struct Resources<'a> { pub shader: Shader<'a>, pub buffer: Buffer, pub lake: Lake, + pub world: World, pub texture: Texture, } @@ -23,12 +24,14 @@ impl Resources<'_> { let shader = Shader::load(facade); let buffer = Buffer::load(facade); let lake = Lake::load(facade); + let world = World::load(facade); let texture = Texture::load(facade); Self { shader, buffer, lake, + world, texture, } } @@ -133,6 +136,32 @@ impl Lake { } } +#[derive(Debug)] +pub struct World { + pub vertex: VertexBuffer, + pub index: IndexBuffer, +} + +impl World { + fn load(facade: &F) -> Self { + let geom = renderer_assets::QueryInterface::world_geometries(); + + let vertex: Vec<_> = geom + .vertices + .iter() + .map(|v| MapVertex { + position: [v.0, v.1], + }) + .collect(); + let vertex = VertexBuffer::immutable(facade, &vertex).unwrap(); + + let index = + IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, geom.indices).unwrap(); + + World { vertex, index } + } +} + #[derive(Debug)] pub struct Shader<'a> { pub border_line: ShaderProgram,