Exemplo n.º 1
0
def distance_sorted_edges(cells, point):
    """Returns the edges of the rectangular region containing all of the
    given geocells, sorted by distance from the given point, along with
    the actual distances from the point to these edges.

    Args:
        cells: The cells (should be adjacent) defining the rectangular region
                whose edge distances are requested.
        point: The point that should determine the edge sort order.

    Returns:
        A list of (direction, distance) tuples, where direction is the edge
        and distance is the distance from the point to that edge. A direction
        value of (0,-1), for example, corresponds to the South edge of the
        rectangular region containing all of the given geocells.
    """
    # TODO(romannurik): Assert that lat,lon are actually inside the geocell.
    boxes = [geocell.compute_box(cell) for cell in cells]

    max_box = geotypes.Box(max([box.north for box in boxes]),
                                                 max([box.east for box in boxes]),
                                                 min([box.south for box in boxes]),
                                                 min([box.west for box in boxes]))
    return zip(*sorted([
            ((0,-1), geomath.distance(geotypes.Point(max_box.south, point.lon),
                                                                point)),
            ((0,1),    geomath.distance(geotypes.Point(max_box.north, point.lon),
                                                                point)),
            ((-1,0), geomath.distance(geotypes.Point(point.lat, max_box.west),
                                                                point)),
            ((1,0),    geomath.distance(geotypes.Point(point.lat, max_box.east),
                                                                point))],
            lambda x, y: cmp(x[1], y[1])))
Exemplo n.º 2
0
def distance_sorted_edges(cells, point):
    """Returns the edges of the rectangular region containing all of the
  given geocells, sorted by distance from the given point, along with
  the actual distances from the point to these edges.

  Args:
    cells: The cells (should be adjacent) defining the rectangular region
        whose edge distances are requested.
    point: The point that should determine the edge sort order.

  Returns:
    A list of (direction, distance) tuples, where direction is the edge
    and distance is the distance from the point to that edge. A direction
    value of (0,-1), for example, corresponds to the South edge of the
    rectangular region containing all of the given geocells.
  """
    # TODO(romannurik): Assert that lat,lon are actually inside the geocell.
    boxes = [geocell.compute_box(cell) for cell in cells]

    max_box = geotypes.Box(max([box.north for box in boxes]),
                           max([box.east for box in boxes]),
                           min([box.south for box in boxes]),
                           min([box.west for box in boxes]))
    return zip(*sorted(
        [((0, -1),
          geomath.distance(geotypes.Point(max_box.south, point.lon), point)),
         ((0, 1),
          geomath.distance(geotypes.Point(max_box.north, point.lon), point)),
         ((-1, 0),
          geomath.distance(geotypes.Point(point.lat, max_box.west), point)),
         ((1, 0),
          geomath.distance(geotypes.Point(point.lat, max_box.east), point)
          )], lambda x, y: cmp(x[1], y[1])))
Exemplo n.º 3
0
def distance_sorted_edges(box, point):
    """Returns the edge directions of the box, sorted by distance from a point.
  
  Returns the edge directions (i.e. NORTH, SOUTH, etc.) of the box, sorted by
  distance from the given point, along with the actual distances from the point
  to these edges.

  Args:
    box: The geotypes.Box defining the rectangular region whose edge distances
        are requested.
    point: The point that should determine the edge sort order.

  Returns:
    A list of (direction, distance) tuples, where direction is the edge
    direction and distance is the distance from the point to that edge.
    Direction values will be NORTH, SOUTH, EAST, or WEST
  """
    # TODO(romannurik): Assert that lat,lon are actually inside the box.
    return zip(
        *sorted(
            [
                (geocell.NORTH, geomath.distance(geotypes.Point(box.north, point.lon), point)),
                (geocell.SOUTH, geomath.distance(geotypes.Point(box.south, point.lon), point)),
                (geocell.WEST, geomath.distance(geotypes.Point(point.lat, box.west), point)),
                (geocell.EAST, geomath.distance(geotypes.Point(point.lat, box.east), point)),
            ],
            lambda x, y: cmp(x[1], y[1]),
        )
    )
Exemplo n.º 4
0
def distance_sorted_edges(box, point):
  """Returns the edge directions of the box, sorted by distance from a point.
  
  Returns the edge directions (i.e. NORTH, SOUTH, etc.) of the box, sorted by
  distance from the given point, along with the actual distances from the point
  to these edges.

  Args:
    box: The geotypes.Box defining the rectangular region whose edge distances
        are requested.
    point: The point that should determine the edge sort order.

  Returns:
    A list of (direction, distance) tuples, where direction is the edge
    direction and distance is the distance from the point to that edge.
    Direction values will be NORTH, SOUTH, EAST, or WEST
  """
  # TODO(romannurik): Assert that lat,lon are actually inside the box.
  return zip(*sorted([
      (geocell.NORTH,
       geomath.distance(geotypes.Point(box.north, point.lon), point)),
      (geocell.SOUTH,
       geomath.distance(geotypes.Point(box.south, point.lon), point)),
      (geocell.WEST,
       geomath.distance(geotypes.Point(point.lat, box.west), point)),
      (geocell.EAST,
       geomath.distance(geotypes.Point(point.lat, box.east), point))],
      lambda x, y: cmp(x[1], y[1])))
Exemplo n.º 5
0
    def parse_flightdata_json(json_data, time):
        aircraft_list = []
        for a in json_data["aircraft"]:

            alt = a["altitude"] if "altitude" in a else 0
            if alt == "ground":
                alt = 0
            dist = -1
            az = 0
            el = 0
            if "lat" in a and "lon" in a:
                dist = geomath.distance(myLoc, (a["lat"], a["lon"]))
                az = geomath.bearing(myLoc, (a["lat"], a["lon"]))
                el = math.degrees(math.atan(alt / (dist * 5280)))
            speed = 0
            if "speed" in a:
                speed = geomath.knot2mph(a["speed"])

            aircraftdata = AirCraftData(
                a["hex"] if "hex" in a else None,
                a["squawk"] if "squawk" in a else None,
                a["flight"] if "flight" in a else "N/A",
                a["lat"] if "lat" in a else None,
                a["lon"] if "lon" in a else None, alt,
                a["vert_rate"] if "vert_rate" in a else 0,
                a["track"] if "track" in a else None, speed,
                a["messages"] if "messages" in a else None,
                a["seen"] if "seen" in a else None,
                a["mlat"] if "mlat" in a else None,
                a["nucp"] if "nucp" in a else None,
                a["seen_pos"] if "seen_pos" in a else None,
                a["rssi"] if "rssi" in a else None, dist, az, el, time)

            aircraft_list.append(aircraftdata)
        return aircraft_list
Exemplo n.º 6
0
    def test_distance(self):
        # known distances using GLatLng from the Maps API
        calc_dist = geomath.distance(geotypes.Point(37, -122),
                                     geotypes.Point(42, -75))
        known_dist = 4024365

        # make sure the calculated distance is within +/- 1% of known distance
        self.assertTrue(abs((calc_dist - known_dist) / known_dist) <= 0.01)
Exemplo n.º 7
0
 def test_distance_rounding(self):
     # Test location that can cause math domain error (due to rounding) unless
     # the distance function clamps the spherical law of cosines value between
     # -1.0 and 1.0. 
     calc_dist = geomath.distance(geotypes.Point(47.291288, 8.56613),
                                                              geotypes.Point(47.291288, 8.56613))
     known_dist = 0.0
     self.assertTrue(calc_dist == known_dist)
Exemplo n.º 8
0
    def test_distance(self):
        # known distances using GLatLng from the Maps API
        calc_dist = geomath.distance(geotypes.Point(37, -122),
                                                                 geotypes.Point(42, -75))
        known_dist = 4024365

        # make sure the calculated distance is within +/- 1% of known distance
        self.assertTrue(abs((calc_dist - known_dist) / known_dist) <= 0.01)
Exemplo n.º 9
0
 def test_distance_rounding(self):
   # Test location that can cause math domain error (due to rounding) unless
   # the distance function clamps the spherical law of cosines value between
   # -1.0 and 1.0. 
   calc_dist = geomath.distance(geotypes.Point(47.291288, 8.56613),
                                geotypes.Point(47.291288, 8.56613))
   known_dist = 0.0
   self.assertTrue(calc_dist == known_dist)
Exemplo n.º 10
0
 def _parse_aircraft_data(self, a, time):
     alt = a.get('Alt', 0)
     dist = -1
     az = 0
     el = 0
     if 'Lat' in a and 'Long' in a:
         rec_pos = (receiver_latitude, receiver_longitude)
         ac_pos = (a['Lat'], a['Long'])
         dist = geomath.distance(rec_pos, ac_pos)
         az = geomath.bearing(rec_pos, ac_pos)
         el = math.degrees(math.atan(alt / (dist * 5280)))
     speed = 0
     if 'Spd' in a:
         speed = geomath.knot2mph(a['Spd'])
     if 'PosTime' in a:
         last_seen_time = datetime.fromtimestamp(a['PosTime'] / 1000.0)
         seen = (time - last_seen_time).total_seconds()
     else:
         seen = 0
     ac_data = AirCraftData(
         a.get('Icao', None).upper(),
         a.get('Sqk', None),
         a.get('Call', None),
         a.get('Reg', None),
         a.get('Lat', None),
         a.get('Long', None),
         alt,
         a.get('Vsi', 0),
         a.get('Trak', None),
         speed,
         a.get('CMsgs', None),
         seen,
         a.get('Mlat', False),
         None,  # NUCP
         None,  # Seen pos
         10.0 * math.log10(a.get('Sig', 0) / 255.0 + 1e-5),
         dist,
         az,
         el,
         time)
     return ac_data
Exemplo n.º 11
0
    def aircraft_data(self, json_data, time):
        aircraft_list = []
        for a in json_data["aircraft"]:

            alt = a["altitude"] if "altitude" in a else 0
            alt = a["alt_baro"] if "alt_baro" in a else 0
            if alt == "ground":
                alt = 0
            dist = -1
            az = 0
            el = 0
            if "lat" in a and "lon" in a:
                dist = geomath.distance(
                    (receiver_latitude, receiver_longitude),
                    (a["lat"], a["lon"]))
                az = geomath.bearing((receiver_latitude, receiver_longitude),
                                     (a["lat"], a["lon"]))
                el = math.degrees(math.atan(alt / (dist * 5280)))
            speed = 0
            if "speed" in a:
                speed = geomath.knot2mph(a["speed"])
            if "mach" in a:
                speed = geomath.mach2mph(a["mach"])

            aircraftdata = AirCraftData(
                a["hex"].upper() if "hex" in a else None,
                a["squawk"] if "squawk" in a else None,
                a["flight"] if "flight" in a else None, None,
                a["lat"] if "lat" in a else None,
                a["lon"] if "lon" in a else None, alt,
                a["vert_rate"] if "vert_rate" in a else 0,
                a["track"] if "track" in a else None, speed,
                a["messages"] if "messages" in a else None,
                a["seen"] if "seen" in a else None,
                a["mlat"] if "mlat" in a else None,
                a["nucp"] if "nucp" in a else None,
                a["seen_pos"] if "seen_pos" in a else None,
                a["rssi"] if "rssi" in a else None, dist, az, el, time)

            aircraft_list.append(aircraftdata)
        return aircraft_list
Exemplo n.º 12
0
def point_distance(cell, point):
    """Returns the shortest distance between a point and a geocell bounding box.

    If the point is inside the cell, the shortest distance is always to a 'edge'
    of the cell rectangle. If the point is outside the cell, the shortest distance
    will be to either a 'edge' or 'corner' of the cell rectangle.

    Returns:
        The shortest distance from the point to the geocell's rectangle, in meters.
    """
    bbox = compute_box(cell)

    between_w_e = bbox.west <= point.lon and point.lon <= bbox.east
    between_n_s = bbox.south <= point.lat and point.lat <= bbox.north

    if between_w_e:
        if between_n_s:
            # Inside the geocell.
            return min(geomath.distance(point, (bbox.south, point.lon)),
                                 geomath.distance(point, (bbox.north, point.lon)),
                                 geomath.distance(point, (point.lat, bbox.east)),
                                 geomath.distance(point, (point.lat, bbox.west)))
        else:
            return min(geomath.distance(point, (bbox.south, point.lon)),
                                 geomath.distance(point, (bbox.north, point.lon)))
    else:
        if between_n_s:
            return min(geomath.distance(point, (point.lat, bbox.east)),
                                 geomath.distance(point, (point.lat, bbox.west)))
        else:
            # TODO(romannurik): optimize
            return min(geomath.distance(point, (bbox.south, bbox.east)),
                                 geomath.distance(point, (bbox.north, bbox.east)),
                                 geomath.distance(point, (bbox.south, bbox.west)),
                                 geomath.distance(point, (bbox.north, bbox.west)))
Exemplo n.º 13
0
  def proximity_fetch(query, center, max_results=10, max_distance=0):
    """Performs a proximity/radius fetch on the given query.

    Fetches at most <max_results> entities matching the given query,
    ordered by ascending distance from the given center point, and optionally
    limited by the given maximum distance.

    This method uses a greedy algorithm that starts by searching high-resolution
    geocells near the center point and gradually looking in lower and lower
    resolution cells until max_results entities have been found matching the
    given query and no closer possible entities can be found.

    Args:
      query: A db.Query on entities of this kind.
      center: A geotypes.Point or db.GeoPt indicating the center point around
          which to search for matching entities.
      max_results: An int indicating the maximum number of desired results.
          The default is 10, and the larger this number, the longer the fetch
          will take.
      max_distance: An optional number indicating the maximum distance to
          search, in meters.

    Returns:
      The fetched entities, sorted in ascending order by distance to the search
      center.

    Raises:
      Any exceptions that google.appengine.ext.db.Query.fetch() can raise.
    """
    # TODO(romannurik): check for GqlQuery
    results = []

    searched_cells = set()

    # The current search geocell containing the lat,lon.
    cur_containing_geocell = geocell.compute(center)

    # The currently-being-searched geocells.
    # NOTES:
    #     * Start with max possible.
    #     * Must always be of the same resolution.
    #     * Must always form a rectangular region.
    #     * One of these must be equal to the cur_containing_geocell.
    cur_geocells = [cur_containing_geocell]

    closest_possible_next_result_dist = 0

    # Assumes both a and b are lists of (entity, dist) tuples, *sorted by dist*.
    # NOTE: This is an in-place merge, and there are guaranteed
    # no duplicates in the resulting list.
    def _merge_results_in_place(a, b):
      util.merge_in_place(a, b,
                        cmp_fn=lambda x, y: cmp(x[1], y[1]),
                        dup_fn=lambda x, y: x[0].key() == y[0].key())

    sorted_edges = [(0,0)]
    sorted_edge_distances = [0]

    while cur_geocells:
      closest_possible_next_result_dist = sorted_edge_distances[0]
      if max_distance and closest_possible_next_result_dist > max_distance:
        break

      cur_geocells_unique = list(set(cur_geocells).difference(searched_cells))

      # Run query on the next set of geocells.
      cur_resolution = len(cur_geocells[0])
      temp_query = copy.deepcopy(query)  # TODO(romannurik): is this safe?
      temp_query.filter('location_geocells IN', cur_geocells_unique)

      # Update results and sort.
      new_results = temp_query.fetch(1000)
      if DEBUG:
        logging.info('fetch complete for %s' % (','.join(cur_geocells_unique),))

      searched_cells.update(cur_geocells)

      # Begin storing distance from the search result entity to the
      # search center along with the search result itself, in a tuple.
      new_results = [(entity, geomath.distance(center, entity.location))
                     for entity in new_results]
      new_results = sorted(new_results, lambda dr1, dr2: cmp(dr1[1], dr2[1]))
      new_results = new_results[:max_results]

      # Merge new_results into results or the other way around, depending on
      # which is larger.
      if len(results) > len(new_results):
        _merge_results_in_place(results, new_results)
      else:
        _merge_results_in_place(new_results, results)
        results = new_results

      results = results[:max_results]

      sorted_edges, sorted_edge_distances = \
          util.distance_sorted_edges(cur_geocells, center)

      if len(results) == 0 or len(cur_geocells) == 4:
        # Either no results (in which case we optimize by not looking at
        # adjacents, go straight to the parent) or we've searched 4 adjacent
        # geocells, in which case we should now search the parents of those
        # geocells.
        cur_containing_geocell = cur_containing_geocell[:-1]
        cur_geocells = list(set([cell[:-1] for cell in cur_geocells]))
        if not cur_geocells or not cur_geocells[0]:
          break  # Done with search, we've searched everywhere.

      elif len(cur_geocells) == 1:
        # Get adjacent in one direction.
        # TODO(romannurik): Watch for +/- 90 degree latitude edge case geocells.
        nearest_edge = sorted_edges[0]
        cur_geocells.append(geocell.adjacent(cur_geocells[0], nearest_edge))

      elif len(cur_geocells) == 2:
        # Get adjacents in perpendicular direction.
        nearest_edge = util.distance_sorted_edges([cur_containing_geocell],
                                                   center)[0][0]
        if nearest_edge[0] == 0:
          # Was vertical, perpendicular is horizontal.
          perpendicular_nearest_edge = [x for x in sorted_edges if x[0] != 0][0]
        else:
          # Was horizontal, perpendicular is vertical.
          perpendicular_nearest_edge = [x for x in sorted_edges if x[0] == 0][0]

        cur_geocells.extend(
            [geocell.adjacent(cell, perpendicular_nearest_edge)
             for cell in cur_geocells])

      # We don't have enough items yet, keep searching.
      if len(results) < max_results:
        if DEBUG:
          logging.debug('have %d results but want %d results, '
                        'continuing search' % (len(results), max_results))
        continue

      if DEBUG:
        logging.debug('have %d results' % (len(results),))

      # If the currently max_results'th closest item is closer than any
      # of the next test geocells, we're done searching.
      current_farthest_returnable_result_dist = \
          geomath.distance(center, results[max_results - 1][0].location)
      if (closest_possible_next_result_dist >=
          current_farthest_returnable_result_dist):
        if DEBUG:
          logging.debug('DONE next result at least %f away, '
                        'current farthest is %f dist' %
                        (closest_possible_next_result_dist,
                         current_farthest_returnable_result_dist))
        break

      if DEBUG:
        logging.debug('next result at least %f away, '
                      'current farthest is %f dist' %
                      (closest_possible_next_result_dist,
                       current_farthest_returnable_result_dist))

    if DEBUG:
      logging.info('proximity query looked '
                   'in %d geocells' % len(searched_cells))

    return [entity for (entity, dist) in results[:max_results]
            if not max_distance or dist < max_distance]
Exemplo n.º 14
0
def point_distance(cell, point):
    """Returns the shortest distance between a point and a geocell bounding box.

  If the point is inside the cell, the shortest distance is always to a 'edge'
  of the cell rectangle. If the point is outside the cell, the shortest distance
  will be to either a 'edge' or 'corner' of the cell rectangle.

  Returns:
    The shortest distance from the point to the geocell's rectangle, in meters.
  """
    bbox = compute_box(cell)

    between_w_e = bbox.west <= point.lon and point.lon <= bbox.east
    between_n_s = bbox.south <= point.lat and point.lat <= bbox.north

    if between_w_e:
        if between_n_s:
            # Inside the geocell.
            return min(geomath.distance(point, (bbox.south, point.lon)),
                       geomath.distance(point, (bbox.north, point.lon)),
                       geomath.distance(point, (point.lat, bbox.east)),
                       geomath.distance(point, (point.lat, bbox.west)))
        else:
            return min(geomath.distance(point, (bbox.south, point.lon)),
                       geomath.distance(point, (bbox.north, point.lon)))
    else:
        if between_n_s:
            return min(geomath.distance(point, (point.lat, bbox.east)),
                       geomath.distance(point, (point.lat, bbox.west)))
        else:
            # TODO(romannurik): optimize
            return min(geomath.distance(point, (bbox.south, bbox.east)),
                       geomath.distance(point, (bbox.north, bbox.east)),
                       geomath.distance(point, (bbox.south, bbox.west)),
                       geomath.distance(point, (bbox.north, bbox.west)))
Exemplo n.º 15
0
    def proximity_fetch(query,
                        center,
                        max_results=10,
                        max_distance=0,
                        start_resolution=geocell.MAX_GEOCELL_RESOLUTION):
        """Performs a proximity/radius fetch on the given query.

    Fetches at most max_results entities matching the given query,
    ordered by ascending distance from the given center point, and optionally
    limited by the given maximum distance.

    This method uses a greedy algorithm that starts by searching high-resolution
    geocells near the center point and gradually looking in lower and lower
    resolution cells until max_results entities have been found matching the
    given query and no closer possible entities can be found.

    Args:
      query: A db.Query on entities of this kind.
      center: A geotypes.Point or db.GeoPt indicating the center point around
          which to search for matching entities.
      max_results: An int indicating the maximum number of desired results.
          The default is 10, and the larger this number, the longer the fetch
          will take.
      max_distance: An optional number indicating the maximum distance to
          search, in meters.
      start_resolution: An optional number indicating the geocell resolution
          to begin the search at. Larger values will result in longer response
          times on average, but are more efficient for extremely dense data.
          The default is geocell.MAX_GEOCELL_RESOLUTION

    Returns:
      The fetched entities, sorted in ascending order by distance to the search
      center.

    Raises:
      Any exceptions that google.appengine.ext.db.Query.fetch() can raise.
    """
        # TODO(romannurik): check for GqlQuery
        results = []

        searched_cells = set()

        # The current search geocell containing the lat,lon.
        cur_containing_geocell = geocell.compute(center, start_resolution)

        # The currently-being-searched geocells.
        # NOTES:
        #     * Start with max possible.
        #     * Must always be of the same resolution.
        #     * Must always form a rectangular region.
        #     * One of these must be equal to the cur_containing_geocell.
        cur_geocells = [cur_containing_geocell]

        closest_possible_next_result_dist = 0

        sorted_edge_dirs = [(0, 0)]
        sorted_edge_distances = [0]

        # Assumes both a and b are lists of (entity, dist) tuples, *sorted by dist*.
        # NOTE: This is an in-place merge, and there are guaranteed
        # no duplicates in the resulting list.
        def _merge_results_in_place(a, b):
            util.merge_in_place(a,
                                b,
                                cmp_fn=lambda x, y: cmp(x[1], y[1]),
                                dup_fn=lambda x, y: x[0].key() == y[0].key())

        def _first_horz(edges):
            return [x for x in edges if x[0] != 0][0]

        def _first_vert(edges):
            return [x for x in edges if x[0] == 0][0]

        while cur_geocells:
            cur_geocells_unique = list(
                set(cur_geocells).difference(searched_cells))
            cur_resolution = len(cur_geocells[0])

            # Run query on the next set of geocells.
            new_results = util.async_in_query_fetch(query,
                                                    'location_geocells',
                                                    cur_geocells_unique,
                                                    max_results=max_results *
                                                    len(cur_geocells_unique),
                                                    debug=DEBUG)

            # Update results and sort.
            searched_cells.update(cur_geocells)

            # Begin storing distance from the search result entity to the
            # search center along with the search result itself, in a tuple.
            new_results = [(entity, geomath.distance(center, entity.location))
                           for entity in new_results]
            new_results = sorted(new_results,
                                 lambda dr1, dr2: cmp(dr1[1], dr2[1]))
            new_results = new_results[:max_results]

            # Merge new_results into results or the other way around, depending on
            # which is larger.
            if len(results) > len(new_results):
                _merge_results_in_place(results, new_results)
            else:
                _merge_results_in_place(new_results, results)
                results = new_results

            results = results[:max_results]

            if DEBUG:
                logging.debug(('GeoModel Proximity Query: '
                               'Have %d results') % (len(results), ))

            if len(results) >= max_results:
                if DEBUG:
                    logging.debug(
                        ('GeoModel Proximity Query: '
                         'Wanted %d results, ending search') % (max_results, ))
                break

            # Determine the next set of geocells to search.
            sorted_edge_dirs, _ = \
                util.distance_sorted_edges(util.max_box(cur_geocells), center)

            if len(results) == 0 or len(cur_geocells) == 4:
                # Either no results (in which case we optimize by not looking at
                # adjacents, go straight to the parent) or we've searched 4 adjacent
                # geocells, in which case we should now search the parents of those
                # geocells.
                cur_containing_geocell = cur_containing_geocell[:-1]
                cur_geocells = list(set([cell[:-1] for cell in cur_geocells]))

                if not cur_geocells or not cur_geocells[0]:
                    break  # Done with search, we've searched everywhere.

                if len(cur_geocells) == 2:
                    # There are two parents for the 4 just-searched cells; get 2
                    # perpendicular adjacent parents to search a full set of 4 cells.
                    perp_dir = (_first_vert(sorted_edge_dirs)
                                if geocell.collinear(cur_geocells[0],
                                                     cur_geocells[1], False)
                                else _first_horz(sorted_edge_dirs))

                    cur_geocells.extend(
                        filter(lambda x: x is not None, [
                            geocell.adjacent(cur_geocells[0], perp_dir),
                            geocell.adjacent(cur_geocells[1], perp_dir)
                        ]))

            elif len(cur_geocells) == 1:
                # Searched one geocell, now search its 3 adjacents.
                horz_dir = _first_horz(sorted_edge_dirs)
                vert_dir = _first_vert(sorted_edge_dirs)
                diag_dir = (horz_dir[0], vert_dir[1])

                cur_geocells.extend(
                    filter(lambda x: x is not None, [
                        geocell.adjacent(cur_geocells[0], horz_dir),
                        geocell.adjacent(cur_geocells[0], vert_dir),
                        geocell.adjacent(cur_geocells[0], diag_dir)
                    ]))

            # Stop the search if the next closest possible search result is farther
            # than max_distance or, if we have max_results results already, farther
            # than the last result.
            _, sorted_edge_distances = \
                util.distance_sorted_edges(util.max_box(cur_geocells), center)
            closest_possible_next_result_dist = sorted_edge_distances[0]

            if DEBUG:
                logging.debug(('GeoModel Proximity Query: '
                               'Next result at least %f meters away') %
                              (closest_possible_next_result_dist, ))

            if max_distance and closest_possible_next_result_dist > max_distance:
                if DEBUG:
                    logging.debug(
                        ('GeoModel Proximity Query: '
                         'Done! Next result at least %f meters away, '
                         'max disance is %f meters') %
                        (closest_possible_next_result_dist, max_distance))
                break

            if len(results) >= max_results:
                current_farthest_returnable_result_dist = \
                    geomath.distance(center, results[max_results - 1][0].location)
                if (closest_possible_next_result_dist >=
                        current_farthest_returnable_result_dist):
                    if DEBUG:
                        logging.debug(
                            ('GeoModel Proximity Query: '
                             'Done! Next result at least %f meters away, '
                             'current farthest is %f meters away') %
                            (closest_possible_next_result_dist,
                             current_farthest_returnable_result_dist))
                    break

                if DEBUG:
                    logging.debug(('GeoModel Proximity Query: '
                                   'Next result at least %f meters away, '
                                   'current farthest is %f meters away') %
                                  (closest_possible_next_result_dist,
                                   current_farthest_returnable_result_dist))

        if DEBUG:
            logging.debug(('GeoModel Proximity Query: '
                           'Looked in %d geocells') % (len(searched_cells), ))

        return [
            entity for (entity, dist) in results[:max_results]
            if not max_distance or dist < max_distance
        ]
Exemplo n.º 16
0
  def proximity_fetch(query, center, max_results=10, max_distance=0,
                      start_resolution=geocell.MAX_GEOCELL_RESOLUTION):
    """Performs a proximity/radius fetch on the given query.

    Fetches at most max_results entities matching the given query,
    ordered by ascending distance from the given center point, and optionally
    limited by the given maximum distance.

    This method uses a greedy algorithm that starts by searching high-resolution
    geocells near the center point and gradually looking in lower and lower
    resolution cells until max_results entities have been found matching the
    given query and no closer possible entities can be found.

    Args:
      query: A db.Query on entities of this kind.
      center: A geotypes.Point or db.GeoPt indicating the center point around
          which to search for matching entities.
      max_results: An int indicating the maximum number of desired results.
          The default is 10, and the larger this number, the longer the fetch
          will take.
      max_distance: An optional number indicating the maximum distance to
          search, in meters.
      start_resolution: An optional number indicating the geocell resolution
          to begin the search at. Larger values will result in longer response
          times on average, but are more efficient for extremely dense data.
          The default is geocell.MAX_GEOCELL_RESOLUTION

    Returns:
      The fetched entities, sorted in ascending order by distance to the search
      center.

    Raises:
      Any exceptions that google.appengine.ext.db.Query.fetch() can raise.
    """
    # TODO(romannurik): check for GqlQuery
    results = []

    searched_cells = set()

    # The current search geocell containing the lat,lon.
    cur_containing_geocell = geocell.compute(center, start_resolution)

    # The currently-being-searched geocells.
    # NOTES:
    #     * Start with max possible.
    #     * Must always be of the same resolution.
    #     * Must always form a rectangular region.
    #     * One of these must be equal to the cur_containing_geocell.
    cur_geocells = [cur_containing_geocell]

    closest_possible_next_result_dist = 0
    
    sorted_edge_dirs = [(0,0)]
    sorted_edge_distances = [0]

    # Assumes both a and b are lists of (entity, dist) tuples, *sorted by dist*.
    # NOTE: This is an in-place merge, and there are guaranteed
    # no duplicates in the resulting list.
    def _merge_results_in_place(a, b):
      util.merge_in_place(a, b,
                        cmp_fn=lambda x, y: cmp(x[1], y[1]),
                        dup_fn=lambda x, y: x[0].key() == y[0].key())
    
    def _first_horz(edges):
      return [x for x in edges if x[0] != 0][0]
    
    def _first_vert(edges):
      return [x for x in edges if x[0] == 0][0]

    while cur_geocells:
      cur_geocells_unique = list(set(cur_geocells).difference(searched_cells))
      cur_resolution = len(cur_geocells[0])

      # Run query on the next set of geocells.
      new_results = util.async_in_query_fetch(
          query, 'location_geocells', cur_geocells_unique,
          max_results=max_results * len(cur_geocells_unique),
          debug=DEBUG)

      # Update results and sort.
      searched_cells.update(cur_geocells)

      # Begin storing distance from the search result entity to the
      # search center along with the search result itself, in a tuple.
      new_results = [(entity, geomath.distance(center, entity.location))
                     for entity in new_results]
      new_results = sorted(new_results, lambda dr1, dr2: cmp(dr1[1], dr2[1]))
      new_results = new_results[:max_results]

      # Merge new_results into results or the other way around, depending on
      # which is larger.
      logging.info("#Results :")
      logging.info(results)
      logging.info("#new_results :")
      logging.info(new_results)
      if len(results) > len(new_results):
        _merge_results_in_place(results, new_results)
      else:
        _merge_results_in_place(new_results, results)
        results = new_results

      results = results[:max_results]

      if DEBUG:
        logging.debug(('GeoModel Proximity Query: '
                       'Have %d results') %
                      (len(results),))
      
      if len(results) >= max_results:
        if DEBUG:
          logging.info(('GeoModel Proximity Query: '
                         'Wanted %d results, ending search') %
                        (max_results,))
        break
      
      # Determine the next set of geocells to search.
      sorted_edge_dirs, _ = \
          util.distance_sorted_edges(util.max_box(cur_geocells), center)

      if len(results) == 0 or len(cur_geocells) == 4:
        # Either no results (in which case we optimize by not looking at
        # adjacents, go straight to the parent) or we've searched 4 adjacent
        # geocells, in which case we should now search the parents of those
        # geocells.
        cur_containing_geocell = cur_containing_geocell[:-1]
        cur_geocells = list(set([cell[:-1] for cell in cur_geocells]))
        
        if not cur_geocells or not cur_geocells[0]:
          break  # Done with search, we've searched everywhere.
        
        if len(cur_geocells) == 2:
          # There are two parents for the 4 just-searched cells; get 2
          # perpendicular adjacent parents to search a full set of 4 cells.
          perp_dir = (_first_vert(sorted_edge_dirs)
                      if geocell.collinear(cur_geocells[0],
                                           cur_geocells[1], False)
                      else _first_horz(sorted_edge_dirs))
          
          cur_geocells.extend(
              filter(lambda x: x is not None, [
                     geocell.adjacent(cur_geocells[0], perp_dir),
                     geocell.adjacent(cur_geocells[1], perp_dir)]))

      elif len(cur_geocells) == 1:
        # Searched one geocell, now search its 3 adjacents.
        horz_dir = _first_horz(sorted_edge_dirs)
        vert_dir = _first_vert(sorted_edge_dirs)
        diag_dir = (horz_dir[0], vert_dir[1])
        
        cur_geocells.extend(
            filter(lambda x: x is not None, [
                   geocell.adjacent(cur_geocells[0], horz_dir),
                   geocell.adjacent(cur_geocells[0], vert_dir),
                   geocell.adjacent(cur_geocells[0], diag_dir)]))

      # Stop the search if the next closest possible search result is farther
      # than max_distance or, if we have max_results results already, farther
      # than the last result.
      _, sorted_edge_distances = \
          util.distance_sorted_edges(util.max_box(cur_geocells), center)
      closest_possible_next_result_dist = sorted_edge_distances[0]
      
      if DEBUG:
        logging.info(('GeoModel Proximity Query: '
                       'Next result at least %f meters away') %
                      (closest_possible_next_result_dist,))
      
      if max_distance and closest_possible_next_result_dist > max_distance:
        if DEBUG:
          logging.info(('GeoModel Proximity Query: '
                         'Done! Next result at least %f meters away, '
                         'max disance is %f meters') %
                        (closest_possible_next_result_dist, max_distance))
        break
      
      if len(results) >= max_results:
        current_farthest_returnable_result_dist = \
            geomath.distance(center, results[max_results - 1][0].location)
        if (closest_possible_next_result_dist >=
            current_farthest_returnable_result_dist):
          if DEBUG:
            logging.info(('GeoModel Proximity Query: '
                           'Done! Next result at least %f meters away, '
                           'current farthest is %f meters away') %
                          (closest_possible_next_result_dist,
                           current_farthest_returnable_result_dist))
          break
        
        if DEBUG:
          logging.info(('GeoModel Proximity Query: '
                         'Next result at least %f meters away, '
                         'current farthest is %f meters away') %
                        (closest_possible_next_result_dist,
                         current_farthest_returnable_result_dist))

    if DEBUG:
      logging.info(('GeoModel Proximity Query: '
                     'Looked in %d geocells') %
                    (len(searched_cells),))

    return [entity for (entity, dist) in results[:max_results]
            if not max_distance or dist < max_distance]