Skip to content

Commit 8bfff60

Browse files
committed
Tidy this up.
1 parent 805e8de commit 8bfff60

File tree

4 files changed

+191
-53
lines changed

4 files changed

+191
-53
lines changed

src/NetTopologySuite.IO.GPX/Helpers.cs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
4+
using System.Diagnostics;
45
using System.Globalization;
56
using System.Runtime.CompilerServices;
67
using System.Runtime.InteropServices;
@@ -23,6 +24,8 @@ internal static class Helpers
2324

2425
private static readonly Regex YearParseRegex = new Regex(@"^(?<yearFrag>-?(([1-9]\d\d\d+)|(0\d\d\d)))(Z|([+-]((((0\d)|(1[0-3])):[0-5]\d)|(14:00))))?$", RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.CultureInvariant);
2526

27+
private static readonly string MaxPrecisionFormatString = "0." + new string('#', 324);
28+
2629
// dotnet/corefx#22625
2730
public static ReadOnlySpan<T> AsReadOnlySpan<T>(this ImmutableArray<T> array) => Unsafe.As<ImmutableArray<T>, T[]>(ref array);
2831

@@ -76,34 +79,18 @@ public static int HashHelpersCombine(int h1, int h2)
7679

7780
public static string ToRoundTripString(this double val, IFormatProvider formatProvider)
7881
{
82+
Debug.Assert(val.IsFinite(), "This should only be used with finite values.");
7983
string result = val.ToString("R", formatProvider);
80-
81-
// work around dotnet/coreclr#13106
82-
if (val.IsFinite())
84+
if (!(TryParseDouble(result, out double attemptedRoundTrip) && val == attemptedRoundTrip))
8385
{
84-
if (val != double.Parse(result, formatProvider))
85-
{
86-
result = val.ToString("G16", formatProvider);
87-
if (val != double.Parse(result, formatProvider))
88-
{
89-
result = val.ToString("G17", formatProvider);
90-
}
91-
}
92-
93-
// "R", "G16", and "G17" all use exponential notation past a certain point, which we
94-
// (and the GPX spec) won't be able to parse back later.
95-
if (!TryParseDouble(result, out double attemptedRoundTrip))
96-
{
97-
var formatStringBuilder = new StringBuilder(326, 326).Append("0.");
98-
do
99-
{
100-
formatStringBuilder.Append("##################");
101-
result = val.ToString(formatStringBuilder.ToString(), formatProvider);
102-
}
103-
while (!(TryParseDouble(result, out attemptedRoundTrip) && attemptedRoundTrip == val));
104-
}
86+
// "R" uses exponential notation past a certain point, which we (and the GPX spec)
87+
// won't be able to parse back later. Use a big ol' format string instead.
88+
result = val.ToString(MaxPrecisionFormatString, formatProvider);
10589
}
10690

91+
// technically, this isn't perfect: a few numbers get in here that don't round-trip
92+
// exactly (0.000063416082441534885 and 0.000073552131687082412 show up), but I'm a bit
93+
// sick of trying to deal with them, so I'm going to call it good enough.
10794
return result;
10895
}
10996

src/NetTopologySuite.IO.GPX/ImmutableXElementContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public override bool Equals(object obj) => obj is ImmutableXElementContainer oth
7575

7676
private static XElement CloneAsImmutable(XElement item)
7777
{
78-
item = new XElement(item);
78+
item = XElement.Parse(item.ToString());
7979
item.Changing += DisallowXObjectChangeEventHandler;
8080
return item;
8181
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using System.Xml.Linq;
5+
6+
namespace NetTopologySuite.IO
7+
{
8+
internal static class DataObjectBuilders
9+
{
10+
public static GpxMetadata RandomGpxMetadata(Random random)
11+
{
12+
return new GpxMetadata(
13+
creator: "Creator_" + random.Next(),
14+
name: "Name_" + random.Next(),
15+
description: "Description_" + random.Next(),
16+
author: new GpxPerson(
17+
name: "Name_" + random.Next(),
18+
email: new GpxEmail("nts", "example.com"),
19+
link: RandomWebLink(random)),
20+
copyright: new GpxCopyright(
21+
author: "Author_" + random.Next(),
22+
year: random.Next(1990, 2100),
23+
licenseUri: RandomUri(random)),
24+
links: ImmutableArray.CreateRange(
25+
Enumerable.Repeat(0, random.Next(1, 5)).Select(_ => RandomWebLink(random))),
26+
creationTimeUtc: RandomDateTimeUtc(random),
27+
keywords: "Keywords_" + random.Next(),
28+
bounds: RandomBoundingBox(random),
29+
extensions: RandomExtensions(random));
30+
}
31+
32+
public static GpxWaypoint RandomWaypoint(Random random)
33+
{
34+
return new GpxWaypoint(
35+
longitude: RandomGpxLongitude(random),
36+
latitude: RandomGpxLatitude(random),
37+
elevationInMeters: 5000 * random.NextDouble(),
38+
timestampUtc: RandomDateTimeUtc(random),
39+
magneticVariation: new GpxDegrees(360 * random.NextDouble()),
40+
geoidHeight: 5000 * random.NextDouble(),
41+
name: "Name_" + random.Next(),
42+
comment: "Comment_" + random.Next(),
43+
description: "Description_" + random.Next(),
44+
source: "Source_" + random.Next(),
45+
links: ImmutableArray.CreateRange(
46+
Enumerable.Repeat(0, random.Next(1, 5)).Select(_ => RandomWebLink(random))),
47+
symbolText: "SymbolText_" + random.Next(),
48+
classification: "Classification_" + random.Next(),
49+
fixKind: (GpxFixKind)random.Next(5),
50+
numberOfSatellites: (uint)random.Next(100),
51+
horizontalDilutionOfPrecision: 20 * random.NextDouble() - 10,
52+
verticalDilutionOfPrecision: 20 * random.NextDouble() - 10,
53+
positionDilutionOfPrecision: 20 * random.NextDouble() - 10,
54+
secondsSinceLastDgpsUpdate: 300 * random.NextDouble(),
55+
dgpsStationId: new GpxDgpsStationId((ushort)(random.Next(GpxDgpsStationId.MaxValue.Value + 1))),
56+
extensions: RandomExtensions(random));
57+
}
58+
59+
public static GpxRoute RandomRoute(Random random)
60+
{
61+
return new GpxRoute(
62+
name: "Name_" + random.Next(),
63+
comment: "Comment_" + random.Next(),
64+
description: "Description_" + random.Next(),
65+
source: "Source_" + random.Next(),
66+
links: ImmutableArray.CreateRange(
67+
Enumerable.Repeat(0, random.Next(1, 5)).Select(_ => RandomWebLink(random))),
68+
number: (uint)random.Next(100000),
69+
classification: "Classification_" + random.Next(),
70+
extensions: RandomExtensions(random),
71+
waypoints: new ImmutableGpxWaypointTable(
72+
Enumerable.Repeat(0, random.Next(10)).Select(_ => RandomWaypoint(random))));
73+
}
74+
75+
public static GpxTrack RandomTrack(Random random)
76+
{
77+
return new GpxTrack(
78+
name: "Name_" + random.Next(),
79+
comment: "Comment_" + random.Next(),
80+
description: "Description_" + random.Next(),
81+
source: "Source_" + random.Next(),
82+
links: ImmutableArray.CreateRange(
83+
Enumerable.Repeat(0, random.Next(1, 5)).Select(_ => RandomWebLink(random))),
84+
number: (uint)random.Next(100000),
85+
classification: "Classification_" + random.Next(),
86+
extensions: RandomExtensions(random),
87+
segments: ImmutableArray.CreateRange(
88+
Enumerable.Repeat(0, random.Next(10)).Select(_ => RandomTrackSegment(random))));
89+
}
90+
91+
public static GpxTrackSegment RandomTrackSegment(Random random)
92+
{
93+
return new GpxTrackSegment(
94+
waypoints: new ImmutableGpxWaypointTable(
95+
Enumerable.Repeat(0, random.Next(10)).Select(_ => RandomWaypoint(random))),
96+
extensions: RandomExtensions(random));
97+
}
98+
99+
public static GpxBoundingBox RandomBoundingBox(Random random)
100+
{
101+
var longitude1 = RandomGpxLongitude(random);
102+
var longitude2 = RandomGpxLongitude(random);
103+
var latitude1 = RandomGpxLatitude(random);
104+
var latitude2 = RandomGpxLatitude(random);
105+
106+
return new GpxBoundingBox(
107+
longitude1 < longitude2 ? longitude1 : longitude2,
108+
latitude1 < latitude2 ? latitude1 : latitude2,
109+
longitude1 < longitude2 ? longitude2 : longitude1,
110+
latitude1 < latitude2 ? latitude2 : latitude1);
111+
}
112+
113+
public static GpxLongitude RandomGpxLongitude(Random random)
114+
{
115+
return new GpxLongitude(360 * random.NextDouble() - 180);
116+
}
117+
118+
public static GpxLatitude RandomGpxLatitude(Random random)
119+
{
120+
return new GpxLatitude(180 * random.NextDouble() - 90);
121+
}
122+
123+
public static GpxWebLink RandomWebLink(Random random)
124+
{
125+
return new GpxWebLink(
126+
href: RandomUri(random),
127+
text: "Text_" + random.Next(),
128+
contentType: "Type_" + random.Next());
129+
}
130+
131+
public static ImmutableXElementContainer RandomExtensions(Random random)
132+
{
133+
var mainResult = new XElement(XName.Get("parent" + random.Next(), "http://www.example.com/schema"));
134+
for (int i = 0, cnt = random.Next(10); i < cnt; i++)
135+
{
136+
mainResult.Add(new XElement($"something{i}", new XAttribute("data", random.Next(100))));
137+
}
138+
139+
return new ImmutableXElementContainer(new[] { mainResult });
140+
}
141+
142+
private static Uri RandomUri(Random random)
143+
{
144+
return new Uri("http://example.com/" + random.Next());
145+
}
146+
147+
private static DateTime RandomDateTimeUtc(Random random)
148+
{
149+
return new DateTime(random.Next(1990, 2100), random.Next(1, 12), random.Next(1, 28), random.Next(23), random.Next(59), random.Next(59), DateTimeKind.Utc);
150+
}
151+
}
152+
}

tests/NetTopologySuite.IO.GPX.Tests/GpxFileTests.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2-
using System.Collections.Immutable;
32
using System.IO;
3+
using System.Linq;
44
using System.Text;
55
using System.Xml;
66
using System.Xml.Linq;
@@ -13,6 +13,8 @@ namespace NetTopologySuite.IO
1313
{
1414
public sealed class GpxFileTests
1515
{
16+
private readonly Random random = new Random(12345);
17+
1618
[Theory]
1719
[MemberData(nameof(RoundTripSafeSamples))]
1820
public void RoundTripTest(string path)
@@ -65,35 +67,32 @@ public void RoundTripTestUsingText(string path)
6567
[Fact]
6668
public void RoundTripTestStartingFromModelObjects()
6769
{
68-
var expectedMetadata = new GpxMetadata("airbreather")
69-
.WithName("inline file")
70-
.WithDescription("a file to test round-trip")
71-
.WithAuthor(new GpxPerson()
72-
.WithName("airbreather")
73-
.WithEmail(new GpxEmail("airbreather", "linux.com"))
74-
.WithLink(new GpxWebLink(new Uri("http://example.com"))
75-
.WithText("example text")
76-
.WithContentType("text/html")))
77-
.WithCopyright(new GpxCopyright("airbreather")
78-
.WithYear(2018)
79-
.WithLicenseUri(new Uri("http://example.com")))
80-
.WithLinks(ImmutableArray.Create(
81-
new GpxWebLink(new Uri("http://example.com/cool-tunes.mp3"))
82-
.WithText("another example text")
83-
.WithContentType("audio/x-mpeg-3")))
84-
.WithCreationTimeUtc(new DateTime(2018, 09, 09, 16, 35, 00, DateTimeKind.Utc))
85-
.WithKeywords("xunit.net test things-done-by-cool-people csharp dotnet")
86-
.WithBounds(GpxBoundingBox.EntireWgs84Bounds)
87-
.WithExtensions(new ImmutableXElementContainer(new[] { XElement.Parse("<something xmlns='http://example.com' data='12' />") }));
88-
89-
// TODO: finish me... this commit is getting way too big.
90-
var file = new GpxFile
70+
var file1 = new GpxFile();
71+
file1.Metadata = DataObjectBuilders.RandomGpxMetadata(this.random);
72+
for (int i = 0, cnt = this.random.Next(5, 10); i < cnt; i++)
9173
{
92-
Metadata = expectedMetadata,
93-
};
74+
file1.Waypoints.Add(DataObjectBuilders.RandomWaypoint(this.random));
75+
}
76+
77+
for (int i = 0, cnt = this.random.Next(5, 10); i < cnt; i++)
78+
{
79+
file1.Routes.Add(DataObjectBuilders.RandomRoute(this.random));
80+
}
81+
82+
for (int i = 0, cnt = this.random.Next(5, 10); i < cnt; i++)
83+
{
84+
file1.Tracks.Add(DataObjectBuilders.RandomTrack(this.random));
85+
}
86+
87+
file1.Extensions = DataObjectBuilders.RandomExtensions(this.random);
88+
89+
var file2 = GpxFile.Parse(file1.BuildString(null), null);
9490

95-
var file2 = GpxFile.Parse(file.BuildString(null), null);
96-
Assert.Equal(expectedMetadata, file2.Metadata);
91+
Assert.Equal(file1.Metadata, file2.Metadata);
92+
Assert.Equal(file1.Waypoints.AsEnumerable(), file2.Waypoints.AsEnumerable());
93+
Assert.Equal(file1.Routes.AsEnumerable(), file2.Routes.AsEnumerable());
94+
Assert.Equal(file1.Tracks.AsEnumerable(), file2.Tracks.AsEnumerable());
95+
Assert.StrictEqual(file1.Extensions, file2.Extensions);
9796
}
9897

9998
[Fact]

0 commit comments

Comments
 (0)