Skip to content

Commit

Permalink
Use EntranceRef for edges to entrances
Browse files Browse the repository at this point in the history
  • Loading branch information
Robbendebiene committed Nov 15, 2023
1 parent e2e6227 commit f3b29df
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 65 deletions.
104 changes: 63 additions & 41 deletions pipeline/routing/ppr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
import json
import os


# Describes a node (e.g. a Quay or Entrance) from the edges table
class MainNode:
def __init__(self, relation_id, IFOPT, lat, lng, type):
self.relation_id = relation_id
self.IFOPT = IFOPT
self.lat = lat
self.lng = lng
self.type = type



def truncateTables(conn, cur):
cur.execute('TRUNCATE TABLE paths_elements_ref')
cur.execute('TRUNCATE TABLE path_links')
Expand Down Expand Up @@ -62,15 +74,19 @@ def insertPathsElementsRef(cur, pathId, edges):
insertPathsElementsRefSQL(cur, pathId, 'N', edge["to_node_osm_id"])


def insertPathLink(cur, relation_id, pathLink, id_from, id_to, level):
def insertPathLink(cur, relation_id, pathLink, from_id, to_id, from_type, to_type, level):
edgeList = [f"{edge[0]} {edge[1]}" for edge in pathLink]
linestring = "LINESTRING(" + ",".join(edgeList) + ")"

# use 'INSERT INTO ... ON CONFLICT DO NOTHING' to avoid duplicate entries
# 'RETURNING path_id' returns the generated path_id
cur.execute(
'INSERT INTO path_links (stop_area_relation_id, start_node_id, end_node_id, geom, level) VALUES (%s, %s, %s, ST_GeomFromText(%s, 4326), %s) ON CONFLICT DO NOTHING RETURNING path_id',
(relation_id, id_from, id_to, linestring, level)
'''
INSERT INTO path_links (stop_area_relation_id, edge, geom, level)
VALUES (%s, (%s, %s, %s, %s), ST_GeomFromText(%s, 4326), %s)
ON CONFLICT DO NOTHING RETURNING path_id
''',
(relation_id, from_id, to_id, from_type, to_type, linestring, level)
)

path_id = cur.fetchone()
Expand All @@ -94,19 +110,19 @@ def insertAccessSpaces(cur, currentEdge, previousEdge, relation_id):

# create unique id for the access space, that will be filled into the 'IFOPT' column
# 'STOP_PLACE'_'OSM_NODE_ID':'LEVEL_IF_EXISTS'
newDHID = str(relation_id) + "_" + str(currentEdge["from_node_osm_id"]) + ":" + (str(current_level) if current_level != None else "")
newIFOPT = str(relation_id) + "_" + str(currentEdge["from_node_osm_id"]) + ":" + (str(current_level) if current_level != None else "")
geomString = "POINT(" + str(currentEdge["path"][0][0]) + " " + str(currentEdge["path"][0][1]) + ")"

try:
# use INSERT INTO ... ON CONFLICT DO NOTHING to avoid duplicate entries
cur.execute(
'INSERT INTO access_spaces (node_id, relation_id, "level", "IFOPT", geom) VALUES (%s, %s, trim_scale(%s), %s, ST_GeomFromText(%s, 4326)) ON CONFLICT DO NOTHING',
(currentEdge["from_node_osm_id"], relation_id, current_level, newDHID, geomString)
(currentEdge["from_node_osm_id"], relation_id, current_level, newIFOPT, geomString)
)
except Exception as e:
exit(e)

return newDHID, current_level
return newIFOPT, current_level


def requiresAccessSpace(currentEdge, previousEdge):
Expand Down Expand Up @@ -162,11 +178,15 @@ def requiresAccessSpace(currentEdge, previousEdge):
return False


def createPathNetwork(cur, edges, relation_id, dhid_from, dhid_to):
def createPathNetwork(cur, edges, fromNode, toNode):
relation_id = fromNode.relation_id
edgeIter = iter(edges)
firstEdge = next(edgeIter)

previousEdge = firstEdge
previousDHID = dhid_from
previousIFOPT = fromNode.IFOPT
previousType = fromNode.type

fromLevel = firstEdge["level"]
toLevel = firstEdge["level"]

Expand All @@ -178,13 +198,15 @@ def createPathNetwork(cur, edges, relation_id, dhid_from, dhid_to):

for edge in edgeIter:
if requiresAccessSpace(previousEdge, edge): # checks whether the given parameters need the creation of an access space
newDHID, toLevel = insertAccessSpaces(cur, edge, previousEdge, relation_id) # returns a newly created DHID for the access space and the level of the access space
pathId = insertPathLink(cur, relation_id, pathLink, previousDHID, newDHID, toLevel - fromLevel)
newIFOPT, toLevel = insertAccessSpaces(cur, edge, previousEdge, relation_id) # returns a newly created IFOPT for the access space and the level of the access space
newType = "ACCESS_SPACE"
pathId = insertPathLink(cur, relation_id, pathLink, previousIFOPT, newIFOPT, previousType, newType, toLevel - fromLevel)
if pathId:
insertPathsElementsRef(cur, pathId, pathLinkEdges)
pathLink = edge["path"] # create a new pathLink consisting of the current edge
pathLinkEdges = [edge]
previousDHID = newDHID
previousIFOPT = newIFOPT
previousType = newType
fromLevel = toLevel
else:
# append all but the first node of the edge, because the first node is the same as the last node of the previous edge
Expand All @@ -195,28 +217,25 @@ def createPathNetwork(cur, edges, relation_id, dhid_from, dhid_to):

previousEdge = edge

# the last part of the path is not inserted yet (between the last access space and the stop_area_element 'dhid_to')
pathId = insertPathLink(cur, relation_id, pathLink, previousDHID, dhid_to, toLevel - fromLevel)
# the last part of the path is not inserted yet (between the last access space and the stop_area_element)
pathId = insertPathLink(cur, relation_id, pathLink, previousIFOPT, toNode.IFOPT, previousType, toNode.type, toLevel - fromLevel)

if pathId:
insertPathsElementsRef(cur, pathId, pathLinkEdges)


def insertPGSQL(cur, insertRoutes, start, stop):
def insertPGSQL(cur, insertRoutes, fromNode, toNode):
# PPR can return multiple possible paths for one connection:
for route in insertRoutes:
edges = route["edges"]
# distance = route["distance"]
relation_id = stop["relation_id"]
createPathNetwork(cur, edges, fromNode, toNode)

createPathNetwork(cur, edges, relation_id, start["IFOPT"], stop["IFOPT"])


def makeRequest(url, payload, start, stop):
payload["start"]["lat"] = start["lat"]
payload["start"]["lng"] = start["lng"]
payload["destination"]["lat"] = stop["lat"]
payload["destination"]["lng"] = stop["lng"]
def makeRequest(url, payload, fromNode, toNode):
payload["start"]["lat"] = fromNode.lat
payload["start"]["lng"] = fromNode.lng
payload["destination"]["lat"] = toNode.lat
payload["destination"]["lng"] = toNode.lng

try:
response = requests.post(url, json=payload)
Expand Down Expand Up @@ -273,8 +292,8 @@ def main():
# SRID in POSTGIS is default set to 4326 --> x = lng, Y = lat
cur.execute('''
SELECT relation_id,
"start_IFOPT", ST_X(start_geom) as start_lng, ST_Y(start_geom) as start_lat,
"end_IFOPT", ST_X(end_geom) as end_lng, ST_Y(end_geom) as end_lat
"start_IFOPT", ST_X(start_geom) as start_lng, ST_Y(start_geom) as start_lat, start_type,
"end_IFOPT", ST_X(end_geom) as end_lng, ST_Y(end_geom) as end_lat, end_type
FROM stop_area_edges
''')
stop_area_edges = result = cur.fetchall()
Expand All @@ -284,21 +303,24 @@ def main():
exit(e)

for edge in stop_area_edges:
edge_start = {
"relation_id": edge["relation_id"],
"IFOPT": edge["start_IFOPT"],
"lat": edge["start_lat"],
"lng": edge["start_lng"]
}
edge_end = {
"relation_id": edge["relation_id"],
"IFOPT": edge["end_IFOPT"],
"lat": edge["end_lat"],
"lng": edge["end_lng"]
}

json_data = makeRequest(url,payload,edge_start,edge_end)
insertPGSQL(cur,json_data["routes"],edge_start,edge_end)
fromNode = MainNode(
edge["relation_id"],
edge["start_IFOPT"],
edge["start_lat"],
edge["start_lng"],
edge["start_type"]
)

toNode = MainNode(
edge["relation_id"],
edge["end_IFOPT"],
edge["end_lat"],
edge["end_lng"],
edge["end_type"]
)

json_data = makeRequest(url, payload, fromNode, toNode)
insertPGSQL(cur, json_data["routes"], fromNode, toNode)

conn.commit()

Expand All @@ -308,4 +330,4 @@ def main():


if __name__ == "__main__":
main()
main()
34 changes: 22 additions & 12 deletions pipeline/setup/sql/02_setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ CREATE TABLE paths_elements_ref (
);


/*
* Create category type:
* Enum type named "category" to account for the different types of stop place elements.
* Used in the "routing" and the "export" step of the pipeline.
* The order is important as it is used to sort/order the different categories later in the export.
*/
CREATE TYPE CATEGORY AS ENUM ('ENTRANCE', 'QUAY', 'ACCESS_SPACE', 'SITE_PATH_LINK');


/*
* A composite type that holds from and to information about an edge / path link.
*/
CREATE TYPE EDGE_DESCRIPTION AS (
fromIFOPT text,
toIFOPT text,
fromType CATEGORY,
toType CATEGORY
);


/* Create path_links table:
* Table for the elemental path links between nodes.
* Nodes can be stop_area_elements (IFOPT from OSM) and access_spaces (IFOPT generated in the "routing" step).
Expand All @@ -48,26 +68,16 @@ CREATE TABLE paths_elements_ref (
CREATE TABLE path_links (
path_id SERIAL PRIMARY KEY,
stop_area_relation_id INT,
start_node_id TEXT,
end_node_id TEXT,
edge EDGE_DESCRIPTION,
level NUMERIC, -- positive for upwards link, negative for downwards link
geom GEOMETRY,
-- constraint used to filter potential duplicated path links
-- include geom column because in rare cases the start & end node can be identical for different path links
-- e.g. when stairs and escelators start and end at the same nodes.
CONSTRAINT check_unique_2 UNIQUE (start_node_id, end_node_id, geom)
CONSTRAINT check_unique_2 UNIQUE (edge, geom)
);


/*
* Create category type:
* Enum type named "category" to account for the different types of stop place elements.
* Used in the "routing" and the "export" step of the pipeline.
* The order is important as it is used to sort/order the different categories later in the export.
*/
CREATE TYPE category AS ENUM ('ENTRANCE', 'QUAY', 'ACCESS_SPACE', 'SITE_PATH_LINK');


/*
* Create access_spaces table:
* Table for the access spaces that will be generated from the paths.
Expand Down
32 changes: 20 additions & 12 deletions pipeline/stop_places/sql/stop_places.sql
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,22 @@ LANGUAGE SQL IMMUTABLE STRICT;
* Creates the From and To element based on given ids
* Returns null when any argument is null
*/
CREATE OR REPLACE FUNCTION ex_FromTo(a text, b text) RETURNS xml AS
CREATE OR REPLACE FUNCTION ex_FromTo(edge EDGE_DESCRIPTION) RETURNS xml AS
$$
SELECT xmlconcat(
xmlelement(name "From",
xmlelement(name "PlaceRef", xmlattributes($1 AS "ref", 'any' AS "version"))
(SELECT CASE
WHEN edge.fromType = 'ENTRANCE'::category
THEN xmlelement(name "EntranceRef", xmlattributes(edge.fromIFOPT AS "ref", 'any' AS "version"))
ELSE xmlelement(name "PlaceRef", xmlattributes(edge.fromIFOPT AS "ref", 'any' AS "version"))
END)
),
xmlelement(name "To",
xmlelement(name "PlaceRef", xmlattributes($2 AS "ref", 'any' AS "version"))
(SELECT CASE
WHEN edge.toType = 'ENTRANCE'::category
THEN xmlelement(name "EntranceRef", xmlattributes(edge.toIFOPT AS "ref", 'any' AS "version"))
ELSE xmlelement(name "PlaceRef", xmlattributes(edge.toIFOPT AS "ref", 'any' AS "version"))
END)
)
)
$$
Expand Down Expand Up @@ -1137,20 +1145,20 @@ CREATE OR REPLACE VIEW final_access_spaces AS (
CREATE OR REPLACE VIEW stop_area_edges AS (
-- permutation of all quays per relation (same as CROSS JOIN with WHERE)
-- will include both directions since it is a self join
SELECT q1.relation_id, q1."IFOPT" AS "start_IFOPT", q2."IFOPT" AS "end_IFOPT", ST_Centroid(q1.geom) AS start_geom, ST_Centroid(q2.geom) AS end_geom
SELECT q1.relation_id, q1."IFOPT" AS "start_IFOPT", q2."IFOPT" AS "end_IFOPT", ST_Centroid(q1.geom) AS start_geom, ST_Centroid(q2.geom) AS end_geom, 'QUAY'::category AS start_type, 'QUAY'::category AS end_type
FROM final_quays AS q1
INNER JOIN final_quays AS q2
ON q1.relation_id = q2.relation_id AND q1 != q2
UNION ALL
-- permutation of all entrances and quays per relation
-- first direction
SELECT q.relation_id, q."IFOPT" AS "start_IFOPT", e."IFOPT" AS "end_IFOPT", ST_Centroid(q.geom) AS start_geom, ST_Centroid(e.geom) AS end_geom
SELECT q.relation_id, q."IFOPT" AS "start_IFOPT", e."IFOPT" AS "end_IFOPT", ST_Centroid(q.geom) AS start_geom, ST_Centroid(e.geom) AS end_geom, 'QUAY'::category AS start_type, 'ENTRANCE'::category AS end_type
FROM final_quays AS q
INNER JOIN final_entrances AS e
ON e.relation_id = q.relation_id
UNION ALL
-- reverse direction
SELECT q.relation_id, e."IFOPT" AS "start_IFOPT", q."IFOPT" AS "end_IFOPT", ST_Centroid(e.geom) AS start_geom, ST_Centroid(q.geom) AS end_geom
SELECT q.relation_id, e."IFOPT" AS "start_IFOPT", q."IFOPT" AS "end_IFOPT", ST_Centroid(e.geom) AS start_geom, ST_Centroid(q.geom) AS end_geom, 'ENTRANCE'::category AS start_type, 'QUAY'::category AS end_type
FROM final_quays AS q
INNER JOIN final_entrances AS e
ON e.relation_id = q.relation_id
Expand All @@ -1168,7 +1176,7 @@ CREATE OR REPLACE VIEW final_site_path_links AS (
-- use distinct to filter any duplicated joined paths
SELECT DISTINCT ON (pl.path_id)
-- fallback to empty tags if no matching element exists
stop_area_relation_id AS relation_id, pl.path_id::text as id, COALESCE(hw.tags, '{}'::jsonb) as tags, pl.geom, pl.level, start_node_id as "from", end_node_id as "to"
stop_area_relation_id AS relation_id, pl.path_id::text as id, COALESCE(hw.tags, '{}'::jsonb) as tags, pl.geom, pl.level, edge
FROM path_links pl
LEFT JOIN (
SELECT DISTINCT per.path_id, jsonb_combine(highways.tags) as tags
Expand Down Expand Up @@ -1293,25 +1301,25 @@ CREATE OR REPLACE VIEW export_data AS (
FROM (
SELECT
'QUAY'::category AS category, relation_id,
qua."IFOPT" AS "id", qua.tags AS tags, qua.geom AS geom, qua."level" AS "level", NULL AS "from", NULL AS "to"
qua."IFOPT" AS "id", qua.tags AS tags, qua.geom AS geom, qua."level" AS "level", NULL::EDGE_DESCRIPTION AS "edge"
FROM final_quays qua
-- Append all Entrances to the table
UNION ALL
SELECT
'ENTRANCE'::category AS category, relation_id,
ent."IFOPT" AS "id", ent.tags AS tags, ent.geom AS geom, ent."level" AS "level", NULL AS "from", NULL AS "to"
ent."IFOPT" AS "id", ent.tags AS tags, ent.geom AS geom, ent."level" AS "level", NULL::EDGE_DESCRIPTION AS "edge"
FROM final_entrances ent
-- Append all AccessSpaces to the table
UNION ALL
SELECT
'ACCESS_SPACE'::category AS category, relation_id,
acc."IFOPT" AS "id", acc.tags AS tags, acc.geom AS geom, acc."level" AS "level", NULL AS "from", NULL AS "to"
acc."IFOPT" AS "id", acc.tags AS tags, acc.geom AS geom, acc."level" AS "level", NULL::EDGE_DESCRIPTION AS "edge"
FROM final_access_spaces acc
-- Append all Path Links to the table
UNION ALL
SELECT
'SITE_PATH_LINK'::category AS category, relation_id,
pat.id AS "id", pat.tags AS tags, pat.geom AS geom, pat."level" AS "level", pat.from AS "from", pat.to AS "to"
pat.id AS "id", pat.tags AS tags, pat.geom AS geom, pat."level" AS "level", pat.edge AS "edge"
FROM final_site_path_links pat
) stop_elements
INNER JOIN final_stop_places pta
Expand Down Expand Up @@ -1425,7 +1433,7 @@ CREATE OR REPLACE VIEW xml_stopPlaces AS (
-- <LineString>
ex_LineString(ex.geom, ex.id),
-- <From> <To>
ex_FromTo(ex.from, ex.to),
ex_FromTo(ex.edge),
-- <NumberOfSteps>
ex_NumberOfSteps(ex.tags),
-- <AccessFeatureType>
Expand Down

0 comments on commit f3b29df

Please sign in to comment.