Esempio n. 1
0
def test_invalid_filter_type(test_pbf):
    from pyrosm import OSM
    osm = OSM(filepath=test_pbf)
    try:
        osm.get_network("MyNetwork")
    except ValueError:
        pass
    except Exception as e:
        raise e
Esempio n. 2
0
def test_getting_nodes_and_edges(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import Point, LineString

    osm = OSM(filepath=test_pbf)

    nodes, edges = osm.get_network(nodes=True)
    nodes = nodes.reset_index(drop=True)

    assert isinstance(edges, GeoDataFrame)
    assert isinstance(edges.loc[0, 'geometry'], LineString)

    assert isinstance(nodes, GeoDataFrame)
    assert isinstance(nodes.loc[0, 'geometry'], Point)

    # Test shape
    assert edges.shape == (1215, 23)
    assert nodes.shape == (1147, 8)

    # Edges should have "u" and "v" columns
    required = ["u", "v", "length"]
    ecols = edges.columns
    for col in required:
        assert col in ecols

    # Nodes should have (at least) "id", "lat", and "lon" columns
    required = ["id", "lat", "lon"]
    ncols = nodes.columns
    for col in required:
        assert col in ncols
Esempio n. 3
0
def test_parse_network_with_shapely_bbox(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import MultiLineString, box

    bounds = box(*[26.94, 60.525, 26.96, 60.535])
    # Init with bounding box
    osm = OSM(filepath=test_pbf, bounding_box=bounds)
    gdf = osm.get_network()

    assert isinstance(gdf.loc[0, 'geometry'], MultiLineString)
    assert isinstance(gdf, GeoDataFrame)

    # Test shape
    assert gdf.shape == (74, 21)

    required_cols = [
        'access', 'bridge', 'foot', 'highway', 'lanes', 'lit', 'maxspeed',
        'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry',
        'tags', 'osm_type', 'length'
    ]
    for col in required_cols:
        assert col in gdf.columns

    # Should not include 'motorway' ways by default
    assert "motorway" not in gdf["highway"].unique()

    # The total bounds of the result should not be larger than the filter
    # (allow some rounding error)
    result_bounds = gdf.total_bounds
    for coord1, coord2 in zip(bounds.bounds, result_bounds):
        assert round(coord2, 3) >= round(coord1, 3)
Esempio n. 4
0
def test_saving_network_to_shapefile(test_pbf, test_output_dir):
    import os
    from pyrosm import OSM
    import geopandas as gpd
    import shutil

    if not os.path.exists(test_output_dir):
        os.makedirs(test_output_dir)

    temp_path = os.path.join(test_output_dir, "pyrosm_test.shp")
    osm = OSM(filepath=test_pbf)
    gdf = osm.get_network(network_type="cycling")
    gdf.to_file(temp_path)

    # Ensure it can be read and matches with original one
    gdf2 = gpd.read_file(temp_path)

    cols = gdf.columns
    for col in cols:
        # Geometry col might contain different types of geoms
        # (due to saving MultiLineGeometries which might be read as a "single")
        if col == "geometry":
            continue
        assert gdf[col].tolist() == gdf2[col].tolist()

    # Clean up
    shutil.rmtree(test_output_dir)
Esempio n. 5
0
def test_directed_edge_generator(test_pbf):
    from geopandas import GeoDataFrame
    from pyrosm.graphs import generate_directed_edges
    from pyrosm import OSM
    osm = OSM(test_pbf)
    nodes, edges = osm.get_network(nodes=True)

    # Calculate the number of edges that should be oneway + bidirectional
    mask = edges[oneway_col].isin(oneway_values)
    oneway_edge_cnt = len(edges.loc[mask])
    twoway_edge_cnt = len(edges.loc[~mask])

    # Bidirectional edges
    bidir_edges = generate_directed_edges(edges,
                                          direction="oneway",
                                          from_id_col="u",
                                          to_id_col="v",
                                          force_bidirectional=True)

    assert len(bidir_edges) == 2 * len(edges)

    # Directed edges according the rules in "oneway" column
    dir_edges = generate_directed_edges(edges,
                                        direction="oneway",
                                        from_id_col="u",
                                        to_id_col="v",
                                        force_bidirectional=False)

    assert len(dir_edges) == oneway_edge_cnt + twoway_edge_cnt * 2
Esempio n. 6
0
def bike_nodes_and_edges():
    from pyrosm import OSM
    # UlanBator is good small dataset for testing
    # (unmodified, i.e. not cropped)
    pbf_path = get_data("ulanbator")
    osm = OSM(pbf_path)
    return osm.get_network(nodes=True, network_type="cycling")
Esempio n. 7
0
def test_adding_extra_attribute(helsinki_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame

    osm = OSM(filepath=helsinki_pbf)
    gdf = osm.get_network()
    extra_col = "wikidata"
    extra = osm.get_network(extra_attributes=[extra_col])

    # The extra should have one additional column compared to the original one
    assert extra.shape[1] == gdf.shape[1] + 1
    # Should have same number of rows
    assert extra.shape[0] == gdf.shape[0]
    assert extra_col in extra.columns
    assert len(extra[extra_col].dropna().unique()) > 0
    assert isinstance(gdf, GeoDataFrame)
Esempio n. 8
0
def test_getting_nodes_and_edges_with_bbox(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import Point, LineString, box

    bounds = [26.94, 60.525, 26.96, 60.535]
    # Init with bounding box
    osm = OSM(filepath=test_pbf, bounding_box=bounds)

    nodes, edges = osm.get_network(nodes=True)
    nodes = nodes.reset_index(drop=True)

    assert isinstance(edges, GeoDataFrame)
    assert isinstance(edges.loc[0, 'geometry'], LineString)

    assert isinstance(nodes, GeoDataFrame)
    assert isinstance(nodes.loc[0, 'geometry'], Point)

    # Test shape
    assert edges.shape == (321, 23)
    assert nodes.shape == (317, 8)

    # Edges should have "u" and "v" columns
    required = ["u", "v", "length"]
    ecols = edges.columns
    for col in required:
        assert col in ecols

    # Nodes should have (at least) "id", "lat", and "lon" columns
    required = ["id", "lat", "lon"]
    ncols = nodes.columns
    for col in required:
        assert col in ncols
Esempio n. 9
0
    def compute_geometry(self, bbox, filename=None):
        """
        Parse OSM file (area in bbox) to retrieve information about geometry.

        :param Sequence[float] bbox: area to be parsed in format (min_lon, min_lat, max_lon, max_lat)
        :param Optional[str] filename: map file in .osm.pbf format or None (map will be downloaded)
        """
        assert len(bbox) == 4
        self.bbox_size = (fabs(bbox[2] - bbox[0]), fabs(bbox[3] - bbox[1]))

        if filename is None:
            converter = OsmConverter(bbox)
            filename = converter.filename

        osm = OSM(filename, bounding_box=bbox)
        multipolygons = GeoDataFrame(columns=['tag', 'geometry'])

        natural = osm.get_natural()
        if natural is not None:
            natural = natural.loc[:, ['natural', 'geometry']].rename(
                columns={'natural': 'tag'})
            self.polygons = self.polygons.append(
                natural.loc[natural.geometry.type == 'Polygon'])
            multipolygons = multipolygons.append(
                natural.loc[natural.geometry.type == 'MultiPolygon'])
            natural.drop(natural.index, inplace=True)

        landuse = osm.get_landuse()
        if landuse is not None:
            landuse = landuse.loc[:, ['landuse', 'geometry']].rename(
                columns={'landuse': 'tag'})
            self.polygons = self.polygons.append(
                landuse.loc[landuse.geometry.type == 'Polygon'])
            multipolygons = multipolygons.append(
                landuse.loc[landuse.geometry.type == 'MultiPolygon'])
            landuse.drop(landuse.index, inplace=True)

        # splitting multipolygons to polygons
        for i in range(multipolygons.shape[0]):
            tag = multipolygons.tag.iloc[i]
            for polygon in multipolygons.geometry.iloc[i].geoms:
                self.polygons = self.polygons.append(
                    {
                        'tag': tag,
                        'geometry': polygon
                    }, ignore_index=True)

        roads = osm.get_network()
        if roads is not None:
            roads = self.__dissolve(roads[["highway", "geometry"]])
            self.multilinestrings = GeoDataFrame(
                roads.loc[roads.geometry.type == 'MultiLineString']).rename(
                    columns={'highway': 'tag'})

        self.tag_value.eval(self.polygons, self.multilinestrings, "tag")
Esempio n. 10
0
def test_pdgraph_connectivity():
    from pyrosm.graphs import to_pandana
    import pandas as pd
    from pyrosm import OSM
    osm = OSM(get_data("helsinki_pbf"))
    nodes, edges = osm.get_network(nodes=True)

    # Prerare some test data for aggregations
    restaurants = osm.get_pois(custom_filter={"amenity": ["restaurant"]})
    restaurants = restaurants.loc[restaurants["osm_type"] == "node"]
    restaurants["employee_cnt"] = 1
    x = restaurants["lon"]
    y = restaurants["lat"]

    g = to_pandana(nodes, edges, retain_all=False)

    # Nodes and edges should be in DataFrames
    assert isinstance(g.nodes_df, pd.DataFrame)
    assert isinstance(g.edges_df, pd.DataFrame)

    # Precompute up to 1000 meters
    g.precompute(1000)

    # Link restaurants to graph
    g.set_pois("restaurants", 1000, 5, x, y)

    # Find the distance to nearest 5 restaurants from each node
    nearest_restaurants = g.nearest_pois(1000, "restaurants", num_pois=5)
    assert isinstance(nearest_restaurants, pd.DataFrame)
    assert nearest_restaurants.shape == (5750, 5)

    # Get closest node_ids for each restaurant
    node_ids = g.get_node_ids(x, y)
    assert isinstance(node_ids, pd.Series)
    assert node_ids.min() > 0
    restaurants["node_id"] = node_ids

    # Attach employee counts to the graph
    g.set(node_ids, variable=restaurants.employee_cnt, name="employee_cnt")

    # Aggregate the number of employees within 500 meters from each node
    access = g.aggregate(500, type="sum", decay="linear", name="employee_cnt")
    assert isinstance(access, pd.Series)
    assert len(access) == 5750

    # Test shortest path calculations
    shortest_distances = g.shortest_path_lengths(node_ids[0:100],
                                                 node_ids[100:200],
                                                 imp_name="length")
    assert isinstance(shortest_distances, list)
    assert len(shortest_distances) == 100
    shortest_distances = pd.Series(shortest_distances)
    assert shortest_distances.min().round(0) == 22
    assert shortest_distances.max().round(0) == 2453
    assert shortest_distances.mean().round(0) == 856
Esempio n. 11
0
def test_to_graph_api(test_pbf):
    from pyrosm import OSM
    import networkx as nx
    import igraph
    import pandana
    osm = OSM(test_pbf)
    nodes, edges = osm.get_network(nodes=True)
    # igraph is the default
    ig = osm.to_graph(nodes, edges)
    nxg = osm.to_graph(nodes, edges, graph_type="networkx")
    pdg = osm.to_graph(nodes, edges, graph_type="pandana")
    assert isinstance(nxg, nx.MultiDiGraph)
    assert isinstance(ig, igraph.Graph)
    assert isinstance(pdg, pandana.Network)
Esempio n. 12
0
def test_passing_incorrect_net_type(test_pbf):
    from pyrosm import OSM

    osm = OSM(filepath=test_pbf)
    try:
        osm.get_network("wrong_network")
    except ValueError as e:
        if "'network_type' should be one of the following" in str(e):
            pass
        else:
            raise (e)
    except Exception as e:
        raise e

    try:
        osm.get_network(42)
    except ValueError as e:
        if "'network_type' should be one of the following" in str(e):
            pass
        else:
            raise (e)
    except Exception as e:
        raise e
Esempio n. 13
0
def test_nxgraph_immutable_counts(test_pbf):
    from geopandas import GeoDataFrame
    from pyrosm.graphs import to_networkx
    import networkx as nx
    from pyrosm import OSM
    osm = OSM(test_pbf)
    nodes, edges = osm.get_network(nodes=True)
    g = to_networkx(nodes, edges, retain_all=True)
    n_nodes = len(nodes)

    assert isinstance(g, nx.MultiDiGraph)
    # Check that the edge count matches
    assert nx.number_of_edges(g) == 2430
    assert nx.number_of_nodes(g) == n_nodes
Esempio n. 14
0
def test_graph_exports_correct_number_of_nodes(test_pbf):
    """
    Check issue: #97
    """
    from pyrosm import OSM
    osm = OSM(test_pbf)
    # NetworkX
    nodes, edges = osm.get_network(nodes=True)
    node_cnt = len(nodes)
    nxg = osm.to_graph(nodes,
                       edges,
                       graph_type="networkx",
                       osmnx_compatible=False,
                       retain_all=True)
    assert node_cnt == nxg.number_of_nodes()
Esempio n. 15
0
def test_reading_network_from_area_without_data(helsinki_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame

    # Bounding box for area that does not have any data
    bbox = [24.940514, 60.173849, 24.942, 60.175892]

    osm = OSM(filepath=helsinki_pbf, bounding_box=bbox)

    # The tool should warn if no buildings were found
    with pytest.warns(UserWarning) as w:
        gdf = osm.get_network()
        # Check the warning text
        if "could not find any network data" in str(w):
            pass

    # Result should be None
    assert gdf is None
Esempio n. 16
0
def test_igraph_immutable_counts(test_pbf):
    """
    A simple check to ensure that
    the graph shape is always the
    same with unmutable data.
    """
    from geopandas import GeoDataFrame
    from pyrosm.graphs import to_igraph
    import igraph
    from pyrosm import OSM
    osm = OSM(test_pbf)
    nodes, edges = osm.get_network(nodes=True)
    g = to_igraph(nodes, edges, retain_all=True)
    n_nodes = len(nodes)

    assert isinstance(g, igraph.Graph)
    # Check that the edge count matches
    assert g.ecount() == 2430
    assert g.vcount() == n_nodes
Esempio n. 17
0
def test_filter_network_by_all(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import MultiLineString
    osm = OSM(filepath=test_pbf)
    gdf = osm.get_network(network_type="all")

    assert isinstance(gdf.loc[0, 'geometry'], MultiLineString)
    assert isinstance(gdf, GeoDataFrame)

    # Test shape
    assert gdf.shape == (331, 22)

    required_cols = [
        'access', 'bicycle', 'bridge', 'foot', 'highway', 'lanes', 'lit',
        'maxspeed', 'name', 'oneway', 'ref', 'service', 'surface', 'tunnel',
        'id', 'geometry', 'tags', 'osm_type', 'length'
    ]
    for col in required_cols:
        assert col in gdf.columns
Esempio n. 18
0
def test_filter_network_by_walking(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import MultiLineString
    osm = OSM(filepath=test_pbf)
    gdf = osm.get_network(network_type="walking")

    assert isinstance(gdf.loc[0, 'geometry'], MultiLineString)
    assert isinstance(gdf, GeoDataFrame)

    # Test shape
    assert gdf.shape == (265, 21)

    required_cols = [
        'access', 'bridge', 'foot', 'highway', 'lanes', 'lit', 'maxspeed',
        'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry',
        'tags', 'osm_type', 'length'
    ]
    for col in required_cols:
        assert col in gdf.columns

    # Should not include 'motorway' ways by default
    assert "motorway" not in gdf["highway"].unique()
Esempio n. 19
0
def test_saving_network_to_shapefile(test_pbf, test_output_dir):
    import os
    from pyrosm import OSM
    import geopandas as gpd
    import shutil

    if not os.path.exists(test_output_dir):
        os.makedirs(test_output_dir)

    temp_path = os.path.join(test_output_dir, "pyrosm_test.shp")
    osm = OSM(filepath=test_pbf)
    gdf = osm.get_network(network_type="cycling")
    gdf.to_file(temp_path)

    # Ensure it can be read and matches with original one
    gdf2 = gpd.read_file(temp_path)

    cols = gdf.columns
    for col in cols:
        assert gdf[col].tolist() == gdf2[col].tolist()

    # Clean up
    shutil.rmtree(test_output_dir)
Esempio n. 20
0
def test_filter_network_by_driving_with_service_roads(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    from shapely.geometry import LineString
    osm = OSM(filepath=test_pbf)
    gdf = osm.get_network(network_type="driving+service")

    assert isinstance(gdf.loc[0, 'geometry'], LineString)
    assert isinstance(gdf, GeoDataFrame)

    # Test shape
    assert gdf.shape == (207, 18)

    required_cols = [
        'access', 'bridge', 'highway', 'int_ref', 'lanes', 'lit', 'maxspeed',
        'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry',
        'tags', 'osm_type'
    ]
    for col in required_cols:
        assert col in gdf.columns

    # Should not include 'footway' or 'path' ways by default
    assert "footway" not in gdf["highway"].unique()
    assert "path" not in gdf["highway"].unique()
Esempio n. 21
0
def immutable_nodes_and_edges():
    from pyrosm import OSM
    pbf_path = get_data("test_pbf")
    osm = OSM(pbf_path)
    return osm.get_network(nodes=True)
Esempio n. 22
0
def driving_nodes_and_edges():
    from pyrosm import OSM
    pbf_path = get_data("ulanbator")
    osm = OSM(pbf_path)
    return osm.get_network(network_type="driving", nodes=True)
Esempio n. 23
0
import osmnx as ox
from pyrosm import OSM

# get real map in PBF fromat from https://extract.bbbike.org/
# install pyrosm and all dependencies via pip install pyrosm osmnx networkx

# Initialize the reader
osm = OSM("cpii.osm.pbf")

# Get all walkable roads and the nodes
nodes, edges = osm.get_network(nodes=True, network_type="driving")
# Create NetworkX graph
G = osm.to_graph(nodes, edges, graph_type="networkx")

ccc = edges.head(40)
print(ccc.geometry)
ox.plot_graph(G)
Esempio n. 24
0
def test_network(test_pbf):
    from pyrosm import OSM
    from geopandas import GeoDataFrame
    osm = OSM(test_pbf)
    gdf = osm.get_network()
    assert isinstance(gdf, GeoDataFrame)
Esempio n. 25
0
class OSMMap(Map):
    def __init__(self, region):
        super().__init__()
        self.region = region
        fp = get_data(region)
        self.osm = OSM(fp)
        self.osm.keep_node_info = True
        self.network = self.osm.get_network(
            "all",
            extra_attributes=[
                "lanes:forward",
                "lanes:backward",
                "cycleway:left",
                "cycleway:right",
            ],
        )
        self.node_tags = (self.create_node_tags_lookup()
                          )  # Used to find traffic signals, all-way stops
        self.intersections = GeometricDict(crs="EPSG:4326")
        self.build_intersections(self.intersections)
        self.links = Links(crs="EPSG:4326")
        self.build_links(self.links)
        self.look_for_stop_signs()
        logging.info("Generated %s intersections and %s links." %
                     (len(self.intersections), len(self.links)))

    def create_node_tags_lookup(self):
        ids = np.concatenate([group["id"] for group in self.osm._nodes])
        tags = np.concatenate([group["tags"] for group in self.osm._nodes])
        return {ids[i]: tags[i] for i in range(0, len(ids))}

    def build_intersections(self, intersections):
        # Find all intersections by going through all the ways in the network and counting how often a node is referenced. If it is referenced more than once, it is an intersection.
        counter = Counter()
        for nodes in self.network.nodes:
            for node in nodes:
                counter[node] += 1
        intersections_list = [
            key for key, count in counter.items() if count >= 2
        ]
        logging.info("Building intersections...")
        for intersection in tqdm(intersections_list):
            name = intersection
            coordinates = self.osm._node_coordinates[intersection]
            geometry = Point(coordinates["lon"], coordinates["lat"])
            if (self.node_tags[intersection]
                    and "highway" in self.node_tags[intersection]
                    and self.node_tags[intersection]["highway"]
                    == "traffic_signals"):
                type_ = "Signal"
            elif (self.node_tags[intersection]
                  and "highway" in self.node_tags[intersection]
                  and self.node_tags[intersection]["highway"] == "stop"):
                type_ = "All-way Stop"
            else:
                type_ = "Yield"
            intersections[intersection] = Intersection(name,
                                                       geometry,
                                                       type_=type_)

    @staticmethod
    def speed_converter(speed):
        if speed is None:
            return DEFAULT_SPEED
        speed_split = speed.split()
        if len(speed_split) == 2:
            if speed_split[1] == "mph":
                speed_unit = units.imperial.mile / units.hour
            elif speed_split[1] == "km/h":
                speed_unit = units.kilometer / units.hour
            else:
                speed_unit = DEFAULT_SPEED_UNIT
            return speed_split[0] * speed_unit
        else:
            logging.warning("No speed unit: %s" % (speed, ))
            return speed_split[0] * DEFAULT_SPEED_UNIT

    def create_link_segment(self, nodes, link_id):
        linestring = []
        for node in nodes:
            coordinates = self.osm._node_coordinates[node]
            linestring.append(Point(coordinates["lon"], coordinates["lat"]))
        link_geometry = LineString(linestring)
        if nodes[0] in self.intersections.keys():
            start_node = self.intersections[nodes[0]]
        else:
            start_node = None
        if nodes[-1] in self.intersections.keys():
            end_node = self.intersections[nodes[-1]]
        else:
            end_node = None
        return {
            "link_geometry": link_geometry,
            "start_node": start_node,
            "end_node": end_node,
            "link_id": link_id,
        }

    def build_link_segments(self, link):
        link_segments = []
        link_id = 0
        nodes = [link.nodes[0]]
        for node in link.nodes[1:]:
            nodes.append(node)
            if node in self.intersections.keys():
                link_segments.append(self.create_link_segment(nodes, link_id))
                link_id += 1
                nodes = [node]
        if len(nodes) > 1:
            link_segments.append(self.create_link_segment(nodes, link_id))
        return link_segments

    def lane_calculator(self, link):
        if link["oneway"] == "yes":
            if link["lanes:forward"]:
                number_of_lanes_forward = int(link["lanes:forward"])
            elif link["lanes"]:
                number_of_lanes_forward = int(link["lanes"])
            else:
                number_of_lanes_forward = 1
            number_of_lanes_backward = 0
        elif link["lanes:forward"]:
            number_of_lanes_forward = int(link["lanes:forward"])
            if link["lanes:backward"]:
                number_of_lanes_backward = int(link["lanes:backward"])
                if link["lanes"]:
                    if number_of_lanes_forward + number_of_lanes_backward != int(
                            link["lanes"]):
                        logging.warning(
                            "lanes:forward + lanes:backward != lanes %s" %
                            link["name"])
            elif link["lanes"]:
                number_of_lanes_backward = int(
                    link["lanes"]) - number_of_lanes_forward
            else:
                number_of_lanes_backward = 1
        elif link["lanes"]:
            if link["lanes"] == "1":
                logging.warning("two way link with only one lane %s" %
                                link["id"])
                number_of_lanes_forward = 1
                number_of_lanes_backward = 1
            number_of_lanes_forward = math.ceil(int(link["lanes"]) / 2)
            number_of_lanes_backward = int(link["lanes"]) // 2
        else:
            number_of_lanes_forward = 1
            number_of_lanes_backward = 1
        return number_of_lanes_forward, number_of_lanes_backward

    def cycleway_calculator(self, link):
        cycleway_types = ["lane", "track"]
        if link["oneway"] == "yes":
            if link["cycleway"] in cycleway_types:
                cycleway_forward = link["cycleway"]
                cycleway_backward = None
            elif link["cycleway:right"] in cycleway_types:
                cycleway_forward = link["cycleway:right"]
                cycleway_backward = None
            elif link["cycleway:left"] in cycleway_types:
                cycleway_forward = link["cycleway:left"]
                cycleway_backward = None
            else:
                cycleway_forward = None
                cycleway_backward = None
        else:
            if link["cycleway"] in cycleway_types:
                cycleway_forward = link["cycleway"]
                cycleway_backward = link["cycleway"]
            elif link["cycleway:right"] in cycleway_types:
                cycleway_forward = link["cycleway:right"]
                if link["cycleway:left"] in cycleway_types:
                    cycleway_backward = link["cycleway:left"]
                else:
                    cycleway_backward = None
            elif link["cycleway:left"] in cycleway_types:
                cycleway_backward = link["cycleway:left"]
                cycleway_forward = None
            else:
                cycleway_forward = None
                cycleway_backward = None
        return cycleway_forward, cycleway_backward

    def sidewalk_calculator(self, link):
        if hasattr(link, "foot"):
            if link["foot"] == "no":
                return False
            else:
                return True
        if hasattr(link, "highway"):
            if link["highway"] == "motorway":
                return False
        return True

    def banned_modes(self, link):
        """
        Determine if any modes are banned on a link. Not used for now: we will trust the routing engine."
        """
        banned_modes = []
        if hasattr(link, "bicycle"):
            if link["bicycle"] == "no":
                banned_modes.append("bicycle")
        if hasattr(link, "highway"):
            if link["highway"] == "pedestrian":
                banned_modes.append("bicycle")
                banned_modes.append("driver")
        return set(banned_modes)

    def build_links(self, links):
        logging.info("Building links...")
        for _, link in tqdm(self.network.iterrows(), total=len(self.network)):
            number_of_lanes_forward, number_of_lanes_backward = self.lane_calculator(
                link)
            link_segments = self.build_link_segments(link)
            cycleway_forward, cycleway_backward = self.cycleway_calculator(
                link)
            sidewalk = self.sidewalk_calculator(link)
            highway = link["highway"]
            for segment in link_segments:
                link_id = str(link["id"]) + ":S" + str(
                    segment["link_id"]) + ":D0"
                links[link_id] = Link(
                    link_id,
                    segment["link_geometry"],
                    segment["start_node"],
                    segment["end_node"],
                    max_speed=self.speed_converter(link["maxspeed"]),
                    highway=highway,
                )
                links[link_id].update_segments_from_osm(
                    number_of_lanes=number_of_lanes_forward,
                    cycleway=cycleway_forward,
                    sidewalk=sidewalk,
                )
                if number_of_lanes_backward or cycleway_backward:
                    reversed_segment_link_geometry = LineString(
                        segment["link_geometry"].coords[::-1]
                    )  # Flip it around for the other direction
                    link_id = str(link["id"]) + ":S" + str(
                        segment["link_id"]) + ":D1"
                    links[link_id] = Link(
                        link_id,
                        reversed_segment_link_geometry,
                        segment["end_node"],
                        segment["start_node"],
                        max_speed=self.speed_converter(link["maxspeed"]),
                        highway=highway,
                    )
                    links[link_id].update_segments_from_osm(
                        number_of_lanes=number_of_lanes_backward,
                        cycleway=cycleway_backward,
                        sidewalk=sidewalk,
                    )

    def look_for_stop_signs(self):
        for intersection in self.intersections.values():
            if intersection.type_ == "Yield":
                link_values = list(
                    map(
                        lambda link: HIGHWAY_HIERARCHY.index(link.highway),
                        intersection.input_links,
                    ))
                if link_values:
                    highest_link_value = min(link_values)
                    priority_links = []
                    for link in intersection.input_links:
                        if HIGHWAY_HIERARCHY.index(
                                link.highway) == highest_link_value:
                            priority_links.append(link)
                    intersection.priority_links = priority_links