use crate::error::Result;
use crate::protocol::Message;
use crate::stream::{BackpressureController, DeltaEncoder, TileData};
use crate::subscription::SubscriptionManager;
use dashmap::DashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, warn};
pub struct TileHandler {
subscriptions: Arc<SubscriptionManager>,
client_senders: Arc<DashMap<String, mpsc::UnboundedSender<Message>>>,
delta_encoder: Arc<DeltaEncoder>,
backpressure: Arc<DashMap<String, BackpressureController>>,
enable_delta: bool,
}
impl TileHandler {
pub fn new(subscriptions: Arc<SubscriptionManager>) -> Self {
Self {
subscriptions,
client_senders: Arc::new(DashMap::new()),
delta_encoder: Arc::new(DeltaEncoder::new()),
backpressure: Arc::new(DashMap::new()),
enable_delta: true,
}
}
pub fn register_client(&self, client_id: String, sender: mpsc::UnboundedSender<Message>) {
self.client_senders.insert(client_id.clone(), sender);
self.backpressure
.insert(client_id, BackpressureController::new(1000));
}
pub fn unregister_client(&self, client_id: &str) {
self.client_senders.remove(client_id);
self.backpressure.remove(client_id);
}
pub async fn stream_tile(&self, tile: TileData) -> Result<usize> {
let (x, y, zoom) = tile.coords();
debug!("Streaming tile: x={}, y={}, z={}", x, y, zoom);
let subscriptions = self.subscriptions.find_tile_subscriptions(x, y, zoom);
if subscriptions.is_empty() {
return Ok(0);
}
let mut sent_count = 0;
for subscription in subscriptions {
let client_id = &subscription.client_id;
if let Some(controller) = self.backpressure.get(client_id) {
if controller.should_drop() {
warn!("Dropping tile for client {} due to backpressure", client_id);
continue;
}
}
let data = if self.enable_delta {
self.delta_encoder.encode(&tile)?
} else {
tile.data.to_vec()
};
if let Some(sender) = self.client_senders.get(client_id) {
let message = Message::TileData {
subscription_id: subscription.id.clone(),
tile: (x, y, zoom),
data,
mime_type: tile.mime_type.clone(),
};
if sender.send(message).is_ok() {
sent_count += 1;
} else {
warn!("Failed to send tile to client {}", client_id);
}
}
}
Ok(sent_count)
}
pub async fn stream_tiles(&self, tiles: Vec<TileData>) -> Result<usize> {
let mut total_sent = 0;
for tile in tiles {
total_sent += self.stream_tile(tile).await?;
}
Ok(total_sent)
}
pub async fn generate_viewport_tiles(&self, bbox: [f64; 4], zoom: u8) -> Result<Vec<TileData>> {
let tiles = Self::bbox_to_tiles(bbox, zoom);
let mut tile_data = Vec::new();
for (x, y) in tiles {
let data = vec![0u8; 256]; tile_data.push(TileData::new(
x,
y,
zoom,
data,
"application/x-protobuf".to_string(),
));
}
Ok(tile_data)
}
fn bbox_to_tiles(bbox: [f64; 4], zoom: u8) -> Vec<(u32, u32)> {
let n = 2_u32.pow(zoom.into());
let min_x = Self::lon_to_tile_x(bbox[0], n);
let max_x = Self::lon_to_tile_x(bbox[2], n);
let min_y = Self::lat_to_tile_y(bbox[3], n); let max_y = Self::lat_to_tile_y(bbox[1], n);
let mut tiles = Vec::new();
for x in min_x..=max_x {
for y in min_y..=max_y {
tiles.push((x, y));
}
}
tiles
}
fn lon_to_tile_x(lon: f64, n: u32) -> u32 {
let x = ((lon + 180.0) / 360.0 * n as f64).floor() as u32;
x.min(n.saturating_sub(1))
}
fn lat_to_tile_y(lat: f64, n: u32) -> u32 {
let lat_rad = lat.to_radians();
((1.0 - lat_rad.tan().asinh() / std::f64::consts::PI) / 2.0 * n as f64).floor() as u32
}
pub async fn prefetch_tiles(
&self,
current_bbox: [f64; 4],
zoom: u8,
direction: Direction,
) -> Result<Vec<TileData>> {
let prefetch_bbox = Self::expand_bbox(current_bbox, direction, 0.5);
self.generate_viewport_tiles(prefetch_bbox, zoom).await
}
fn expand_bbox(bbox: [f64; 4], direction: Direction, factor: f64) -> [f64; 4] {
let width = bbox[2] - bbox[0];
let height = bbox[3] - bbox[1];
match direction {
Direction::North => [bbox[0], bbox[1], bbox[2], bbox[3] + height * factor],
Direction::South => [bbox[0], bbox[1] - height * factor, bbox[2], bbox[3]],
Direction::East => [bbox[0], bbox[1], bbox[2] + width * factor, bbox[3]],
Direction::West => [bbox[0] - width * factor, bbox[1], bbox[2], bbox[3]],
}
}
pub fn clear_delta_cache(&self) {
self.delta_encoder.clear();
}
pub fn delta_cache_size(&self) -> usize {
self.delta_encoder.cache_size()
}
pub fn set_delta_enabled(&mut self, enabled: bool) {
self.enable_delta = enabled;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bbox_to_tiles() {
let bbox = [-180.0, -85.0, 180.0, 85.0];
let tiles = TileHandler::bbox_to_tiles(bbox, 0);
assert_eq!(tiles.len(), 1);
assert_eq!(tiles[0], (0, 0));
}
#[test]
fn test_lon_to_tile_x() {
let n = 2_u32.pow(1);
assert_eq!(TileHandler::lon_to_tile_x(-180.0, n), 0);
assert_eq!(TileHandler::lon_to_tile_x(0.0, n), 1);
assert_eq!(TileHandler::lon_to_tile_x(180.0, n), 1);
}
#[test]
fn test_expand_bbox() {
let bbox = [0.0, 0.0, 10.0, 10.0];
let north = TileHandler::expand_bbox(bbox, Direction::North, 0.5);
assert_eq!(north, [0.0, 0.0, 10.0, 15.0]);
let east = TileHandler::expand_bbox(bbox, Direction::East, 0.5);
assert_eq!(east, [0.0, 0.0, 15.0, 10.0]);
}
#[tokio::test]
async fn test_tile_handler_creation() {
let subscriptions = Arc::new(SubscriptionManager::new());
let handler = TileHandler::new(subscriptions);
assert_eq!(handler.delta_cache_size(), 0);
assert!(handler.enable_delta);
}
}