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 4 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
169 changes: 164 additions & 5 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ 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,
LinkTypeIcon, LinkTypeLink, 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,12 @@ 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::LinkTypeLink(self.read_link_type_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 +565,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 +583,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 +604,102 @@ 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 = match self.read_str()?.as_str() {
"onChange" => Some(RefreshMode::OnChange),
"onInterval" => Some(RefreshMode::OnInterval),
"onExpire" => Some(RefreshMode::OnExpire),
_ => None,
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Instead of including this here, could we implement FromStr for RefreshMode as well as the other added enums? We've done this for AltitudeMode and ColorMode as well and it keeps things a little cleaner

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.

b"refreshInterval" => icon.refresh_interval = self.read_float()?,
b"viewRefreshMode" => {
icon.view_refresh_mode = match self.read_str()?.as_str() {
"never" => Some(ViewRefreshMode::Never),
"onRequest" => Some(ViewRefreshMode::OnRequest),
"onStop" => Some(ViewRefreshMode::OnStop),
"onRegion" => Some(ViewRefreshMode::OnRegion),
_ => None,
}
}
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_type_link(
&mut self,
attrs: HashMap<String, String>,
) -> Result<LinkTypeLink, Error> {
let mut link = LinkTypeLink {
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 = match self.read_str()?.as_str() {
"onChange" => Some(RefreshMode::OnChange),
"onInterval" => Some(RefreshMode::OnInterval),
"onExpire" => Some(RefreshMode::OnExpire),
_ => None,
}
}
b"refreshInterval" => link.refresh_interval = self.read_float()?,
b"viewRefreshMode" => {
link.view_refresh_mode = match self.read_str()?.as_str() {
"never" => Some(ViewRefreshMode::Never),
"onRequest" => Some(ViewRefreshMode::OnRequest),
"onStop" => Some(ViewRefreshMode::OnStop),
"onRegion" => Some(ViewRefreshMode::OnRegion),
_ => None,
}
}
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 +1031,64 @@ mod tests {
);
}

#[test]
fn test_read_link_type_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::LinkTypeLink(LinkTypeLink {
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, LinkTypeIcon, LinkTypeLink, 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),
LinkTypeLink(LinkTypeLink),
Element(Element),
}
114 changes: 114 additions & 0 deletions src/types/link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{
collections::HashMap,
fmt::{Display, Formatter, Result},
};

/// `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 Display for RefreshMode {
fn fmt(&self, f: &mut Formatter<'_>) -> 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 Display for ViewRefreshMode {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
ViewRefreshMode::Never => write!(f, "never"),
ViewRefreshMode::OnRequest => write!(f, "onRequest"),
ViewRefreshMode::OnStop => write!(f, "onStop"),
ViewRefreshMode::OnRegion => write!(f, "onRegion"),
}
}
}
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ mod geometry;

pub use geometry::Geometry;

mod link;

pub use link::{Icon as LinkTypeIcon, Link as LinkTypeLink, RefreshMode, ViewRefreshMode};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not a fan of these aliases, but I couldn't think of anything better. Decided to alias Link only because Icon was aliased. WDYT?

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 I'd prefer we only alias Icon to LinkTypeIcon and remove the LinkTypeLink to keep things as consistent with the spec as possible


mod style;

pub use style::{
Expand Down
Loading