def polygon_chunk(self, size=64): stmt = (session.query( func.ST_Dump(Place.geom.cast(Geometry())).label('x')).filter_by( place_id=self.place_id).subquery()) q = session.query( stmt.c.x.path[1], func.ST_Area(stmt.c.x.geom.cast(Geography)) / (1000 * 1000), func.Box2D(stmt.c.x.geom)) for num, area, box2d in q: chunk_size = utils.calc_chunk_size(area, size=size) west, south, east, north = map(float, re_box.match(box2d).groups()) for chunk in bbox_chunk((south, north, west, east), chunk_size): yield chunk
def show_polygons(place_identifier): place = get_place(place_identifier) num = 0 for chunk in place.polygon_chunk(size=64): num += 1 print(chunk) print() print(num) return num = '(-?[0-9.]+)' re_box = re.compile(f'^BOX\({num} {num},{num} {num}\)$') # select ST_Dump(geom::geometry) as poly from place where osm_id=1543125 stmt = (database.session.query( func.ST_Dump(Place.geom.cast(Geometry())).label('x')).filter_by( place_id=place.place_id).subquery()) q = database.session.query( stmt.c.x.path[1], func.ST_Area(stmt.c.x.geom.cast(Geography)) / (1000 * 1000), func.Box2D(stmt.c.x.geom)) print(q) for num, area, box2d in q: # west, south, east, north # BOX(135.8536855 20.2145811,136.3224209 20.6291059) size = wikidata_chunk_size(area) west, south, east, north = map(float, re_box.match(box2d).groups()) bbox = (south, north, west, east) # print((num, area, size, box2d)) for chunk in chunk_n(bbox, size): print(chunk)
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
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), )
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