def _relations_sync(moved_sensor_ids: typing.List[int]): logger = get_celery_logger() trie: Trie[Zipcode] = Trie() for zipcode in Zipcode.query.all(): trie.insert(zipcode.geohash, zipcode) new_relations = [] # Delete the old relations before rebuilding them deleted_relations_count = SensorZipcodeRelation.query.filter( SensorZipcodeRelation.sensor_id.in_(moved_sensor_ids)).delete( synchronize_session=False) logger.info("Deleting %s relations", deleted_relations_count) sensors = Sensor.query.filter(Sensor.id.in_(moved_sensor_ids)).all() for sensor in sensors: gh = sensor.geohash latitude = sensor.latitude longitude = sensor.longitude done = False zipcode_ids: typing.Set[int] = set() # TODO: Use Postgres' native geolocation extension. while gh and not done: zipcodes = [ zipcode for zipcode in trie.get(gh) if zipcode.id not in zipcode_ids ] for zipcode_id, distance in sorted( [( z.id, haversine_distance(longitude, latitude, z.longitude, z.latitude), ) for z in zipcodes], key=lambda t: t[1], ): if distance >= 25: done = True break if len(zipcode_ids) >= 25: done = True break zipcode_ids.add(zipcode_id) data = { "zipcode_id": zipcode_id, "sensor_id": sensor.id, "distance": distance, } new_relations.append(SensorZipcodeRelation(**data)) gh = gh[:-1] if new_relations: logger.info("Creating %s relations", len(new_relations)) for objs in chunk_list(new_relations): db.session.bulk_save_objects(objs) db.session.commit()
def distance(self, other: "Zipcode") -> float: """Distance between this zip and the given zip.""" if other.id in self._distance_cache: return self._distance_cache[other.id] if self.id in other._distance_cache: return other._distance_cache[self.id] self._distance_cache[other.id] = haversine_distance( other.longitude, other.latitude, self.longitude, self.latitude, ) return self._distance_cache[other.id]
def _is_in_range(lat: float, lon: float): return haversine_distance(lon, lat, COORDINATES[1], COORDINATES[0]) < RADIUS