Skip to content
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

Merged
merged 6 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ pub enum Error {
InvalidColorMode(String),
#[error("Invalid list item type: {0}")]
InvalidListItemType(String),
#[error("Invalid refresh mode: {0}")]
InvalidRefreshMode(String),
#[error("Invalid view refresh mode: {0}")]
InvalidViewRefreshMode(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[cfg(feature = "zip")]
Expand Down
144 changes: 138 additions & 6 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)?))
}
Expand Down Expand Up @@ -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>()?
Expand All @@ -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)?;
Expand All @@ -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>,
Expand Down Expand Up @@ -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()),
Copy link
Contributor Author

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 to None?

Copy link
Member

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

attrs,
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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: id, targetId, and a user-defined one. Maybe I should ignore them to conform to how it is done for other types?

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think enough of the other types include attrs to keep it for now, but worth flagging for the future

..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>
Expand Down
6 changes: 4 additions & 2 deletions src/types/kml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::str::FromStr;
use crate::errors::Error;
use crate::types::{
BalloonStyle, CoordType, Element, Icon, IconStyle, LabelStyle, LineString, LineStyle,
LinearRing, ListStyle, Location, MultiGeometry, Orientation, Pair, Placemark, Point, PolyStyle,
Polygon, Scale, Style, StyleMap,
LinearRing, Link, LinkTypeIcon, ListStyle, Location, MultiGeometry, Orientation, Pair,
Placemark, Point, PolyStyle, Polygon, Scale, Style, StyleMap,
};

/// Enum for representing the KML version being parsed
Expand Down Expand Up @@ -82,5 +82,7 @@ pub enum Kml<T: CoordType = f64> {
LineStyle(LineStyle),
PolyStyle(PolyStyle),
ListStyle(ListStyle),
LinkTypeIcon(LinkTypeIcon),
Link(Link),
Element(Element),
}
183 changes: 183 additions & 0 deletions src/types/link.rs
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to implement FromStr here as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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()
);
}
}
Loading