Skip to content

Commit

Permalink
Merge pull request #3 from felt/waypoints-and-routes
Browse files Browse the repository at this point in the history
Add support for waypoints and routes
  • Loading branch information
caspg authored Apr 12, 2022
2 parents 717c3fa + b579703 commit 7b5c744
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 12 deletions.
20 changes: 18 additions & 2 deletions lib/gpx_ex/gpx.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
defmodule GpxEx.Gpx do
@type t :: %__MODULE__{tracks: list(GpxEx.Track.t)}
@typedoc """
A struct representing a fully parsed GPX file.
defstruct tracks: nil
GPX files may contain zero or more:
- Tracks—ordered lists of points describing a path someone traveled.
- Routes—ordered lists of waypoints representing a series of turn points
leading to a destination. Whereas a track might sample the GPS location
of actual movements at some interval, a route represents directions
such as you might get from a routing application.
- Waypoints—a point of interest or named feature on a map.
"""
@type t :: %__MODULE__{
tracks: list(GpxEx.Track.t()),
waypoints: list(GpxEx.Waypoint.t()),
routes: list(GpxEx.Route.t())
}

defstruct tracks: [], waypoints: [], routes: []
end
64 changes: 57 additions & 7 deletions lib/gpx_ex/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ defmodule GpxEx.Parser do
|> get_track_elements()
|> Enum.map(&build_track/1)

{:ok, %GpxEx.Gpx{tracks: tracks}}
standalone_waypoints =
gpx_document
|> get_top_level_waypoint_elements()
|> Enum.map(&build_waypoint/1)

routes =
gpx_document
|> get_route_elements()
|> Enum.map(&build_route/1)

{:ok, %GpxEx.Gpx{tracks: tracks, waypoints: standalone_waypoints, routes: routes}}
end

defp build_track(track_xml_element) do
Expand All @@ -16,7 +26,7 @@ defmodule GpxEx.Parser do
|> get_segment_elements()
|> Enum.map(&build_segment/1)

track_name = get_track_name(track_xml_element)
track_name = optional_string(track_xml_element, "name")

%GpxEx.Track{segments: segments, name: track_name}
end
Expand All @@ -39,17 +49,57 @@ defmodule GpxEx.Parser do
}
end

defp build_route(route_element) do
%GpxEx.Route{
name: optional_string(route_element, "name"),
points:
route_element
|> get_route_point_elements()
|> Enum.map(&build_waypoint/1)
}
end

defp build_waypoint(waypoint_element) do
%GpxEx.Waypoint{
lat: get_lat(waypoint_element),
lon: get_lon(waypoint_element),
ele: get_ele(waypoint_element),
name: optional_string(waypoint_element, "name"),
symbol: optional_string(waypoint_element, "sym"),
description: optional_string(waypoint_element, "desc"),
url: optional_string(waypoint_element, "url")
}
end

defp get_track_elements(xml), do: xpath(xml, ~x"//trk"l)
defp get_segment_elements(xml), do: xpath(xml, ~x"./trkseg"l)
defp get_point_elements(xml), do: xpath(xml, ~x"./trkpt"l)

defp get_track_name(xml), do: xpath(xml, ~x"./name/text()"s) |> optinal_string
defp get_top_level_waypoint_elements(xml), do: xpath(xml, ~x"//wpt"l)

defp get_route_elements(xml), do: xpath(xml, ~x"//rte"l)
defp get_route_point_elements(xml), do: xpath(xml, ~x"./rtept"l)

defp get_lat(xml), do: xpath(xml, ~x"./@lat"f)
defp get_lon(xml), do: xpath(xml, ~x"./@lon"f)
defp get_ele(xml), do: xpath(xml, ~x"./ele/text()"Fo)
defp get_time(xml), do: xpath(xml, ~x"./time/text()"s) |> optinal_string
defp get_ele(xml), do: optional_xpath(xml, ~x"./ele/text()"f)
defp get_time(xml), do: optional_string(xml, "time")

defp optional_string(xml, element_type) do
el = xpath(xml, ~x"./#{element_type}")

defp optinal_string(maybe_string) when maybe_string == "", do: nil
defp optinal_string(maybe_string), do: maybe_string
if is_nil(el) do
nil
else
xpath(el, ~x"./text()"s)
end
end

defp optional_xpath(xml, path) do
try do
xpath(xml, path)
catch
_, _ -> nil
end
end
end
9 changes: 9 additions & 0 deletions lib/gpx_ex/route.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule GpxEx.Route do
@typedoc """
A struct representing a route, an idealized list of turn points that describe
the directions one should follow to go from one place to another.
"""
@type t :: %__MODULE__{points: list(GpxEx.Waypoint.t()), name: String.t() | nil}

defstruct points: [], name: nil
end
28 changes: 28 additions & 0 deletions lib/gpx_ex/waypoint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule GpxEx.Waypoint do
@enforce_keys [:lat, :lon]
defstruct @enforce_keys ++
[
ele: nil,
name: nil,
symbol: nil,
description: nil,
url: nil
]

@typedoc """
A single waypoint, which may be part of a route or
a standalone point-of-interest or named feature.
- Lon and lat are in decimal degrees
- Elevation is in meters (presumably above mean sea level)
"""
@type t :: %__MODULE__{
lat: float(),
lon: float(),
ele: float() | nil,
name: String.t() | nil,
symbol: String.t() | nil,
description: String.t() | nil,
url: String.t() | nil
}
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule GpxEx.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:sweet_xml, "~> 0.6.0"}
{:sweet_xml, "~> 0.7.0"}
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
%{
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"},
}
60 changes: 59 additions & 1 deletion test/gpx_ex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule GpxExTest do
use ExUnit.Case
doctest GpxEx

test "parser" do
test "parses a track" do
{:ok, gpx_doc} = File.read("./test/gpx_files/gdynia.gpx")

expected = %GpxEx.Gpx{
Expand Down Expand Up @@ -45,4 +45,62 @@ defmodule GpxExTest do

assert {:ok, expected} == GpxEx.parse(gpx_doc)
end

test "parses standalone waypoints" do
{:ok, gpx_doc} = File.read("./test/gpx_files/standalone_wpts.gpx")

expected = %GpxEx.Gpx{
waypoints: [
%GpxEx.Waypoint{
lat: 39.2,
lon: -94.5,
name: "Kansas City, MO",
symbol: "City",
description: "Midwestern city",
url: "https://www.visitkc.com",
ele: 308.15
},
%GpxEx.Waypoint{
lat: 32.7,
lon: -117.2,
name: "San Diego, CA",
symbol: "City",
description: "Port city",
url: "https://www.sandiego.gov/",
ele: nil
},
%GpxEx.Waypoint{
lat: 36.4,
lon: -94.2,
name: "Bentonville, AR",
symbol: "Town",
description: nil,
url: "http://bentonvillear.com/",
ele: 395
}
]
}

assert {:ok, expected} == GpxEx.parse(gpx_doc)
end

test "parses routes" do
{:ok, gpx_doc} = File.read("./test/gpx_files/route.gpx")

expected = %GpxEx.Gpx{
routes: [
%GpxEx.Route{
name: "Sample route",
points: [
%GpxEx.Waypoint{name: "Point 1", lon: -94.5, lat: 39.2, ele: 388.1},
%GpxEx.Waypoint{name: "Point 2", lon: 120.4, lat: -19.9},
%GpxEx.Waypoint{name: "Point 3", lon: -0.1, lat: -0.2},
%GpxEx.Waypoint{name: "Point 4", lon: 0.2, lat: 0.4, ele: -0.1}
]
}
]
}

assert {:ok, expected} == GpxEx.parse(gpx_doc)
end
end
21 changes: 21 additions & 0 deletions test/gpx_files/route.gpx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<gpx xmlns="http://www.topografix.com/GPX/1/0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:gpx="http://www.topografix.com/GPX/1/0" version="1.0" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
<rte>
<name><![CDATA[Sample route]]></name>
<desc>Fake route</desc>
<rtept lat="39.2" lon="-94.5">
<ele>388.1</ele>
<name>Point 1</name>
</rtept>
<rtept lat="-19.9" lon="120.4">
<name>Point 2</name>
</rtept>
<rtept lat="-0.2" lon="-0.1">
<name>Point 3</name>
</rtept>
<rtept lat="0.4" lon="0.2">
<name>Point 4</name>
<ele>-0.1</ele>
</rtept>
</rte>
</gpx>
22 changes: 22 additions & 0 deletions test/gpx_files/standalone_wpts.gpx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<gpx xmlns="http://www.topografix.com/GPX/1/0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:gpx="http://www.topografix.com/GPX/1/0" version="1.0" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
<wpt lat="39.2" lon="-94.5">
<name>Kansas City, MO</name>
<sym>City</sym>
<desc>Midwestern city</desc>
<url>https://www.visitkc.com</url>
<ele>308.15</ele>
</wpt>
<wpt lat="32.7" lon="-117.2">
<sym>City</sym>
<name>San Diego, CA</name>
<url>https://www.sandiego.gov/</url>
<desc>Port city</desc>
</wpt>
<wpt lat="36.4" lon="-94.2">
<ele>395</ele>
<url>http://bentonvillear.com/</url>
<name>Bentonville, AR</name>
<sym>Town</sym>
</wpt>
</gpx>

0 comments on commit 7b5c744

Please sign in to comment.