Skip to content

Commit

Permalink
Prototype de convertisseur NeTEx -> GeoJSON
Browse files Browse the repository at this point in the history
  • Loading branch information
ptitfred committed Oct 7, 2024
1 parent 28390d9 commit eaf6aaf
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 15 deletions.
58 changes: 58 additions & 0 deletions apps/transport/lib/netex/geojson_converter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Transport.NeTEx.GeoJSONConverter do
@moduledoc """
A first converter to extract stop places and display it as a GeoJSON.
"""

@doc """
Reads a NeTEx zip archive stored on disk, and returns a GeoJSON.
"""
@spec convert(binary()) :: binary()
def convert(zip_file_name) do
{:ok, result} =
zip_file_name
|> collect_stop_places()
|> to_geojson_feature_collection()
|> Jason.encode()

result
end

def collect_stop_places(zip_file_name) do
zip_file_name
|> Transport.NeTEx.read_all_stop_places()
|> Enum.flat_map(&keep_stop_places_with_location/1)
end

@spec keep_stop_places_with_location({binary(), list()}) :: list()
defp keep_stop_places_with_location({_filename, stop_places}) do
Enum.filter(stop_places, &has_location?/1)
end

@spec has_location?(map()) :: boolean()
defp has_location?(stop_place) do
Map.has_key?(stop_place, :latitude) && Map.has_key?(stop_place, :longitude)
end

defp to_geojson_feature_collection(features) do
%{
type: "FeatureCollection",
features: Enum.map(features, &to_geojson_feature/1)
}
end

defp to_geojson_feature(%{latitude: latitude, longitude: longitude, name: name}) do
%{
type: "Feature",
geometry: %{
type: "Point",
coordinates: [longitude, latitude]
},
properties: %{
name: name
}
}
end

defp to_geojson_feature(%{id: id, latitude: latitude, longitude: longitude}),
do: to_geojson_feature(%{latitude: latitude, longitude: longitude, name: id})
end
73 changes: 73 additions & 0 deletions apps/transport/test/netex/geojson_converter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
defmodule Transport.NeTEx.GeoJSONConverterTest do
use ExUnit.Case, async: true

alias Transport.ZipCreator

# not fully correct XML, but close enough for what we want to test
def some_netex_content do
"""
<GeneralFrame>
<members>
<StopPlace id="FR:HELLO:POYARTIN:001">
<Name>Poyartin</Name>
<Centroid>
<Location>
<Latitude>43.6690</Latitude>
<Longitude>-0.9190</Longitude>
</Location>
</Centroid>
</StopPlace>
<StopPlace id="FR:HELLO:DAX:001">
<Name>Dax</Name>
<Centroid>
<Location>
<Latitude>43.7154</Latitude>
<Longitude>-1.0530</Longitude>
</Location>
</Centroid>
</StopPlace>
</members>
</GeneralFrame>
"""
end

test "valid JSON from valid NeTEx" do
tmp_file = System.tmp_dir!() |> Path.join("temp-netex-#{Ecto.UUID.generate()}.zip")

netex_files = [
{"arrets_1.xml", some_netex_content()},
{"arrets_2.xml", some_netex_content()}
]

ZipCreator.create!(tmp_file, netex_files)

result = Transport.NeTEx.GeoJSONConverter.convert(tmp_file)

assert {:ok,
%{
type: "FeatureCollection",
features: [
%{
type: "Feature",
geometry: %{type: "Point", coordinates: [-0.919, 43.669]},
properties: %{name: "Poyartin"}
},
%{
type: "Feature",
geometry: %{type: "Point", coordinates: [-1.053, 43.7154]},
properties: %{name: "Dax"}
},
%{
type: "Feature",
geometry: %{type: "Point", coordinates: [-0.919, 43.669]},
properties: %{name: "Poyartin"}
},
%{
type: "Feature",
geometry: %{type: "Point", coordinates: [-1.053, 43.7154]},
properties: %{name: "Dax"}
}
]
}} = Jason.decode(result, keys: :atoms)
end
end
16 changes: 1 addition & 15 deletions apps/transport/test/netex/netex_archive_parser_test.exs
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
defmodule Transport.NeTEx.ArchiveParserTest do
use ExUnit.Case, async: true

defmodule ZipCreator do
@moduledoc """
A light wrapper around OTP `:zip` features. Does not support streaming here,
but massages the string <-> charlist differences.
"""
@spec create!(String.t(), [{String.t(), binary()}]) :: no_return()
def create!(zip_filename, file_data) do
{:ok, ^zip_filename} =
:zip.create(
zip_filename,
file_data
|> Enum.map(fn {name, content} -> {name |> to_charlist(), content} end)
)
end
end
alias Transport.ZipCreator

# not fully correct XML, but close enough for what we want to test
def some_netex_content do
Expand Down
16 changes: 16 additions & 0 deletions apps/transport/test/support/zip_creator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Transport.ZipCreator do
@moduledoc """
A light wrapper around OTP `:zip` features. Does not support streaming here,
but massages the string <-> charlist differences.
"""
def create!(zip_filename, file_data) do
{:ok, ^zip_filename} =
:zip.create(
zip_filename,
file_data
|> Enum.map(fn {name, content} -> {name |> to_charlist(), content} end)
)

:ok
end
end

0 comments on commit eaf6aaf

Please sign in to comment.