Esempio n. 1
0
 def match_point(self, s):
     if isinstance(s, str):
         try:
             point = Point.from_string(s)
         except ValueError:
             return None
     normalized_point = point
     if self.is_lat_long(point):
         point = point.reproject()
     geom = func.ST_GeomFromText(point.wkt, DEFAULT_SRID)
     distance = func.ST_Distance(geom, Intersection.geom)
     distance = distance.label('distance')
     # Try to get an Intersection first
     q = self.session.query(Intersection, distance)
     q = q.filter(distance < 5)  # 5 meters (make configurable)
     q = q.order_by(distance)
     result = q.first()
     if result is not None:
         closest_object = result.Intersection
         closest_point = closest_object.geom
     else:
         # Otherwise, get a Street
         distance = func.ST_Distance(geom, Street.geom).label('distance')
         q = self.session.query(Street, distance)
         q = q.order_by(distance)
         closest_object = q.first().Street
         # Get point on Street closest to input point
         closest_point = func.ST_ClosestPoint(Street.geom, geom)
         closest_point = closest_point.label('closest_point')
         q = self.session.query(closest_point).select_from(Street)
         q = q.filter_by(id=closest_object.id)
         closest_point = q.scalar()
         closest_point = Point.from_wkb(closest_point)
     name = closest_object.name
     return LookupResult(s, normalized_point, closest_point, closest_object, name)
Esempio n. 2
0
    def process_nodes(self):
        """Process nodes"""
        # All the nodes encountered so far.
        encountered = set()

        # Nodes at the start or end of a way AND nodes that are shared
        # between two or more ways. These will be inserted into the DB.
        intersections = set()

        # Go through all the ways and find all the intersection nodes.
        for el in self.iter_ways():
            node_ids = el['nodes']
            # This is necessary in case a start or end node isn't shared
            # with any other ways. E.g., at a dead end or at the data
            # boundary.
            encountered |= {node_ids[0], node_ids[-1]}
            for node_id in node_ids:
                if node_id in encountered:
                    intersections.add(node_id)
                else:
                    encountered.add(node_id)

        del encountered

        rows = []

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

        self.engine.execute(NODE_TABLE.delete())
        self.engine.execute(INTERSECTION_TABLE.delete())

        for el in self.iter_nodes():
            osm_id = el['id']
            latitude = el['lat']
            longitude = el['lon']
            lat_long = Point(longitude, latitude)
            geom = lat_long.reproject()
            node = {
                'id': osm_id,
                'is_intersection': osm_id in intersections,
                'geom': geom,
            }
            rows.append(node)
            if len(rows) > 1000:
                insert()

        del intersections

        if rows:
            insert()

        self.engine.execute("""
            INSERT INTO {to_table} (id, geom)
            SELECT id, geom FROM {from_table} WHERE is_intersection
        """.format(from_table=Node.__tablename__,
                   to_table=Intersection.__tablename__))
Esempio n. 3
0
    def match_point(self, s):
        if isinstance(s, str):
            try:
                point = Point.from_string(s)
            except ValueError:
                return None

        normalized_point = point
        geom = func.ST_GeomFromText(point.wkt, DEFAULT_SRID)
        distance = func.ST_Distance(
            func.ST_GeogFromWKB(geom),
            func.ST_GeogFromWKB(Intersection.geom))
        distance = distance.label('distance')

        # Distance threshold in meters
        # TODO: Should this be scale-dependent?
        distance_threshold = self.config.get('distance_threshold', 10)

        # Try to get an Intersection first
        q = self.session.query(Intersection, distance)
        q = q.filter(distance < distance_threshold)
        q = q.order_by(distance)
        result = q.first()

        if result is not None:
            closest_object = result.Intersection
            closest_point = closest_object.geom
            name = closest_object.name
        else:
            # Otherwise, get a Street
            distance = func.ST_Distance(geom, Street.geom).label('distance')
            q = self.session.query(Street, distance)
            q = q.filter(
                Street.highway.in_(Street.routable_types) |
                Street.bicycle.in_(Street.bicycle_allowed_types)
            )
            q = q.order_by(distance)
            closest_object = q.first().Street
            # Get point on Street closest to input point
            closest_point = func.ST_ClosestPoint(Street.geom, geom)
            closest_point = closest_point.label('closest_point')
            q = self.session.query(closest_point).select_from(Street)
            q = q.filter_by(id=closest_object.id)
            closest_point = q.scalar()
            closest_point = Point.from_wkb(closest_point)
            name = closest_object.display_name

        return LookupResult(s, normalized_point, closest_point, closest_object, name, 'byCycle point')
Esempio n. 4
0
 def _mapbox_result_to_lookup_result(self, s, result):
     name = result['place_name'].rsplit(', ', 1)[0]  # Remove country
     long, lat = result['center']
     geom = Point(long, lat)
     closest_object = self.match_point(f'{lat},{long}').closest_object
     data = {
         'relevance': result['relevance'],
         'score': result.get('score'),  # Mapbox prominence score
     }
     return LookupResult(s, name, geom, closest_object, name, 'Mapbox', data)
Esempio n. 5
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)
Esempio n. 6
0
 def match_id(self, s):
     match = ID_RE.search(s)
     if match:
         type_ = match.group('type')
         if type_ not in TYPE_MAP:
             raise InputError('Unknown type: %s' % type_)
         type_ = TYPE_MAP[type_]
         obj = self.session.query(type_).get(match.group('id'))
         if obj is None:
             return None
         if isinstance(obj, Intersection):
             geom = obj.geom
         else:
             length = obj.geom.length
             geom = Point(obj.geom.interpolate(length / 2))
         return LookupResult(s, obj, geom, obj, obj.name, 'byCycle ID')
Esempio n. 7
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
Esempio n. 8
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)
Esempio n. 9
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