def test_midpoint(self): """El punto medio entre dos puntos debe ser correcto.""" p1 = Point(0, 0) p2 = Point(10, 10) midpoint = p1.midpoint(p2) self.assertAlmostEqual(midpoint.lat, 5) self.assertAlmostEqual(midpoint.lon, 5)
def test_between_location_no_door_num(self): """Si no se especifica una altura en una una dirección de tipo 'between', el resultado final debería tener una posición calculada a partir del promedio de la posición de las dos intersecciones.""" resp_btwn = self.get_response({ 'direccion': 'Mar del Plata entre Diaz y 12 de Octubre', 'departamento': 'Río Hondo' }) point_btwn = Point.from_json_location(resp_btwn[0]['ubicacion']) resp_isct_1 = self.get_response({ 'direccion': 'Mar del Plata esquina Diaz', 'departamento': 'Río Hondo' }) point_isct_1 = Point.from_json_location(resp_isct_1[0]['ubicacion']) resp_isct_2 = self.get_response({ 'direccion': 'Mar del Plata esq. 12 de Octubre', 'departamento': 'Río Hondo' }) point_isct_2 = Point.from_json_location(resp_isct_2[0]['ubicacion']) midpoint = point_isct_1.midpoint(point_isct_2) distance = point_btwn.approximate_distance_meters(midpoint) self.assertAlmostEqual(distance, 0)
def test_intersection_location_no_door_num(self): """Si no se especifica una altura en una intersección, se debería utilizar la posición de la intersección como campo 'ubicacion'.""" resp = self.get_response({ 'direccion': 'Maipú esquina Mendoza', 'departamento': 'Rosario' }) loc = resp[0]['ubicacion'] point_isct = Point.from_json_location(loc) point_target = Point(-60.636548, -32.952020) error_m = point_isct.approximate_distance_meters(point_target) self.assertTrue(error_m < 15, error_m)
def test_position(self): """Cuando sea posible, se debería georreferenciar la dirección utilizando los datos de la calle y la altura.""" resp = self.get_response({ 'direccion': 'Billinghurst nro 650', 'departamento': 'Comuna 5' }) point_resp = Point.from_json_location(resp[0]['ubicacion']) point_target = Point(-58.415413, -34.602319) error_m = point_resp.approximate_distance_meters(point_target) self.assertTrue(error_m < 15, error_m)
def test_intersection_position(self): """La posición de una intersección debería estar cerca a la posición de una altura sobre la primera calle cerca de la esquina.""" resp_simple = self.get_response({ 'direccion': 'Dr. Adolfo Guemes al 600', 'departamento': '66028' }) resp_isct = self.get_response({ 'direccion': 'Dr. Adolfo esq. Rivadavia', 'departamento': '66028' }) point_simple = Point.from_json_location(resp_simple[0]['ubicacion']) point_isct = Point.from_json_location(resp_isct[0]['ubicacion']) self.assertLess( point_simple.approximate_distance_meters(point_isct), 30 # metros )
def run_location_queries(es, params_list, queries): """Dada una lista de queries de ubicación, construye las queries apropiadas a índices de departamentos y municipios, y las ejecuta utilizando Elasticsearch. Args: es (Elasticsearch): Conexión a Elasticsearch. params_list (list): Lista de ParametersParseResult. queries (list): Lista de queries de ubicación, generadas a partir de 'params_list'. Returns: list: Resultados de ubicaciones (QueryResult). """ # TODO: # Por problemas con los datos de origen, se optó por utilizar una # implementación simple para la la funcion 'run_location_queries'. # Cuando los datos de departamentos cubran todo el departamento nacional, # se podría modificar la función para que funcione de la siguiente forma: # # (Recordar que las provincias y departamentos cubren todo el territorio # nacional, pero no los municipios.) # # Por cada consulta, implementar un patrón similar al de address.py (con # iteradores de consultas), donde cada iterador ('búsqueda') realiza los # siguientes pasos: # # 1) Buscar la posición en el índice de departamentos. # 2) Si se obtuvo un departamento, buscar la posición nuevamente pero en el # índice de municipios. Si no se obtuvo nada, cancelar la búsqueda. # 3) Componer el departamento, la provincia del departamento y el municipio # en un QueryResult para completar la búsqueda. all_searches = [] state_searches = [] muni_searches = [] dept_searches = [] for query in queries: es_query = { 'geo_shape_geoms': [Point.from_json_location(query).to_geojson()], 'fields': [N.ID, N.NAME, N.SOURCE], 'size': 1 } # Buscar la posición en provincias, departamentos y municipios search = StatesSearch(es_query) all_searches.append(search) state_searches.append(search) search = DepartmentsSearch(es_query) all_searches.append(search) dept_searches.append(search) search = MunicipalitiesSearch(es_query) all_searches.append(search) muni_searches.append(search) # Ejecutar todas las búsquedas preparadas ElasticsearchSearch.run_searches(es, all_searches) locations = [] iterator = zip(params_list, queries, state_searches, dept_searches, muni_searches) for params, query, state_search, dept_search, muni_search in iterator: # Ya que la query de tipo location retorna una o cero entidades, # extraer la primera entidad de los resultados, o tomar None si # no hay resultados. state = state_search.result.hits[0] if state_search.result else None dept = dept_search.result.hits[0] if dept_search.result else None muni = muni_search.result.hits[0] if muni_search.result else None result = _build_location_result(params.received_values(), query, state, dept, muni) locations.append(result) return locations
def _process_intersections(self, intersections, street_1_ids, street_2_ids, street_3_ids, street_1_points): """Procesa los resultados de las tres búsquedas realizadas en 'planner_steps', para generar los 'BetweenEntry' que representan resultados potenciales a la búsqueda de direcciones. Args: intersections (list): Lista de intersecciones encontradas (documentos). street_1_ids (set): Conjunto de IDs de resultados para la calle 1. street_2_ids (set): Conjunto de IDs de resultados para la calle 2. street_3_ids (set): Conjunto de IDs de resultados para la calle 3. street_1_points (dict): Posición calculada utilizando la altura sobre cada resultado de la calle 1. Returns: list: Lista de 'BetweenEntry', cada una representando un resultado potencial. """ between_entries = {} street_2_3_ids = street_2_ids | street_3_ids # Recorrer cada intersección. Las intersecciones pueden ser entre las # calles 1 y 2, o 1 y 3. Es necesario distinguir entre las dos # opciones. for intersection in intersections: id_a = intersection[N.STREET_A][N.ID] id_b = intersection[N.STREET_B][N.ID] # Comprobar que la intersección es entre las calles 1 y 2, o 1 y 3 if id_a in street_1_ids and id_b in street_2_3_ids: street_1 = intersection[N.STREET_A] street_other = intersection[N.STREET_B] elif id_a in street_2_3_ids and id_b in street_1_ids: street_1 = intersection[N.STREET_B] street_other = intersection[N.STREET_A] else: raise RuntimeError( 'Unknown street IDs for intersection {} - {}'.format(id_a, id_b)) # Si la calle 1 no está en between_entries, construir un nuevo # BetweenEntry. if street_1[N.ID] in between_entries: entry = between_entries[street_1[N.ID]] else: entry = AddressBtwnQueryPlanner.BetweenEntry(street_1) if self._numerical_door_number: entry.street_1_point = street_1_points[street_1[N.ID]] between_entries[street_1[N.ID]] = entry point = Point.from_geojson_point(intersection[N.GEOM]) # Tomar nuestro BetweenEntry asociado a la calle 1, y asignarle su # calle 2 o 3. Un BetweenEntry está completo cuando tiene las tres # calles asignadas (1, 2 y 3). if street_other[N.ID] in street_2_ids: entry.street_2 = street_other entry.street_2_point = point elif street_other[N.ID] in street_3_ids: entry.street_3 = street_other entry.street_3_point = point else: raise RuntimeError('Unknown street ID: {}'.format( street_other[N.ID])) # Luego de iterar, algunos 'BetweenEntry' van a contener las tres # calles necesarias, y otros no. return between_entries.values()
def planner_steps(self): """Crea un iterador de ElasticsearchSearch, representando los pasos requeridos para completar la búsqueda de los datos la dirección. Pasos requeridos: 1) Expandir búsqueda de localidad, si la hay. 2) Búsqueda de la calle principal (calle 1). 3) Búsqueda de la calle 2. 4) Búsqueda de intersecciones entre las calles 1 y 2. Explicación: Primero, se buscan las calles 1 y 2, obteniendo todos los IDs de las mismas. Luego, se buscan intersecciones de calles que hagan referencia a los IDs obtenidos. Aunque es posible buscar intersecciones directamente por nombre, no se hace esto ya que sería difícil luego interpretar si la calle A de la intersección corresponde a la calle 1 y la B a la 2, o vice versa. Yields: ElasticsearchSearch: Búsqueda que debe ser ejecutada por el invocador de 'next()'. """ if self._locality: found = yield from self._expand_locality_search() if not found: return # Buscar la primera calle, incluyendo la altura si está presente result = yield self._build_street_blocks_search( self._address_data.street_names[0], add_number=True, force_all=True ) street_1_ids, street_1_points = self._read_street_blocks_1_results( result) if not street_1_ids: # Ninguno de los resultados pudo ser utilizado para # calcular la ubicación, o no se encontraron resultados. # Devolver 0 resultados de intersección. return result = yield self._build_street_blocks_search( self._address_data.street_names[1], force_all=True ) # Resultados de la segunda calle street_2_ids = {hit[N.STREET][N.ID] for hit in result.hits} if not street_2_ids: return # Buscar intersecciones que tengan nuestras dos calles en cualquier # orden ("Calle 1 y Calle 2" o "Calle 2 y Calle 1"). Si tenemos altura, # comprobar que las intersecciones no estén a mas de X metros de cada # ubicación sobre la calle 1 que calculamos anteriormente. self._intersections_result = yield self._build_intersections_search( street_1_ids, street_2_ids, street_1_points.values(), constants.ISCT_DOOR_NUM_TOLERANCE_M ) # Iterar sobre los resultados, fijándose si cada intersección tiene la # calle 1 del lado A o B. Si la calle 1 está del lado B, invertir la # intersección. Se requiere que los datos devueltos al usuario tengan # el mismo orden en el que fueron recibidos ("calle X y calle Y" no es # lo mismo que "calle Y y calle X"). intersections = [] for intersection in self._intersections_result.hits: id_a = intersection[N.STREET_A][N.ID] id_b = intersection[N.STREET_B][N.ID] if id_a in street_1_ids and id_b in street_2_ids: street_1 = intersection[N.STREET_A] street_2 = intersection[N.STREET_B] elif id_a in street_2_ids and id_b in street_1_ids: street_1 = intersection[N.STREET_B] street_2 = intersection[N.STREET_A] else: raise RuntimeError( 'Unknown street IDs for intersection {} - {}'.format(id_a, id_b)) if street_1[N.ID] in street_1_points: # Como tenemos altura, usamos la posición sobre la calle 1 en # lugar de la posición de la intersección. point = street_1_points[street_1[N.ID]] else: point = Point.from_geojson_point(intersection[N.GEOM]) intersections.append((street_1, street_2, point)) self._intersection_hits = self._build_intersection_hits(intersections)
def test_distance_0(self): """La distancia entre dos puntos iguales debería ser 0.""" point = Point(0, 0) self.assertAlmostEqual(point.approximate_distance_meters(point), 0)
from service.geometry import Point from . import GeorefMockTest POINT_1 = Point(-58.381614, -34.603713) # Obelisco POINT_2 = Point(-58.373691, -34.608874) # Cabildo POINT_3 = Point(-58.373720, -34.592172) # Torre Monumental - Retiro DISTANCES = [(POINT_1, POINT_2, 920), (POINT_1, POINT_3, 1470), (POINT_2, POINT_3, 1855)] class PointTest(GeorefMockTest): def test_distance_0(self): """La distancia entre dos puntos iguales debería ser 0.""" point = Point(0, 0) self.assertAlmostEqual(point.approximate_distance_meters(point), 0) def test_distances(self): """La distancia entre dos puntos conocidos debe ser correcta.""" for p1, p2, distance in DISTANCES: calculated = p1.approximate_distance_meters(p2) self.assertAlmostEqual(distance, calculated, delta=5) def test_midpoint(self): """El punto medio entre dos puntos debe ser correcto.""" p1 = Point(0, 0) p2 = Point(10, 10) midpoint = p1.midpoint(p2) self.assertAlmostEqual(midpoint.lat, 5) self.assertAlmostEqual(midpoint.lon, 5)