@@ 3,13 3,12 @@ defmodule Triglav.PublicTransport.Validation do
Validates public transport routes in OSM based on imported data.
"""
alias Triglav.Osm
- alias Triglav.PublicTransport.Schemas.{Error, Feed}
+ alias Triglav.PublicTransport.Schemas.{Error, Feed, RouteRelation}
alias Triglav.PublicTransport.Validation.Errors
alias Triglav.Repo
alias Triglav.Schemas.Osmosis.Relation
- # Maximum distance from platform to route
- @max_platform_distance_from_route 20
+ import Ecto.Query
@spec validate(Feed.id()) :: [Error.t()]
def validate(feed_id) do
@@ 19,40 18,54 @@ defmodule Triglav.PublicTransport.Validation do
|> Repo.preload([
:operator,
routes: [
- [variants: [platforms: [platform: [mappings: [:node, :way]]]]],
+ :variants,
[mapped_route_relations: :members],
[mapped_route_master_relations: :members]
]
])
- # TODO: this is slow, seek to avoid it
+ route_relations =
+ RouteRelation
+ |> where(operator_id: ^feed.operator_id)
+ |> Repo.all()
+ |> Repo.preload([:relation, platforms: [:node, :way]])
+ |> Map.new(&{&1.id, &1})
+
relation_map =
- feed.routes
- |> Enum.flat_map(& &1.mapped_route_relations)
- |> Osm.preload_members()
+ route_relations
+ |> Map.values()
+ |> Enum.map(& &1.relation)
+ |> Osm.preload_members(&(&1.member_role == "" and &1.member_type == "W"))
|> Map.new(&{&1.id, &1})
+ # # TODO: this is slow, seek to avoid it
+ # relation_map =
+ # feed.routes
+ # |> Enum.flat_map(& &1.mapped_route_relations)
+ # |> Osm.preload_members()
+ # |> Map.new(&{&1.id, &1})
+
feed
- |> validate_routes(relation_map)
+ |> validate_routes(relation_map, route_relations)
|> List.flatten()
|> Enum.reject(&is_nil/1)
end
- defp validate_routes(feed, relation_map) do
+ defp validate_routes(feed, relation_map, route_relations) do
for route <- feed.routes do
- validate_route(feed, route, relation_map)
+ validate_route(feed, route, relation_map, route_relations)
end
end
- defp validate_route(feed, route, relation_map) do
+ defp validate_route(feed, route, relation_map, route_relations) do
[
validate_has_relations(feed, route),
validate_has_route_master(feed, route),
validate_routes_are_contained_in_route_master(feed, route),
validate_routes_master_route_has_no_unknown_members(feed, route),
- validate_platform_tags(feed, route, relation_map),
+ validate_platform_tags(feed, route, route_relations),
Enum.map(route.mapped_route_master_relations, &validate_route_master_relation(feed, route, &1)),
- Enum.map(route.mapped_route_relations, &validate_route_relation(feed, route, &1, relation_map))
+ Enum.map(route.mapped_route_relations, &validate_route_relation(feed, route, &1, relation_map, route_relations))
]
end
@@ 140,7 153,7 @@ defmodule Triglav.PublicTransport.Validation do
]
end
- defp validate_route_relation(feed, route, relation, relation_map) do
+ defp validate_route_relation(feed, route, relation, relation_map, route_relations) do
required_tags = ["type", "route", "ref", "from", "to", "name"]
allowed_tags =
@@ 168,11 181,11 @@ defmodule Triglav.PublicTransport.Validation do
validate_required_tags(feed, route, relation, required_tags),
validate_allowed_tags(feed, route, relation, allowed_tags),
validate_tag_values(feed, route, relation, expected_tags),
- validate_continuous_ways(feed, route, relation, relation_map),
+ validate_continuous_ways(feed, route, relation, route_relations),
validate_oneway_streets(feed, route, relation, relation_map),
- validate_has_gtfs_stop_ids(feed, route, relation, relation_map),
- validate_member_roles(feed, route, relation)
- # validate_platform_distance_from_route(feed, route, relation)
+ validate_has_gtfs_stop_ids(feed, route, relation, route_relations),
+ validate_member_roles(feed, route, relation),
+ validate_platform_distance_from_route(feed, route, relation, route_relations)
]
end
@@ 207,22 220,11 @@ defmodule Triglav.PublicTransport.Validation do
# If trip has an exact linestring, that means route is complete, so only
# run this check if exact is false.
- defp validate_continuous_ways(feed, route, relation, relation_map) do
- relation = Map.fetch!(relation_map, relation.id)
-
- ordered_ways =
- relation.members
- |> Enum.filter(&(&1.member_type == "W" and &1.member_role == ""))
- |> Enum.map(& &1.member)
-
- router_result = Triglav.Osm.Router.route_ways(ordered_ways)
-
- case router_result do
- {:ok, _} ->
- nil
+ defp validate_continuous_ways(feed, route, relation, route_relations) do
+ relation = Map.fetch!(route_relations, relation.id)
- {:error, {:ways_not_connected, _w1, _w2}} ->
- Errors.broken_route(feed, route, relation)
+ if not relation.contiguous do
+ Errors.broken_route(feed, route, relation.relation)
end
end
@@ 255,13 257,9 @@ defmodule Triglav.PublicTransport.Validation do
end
end
- defp validate_has_gtfs_stop_ids(feed, route, relation, relation_map) do
- relation = Map.fetch!(relation_map, relation.id)
-
- empty_count =
- relation.members
- |> Enum.filter(&String.starts_with?(&1.member_role, "platform"))
- |> Enum.count(&(Map.get(&1.member.tags, "gtfs:stop_id", "") == ""))
+ defp validate_has_gtfs_stop_ids(feed, route, relation, route_relations) do
+ route_relation = Map.fetch!(route_relations, relation.id)
+ empty_count = Enum.count(route_relation.platforms, &is_nil(&1.stop_id))
if empty_count > 0 do
Errors.relation_missing_gtfs_stop_ids(feed, route, relation, empty_count)
@@ 295,24 293,37 @@ defmodule Triglav.PublicTransport.Validation do
end
end
- # defp validate_platform_distance_from_route(feed, route, relation) do
- # for platform <- platforms, platform.distance_from_route > @max_platform_distance_from_route do
- # Error.platform_too_far_from_route(route, relation, platform)
- # end
- # end
+ defp validate_platform_distance_from_route(feed, route, relation, route_relations) do
+ route_relation = Map.fetch!(route_relations, relation.id)
+
+ for platform <- route_relation.platforms do
+ if platform.distance_from_route > 25 do
+ Errors.platform_too_far_from_route(feed, route, relation, platform)
+ end
+ end
+ end
- defp validate_platform_tags(feed, route, relation_map) do
+ defp validate_platform_tags(feed, route, route_relations) do
for relation <- route.mapped_route_relations do
- relation = Map.fetch!(relation_map, relation.id)
+ route_relation = Map.fetch!(route_relations, relation.id)
- for member <- relation.members, String.starts_with?(member.member_role, "platform") do
- expected_tags = expected_platform_tags(relation)
+ for platform <- route_relation.platforms do
+ expected_tags = expected_platform_tags(route_relation.relation)
+ member = platform.way || platform.node
for {name, expected} <- expected_tags do
- actual = Map.get(member.member.tags, name)
+ actual = Map.get(member.tags, name)
if actual != expected do
- Errors.platform_invalid_tag_value(feed, route, relation, member.sequence_id, name, actual, expected)
+ Errors.platform_invalid_tag_value(
+ feed,
+ route,
+ relation,
+ platform.sequence_id,
+ name,
+ actual,
+ expected
+ )
end
end
end
@@ 1,7 1,7 @@
defmodule Triglav.PublicTransport.Validation.Errors do
alias Triglav.PublicTransport.Schemas.Error
- alias Triglav.PublicTransport.Schemas.{Feed, Route}
- alias Triglav.Schemas.Osmosis.{Relation, RelationMember}
+ alias Triglav.PublicTransport.Schemas.{Feed, Route, RouteRelationPlatform}
+ alias Triglav.Schemas.Osmosis.{Relation, RelationMember, Way}
@spec no_relations(Feed.t(), Route.t()) :: Error.t()
def no_relations(feed, route) do
@@ 183,16 183,20 @@ defmodule Triglav.PublicTransport.Validation.Errors do
}
end
- @spec platform_too_far_from_route(Feed.t(), Route.t(), Relation.t(), integer(), float()) :: Error.t()
- def platform_too_far_from_route(feed, route, relation, sequence_id, distance) do
+ @spec platform_too_far_from_route(Feed.t(), Route.t(), Relation.t(), RouteRelationPlatform.t()) :: Error.t()
+ def platform_too_far_from_route(feed, route, relation, platform) do
%Error{
key: "platform_too_far_from_route",
operator_id: feed.operator_id,
feed_id: feed.id,
route_ref: route.ref,
relation_id: relation.id,
- sequence_id: sequence_id,
- params: %{distance: distance}
+ sequence_id: platform.sequence_id,
+ params: %{
+ distance: platform.distance_from_route,
+ node_id: platform.node_id,
+ way_id: platform.way_id
+ }
}
end
end