Ejemplo n.º 1
0
    def split(self, point, node_id, way1_id, way2_id):
        """Split this street at ``point``."""
        distance = self.geom.project(point)
        coords = self.geom.coords
        shared_coords = list(point.coords)

        coords1 = [coords[0]]
        coords2 = []

        for c in coords[1:-1]:
            p = Point(c)
            p_distance = self.geom.project(p)
            if p_distance < distance:
                coords1.append(c)
            elif p_distance > distance:
                coords2.append(c)

        coords2.append(coords[-1])

        coords1 = coords1 + shared_coords
        coords2 = shared_coords + coords2

        line1 = LineString(coords1)
        line2 = LineString(coords2)

        shared_node = Intersection(id=node_id, geom=point)

        # Start to shared node
        way1 = self.clone(
            id=way1_id,
            end_node_id=node_id,
            end_node=shared_node,
            geom=line1,
        )

        # Shared node to end
        way2 = self.clone(
            id=way2_id,
            start_node_id=node_id,
            start_node=shared_node,
            geom=line2,
        )

        shared_node.ways = [way1, way2]
        return shared_node, way1, way2
Ejemplo n.º 2
0
def trim_line(line, point1, point2):
    distance1 = line.project(point1)
    distance2 = line.project(point2)
    if distance1 > distance2:
        point1, point2 = point2, point1
        distance1, distance2 = distance2, distance1
    coords = [point1]
    for c in line.coords:
        p = Point(c)
        p_distance = line.project(p)
        if distance1 <= p_distance <= distance2:
            coords.append(c)
    coords.append(point2)
    return LineString(coords)
Ejemplo n.º 3
0
def split_line(line, point):
    """Split linestring at point."""
    distance = line.project(point)
    coords = line.coords
    shared_coords = list(point.coords)

    coords1 = [coords[0]]
    coords2 = []

    for c in coords[1:-1]:
        p = Point(c)
        p_distance = line.project(p)
        if p_distance < distance:
            coords1.append(c)
        elif p_distance > distance:
            coords2.append(c)

    coords2.append(coords[-1])

    coords1 = coords1 + shared_coords
    coords2 = shared_coords + coords2

    return LineString(coords1), LineString(coords2)
Ejemplo n.º 4
0
 def query(self, q, points=None):
     waypoints = self.get_waypoints(q, points)
     starts = waypoints[:-1]
     ends = waypoints[1:]
     routes = []
     for start, end in zip(starts, ends):
         if start.geom == end.geom:
             coords = start.geom.coords[0]
             route = Route(start, end, [], LineString([coords, coords]),
                           self.distance_dict(0))
         else:
             path = self.find_path(start, end)
             directions, linestring, distance = self.make_directions(*path)
             route = Route(start, end, directions, linestring, distance)
         routes.append(route)
     return routes[0] if len(routes) == 1 else routes
Ejemplo n.º 5
0
    def process_ways(self):
        """Process ways"""
        get_tag = self.get_tag
        normalize_street_name = self.normalize_street_name

        bool_converter = get_converter('bool')

        # TODO: Change oneway from bool to (-1, 0, 1)?
        #        0 => not a one way
        #        1 => one way in node order
        #       -1 => one way in reverse node order
        def oneway_converter(v):
            try:
                return bool_converter(v)
            except ValueError:
                return True

        way_id = 0
        rows = []
        empty_tags = []

        def insert():
            self.engine.execute(STREET_TABLE.insert(), rows)
            rows.clear()

        self.engine.execute(STREET_TABLE.delete())

        for el in self.iter_ways():
            osm_id = el['id']
            node_ids = el['nodes']
            tags = el.get('tags', empty_tags)

            name = get_tag(tags, 'name', normalize_street_name)
            highway = get_tag(tags, 'highway')
            bicycle = get_tag(tags, 'bicycle')
            cycleway = get_tag(tags, 'cycleway')
            foot = get_tag(tags, 'foot')
            sidewalk = get_tag(tags, 'sidewalk')

            oneway = get_tag(tags, 'oneway', oneway_converter, False)
            oneway_bicycle = get_tag(tags, 'oneway:bicycle', oneway_converter)
            if oneway_bicycle is None:
                oneway_bicycle = get_tag(tags, 'bicycle:oneway', oneway_converter)
                if oneway_bicycle is None:
                    oneway_bicycle = oneway

            node_q = NODE_TABLE.select()
            node_q = node_q.where(NODE_TABLE.c.id.in_(node_ids))
            node_map = {n.id: n for n in self.engine.execute(node_q)}
            nodes = [node_map[i] for i in node_ids]

            way = []
            ways = []
            last_i = len(nodes) - 1
            for i, node in enumerate(nodes):
                way.append(node)
                if len(way) > 1 and node.is_intersection:
                    ways.append(way)
                    if i < last_i:
                        way = [node]

            for i, way in enumerate(ways):
                way_id += 1
                start_node_id = way[0].id
                end_node_id = way[-1].id
                geom = LineString((n.geom.coords[0] for n in way))
                rows.append({
                    'id': way_id,
                    'osm_id': osm_id,
                    'osm_seq': i,
                    'geom': geom,
                    'start_node_id': start_node_id,
                    'end_node_id': end_node_id,
                    'name': name,
                    'highway': highway,
                    'bicycle': bicycle,
                    'cycleway': cycleway,
                    'foot': foot,
                    'sidewalk': sidewalk,
                    'oneway': oneway,
                    'oneway_bicycle': oneway_bicycle,
                })

            if len(rows) > 500:
                insert()

        if rows:
            insert()
Ejemplo n.º 6
0
    def make_directions(self, node_ids, edge_attrs, split_edges):
        """Process the shortest path into a nice list of directions.

        ``node_ids``
            The IDs of the nodes on the route

        ``edges_attrs``
            The attributes of the edges on the route as a list or tuple.
            The first item in each list must be the edge ID.

        ``split_edges``
            Temporary edges formed by splitting an existing edge when the
            start and/or end of a route is within an edge (e.g., for an
            address like "123 Main St")

        return
            * A list of directions. Each direction has the following form::

              {
                  'turn': 'left',
                  'name': 'SE Stark St',
                  'display_name': 'SE Stark St',
                  'type': 'residential',
                  'toward': 'SE 45th Ave',
                  'distance': {
                       'feet': 264.0,
                       'miles': 0.05,
                       'meters': 80.0,
                       'kilometers': 0.08,
                   },
                   'jogs': [{'turn': 'left', 'name': 'NE 7th Ave'}, ...]
               }

            * A linestring, which is a list of x, y coords:

              [(x, y), ...]

            * A `dict` of total distances in units of feet, miles, kilometers:

              {
                  'feet': 5487.0,
                  'miles': 1.04,
                  'meters': 1110.0,
                  'kilometers': 1.11,
              }

        """
        directions = []

        # Gather edges into a list.

        edges = []
        edge_ids = [attrs[0] for attrs in edge_attrs]

        if edge_ids and edge_ids[0] < 0:
            edges.append(split_edges[edge_ids[0]])

        filter_ids = [i for i in edge_ids if i > 0]
        if filter_ids:
            q = self.session.query(Street).filter(Street.id.in_(filter_ids))
            q = q.options(joinedload(Street.start_node))
            q = q.options(joinedload(Street.end_node))
            edge_map = {edge.id: edge for edge in q}
            edges.extend(edge_map[i] for i in filter_ids)

        if len(edge_ids) > 1 and edge_ids[-1] < 0:
            edges.append(split_edges[edge_ids[-1]])

        # Group edges together by street name into stretches.

        start_edges = []
        prev_name = None
        prev_end_bearing = None
        linestring_points = []
        loop_data = zip(node_ids[1:], edges, [None] + edges[:-1], edges[1:] + [None])

        for node_id, edge, prev_edge, next_edge in loop_data:
            length = edge.meters
            name = edge.name

            points = edge.geom.coords
            if edge.start_node.id == node_id:
                points = points[::-1]

            start_bearing = self.get_bearing(*points[:2])
            end_bearing = self.get_bearing(*points[-2:])

            if next_edge is None:
                # Reached last edge
                next_name = None
                linestring_points.extend(points)
            else:
                next_name = next_edge.name
                linestring_points.extend(points[:-1])

            if name and name == prev_name:
                start_edge = start_edges[-1]
                start_edge['edges'].append(edge)
                start_edge['end_bearing'] = end_bearing
            elif (prev_name and
                  length < 30 and  # 30 meters TODO: Magic number
                  name != prev_name and
                  prev_name == next_name):
                start_edge = start_edges[-1]
                start_edge['jogs'].append({
                    'edge': edge,
                    'start_point': points[0],
                    'start_bearing': start_bearing,
                    'end_bearing': end_bearing,
                    'prev_end_bearing': prev_end_bearing,
                })
            else:
                # Start of a new stretch
                start_edges.append({
                    'edge': edge,
                    'edges': [edge],
                    'jogs': [],
                    'toward_node_id': node_id,
                    'start_point': points[0],
                    'start_bearing': start_bearing,
                    'end_bearing': end_bearing,
                })
                prev_name = name

            prev_end_bearing = end_bearing

        # Create directions from stretches.

        for prev_start_edge, start_edge in zip([None] + start_edges[:-1], start_edges):
            edge = start_edge['edge']
            length = sum(edge.meters for edge in start_edge['edges'])
            jogs = start_edge['jogs']
            start_bearing = start_edge['start_bearing']
            toward_node_id = start_edge['toward_node_id']

            if prev_start_edge is None:
                # First
                turn = self.get_direction_from_bearing(start_bearing)
            else:
                prev_end_bearing = prev_start_edge['end_bearing']
                turn = self.calculate_way_to_turn(prev_end_bearing, start_bearing)

            processed_jogs = []
            for jog in jogs:
                jog_edge = jog['edge']
                processed_jogs.append({
                    'turn': self.calculate_way_to_turn(
                        jog['prev_end_bearing'], jog['start_bearing']),
                    'name': jog_edge.name,
                    'display_name': jog_edge.display_name,
                })

            direction = {
                'turn': turn,
                'name': edge.name,
                'display_name': edge.display_name,
                'type': edge.highway,
                'toward': toward_node_id,
                'jogs': processed_jogs,
                'distance': self.distance_dict(length),
                'start_point': start_edge['start_point'],
            }

            directions.append(direction)

        # Get the toward street at the start of each stretch found in
        # the loop just above. This is deferred to here so that we can
        # fetch all the toward nodes up front with their associated
        # edges in a single query. This is much faster than processing
        # each node individually inside the loop--that causes up to 2*N
        # additional queries being issued to the database (fetching of
        # the inbound and outbound edges for the node).

        filter_ids = [direction['toward'] for direction in directions]
        if filter_ids:
            q = self.session.query(Intersection)
            q = q.filter(Intersection.id.in_(filter_ids))
            q = q.options(joinedload(Intersection.streets))
            node_map = {node.id: node for node in q}
        else:
            node_map = {}

        for direction in directions:
            name = direction['name']
            toward_node_id = direction['toward']
            if toward_node_id < 0:
                # This is a special case where the destination is within
                # an edge (i.e., it's a street address) AND there are no
                # intersections between the last turn and the
                # (synthetic) destination node. In this case, since the
                # destination node doesn't have any intersecting edges,
                # a toward street can't be determined. Also, the
                # destination node won't have been fetched in the query
                # above because it doesn't really exist.
                toward = None
            else:
                node = node_map[toward_node_id]
                toward = self.get_different_name_from_intersection(name, node)
            direction['toward'] = toward

        linestring = LineString(linestring_points)
        distance = self.distance_dict(sum(edge.meters for edge in edges))
        return directions, linestring, distance
Ejemplo n.º 7
0
    def make_directions(self, node_ids, edge_ids, split_edges):
        """Process the shortest path into a nice list of directions.

        ``node_ids``
            The IDs of the nodes on the route

        ``edges_ids``
            The IDs of the edges on the route

        ``split_edges``
            Temporary edges formed by splitting an existing edge when the
            start and/or end of a route is within an edge (e.g., for an
            address like "123 Main St")

        return
            * A list of directions. Each direction has the following form::

              {
                  'turn': 'left',
                  'name': 'SE Stark St',
                  'type': 'residential',
                  'toward': 'SE 45th Ave',
                  'distance': {
                       'meters': 80.0,
                       'kilometers': 0.08,
                       'feet': 264.0,
                       'miles': 0.05,
                   },
                  'point': (-122.5, 45.5),
                  'edge_ids': [1, 2, 3, 4],
               }

            * A linestring, which is a list of x, y coords:

              [(x, y), ...]

            * A `dict` of total distances in units of meters,
              kilometers, feet, and miles:

              {
                  'meters': 1110.0,
                  'kilometers': 1.11,
                  'feet': 5487.0,
                  'miles': 1.04,
              }

        """
        edges = []

        synthetic_start_edge = edge_ids[0] < 0
        synthetic_end_edge = len(edge_ids) > 1 and edge_ids[-1] < 0

        if synthetic_start_edge:
            edges.append(split_edges[edge_ids[0]])

        i = 1 if synthetic_start_edge else None
        j = -1 if synthetic_end_edge else None
        filter_ids = edge_ids[i:j] if i or j else edge_ids
        if filter_ids:
            q = self.session.query(Street).filter(Street.id.in_(filter_ids))
            q = q.options(joinedload(Street.start_node))
            q = q.options(joinedload(Street.end_node))
            edge_map = {edge.id: edge for edge in q}
            edges.extend(edge_map[edge_id] for edge_id in filter_ids)

        if synthetic_end_edge:
            edges.append(split_edges[edge_ids[-1]])

        directions = []
        stretches = []
        prev_name = None
        linestring_points = []
        total_distance = 0

        for edge, next_edge, next_node_id in zip(edges,
                                                 chain(edges[1:],
                                                       [None]), node_ids[1:]):
            name = edge.name
            geom = edge.geom
            length = edge.meters

            reverse_geom = edge.start_node_id == next_node_id
            points = geom.coords[::-1] if reverse_geom else geom.coords

            start_bearing = self.get_bearing(*points[:2])
            end_bearing = self.get_bearing(*points[-2:])

            if next_edge is None:
                # Reached last edge
                linestring_points.extend(points)
            else:
                linestring_points.extend(points[:-1])

            total_distance += length

            if name and name == prev_name:
                stretch = stretches[-1]
                stretch['edges'].append(edge)
                stretch['length'] += length
                stretch['end_bearing'] = end_bearing
            else:
                # Start of a new stretch
                stretches.append({
                    'edges': [edge],
                    'length':
                    length,
                    'toward_node_id':
                    next_node_id if next_node_id > -1 else None,
                    'point':
                    Point(*points[0]),
                    'start_bearing':
                    start_bearing,
                    'end_bearing':
                    end_bearing,
                })
                prev_name = name

        # Create directions from stretches.

        for prev_stretch, stretch in zip([None] + stretches[:-1], stretches):
            first_edge = stretch['edges'][0]
            length = stretch['length']
            start_bearing = stretch['start_bearing']
            toward_node_id = stretch['toward_node_id']

            if prev_stretch is None:
                # First edge in stretch
                turn = self.get_direction_from_bearing(start_bearing)
            else:
                # Remaining edges
                prev_end_bearing = prev_stretch['end_bearing']
                turn = self.calculate_way_to_turn(prev_end_bearing,
                                                  start_bearing)

            direction = {
                'turn': turn,
                'name': first_edge.display_name,
                'type': first_edge.highway,
                'toward': toward_node_id,
                'distance': self.distance_dict(length),
                'point': stretch['point'],
                'edge_ids': [edge.id for edge in stretch['edges']]
            }

            directions.append(direction)

        # Get the toward street at the start of each stretch found in
        # the loop just above. This is deferred to here so that we can
        # fetch all the toward nodes up front with their associated
        # edges in a single query. This is much faster than processing
        # each node individually inside the loop--that causes up to 2*N
        # additional queries being issued to the database (fetching of
        # the inbound and outbound edges for the node).

        filter_ids = [direction['toward'] for direction in directions]
        if filter_ids:
            q = self.session.query(Intersection)
            q = q.filter(Intersection.id.in_(filter_ids))
            q = q.options(joinedload(Intersection.streets))
            node_map = {node.id: node for node in q}
        else:
            node_map = {}

        for direction in directions:
            name = direction['name']
            toward_node_id = direction['toward']
            if toward_node_id is None:
                # This is a special case where the destination is within
                # an edge (i.e., it's a street address) AND there are no
                # intersections between the last turn and the
                # (synthetic) destination node. In this case, since the
                # destination node doesn't have any intersecting edges,
                # a toward street can't be determined. Also, the
                # destination node won't have been fetched in the query
                # above because it doesn't really exist.
                toward = 'your destination'
            else:
                node = node_map[toward_node_id]
                toward = self.get_different_name_from_intersection(name, node)
            direction['toward'] = toward

        # TODO: Extract jogs?

        linestring = LineString(linestring_points)
        distance = self.distance_dict(total_distance)
        return directions, linestring, distance