Skip to content

Commit

Permalink
Support drawing points with images (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maximkaaa authored Jan 29, 2024
1 parent d984043 commit 4e50e3c
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 60 deletions.
Binary file added galileo/examples/data/pin-yellow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 14 additions & 5 deletions galileo/examples/georust.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use galileo::galileo_map::MapBuilder;
use galileo::layer::feature_layer::symbol::point::CirclePointSymbol;
use galileo::layer::feature_layer::FeatureLayer;
use galileo::layer::feature_layer::{FeatureLayer, FeatureLayerOptions};
use galileo::symbol::point::ImagePointSymbol;
use galileo::tile_scheme::TileScheme;
use galileo::Color;
use galileo_types::disambig::{Disambig, Disambiguate};
use galileo_types::geo::crs::Crs;
use galileo_types::geometry_type::GeoSpace2d;
use galileo_types::latlon;
use geozero::geojson::GeoJson;
use geozero::ToGeo;
use nalgebra::Vector2;

#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
Expand All @@ -35,9 +35,18 @@ fn load_points() -> Vec<Disambig<geo_types::Point, GeoSpace2d>> {
pub async fn run(builder: MapBuilder) {
let point_layer = FeatureLayer::new(
load_points(),
CirclePointSymbol::new(Color::RED, 10.0),
ImagePointSymbol::from_file(
"galileo/examples/data/pin-yellow.png",
Vector2::new(0.5, 1.0),
0.5,
)
.unwrap(),
Crs::WGS84,
);
)
.with_options(FeatureLayerOptions {
sort_by_depth: true,
..Default::default()
});

builder
.center(latlon!(53.732562, -1.863383))
Expand Down
9 changes: 2 additions & 7 deletions galileo/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use galileo_mvt::error::GalileoMvtError;
use image::ImageError;
use std::io::Error;
use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -17,6 +16,8 @@ pub enum GalileoError {
ImageDecode(#[from] ImageError),
#[error("{0}")]
Generic(String),
#[error("failed to read file")]
FsIo(#[from] std::io::Error),
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -26,12 +27,6 @@ impl From<reqwest::Error> for GalileoError {
}
}

impl From<std::io::Error> for GalileoError {
fn from(_value: Error) -> Self {
Self::IO
}
}

#[cfg(target_arch = "wasm32")]
impl From<wasm_bindgen::JsValue> for GalileoError {
fn from(value: wasm_bindgen::JsValue) -> Self {
Expand Down
71 changes: 53 additions & 18 deletions galileo/src/layer/feature_layer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ use maybe_sync::{MaybeSend, MaybeSync};
use num_traits::AsPrimitive;
use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
use web_time::SystemTime;

pub mod feature;
pub mod symbol;

const BUFFER_SIZE_LIMIT: usize = 10_000_000;

pub struct FeatureLayer<P, F, S, Space>
where
F: Feature,
Expand All @@ -38,10 +35,41 @@ where
crs: Crs,
lods: Vec<Lod>,
messenger: RwLock<Option<Box<dyn Messenger>>>,
options: FeatureLayerOptions,

space: PhantomData<Space>,
}

#[derive(Debug, Copy, Clone)]
pub struct FeatureLayerOptions {
/// If set to true, images drawn by the layer will be sorted by the depth value (relative to viewer) before being
/// rendered.
///
/// This option is useful for layers that render points as images, and when the map is rendered in 3D you want the
/// images that are positioned behind other pins to be drawn behind. Without this option, the images are drawn in
/// the order they are added to the feature list.
///
/// Use this with caution though, as turning on this option affects performance drastically. You probably don't want
/// it if the layer will have more then a 1000 images drawn. If you decide to use this option for larger layers
/// anyway, don't forget to also increase [`buffer_size_limit`](FeatureLayerOptions::buffer_size_limit) as only
/// features from the same buffer will be sorted.
pub sort_by_depth: bool,

/// Sets up a soft limit on the internal GPU buffers' size (in bytes) used to render this layer. Larger values
/// slightly improve performance when rendering, bun drastically improve performance when updating just a
/// few features from the set.
pub buffer_size_limit: usize,
}

impl Default for FeatureLayerOptions {
fn default() -> Self {
Self {
sort_by_depth: false,
buffer_size_limit: 10_000_000,
}
}
}

struct Lod {
min_resolution: f64,
render_bundles: RwLock<Vec<RenderBundle>>,
Expand Down Expand Up @@ -72,6 +100,7 @@ where
packed_bundles: RwLock::new(vec![]),
feature_render_map: RwLock::new(Vec::new()),
}],
options: Default::default(),
space: Default::default(),
}
}
Expand All @@ -94,23 +123,33 @@ where
crs,
messenger: RwLock::new(None),
lods,
options: Default::default(),
space: Default::default(),
}
}

fn render_internal(&self, lod: &Lod, canvas: &mut dyn Canvas) {
pub fn with_options(mut self, options: FeatureLayerOptions) -> Self {
self.options = options;
self
}

fn render_internal(&self, lod: &Lod, canvas: &mut dyn Canvas, view: &MapView) {
let mut packed_bundles = lod.packed_bundles.write().unwrap();
let bundles = lod.render_bundles.read().unwrap();
for (index, bundle) in bundles.iter().enumerate() {
let mut bundles = lod.render_bundles.write().unwrap();
for (index, bundle) in bundles.iter_mut().enumerate() {
if packed_bundles.len() == index {
packed_bundles.push(None);
}
if packed_bundles[index].is_none() {

if self.options.sort_by_depth {
bundle.sort_by_depth(view);
}

if packed_bundles[index].is_none() || self.options.sort_by_depth {
packed_bundles[index] = Some(canvas.pack_bundle(bundle))
}
}

let start = SystemTime::now();
canvas.draw_bundles(
&packed_bundles
.iter()
Expand All @@ -120,10 +159,6 @@ where
antialias: self.symbol.use_antialiasing(),
},
);
log::info!(
"Packed everything in {} ms",
start.elapsed().unwrap().as_millis()
);
}
}

Expand Down Expand Up @@ -265,7 +300,7 @@ where
primitive_ids: ids,
});

if bundle.approx_buffer_size() > BUFFER_SIZE_LIMIT {
if bundle.approx_buffer_size() > self.options.buffer_size_limit {
let full_bundle = std::mem::replace(&mut bundle, canvas.create_bundle());
render_bundles.push(full_bundle);
}
Expand All @@ -274,7 +309,7 @@ where
render_bundles.push(bundle);
}

self.render_internal(lod, canvas);
self.render_internal(lod, canvas, view);
}

fn prepare(&self, _view: &MapView, _renderer: &Arc<RwLock<dyn Renderer>>) {
Expand Down Expand Up @@ -330,7 +365,7 @@ where
primitive_ids: ids,
});

if bundle.approx_buffer_size() > BUFFER_SIZE_LIMIT {
if bundle.approx_buffer_size() > self.options.buffer_size_limit {
let full_bundle = std::mem::replace(&mut bundle, canvas.create_bundle());
render_bundles.push(full_bundle);
}
Expand All @@ -339,7 +374,7 @@ where
render_bundles.push(bundle);
}

self.render_internal(lod, canvas);
self.render_internal(lod, canvas, view);
}

fn prepare(&self, _view: &MapView, _renderer: &Arc<RwLock<dyn Renderer>>) {
Expand Down Expand Up @@ -391,15 +426,15 @@ where
};
}

if bundle.approx_buffer_size() > BUFFER_SIZE_LIMIT {
if bundle.approx_buffer_size() > self.options.buffer_size_limit {
let full_bundle = std::mem::replace(&mut bundle, canvas.create_bundle());
render_bundles.push(full_bundle);
}

render_bundles.push(bundle);
}

self.render_internal(lod, canvas);
self.render_internal(lod, canvas, view);
}

fn prepare(&self, _view: &MapView, _renderer: &Arc<RwLock<dyn Renderer>>) {
Expand Down
66 changes: 66 additions & 0 deletions galileo/src/layer/feature_layer/symbol/point.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use crate::error::GalileoError;
use crate::layer::feature_layer::symbol::Symbol;
use crate::primitives::DecodedImage;
use crate::render::point_paint::PointPaint;
use crate::render::render_bundle::RenderBundle;
use crate::render::PrimitiveId;
use crate::Color;
use galileo_types::cartesian::traits::cartesian_point::CartesianPoint3d;
use galileo_types::geometry::Geom;
use galileo_types::multi_point::MultiPoint;
use image::GenericImageView;
use nalgebra::Vector2;
use num_traits::AsPrimitive;
use std::ops::Deref;
use std::sync::Arc;

pub struct CirclePointSymbol {
pub color: Color,
Expand Down Expand Up @@ -39,3 +45,63 @@ impl<F> Symbol<F> for CirclePointSymbol {
}
}
}

pub struct ImagePointSymbol {
image: Arc<DecodedImage>,
offset: Vector2<f32>,
scale: f32,
}

impl ImagePointSymbol {
pub fn from_file(path: &str, offset: Vector2<f32>, scale: f32) -> Result<Self, GalileoError> {
let image = image::io::Reader::open(path)?.decode()?;
let dimensions = image.dimensions();

Ok(Self {
image: Arc::new(DecodedImage {
bytes: Vec::from(image.to_rgba8().deref()),
dimensions,
}),
offset,
scale,
})
}
}

impl<F> Symbol<F> for ImagePointSymbol {
fn render<N: AsPrimitive<f32>, P: CartesianPoint3d<Num = N>>(
&self,
_feature: &F,
geometry: &Geom<P>,
bundle: &mut RenderBundle,
_min_resolution: f64,
) -> Vec<PrimitiveId> {
let paint = PointPaint::image(self.image.clone(), self.offset, self.scale);

match geometry {
Geom::Point(point) => vec![bundle.add_point(point, paint)],
Geom::MultiPoint(points) => points
.iter_points()
.map(|point| bundle.add_point(point, paint.clone()))
.collect(),
_ => vec![],
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn image_symbol_from_file() {
let symbol = ImagePointSymbol::from_file(
"examples/data/pin-yellow.png",
Vector2::new(0.5, 1.0),
1.0,
)
.unwrap();
assert_eq!(symbol.image.dimensions, (62, 99));
assert_eq!(symbol.image.bytes.len(), 62 * 99 * 4);
}
}
1 change: 1 addition & 0 deletions galileo/src/layer/raster_tile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ where
.unwrap();
let packed = canvas.pack_bundle(&rendered.render_bundle);
rendered.packed_bundle = packed;
rendered.is_opaque = is_opaque;
}
TileState::Loaded(decoded_image) => {
let mut bundle = canvas.create_bundle();
Expand Down
6 changes: 0 additions & 6 deletions galileo/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
use image::GenericImageView;
use maybe_sync::{MaybeSend, MaybeSync};
use std::any::Any;
use std::ops::Deref;

use crate::error::GalileoError;

pub trait Image: MaybeSend + MaybeSync {
fn as_any(&self) -> &dyn Any;
}

#[derive(Debug, Clone)]
pub struct DecodedImage {
pub bytes: Vec<u8>,
Expand Down
27 changes: 22 additions & 5 deletions galileo/src/render/point_paint.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::render::{LineCap, LinePaint, PreparedImage};
use crate::primitives::DecodedImage;
use crate::render::{LineCap, LinePaint};
use crate::Color;
use galileo_types::cartesian::impls::contour::ClosedContour;
use nalgebra::{Point2, Vector2};
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct PointPaint<'a> {
Expand Down Expand Up @@ -64,6 +66,20 @@ impl<'a> PointPaint<'a> {
}
}

pub fn image(image: Arc<DecodedImage>, offset: Vector2<f32>, scale: f32) -> Self {
let width = image.dimensions.0 as f32 * scale;
let height = image.dimensions.1 as f32 * scale;
Self {
offset,
shape: PointShape::Image {
image,
opacity: 255,
width,
height,
},
}
}

pub fn with_outline(mut self, color: Color, width: f32) -> Self {
match &mut self.shape {
PointShape::Circle { outline, .. }
Expand Down Expand Up @@ -105,10 +121,11 @@ pub(crate) enum PointShape<'a> {
outline: Option<LinePaint>,
shape: &'a ClosedContour<Point2<f32>>,
},
_Image {
source: PreparedImage,
width: Option<f32>,
height: Option<f32>,
Image {
image: Arc<DecodedImage>,
opacity: u8,
width: f32,
height: f32,
},
}

Expand Down
Loading

0 comments on commit 4e50e3c

Please sign in to comment.