mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-15 07:07:07 +00:00
batch of fixes
This commit is contained in:
1 parent
a67b982b1c
commit
eee76f05d4
9 files changed
+288
-66
No files matched your search
@@ -44,14 +44,24 @@ where
|
||||
let last = trkseg.last_index().unwrap();
|
||||
|
||||
let mut kept = vec![(first, None)];
|
||||
ramer_douglas_peucker_helper(trkseg, first, last, measure, epsilon, &mut kept);
|
||||
ramer_douglas_peucker_ranges(trkseg, first, last, measure, epsilon, &mut kept);
|
||||
kept.push((last, None));
|
||||
kept
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ramer_douglas_peucker_helper<M>(
|
||||
/// What is left to do for the points between two ends, in the order of the points.
|
||||
enum Step {
|
||||
/// Looks for the point to keep between these two.
|
||||
Range(TrackSegmentIndex, TrackSegmentIndex),
|
||||
/// Keeps a point, selected at this distance.
|
||||
Keep(TrackSegmentIndex, f64),
|
||||
}
|
||||
|
||||
/// Adds the points to keep between `start` and `end` to `kept`, in order. It does not recurse: a
|
||||
/// track can make it go as deep as it has points, which would overflow the stack.
|
||||
fn ramer_douglas_peucker_ranges<M>(
|
||||
trkseg: &TrackSegment,
|
||||
start: TrackSegmentIndex,
|
||||
end: TrackSegmentIndex,
|
||||
@@ -61,28 +71,40 @@ fn ramer_douglas_peucker_helper<M>(
|
||||
) where
|
||||
M: Fn(TrackSegmentIndex, TrackSegmentIndex, TrackSegmentIndex) -> f64,
|
||||
{
|
||||
let mut max_idx = None;
|
||||
let mut max_dist = 0.0;
|
||||
let mut steps = vec![Step::Range(start, end)];
|
||||
while let Some(step) = steps.pop() {
|
||||
let (start, end) = match step {
|
||||
Step::Keep(idx, dist) => {
|
||||
kept.push((idx, Some(dist)));
|
||||
continue;
|
||||
}
|
||||
Step::Range(start, end) => (start, end),
|
||||
};
|
||||
|
||||
let mut cur = trkseg.next_index(Some(start));
|
||||
while let Some(idx) = cur {
|
||||
if idx == end {
|
||||
break;
|
||||
let mut max_idx = None;
|
||||
let mut max_dist = 0.0;
|
||||
|
||||
let mut cur = trkseg.next_index(Some(start));
|
||||
while let Some(idx) = cur {
|
||||
if idx == end {
|
||||
break;
|
||||
}
|
||||
|
||||
let dist = measure(start, end, idx);
|
||||
if dist > max_dist {
|
||||
max_idx = Some(idx);
|
||||
max_dist = dist;
|
||||
}
|
||||
|
||||
cur = trkseg.next_index(cur);
|
||||
}
|
||||
|
||||
let dist = measure(start, end, idx);
|
||||
if dist > max_dist {
|
||||
max_idx = Some(idx);
|
||||
max_dist = dist;
|
||||
if let Some(idx) = max_idx.filter(|_| max_dist > epsilon) {
|
||||
// the last one pushed is done first
|
||||
steps.push(Step::Range(idx, end));
|
||||
steps.push(Step::Keep(idx, max_dist));
|
||||
steps.push(Step::Range(start, idx));
|
||||
}
|
||||
|
||||
cur = trkseg.next_index(cur);
|
||||
}
|
||||
|
||||
if let Some(idx) = max_idx.filter(|_| max_dist > epsilon) {
|
||||
ramer_douglas_peucker_helper(trkseg, start, idx, measure, epsilon, kept);
|
||||
kept.push((idx, Some(max_dist)));
|
||||
ramer_douglas_peucker_helper(trkseg, idx, end, measure, epsilon, kept);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +217,16 @@ mod tests {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_goes_as_deep_as_the_segment_is_long_in_order() {
|
||||
let n = 12_000;
|
||||
let trkseg = segment(&vec![0.0; n]);
|
||||
// the point before the end is always the farthest one, so each range only loses one point
|
||||
let kept = ramer_douglas_peucker_by(&trkseg, &|_, _, idx| idx.flat as f64 + 1.0, 0.5);
|
||||
let kept: Vec<usize> = kept.iter().map(|(idx, _)| idx.flat).collect();
|
||||
assert_eq!(kept, (0..n).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_and_single_point() {
|
||||
assert!(simplify(&[], 1.0).is_empty());
|
||||
|
||||
@@ -6,7 +6,7 @@ use chrono::DateTime;
|
||||
use quick_xml::Error;
|
||||
use quick_xml::escape::resolve_predefined_entity;
|
||||
use quick_xml::events::Event;
|
||||
use quick_xml::events::attributes::Attributes;
|
||||
use quick_xml::events::attributes::{Attribute, Attributes};
|
||||
use quick_xml::reader::Reader;
|
||||
|
||||
enum GPXElement {
|
||||
@@ -260,6 +260,13 @@ fn apply_text(
|
||||
}
|
||||
}
|
||||
|
||||
/// The text of an attribute, without its escaping (`&` is `&`), which is written back when the
|
||||
/// file is. As it is in the file if it cannot be read.
|
||||
fn attribute_text(attr: &Attribute<'_>) -> String {
|
||||
attr.unescape_value()
|
||||
.map_or_else(|_| attr.value.to_string(), |text| text.into_owned())
|
||||
}
|
||||
|
||||
/// The email of the author, given as the `id` and `domain` attributes of the element.
|
||||
fn set_email(stack: &mut [GPXElement], attributes: Attributes<'_>) {
|
||||
let Some(GPXElement::Author(author)) = stack.last_mut() else {
|
||||
@@ -268,8 +275,8 @@ fn set_email(stack: &mut [GPXElement], attributes: Attributes<'_>) {
|
||||
let (mut id, mut domain) = (None, None);
|
||||
for attr in attributes.flatten() {
|
||||
match attr.key.as_ref() {
|
||||
"id" => id = Some(attr.value.to_string()),
|
||||
"domain" => domain = Some(attr.value.to_string()),
|
||||
"id" => id = Some(attribute_text(&attr)),
|
||||
"domain" => domain = Some(attribute_text(&attr)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -283,7 +290,7 @@ fn parse_link(attributes: Attributes<'_>) -> Link {
|
||||
let mut link = Link::default();
|
||||
for attr in attributes.flatten() {
|
||||
if attr.key.as_ref() == "href" {
|
||||
link.href = attr.value.to_string();
|
||||
link.href = attribute_text(&attr);
|
||||
}
|
||||
}
|
||||
link
|
||||
@@ -626,6 +633,14 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_href_is_unescaped_and_written_back_once() {
|
||||
let data =
|
||||
r#"<gpx><metadata><link href="https://a.example/?x=1&y=2"/></metadata></gpx>"#;
|
||||
let gpx = parse(data.as_bytes(), &mut Default::default()).unwrap();
|
||||
assert_eq!(gpx.info.links[0].href, "https://a.example/?x=1&y=2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_entities_and_cdata_in_text() {
|
||||
let data = r#"<gpx><metadata><name>Tom & Jerry <3 éA "x" 'y'</name>
|
||||
|
||||
Reference in new issue
Block a user