# imports

# standard

import random
from math import isclose
import uuid

# local, external
import osmnx as ox
import networkx as nx
from shapely.geometry import Point, LineString

# local, internal

import src.topupopt.data.gis.identify as gis_iden
import src.topupopt.data.gis.osm as gis_osm

# *****************************************************************************
# *****************************************************************************


class TestGisIdentify:
    # *************************************************************************
    # *************************************************************************

    def straight_path_validator(
        self,
        network: nx.MultiDiGraph,
        path: list,
        protected_nodes: list,
        consider_reversed_edges: bool,
        ignore_self_loops: bool,
    ):
        # find out the unique nodes
        set_nodes = set(path)
        # at least three nodes
        assert len(path) >= 3
        # no repeated nodes unless they appear first and last
        for node in set_nodes:
            # count the nodes
            node_count = path.count(node)
            if node_count >= 3:
                # the same node cannot appear three or more times
                assert False
            elif node_count == 2:
                # cycle: the first and last nodes must match
                assert path[0] == path[-1] and path[0] == node
                # cycle: make sure
        # no excluded nodes in the intermediate positions
        for node in protected_nodes:
            assert node not in path[1:-1]
        # intermediate nodes can only have two neighbours
        for node_key in path[1:-1]:
            assert (
                len(set(gis_iden.neighbours(network, node_key, ignore_self_loops=True)))
                == 2
            )
        # end nodes need to have at least one neighbour, except in loops,
        # wherein they need to have two neighbours
        if path[0] == path[-1]:
            # end nodes in loops need to have at least two neighbours
            assert (
                len(set(gis_iden.neighbours(network, path[0], ignore_self_loops=True)))
                >= 2
            )
        else:
            # end nodes need to have at least one neighbour
            assert (
                len(set(gis_iden.neighbours(network, path[0], ignore_self_loops=True)))
                >= 1
            )
            assert (
                len(set(gis_iden.neighbours(network, path[-1], ignore_self_loops=True)))
                >= 1
            )
        # if ignore_self_loops=False, intermediate nodes cannot have self-loops
        if not ignore_self_loops:
            for node in path[1:-1]:
                assert not network.has_edge(node, node)
            # if path[0] == path[-1]:
            #     # loop, applies to all nodes
            #     for node in path:
            #         assert not network.has_edge(node, node)
            # else:
            #     # not a loop, applies only to intermediate nodes
            #     for node in path[1:-1]:
            #         assert not network.has_edge(node, node)
        # make sure it qualifies as a path
        assert gis_iden.is_node_path(
            network, path, consider_reversed_edges=consider_reversed_edges
        )
        # make sure it is a straight path
        assert gis_iden.is_path_straight(
            network,
            path,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )

    # *************************************************************************
    # *************************************************************************

    def test_finding_simplifiable_paths_osmnx(self):
        # network should be a OSM-nx formatted graph
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter='["highway"~"residential|tertiary|unclassified|service"]',
            truncate_by_edge=True,
        )

        # *********************************************************************
        # *********************************************************************

        consider_reversed_edges = False
        ignore_self_loops = False

        # paths
        paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes=[],
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        # verify the paths
        for path in paths:
            self.straight_path_validator(
                network,
                path,
                protected_nodes=[],
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        consider_reversed_edges = False
        ignore_self_loops = True

        # paths
        paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes=[],
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        # verify the paths
        for path in paths:
            self.straight_path_validator(
                network,
                path,
                protected_nodes=[],
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        consider_reversed_edges = True
        ignore_self_loops = False

        # paths
        paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes=[],
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        # verify the paths
        for path in paths:
            self.straight_path_validator(
                network,
                path,
                protected_nodes=[],
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        consider_reversed_edges = True
        ignore_self_loops = True

        # paths
        paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes=[],
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        # verify the paths
        for path in paths:
            self.straight_path_validator(
                network,
                path,
                protected_nodes=[],
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_straight_path_empty_graph(self):
        # test variations:
        # 1) excluded nodes
        # 2) self loops
        # 3) reversed edges

        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()

        # no reversed edges, no self loops, no excluded nodes
        consider_reversed_edges = False
        ignore_self_loops = False
        protected_nodes = []

        # test path validator with non-path
        error_raised = False
        try:
            assert not self.straight_path_validator(
                network,
                [1, 1, 1],
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )
        except AssertionError:
            error_raised = True
        assert error_raised

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)
        assert len(straight_paths) == 0

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        consider_reversed_edges = False
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)
        assert len(straight_paths) == 0

        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()

        # no reversed edges, no self loops, no excluded nodes
        consider_reversed_edges = True
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)
        assert len(straight_paths) == 0

        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()

        # no reversed edges, no self loops, no excluded nodes
        consider_reversed_edges = True
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)
        assert len(straight_paths) == 0

        # *********************************************************************
        # *********************************************************************

        # enhance test coverage

        # add single node
        network.add_node(0)
        path = gis_iden._find_path_direction_insensitive(network, [], 0, False)
        assert type(path) == list
        assert len(path) == 1
        assert repr(path) == repr([0])

    # *************************************************************************
    # *************************************************************************

    def test_find_one_edge_path(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_one_edge_path_w_reversed_edge(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (1, 0, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simple_straight_path(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (1, 2, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simple_straight_path_with_antiparallel_edge(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 1, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_straight_path_cycle(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 0, 0), (0, 2, 0), (1, 1, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0, 1]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2, 0], [1, 2, 0, 1], [2, 1, 0, 2]]
        assert len(straight_paths) == len(true_straight_paths) - 2
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # no self loops, excluded the "middle" node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0, 1]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[2, 0, 1]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2, 0], [1, 2, 0, 1], [2, 1, 0, 2]]
        assert len(straight_paths) == len(true_straight_paths) - 2
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0, 1], [1, 0, 2, 1]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0, 1], [1, 0, 2, 1]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 0], [0, 2, 1]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 0, 2], [2, 0, 1]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simple_straight_path_w_reversed_edge(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (2, 1, 0)])

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simple_straight_path_w_reversed_edge2(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(1, 0, 0), (1, 2, 0)])

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # do not allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, allow self loops, no excluded nodes
        ignore_self_loops = True
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the middle node
        ignore_self_loops = False
        protected_nodes = [1]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = []
        assert len(straight_paths) == len(true_straight_paths)

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the start node
        ignore_self_loops = False
        protected_nodes = [0]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # allow reversed edges, no self loops, excluded the end node
        ignore_self_loops = False
        protected_nodes = [2]

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2], [2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simplifiable_path_four_nodes(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(1, 0, 0), (1, 2, 0), (2, 3, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[1, 2, 3]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2, 3], [3, 2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths) - 1
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
            include_both_directions=True,
        )
        true_straight_paths = [[0, 1, 2, 3], [3, 2, 1, 0]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_find_simplifiable_path_four_node_cycle(self):
        # *********************************************************************
        # *********************************************************************

        # network with a two edge path
        network = nx.MultiDiGraph()
        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 3, 0), (0, 3, 0)])

        # *********************************************************************
        # *********************************************************************

        # do not consider reversed edges
        consider_reversed_edges = False

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [[0, 1, 2, 3]]
        assert len(straight_paths) == len(true_straight_paths)
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

        # consider reversed edges
        consider_reversed_edges = True

        # *********************************************************************
        # *********************************************************************

        # no reversed edges, no self loops, no excluded nodes
        ignore_self_loops = False
        protected_nodes = []

        straight_paths = gis_iden.find_simplifiable_paths(
            network,
            protected_nodes,
            consider_reversed_edges=consider_reversed_edges,
            ignore_self_loops=ignore_self_loops,
        )
        true_straight_paths = [
            [0, 1, 2, 3, 0],
            [1, 2, 3, 0, 1],
            [2, 3, 0, 1, 2],
            [3, 0, 1, 2, 3],
        ]
        assert len(straight_paths) == len(true_straight_paths) - 3
        for straight_path in straight_paths:
            assert straight_path in true_straight_paths
            self.straight_path_validator(
                network,
                straight_path,
                protected_nodes,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_start_end_points_identification(self):
        # create LineString object

        line = LineString([(0, 0), (1, 0), (2, 0)])

        # tolerance

        proximity_tolerance = 1e-3

        # start point

        point_a = Point(0, 0)

        # end point

        point_b = Point(2, 0)

        # approximately the start point

        point_c = Point(0 + proximity_tolerance / 2, 0)

        # approximately the end point

        point_d = Point(2 + proximity_tolerance / 2, 0)

        # close, but not quite the start point

        point_e = Point(0 + 2 * proximity_tolerance, 0)

        # close, but not quite the end point

        point_f = Point(2 + 2 * proximity_tolerance, 0)

        # another point on the line other than the start and end points

        point_g = Point(1, 0)

        # tests

        assert gis_iden.is_start_or_end_point_or_close(
            line, point_a, tolerance=proximity_tolerance
        )

        assert gis_iden.is_start_or_end_point_or_close(
            line, point_b, tolerance=proximity_tolerance
        )

        assert gis_iden.is_start_or_end_point_or_close(
            line, point_c, tolerance=proximity_tolerance
        )

        assert gis_iden.is_start_or_end_point_or_close(
            line, point_d, tolerance=proximity_tolerance
        )

        assert not gis_iden.is_start_or_end_point_or_close(
            line, point_e, tolerance=proximity_tolerance
        )

        assert not gis_iden.is_start_or_end_point_or_close(
            line, point_f, tolerance=proximity_tolerance
        )

        assert not gis_iden.is_start_or_end_point_or_close(
            line, point_g, tolerance=proximity_tolerance
        )

        # other method

        assert gis_iden.is_start_or_end_point(line, point_a)

        assert gis_iden.is_start_or_end_point(line, point_b)

        assert not gis_iden.is_start_or_end_point(line, point_c)

        assert not gis_iden.is_start_or_end_point(line, point_d)

        assert not gis_iden.is_start_or_end_point(line, point_e)

        assert not gis_iden.is_start_or_end_point(line, point_f)

        assert not gis_iden.is_start_or_end_point(line, point_g)

    # *************************************************************************
    # *************************************************************************

    def test_find_unconnected_nodes(self):
        G = nx.MultiDiGraph()

        G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 1), (2, 0)])

        G.add_nodes_from([4, 5])

        node_keys = gis_iden.find_unconnected_nodes(G)

        true_node_keys = [4, 5]

        assert len(node_keys) == len(true_node_keys)

        for node_key in node_keys:
            assert node_key in true_node_keys

    # *************************************************************************
    # *************************************************************************

    def test_identify_get_from_a_to_b(self):
        # edge_keys = gis_iden.get_edges_from_a_to_b(G, 0, 1)

        G = nx.MultiDiGraph()

        G.add_edges_from(
            [
                (0, 1, {"a": 1}),
                (1, 2, {"b": 2}),
                (2, 3, {"c": 3}),
                (2, 3, {"d": 4}),
                (1, 0, {"e": 5}),
                (3, 2, {"f": 6}),
            ]
        )

        # from node 0 to node 1

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 0, 1)

        true_edge_keys = [(0, 1, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # from node 1 to node 0

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 1, 0)

        true_edge_keys = [(1, 0, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # from node 1 to node 2

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 1, 2)

        true_edge_keys = [(1, 2, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # from node 2 to node 1

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 2, 1)

        true_edge_keys = []

        assert len(true_edge_keys) == len(edge_keys)

        # from node 2 to node 3

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 2, 3)

        true_edge_keys = [(2, 3, 0), (2, 3, 1)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # from node 3 to node 2

        true_edge_keys = [(3, 2, 0)]

        edge_keys = gis_iden.get_edges_from_a_to_b(G, 3, 2)

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

    # *************************************************************************
    # *************************************************************************

    def test_identify_get_edges_involving_nodes(self):
        G = nx.MultiDiGraph()

        G.add_node("noedges")

        G.add_edges_from([(0, 1, 0), (1, 2, 0), (1, 2, 1)])

        # no edges
        node_key = "noedges"
        edges = gis_iden.get_edges_involving_node(G, node_key)
        assert len(edges) == 0

        # one edge
        node_key = 0
        true_edges = [(0, 1, 0)]
        edges = gis_iden.get_edges_involving_node(G, node_key)
        assert len(edges) == len(true_edges)
        for edge in edges:
            assert edge in true_edges

        # multiple edges
        node_key = 1
        true_edges = [(0, 1, 0), (1, 2, 1), (1, 2, 0)]
        edges = gis_iden.get_edges_involving_node(G, node_key)
        assert len(edges) == len(true_edges)
        for edge in edges:
            assert edge in true_edges

    # *************************************************************************
    # *************************************************************************

    def test_identify_get_edges_between_nodes(self):
        G = nx.MultiDiGraph()

        G.add_edges_from(
            [
                (0, 1, {"a": 1}),
                (1, 2, {"b": 2}),
                (2, 3, {"c": 3}),
                (2, 3, {"d": 4}),
                (1, 0, {"e": 5}),
                (3, 2, {"f": 6}),
                (4, 1, {"f": 6}),
            ]
        )

        # between two nodes that are not connected

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 4)

        assert 0 == len(edge_keys)

        # between nodes 0 and 1, nominal direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 0, 1)

        true_edge_keys = [(0, 1, 0), (1, 0, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # between nodes 0 and 1, reverse direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 1, 0)

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # between nodes 1 and 2, nominal direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 1, 2)

        true_edge_keys = [(1, 2, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # between nodes 1 and 2, reverse direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 1)

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # between nodes 2 and 3, nominal direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 2, 3)

        true_edge_keys = [(2, 3, 0), (2, 3, 1), (3, 2, 0)]

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

        # between nodes 2 and 3, reverse direction

        edge_keys = gis_iden.get_edges_between_two_nodes(G, 3, 2)

        assert len(true_edge_keys) == len(edge_keys)

        for edge_key in edge_keys:
            assert edge_key in true_edge_keys

    # *************************************************************************
    # *************************************************************************

    def test_identify_edges_closest_to_nodes(self):
        # network should be a OSM-nx formatted graph

        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter='["highway"~"residential|tertiary|unclassified|service"]',
            truncate_by_edge=True,
        )

        # pick a certain number of nodes randomly
        number_nodes = 3
        node_keys = list(network.nodes())
        while len(node_keys) > number_nodes:
            node_keys.pop(random.randint(0, len(node_keys) - 1))

        # find out which edges are closest
        edge_keys, projected_network = gis_iden.identify_edge_closest_to_node(
            network=network, node_keys=node_keys
        )

        # for each node, verify that there is no closer edge
        # prepare the edge geometries
        edge_key_geos = [
            (
                projected_network.edges[edge_key][gis_osm.KEY_OSMNX_GEOMETRY]
                if gis_osm.KEY_OSMNX_GEOMETRY in projected_network.edges[edge_key]
                else LineString(
                    [
                        (
                            projected_network.nodes[edge_key[0]][gis_osm.KEY_OSMNX_X],
                            projected_network.nodes[edge_key[0]][gis_osm.KEY_OSMNX_Y],
                        ),
                        (
                            projected_network.nodes[edge_key[1]][gis_osm.KEY_OSMNX_X],
                            projected_network.nodes[edge_key[1]][gis_osm.KEY_OSMNX_Y],
                        ),
                    ]
                )
            )
            for edge_key in edge_keys
        ]
        for node_index, node_key in enumerate(node_keys):
            # prepare the node geometry
            the_point = Point(
                projected_network.nodes[node_key][gis_osm.KEY_OSMNX_X],
                projected_network.nodes[node_key][gis_osm.KEY_OSMNX_Y],
            )
            # calculate the distances to every edge
            edge_point_distances = the_point.distance(edge_key_geos)
            # find the distance to the edge identified as the closest one
            shortest_distance = edge_point_distances[node_index]
            # the edge identified must lead to the shortest distance
            assert shortest_distance == min(edge_point_distances)

        # find out which edges are closest using the projected network
        edge_keys_proj, _ = gis_iden.identify_edge_closest_to_node(
            network=projected_network, node_keys=node_keys
        )
        # assert that the same edges have been returned (with the projected network)
        assert len(edge_keys) == len(edge_keys_proj)
        for edge_key1, edge_key2 in zip(edge_keys, edge_keys_proj):
            assert edge_key1 == edge_key2

    # *************************************************************************
    # *************************************************************************

    def test_identify_roundabouts(self):
        # *********************************************************************
        # *********************************************************************

        edge_container = [
            # roundabout with 2 nodes
            (0, 1, {"length": 3, "oneway": True}),
            (1, 0, {"length": 5, "oneway": True}),
            # roundabout with 3 nodes
            (2, 3, {"length": 8, "oneway": True}),
            (3, 4, {"length": 19, "oneway": True}),
            (4, 2, {"length": 4, "oneway": True}),
            # roundabout with 4 nodes
            (5, 6, {"length": 8, "oneway": True}),
            (6, 7, {"length": 5, "oneway": True}),
            (7, 8, {"length": 10, "oneway": True}),
            (8, 5, {"length": 7, "oneway": True}),
            # roundabout within roundabout 2
            (2, 4, {"length": 6, "oneway": True}),
            # roundabout overlapping with roundabouts 1 and 3
            (9, 10, {"length": 14, "oneway": True}),
            (10, 11, {"length": 57, "oneway": True}),
            (11, 9, {"length": 13, "oneway": True}),
            # fake roundabouts
            # self loop
            (12, 12, {"length": 0, "oneway": True}),
            # no oneway attr
            (13, 14, {"length": 9}),
            (14, 15, {"length": 6, "oneway": True}),
            (15, 13, {"length": 7, "oneway": True}),
            # oneway = False
            (16, 17, {"length": 9, "oneway": True}),
            (17, 18, {"length": 5, "oneway": False}),
            (18, 19, {"length": 3, "oneway": True}),
            (19, 16, {"length": 2, "oneway": True}),
            # connect roundabouts 1 and 3 with a edge
            (1, 6, {"length": 9, "oneway": False}),
            (6, 1, {"length": 24, "oneway": False}),
            # create an edge between node 7 and 5 that cannot be used,
            (7, 5, {"length": 6, "oneway": False}),
        ]

        network = nx.MultiDiGraph()

        network.add_edges_from(edge_container)

        # *********************************************************************
        # *********************************************************************

        # true roundabouts: must be oneway streets forming an endless loop

        true_roundabouts = [
            # roundabout with 2 nodes
            [0, 1],
            # roundabout with 3 nodes
            [2, 3, 4],
            # roundabout with 4 nodes
            [5, 6, 7, 8],
            # roundabout within roundabout 2
            [2, 4],
            # roundabout overlapping with roundabouts 1 and 3
            [9, 10, 11],
        ]

        for roundabout in true_roundabouts:
            assert gis_iden.is_roundabout(network, path=roundabout)

        # *********************************************************************
        # *********************************************************************

        # fake roundabouts

        fake_roundabouts = [
            # self loop
            [12, 12],
            # no oneway attr
            [13, 14, 15],
            # oneway = False
            [16, 17, 18, 19],
            # true roundabout with one non-existent node added
            [2, 3, "h", 4],
            # path whose last node does not lead to the first through a valid edge
            [5, 6, 7],
            # true roundabout without the last node (8)
            [5, 6, 7, 19],
            # # connect 1 and 3 with a edge
            # [1, 6] # why?
        ]

        for roundabout in fake_roundabouts:
            assert not gis_iden.is_roundabout(network, path=roundabout)

        # *********************************************************************
        # *********************************************************************

        # errors

        # incorrect path

        error_raised = False
        try:
            gis_iden.is_roundabout(network, path=[1])
        except ValueError:
            error_raised = True
        assert error_raised

    # *************************************************************************
    # *************************************************************************

    def test_identify_points_extremities(self):
        # *********************************************************************
        # *********************************************************************

        # redundant points

        line_coords = tuple([(1.0, 1.0), (2.0, 2.0), (3.0, 0.0)])
        line = LineString(line_coords)

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(1, 1), Point(2, 2), Point(3, 0)],
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([2])

        # redundant points, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(1, 1), Point(2, 2), Point(3, 0)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([2])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0, 0, 0]
        true_start_distances = [0, 1.41421356, 2.23606798]
        true_end_distances = [2.23606798, 2.23606798, 0]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # redundant points, different order

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line, points=[Point(3, 0), Point(2, 2), Point(1, 1)]
        )

        assert repr(close_to_start) == repr([2])
        assert repr(close_to_end) == repr([0])

        # redundant points, different order, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(3, 0), Point(2, 2), Point(1, 1)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([2])
        assert repr(close_to_end) == repr([0])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0, 0, 0]
        true_start_distances = [2.23606798, 1.41421356, 0]
        true_end_distances = [0, 2.23606798, 2.23606798]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # redundant points, yet another order

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line, points=[Point(2, 2), Point(3, 0), Point(1, 1)]
        )

        assert repr(close_to_start) == repr([2])
        assert repr(close_to_end) == repr([1])

        # redundant points, yet another order, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(2, 2), Point(3, 0), Point(1, 1)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([2])
        assert repr(close_to_end) == repr([1])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0, 0, 0]
        true_start_distances = [1.41421356, 2.23606798, 0]
        true_end_distances = [2.23606798, 0, 2.23606798]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # new points, directly on the line

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(1.2, 1.2), Point(3, 0), Point(1.5, 1.5)],
        )

        assert repr(close_to_start) == repr([])
        assert repr(close_to_end) == repr([1])

        # new points, directly on the line, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(1.2, 1.2), Point(3, 0), Point(1.5, 1.5)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([])
        assert repr(close_to_end) == repr([1])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0, 0, 0]
        true_start_distances = [0.28284271, 2.23606798, 0.70710678]
        true_end_distances = [2.16333077, 0, 2.12132034]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # new points, extending beyond the line

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.5), Point(3, 0), Point(4, -2)],
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([1, 2])

        # new points, extending beyond the line, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.5), Point(3, 0), Point(4, -2)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([1, 2])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0.70710678, 0, 2.23606798]
        true_start_distances = [0.70710678, 2.23606798, 4.24264069]
        true_end_distances = [2.54950976, 0, 2.23606798]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # new points, extending beyond the line

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.5), Point(1.0, 1.0), Point(4, -2)],
        )

        assert repr(close_to_start) == repr([0, 1])
        assert repr(close_to_end) == repr([2])

        # new points, extending beyond the line, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.5), Point(1.0, 1.0), Point(4, -2)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([0, 1])
        assert repr(close_to_end) == repr([2])
        abs_tols = [1e-3, 1e-3, 1e-3]
        true_line_distances = [0.70710678, 0, 2.23606798]
        true_start_distances = [0.70710678, 0, 4.24264069]
        true_end_distances = [2.54950976, 2.23606798, 2.23606798]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # new points, not on the line
        # expected result: the new points appear on the geometry

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.75), Point(3.0, -0.5)],
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([1])

        # new points, not on the line, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.75), Point(3.0, -0.5)],
            return_distances=True,
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([1])
        abs_tols = [1e-3, 1e-3]
        true_line_distances = [0.55901699, 0.5]
        true_start_distances = [0.55901699, 2.5]
        true_end_distances = [2.61007663, 0.5]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # new points, close to multiple segments

        line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
        line = LineString(line_coords)

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line,
            points=[Point(0.5, 0.5)],
        )

        assert repr(close_to_start) == repr([])
        assert repr(close_to_end) == repr([])

        # new points, close to multiple segments, with distance

        (
            close_to_start,
            close_to_end,
            line_distances,
            start_distances,
            end_distances,
        ) = gis_iden.close_to_extremities(
            line=line, points=[Point(0.5, 0.5)], return_distances=True
        )

        assert repr(close_to_start) == repr([])
        assert repr(close_to_end) == repr([])
        abs_tols = [1e-3]
        true_line_distances = [0.5]
        true_start_distances = [0.70710678]
        true_end_distances = [0.70710678]
        for (
            line_d,
            start_d,
            end_d,
            true_line_d,
            true_start_d,
            true_end_d,
            abs_tol,
        ) in zip(
            line_distances,
            start_distances,
            end_distances,
            true_line_distances,
            true_start_distances,
            true_end_distances,
            abs_tols,
        ):
            assert isclose(line_d, true_line_d, abs_tol=abs_tol)
            assert isclose(start_d, true_start_d, abs_tol=abs_tol)
            assert isclose(end_d, true_end_d, abs_tol=abs_tol)

        # *********************************************************************
        # *********************************************************************

        # point equidistant to start and end, favour start

        line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
        line = LineString(line_coords)

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line, points=[Point(0.5, -0.5)], use_start_point_equidistant=True
        )

        assert repr(close_to_start) == repr([0])
        assert repr(close_to_end) == repr([])

        # *********************************************************************
        # *********************************************************************

        # point equidistant to start and end, favour end

        line_coords = tuple([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
        line = LineString(line_coords)

        close_to_start, close_to_end = gis_iden.close_to_extremities(
            line=line, points=[Point(0.5, -0.5)], use_start_point_equidistant=False
        )

        assert repr(close_to_start) == repr([])
        assert repr(close_to_end) == repr([0])

    # *************************************************************************
    # *************************************************************************

    def test_identify_self_loops(self):
        # find one self-loop

        network = nx.MultiDiGraph()

        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 0, 0), (1, 1, 0)])

        true_selflooping_nodes = [1]

        selflooping_nodes = list(gis_iden.find_self_loops(network))

        assert len(selflooping_nodes) == len(true_selflooping_nodes)

        for node_key in selflooping_nodes:
            assert node_key in true_selflooping_nodes

        # find two self-loops

        network = nx.MultiDiGraph()

        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 0, 0), (1, 1, 0), (2, 2, 0)])

        true_selflooping_nodes = [1, 2]

        selflooping_nodes = list(gis_iden.find_self_loops(network))

        assert len(selflooping_nodes) == len(true_selflooping_nodes)

        for node_key in selflooping_nodes:
            assert node_key in true_selflooping_nodes

        # find no self-loops

        network = nx.MultiDiGraph()

        network.add_edges_from([(0, 1, 0), (1, 2, 0), (2, 0, 0)])

        true_selflooping_nodes = []

        selflooping_nodes = list(gis_iden.find_self_loops(network))

        assert len(selflooping_nodes) == len(true_selflooping_nodes)

        assert len(true_selflooping_nodes) == 0

    # *************************************************************************
    # *************************************************************************

    def test_reversed_edges(self):
        #

        network = nx.MultiDiGraph()

        network.add_nodes_from(
            [
                (0, {gis_osm.KEY_OSMNX_X: 0, gis_osm.KEY_OSMNX_Y: 0}),
                (1, {gis_osm.KEY_OSMNX_X: 0, gis_osm.KEY_OSMNX_Y: 1}),
                (2, {gis_osm.KEY_OSMNX_X: 0, gis_osm.KEY_OSMNX_Y: 1}),
                (3, {gis_osm.KEY_OSMNX_X: 0, gis_osm.KEY_OSMNX_Y: 2}),
            ]
        )

        network.add_edges_from(
            [
                # reversed, without geometry
                (
                    0,
                    1,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 1,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                (
                    1,
                    0,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 1,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # reversed with geometry
                (
                    2,
                    3,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 2,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString([(0, 1), (0, 2)]),
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                (
                    3,
                    2,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 2,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString([(0, 2), (0, 1)]),
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # parallel to (0,1,0) but with different osmid
                (
                    0,
                    1,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 3,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString(
                            [(0, 0), (1, 0), (1, 1), (0, 1)]
                        ),
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                # parallel to (2,3,0) but with different osmid
                (
                    2,
                    3,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 4,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString(
                            [(0, 1), (1, 1), (1, 2), (0, 2)]
                        ),
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                # parallel to (2,3,0) but with a different geometry
                (
                    2,
                    3,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 2,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString(
                            [(0, 1), (1, 1), (1, 2), (0, 2)]
                        ),
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                # parallel to (2,3,0) but without a geometry
                (
                    2,
                    3,
                    3,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 2,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                # parallel to (3,2,0) but without a geometry
                (
                    3,
                    2,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 4,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # parallel to (3,2,0) but with a different geometry
                (
                    3,
                    2,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 2,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_GEOMETRY: LineString([(0, 0), (0, 1)]),
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # edge sharing one node with (0,1,0) and (2,3,0)
                (
                    0,
                    2,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 5,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_LENGTH: 4,
                    },
                ),
                # edge that is identical to (1,0,0) but has the opposite reversed attr.
                (
                    1,
                    0,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 1,
                        gis_osm.KEY_OSMNX_LENGTH: 1,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                # edge that is identical to (1,0,0) but has a different length
                (
                    1,
                    0,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 1,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # edges with different list sizes for 'reversed'
                (
                    5,
                    0,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: 1,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # simplified edges with the wrong number of osmids
                (
                    6,
                    7,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                (
                    7,
                    6,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2, 3],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # simplified edges with different osmids
                (
                    6,
                    7,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 3],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: False,
                    },
                ),
                (
                    7,
                    6,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # edges without a 'reversed' attribute
                (
                    7,
                    6,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                    },
                ),
                # edges with different 'osmid' attribute types
                (
                    7,
                    6,
                    3,
                    {
                        gis_osm.KEY_OSMNX_OSMID: {1, 2},
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: True,
                    },
                ),
                # simplified edges with non-boolean types
                (
                    7,
                    8,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: 0,
                    },
                ),
                (
                    8,
                    7,
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [2, 1],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: 1,
                    },
                ),
                # simplified edges without True or without False
                (
                    7,
                    8,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True],
                    },
                ),
                (
                    8,
                    7,
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [2, 1],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [False],
                    },
                ),
                # simplified edges
                (
                    7,
                    8,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                    },
                ),
                (
                    8,
                    7,
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [2, 1],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                    },
                ),
            ]
        )

        # test reversed edges without geometry
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(1, 0, 0)
        )
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(1, 0, 0), edge_b=(0, 1, 0)
        )

        # test reversed edges with geometry
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 0), edge_b=(3, 2, 0)
        )
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 0), edge_b=(2, 3, 0)
        )

        # test non-reversed edges between the same nodes
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(1, 0, 0), edge_b=(0, 1, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 1), edge_b=(1, 0, 0)
        )

        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(0, 1, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 1), edge_b=(0, 1, 0)
        )

        # test non-reversed edges between the same nodes
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 0), edge_b=(2, 3, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 1), edge_b=(3, 2, 0)
        )

        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 0), edge_b=(2, 3, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 1), edge_b=(2, 3, 0)
        )

        # test edges that are not parallel/anti-parallel
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(0, 2, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 2, 0), edge_b=(0, 1, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(2, 3, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 0), edge_b=(0, 1, 0)
        )

        # test edges with the same osmid but whose reversed attribues are the same
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(1, 0, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(1, 0, 1), edge_b=(0, 1, 0)
        )

        # test edges that have the same osmid but somehow have different lengths
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(0, 1, 0), edge_b=(1, 0, 2)
        )

        # test edges that have the different geometries
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 2), edge_b=(3, 2, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 0), edge_b=(2, 3, 2)
        )

        # # old:
        # # test one edge with a simple geometry against one without a geometry (False case: no match)
        # assert not gis_iden.edges_are_in_reverse(
        #     network, edge_a=(2, 3, 3), edge_b=(3, 2, 2))
        # assert not gis_iden.edges_are_in_reverse(
        #     network, edge_a=(3, 2, 2), edge_b=(2, 3, 3))
        # # test one edge with a simple geometry against one without a geometry (True case: match)
        # assert gis_iden.edges_are_in_reverse(
        #     network, edge_a=(2, 3, 3), edge_b=(3, 2, 0))
        # assert gis_iden.edges_are_in_reverse(
        #     network, edge_a=(3, 2, 0), edge_b=(2, 3, 3))

        # new: geometries should exist in both or not at all
        # test one edge with a simple geometry against one without a geometry (False case: no match)
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 3), edge_b=(3, 2, 2)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 2), edge_b=(2, 3, 3)
        )
        # test one edge with a simple geometry against one without a geometry (True case: match)
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 3), edge_b=(3, 2, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 0), edge_b=(2, 3, 3)
        )

        # test one edge with a simplified geometry against one without a geometry (False)
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(2, 3, 1), edge_b=(3, 2, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(3, 2, 1), edge_b=(2, 3, 1)
        )

        # test non-inexistent edges
        error_raised = False
        try:
            gis_iden.edges_are_in_reverse(network, edge_a=(5, 0, 0), edge_b=(0, 5, 0))
        except ValueError:
            error_raised = True
        assert error_raised

        # test simplified edges with the wrong number of osmids
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 0), edge_b=(7, 6, 0)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 6, 0), edge_b=(6, 7, 0)
        )
        # test simplified edges with thedifferent osmids
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 1), edge_b=(7, 6, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 6, 1), edge_b=(6, 7, 1)
        )
        # test simplified edges
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 0), edge_b=(7, 6, 1)
        )
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 6, 1), edge_b=(6, 7, 0)
        )

        # test edges with a 'reversed' attribute
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 0), edge_b=(7, 6, 2)
        )
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 6, 2), edge_b=(6, 7, 0)
        )

        # test simplified edges without True or without False
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 8, 1), edge_b=(8, 7, 1)
        )
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(8, 7, 1), edge_b=(7, 8, 1)
        )
        # test simplified edges
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 8, 2), edge_b=(8, 7, 2)
        )
        assert gis_iden.edges_are_in_reverse(
            network, edge_a=(8, 7, 2), edge_b=(7, 8, 2)
        )

        # errors

        # test edges with different 'osmid' attribute types
        error_raised = False
        try:
            assert not gis_iden.edges_are_in_reverse(
                network, edge_a=(6, 7, 0), edge_b=(7, 6, 3)
            )
        except ValueError:
            error_raised = True
        assert error_raised

        # test edges with different 'osmid' attribute types
        error_raised = False
        try:
            assert not gis_iden.edges_are_in_reverse(
                network, edge_a=(7, 6, 3), edge_b=(6, 7, 0)
            )
        except ValueError:
            error_raised = True
        assert error_raised

        # test simplified edges with non-boolean types
        error_raised = False
        try:
            assert not gis_iden.edges_are_in_reverse(
                network, edge_a=(7, 8, 0), edge_b=(8, 7, 0)
            )
        except ValueError:
            error_raised = True
        assert error_raised
        # test simplified edges with non-boolean types, reverse
        error_raised = False
        try:
            assert not gis_iden.edges_are_in_reverse(
                network, edge_a=(8, 7, 0), edge_b=(7, 8, 0)
            )
        except ValueError:
            error_raised = True
        assert error_raised
        # test simplified edges with non-boolean types
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 0), edge_b=(8, 7, 0)
        )
        # test simplified edges with non-boolean types, reverse
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(8, 7, 0), edge_b=(6, 7, 0)
        )
        # test simplified edges with non-boolean types
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(6, 7, 0), edge_b=(7, 8, 0)
        )
        # test simplified edges with non-boolean types, reverse
        assert not gis_iden.edges_are_in_reverse(
            network, edge_a=(7, 8, 0), edge_b=(6, 7, 0)
        )

    # *************************************************************************
    # *************************************************************************

    def test_osmnx_compliance(self):
        # get the network
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter=('["highway"~"residential|tertiary|unclassified|service"]'),
            truncate_by_edge=True,
        )

        for edge_key in network.edges(keys=True):
            assert gis_iden.is_edge_osmnx_compliant(network, edge_key)

        # try a non-existent edge
        edge_key = ("a", "b", "c")
        assert not network.has_edge(*edge_key)

        error_raised = False
        try:
            assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        except ValueError:
            error_raised = True
        assert error_raised

        # add uncompliant edges

        network.add_edges_from(
            [
                # edge with non-integer osmids
                (
                    "a",
                    "b",
                    0,
                    {
                        gis_osm.KEY_OSMNX_OSMID: "hello",
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                    },
                ),
                # edge with non-integer osmids in a list
                (
                    "a",
                    "b",
                    1,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, "a"],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                    },
                ),
                # edge with non-numeric lengths
                (
                    "a",
                    "b",
                    2,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: "three",
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                    },
                ),
                # edge with non-LineString geometries
                (
                    "a",
                    "b",
                    3,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                        gis_osm.KEY_OSMNX_GEOMETRY: Point((1, 0)),
                    },
                ),
                # edge with non-boolean reversed attr in a list
                (
                    "a",
                    "b",
                    4,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                        gis_osm.KEY_OSMNX_REVERSED: ["True", False],
                    },
                ),
                # edge with non-boolean oneway
                (
                    "a",
                    "b",
                    5,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                        gis_osm.KEY_OSMNX_ONEWAY: "True",
                    },
                ),
                # edge with non-boolean oneway in a list
                (
                    "a",
                    "b",
                    6,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_LENGTH: 3,
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                        gis_osm.KEY_OSMNX_ONEWAY: [False, "True"],
                    },
                ),
                # edge without all the essential attributes
                (
                    "a",
                    "b",
                    7,
                    {
                        gis_osm.KEY_OSMNX_OSMID: [1, 2],
                        gis_osm.KEY_OSMNX_REVERSED: [True, False],
                        gis_osm.KEY_OSMNX_ONEWAY: False,
                    },
                ),
            ]
        )

        # edge with non-integer osmids
        edge_key = ("a", "b", 0)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 1)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 2)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 3)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 4)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 5)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 6)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        edge_key = ("a", "b", 7)
        assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)

        # error_raised = False
        # try:
        #     edge_key = ('a', 'b', 'c')
        #     assert not gis_iden.is_edge_osmnx_compliant(network, edge_key)
        # except ValueError:
        #     error_raised = True
        # assert error_raised

    # *************************************************************************
    # *************************************************************************

    def test_reversed_edges_osmnx(self):
        # get the network
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter=('["highway"~"residential|tertiary|unclassified|service"]'),
            truncate_by_edge=True,
        )

        # create edge to trigger the negative case with a different length
        # (317812803, 317812802, 2)
        edge_k = network.add_edge(
            317812803,
            317812802,
            **{
                "osmid": 28913471,
                "name": "Tingstedet",
                "highway": "residential",
                "maxspeed": "50",
                "oneway": False,
                "reversed": True,
                "length": 27.601 + 1,
            }
        )
        assert edge_k == 2
        # create edge tp trigger the negative case with a different geometry
        # (317812802, 317812803, 2)
        edge_k = network.add_edge(
            317812802,
            317812803,
            **{
                "osmid": 28913483,
                "name": "Tingstedet",
                "highway": "residential",
                "maxspeed": "50",
                "oneway": False,
                "reversed": False,
                "length": 99.155,
                "geometry": LineString([(1, 3), (2, 4)]),
            }
        )
        assert edge_k == 2

        # find edges that have matching edges in the reverse direction
        for edge_key in network.edges(keys=True):
            edge_dict = network.get_edge_data(*edge_key)
            for _attr in gis_osm.KEYS_OSMNX_EDGES_ESSENTIAL:
                assert _attr in edge_dict.keys()
            # for each edge, find if there is one in the opposite direction
            if network.has_edge(u=edge_key[1], v=edge_key[0]):
                # there is an edge in the opposite sense
                for other_edge_key in gis_iden.get_edges_from_a_to_b(
                    network=network, node_start=edge_key[1], node_end=edge_key[0]
                ):
                    # check if the edges are the same but in reverse
                    if gis_iden.edges_are_in_reverse(
                        network, edge_a=edge_key, edge_b=other_edge_key
                    ):
                        # the edges are the same but in reverse:
                        # - all attributes have to be the same or lists with
                        # the same content, as in a set, except for
                        # the geometry and reversed attributes
                        fw_dict = network.get_edge_data(*edge_key)
                        rv_dict = network.get_edge_data(*other_edge_key)

                        # should have the same attributes
                        assert set(fw_dict.keys()) == set(rv_dict.keys())
                        # the attributes must be identical except if they are
                        # lists or if they geometries or reversed arguments
                        for attr_key, attr_value in fw_dict.items():
                            if type(attr_value) == list:
                                # the dict values are lists, their equivalent
                                # sets must match (they cannot be geometries)
                                assert set(attr_value) == set(rv_dict[attr_key])
                            elif attr_key == gis_osm.KEY_OSMNX_GEOMETRY:
                                # the dict values are geometries, they must be
                                # the reverse of one another
                                assert tuple(attr_value.coords) == tuple(
                                    rv_dict[attr_key].reverse().coords
                                )
                            elif attr_key == gis_osm.KEY_OSMNX_REVERSED:
                                # the dict values are the reversed attributes,
                                # they must be opposites (since type(..)!=list)
                                assert not attr_value == rv_dict[attr_key]
                            elif attr_key == gis_osm.KEY_OSMNX_LENGTH:
                                # the dict values are the reversed attributes,
                                # they must be opposites (since type(..)!=list)
                                assert isclose(
                                    attr_value, rv_dict[attr_key], abs_tol=1e-3
                                )
                            else:
                                # the dict values are not lists, nor geometries
                                # nor reversed attributes: they must match
                                assert attr_value == rv_dict[attr_key]

                    else:  # the edges are not the same in reverse
                        # at least one of their attributes must be different or
                        # incompatible
                        fw_dict = network.get_edge_data(*edge_key)
                        rv_dict = network.get_edge_data(*other_edge_key)
                        error_raised = False
                        try:
                            # should have the same attributes
                            assert set(fw_dict.keys()) == set(rv_dict.keys())
                            # the attributes must be identical except if they are
                            # lists or if they geometries or reversed arguments
                            for attr_key, attr_value in fw_dict.items():
                                if type(attr_value) == list:
                                    # the dict values are lists, their equivalent
                                    # sets must match (they cannot be geometries)
                                    assert set(attr_value) == set(rv_dict[attr_key])
                                elif attr_key == gis_osm.KEY_OSMNX_GEOMETRY:
                                    # the dict values are geometries, they must be
                                    # the reverse of one another
                                    assert tuple(attr_value.coords) == tuple(
                                        rv_dict[attr_key].reverse().coords
                                    )
                                elif attr_key == gis_osm.KEY_OSMNX_REVERSED:
                                    # the dict values are the reversed attributes,
                                    # they must be opposites (since type(..)!=list)
                                    assert not attr_value == rv_dict[attr_key]
                                elif attr_key == gis_osm.KEY_OSMNX_LENGTH:
                                    # the dict values are the reversed attributes,
                                    # they must be opposites (since type(..)!=list)
                                    assert isclose(
                                        attr_value, rv_dict[attr_key], abs_tol=1e-3
                                    )
                                else:
                                    # the dict values are not lists, nor geometries
                                    # nor reversed attributes: they must match
                                    assert attr_value == rv_dict[attr_key]
                        except Exception:
                            error_raised = True
                        assert error_raised

    # *************************************************************************
    # *************************************************************************

    def test_edge_geometry_consistency(self):
        network = nx.MultiDiGraph()

        node_key0 = 0
        node_key0_dict = {gis_osm.KEY_OSMNX_X: 55, gis_osm.KEY_OSMNX_Y: 25}
        node_key1 = 1
        node_key1_dict = {gis_osm.KEY_OSMNX_X: 55.001, gis_osm.KEY_OSMNX_Y: 25.001}

        network.add_node(node_key0, **node_key0_dict)
        network.add_node(node_key1, **node_key1_dict)
        # create a line between node 0 and node 1
        geo_line = LineString(
            [
                (
                    node_key0_dict[gis_osm.KEY_OSMNX_X],
                    node_key0_dict[gis_osm.KEY_OSMNX_Y],
                ),
                (
                    node_key1_dict[gis_osm.KEY_OSMNX_X],
                    node_key1_dict[gis_osm.KEY_OSMNX_Y],
                ),
            ]
        )
        # the same line, reversed
        geo_line_reversed = geo_line.reverse()

        k_no_geo = network.add_edge(0, 1, **{gis_osm.KEY_OSMNX_LENGTH: 3})

        k_normal = network.add_edge(
            0, 1, **{gis_osm.KEY_OSMNX_LENGTH: 3, gis_osm.KEY_OSMNX_GEOMETRY: geo_line}
        )

        k_reversed = network.add_edge(
            0,
            1,
            **{
                gis_osm.KEY_OSMNX_LENGTH: 3,
                gis_osm.KEY_OSMNX_GEOMETRY: geo_line_reversed,
            }
        )
        # edge without geometry should be consistent
        edge_key = (node_key0, node_key1, k_no_geo)
        assert gis_iden.is_edge_consistent_with_geometry(network, edge_key)
        # edge with normal geometry should be consistent
        edge_key = (node_key0, node_key1, k_normal)
        assert gis_iden.is_edge_consistent_with_geometry(network, edge_key)
        # edge with reversed geometry should not be consistent
        edge_key = (node_key0, node_key1, k_reversed)
        assert not gis_iden.is_edge_consistent_with_geometry(network, edge_key)

        # trigger no edge found error
        error_raised = False
        try:
            edge_key = (node_key0, node_key1, k_no_geo - 1)
            gis_iden.is_edge_consistent_with_geometry(network, edge_key)
        except ValueError:
            error_raised = True
        assert error_raised

    # *************************************************************************
    # *************************************************************************

    def test_edge_geometry_consistency_osmnx(self):
        # get the network
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter=('["highway"~"residential|tertiary|unclassified|service"]'),
            truncate_by_edge=True,
        )

        for edge_key in network.edges(keys=True):
            assert gis_iden.is_edge_consistent_with_geometry(network, edge_key)

        edge_key = (115831, 1104936963, 0)
        assert gis_iden.is_edge_consistent_with_geometry(network, edge_key)
        edge_key = (1104936963, 115831, 0)
        assert gis_iden.is_edge_consistent_with_geometry(network, edge_key)

    # *************************************************************************
    # *************************************************************************

    def test_valid_node_paths(self):
        # *********************************************************************
        # *********************************************************************

        # create network

        network = nx.MultiDiGraph()

        # define and add edges

        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]

        network.add_edges_from(list_edges)

        # *********************************************************************
        # *********************************************************************

        valid_node_paths = [[0, 1, 2, 3], [6, 5, 4, 3], [6, 7, 8, 9], [10, 8, 9]]

        invalid_node_paths = [
            [],  # empty list
            [1],  # single node path
            [-1, 0, 1, 2, 3],  # nodes do not belong to the network
            [3, 2, 1, 0],  # path 1 reversed
            [3, 4, 5, 6],  # path 2 reversed
            [9, 8, 7, 6],  # path 3 reversed
            [6, 7, 8, 10],  # node 10 is connected to node 8 but not the other way arou.
        ]

        # make sure valid node paths are valid
        for path in valid_node_paths:
            assert gis_iden.is_node_path(network, path)
        # make sure invalid node paths are invalid
        for path in invalid_node_paths:
            assert not gis_iden.is_node_path(network, path)

        # *********************************************************************
        # *********************************************************************

        consider_reversed_edges = True

        valid_node_paths = [
            [0, 1, 2, 3],
            [6, 5, 4, 3],
            [6, 7, 8, 9],
            [10, 8, 9],
            [3, 2, 1, 0],  # path 1 reversed
            [3, 4, 5, 6],  # path 2 reversed
            [9, 8, 7, 6],  # path 3 reversed
            [6, 7, 8, 10],  # node 10 is connected to node 8 but not the other way arou.
        ]

        invalid_node_paths = [
            [],  # empty list
            [1],  # single node path
            [-1, 0, 1, 2, 3],  # nodes do not belong to the network
        ]

        # make sure valid node paths are valid
        for path in valid_node_paths:
            assert gis_iden.is_node_path(network, path, consider_reversed_edges)
        # make sure invalid node paths are invalid
        for path in invalid_node_paths:
            assert not gis_iden.is_node_path(network, path, consider_reversed_edges)

    # *************************************************************************
    # *************************************************************************

    def test_valid_edge_paths(self):
        # *********************************************************************
        # *********************************************************************

        # create network

        network = nx.MultiDiGraph()

        # define and add edges

        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]

        network.add_edges_from(list_edges)

        # *********************************************************************
        # *********************************************************************

        valid_edge_paths = [
            [(0, 1), (1, 2), (2, 3)],
            [(6, 5), (5, 4), (4, 3)],
            [(6, 7), (7, 8), (8, 9)],
            [(10, 8), (8, 9)],
            [(0, 1, 0), (1, 2, 0), (2, 3, 0)],  # with keys
            [(6, 5, 0), (5, 4, 0), (4, 3, 0)],  # with keys
            [(6, 7, 0), (7, 8, 0), (8, 9, 0)],  # with keys
            [(10, 8, 0), (8, 9, 0)],  # with keys
        ]

        invalid_edge_paths = [
            [(0, 1, 1), (1, 2, 0), (2, 3, 3)],  # with incorrect keys
            [(6, 5, 2), (5, 4, 0), (4, 3, 2)],  # with incorrect keys
            [(6, 7, 3), (7, 8, 0), (8, 9, 1)],  # with incorrect keys
            [(10, 8, 0), (8, 9, 2)],  # with incorrect keys
            [],  # empty list
            [(-1, 0), (0, 1), (1, 2), (2, 3)],  # nodes do not belong to the network
            [(3, 2), (2, 1), (1, 0)],  # path 1 reversed
            [(3, 4), (4, 5), (5, 6)],  # path 2 reversed
            [(9, 8), (8, 7), (7, 6)],  # path 3 reversed
            [
                (6, 7),
                (7, 8),
                (8, 10),
            ],  # node 10 is connected to node 8 but not the other way
            [(), (7, 8), (8)],  # unknown format
            # [(6,5,0),(5,4),(4,3,0)], # inconsistent edge key format
            [(6, 5), (9, 9), (5, 4), (4, 3)],  # no sequence due to (9,9)
        ]

        for path in valid_edge_paths:
            assert gis_iden.is_edge_path(network, path)

        # make sure invalid node paths are invalid

        for path in invalid_edge_paths:
            assert not gis_iden.is_edge_path(network, path)

        # *********************************************************************
        # *********************************************************************

        # inconsistent edge key format

        # allowed

        assert gis_iden.is_edge_path(
            network, path=[(6, 5, 0), (5, 4), (4, 3, 0)], allow_multiple_formats=True
        )

        # not allowed

        error_raised = False
        try:
            # inconsistent edge key format
            gis_iden.is_edge_path(
                network,
                path=[(6, 5, 0), (5, 4), (4, 3, 0)],
                allow_multiple_formats=False,
            )
        except ValueError:
            error_raised = True
        assert error_raised

        # *********************************************************************
        # *********************************************************************

        ignore_edge_direction = True

        valid_edge_paths = [
            [(0, 1), (1, 2), (2, 3)],
            [(6, 5), (5, 4), (4, 3)],
            [(6, 7), (7, 8), (8, 9)],
            [(10, 8), (8, 9)],
            [(0, 1, 0), (1, 2, 0), (2, 3, 0)],  # with keys
            [(6, 5, 0), (5, 4, 0), (4, 3, 0)],  # with keys
            [(6, 7, 0), (7, 8, 0), (8, 9, 0)],  # with keys
            [(10, 8, 0), (8, 9, 0)],  # with keys
            [(2, 3), (1, 2), (0, 1)],  # path 1 reversed
            [(4, 3), (5, 4), (6, 5)],  # path 2 reversed
            [(8, 9), (7, 8), (6, 7)],  # path 3 reversed
            [
                (6, 7),
                (7, 8),
                (10, 8),
            ],  # node 10 is connected to node 8 but not the other way
        ]

        invalid_edge_paths = [
            [(0, 1, 1), (1, 2, 0), (2, 3, 3)],  # with incorrect keys
            [(6, 5, 2), (5, 4, 0), (4, 3, 2)],  # with incorrect keys
            [(6, 7, 3), (7, 8, 0), (8, 9, 1)],  # with incorrect keys
            [(10, 8, 0), (8, 9, 2)],  # with incorrect keys
            [],  # empty list
            [
                (-1, 0),
                (0, 1),
                (1, 2),
                (2, 3),
            ],  # nodes do not belong to the network arou.
            # [(10,8,0),(8,9)],           # different key formats
            # [(6,5),(5,4,0),(4,3)],      # different key formats
            [(), (5, 4, 0), (4, 3)],  # unknown key formats
            [(6, 5), (9, 9), (5, 4, 0), (4, 3)],  # no sequence due to (9,9)
        ]

        for path in valid_edge_paths:
            assert gis_iden.is_edge_path(
                network, path, ignore_edge_direction=ignore_edge_direction
            )

        # make sure invalid node paths are invalid

        for path in invalid_edge_paths:
            assert not gis_iden.is_edge_path(
                network, path, ignore_edge_direction=ignore_edge_direction
            )

        # *********************************************************************
        # *********************************************************************

        # inconsistent edge key format

        # allowed

        assert gis_iden.is_edge_path(
            network, path=[(10, 8, 0), (8, 9)], allow_multiple_formats=True
        )

        # not allowed

        error_raised = False
        try:
            # inconsistent edge key format
            gis_iden.is_edge_path(
                network, path=[(10, 8, 0), (8, 9)], allow_multiple_formats=False
            )
        except ValueError:
            error_raised = True
        assert error_raised

        # inconsistent edge key format

        # allowed

        assert gis_iden.is_edge_path(
            network, path=[(6, 5), (5, 4, 0), (4, 3)], allow_multiple_formats=True
        )

        # not allowed

        error_raised = False
        try:
            # inconsistent edge key format
            gis_iden.is_edge_path(
                network, path=[(6, 5), (5, 4, 0), (4, 3)], allow_multiple_formats=False
            )
        except ValueError:
            error_raised = True
        assert error_raised

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_straight_paths_reversed_edges_self_loops(self):
        # create network

        network = nx.MultiDiGraph()

        # define and add edges
        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]
        network.add_edges_from(list_edges)

        # reversed edges are okay, self loops too

        ignore_self_loops = True
        consider_reversed_edges = True

        # valid node paths
        valid_straight_node_paths = [
            [0, 1, 2],
            [1, 2, 3],
            [0, 1, 2, 3],
            [5, 4, 3],
            [6, 5, 4],
            [6, 5, 4, 3],
            [0, 1],  # just two nodes
            [0, 1, 2, 3, 4],  # node 4 is connected using an edge in the opposite dir.
            [6, 5, 4, 3, 2],  # node 2 is connected using an edge in the opposite dir.
            [3, 4, 5, 6],  # the path is reversed
        ]

        # invalid node paths
        invalid_straight_node_paths = [
            [6, 7, 8, 9],  # node 8 has three neighbours (7, 9 and 10)
            [0, 4, 1],  # node 4 is not a neighbour of nodes 0 and 1
            [11, 3, 4, 5, 6],  # there is no node 11
        ]

        # make sure valid node paths are valid
        for path in valid_straight_node_paths:
            assert gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # make sure invalid node paths are invalid
        for path in invalid_straight_node_paths:
            assert not gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_straight_paths_no_reversed_edges_self_loops(self):
        # create network

        network = nx.MultiDiGraph()

        # define and add edges
        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]
        network.add_edges_from(list_edges)

        # no reversed edges, self loops are okay

        ignore_self_loops = True
        consider_reversed_edges = False

        # valid node paths

        valid_straight_node_paths = [
            [0, 1, 2],
            [1, 2, 3],
            [0, 1, 2, 3],
            [5, 4, 3],
            [6, 5, 4],
            [6, 5, 4, 3],
            [0, 1],  # just two nodes
        ]

        # invalid node paths

        invalid_straight_node_paths = [
            [2, 1, 0],  # reversed path
            [3, 2, 1],  # reversed path
            [3, 2, 1, 0],  # reversed path
            [3, 4, 5],  # reversed path
            [4, 5, 6],  # reversed path
            [3, 4, 5, 6],  # reversed path
            [0, 1, 2, 3, 4, 5, 6, 7, 8],  # path with reversed elements
            [8, 7, 6, 5, 4, 3, 2, 1, 0],  # path with reversed element
            [0, 1, 2, 3, 4],  # path with reversed elements
            [6, 5, 4, 3, 2],  # the last edge is reversed
            [6, 7, 8, 9],  # node 8 has three neighbours (7, 9 and 10)
            [0, 4, 1],  # node 4 is not a neighbour of nodes 0 and 1
            [11, 3, 4, 5, 6],  # there is no node 11
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],  # node 8 has three neighbours (7, 9 and 10)
            [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],  # node 8 has three neighbours (7, 9 and 10)
        ]

        # make sure valid node paths are valid
        for path in valid_straight_node_paths:
            assert gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # make sure invalid node paths are invalid
        for path in invalid_straight_node_paths:
            assert not gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_straight_paths_no_reversed_edges_no_self_loops(self):
        # create network

        network = nx.MultiDiGraph()

        # define and add edges
        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]
        network.add_edges_from(list_edges)

        # no reversed edges, self loops are not okay

        ignore_self_loops = False
        consider_reversed_edges = False

        # (0,1),(1,2),(2,3), # path 1
        # (4,3),(5,4),(6,5), # path 2
        # (6,7),(7,8),(8,9), # path 3
        # (2,2), # self loop on path 1
        # (4,4), # self loop on path 2
        # (9,9), # self loop on path 3

        # valid node paths

        valid_straight_node_paths = [[6, 5, 4], [6, 7, 8], [0, 1, 2]]

        # invalid node paths

        invalid_straight_node_paths = [
            [1, 2, 3],
            [0, 1, 2, 3],
            [5, 4, 3],
            [6, 5, 4, 3],
            [4, 5, 6],
            [8, 7, 6],
            [2, 1, 0],
        ]

        # make sure valid node paths are valid
        for path in valid_straight_node_paths:
            assert gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # make sure invalid node paths are invalid
        for path in invalid_straight_node_paths:
            assert not gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_straight_paths_reversed_edges_no_self_loops(self):
        # create network

        network = nx.MultiDiGraph()

        # define and add edges
        list_edges = [
            (0, 1),
            (1, 2),
            (2, 3),  # path 1
            (4, 3),
            (5, 4),
            (6, 5),  # path 2
            (6, 7),
            (7, 8),
            (8, 9),  # path 3
            (2, 2),  # self loop on path 1
            (4, 4),  # self loop on path 2
            (9, 9),  # self loop on path 3
            (10, 8),  # extra lone neighbour for node 8 on
        ]
        network.add_edges_from(list_edges)

        # reversed edges are okay, self loops are not

        ignore_self_loops = False
        consider_reversed_edges = True

        # valid node paths

        valid_straight_node_paths = [
            [4, 5, 6],
            [6, 5, 4],
            [6, 7, 8],
            [8, 7, 6],
            [0, 1, 2],
            [2, 1, 0],
        ]

        # invalid node paths

        invalid_straight_node_paths = [[1, 2, 3], [0, 1, 2, 3], [5, 4, 3], [6, 5, 4, 3]]

        # make sure valid node paths are valid
        for path in valid_straight_node_paths:
            assert gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # make sure invalid node paths are invalid
        for path in invalid_straight_node_paths:
            assert not gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_straight_path_parallel_antiparallel_edges(self):
        # create network

        network = nx.MultiDiGraph()

        # define and add edges
        list_edges = [
            (0, 1, 0),
            (1, 2, 0),
            (2, 3, 0),  # path 1
            (4, 5, 0),
            (5, 6, 0),
            (6, 7, 0),  # path 2
            (8, 9, 0),
            (9, 10, 0),
            (10, 11, 0),  # path 3
            # extra edges
            (0, 1, 0),  # path 1
            (5, 4, 0),  # path 2
            (8, 9, 0),
            (11, 10, 0),  # path 3
        ]
        network.add_edges_from(list_edges)

        # reversed edges are okay, self loops are not

        ignore_self_loops = True
        consider_reversed_edges = True

        # valid node paths

        valid_straight_node_paths = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]

        # make sure valid node paths are valid
        for path in valid_straight_node_paths:
            assert gis_iden.is_path_straight(
                network,
                path,
                consider_reversed_edges=consider_reversed_edges,
                ignore_self_loops=ignore_self_loops,
            )

        # *********************************************************************
        # *********************************************************************

    # *************************************************************************
    # *************************************************************************

    def test_nearest_node_keys(self):
        # *********************************************************************

        # create a network
        network = nx.MultiDiGraph()
        network.graph["crs"] = "EPSG:4326"
        # network.graph['crs'] = 'init'
        # add edges and nodes
        number_edges = random.randint(3, 10)
        edge_keys = [
            (random.randint(0, number_edges), random.randint(0, number_edges))
            for edge_index in range(number_edges)
        ]
        network.add_edges_from(edge_keys)
        # add attributes to the nodes used in the edges
        for node_key in network.nodes():
            _xy = (random.random(), random.random())
            network.add_node(node_key, x=_xy[0], y=_xy[0])
        # add new (unconnected) nodes
        number_new_nodes = random.randint(3, 5)
        unconnected_node_keys = []
        for node_index in range(number_new_nodes):
            new_node_key = uuid.uuid4()
            _xy = (random.random(), random.random())
            network.add_node(new_node_key, x=_xy[0], y=_xy[0])
            unconnected_node_keys.append(new_node_key)

        # *********************************************************************

        # find the nearest nodes using the osmnx method
        nearest_node_keys = gis_iden.nearest_nodes(
            network,
            [network.nodes[node_key]["x"] for node_key in unconnected_node_keys],
            [network.nodes[node_key]["y"] for node_key in unconnected_node_keys],
        )

        # assert that the test is meaningful
        assert len(nearest_node_keys) != 0
        assert len(nearest_node_keys) == len(unconnected_node_keys)

        # assert that the nodes are the same
        for i, node_key in enumerate(unconnected_node_keys):
            assert node_key == nearest_node_keys[i]

        # *********************************************************************

        # find the nodes nearest to select nodes excluding themselves
        nearest_node_keys = gis_iden.nearest_nodes_other_than_themselves(
            network, unconnected_node_keys
        )

        # assert that the test is meaningful
        assert len(nearest_node_keys) != 0
        assert len(nearest_node_keys) == len(unconnected_node_keys)
        all_node_keys = list(network.nodes())
        list_all_geos = []
        for node_key in all_node_keys:
            list_all_geos.append(
                Point((network.nodes[node_key]["x"], network.nodes[node_key]["y"]))
            )
        all_node_geos = {
            node_key: list_all_geos[i] for i, node_key in enumerate(all_node_keys)
        }
        # for each node
        for i, node_key in enumerate(unconnected_node_keys):
            # assert that they are not the same
            assert node_key != nearest_node_keys[i]
            # verify that the distance between is the lowest among all
            unconnected_node_geo = all_node_geos[node_key]
            all_distances = [
                unconnected_node_geo.distance(all_node_geos[other_node_key])
                for other_node_key in all_node_keys
                if other_node_key != node_key
            ]
            actual_distance = unconnected_node_geo.distance(
                all_node_geos[nearest_node_keys[i]]
            )
            assert isclose(min(all_distances), actual_distance, abs_tol=1)

    # *************************************************************************
    # *************************************************************************

    def test_finding_roundabouts(self):
        # network should be a OSM-nx formatted graph
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter='["highway"~"residential|tertiary|unclassified|service"]',
            truncate_by_edge=True,
        )
        # find all roundabouts
        roundabouts = gis_iden.find_roundabouts(network)
        # confirm they are roundabouts
        for roundabout in roundabouts:
            assert gis_iden.is_roundabout(network, roundabout)

        # find roundabouts with constraints
        roundabouts = gis_iden.find_roundabouts(
            network,
            maximum_perimeter=200,
            minimum_perimeter=25,
            maximum_number_nodes=6,
            minimum_number_nodes=4,
        )
        # confirm they are roundabouts
        for roundabout in roundabouts:
            assert gis_iden.is_roundabout(network, roundabout)

    # *************************************************************************
    # *************************************************************************

    def test_finding_reversed_edges(self):
        # network should be a OSM-nx formatted graph
        network = ox.graph_from_point(
            (55.71654, 9.11728),
            network_type="drive",
            custom_filter=('["highway"~"residential|tertiary|unclassified|service"]'),
            truncate_by_edge=True,
        )
        # find edges in reverse
        edges_in_rev = gis_iden.find_edges_in_reverse(network)
        # confirm
        for edge_key, reversed_edge_keys in edges_in_rev.items():
            for _edge_key in reversed_edge_keys:
                assert gis_iden.edges_are_in_reverse(network, edge_key, _edge_key)

    # *************************************************************************
    # *************************************************************************


# *****************************************************************************
# *****************************************************************************