used OSM Renderer as example: https://github.com/dfyz/osm-renderer
Project covers:
Way
nodes;Way
using nodes coordinates translated to Tiles coordinates using Cairo library.Complete source code:
use std::{
collections::{HashMap, HashSet},
f64::consts::PI,
fs::File,
io::{BufReader, BufWriter},
time::Instant,
};
use cairo::{Context, ImageSurface};
use ciborium::from_reader;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct Node {
#[serde(rename = "@id")]
pub id: u64,
#[serde(rename = "@lat")]
pub lat: f64,
#[serde(rename = "@lon")]
pub lon: f64,
pub tag: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize)]
pub struct Nd {
#[serde(rename = "@ref")]
pub reference: u64,
}
#[derive(Deserialize, Serialize)]
pub struct Tag {
#[serde(rename = "@k")]
pub k: String,
#[serde(rename = "@v")]
pub v: String,
}
#[derive(Deserialize, Serialize)]
pub struct Way {
#[serde(rename = "@id")]
pub id: u64,
pub nd: Vec<Nd>,
pub tag: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize)]
pub struct Osm {
pub node: Vec<Node>,
pub way: Vec<Way>,
}
fn build_index() {
let start = Instant::now();
let osm_path = "moldova-latest.osm";
let buffer = BufReader::new(File::open(osm_path).unwrap());
let osm: Osm = quick_xml::de::from_reader(buffer).unwrap();
println!("nodes: {}", osm.node.len());
println!("ways: {}", osm.way.len());
println!("loading from osm: {:?}", start.elapsed());
let writer = BufWriter::new(File::create("test.bin").unwrap());
ciborium::ser::into_writer(&osm, writer).unwrap();
let start = Instant::now();
let mut osm: Osm = from_reader(BufReader::new(File::open("test.bin").unwrap())).unwrap();
println!("nodes: {}", osm.node.len());
println!("ways: {}", osm.way.len());
let mut filters = HashMap::<String, HashSet<String>>::new();
filters.insert(
"highway".to_string(),
HashSet::from_iter(
vec![
"primary",
"secondary",
"trunk",
"motorway",
"primary_link",
"tertiary",
"residential",
"service",
"unclassified",
]
.iter()
.map(|item| item.to_string())
.collect::<Vec<String>>(),
),
);
osm.way = filter(osm.way, &filters);
println!("ways: {}", osm.way.len());
let items: HashSet<u64> = osm
.way
.iter()
.flat_map(|item| item.nd.iter().map(|item| item.reference))
.collect();
osm.node = osm.node.into_iter().filter(|item| items.contains(&item.id)).collect();
let writer = BufWriter::new(File::create("test-filter.bin").unwrap());
ciborium::ser::into_writer(&osm, writer).unwrap();
println!("loading from binary: {:?}", start.elapsed());
let start = Instant::now();
let osm: Osm = from_reader(BufReader::new(File::open("test-filter.bin").unwrap())).unwrap();
println!("nodes: {}", osm.node.len());
println!("ways: {}", osm.way.len());
let mapped: HashMap<u64, (f64, f64)> = osm
.node
.iter()
.fold(HashMap::<u64, (f64, f64)>::new(), |mut acc, item| {
acc.insert(item.id, convert_to_tile(item, 12));
acc
});
let x_s: Vec<f64> = mapped.iter().map(|a| a.1 .0).collect();
let min_x = x_s.iter().max_by(|a, b| b.total_cmp(&a)).unwrap();
let max_x = x_s.iter().max_by(|a, b| a.total_cmp(&b)).unwrap();
let y_s: Vec<f64> = mapped.iter().map(|a| a.1 .1).collect();
let min_y = y_s.iter().max_by(|a, b| b.total_cmp(&a)).unwrap();
let max_y = y_s.iter().max_by(|a, b| a.total_cmp(&b)).unwrap();
println!("coordinated: {:?} {:?} {:?} {:?}", min_x, min_y, max_x, max_y);
println!("loading from binary: {:?}", start.elapsed());
draw_to_image(
&mapped,
*min_x,
*min_y,
&osm.way,
(max_x - min_x) as i32,
(max_y - min_y) as i32,
);
}
fn draw_to_image(
mapped_nodes: &HashMap<u64, (f64, f64)>,
min_x: f64,
min_y: f64,
way: &Vec<Way>,
width: i32,
height: i32,
) {
println!("creating image width: {} height: {}", width, height);
let surface = ImageSurface::create(cairo::Format::Rgb24, width, height).unwrap();
let context = Context::new(&surface).unwrap();
context.set_source_rgb(0.2, 0.2, 0.2);
context.paint().unwrap();
context.set_line_width(1f64);
context.set_line_join(cairo::LineJoin::Round);
context.set_source_rgb(0.5, 0.5, 0.5);
way.iter().for_each(|way| {
way.nd.iter().for_each(|node| {
let point = mapped_nodes.get(&node.reference).unwrap();
let x = point.0 - min_x;
let y = point.1 - min_y;
// println!("draw line {} {}", x, y);
context.line_to(x, y);
});
// println!("done drawing line");
context.stroke().unwrap();
});
let mut file = File::create("image.png").unwrap();
surface.write_to_png(&mut file).unwrap();
}
fn filter(way: Vec<Way>, filter: &HashMap<String, HashSet<String>>) -> Vec<Way> {
way.into_iter()
.filter(|item| {
if let Some(tag) = &item.tag {
tag.iter()
.filter(|item| filter.get(&item.k).is_some() && filter.get(&item.k).unwrap().contains(&item.v))
.count()
> 0usize
} else {
false
}
})
.collect()
}
const TILE_SIZE: u32 = 256;
fn convert_to_tile(node: &Node, zoom: u8) -> (f64, f64) {
let (lat_rad, lon_rad) = (node.lat.to_radians(), node.lon.to_radians());
let x = lon_rad + PI;
let y = PI - ((PI / 4f64) + (lat_rad / 2f64)).tan().ln();
let rescale = |x: f64| {
let factor = x / (2f64 * PI);
let dimension_in_pixels = f64::from(TILE_SIZE * (1 << zoom));
factor * dimension_in_pixels
};
(rescale(x), rescale(y))
}
fn main() {
build_index();
}