use json::stringify;
use lexical;
use std::{
fmt::Write,
fs::File,
io::stdout,
path::PathBuf,
sync::mpsc::{Receiver, channel},
};
use crate::{
SkywayError,
chunks::{Chunk, ElementChunk, OrderedChunkIterator},
elements::{Element, ElementType, Metadata, SimpleElementType},
};
use super::Writer;
struct ToFmtWrite<T>(pub T);
impl<T> Write for ToFmtWrite<T>
where
T: std::io::Write,
{
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
}
}
fn create_header(metadata: Metadata, overpass: bool) -> String {
let mut header = String::from("{");
if let Some(g) = metadata.generator {
header.push_str("\"generator\":");
header.push_str(&stringify(g));
header.push(',');
}
if overpass {
let mut item_appended = false;
header.push_str("\"osm3s\":{");
if let Some(t) = metadata.timestamp {
header.push_str("\"timestamp_osm_base\":");
header.push_str(&stringify(t));
item_appended = true;
}
if let Some(c) = metadata.copyright {
if item_appended {
header.push(',')
};
header.push_str("\"copyright\":");
header.push_str(&stringify(c));
}
header.push_str("},\"version\":0.6,\"elements\":[");
} else {
if let Some(c) = metadata.copyright {
header.push_str("\"copyright\":");
header.push_str(&stringify(c));
header.push(',');
}
if let Some(l) = metadata.license {
header.push_str("\"license\":");
header.push_str(&stringify(l));
header.push(',');
}
header.push_str("\"version\":\"0.6\",\"elements\":[");
}
header
}
fn append_serialized_element(base: &mut String, element: Element) {
base.push_str("{\"type\":");
match element.element_type {
ElementType::Node { lat, lon } => {
base.push_str("\"node\"");
base.push_str(",\"lat\":");
base.push_str(&lexical::to_string(lat));
base.push_str(",\"lon\":");
base.push_str(&lexical::to_string(lon));
}
ElementType::Way { nodes } => {
base.push_str("\"way\",\"nodes\":[");
let mut first_node_appended = false;
for n in nodes {
if first_node_appended {
base.push(',');
}
first_node_appended = true;
base.push_str(&lexical::to_string(n));
}
base.push(']');
}
ElementType::Relation { members } => {
base.push_str("\"relation\",\"members\":[");
let mut first_node_appended = false;
for m in members {
if first_node_appended {
base.push(',');
}
first_node_appended = true;
base.push('{');
base.push_str("\"ref\":");
base.push_str(&lexical::to_string(m.id));
match m.t {
Some(SimpleElementType::Node) => base.push_str(",\"type\":\"node\""),
Some(SimpleElementType::Way) => base.push_str(",\"type\":\"way\""),
Some(SimpleElementType::Relation) => base.push_str(",\"type\":\"relation\""),
None => (),
}
base.push_str(",\"role\":");
match m.role {
Some(r) => base.push_str(&stringify(r)),
None => base.push_str("\"\""),
}
base.push('}');
}
base.push(']');
}
}
base.push_str(",\"id\":");
base.push_str(&lexical::to_string(element.id));
if let Some(c) = element.changeset {
base.push_str(",\"changeset\":");
base.push_str(&lexical::to_string(c));
}
if let Some(t) = element.timestamp {
base.push_str(",\"timestamp\":");
base.push_str(&stringify(t));
}
if let Some(u) = element.uid {
base.push_str(",\"uid\":");
base.push_str(&lexical::to_string(u));
}
if let Some(u) = element.user {
base.push_str(",\"user\":");
base.push_str(&stringify(u));
}
if element.visible == Some(false) {
base.push_str(",\"visible\":false");
}
if !element.tags.is_empty() {
base.push_str(",\"tags\":{");
let mut first_tag_appended = false;
for (k, v) in element.tags {
if first_tag_appended {
base.push(',');
}
first_tag_appended = true;
base.push_str(&stringify(k));
base.push(':');
base.push_str(&stringify(v));
}
base.push('}');
}
base.push('}');
}
fn serialize_chunk(chunk: ElementChunk) -> Chunk<String> {
let mut output = String::new();
let mut first_element_appended = false;
for element in chunk.content {
if first_element_appended {
output.push(',');
}
first_element_appended = true;
append_serialized_element(&mut output, element);
}
Chunk {
index: chunk.index,
content: output,
}
}
fn write_output(
metadata_receiver: Receiver<Metadata>,
data_receiver: Receiver<Chunk<String>>,
dest: impl std::io::Write,
overpass: bool,
) {
let metadata = metadata_receiver.into_iter().next();
let mut writer = ToFmtWrite(dest);
let header = create_header(metadata.unwrap(), overpass); writer
.write_str(&header)
.expect("Couldn't write opening metadata to output.");
let ordered_chunks = OrderedChunkIterator::new(data_receiver.into_iter());
let mut first_chunk_written = false;
for chunk_content in ordered_chunks {
if first_chunk_written {
writer
.write_str(",")
.expect("Failed to write comma between chunks");
}
first_chunk_written = true;
writer
.write_str(&chunk_content)
.expect("Failed to write chunk");
}
writer
.write_str("]}")
.expect("Couldn't write final closing curly brace to output.");
}
pub struct JsonWriter {
pub overpass: bool,
}
impl JsonWriter {
pub fn new(overpass: bool) -> Self {
JsonWriter { overpass }
}
}
impl Writer for JsonWriter {
fn write(
&self,
element_receiver: Receiver<ElementChunk>,
metadata_receiver: Receiver<Metadata>,
dest: Option<PathBuf>,
) -> Result<(), SkywayError> {
let (sender, receiver) = channel();
let overpass = self.overpass.clone();
let write_thread = std::thread::spawn({
move || {
match dest {
None => write_output(metadata_receiver, receiver, stdout(), overpass),
Some(a) => match File::create(PathBuf::from(a)) {
Ok(b) => write_output(metadata_receiver, receiver, b, overpass),
Err(e) => {
panic!("Unable to open output file: {e:?}");
}
},
};
}
});
for chunk in element_receiver {
sender
.send(serialize_chunk(chunk))
.expect("Failed to send serialized chunk.")
}
drop(sender);
write_thread.join().map_err(|e| {
SkywayError::UnexpectedError(format!("Could not join writer thread: {:?}", e))
})?;
Ok(())
}
}