-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add kml:Link, kml:Icon #28
Changes from all commits
371945e
6e83cff
4ec2c4e
0b86a26
8b0a6ad
5f70a96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,9 +16,9 @@ use crate::errors::Error; | |
use crate::types::geom_props::GeomProps; | ||
use crate::types::{ | ||
self, coords_from_str, BalloonStyle, ColorMode, Coord, CoordType, Element, Geometry, Icon, | ||
IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, | ||
ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle, Polygon, | ||
Scale, Style, StyleMap, Units, Vec2, | ||
IconStyle, Kml, KmlDocument, KmlVersion, LabelStyle, LineString, LineStyle, LinearRing, Link, | ||
LinkTypeIcon, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, | ||
PolyStyle, Polygon, RefreshMode, Scale, Style, StyleMap, Units, Vec2, ViewRefreshMode, | ||
}; | ||
|
||
/// Main struct for reading KML documents | ||
|
@@ -158,7 +158,10 @@ where | |
elements.push(Kml::BalloonStyle(self.read_balloon_style(attrs)?)) | ||
} | ||
b"IconStyle" => elements.push(Kml::IconStyle(self.read_icon_style(attrs)?)), | ||
b"Icon" => elements.push(Kml::Icon(self.read_icon()?)), | ||
b"Link" => elements.push(Kml::Link(self.read_link(attrs)?)), | ||
b"Icon" => { | ||
elements.push(Kml::LinkTypeIcon(self.read_link_type_icon(attrs)?)) | ||
} | ||
b"LabelStyle" => { | ||
elements.push(Kml::LabelStyle(self.read_label_style(attrs)?)) | ||
} | ||
|
@@ -560,7 +563,7 @@ where | |
}); | ||
} | ||
} | ||
b"Icon" => icon_style.icon = self.read_icon()?, | ||
b"Icon" => icon_style.icon = self.read_basic_link_type_icon()?, | ||
b"color" => icon_style.color = self.read_str()?, | ||
b"colorMode" => { | ||
icon_style.color_mode = self.read_str()?.parse::<ColorMode>()? | ||
|
@@ -578,7 +581,7 @@ where | |
Ok(icon_style) | ||
} | ||
|
||
fn read_icon(&mut self) -> Result<Icon, Error> { | ||
fn read_basic_link_type_icon(&mut self) -> Result<Icon, Error> { | ||
let mut href = String::new(); | ||
loop { | ||
let mut e = self.reader.read_event(&mut self.buf)?; | ||
|
@@ -599,6 +602,77 @@ where | |
Ok(Icon { href }) | ||
} | ||
|
||
fn read_link_type_icon( | ||
&mut self, | ||
attrs: HashMap<String, String>, | ||
) -> Result<LinkTypeIcon, Error> { | ||
let mut icon = LinkTypeIcon { | ||
attrs, | ||
..Default::default() | ||
}; | ||
loop { | ||
let mut e = self.reader.read_event(&mut self.buf)?; | ||
match e { | ||
Event::Start(ref mut e) => match e.local_name() { | ||
b"href" => icon.href = Some(self.read_str()?), | ||
b"refreshMode" => { | ||
icon.refresh_mode = Some(RefreshMode::from_str(&self.read_str()?)?); | ||
} | ||
b"refreshInterval" => icon.refresh_interval = self.read_float()?, | ||
b"viewRefreshMode" => { | ||
icon.view_refresh_mode = Some(ViewRefreshMode::from_str(&self.read_str()?)?) | ||
} | ||
b"viewRefreshTime" => icon.view_refresh_time = self.read_float()?, | ||
b"viewBoundScale" => icon.view_bound_scale = self.read_float()?, | ||
b"viewFormat" => icon.view_format = Some(self.read_str()?), | ||
b"httpQuery" => icon.http_query = Some(self.read_str()?), | ||
_ => {} | ||
}, | ||
Event::End(ref mut e) => { | ||
if e.local_name() == b"Icon" { | ||
break; | ||
} | ||
} | ||
_ => break, | ||
} | ||
} | ||
Ok(icon) | ||
} | ||
|
||
fn read_link(&mut self, attrs: HashMap<String, String>) -> Result<Link, Error> { | ||
let mut link = Link { | ||
attrs, | ||
..Default::default() | ||
}; | ||
loop { | ||
let mut e = self.reader.read_event(&mut self.buf)?; | ||
match e { | ||
Event::Start(ref mut e) => match e.local_name() { | ||
b"href" => link.href = Some(self.read_str()?), | ||
b"refreshMode" => { | ||
link.refresh_mode = Some(RefreshMode::from_str(&self.read_str()?)?); | ||
} | ||
b"refreshInterval" => link.refresh_interval = self.read_float()?, | ||
b"viewRefreshMode" => { | ||
link.view_refresh_mode = Some(ViewRefreshMode::from_str(&self.read_str()?)?) | ||
} | ||
b"viewRefreshTime" => link.view_refresh_time = self.read_float()?, | ||
b"viewBoundScale" => link.view_bound_scale = self.read_float()?, | ||
b"viewFormat" => link.view_format = Some(self.read_str()?), | ||
b"httpQuery" => link.http_query = Some(self.read_str()?), | ||
_ => {} | ||
}, | ||
Event::End(ref mut e) => { | ||
if e.local_name() == b"Link" { | ||
break; | ||
} | ||
} | ||
_ => break, | ||
} | ||
} | ||
Ok(link) | ||
} | ||
|
||
fn read_balloon_style( | ||
&mut self, | ||
attrs: HashMap<String, String>, | ||
|
@@ -930,6 +1004,64 @@ mod tests { | |
); | ||
} | ||
|
||
#[test] | ||
fn test_read_link() { | ||
let kml_str = r#"<Link id="Some ID"> | ||
<href>/path/to/local/resource</href> | ||
<refreshMode>onChange</refreshMode> | ||
<refreshInterval>4</refreshInterval> | ||
<viewRefreshMode>onStop</viewRefreshMode> | ||
<viewRefreshTime>4</viewRefreshTime> | ||
<viewBoundScale>1</viewBoundScale> | ||
<viewFormat></viewFormat> | ||
</Link>"#; | ||
|
||
let mut attrs = HashMap::new(); | ||
attrs.insert("id".to_string(), "Some ID".to_string()); | ||
|
||
let l: Kml = kml_str.parse().unwrap(); | ||
assert_eq!( | ||
l, | ||
Kml::Link(Link { | ||
href: Some("/path/to/local/resource".to_string()), | ||
refresh_mode: Some(types::RefreshMode::OnChange), | ||
view_refresh_mode: Some(types::ViewRefreshMode::OnStop), | ||
view_format: Some(String::new()), | ||
attrs, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to store the attributes? I see some other types do, but most ignore them. The spec only allows three here: As an aside, I could see how many attributes could be an issue with memory. Perhaps that's a separate issue to be solved in another PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think enough of the other types include |
||
..Default::default() | ||
}) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_read_link_type_icon() { | ||
let kml_str = r#"<Icon id="Some ID"> | ||
<href>/path/to/local/resource</href> | ||
<refreshMode>onChange</refreshMode> | ||
<refreshInterval>4</refreshInterval> | ||
<viewRefreshMode>onStop</viewRefreshMode> | ||
<viewRefreshTime>4</viewRefreshTime> | ||
<viewBoundScale>1</viewBoundScale> | ||
<viewFormat></viewFormat> | ||
</Icon>"#; | ||
|
||
let mut attrs = HashMap::new(); | ||
attrs.insert("id".to_string(), "Some ID".to_string()); | ||
|
||
let l: Kml = kml_str.parse().unwrap(); | ||
assert_eq!( | ||
l, | ||
Kml::LinkTypeIcon(LinkTypeIcon { | ||
href: Some("/path/to/local/resource".to_string()), | ||
refresh_mode: Some(types::RefreshMode::OnChange), | ||
view_refresh_mode: Some(types::ViewRefreshMode::OnStop), | ||
view_format: Some(String::new()), | ||
attrs, | ||
..Default::default() | ||
}) | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_parse_scale() { | ||
let kml_str = r#"<Scale> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::str::FromStr; | ||
|
||
use crate::Error; | ||
|
||
/// `kml:Link`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct Link { | ||
pub href: Option<String>, | ||
pub refresh_mode: Option<RefreshMode>, | ||
pub refresh_interval: f64, | ||
pub view_refresh_mode: Option<ViewRefreshMode>, | ||
pub view_refresh_time: f64, | ||
pub view_bound_scale: f64, | ||
pub view_format: Option<String>, | ||
pub http_query: Option<String>, | ||
pub attrs: HashMap<String, String>, | ||
} | ||
|
||
impl Default for Link { | ||
fn default() -> Self { | ||
Self { | ||
href: None, | ||
refresh_mode: None, | ||
refresh_interval: 4.0, | ||
view_refresh_mode: None, | ||
view_refresh_time: 4.0, | ||
view_bound_scale: 1.0, | ||
view_format: None, | ||
http_query: None, | ||
attrs: HashMap::new(), | ||
} | ||
} | ||
} | ||
|
||
/// `kml:Icon`, [13.1](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#974) in the KML specification. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct Icon { | ||
pub href: Option<String>, | ||
pub refresh_mode: Option<RefreshMode>, | ||
pub refresh_interval: f64, | ||
pub view_refresh_mode: Option<ViewRefreshMode>, | ||
pub view_refresh_time: f64, | ||
pub view_bound_scale: f64, | ||
pub view_format: Option<String>, | ||
pub http_query: Option<String>, | ||
pub attrs: HashMap<String, String>, | ||
} | ||
|
||
impl Default for Icon { | ||
fn default() -> Self { | ||
Self { | ||
href: None, | ||
refresh_mode: None, | ||
refresh_interval: 4.0, | ||
view_refresh_mode: None, | ||
view_refresh_time: 4.0, | ||
view_bound_scale: 1.0, | ||
view_format: None, | ||
http_query: None, | ||
attrs: HashMap::new(), | ||
} | ||
} | ||
} | ||
|
||
/// `kml:refreshModeEnumType`, [16.21](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1239) in the KML specification. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub enum RefreshMode { | ||
OnChange, | ||
OnInterval, | ||
OnExpire, | ||
} | ||
|
||
impl Default for RefreshMode { | ||
fn default() -> RefreshMode { | ||
RefreshMode::OnChange | ||
} | ||
} | ||
|
||
impl FromStr for RefreshMode { | ||
type Err = Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
match s { | ||
"onChange" => Ok(Self::OnChange), | ||
"onInterval" => Ok(Self::OnInterval), | ||
"onExpire" => Ok(Self::OnExpire), | ||
v => Err(Error::InvalidRefreshMode(v.to_string())), | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for RefreshMode { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
RefreshMode::OnChange => write!(f, "onChange"), | ||
RefreshMode::OnInterval => write!(f, "onInterval"), | ||
RefreshMode::OnExpire => write!(f, "onExpire"), | ||
} | ||
} | ||
} | ||
|
||
/// `kml:viewRefreshModeEnumType`, [16.27](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html#1270) in the KML specification. | ||
#[derive(Clone, Debug, PartialEq)] | ||
pub enum ViewRefreshMode { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
Never, | ||
OnRequest, | ||
OnStop, | ||
OnRegion, | ||
} | ||
|
||
impl Default for ViewRefreshMode { | ||
fn default() -> ViewRefreshMode { | ||
ViewRefreshMode::Never | ||
} | ||
} | ||
|
||
impl FromStr for ViewRefreshMode { | ||
type Err = Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
match s { | ||
"never" => Ok(Self::Never), | ||
"onRequest" => Ok(Self::OnRequest), | ||
"onStop" => Ok(Self::OnStop), | ||
"onRegion" => Ok(Self::OnRegion), | ||
v => Err(Error::InvalidViewRefreshMode(v.to_string())), | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for ViewRefreshMode { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
match self { | ||
ViewRefreshMode::Never => write!(f, "never"), | ||
ViewRefreshMode::OnRequest => write!(f, "onRequest"), | ||
ViewRefreshMode::OnStop => write!(f, "onStop"), | ||
ViewRefreshMode::OnRegion => write!(f, "onRegion"), | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_refresh_mode_from_str() { | ||
assert_eq!( | ||
RefreshMode::OnChange, | ||
RefreshMode::from_str("onChange").unwrap() | ||
); | ||
assert_eq!( | ||
RefreshMode::OnExpire, | ||
RefreshMode::from_str("onExpire").unwrap() | ||
); | ||
assert_eq!( | ||
RefreshMode::OnInterval, | ||
RefreshMode::from_str("onInterval").unwrap() | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_view_refresh_mode_from_str() { | ||
assert_eq!( | ||
ViewRefreshMode::Never, | ||
ViewRefreshMode::from_str("never").unwrap() | ||
); | ||
assert_eq!( | ||
ViewRefreshMode::OnRegion, | ||
ViewRefreshMode::from_str("onRegion").unwrap() | ||
); | ||
assert_eq!( | ||
ViewRefreshMode::OnRequest, | ||
ViewRefreshMode::from_str("onRequest").unwrap() | ||
); | ||
assert_eq!( | ||
ViewRefreshMode::OnStop, | ||
ViewRefreshMode::from_str("onStop").unwrap() | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this test,
viewFormat
is an empty element, and this is treated as a value. Any desire to identify this and assign the optional String value toNone
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the test and behavior work as-is, since I would assume
None
would mean the tag isn't present at all