use errors::*;
use std::io::Read;
use xml::reader::XmlEvent;
use geo::Point;
use parser::extensions;
use parser::fix;
use parser::link;
use parser::string;
use parser::time;
use parser::verify_starting_tag;
use parser::Context;
use Waypoint;
use GpxVersion;
pub fn consume<R: Read>(context: &mut Context<R>, tagname: &'static str) -> Result<Waypoint> {
let attributes = verify_starting_tag(context, tagname)?;
let latitude = attributes
.iter()
.filter(|attr| attr.name.local_name == "lat")
.nth(0)
.ok_or(ErrorKind::InvalidElementLacksAttribute(
"latitude", "waypoint",
))?;
let latitude: f64 = latitude
.clone()
.value
.parse()
.chain_err(|| "error while casting latitude to f64")?;
let longitude = attributes
.iter()
.filter(|attr| attr.name.local_name == "lon")
.nth(0)
.ok_or(ErrorKind::InvalidElementLacksAttribute(
"longitude",
"waypoint",
))?;
let longitude: f64 = longitude
.clone()
.value
.parse()
.chain_err(|| "error while casting longitude to f64")?;
let mut waypoint: Waypoint = Waypoint::new(Point::new(longitude, latitude));
loop {
let next_event = {
if let Some(next) = context.reader.peek() {
next.clone()
} else {
break;
}
};
match next_event.chain_err(|| Error::from("error while parsing waypoint event"))? {
XmlEvent::StartElement { ref name, .. } => match name.local_name.as_ref() {
"ele" => {
waypoint.elevation = Some(
string::consume(context, "ele")?
.parse()
.chain_err(|| "error while casting elevation to f64")?,
)
}
"speed" if context.version == GpxVersion::Gpx10 => {
waypoint.speed = Some(
string::consume(context, "speed")?
.parse()
.chain_err(|| "error while casting speed to f64")?,
);
}
"time" => waypoint.time = Some(time::consume(context)?),
"name" => waypoint.name = Some(string::consume(context, "name")?),
"cmt" => waypoint.comment = Some(string::consume(context, "cmt")?),
"desc" => waypoint.description = Some(string::consume(context, "desc")?),
"src" => waypoint.source = Some(string::consume(context, "src")?),
"link" => waypoint.links.push(link::consume(context)?),
"sym" => waypoint.symbol = Some(string::consume(context, "sym")?),
"type" => waypoint._type = Some(string::consume(context, "type")?),
"fix" => waypoint.fix = Some(fix::consume(context)?),
"geoidheight" => {
waypoint.geoidheight = Some(
string::consume(context, "geoidheight")?
.parse()
.chain_err(|| "error while casting geoid (geoidheight) to f64")?,
)
}
"sat" => {
waypoint.sat = Some(
string::consume(context, "sat")?
.parse()
.chain_err(|| "error while casting number of satellites (sat) to u64")?,
)
}
"hdop" => {
waypoint.hdop = Some(string::consume(context, "hdop")?.parse().chain_err(
|| "error while casting horizontal dilution of precision (hdop) to f64",
)?)
}
"vdop" => {
waypoint.vdop = Some(string::consume(context, "vdop")?.parse().chain_err(
|| "error while casting vertical dilution of precision (vdop) to f64",
)?)
}
"pdop" => {
waypoint.pdop = Some(string::consume(context, "pdop")?.parse().chain_err(
|| "error while casting position dilution of precision (pdop) to f64",
)?)
}
"ageofgpsdata" => {
waypoint.age = Some(
string::consume(context, "ageofgpsdata")?
.parse()
.chain_err(|| "error while casting age of GPS data to f64")?,
)
}
"dgpsid" => {
waypoint.dgpsid = Some(
string::consume(context, "dgpsid")?
.parse()
.chain_err(|| "error while casting DGPS station ID to u16")?,
)
}
"extensions" => extensions::consume(context)?,
child => {
bail!(ErrorKind::InvalidChildElement(
String::from(child),
"waypoint"
));
}
},
XmlEvent::EndElement { ref name } => {
ensure!(
name.local_name == tagname,
ErrorKind::InvalidClosingTag(name.local_name.clone(), "waypoint")
);
context.reader.next(); return Ok(waypoint);
}
_ => {
context.reader.next(); }
}
}
bail!(ErrorKind::MissingClosingTag("waypoint"));
}
#[cfg(test)]
mod tests {
use geo::Point;
use std::io::BufReader;
use super::consume;
use Fix;
use GpxVersion;
#[test]
fn consume_waypoint() {
let waypoint = consume!(
"
<wpt lon=\"-77.0365\" lat=\"38.8977\">
<name>The White House</name>
<cmt>This is a comment about the white house.</cmt>
<desc>The white house is very nice!</desc>
<src>Garmin eTrex</src>
<type>waypoint classification</type>
<ele>4608.12</ele>
<fix>dgps</fix>
<sat>4</sat>
<hdop>6.058</hdop>
<speed>0.0000</speed>
</wpt>
",
GpxVersion::Gpx10,
"wpt"
);
assert!(waypoint.is_ok());
let waypoint = waypoint.unwrap();
assert_eq!(waypoint.point(), Point::new(-77.0365, 38.8977));
assert_eq!(waypoint.name.unwrap(), "The White House");
assert_eq!(
waypoint.comment.unwrap(),
"This is a comment about the white house."
);
assert_eq!(
waypoint.description.unwrap(),
"The white house is very nice!"
);
assert_eq!(waypoint.source.unwrap(), "Garmin eTrex");
assert_eq!(waypoint._type.unwrap(), "waypoint classification");
assert_eq!(waypoint.elevation.unwrap(), 4608.12);
assert_eq!(waypoint.fix.unwrap(), Fix::DGPS);
assert_eq!(waypoint.sat.unwrap(), 4);
assert_eq!(waypoint.hdop.unwrap(), 6.058);
}
#[test]
fn consume_empty() {
let waypoint = consume!(
"<trkpt lat=\"2.345\" lon=\"1.234\"></trkpt>",
GpxVersion::Gpx11,
"trkpt"
);
assert!(waypoint.is_ok());
let waypoint = waypoint.unwrap();
assert_eq!(waypoint.point(), Point::new(1.234, 2.345));
assert_eq!(waypoint.point().lng(), 1.234);
assert_eq!(waypoint.point().lat(), 2.345);
}
#[test]
fn consume_bad_waypoint() {
let waypoint = consume!(
"<wpt lat=\"32.4\" lon=\"notanumber\"></wpt>",
GpxVersion::Gpx11,
"wpt"
);
assert!(waypoint.is_err());
}
}