def answer_query(self, query): """Selects the top N geographically closest SliverTools to the client. Finds the top N closest SliverTools to the client and returns them. Note that N is currently hardcoded to 4. Args: query: A LookupQuery instance. Returns: A list of SliverTool entities on success, or None if there is no SliverTool available that matches the query. """ # Return no more than MAX_RESULTS SliverTools in the result. MAX_RESULTS = 4 candidates = self.get_candidates(query) if len(candidates) == 0: logging.error('No results found for %s.', query.tool_id) return None if (query.latitude is None) or (query.longitude is None): logging.warning('No latide/longitude, return a random sliver tool.') return [random.choice(candidates)] min_distance = float('+inf') sliver_tool_bins = {} sliver_tools = [] distances = {} # Combine the candidates into bins by site. for sliver_tool in candidates: if not sliver_tool_bins.has_key(sliver_tool.site_id): sliver_tool_bins[sliver_tool.site_id] = [sliver_tool] else: sliver_tool_bins[sliver_tool.site_id].append(sliver_tool) for site_id in sliver_tool_bins: # Take a random sliver from the list. sliver_tool = random.choice(sliver_tool_bins[site_id]) # Check if we already computed the distance of this site. if distances.has_key(sliver_tool.site_id): current_distance = distances[sliver_tool.site_id] else: current_distance = distance.distance( query.latitude, query.longitude, sliver_tool.latitude, sliver_tool.longitude) distances[sliver_tool.site_id] = current_distance sliver_tools.append(\ sliver_tool_distance.SliverToolDistance(sliver_tool, \ current_distance)) sliver_tools_sorted = sorted(sliver_tools, key=attrgetter('distance')) final_results = [] for std in sliver_tools_sorted: final_results.append(std.sliver_tool) return final_results[:MAX_RESULTS]
def _add_candidate(self, query, candidate, site_distances, tool_distances): if candidate.site_id not in site_distances: site_distances[candidate.site_id] = distance.distance( query.latitude, query.longitude, candidate.latitude, candidate.longitude) tool_distances.append({ 'distance': site_distances[candidate.site_id], 'tool': candidate })
def testInvalidInputs(self): import math from numbers import Number dist = 0 try: dist = distance.distance(-700, 1000, 999, -5454) except Exception: self.fail("distance threw an exception on invalid entry") self.assertTrue(isinstance(dist, Number)) self.assertFalse(math.isnan(dist))
def answer_query(self, query): """Selects the geographically closest SliverTool. Args: query: A LookupQuery instance. Returns: A SliverTool entity in case of success, or None if there is no SliverTool available that matches the query. """ candidates = self.get_candidates(query) if len(candidates) == 0: logging.error('No results found for %s.', query.tool_id) return None if (query.latitude is None) or (query.longitude is None): logging.warning('No latide/longitude, return a random sliver tool.') return [random.choice(candidates)] min_distance = float('+inf') closest_sliver_tools = [] distances = {} # Compute for each SliverTool the distance and add keep in the # 'closest_sliver_tools' list only the SliverTools whose distance is # less or equal than the current minimum. for sliver_tool in candidates: # Check if we already computed the distance of this site. if distances.has_key(sliver_tool.site_id): current_distance = distances[sliver_tool.site_id] else: current_distance = distance.distance( query.latitude, query.longitude, sliver_tool.latitude, sliver_tool.longitude) distances[sliver_tool.site_id] = current_distance # Update the min distance and add the SliverTool to the list. if current_distance < min_distance: min_distance = current_distance closest_sliver_tools = [sliver_tool] elif current_distance == min_distance: closest_sliver_tools.append(sliver_tool) # Add the min_distance to the query so it can be logged later. Round to # the next highest kilometre radius to remove precision. query.distance = math.ceil(min_distance) # Choose randomly among candidates with the same, minimum distance. return [random.choice(closest_sliver_tools)]
def log_location(self, query, sliver_tools): """Logs the client Country and compares Maxmind to AppEngine distance""" if query.tool_id != 'ndt_ssl': # We only want to look at ndt_ssl clients for now. return if type(sliver_tools) != list or not sliver_tools: logging.info('unexpected sliver_tools type: %s', len(sliver_tools)) return if query._geolocation_type != constants.GEOLOCATION_APP_ENGINE: # We cannot compare AppEngine location to Maxmind in this case. return t0 = datetime.datetime.now() # Log client country to display geomap summaries of client origins. logging.info('[client.country],%s', query.country) # Log only the first (closest) site. sliver_tool = sliver_tools[0] # Lookup the maxmind information, if it doesn't exist, then just # return. if not query._set_maxmind_geolocation(query.ip_address, None, None): return # Calculate the difference between the two systems. difference = distance.distance(query._gae_latitude, query._gae_longitude, query._maxmind_latitude, query._maxmind_longitude) logging.info( ('[server.distance],{scheme},{tool_id},{site_id},{country},' '{city},{same_country},{difference}').format( scheme=self.request.scheme, tool_id=query.tool_id, site_id=sliver_tool.site_id, country=sliver_tool.country, city=sliver_tool.city, same_country=(query._gae_country == query._maxmind_country), difference=difference)) t1 = datetime.datetime.now() logging.info('[log_location.delay],{delay}'.format( delay=str((t1 - t0).total_seconds())))
def log_location(self, query, sliver_tools): """Logs the client Country and compares Maxmind to AppEngine distance""" if query.tool_id != 'ndt_ssl': # We only want to look at ndt_ssl clients for now. return if type(sliver_tools) != list or not sliver_tools: logging.info('unexpected sliver_tools type: %s', len(sliver_tools)) return if query._geolocation_type != constants.GEOLOCATION_APP_ENGINE: # We cannot compare AppEngine location to Maxmind in this case. return t0 = datetime.datetime.now() # Log client country to display geomap summaries of client origins. logging.info('[client.country],%s', query.country) # Log only the first (closest) site. sliver_tool = sliver_tools[0] # Lookup the maxmind information. query._set_maxmind_geolocation(query.ip_address, None, None) # Calculate the difference between the two systems. difference = distance.distance( query._gae_latitude, query._gae_longitude, query._maxmind_latitude, query._maxmind_longitude) logging.info(( '[server.distance],{scheme},{tool_id},{site_id},{country},' '{city},{same_country},{difference}').format( scheme=self.request.scheme, tool_id=query.tool_id, site_id=sliver_tool.site_id, country=sliver_tool.country, city=sliver_tool.city, same_country=(query._gae_country == query._maxmind_country), difference=difference)) t1 = datetime.datetime.now() logging.info('[log_location.delay],{delay}'.format(delay=str(( t1 - t0).total_seconds())))
def _get_closest_n_candidates(self, query, max_results): """Selects the top N geographically closest SliverTools to the client. Args: query: A LookupQuery instance. max_results: The maximum number of candidates to return. Returns: A list of SliverTool entities on success, or None if there is no SliverTool available that matches the query. """ candidates = self._get_matching_candidates(query) if not candidates: return None if (query.latitude is None) or (query.longitude is None): logging.warning( 'No latitude/longitude, return random sliver tool(s).') return random.sample(candidates, min(len(candidates), max_results)) # Pre-shuffle the candidates to randomize the order of equidistant # results. random.shuffle(candidates) site_distances = {} tool_distances = [] for candidate in candidates: if candidate.site_id not in site_distances: site_distances[candidate.site_id] = distance.distance( query.latitude, query.longitude, candidate.latitude, candidate.longitude) tool_distances.append({ 'distance': site_distances[candidate.site_id], 'tool': candidate }) # Sort the tools by distance tool_distances.sort(key=lambda t: t['distance']) # Create a new list of just the sorted SliverTool objects. sorted_tools = [t['tool'] for t in tool_distances] return sorted_tools[:max_results]
def testValidSmallDistance(self): dist = distance.distance(0, 0, 10, 10) self.assertEqual(1568.5205567985761, dist)
def testValidLargeDistance(self): dist = distance.distance(20, 20, 100, 100) self.assertEqual(8009.5721050828461, dist)