Map server
This commit is contained in:
parent
c134280b63
commit
2082f7e211
|
@ -0,0 +1 @@
|
|||
mapserver/target
|
|
@ -33,7 +33,7 @@ If you make substancial modifications or fixes to the backend (Python) code, ple
|
|||
|
||||
## License
|
||||
|
||||
CopyLeft 2020-2021 Pascal Engélibert
|
||||
CopyLeft 2020-2022 Pascal Engélibert
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "mapserver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1.6.5", features = ["attributes"] }
|
||||
crossbeam-channel = "0.5.2"
|
||||
ctrlc = "3.2.1"
|
||||
http-types = "2.12.0"
|
||||
image = "0.24.1"
|
||||
log = "0.4.14"
|
||||
parking_lot = "0.12.0"
|
||||
serde = "1.0.117"
|
||||
simplelog = "0.11.1"
|
||||
tide = "0.16.0"
|
|
@ -0,0 +1,8 @@
|
|||
hard_tabs = true
|
||||
newline_style = "unix"
|
||||
|
||||
unstable_features = true
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_bodies = true
|
||||
format_macro_matchers = true
|
||||
format_strings = true
|
|
@ -0,0 +1,43 @@
|
|||
pub struct Config {
|
||||
/// Must start and end with `/`
|
||||
pub base_url: String,
|
||||
pub cache_age: u64,
|
||||
pub listen: String,
|
||||
pub mapper_threads: usize,
|
||||
pub minetestmapper_args: Vec<String>,
|
||||
pub minetestmapper_path: String,
|
||||
pub output_path: String,
|
||||
pub tile_size: usize,
|
||||
pub world_path: String,
|
||||
pub zoom_default: i32,
|
||||
pub zoom_max: i32,
|
||||
pub zoom_min: i32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base_url: String::from("/"),
|
||||
cache_age: 86400,
|
||||
listen: String::from("127.0.0.1:8080"),
|
||||
mapper_threads: 4,
|
||||
minetestmapper_args: vec![
|
||||
String::from("--drawalpha"),
|
||||
String::from("--noemptyimage"),
|
||||
String::from("--max-y"),
|
||||
String::from("2000"),
|
||||
String::from("--colors"),
|
||||
String::from("/home/tuxmain/Colors.txt/colors.txt"),
|
||||
],
|
||||
minetestmapper_path: String::from("/home/tuxmain/minetestmapper/minetestmapper"),
|
||||
output_path: String::from("/var/www/html/minetest-map/survival/"),
|
||||
tile_size: 256,
|
||||
world_path: String::from(
|
||||
"/var/games/minetest-server/.minetest/worlds/juneland-survival/",
|
||||
),
|
||||
zoom_default: 7,
|
||||
zoom_max: 10,
|
||||
zoom_min: 4,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
#![feature(try_blocks)]
|
||||
|
||||
mod config;
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use log::{debug, error, info, warn};
|
||||
use parking_lot::RwLock;
|
||||
use std::{collections::HashMap, io::Read, sync::Arc, time::SystemTime};
|
||||
use tide::Request;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Io(std::io::Error),
|
||||
Minetestmapper,
|
||||
Todo,
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
simplelog::TermLogger::init(
|
||||
simplelog::LevelFilter::Info,
|
||||
simplelog::Config::default(),
|
||||
simplelog::TerminalMode::Mixed,
|
||||
simplelog::ColorChoice::Auto,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = config::Config::default();
|
||||
assert!(
|
||||
config.base_url.starts_with('/') && config.base_url.ends_with('/'),
|
||||
"`base_url` must start and end with `/`"
|
||||
);
|
||||
let config = Arc::new(config);
|
||||
|
||||
let tasks = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
let mut app = tide::new();
|
||||
app.at(&format!("{}:z/:x/:y", config.base_url)).get({
|
||||
let config = config.clone();
|
||||
let tasks = tasks.clone();
|
||||
move |req| req_tile(req, tasks.clone(), config.clone())
|
||||
});
|
||||
app.listen(&config.listen).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_minetestmapper(
|
||||
z: i32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
tile_path: &str,
|
||||
config: Arc<config::Config>,
|
||||
) -> Result<(), Error> {
|
||||
debug!("Generating tile ({},{},{})", z, x, y);
|
||||
std::process::Command::new(&config.minetestmapper_path)
|
||||
.args([
|
||||
"-i",
|
||||
&config.world_path,
|
||||
"-o",
|
||||
tile_path,
|
||||
"--geometry",
|
||||
&format!(
|
||||
"{}:{}+{}+{}",
|
||||
x * config.tile_size as i32,
|
||||
-1 - z * config.tile_size as i32,
|
||||
config.tile_size,
|
||||
config.tile_size
|
||||
),
|
||||
])
|
||||
.args(&config.minetestmapper_args)
|
||||
.output()
|
||||
.map_err(|e| {
|
||||
error!("Generating tile ({},{},{}): {}", z, x, y, e);
|
||||
Error::Minetestmapper
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tile_file(tile_path: &str) -> Result<Vec<u8>, Error> {
|
||||
let mut tile_content = Vec::new();
|
||||
std::fs::File::open(&tile_path)
|
||||
.map_err(Error::Io)?
|
||||
.read(&mut tile_content)
|
||||
.map_err(Error::Io)?; // TODO resp_tile if error
|
||||
Ok(tile_content)
|
||||
}
|
||||
|
||||
fn generate_tile(
|
||||
z: i32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
tile_path: &str,
|
||||
tasks: Arc<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
|
||||
config: Arc<config::Config>,
|
||||
) -> Result<(), Error> {
|
||||
let mut generate = true;
|
||||
loop {
|
||||
let mut tasks_guard = tasks.write();
|
||||
if let Some(sender) = tasks_guard.get(&(z, x, y)) {
|
||||
let sender = sender.clone();
|
||||
drop(tasks_guard);
|
||||
generate = false;
|
||||
sender.send(()).ok();
|
||||
} else {
|
||||
if generate {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(0);
|
||||
tasks_guard.insert((z, x, y), sender);
|
||||
drop(tasks_guard);
|
||||
|
||||
let res: Result<(), Error> = try {
|
||||
if z == config.zoom_default {
|
||||
run_minetestmapper(z, x, y, tile_path, config)?;
|
||||
} else {
|
||||
Err(Error::Todo)?;
|
||||
}
|
||||
};
|
||||
|
||||
tasks.write().remove(&(z, x, y));
|
||||
receiver.recv().ok();
|
||||
res?;
|
||||
} else {
|
||||
drop(tasks_guard);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resp_tile(
|
||||
z: i32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
tile_path: &str,
|
||||
tasks: Arc<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
|
||||
config: Arc<config::Config>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
generate_tile(z, x, y, tile_path, tasks, config)?;
|
||||
read_tile_file(tile_path)
|
||||
}
|
||||
|
||||
async fn req_tile(
|
||||
req: Request<()>,
|
||||
tasks: Arc<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
|
||||
config: Arc<config::Config>,
|
||||
) -> tide::Result {
|
||||
let z: i32 = req.param("z")?.parse()?;
|
||||
let x: i32 = req.param("x")?.parse()?;
|
||||
let y: i32 = req.param("y")?.parse()?;
|
||||
|
||||
let tile_path = format!("{}/{}/{}/{}.png", config.output_path, z, x, y);
|
||||
|
||||
match std::fs::metadata(&tile_path) {
|
||||
Ok(metadata) => match metadata.modified() {
|
||||
Ok(modified_time) => {
|
||||
if SystemTime::now()
|
||||
.duration_since(modified_time)
|
||||
.unwrap()
|
||||
.as_secs() > config.cache_age
|
||||
{
|
||||
match resp_tile(z, x, y, &tile_path, tasks, config) {
|
||||
Ok(tile_content) => Ok(tide::Response::builder(200)
|
||||
.content_type(http_types::mime::PNG)
|
||||
.body(tile_content)
|
||||
.build()),
|
||||
Err(_) => Ok(tide::StatusCode::InternalServerError.into()),
|
||||
}
|
||||
} else {
|
||||
match read_tile_file(&tile_path) {
|
||||
Ok(tile_content) => Ok(tide::Response::builder(200)
|
||||
.content_type(http_types::mime::PNG)
|
||||
.body(tile_content)
|
||||
.build()),
|
||||
Err(_) => Ok(tide::StatusCode::InternalServerError.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Cannot get modified time of `{}`: {}", tile_path, e);
|
||||
match resp_tile(z, x, y, &tile_path, tasks, config) {
|
||||
Ok(tile_content) => Ok(tide::Response::builder(200)
|
||||
.content_type(http_types::mime::PNG)
|
||||
.body(tile_content)
|
||||
.build()),
|
||||
Err(_) => Ok(tide::StatusCode::InternalServerError.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
debug!("Tile not found `{}`", tile_path);
|
||||
} else {
|
||||
warn!("Cannot get metadata of `{}`: {}", tile_path, e);
|
||||
}
|
||||
match resp_tile(z, x, y, &tile_path, tasks, config) {
|
||||
Ok(tile_content) => Ok(tide::Response::builder(200)
|
||||
.content_type(http_types::mime::PNG)
|
||||
.body(tile_content)
|
||||
.build()),
|
||||
Err(_) => Ok(tide::StatusCode::InternalServerError.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue