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

Add kml:Link, kml:Icon #28

merged 6 commits into from
Jul 31, 2022

Conversation

k-mack
Copy link
Contributor

@k-mack k-mack commented Jul 28, 2022

Attempt to add support for kml:Link and kml:Icon.

A couple of points to discuss.

First

Since the spec's kml:Icon (or kml:BasicLinkType) is already implemented and exported by the library as Kml::Icon, I used an enum to model the spec's kml:LinkType. Its variants are Kml::Link and Kml:Icon. Both are constructed with kml::types::LinkModel, the common data model used by the spec's kml:LinkType.

If kml:Model was to be implemented using this PR, then it's link field would be of type kml::types::LinkType and it would need to be guarded so as not to have it be set to kml::types::LinkType::Icon:

pub struct Model {
    link: Option<kml::types::LinkType>,
    // ...
}

impl Default for Model {
    fn default() -> Model {
        Model { link: None }
    }
}

impl Model {
    pub fn get_link(&self) -> Option<&kml::types::LinkType> {
        self.link.as_ref()
    }

    pub fn set_link(&mut self, link_model: &kml::types::LinkModel) {
        self.link = Some(kml::types::LinkType::Link(link_model.clone()));
    }
}

Let me know how you feel about this approach.

Alternatively, I could use two identical structs with different names. I could expose Icon as Kml::LinkIcon to not have it interfere with the existing Kml::Icon.

What makes the most sense for an approachable, intuitive API?

Second

I used empty strings for the optional String fields that have no default value. Let me know if you'd prefer to have them be Options instead.

Closes #10.

Copy link
Member

@pjsier pjsier left a comment

Choose a reason for hiding this comment

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

Thanks so much for the PR and thorough write up! I think this approach makes sense, but I'm a bit worried about needing a setter for kml:Model. Other than the duplicate code do you see any issues creating separate structs?

Otherwise it would be great to convert the fields with empty string to Option<String> since that's what we're using for other elements, and thanks for flagging that.

src/writer.rs Outdated
LinkType::Link(model) => (b"Link", &*model),
};
self.writer
.write_event(Event::Start(BytesStart::owned_name(tag_name.to_vec())))?;
Copy link
Member

Choose a reason for hiding this comment

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

We'll need to add this here to capture the attributes

.with_attributes(self.hash_map_as_attrs(&link_model.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.

Done.

@k-mack
Copy link
Contributor Author

k-mack commented Jul 29, 2022

As much as I like the idea of using an enum, it doesn't make sense from an API ergonomics perspective. If Rust supported enum variants as first-class types, then I think it'd be a clean approach.

I'll break the enum into separate structs.

Instead of using enum variants to represent `kml:Link` and `kml:Icon`,
which makes it awkward to use them since enum variants are not
first-class types in Rust, they are implemented using two separate
structs.

Since the library already exports the public type `Kml::Icon` from the
`crate::types::style` module, `Kml:LinkTypeLink` and `Kml:LinkTypeIcon`
are exported. These are aliases for the internal types
`kml::types::link::{Link,Icon}`.
Copy link
Contributor Author

@k-mack k-mack left a comment

Choose a reason for hiding this comment

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

Separate structs feels more Rusty. I left a couple of comments to call out some of the decisions I made that are worth a review.

refresh_mode: Some(types::RefreshMode::OnChange),
view_refresh_mode: Some(types::ViewRefreshMode::OnStop),
view_format: Some(String::new()),
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

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

src/types/mod.rs Outdated
@@ -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

Copy link
Member

@pjsier pjsier left a comment

Choose a reason for hiding this comment

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

This is looking great, thanks for the changes! The last two things to update would be implementing FromStr for the enums and removing the Link alias, then I think this is ready to merge

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
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

src/types/mod.rs Outdated
@@ -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
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

src/reader.rs Outdated
Comment on lines 621 to 627
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.


/// `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.

Enable and simplify reading these types using str's `parse` method.
Copy link
Member

@pjsier pjsier left a comment

Choose a reason for hiding this comment

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

This looks great, thanks again!

@pjsier pjsier merged commit eaa6db7 into georust:main Jul 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add kml:Link, kml:Icon
2 participants