Skip to content

Commit

Permalink
Now supports rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
Edouard Poitras committed Nov 15, 2024
1 parent cae9bb2 commit 6ce047c
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 112 deletions.
103 changes: 57 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,94 @@
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)

A helper bevy plugin to handle downloading OpenStreetMap-compliant [slippy tiles](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames). Configurable concurrency, retries, and rate limit settings.
A helper bevy plugin to handle downloading and displaying OpenStreetMap-compliant [slippy tiles](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).

[`DownloadSlippyTilesEvent`] can be fired to request one or more slippy tile downloads.

[`SlippyTileDownloadedEvent`] is fired when a requested slippy tile has been retrieved successfully. The file path is stored in the event and can be used with the asset loader.

## Example

Here's a snippet of the example in this crate. This app will load a slippy tile and it's surrounding 8 tiles at the latitude and longitude specified.
## Features

Run with: `cargo run --example simple`
- Automatic tile rendering with configurable reference point
- Control over tile Z-layer for proper rendering order
- Optional transform offset for precise positioning
- Toggle automatic rendering for manual control
- Configurable download settings (concurrency, retries, rate limits)

## Example

https://user-images.githubusercontent.com/14075649/214139995-c69fc4c7-634e-487a-af0d-a8ac42b6851f.mp4

Here's a snippet showing how to download and display map tiles at a specific location. This app will load a slippy tile and its surrounding 8 tiles at the specified latitude and longitude.

```rust,ignore
Run with: `cargo run --example simple`

// ...
```rust
use bevy::prelude::*;
use bevy_slippy_tiles::*;

const LATITUDE: f64 = 45.4111;
const LONGITUDE: f64 = -75.6980;

fn main() {
App::new()
// Our slippy tiles settings and plugin
// Configure settings with defaults
.insert_resource(SlippyTilesSettings {
endpoint: "https://tile.openstreetmap.org", // Tile server endpoint.
tiles_directory: "tiles/", // assets/ folder storing the slippy tile downloads.
reference_latitude: LATITUDE,
reference_longitude: LONGITUDE,
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_plugins(SlippyTilesPlugin);
// ...
.add_plugins(SlippyTilesPlugin)
.add_systems(Startup, (spawn_camera, request_slippy_tiles))
.run();
}

// ...
fn request_slippy_tiles(mut download_slippy_tile_events: EventWriter<DownloadSlippyTilesEvent>) {
// ...
let slippy_tile_event = DownloadSlippyTilesEvent {
tile_size: TileSize::Normal, // Size of tiles - Normal = 256px, Large = 512px (not all tile servers).
zoom_level: ZoomLevel::L18, // Map zoom level (L0 = entire world, L19 = closest zoom level).
tile_size: TileSize::Normal, // Size of tiles - Normal = 256px, Large = 512px
zoom_level: ZoomLevel::L18, // Map zoom level (L0 = entire world, L19 = closest)
coordinates: Coordinates::from_latitude_longitude(LATITUDE, LONGITUDE),
radius: Radius(1), // Request one layer of surrounding tiles (2 = two layers of surrounding tiles - 25 total, 3 = three layers of surrounding tiles - 49 total, etc).
use_cache: true, // Don't make request if already requested previously, or if file already exists in tiles directory.
radius: Radius(2), // Request surrounding tiles (2 = 25 tiles total)
use_cache: true, // Use cached tiles if available
};
download_slippy_tile_events.send(slippy_tile_event);
}
```

fn display_slippy_tiles(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut slippy_tile_downloaded_events: EventReader<SlippyTileDownloadedEvent>,
) {
for slippy_tile_downloaded_event in slippy_tile_downloaded_events.read() {
let zoom_level = slippy_tile_downloaded_event.zoom_level;
// ..
let tile_pixels = slippy_tile_downloaded_event.tile_size.to_pixels() as f32;
let transform_x = (current_x as f32 - center_x as f32) * tile_pixels;
let transform_y = (center_y as f32 - current_y as f32) * tile_pixels;
// Add our slippy tile to the screen.
commands.spawn(SpriteBundle {
texture: asset_server.load(slippy_tile_downloaded_event.path.clone()),
transform: Transform::from_xyz(transform_x, transform_y, 0.0),
..Default::default()
});
}
## Configuration

### SlippyTilesSettings

The plugin uses reasonable defaults but can be configured:
- `endpoint`: The tile server endpoint
- `tiles_directory`: The tile cache directory (where tiles will end up after being downloaded)
- `max_concurrent_downloads`: Maximum number of concurrent tile downloads
- `max_retries`: Maximum number of times a tile download will be retried upon failure
- `rate_limit_requests`: Maximum number of tile download requests within the rate limit window
- `rate_limit_window`: The duration of the rate limit window
- `reference_latitude`/`reference_longitude`: The geographic point that should appear at Transform(0,0,0) (or at transform_offset if specified)
- `transform_offset`: Optional Transform to offset where the reference point appears
- `z_layer`: Z coordinate for rendered tiles, useful for layering with other sprites
- `auto_render`: Toggle automatic tile rendering (disable for manual control)

```rust
SlippyTilesSettings {
endpoint: "https://tile.openstreetmap.org".into(), // Tile server endpoint
tiles_directory: "tiles/".into(), // Cache directory
max_concurrent_downloads: 4, // Concurrent downloads
max_retries: 3, // Download retry attempts
rate_limit_requests: 10, // Rate limit requests
rate_limit_window: Duration::from_secs(1), // Rate limit window
reference_latitude: 45.4111, // Reference latitude
reference_longitude: -75.6980, // Reference longitude
transform_offset: Some( // Optional offset from 0,0 (default: None)
Transform::from_xyz(100.0, 100.0, 0.0)
),
z_layer: 1.0, // Z coordinate for tiles (default: 0.0)
auto_render: true, // Enable automatic rendering (default: true)
}
```


## Bevy Compatibility

|bevy|bevy_slippy_tiles|
Expand Down
47 changes: 4 additions & 43 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy::prelude::*;
use bevy_slippy_tiles::{
Coordinates, DownloadSlippyTilesEvent, Radius, SlippyTileCoordinates,
SlippyTileDownloadedEvent, SlippyTilesPlugin, SlippyTilesSettings, TileSize, ZoomLevel,
Coordinates, DownloadSlippyTilesEvent, Radius, SlippyTilesPlugin, SlippyTilesSettings,
TileSize, ZoomLevel,
};

const LATITUDE: f64 = 45.4111;
Expand All @@ -10,18 +10,13 @@ const LONGITUDE: f64 = -75.6980;
fn main() {
App::new()
.insert_resource(SlippyTilesSettings {
//endpoint: "https://tile.openstreetmap.org".into(), // Tile server endpoint.
//tiles_directory: PathBuf::from("tiles/"), // Directory to store tiles.
//max_concurrent_downloads: 4,
//max_retries: 3,
//rate_limit_requests: 10,
//rate_limit_window: Duration::from_secs(1),
reference_latitude: LATITUDE,
reference_longitude: LONGITUDE,
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_plugins(SlippyTilesPlugin)
.add_systems(Startup, (spawn_camera, request_slippy_tiles))
.add_systems(Update, display_slippy_tiles)
.run();
}

Expand All @@ -43,37 +38,3 @@ fn request_slippy_tiles(mut download_slippy_tile_events: EventWriter<DownloadSli
};
download_slippy_tile_events.send(slippy_tile_event);
}

fn display_slippy_tiles(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut slippy_tile_downloaded_events: EventReader<SlippyTileDownloadedEvent>,
) {
for slippy_tile_downloaded_event in slippy_tile_downloaded_events.read() {
info!("Slippy tile fetched: {:?}", slippy_tile_downloaded_event);
let zoom_level = slippy_tile_downloaded_event.zoom_level;
// Convert our slippy tile position to pixels on the screen relative to the center tile.
let SlippyTileCoordinates {
x: center_x,
y: center_y,
} = Coordinates::from_latitude_longitude(LATITUDE, LONGITUDE)
.get_slippy_tile_coordinates(zoom_level);
let SlippyTileCoordinates {
x: current_x,
y: current_y,
} = slippy_tile_downloaded_event
.coordinates
.get_slippy_tile_coordinates(zoom_level);

let tile_pixels = slippy_tile_downloaded_event.tile_size.to_pixels() as f32;
let transform_x = (current_x as f32 - center_x as f32) * tile_pixels;
let transform_y = (center_y as f32 - current_y as f32) * tile_pixels;

// Add our slippy tile to the screen.
commands.spawn(SpriteBundle {
texture: asset_server.load(slippy_tile_downloaded_event.path.clone()),
transform: Transform::from_xyz(transform_x, transform_y, 0.0),
..Default::default()
});
}
}
62 changes: 62 additions & 0 deletions src/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::{
world_coords_to_world_pixel, LatitudeLongitudeCoordinates, SlippyTileDownloadedEvent,
SlippyTilesSettings,
};
use bevy::prelude::*;

/// Component to mark entities as map tiles
#[derive(Component)]
pub struct MapTile;

/// System to display tiles as they are downloaded
pub fn display_tiles(
mut commands: Commands,
asset_server: Res<AssetServer>,
settings: Res<SlippyTilesSettings>,
mut tile_events: EventReader<SlippyTileDownloadedEvent>,
) {
// Skip if auto-render is disabled
if !settings.auto_render {
return;
}

for event in tile_events.read() {
// Convert reference coordinates to pixel coordinates
let reference_point = LatitudeLongitudeCoordinates {
latitude: settings.reference_latitude,
longitude: settings.reference_longitude,
};
let (ref_x, ref_y) =
world_coords_to_world_pixel(&reference_point, event.tile_size, event.zoom_level);

// Convert tile coordinates to pixel coordinates
let current_coords = match event.coordinates {
crate::Coordinates::LatitudeLongitude(coords) => coords,
crate::Coordinates::SlippyTile(coords) => {
coords.to_latitude_longitude(event.zoom_level)
},
};
let (tile_x, tile_y) =
world_coords_to_world_pixel(&current_coords, event.tile_size, event.zoom_level);

// Calculate offset from reference point
let mut transform_x = (tile_x - ref_x) as f32;
let mut transform_y = (tile_y - ref_y) as f32;

// Apply optional transform offset
if let Some(offset) = &settings.transform_offset {
transform_x += offset.translation.x;
transform_y += offset.translation.y;
}

// Spawn the tile sprite
commands.spawn((
SpriteBundle {
texture: asset_server.load(event.path.clone()),
transform: Transform::from_xyz(transform_x, transform_y, settings.z_layer),
..default()
},
MapTile,
));
}
}
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

mod constants;
mod coordinates;
mod display;
mod download;
mod settings;
mod systems;
mod types;

pub use constants::*;
pub use coordinates::*;
pub use display::*;
pub use download::*;
pub use settings::*;
pub use types::*;
Expand All @@ -26,7 +28,8 @@ impl Plugin for SlippyTilesPlugin {
.add_event::<DownloadSlippyTilesEvent>()
.add_event::<SlippyTileDownloadedEvent>()
.add_systems(Update, systems::download_slippy_tiles)
.add_systems(Update, systems::download_slippy_tiles_completed);
.add_systems(Update, systems::download_slippy_tiles_completed)
.add_systems(Update, display::display_tiles);
}
}

Expand Down Expand Up @@ -65,9 +68,9 @@ mod tests {
tiles_directory: "tiles_directory".into(),
..Default::default()
};
assert_eq!(sts.get_endpoint(), "endpoint");
assert_eq!(sts.endpoint, "endpoint");
assert_eq!(
sts.get_tiles_directory(),
sts.tiles_directory,
std::path::PathBuf::from("tiles_directory")
);
assert_eq!(sts.get_tiles_directory_string(), "tiles_directory");
Expand Down
50 changes: 30 additions & 20 deletions src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
use bevy::prelude::Resource;
use bevy::prelude::{Resource, Transform};
use std::{path::PathBuf, time::Duration};

/// Type used to dictate various settings for this crate.
///
/// `endpoint` - Tile server endpoint (example: <https://tile.openstreetmap.org>).
/// TODO: Choose a few as backup and abide by usage policy - <https://wiki.openstreetmap.org/wiki/Tile_servers>
/// Download Settings:
/// - `endpoint` - Tile server endpoint (example: <https://tile.openstreetmap.org>)
/// - `tiles_directory` - The folder that all tiles will be stored in
/// - `max_concurrent_downloads` - Maximum number of concurrent tile downloads
/// - `max_retries` - Maximum number of retry attempts for failed downloads
/// - `rate_limit_requests` - Maximum number of requests allowed within the rate limit window
/// - `rate_limit_window` - Duration of the rate limit window
///
/// `tiles_directory` - The folder that all tiles will be stored in.
///
/// `max_concurrent_downloads` - Maximum number of concurrent tile downloads.
///
/// `max_retries` - Maximum number of retry attempts for failed downloads.
///
/// `rate_limit_requests` - Maximum number of requests allowed within the rate limit window.
///
/// `rate_limit_window` - Duration of the rate limit window.
/// Display Settings:
/// - `reference_latitude` - Latitude that maps to Transform(0,0,0) or transform_offset if specified
/// - `reference_longitude` - Longitude that maps to Transform(0,0,0) or transform_offset if specified
/// - `transform_offset` - Optional offset from 0,0 where the reference coordinates should appear
/// - `z_layer` - Z coordinate for rendered tiles
/// - `auto_render` - Whether tiles should be automatically rendered
#[derive(Clone, Resource)]
pub struct SlippyTilesSettings {
// Download settings
pub endpoint: String,
pub tiles_directory: PathBuf,
pub max_concurrent_downloads: usize,
pub max_retries: u32,
pub rate_limit_requests: usize,
pub rate_limit_window: Duration,

// Display settings
pub reference_latitude: f64,
pub reference_longitude: f64,
pub transform_offset: Option<Transform>,
pub z_layer: f32,
pub auto_render: bool,
}

impl SlippyTilesSettings {
pub fn get_endpoint(&self) -> String {
self.endpoint.clone()
}

pub fn get_tiles_directory(&self) -> PathBuf {
self.tiles_directory.clone()
}

pub fn get_tiles_directory_string(&self) -> String {
self.tiles_directory.as_path().to_str().unwrap().to_string()
}
Expand All @@ -42,12 +44,20 @@ impl SlippyTilesSettings {
impl Default for SlippyTilesSettings {
fn default() -> Self {
Self {
// Download defaults
endpoint: "https://tile.openstreetmap.org".into(),
tiles_directory: PathBuf::from("tiles/"),
max_concurrent_downloads: 4,
max_retries: 3,
rate_limit_requests: 10,
rate_limit_window: Duration::from_secs(1),

// Display defaults
reference_latitude: 0.0,
reference_longitude: 0.0,
transform_offset: None,
z_layer: 0.0,
auto_render: true,
}
}
}

0 comments on commit 6ce047c

Please sign in to comment.