示例#1
0
 def apply(self, query):
     return query.filter(
         models.School.location.intersects(
             func.ST_MakeEnvelope(self.values['left'],
                                  self.values['bottom'],
                                  self.values['right'],
                                  self.values['top'])))
示例#2
0
    def suggest_larger_areas(self):
        ret = []
        for e in reversed(self.is_in() or []):
            osm_type, osm_id, bounds = e['type'], e['id'], e['bounds']
            if osm_type == self.osm_type and osm_id == self.osm_id:
                continue

            box = func.ST_MakeEnvelope(bounds['minlon'], bounds['minlat'],
                                       bounds['maxlon'], bounds['maxlat'],
                                       4326)

            q = func.ST_Area(box.cast(Geography))
            bbox_area = session.query(q).scalar()
            area_in_sq_km = bbox_area / (1000 * 1000)

            if area_in_sq_km < 10 or area_in_sq_km > 40_000:
                continue
            place = Place.from_osm(osm_type, osm_id)
            if not place:
                continue
            place.admin_level = e['tags'].get(
                'admin_level') or None if 'tags' in e else None
            ret.append(place)

        ret.sort(key=lambda place: place.area_in_sq_km)
        return ret
示例#3
0
    def chunk(self):
        chunk_size = utils.calc_chunk_size(self.area_in_sq_km)
        chunks = self.chunk_n(chunk_size)

        print('chunk size:', chunk_size)

        files = []

        need_pause = False
        for chunk_num, (ymin, ymax, xmin, xmax) in enumerate(chunks):
            q = self.items
            # note: different order for coordinates, xmin first, not ymin
            q = q.filter(
                cast(Item.location, Geometry).contained(
                    func.ST_MakeEnvelope(xmin, ymin, xmax, ymax)))

            tags = set()
            for item in q:
                tags |= set(item.tags)
            tags.difference_update(skip_tags)
            tags = matcher.simplify_tags(tags)
            filename = '{}_{:03d}_{:03d}.xml'.format(self.place_id, chunk_num,
                                                     len(chunks))
            print(chunk_num, q.count(), len(tags), filename, list(tags))
            full = os.path.join('overpass', filename)
            if not (tags):
                print('no tags, skipping')
                continue

            if not (tags):
                continue
            files.append(full)
            if os.path.exists(full):
                continue

            oql_bbox = '{:f},{:f},{:f},{:f}'.format(ymin, xmin, ymax, xmax)

            if need_pause:
                seconds = 2
                print('waiting {:d} seconds'.format(seconds))
                time.sleep(seconds)

            oql = overpass.oql_for_area(self.overpass_type,
                                        self.osm_id,
                                        tags,
                                        oql_bbox,
                                        None,
                                        include_self=(chunk_num == 0))
            r = overpass.run_query_persistent(oql)
            if not r:
                print(oql)
            assert r
            open(full, 'wb').write(r.content)
            need_pause = True

        cmd = ['osmium', 'merge'] + files + ['-o', self.overpass_filename]
        print(' '.join(cmd))
        subprocess.run(cmd)
示例#4
0
    def get(self):
        parser = reqparse.RequestParser()
        parser.add_argument('request_fields', action='append')
        #xmin
        parser.add_argument('sw_longitude', type=float)
        #ymin
        parser.add_argument('sw_latitude', type=float)
        #xmax
        parser.add_argument('ne_longitude', type=float)
        #ymax
        parser.add_argument('ne_latitude', type=float)
        parser.add_argument('keyword', type=str)
        parser.add_argument('current_user_id', type=int)

        args = parser.parse_args()
        pin_query = Pin.query

        sw_longitude = args['sw_longitude']
        sw_latitude = args['sw_latitude']
        ne_longitude = args['ne_longitude']
        ne_latitude = args['ne_latitude']

        pin_query = pin_query.filter(func.ST_Contains(func.ST_MakeEnvelope(sw_longitude, sw_latitude, ne_longitude, ne_latitude, 4326), Pin.geo))

        if(args['request_fields']):
            request_fields = tuple(args['request_fields'])
            pin_schema = PinSchema(only=request_fields)
        else:
            pin_schema = PinSchema()

        if(args['keyword']):
            keyword = args['keyword']

            combined_search_vector = ( Pin.search_vector | User.search_vector | func.coalesce(Tag.search_vector, u'') )

            pin_query = pin_query.join(User).outerjoin(PinTag).outerjoin(Tag).filter(combined_search_vector.match(parse_search_query(keyword)))

        pins = pin_query.all()
        if(args['current_user_id']):
            for pin in pins:
                if(pin.votes):
                    for vote in pin.votes:
                        if(vote.user_id == args['current_user_id']):
                            pin.vote_by_current_user = vote

        if(not pins):
            return {"message" :"Pin not found"}, HTTP_NOT_FOUND

        try:
            pin_json = pin_schema.dump(pins, many=True).data
            return pin_json
        except AttributeError as err:
            return {"message" : {"request_fields" : format(err)} }, HTTP_BAD_REQUEST
示例#5
0
 def get_places_in_rect(self, rect, sort_close_to=None):
     query = self.session.query(TestPlace)
     query = query.filter(
         func.ST_Contains(
             func.ST_MakeEnvelope(rect[0], rect[1], rect[2], rect[3], 4326),
             cast(TestPlace.coord, Geometry)))
     if sort_close_to is not None:
         query = query.order_by(
             func.ST_DistanceSphere(
                 func.ST_GeomFromText('POINT({} {} 4326)'.format(
                     sort_close_to[0], sort_close_to[1])),
                 cast(TestPlace.coord, Geometry),
             ))
     places = query.limit(100)
     return [place for place in places]
示例#6
0
def suggest_larger_areas(place_identifier):
    top = get_place(place_identifier)

    for place in top.go_bigger():
        area_in_sq_km = place.area_in_sq_km
        print(f'{area_in_sq_km:>10.1f} sq km  {place.name}')

    return

    for e in reversed(top.is_in()):
        # pprint(e)
        osm_type, osm_id = e['type'], e['id']
        if osm_type == top.osm_type and osm_id == top.osm_id:
            continue

        level = e['tags'].get('admin_level')

        # {'minlat': 49, 'minlon': -14, 'maxlat': 61.061, 'maxlon': 2}

        box = func.ST_MakeEnvelope(e['bounds']['minlon'],
                                   e['bounds']['minlat'],
                                   e['bounds']['maxlon'],
                                   e['bounds']['maxlat'], 4326)

        bbox_area = database.session.query(func.ST_Area(
            box.cast(Geography))).scalar()
        area_in_sq_km = bbox_area / (1000 * 1000)

        if area_in_sq_km > 20_000:
            continue
        place = Place.from_osm(osm_type, osm_id)
        area_in_sq_km = place.area_in_sq_km
        print(f'{area_in_sq_km:>10.1f} sq km', e['type'], f"{e['id']:10d}",
              level, e['tags']['name'])

        continue

        print(f'{place.area_in_sq_km:>10.1f} sq km  {place.name:30s}')
        continue

        hit = nominatim.reverse(e['type'], e['id'], polygon_text=0)
        pprint(hit)
        print()
        sleep(2)
示例#7
0
def polygons(place_identifier):
    place = get_place(place_identifier)

    chunk_size = utils.calc_chunk_size(place.area_in_sq_km)
    place_geojson = (database.session.query(func.ST_AsGeoJSON(
        Place.geom, 4)).filter(Place.place_id == place.place_id).scalar())
    # print(place_geojson)
    for chunk in place.chunk_n(chunk_size):
        print(', '.join('{:.3f}'.format(i) for i in chunk))

        (ymin, ymax, xmin, xmax) = chunk

        clip = func.ST_Intersection(
            Place.geom, func.ST_MakeEnvelope(xmin, ymin, xmax, ymax))

        chunk_geojson = (database.session.query(func.ST_AsGeoJSON(
            clip, 4)).filter(Place.place_id == place.place_id).scalar())

        print(chunk_geojson)
def point():
    bbox = request.args.get('bbox')
    page = request.args.get('page', type=int)
    rows = request.args.get('rows', app.Config.POINTS_PER_PAGE, type=int)
    if bbox:
        split = bbox.split(',')
        points = Point.query.filter(
            func.ST_Contains(
                func.ST_MakeEnvelope(split[0], split[1], split[2], split[3],
                                     3857), Point.geom))
    if page is None:
        points = Point.query.all()
    else:
        points = Point.query.paginate(page, rows, False).items
    data = {
        'type': 'FeatureCollection',
        'features': [incident.to_small_dict() for incident in points],
        'totalCount': Point.query.count()
    }
    return jsonify(data)
def amenity():
    if request.method == 'POST':
        data = request.get_json() or {}
        data["type"] = "amenity"
        missing_fields = validate_amenity_data(data)
        if (len(missing_fields) > 0):
            return make_missing_fields_error(missing_fields)
        amenity = Amenity()
        amenity.from_dict(data)
        db.session.add(amenity)
        db.session.commit()
        response = jsonify(amenity.to_small_dict())
        response.status_code = 201
        return response

    # Handle GET request
    bbox = request.args.get('bbox')
    filter = request.args.get('filter', default=False, type=boolean)
    page = request.args.get('page', type=int)
    rows = request.args.get('rows', app.Config.POINTS_PER_PAGE, type=int)
    if bbox:
        split = bbox.split(',')
        amenities = Amenity.query.filter(
            func.ST_Contains(
                func.ST_MakeEnvelope(split[0], split[1], split[2], split[3],
                                     3857), Amenity.geom))
    if page is None:
        amenities = Amenity.query.all()
    else:
        amenities = Amenity.query.paginate(page, rows, False).items
    if filter == True:
        amenities = _filter_amenities(amenities)
    data = {
        'type': 'FeatureCollection',
        'features': [amenity.to_small_dict() for amenity in amenities],
        'totalCount': Amenity.query.count()
    }
    return jsonify(data)
示例#10
0
文件: data.py 项目: betonr/bdc-stac
def get_collection_items(
    collection_id=None,
    roles=[],
    item_id=None,
    bbox=None,
    datetime=None,
    ids=None,
    collections=None,
    intersects=None,
    page=1,
    limit=10,
    query=None,
    **kwargs,
):
    """Retrieve a list of collection items based on filters.

    :param collection_id: Single Collection ID to include in the search for items.
                          Only Items in one of the provided Collection will be searched, defaults to None
    :type collection_id: str, optional
    :param item_id: item identifier, defaults to None
    :type item_id: str, optional
    :param bbox: bounding box for intersection [west, north, east, south], defaults to None
    :type bbox: list, optional
    :param datetime: Single date+time, or a range ('/' seperator), formatted to RFC 3339, section 5.6.
                     Use double dots '..' for open date ranges, defaults to None. If the start or end date of an image
                     generated by a temporal composition intersects the given datetime or range it will be included in the
                     result.
    :type datetime: str, optional
    :param ids: Array of Item ids to return. All other filter parameters that further restrict the
                number of search results are ignored, defaults to None
    :type ids: list, optional
    :param collections: Array of Collection IDs to include in the search for items.
                        Only Items in one of the provided Collections will be searched, defaults to None
    :type collections: list, optional
    :param intersects: Searches items by performing intersection between their geometry and provided GeoJSON geometry.
                       All GeoJSON geometry types must be supported., defaults to None
    :type intersects: dict, optional
    :param page: The page offset of results, defaults to 1
    :type page: int, optional
    :param limit: The maximum number of results to return (page size), defaults to 10
    :type limit: int, optional
    :return: list of collectio items
    :rtype: list
    """
    columns = [
        func.concat(Collection.name, "-",
                    Collection.version).label("collection"),
        Collection.collection_type,
        Collection._metadata.label("meta"),
        Item._metadata.label("item_meta"),
        Item.name.label("item"),
        Item.id,
        Item.collection_id,
        Item.start_date.label("start"),
        Item.end_date.label("end"),
        Item.assets,
        Item.created,
        Item.updated,
        cast(Item.cloud_cover, Float).label("cloud_cover"),
        func.ST_AsGeoJSON(Item.geom).label("geom"),
        func.Box2D(Item.geom).label("bbox"),
        Tile.name.label("tile"),
    ]

    where = [
        Collection.id == Item.collection_id,
        or_(Collection.is_public.is_(True),
            Collection.id.in_([int(r.split(":")[0]) for r in roles])),
    ]

    if ids is not None:
        where += [Item.name.in_(ids.split(","))]
    else:
        if collections is not None:
            where += [
                func.concat(Collection.name, "-",
                            Collection.version).in_(collections.split(","))
            ]
        elif collection_id is not None:
            where += [
                func.concat(Collection.name, "-",
                            Collection.version) == collection_id
            ]

        if item_id is not None:
            where += [Item.name.like(item_id)]

        if query:
            filters = create_query_filter(query)
            if filters:
                where += filters

        if intersects is not None:
            where += [
                func.ST_Intersects(func.ST_GeomFromGeoJSON(str(intersects)),
                                   Item.geom)
            ]
        elif bbox is not None:
            try:
                split_bbox = [float(x) for x in bbox.split(",")]
                if split_bbox[0] == split_bbox[2] or split_bbox[
                        1] == split_bbox[3]:
                    raise InvalidBoundingBoxError("")

                where += [
                    func.ST_Intersects(
                        func.ST_MakeEnvelope(
                            split_bbox[0],
                            split_bbox[1],
                            split_bbox[2],
                            split_bbox[3],
                            func.ST_SRID(Item.geom),
                        ),
                        Item.geom,
                    )
                ]
            except:
                raise (
                    InvalidBoundingBoxError(f"'{bbox}' is not a valid bbox."))

        if datetime is not None:
            date_filter = None
            if "/" in datetime:
                matches_open = ("..", "")
                time_start, time_end = datetime.split("/")
                if time_start in matches_open:  # open start
                    date_filter = [
                        or_(Item.start_date <= time_end,
                            Item.end_date <= time_end)
                    ]
                elif time_end in matches_open:  # open end
                    date_filter = [
                        or_(Item.start_date >= time_start,
                            Item.end_date >= time_start)
                    ]
                else:  # closed range
                    date_filter = [
                        or_(
                            and_(Item.start_date >= time_start,
                                 Item.start_date <= time_end),
                            and_(Item.end_date >= time_start,
                                 Item.end_date <= time_end),
                            and_(Item.start_date < time_start,
                                 Item.end_date > time_end),
                        )
                    ]
            else:
                date_filter = [
                    and_(Item.start_date <= datetime,
                         Item.end_date >= datetime)
                ]
            where += date_filter
    outer = [Item.tile_id == Tile.id]
    query = session.query(*columns).outerjoin(
        Tile, *outer).filter(*where).order_by(Item.start_date.desc(), Item.id)

    result = query.paginate(page=int(page),
                            per_page=int(limit),
                            error_out=False,
                            max_per_page=BDC_STAC_MAX_LIMIT)

    return result
示例#11
0
def envelope(bbox):
    # note: different order for coordinates, xmin first, not ymin
    ymin, ymax, xmin, xmax = bbox
    return func.ST_MakeEnvelope(xmin, ymin, xmax, ymax, 4326)
示例#12
0
    def search_items(
        self,
        *,
        product_name: Optional[str] = None,
        time: Optional[Tuple[datetime, datetime]] = None,
        bbox: Tuple[float, float, float, float] = None,
        limit: int = 500,
        offset: int = 0,
        full_dataset: bool = False,
        dataset_ids: Sequence[UUID] = None,
        require_geometry=True,
        ordered=True,
    ) -> Generator[DatasetItem, None, None]:
        """
        Search datasets using Cubedash's spatial table

        Returned as DatasetItem records, with optional embedded full Datasets
        (if full_dataset==True)

        Returned results are always sorted by (center_time, id)
        """
        geom = func.ST_Transform(DATASET_SPATIAL.c.footprint, 4326)

        columns = [
            geom.label("geometry"),
            func.Box2D(geom).label("bbox"),
            # TODO: dataset label?
            DATASET_SPATIAL.c.region_code.label("region_code"),
            DATASET_SPATIAL.c.creation_time,
            DATASET_SPATIAL.c.center_time,
        ]

        # If fetching the whole dataset, we need to join the ODC dataset table.
        if full_dataset:
            query: Select = select(
                (*columns, *_utils.DATASET_SELECT_FIELDS)).select_from(
                    DATASET_SPATIAL.join(
                        ODC_DATASET,
                        onclause=ODC_DATASET.c.id == DATASET_SPATIAL.c.id))
        # Otherwise query purely from the spatial table.
        else:
            query: Select = select((*columns, DATASET_SPATIAL.c.id,
                                    DATASET_SPATIAL.c.dataset_type_ref
                                    )).select_from(DATASET_SPATIAL)

        if time:
            query = query.where(
                func.tstzrange(
                    _utils.default_utc(time[0]),
                    _utils.default_utc(time[1]),
                    "[]",
                    type_=TSTZRANGE,
                ).contains(DATASET_SPATIAL.c.center_time))

        if bbox:
            query = query.where(
                func.ST_Transform(DATASET_SPATIAL.c.footprint,
                                  4326).intersects(
                                      func.ST_MakeEnvelope(*bbox)))

        if product_name:
            query = query.where(DATASET_SPATIAL.c.dataset_type_ref == select(
                [ODC_DATASET_TYPE.c.id]).where(
                    ODC_DATASET_TYPE.c.name == product_name))

        if dataset_ids:
            query = query.where(DATASET_SPATIAL.c.id.in_(dataset_ids))

        if require_geometry:
            query = query.where(DATASET_SPATIAL.c.footprint != None)

        if ordered:
            query = query.order_by(DATASET_SPATIAL.c.center_time,
                                   DATASET_SPATIAL.c.id)

        query = query.limit(limit).offset(
            # TODO: Offset/limit isn't particularly efficient for paging...
            offset)

        for r in self._engine.execute(query):
            yield DatasetItem(
                dataset_id=r.id,
                bbox=_box2d_to_bbox(r.bbox) if r.bbox else None,
                product_name=self.index.products.get(r.dataset_type_ref).name,
                geometry=_get_shape(r.geometry),
                region_code=r.region_code,
                creation_time=r.creation_time,
                center_time=r.center_time,
                odc_dataset=(_utils.make_dataset_from_select_fields(
                    self.index, r) if full_dataset else None),
            )
示例#13
0
def get_collection_items(
    collection_id=None,
    roles=None,
    item_id=None,
    bbox=None,
    datetime=None,
    ids=None,
    collections=None,
    intersects=None,
    page=1,
    limit=10,
    query=None,
    **kwargs,
) -> Pagination:
    """Retrieve a list of collection items based on filters.

    :param collection_id: Single Collection ID to include in the search for items.
                          Only Items in one of the provided Collection will be searched, defaults to None
    :type collection_id: str, optional
    :param item_id: item identifier, defaults to None
    :type item_id: str, optional
    :param bbox: bounding box for intersection [west, north, east, south], defaults to None
    :type bbox: list, optional
    :param datetime: Single date+time, or a range ('/' seperator), formatted to RFC 3339, section 5.6.
                     Use double dots '..' for open date ranges, defaults to None. If the start or end date of an image
                     generated by a temporal composition intersects the given datetime or range it will be included in the
                     result.
    :type datetime: str, optional
    :param ids: Array of Item ids to return. All other filter parameters that further restrict the
                number of search results are ignored, defaults to None
    :type ids: list, optional
    :param collections: Array of Collection IDs to include in the search for items.
                        Only Items in one of the provided Collections will be searched, defaults to None
    :type collections: list, optional
    :param intersects: Searches items by performing intersection between their geometry and provided GeoJSON geometry.
                       All GeoJSON geometry types must be supported., defaults to None
    :type intersects: dict, optional
    :param page: The page offset of results, defaults to 1
    :type page: int, optional
    :param limit: The maximum number of results to return (page size), defaults to 10
    :type limit: int, optional
    :return: list of collectio items
    :rtype: list
    """
    columns = [
        func.concat(Collection.name, "-",
                    Collection.version).label("collection"),
        Collection.collection_type,
        Collection._metadata.label("meta"),
        Item._metadata.label("item_meta"),
        Item.name.label("item"),
        Item.id,
        Item.collection_id,
        Item.start_date.label("start"),
        Item.end_date.label("end"),
        Item.assets,
        Item.created,
        Item.updated,
        cast(Item.cloud_cover, Float).label("cloud_cover"),
        func.ST_AsGeoJSON(Item.geom).label("geom"),
        func.ST_XMin(Item.geom).label("xmin"),
        func.ST_XMax(Item.geom).label("xmax"),
        func.ST_YMin(Item.geom).label("ymin"),
        func.ST_YMax(Item.geom).label("ymax"),
        Tile.name.label("tile"),
    ]

    if roles is None:
        roles = []

    where = [
        Collection.id == Item.collection_id,
        or_(Collection.is_public.is_(True),
            Collection.id.in_([int(r.split(":")[0]) for r in roles])),
    ]

    collections_where = _where_collections(collection_id, collections)
    collections_where.append(
        or_(Collection.is_public.is_(True),
            Collection.id.in_([int(r.split(":")[0]) for r in roles])))
    outer_join = [(Tile, [Item.tile_id == Tile.id])]
    _geom_tables = []
    _collections = Collection.query().filter(*collections_where).all()
    if bbox or intersects:
        grids = GridRefSys.query().filter(
            GridRefSys.id.in_([c.grid_ref_sys_id
                               for c in _collections])).all()
        for grid in grids:
            geom_table = grid.geom_table
            if geom_table is None:
                continue
            _geom_tables.append(geom_table)

    if ids is not None:
        if isinstance(ids, str):
            ids = ids.split(",")
        where += [Item.name.in_(ids)]
    else:
        where += _where_collections(collection_id, collections)

        if item_id is not None:
            where += [Item.name.like(item_id)]

        if query:
            filters = create_query_filter(query)
            if filters:
                where += filters

        if intersects is not None:
            # Intersect with native grid if there is
            geom_expr = func.ST_GeomFromGeoJSON(str(intersects))
            grids_where, joins = intersect_grids(geom_expr,
                                                 geom_tables=_geom_tables)

            where += grids_where
            outer_join += joins
        elif bbox is not None:
            try:
                if isinstance(bbox, str):
                    bbox = bbox.split(",")

                bbox = [float(x) for x in bbox]

                if bbox[0] == bbox[2] or bbox[1] == bbox[3]:
                    raise InvalidBoundingBoxError("")

                geom_expr = func.ST_MakeEnvelope(bbox[0], bbox[1], bbox[2],
                                                 bbox[3],
                                                 func.ST_SRID(Item.geom))
                grid_where, joins = intersect_grids(geom_expr,
                                                    geom_tables=_geom_tables)

                where += grid_where
                outer_join += joins
            except (ValueError, InvalidBoundingBoxError) as e:
                abort(400, f"'{bbox}' is not a valid bbox.")

        if datetime is not None:
            if "/" in datetime:
                matches_open = ("..", "")
                time_start, time_end = datetime.split("/")
                if time_start in matches_open:  # open start
                    date_filter = [
                        or_(Item.start_date <= time_end,
                            Item.end_date <= time_end)
                    ]
                elif time_end in matches_open:  # open end
                    date_filter = [
                        or_(Item.start_date >= time_start,
                            Item.end_date >= time_start)
                    ]
                else:  # closed range
                    date_filter = [
                        or_(
                            and_(Item.start_date >= time_start,
                                 Item.start_date <= time_end),
                            and_(Item.end_date >= time_start,
                                 Item.end_date <= time_end),
                            and_(Item.start_date < time_start,
                                 Item.end_date > time_end),
                        )
                    ]
            else:
                date_filter = [
                    and_(Item.start_date <= datetime,
                         Item.end_date >= datetime)
                ]
            where += date_filter

    query = session.query(*columns)
    for entity, join_conditions in outer_join:
        query = query.outerjoin(entity, *join_conditions)

    try:
        query = query.filter(*where).order_by(Item.start_date.desc(), Item.id)
        result = query.paginate(page=int(page),
                                per_page=int(limit),
                                error_out=False,
                                max_per_page=BDC_STAC_MAX_LIMIT)

        return result
    except Exception as err:
        msg = str(err)
        if hasattr(err, "orig"):
            msg = str(err.orig)
        abort(400, msg.rstrip())
示例#14
0
    def check_scenes(cls, collections: str, start_date: datetime, end_date: datetime,
                     catalog: str = None, dataset: str = None,
                     grid: str = None, tiles: list = None, bbox: list = None, catalog_kwargs=None, only_tiles=False):
        """Check for the scenes in remote provider and compares with the Collection Builder."""
        bbox_list = []
        if grid and tiles:
            grid = GridRefSys.query().filter(GridRefSys.name == grid).first_or_404(f'Grid "{grid}" not found.')
            geom_table = grid.geom_table

            rows = db.session.query(
                geom_table.c.tile,
                func.ST_Xmin(func.ST_Transform(geom_table.c.geom, 4326)).label('xmin'),
                func.ST_Ymin(func.ST_Transform(geom_table.c.geom, 4326)).label('ymin'),
                func.ST_Xmax(func.ST_Transform(geom_table.c.geom, 4326)).label('xmax'),
                func.ST_Ymax(func.ST_Transform(geom_table.c.geom, 4326)).label('ymax'),
            ).filter(geom_table.c.tile.in_(tiles)).all()
            for row in rows:
                bbox_list.append((row.tile, (row.xmin, row.ymin, row.xmax, row.ymax)))
        else:
            bbox_list.append(('', bbox))

        instance, provider = get_provider(catalog)

        collection_map = dict()
        collection_ids = list()

        for _collection in collections:
            collection, version = _collection.split('-')

            collection = Collection.query().filter(
                Collection.name == collection,
                Collection.version == version
            ).first_or_404(f'Collection "{collection}-{version}" not found.')

            collection_ids.append(collection.id)
            collection_map[_collection] = collection

        options = dict(start_date=start_date, end_date=end_date)
        if catalog_kwargs:
            options.update(catalog_kwargs)

        redis = current_app.redis
        output = dict(
            collections={cname: dict(total_scenes=0, total_missing=0, missing_external=[]) for cname in collections}
        )

        items = {cid: set() for cid in collection_ids}
        external_scenes = set()

        for tile, _bbox in bbox_list:
            with redis.pipeline() as pipe:
                if only_tiles:
                    entry = tile
                    options['tile'] = tile
                else:
                    options['bbox'] = _bbox
                    entry = _bbox

                periods = _generate_periods(start_date.replace(tzinfo=None), end_date.replace(tzinfo=None))

                for period_start, period_end in periods:
                    _items = db.session.query(Item.name, Item.collection_id).filter(
                        Item.collection_id.in_(collection_ids),
                        func.ST_Intersects(
                            func.ST_MakeEnvelope(
                                *_bbox, func.ST_SRID(Item.geom)
                            ),
                            Item.geom
                        ),
                        or_(
                            and_(Item.start_date >= period_start, Item.start_date <= period_end),
                            and_(Item.end_date >= period_start, Item.end_date <= period_end),
                            and_(Item.start_date < period_start, Item.end_date > period_end),
                        )
                    ).order_by(Item.name).all()

                    for item in _items:
                        items[item.collection_id].add(item.name)

                    options['start_date'] = period_start.strftime('%Y-%m-%d')
                    options['end_date'] = period_end.strftime('%Y-%m-%d')

                    key = f'scenes:{catalog}:{dataset}:{period_start.strftime("%Y%m%d")}_{period_end.strftime("%Y%m%d")}_{entry}'

                    pipe.get(key)
                    provider_scenes = []

                    if not redis.exists(key):
                        provider_scenes = provider.search(dataset, **options)
                        provider_scenes = [s.scene_id for s in provider_scenes]

                        pipe.set(key, json.dumps(provider_scenes))

                    external_scenes = external_scenes.union(set(provider_scenes))

                cached_scenes = pipe.execute()

                for cache in cached_scenes:
                    # When cache is True, represents set the value were cached.
                    if cache is not None and cache is not True:
                        external_scenes = external_scenes.union(set(json.loads(cache)))

        output['total_external'] = len(external_scenes)
        for _collection_name, _collection in collection_map.items():
            _items = set(items[_collection.id])
            diff = list(external_scenes.difference(_items))

            output['collections'][_collection_name]['total_scenes'] = len(_items)
            output['collections'][_collection_name]['total_missing'] = len(diff)
            output['collections'][_collection_name]['missing_external'] = diff

            for cname, _internal_collection in collection_map.items():
                if cname != _collection_name:
                    diff = list(_items.difference(set(items[_internal_collection.id])))
                    output['collections'][_collection_name][f'total_missing_{cname}'] = len(diff)
                    output['collections'][_collection_name][f'missing_{cname}'] = diff

        return output
示例#15
0
def get_collection_items(collection_id=None,
                         roles=[],
                         item_id=None,
                         bbox=None,
                         time=None,
                         ids=None,
                         collections=None,
                         cubes=None,
                         intersects=None,
                         page=1,
                         limit=10,
                         query=None,
                         **kwargs):
    """Retrieve a list of collection items based on filters.

    :param collection_id: Single Collection ID to include in the search for items.
                          Only Items in one of the provided Collection will be searched, defaults to None
    :type collection_id: str, optional
    :param item_id: item identifier, defaults to None
    :type item_id: str, optional
    :param bbox: bounding box for intersection [west, north, east, south], defaults to None
    :type bbox: list, optional
    :param time: Single date+time, or a range ('/' seperator), formatted to RFC 3339, section 5.6, defaults to None
    :type time: str, optional
    :param ids: Array of Item ids to return. All other filter parameters that further restrict the
                number of search results are ignored, defaults to None
    :type ids: list, optional
    :param collections: Array of Collection IDs to include in the search for items.
                        Only Items in one of the provided Collections will be searched, defaults to None
    :type collections: list, optional
    :param cubes: Bool indicating if only cubes should be returned, defaults to None
    :type cubes: bool, optional
    :param intersects: Searches items by performing intersection between their geometry and provided GeoJSON geometry.
                       All GeoJSON geometry types must be supported., defaults to None
    :type intersects: dict, optional
    :param page: The page offset of results, defaults to 1
    :type page: int, optional
    :param limit: The maximum number of results to return (page size), defaults to 10
    :type limit: int, optional
    :return: list of collectio items
    :rtype: list
    """
    columns = [
        Collection.name.label('collection'),
        Item.name.label('item'),
        Item.start_date.label('start'),
        Item.end_date.label('end'), Item.assets,
        func.ST_AsGeoJSON(Item.geom).label('geom'),
        func.Box2D(Item.geom).label('bbox'),
        Tile.name.label('tile')
    ]

    where = [
        Collection.id == Item.collection_id, Item.tile_id == Tile.id,
        or_(Collection.is_public.is_(True),
            Collection.id.in_([int(r.split(':')[0]) for r in roles]))
    ]

    if ids is not None:
        where += [Item.id.in_(ids.split(','))]
    elif item_id is not None:
        where += [Item.id.like(item_id)]
    else:
        if collections is not None:
            where += [Collection.name.in_(collections.split(','))]
        elif collection_id is not None:
            where += [Collection.name.like(collection_id)]

        if intersects is not None:
            where += [
                func.ST_Intersects(func.ST_GeomFromGeoJSON(str(intersects)),
                                   Item.geom)
            ]

        if query:
            filters = create_query_filter(query)
            if filters:
                where += filters

        if bbox is not None:
            try:
                split_bbox = [float(x) for x in bbox.split(',')]

                where += [
                    func.ST_Intersects(
                        func.ST_MakeEnvelope(split_bbox[0], split_bbox[1],
                                             split_bbox[2], split_bbox[3],
                                             func.ST_SRID(Item.geom)),
                        Item.geom)
                ]
            except:
                raise (
                    InvalidBoundingBoxError(f"'{bbox}' is not a valid bbox.'"))

        if time is not None:
            if "/" in time:
                time_start, time_end = time.split("/")
                time_end = datetime.fromisoformat(time_end)
                where += [
                    or_(Item.end_date <= time_end, Item.start_date <= time_end)
                ]
            else:
                time_start = datetime.fromisoformat(time)
            where += [
                or_(Item.start_date >= time_start, Item.end_date >= time_start)
            ]

    query = session.query(*columns).filter(*where).order_by(
        Item.start_date.desc())

    result = query.paginate(page=int(page),
                            per_page=int(limit),
                            error_out=False,
                            max_per_page=int(BDC_STAC_MAX_LIMIT))

    return result