Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addd postgis example #55

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openlr_dereferencer/decoding/candidate_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def make_candidates(
bear_diff = angle_difference(bearing, lrp.bear)
if abs(bear_diff) > config.max_bear_deviation:
debug(
f"Not considering {candidate} because the bearing difference is {bear_diff} °.",
f"Not considering {candidate} because the bearing difference is {bear_diff} °." +
f"bear: {bearing}. lrp bear: {lrp.bear}",
)
return
Expand Down
94 changes: 94 additions & 0 deletions openlr_dereferencer/example_postgres_map/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""The example map format described in `map_format.md`, conforming to
the interface in openlr_dereferencer.maps"""

import psycopg2
from typing import Iterable
from openlr import Coordinates
from .primitives import Line, Node, ExampleMapError
from openlr_dereferencer.maps import MapReader


class PostgresMapReader(MapReader):
"""
This is a reader for the example map format described in `map_format.md`.

Create an instance with: `ExampleMapReader('example.sqlite')`.
""",

def __init__(self,user,password,dbname):
self.connection = psycopg2.connect(
host="localhost",
port=5432,
user=user,
password=password,
dbname=dbname,
connect_timeout=10,
options="-c statement_timeout=20000",
application_name="openlr",
)
self.cursor = self.connection.cursor()

def get_line(self, line_id: int) -> Line:
# Just verify that this line ID exists.
self.cursor.execute("SELECT line_id FROM openlr_lines WHERE line_id=%s", (line_id,))
if self.cursor.fetchone() is None:
raise ExampleMapError(f"The line {line_id} does not exist")
return Line(self, line_id)

def get_lines(self) -> Iterable[Line]:
self.cursor.execute("SELECT line_id FROM openlr_lines")
for (line_id,) in self.cursor.fetchall():
yield Line(self, line_id)

def get_linecount(self) -> int:
self.cursor.execute("SELECT COUNT(*) FROM openlr_lines")
(count,) = self.cursor.fetchone()
return count

def get_node(self, node_id: int) -> Node:
self.cursor.execute("SELECT node_id FROM openlr_nodes WHERE node_id=%s", (node_id,))
(node_id,) = self.cursor.fetchone()
return Node(self, node_id)

def get_nodes(self) -> Iterable[Node]:
self.cursor.execute("SELECT node_id FROM openlr_nodes")
for (node_id,) in self.cursor.fetchall():
yield Node(self, node_id)

def get_nodecount(self) -> int:
self.cursor.execute("SELECT COUNT(*) FROM openlr_nodes")
(count,) = self.cursor.fetchone()
return count

def find_nodes_close_to(self, coord: Coordinates, dist: float) -> Iterable[Node]:
"""Finds all nodes in a given radius, given in meters
Yields every node within this distance to `coord`."""
lon, lat = coord.lon, coord.lat
stmt = """
SELECT
node_id
FROM openlr_nodes
WHERE ST_Distance(
ST_SetSRID(ST_MakePoint(%s,%s),4326)::geography,
coord::geography
) < %s;
"""
self.cursor.execute(stmt, (lon, lat, dist))
for (node_id,) in self.cursor.fetchall():
yield Node(self, node_id)

def find_lines_close_to(self, coord: Coordinates, dist: float) -> Iterable[Line]:
"Yields all lines within `dist` meters around `coord`"
lon, lat = coord.lon, coord.lat
stmt = """
SELECT
line_id
FROM openlr_lines
WHERE ST_Distance(
ST_SetSRID(ST_MakePoint(%s,%s),4326)::geography,
path::geography
) < %s;
"""
self.cursor.execute(stmt, (lon, lat, dist))
for (line_id,) in self.cursor.fetchall():
yield Line(self, line_id)
19 changes: 19 additions & 0 deletions openlr_dereferencer/example_postgres_map/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
CREATE TABLE openlr_nodes (
node_id BIGINT PRIMARY KEY,
coord geometry(Point,4326) DEFAULT NULL::geometry
);

CREATE TABLE openlr_lines (
line_id BIGINT PRIMARY KEY,
startnode BIGINT NOT NULL,
endnode BIGINT NOT NULL,
frc integer NOT NULL,
fow integer NOT NULL,
path geometry(LineString,4326) DEFAULT NULL::geometry
ref_id BIGINT
);

CREATE INDEX idx_openlr_nodes_geometry ON openlr_nodes USING GIST (coord gist_geometry_ops_2d);
CREATE INDEX idx_openlr_lines_geometry ON openlr_lines USING GIST (path gist_geometry_ops_2d);
CREATE INDEX idx_openlr_lines_startnode ON openlr_lines(startnode int8_ops);
CREATE INDEX idx_openlr_lines_endnode ON openlr_lines(endnode int8_ops);
162 changes: 162 additions & 0 deletions openlr_dereferencer/example_postgres_map/primitives.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"Contains the Node and the Line class of the example format"

from itertools import chain
from typing import Iterable
from openlr import Coordinates, FRC, FOW
from shapely.geometry import LineString
from openlr_dereferencer.maps import Line as AbstractLine, Node as AbstractNode

class Line(AbstractLine):
"Line object implementation for the example format"

def __init__(self, map_reader, line_id: int):
if not isinstance(line_id, int):
raise ExampleMapError(f"Line id '{line_id}' has confusing type {type(line_id)}")
self.map_reader = map_reader
self.line_id_internal = line_id

def __repr__(self):
return f"Line with id={self.line_id} of length {self.length}"

@property
def line_id(self) -> int:
"Returns the line id"
return self.line_id_internal

@property
def start_node(self) -> "Node":
"Returns the node from which this line comes from"
stmt = "SELECT startnode FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(point_id,) = self.map_reader.cursor.fetchone()
return self.map_reader.get_node(point_id)

@property
def end_node(self) -> "Node":
"Returns the node to which this line goes"
stmt = "SELECT endnode FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(point_id,) = self.map_reader.cursor.fetchone()
return self.map_reader.get_node(point_id)

@property
def fow(self) -> FOW:
"Returns the form of way for this line"
stmt = "SELECT fow FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(fow,) = self.map_reader.cursor.fetchone()
return FOW(fow)

@property
def frc(self) -> FRC:
"Returns the functional road class for this line"
stmt = "SELECT frc FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(frc,) = self.map_reader.cursor.fetchone()
return FRC(frc)

@property
def geometry(self) -> LineString:
"Returns the line geometry"
points = [self.point_n(index + 1) for index in range(self.num_points())]
return LineString(points)

def distance_to(self, coord) -> float:
"Returns the distance of this line to `coord` in meters"
stmt = """
SELECT
ST_Distance(
ST_SetSRID(ST_MakePoint(%s,%s),4326)::geography,
path::geography
)
FROM openlr_lines
WHERE
line_id = %s;
"""
cur = self.map_reader.cursor
cur.execute(stmt, (coord.lon, coord.lat, self.line_id))
(dist,) = cur.fetchone()
if dist is None:
return 0.0
return dist

def num_points(self) -> int:
"Returns how many points the path geometry contains"
stmt = "SELECT ST_NumPoints(path) FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(count,) = self.map_reader.cursor.fetchone()
return count

def point_n(self, index) -> Coordinates:
"Returns the `n` th point in the path geometry, starting at 0"
stmt = "SELECT ST_X(ST_PointN(path, %s)), ST_Y(ST_PointN(path, %s)) FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (index, index, self.line_id))
(lon, lat) = self.map_reader.cursor.fetchone()
if lon is None or lat is None:
raise Exception(f"line {self.line_id} has no point {index}!")
return Coordinates(lon, lat)

def near_nodes(self, distance):
"Yields every point within a certain distance, in meters."
stmt = """
SELECT
openlr_nodes.node_id
FROM openlr_nodes,openlr_lines
WHERE
openlr_lines.line_id = %s AND
ST_Distance(
openlr_nodes.coord::geography,
openlr_lines.path::geography
)<= %s
"""
self.map_reader.cursor.execute(stmt, (self.line_id, distance))
for (point_id,) in self.map_reader.cursor.fetchall():
yield self.map_reader.get_node(point_id)

@property
def length(self) -> float:
"Length of line in meters"
stmt = "SELECT ST_Length(path::geography) FROM openlr_lines WHERE line_id = %s"
self.map_reader.cursor.execute(stmt, (self.line_id,))
(result,) = self.map_reader.cursor.fetchone()
return result


class Node(AbstractNode):
"Node class implementation for example_sqlite_map"

def __init__(self, map_reader, node_id: int):
if not isinstance(node_id, int):
raise ExampleMapError(f"Node id '{id}' has confusing type {type(node_id)}")
self.map_reader = map_reader
self.node_id_internal = node_id

@property
def node_id(self):
return self.node_id_internal

@property
def coordinates(self) -> Coordinates:
stmt = "SELECT ST_X(coord), ST_Y(coord) FROM openlr_nodes WHERE node_id = %s"
self.map_reader.cursor.execute(stmt, (self.node_id,))
geo = self.map_reader.cursor.fetchone()
return Coordinates(lon=geo[0], lat=geo[1])

def outgoing_lines(self) -> Iterable[Line]:
stmt = "SELECT line_id FROM openlr_lines WHERE startnode = %s"
self.map_reader.cursor.execute(stmt, (self.node_id,))
for (line_id,) in self.map_reader.cursor.fetchall():
yield Line(self.map_reader, line_id)

def incoming_lines(self) -> Iterable[Line]:
stmt = "SELECT line_id FROM openlr_lines WHERE endnode = %s"
self.map_reader.cursor.execute(stmt, [self.node_id])
for (line_id,) in self.map_reader.cursor.fetchall():
yield Line(self.map_reader, line_id)

def connected_lines(self) -> Iterable[Line]:
return chain(self.incoming_lines(), self.outgoing_lines())


class ExampleMapError(Exception):
"Some error reading the DB"