Exemple #1
0
def to_geojson(regions):  # pragma: no cover
    features = []
    for code, region in regions.items():
        # calculate the maximum radius of each polygon
        boundary = region.boundary
        if isinstance(boundary, shapely.geometry.base.BaseMultipartGeometry):
            boundaries = list(boundary.geoms)
        else:
            boundaries = [boundary]

        radii = []
        for boundary in boundaries:
            # flip x/y aka lon/lat to lat/lon
            boundary = numpy.fliplr(numpy.array(boundary))
            ctr_lat, ctr_lon = geocalc.centroid(boundary)
            radii.append(geocalc.max_distance(ctr_lat, ctr_lon, boundary))
        radius = round(max(radii) / 1000.0) * 1000.0

        features.append({
            'type': 'Feature',
            'properties': {
                'alpha2': code,
                'radius': radius,
            },
            'geometry': shapely.geometry.mapping(region),
        })

    def _sort(value):
        return value['properties']['alpha2']

    features = sorted(features, key=_sort)
    lines = ',\n'.join([json.dumps(f, sort_keys=True, separators=(',', ':'))
                        for f in features])
    return '{"type": "FeatureCollection", "features": [\n' + lines + '\n]}\n'
Exemple #2
0
def to_geojson(regions):  # pragma: no cover
    features = []
    for code, region in regions.items():
        # calculate the maximum radius of each polygon
        boundary = region.boundary
        if isinstance(boundary, shapely.geometry.base.BaseMultipartGeometry):
            boundaries = list(boundary.geoms)
        else:
            boundaries = [boundary]

        radii = []
        for boundary in boundaries:
            # flip x/y aka lon/lat to lat/lon
            boundary = numpy.fliplr(numpy.array(boundary))
            ctr_lat, ctr_lon = geocalc.centroid(boundary)
            radii.append(geocalc.max_distance(ctr_lat, ctr_lon, boundary))
        radius = round(max(radii) / 1000.0) * 1000.0

        features.append({
            'type': 'Feature',
            'properties': {
                'alpha2': code,
                'radius': radius,
            },
            'geometry': shapely.geometry.mapping(region),
        })

    def _sort(value):
        return value['properties']['alpha2']

    features = sorted(features, key=_sort)
    lines = ',\n'.join([
        json.dumps(f, sort_keys=True, separators=(',', ':')) for f in features
    ])
    return '{"type": "FeatureCollection", "features": [\n' + lines + '\n]}\n'
Exemple #3
0
    def update(self, area_key):
        # Select all cells in this area and derive a bounding box for them
        cell_query = (self.cell_model.querykey(self.session, area_key)
                                     .filter(self.cell_model.lat.isnot(None))
                                     .filter(self.cell_model.lon.isnot(None)))
        cells = cell_query.all()

        area_query = self.cell_area_model.querykey(self.session, area_key)
        if len(cells) == 0:
            # If there are no more underlying cells, delete the area entry
            area_query.delete()
        else:
            # Otherwise update the area entry based on all the cells
            area = area_query.first()

            points = [(c.lat, c.lon) for c in cells]
            min_lat = min([c.min_lat for c in cells])
            min_lon = min([c.min_lon for c in cells])
            max_lat = max([c.max_lat for c in cells])
            max_lon = max([c.max_lon for c in cells])

            bbox_points = [(min_lat, min_lon),
                           (min_lat, max_lon),
                           (max_lat, min_lon),
                           (max_lat, max_lon)]

            ctr = centroid(points)
            rng = range_to_points(ctr, bbox_points)

            # Switch units back to meters
            ctr_lat = ctr[0]
            ctr_lon = ctr[1]
            rng = int(round(rng * 1000.0))

            # Now create or update the area
            num_cells = len(cells)
            avg_cell_range = int(sum(
                [cell.range for cell in cells]) / float(num_cells))
            if area is None:
                area = self.cell_area_model(
                    created=self.utcnow,
                    modified=self.utcnow,
                    lat=ctr_lat,
                    lon=ctr_lon,
                    range=rng,
                    avg_cell_range=avg_cell_range,
                    num_cells=num_cells,
                    **area_key.__dict__)
                self.session.add(area)
            else:
                area.modified = self.utcnow
                area.lat = ctr_lat
                area.lon = ctr_lon
                area.range = rng
                area.avg_cell_range = avg_cell_range
                area.num_cells = num_cells
Exemple #4
0
def update_lac(self, radio, mcc, mnc, lac):

    with self.db_session() as session:

        # Select all the cells in this LAC that aren't the virtual
        # cell itself, and derive a bounding box for them.

        q = session.query(Cell).filter(Cell.radio == radio).filter(
            Cell.mcc == mcc).filter(Cell.mnc == mnc).filter(
                Cell.lac == lac).filter(Cell.cid != CELLID_LAC)

        cells = q.all()
        points = [(to_degrees(c.lat), to_degrees(c.lon)) for c in cells]
        min_lat = to_degrees(min([c.min_lat for c in cells]))
        min_lon = to_degrees(min([c.min_lon for c in cells]))
        max_lat = to_degrees(max([c.max_lat for c in cells]))
        max_lon = to_degrees(max([c.max_lon for c in cells]))

        bbox_points = [(min_lat, min_lon), (min_lat, max_lon),
                       (max_lat, min_lon), (max_lat, max_lon)]

        ctr = centroid(points)
        rng = range_to_points(ctr, bbox_points)

        # switch units back to DB preferred centimicrodegres angle
        # and meters distance.
        ctr_lat = from_degrees(ctr[0])
        ctr_lon = from_degrees(ctr[1])
        rng = int(round(rng * 1000.0))

        # Now create or update the LAC virtual cell

        q = session.query(Cell).filter(Cell.radio == radio).filter(
            Cell.mcc == mcc).filter(Cell.mnc == mnc).filter(
                Cell.lac == lac).filter(Cell.cid == CELLID_LAC)

        lac = q.first()

        if lac is None:
            lac = Cell(radio=radio,
                       mcc=mcc,
                       mnc=mnc,
                       lac=lac,
                       cid=CELLID_LAC,
                       lat=ctr_lat,
                       lon=ctr_lon,
                       range=rng)
        else:
            lac.new_measures = 0
            lac.lat = ctr_lat
            lac.lon = ctr_lon
            lac.range = rng

        session.commit()
Exemple #5
0
    def update(self, area_key):
        # Select all cells in this area and derive a bounding box for them
        cell_query = (self.cell_model.querykey(self.session, area_key).filter(
            self.cell_model.lat.isnot(None)).filter(
                self.cell_model.lon.isnot(None)))
        cells = cell_query.all()

        area_query = self.cell_area_model.querykey(self.session, area_key)
        if len(cells) == 0:
            # If there are no more underlying cells, delete the area entry
            area_query.delete()
        else:
            # Otherwise update the area entry based on all the cells
            area = area_query.first()

            points = [(c.lat, c.lon) for c in cells]
            min_lat = min([c.min_lat for c in cells])
            min_lon = min([c.min_lon for c in cells])
            max_lat = max([c.max_lat for c in cells])
            max_lon = max([c.max_lon for c in cells])

            bbox_points = [(min_lat, min_lon), (min_lat, max_lon),
                           (max_lat, min_lon), (max_lat, max_lon)]

            ctr = centroid(points)
            rng = range_to_points(ctr, bbox_points)

            # Switch units back to meters
            ctr_lat = ctr[0]
            ctr_lon = ctr[1]
            rng = int(round(rng * 1000.0))

            # Now create or update the area
            num_cells = len(cells)
            avg_cell_range = int(
                sum([cell.range for cell in cells]) / float(num_cells))
            if area is None:
                area = self.cell_area_model(created=self.utcnow,
                                            modified=self.utcnow,
                                            lat=ctr_lat,
                                            lon=ctr_lon,
                                            range=rng,
                                            avg_cell_range=avg_cell_range,
                                            num_cells=num_cells,
                                            **area_key.__dict__)
                self.session.add(area)
            else:
                area.modified = self.utcnow
                area.lat = ctr_lat
                area.lon = ctr_lon
                area.range = rng
                area.avg_cell_range = avg_cell_range
                area.num_cells = num_cells
Exemple #6
0
    def station_values(self, station_key, shard_station, observations):
        """
        Return two-tuple of status, value dict where status is one of:
        `new`, `new_moving`, `moving`, `changed`.
        """
        # cases:
        # we always get a station key and observations
        # 0. observations disagree
        # 0.a. no shard station, return new_moving
        # 0.b. shard station, return moving
        # 1. no shard station
        # 1.a. obs agree -> return new
        # 2. shard station
        # 2.a. obs disagree -> return moving
        # 2.b. obs agree -> return changed
        created = self.utcnow
        values = self._base_station_values(station_key, observations)

        obs_length = len(observations)
        obs_positions = numpy.array([(obs.lat, obs.lon)
                                     for obs in observations],
                                    dtype=numpy.double)
        obs_new_lat, obs_new_lon = centroid(obs_positions)
        obs_max_lat, obs_max_lon = numpy.nanmax(obs_positions, axis=0)
        obs_min_lat, obs_min_lon = numpy.nanmin(obs_positions, axis=0)
        obs_box_dist = distance(obs_min_lat, obs_min_lon, obs_max_lat,
                                obs_max_lon)

        if obs_box_dist > self.max_dist_meters:
            # the new observations are already too far apart
            if not shard_station:
                values.update({
                    'created': created,
                    'block_first': self.today,
                    'block_last': self.today,
                    'block_count': 1,
                })
                return ('new_moving', values)
            else:
                block_count = shard_station.block_count or 0
                values.update({
                    'lat':
                    None,
                    'lon':
                    None,
                    'max_lat':
                    None,
                    'min_lat':
                    None,
                    'max_lon':
                    None,
                    'min_lon':
                    None,
                    'radius':
                    None,
                    'region':
                    shard_station.region,
                    'samples':
                    None,
                    'source':
                    None,
                    'block_first':
                    shard_station.block_first or self.today,
                    'block_last':
                    self.today,
                    'block_count':
                    block_count + 1,
                })
                return ('moving', values)

        if shard_station is None:
            # totally new station, only agreeing observations
            radius = circle_radius(obs_new_lat, obs_new_lon, obs_max_lat,
                                   obs_max_lon, obs_min_lat, obs_min_lon)
            values.update({
                'created': created,
                'lat': obs_new_lat,
                'lon': obs_new_lon,
                'max_lat': float(obs_max_lat),
                'min_lat': float(obs_min_lat),
                'max_lon': float(obs_max_lon),
                'min_lon': float(obs_min_lon),
                'radius': radius,
                'region': GEOCODER.region(obs_new_lat, obs_new_lon),
                'samples': obs_length,
                'source': None,
            })
            return ('new', values)
        else:
            # shard_station + new observations
            positions = numpy.append(obs_positions, [
                (numpy.nan if shard_station.lat is None else shard_station.lat,
                 numpy.nan
                 if shard_station.lon is None else shard_station.lon),
                (numpy.nan if shard_station.max_lat is None else
                 shard_station.max_lat, numpy.nan
                 if shard_station.max_lon is None else shard_station.max_lon),
                (numpy.nan if shard_station.min_lat is None else
                 shard_station.min_lat, numpy.nan
                 if shard_station.min_lon is None else shard_station.min_lon),
            ],
                                     axis=0)
            max_lat, max_lon = numpy.nanmax(positions, axis=0)
            min_lat, min_lon = numpy.nanmin(positions, axis=0)
            box_dist = distance(min_lat, min_lon, max_lat, max_lon)
            if box_dist > self.max_dist_meters:
                # shard_station + disagreeing observations
                block_count = shard_station.block_count or 0
                values.update({
                    'lat':
                    None,
                    'lon':
                    None,
                    'max_lat':
                    None,
                    'min_lat':
                    None,
                    'max_lon':
                    None,
                    'min_lon':
                    None,
                    'radius':
                    None,
                    'region':
                    shard_station.region,
                    'samples':
                    None,
                    'source':
                    None,
                    'block_first':
                    shard_station.block_first or self.today,
                    'block_last':
                    self.today,
                    'block_count':
                    block_count + 1,
                })
                return ('moving', values)
            else:
                # shard_station + agreeing observations
                if shard_station.lat is None or shard_station.lon is None:
                    old_weight = 0
                else:
                    old_weight = min((shard_station.samples or 0),
                                     self.MAX_OLD_OBSERVATIONS)
                new_lat = ((obs_new_lat * obs_length +
                            (shard_station.lat or 0.0) * old_weight) /
                           (obs_length + old_weight))
                new_lon = ((obs_new_lon * obs_length +
                            (shard_station.lon or 0.0) * old_weight) /
                           (obs_length + old_weight))
                samples = (shard_station.samples or 0) + obs_length
                radius = circle_radius(new_lat, new_lon, max_lat, max_lon,
                                       min_lat, min_lon)
                region = shard_station.region
                if (region
                        and not GEOCODER.in_region(new_lat, new_lon, region)):
                    # reset region if it no longer matches
                    region = None
                if not region:
                    region = GEOCODER.region(new_lat, new_lon)
                values.update({
                    'lat': new_lat,
                    'lon': new_lon,
                    'max_lat': float(max_lat),
                    'min_lat': float(min_lat),
                    'max_lon': float(max_lon),
                    'min_lon': float(min_lon),
                    'radius': radius,
                    'region': region,
                    'samples': samples,
                    'source': None,
                    # use the exact same keys as in the moving case
                    'block_first': shard_station.block_first,
                    'block_last': shard_station.block_last,
                    'block_count': shard_station.block_count,
                })
                return ('changed', values)

        return (None, None)  # pragma: no cover
Exemple #7
0
def update_lac(self, radio, mcc, mnc, lac):

    with self.db_session() as session:

        # Select all the cells in this LAC that aren't the virtual
        # cell itself, and derive a bounding box for them.

        q = session.query(Cell).filter(
            Cell.radio == radio).filter(
            Cell.mcc == mcc).filter(
            Cell.mnc == mnc).filter(
            Cell.lac == lac).filter(
            Cell.cid != CELLID_LAC).filter(
            Cell.new_measures == 0).filter(
            Cell.lat.isnot(None)).filter(
            Cell.lon.isnot(None))

        cells = q.all()
        if len(cells) == 0:
            return

        points = [(to_degrees(c.lat),
                   to_degrees(c.lon)) for c in cells]
        min_lat = to_degrees(min([c.min_lat for c in cells]))
        min_lon = to_degrees(min([c.min_lon for c in cells]))
        max_lat = to_degrees(max([c.max_lat for c in cells]))
        max_lon = to_degrees(max([c.max_lon for c in cells]))

        bbox_points = [(min_lat, min_lon),
                       (min_lat, max_lon),
                       (max_lat, min_lon),
                       (max_lat, max_lon)]

        ctr = centroid(points)
        rng = range_to_points(ctr, bbox_points)

        # switch units back to DB preferred centimicrodegres angle
        # and meters distance.
        ctr_lat = from_degrees(ctr[0])
        ctr_lon = from_degrees(ctr[1])
        rng = int(round(rng * 1000.0))

        # Now create or update the LAC virtual cell

        q = session.query(Cell).filter(
            Cell.radio == radio).filter(
            Cell.mcc == mcc).filter(
            Cell.mnc == mnc).filter(
            Cell.lac == lac).filter(
            Cell.cid == CELLID_LAC)

        lac = q.first()

        if lac is None:
            lac = Cell(radio=radio,
                       mcc=mcc,
                       mnc=mnc,
                       lac=lac,
                       cid=CELLID_LAC,
                       lat=ctr_lat,
                       lon=ctr_lon,
                       range=rng)
        else:
            lac.new_measures = 0
            lac.lat = ctr_lat
            lac.lon = ctr_lon
            lac.range = rng

        session.commit()
Exemple #8
0
    def update_area(self, areaid):
        # Select all cells in this area and derive a bounding box for them
        radio, mcc, mnc, lac = decode_cellarea(areaid)
        load_fields = ('lat', 'lon', 'radius', 'region',
                       'max_lat', 'max_lon', 'min_lat', 'min_lon')

        shard = self.cell_model.shard_model(radio)
        cells = (self.session.query(shard)
                             .options(load_only(*load_fields))
                             .filter(shard.radio == radio)
                             .filter(shard.mcc == mcc)
                             .filter(shard.mnc == mnc)
                             .filter(shard.lac == lac)
                             .filter(shard.lat.isnot(None))
                             .filter(shard.lon.isnot(None))).all()

        area_query = (self.session.query(self.area_model)
                                  .filter(self.area_model.areaid == areaid))

        if len(cells) == 0:
            # If there are no more underlying cells, delete the area entry
            area_query.delete()
        else:
            # Otherwise update the area entry based on all the cells
            area = area_query.first()

            cell_extremes = numpy.array([
                (numpy.nan if cell.max_lat is None else cell.max_lat,
                 numpy.nan if cell.max_lon is None else cell.max_lon)
                for cell in cells] + [
                (numpy.nan if cell.min_lat is None else cell.min_lat,
                 numpy.nan if cell.min_lon is None else cell.min_lon)
                for cell in cells
            ], dtype=numpy.double)

            max_lat, max_lon = numpy.nanmax(cell_extremes, axis=0)
            min_lat, min_lon = numpy.nanmin(cell_extremes, axis=0)

            ctr_lat, ctr_lon = centroid(
                numpy.array([(c.lat, c.lon) for c in cells],
                            dtype=numpy.double))
            radius = circle_radius(
                ctr_lat, ctr_lon, max_lat, max_lon, min_lat, min_lon)

            cell_radii = numpy.array([
                (numpy.nan if cell.radius is None else cell.radius)
                for cell in cells
            ], dtype=numpy.int32)
            avg_cell_radius = int(round(numpy.nanmean(cell_radii)))
            num_cells = len(cells)
            region = self.region(ctr_lat, ctr_lon, mcc, cells)

            if area is None:
                stmt = self.area_model.__table__.insert(
                    mysql_on_duplicate='num_cells = num_cells'  # no-op
                ).values(
                    areaid=areaid,
                    radio=radio,
                    mcc=mcc,
                    mnc=mnc,
                    lac=lac,
                    created=self.utcnow,
                    modified=self.utcnow,
                    lat=ctr_lat,
                    lon=ctr_lon,
                    radius=radius,
                    region=region,
                    avg_cell_radius=avg_cell_radius,
                    num_cells=num_cells,
                )
                self.session.execute(stmt)
            else:
                area.modified = self.utcnow
                area.lat = ctr_lat
                area.lon = ctr_lon
                area.radius = radius
                area.region = region
                area.avg_cell_radius = avg_cell_radius
                area.num_cells = num_cells
Exemple #9
0
    def station_values(self, station_key, shard_station, observations):
        """
        Return two-tuple of status, value dict where status is one of:
        `new`, `new_moving`, `moving`, `changed`.
        """
        # cases:
        # we always get a station key and observations
        # 0. observations disagree
        # 0.a. no shard station, return new_moving
        # 0.b. shard station, return moving
        # 1. no shard station
        # 1.a. obs agree -> return new
        # 2. shard station
        # 2.a. obs disagree -> return moving
        # 2.b. obs agree -> return changed
        created = self.utcnow
        values = {
            'mac': station_key,
            'modified': self.utcnow,
        }

        obs_length = len(observations)
        obs_positions = numpy.array(
            [(obs.lat, obs.lon) for obs in observations],
            dtype=numpy.double)
        obs_new_lat, obs_new_lon = centroid(obs_positions)
        obs_max_lat, obs_max_lon = numpy.nanmax(obs_positions, axis=0)
        obs_min_lat, obs_min_lon = numpy.nanmin(obs_positions, axis=0)
        obs_box_dist = distance(obs_min_lat, obs_min_lon,
                                obs_max_lat, obs_max_lon)

        if obs_box_dist > self.max_dist_meters:
            # the new observations are already too far apart
            if not shard_station:
                values.update({
                    'created': created,
                    'block_first': self.today,
                    'block_last': self.today,
                    'block_count': 1,
                })
                return ('new_moving', values)
            else:
                block_count = shard_station.block_count or 0
                values.update({
                    'lat': None,
                    'lon': None,
                    'max_lat': None,
                    'min_lat': None,
                    'max_lon': None,
                    'min_lon': None,
                    'country': shard_station.country,
                    'radius': None,
                    'samples': None,
                    'source': None,
                    'block_last': self.today,
                    'block_count': block_count + 1,
                })
                return ('moving', values)

        if shard_station is None:
            # totally new station, only agreeing observations
            radius = circle_radius(
                obs_new_lat, obs_new_lon,
                obs_max_lat, obs_max_lon, obs_min_lat, obs_min_lon)
            values.update({
                'created': created,
                'lat': obs_new_lat,
                'lon': obs_new_lon,
                'max_lat': float(obs_max_lat),
                'min_lat': float(obs_min_lat),
                'max_lon': float(obs_max_lon),
                'min_lon': float(obs_min_lon),
                'country': country_for_location(obs_new_lat, obs_new_lon),
                'radius': radius,
                'samples': obs_length,
                'source': None,
            })
            return ('new', values)
        else:
            # shard_station + new observations
            positions = numpy.append(obs_positions, [
                (numpy.nan if shard_station.lat is None
                    else shard_station.lat,
                 numpy.nan if shard_station.lon is None
                    else shard_station.lon),
                (numpy.nan if shard_station.max_lat is None
                    else shard_station.max_lat,
                 numpy.nan if shard_station.max_lon is None
                    else shard_station.max_lon),
                (numpy.nan if shard_station.min_lat is None
                    else shard_station.min_lat,
                 numpy.nan if shard_station.min_lon is None
                    else shard_station.min_lon),
            ], axis=0)
            max_lat, max_lon = numpy.nanmax(positions, axis=0)
            min_lat, min_lon = numpy.nanmin(positions, axis=0)
            box_dist = distance(min_lat, min_lon, max_lat, max_lon)
            if box_dist > self.max_dist_meters:
                # shard_station + disagreeing observations
                block_count = shard_station.block_count or 0
                values.update({
                    'lat': None,
                    'lon': None,
                    'max_lat': None,
                    'min_lat': None,
                    'max_lon': None,
                    'min_lon': None,
                    'country': shard_station.country,
                    'radius': None,
                    'samples': None,
                    'source': None,
                    'block_last': self.today,
                    'block_count': block_count + 1,
                })
                return ('moving', values)
            else:
                # shard_station + agreeing observations
                if shard_station.lat is None or shard_station.lon is None:
                    old_weight = 0
                else:
                    old_weight = min((shard_station.samples or 0),
                                     self.MAX_OLD_OBSERVATIONS)
                new_lat = ((obs_new_lat * obs_length +
                            (shard_station.lat or 0.0) * old_weight) /
                           (obs_length + old_weight))
                new_lon = ((obs_new_lon * obs_length +
                            (shard_station.lon or 0.0) * old_weight) /
                           (obs_length + old_weight))
                samples = (shard_station.samples or 0) + obs_length
                radius = circle_radius(
                    new_lat, new_lon, max_lat, max_lon, min_lat, min_lon)
                country = shard_station.country
                if (country and not country_matches_location(
                        new_lat, new_lon, country)):
                    # reset country if it no longer matches
                    country = None
                if not country:
                    country = country_for_location(new_lat, new_lon)
                values.update({
                    'lat': new_lat,
                    'lon': new_lon,
                    'max_lat': float(max_lat),
                    'min_lat': float(min_lat),
                    'max_lon': float(max_lon),
                    'min_lon': float(min_lon),
                    'country': country,
                    'radius': radius,
                    'samples': samples,
                    'source': None,
                    # use the exact same keys as in the moving case
                    'block_last': shard_station.block_last,
                    'block_count': shard_station.block_count,
                })
                return ('changed', values)

        return (None, None)  # pragma: no cover
Exemple #10
0
    def new_station_values(self, station, station_key,
                           first_blocked, observations):
        # This function returns a 3-tuple, the first element is True,
        # if the station was found to be moving.
        # The second element is either None or a dict of values,
        # if the station is new and should result in a table insert
        # The third element is either None or a dict of values
        # if the station did exist and should be updated

        obs_length = len(observations)
        obs_positions = numpy.array(
            [(obs.lat, obs.lon) for obs in observations],
            dtype=numpy.double)
        obs_lat, obs_lon = centroid(obs_positions)

        values = {
            'modified': self.utcnow,
        }
        values.update(station_key.__dict__)
        if self.station_type == 'cell':
            # pass on extra psc column which is not actually part
            # of the stations hash key
            values['psc'] = observations[-1].psc

        created = self.utcnow
        if station is None:
            if first_blocked:
                # if the station did previously exist, retain at least the
                # time it was first put on a blocklist as the creation date
                created = first_blocked
            values.update({
                'created': created,
                'range': 0,
                'total_measures': 0,
            })

        if (station is not None and
                station.lat is not None and station.lon is not None):
            obs_positions = numpy.append(obs_positions, [
                (station.lat, station.lon),
                (numpy.nan if station.max_lat is None else station.max_lat,
                 numpy.nan if station.max_lon is None else station.max_lon),
                (numpy.nan if station.min_lat is None else station.min_lat,
                 numpy.nan if station.min_lon is None else station.min_lon),
            ], axis=0)
            existing_station = True
        else:
            values['lat'] = obs_lat
            values['lon'] = obs_lon
            existing_station = False

        max_lat, max_lon = numpy.nanmax(obs_positions, axis=0)
        min_lat, min_lon = numpy.nanmin(obs_positions, axis=0)

        # calculate sphere-distance from opposite corners of
        # bounding box containing current location estimate
        # and new observations; if too big, station is moving
        box_dist = distance(min_lat, min_lon, max_lat, max_lon)

        # TODO: If we get a too large box_dist, we should not create
        # a new station record with the impossibly big distance,
        # so moving the box_dist > self.max_dist_meters here

        if existing_station:
            if box_dist > self.max_dist_meters:
                # Signal a moving station and return early without updating
                # the station since it will be deleted by caller momentarily
                return (True, None, None)
            # limit the maximum weight of the old station estimate
            old_weight = min(station.total_measures,
                             self.MAX_OLD_OBSERVATIONS)
            new_weight = old_weight + obs_length

            values['lat'] = ((station.lat * old_weight) +
                             (obs_lat * obs_length)) / new_weight
            values['lon'] = ((station.lon * old_weight) +
                             (obs_lon * obs_length)) / new_weight

        # increase total counter
        if station is not None:
            values['total_measures'] = station.total_measures + obs_length
        else:
            values['total_measures'] = obs_length

        # update max/min lat/lon columns
        values['min_lat'] = float(min_lat)
        values['min_lon'] = float(min_lon)
        values['max_lat'] = float(max_lat)
        values['max_lon'] = float(max_lon)

        # give radio-range estimate between extreme values and centroid
        values['range'] = circle_radius(
            values['lat'], values['lon'],
            max_lat, max_lon, min_lat, min_lon)

        if station is None:
            # return new values
            return (False, values, None)
        else:
            # return updated values, remove station from session
            self.session.expunge(station)
            return (False, None, values)
Exemple #11
0
    def update_area(self, area_key):
        # Select all cells in this area and derive a bounding box for them
        cell_query = (self.cell_model.querykey(self.session, area_key)
                                     .filter(self.cell_model.lat.isnot(None))
                                     .filter(self.cell_model.lon.isnot(None)))
        cells = cell_query.all()

        area_query = self.area_model.querykey(self.session, area_key)
        if len(cells) == 0:
            # If there are no more underlying cells, delete the area entry
            area_query.delete()
        else:
            # Otherwise update the area entry based on all the cells
            area = area_query.first()

            cell_extremes = numpy.array([
                (numpy.nan if cell.max_lat is None else cell.max_lat,
                 numpy.nan if cell.max_lon is None else cell.max_lon)
                for cell in cells] + [
                (numpy.nan if cell.min_lat is None else cell.min_lat,
                 numpy.nan if cell.min_lon is None else cell.min_lon)
                for cell in cells
            ], dtype=numpy.double)

            max_lat, max_lon = numpy.nanmax(cell_extremes, axis=0)
            min_lat, min_lon = numpy.nanmin(cell_extremes, axis=0)

            ctr_lat, ctr_lon = centroid(
                numpy.array([(c.lat, c.lon) for c in cells],
                            dtype=numpy.double))
            radius = circle_radius(
                ctr_lat, ctr_lon,
                max_lat, max_lon, min_lat, min_lon)

            # Now create or update the area
            cell_ranges = numpy.array([
                (numpy.nan if cell.range is None else cell.range)
                for cell in cells
            ], dtype=numpy.int32)
            avg_cell_range = int(round(numpy.nanmean(cell_ranges)))
            num_cells = len(cells)

            if area is None:
                stmt = self.area_model.__table__.insert(
                    mysql_on_duplicate='num_cells = num_cells'  # no-op
                ).values(
                    created=self.utcnow,
                    modified=self.utcnow,
                    lat=ctr_lat,
                    lon=ctr_lon,
                    range=radius,
                    avg_cell_range=avg_cell_range,
                    num_cells=num_cells,
                    **area_key.__dict__
                )
                self.session.execute(stmt)
            else:
                area.modified = self.utcnow
                area.lat = ctr_lat
                area.lon = ctr_lon
                area.range = radius
                area.avg_cell_range = avg_cell_range
                area.num_cells = num_cells
Exemple #12
0
def to_geojson(regions):  # pragma: no cover
    features = []
    features_buffered = []

    for code, region in regions.items():
        # calculate the maximum radius of each polygon
        boundary = region.boundary
        if isinstance(boundary, shapely.geometry.base.BaseMultipartGeometry):
            boundaries = list(boundary.geoms)
        else:
            boundaries = [boundary]

        radii = []
        for boundary in boundaries:
            # flip x/y aka lon/lat to lat/lon
            boundary = numpy.fliplr(numpy.array(boundary))
            ctr_lat, ctr_lon = geocalc.centroid(boundary)
            radii.append(geocalc.max_distance(ctr_lat, ctr_lon, boundary))
        radius = round(max(radii) / 1000.0) * 1000.0

        features.append({
            'type': 'Feature',
            'properties': {
                'alpha2': code,
                'radius': radius,
            },
            'geometry': shapely.geometry.mapping(region),
        })

        # Build up region buffers, to create shapes that include all of
        # the coastal areas and boundaries of the regions and anywhere
        # a cell signal could still be recorded. The value is in decimal
        # degrees (1.0 == ~100km) but calculations don't take projection
        # / WSG84 into account.
        # After buffering remove any parts that crosses the -180.0/+180.0
        # longitude boundary to the east or west.
        buffered = (region.buffer(0.5)
                          .simplify(0.02)
                          .difference(DATELINE_EAST)
                          .difference(DATELINE_WEST))

        features_buffered.append({
            'type': 'Feature',
            'properties': {
                'alpha2': code,
            },
            'geometry': shapely.geometry.mapping(buffered),
        })

    def _sort(value):
        return value['properties']['alpha2']

    def _to_collection(features):
        # put each region on a single line to get a diffable output
        lines = ',\n'.join([json.dumps(feature,
                                       sort_keys=True,
                                       separators=(',', ':'))
                            for feature in sorted(features, key=_sort)])

        return ('{"type": "FeatureCollection", '
                '"features": [\n' + lines + '\n]}\n')

    return (_to_collection(features), _to_collection(features_buffered))
Exemple #13
0
def update_lac(self, radio, mcc, mnc, lac):
    try:
        utcnow = util.utcnow()
        with self.db_session() as session:
            # Select all the cells in this LAC that aren't the virtual
            # cell itself, and derive a bounding box for them.

            cell_query = session.query(Cell).filter(
                Cell.radio == radio).filter(
                Cell.mcc == mcc).filter(
                Cell.mnc == mnc).filter(
                Cell.lac == lac).filter(
                Cell.cid != CELLID_LAC).filter(
                Cell.lat.isnot(None)).filter(
                Cell.lon.isnot(None))

            cells = cell_query.all()

            lac_query = session.query(Cell).filter(
                Cell.radio == radio).filter(
                Cell.mcc == mcc).filter(
                Cell.mnc == mnc).filter(
                Cell.lac == lac).filter(
                Cell.cid == CELLID_LAC)

            if len(cells) == 0:
                # If there are no more underlying cells, delete the lac entry
                lac_query.delete()
            else:
                # Otherwise update the lac entry based on all the cells
                lac_obj = lac_query.first()

                points = [(c.lat, c.lon) for c in cells]
                min_lat = min([c.min_lat for c in cells])
                min_lon = min([c.min_lon for c in cells])
                max_lat = max([c.max_lat for c in cells])
                max_lon = max([c.max_lon for c in cells])

                bbox_points = [(min_lat, min_lon),
                               (min_lat, max_lon),
                               (max_lat, min_lon),
                               (max_lat, max_lon)]

                ctr = centroid(points)
                rng = range_to_points(ctr, bbox_points)

                # Switch units back to DB preferred centimicrodegres angle
                # and meters distance.
                ctr_lat = ctr[0]
                ctr_lon = ctr[1]
                rng = int(round(rng * 1000.0))

                # Now create or update the LAC virtual cell
                if lac_obj is None:
                    lac_obj = Cell(radio=radio,
                                   mcc=mcc,
                                   mnc=mnc,
                                   lac=lac,
                                   cid=CELLID_LAC,
                                   lat=ctr_lat,
                                   lon=ctr_lon,
                                   created=utcnow,
                                   modified=utcnow,
                                   range=rng,
                                   total_measures=len(cells))
                    session.add(lac_obj)
                else:
                    lac_obj.total_measures = len(cells)
                    lac_obj.lat = ctr_lat
                    lac_obj.lon = ctr_lon
                    lac_obj.modified = utcnow
                    lac_obj.range = rng

            session.commit()
    except Exception as exc:  # pragma: no cover
        self.heka_client.raven('error')
        raise self.retry(exc=exc)
Exemple #14
0
def update_lac(self,
               radio,
               mcc,
               mnc,
               lac,
               cell_model_key='cell',
               cell_area_model_key='cell_area'):
    utcnow = util.utcnow()
    with self.db_session() as session:
        # Select all the cells in this LAC that aren't the virtual
        # cell itself, and derive a bounding box for them.

        cell_model = CELL_MODEL_KEYS[cell_model_key]
        cell_query = (session.query(cell_model).filter(
            cell_model.radio == radio).filter(cell_model.mcc == mcc).filter(
                cell_model.mnc == mnc).filter(cell_model.lac == lac).filter(
                    cell_model.lat.isnot(None)).filter(
                        cell_model.lon.isnot(None)))

        cells = cell_query.all()

        cell_area_model = CELL_MODEL_KEYS[cell_area_model_key]
        lac_query = (session.query(cell_area_model).filter(
            cell_area_model.radio == radio).filter(
                cell_area_model.mcc == mcc).filter(
                    cell_area_model.mnc == mnc).filter(
                        cell_area_model.lac == lac))

        if len(cells) == 0:
            # If there are no more underlying cells, delete the lac entry
            lac_query.delete()
        else:
            # Otherwise update the lac entry based on all the cells
            lac_obj = lac_query.first()

            points = [(c.lat, c.lon) for c in cells]
            min_lat = min([c.min_lat for c in cells])
            min_lon = min([c.min_lon for c in cells])
            max_lat = max([c.max_lat for c in cells])
            max_lon = max([c.max_lon for c in cells])

            bbox_points = [(min_lat, min_lon), (min_lat, max_lon),
                           (max_lat, min_lon), (max_lat, max_lon)]

            ctr = centroid(points)
            rng = range_to_points(ctr, bbox_points)

            # Switch units back to DB preferred centimicrodegres angle
            # and meters distance.
            ctr_lat = ctr[0]
            ctr_lon = ctr[1]
            rng = int(round(rng * 1000.0))

            # Now create or update the LAC virtual cell
            num_cells = len(cells)
            avg_cell_range = int(
                sum([cell.range for cell in cells]) / float(num_cells))
            if lac_obj is None:
                lac_obj = cell_area_model(
                    created=utcnow,
                    modified=utcnow,
                    radio=radio,
                    mcc=mcc,
                    mnc=mnc,
                    lac=lac,
                    lat=ctr_lat,
                    lon=ctr_lon,
                    range=rng,
                    avg_cell_range=avg_cell_range,
                    num_cells=num_cells,
                )
                session.add(lac_obj)
            else:
                lac_obj.modified = utcnow
                lac_obj.lat = ctr_lat
                lac_obj.lon = ctr_lon
                lac_obj.range = rng
                lac_obj.avg_cell_range = avg_cell_range
                lac_obj.num_cells = num_cells

        session.commit()
Exemple #15
0
def update_lac(self, radio, mcc, mnc, lac):
    try:
        utcnow = util.utcnow()
        with self.db_session() as session:
            # Select all the cells in this LAC that aren't the virtual
            # cell itself, and derive a bounding box for them.

            cell_query = session.query(Cell).filter(
                Cell.radio == radio).filter(Cell.mcc == mcc).filter(
                    Cell.mnc == mnc).filter(
                        Cell.lac == lac).filter(Cell.cid != CELLID_LAC).filter(
                            Cell.lat.isnot(None)).filter(Cell.lon.isnot(None))

            cells = cell_query.all()

            lac_query = session.query(Cell).filter(Cell.radio == radio).filter(
                Cell.mcc == mcc).filter(Cell.mnc == mnc).filter(
                    Cell.lac == lac).filter(Cell.cid == CELLID_LAC)

            if len(cells) == 0:
                # If there are no more underlying cells, delete the lac entry
                lac_query.delete()
            else:
                # Otherwise update the lac entry based on all the cells
                lac_obj = lac_query.first()

                points = [(c.lat, c.lon) for c in cells]
                min_lat = min([c.min_lat for c in cells])
                min_lon = min([c.min_lon for c in cells])
                max_lat = max([c.max_lat for c in cells])
                max_lon = max([c.max_lon for c in cells])

                bbox_points = [(min_lat, min_lon), (min_lat, max_lon),
                               (max_lat, min_lon), (max_lat, max_lon)]

                ctr = centroid(points)
                rng = range_to_points(ctr, bbox_points)

                # Switch units back to DB preferred centimicrodegres angle
                # and meters distance.
                ctr_lat = ctr[0]
                ctr_lon = ctr[1]
                rng = int(round(rng * 1000.0))

                # Now create or update the LAC virtual cell
                if lac_obj is None:
                    lac_obj = Cell(radio=radio,
                                   mcc=mcc,
                                   mnc=mnc,
                                   lac=lac,
                                   cid=CELLID_LAC,
                                   lat=ctr_lat,
                                   lon=ctr_lon,
                                   created=utcnow,
                                   modified=utcnow,
                                   range=rng,
                                   total_measures=len(cells))
                    session.add(lac_obj)
                else:
                    lac_obj.total_measures = len(cells)
                    lac_obj.lat = ctr_lat
                    lac_obj.lon = ctr_lon
                    lac_obj.modified = utcnow
                    lac_obj.range = rng

            session.commit()
    except Exception as exc:  # pragma: no cover
        self.heka_client.raven('error')
        raise self.retry(exc=exc)