diff --git a/capella_console_client/client.py b/capella_console_client/client.py index de667b5..30db5d8 100644 --- a/capella_console_client/client.py +++ b/capella_console_client/client.py @@ -113,35 +113,27 @@ def create_tasking_request(self, **kwargs): geometry: A GeoJSON representation of the area/point of interest. Must be either a polygon or point name: Can be used along with description to help characterize and describe the tasking request. Default: "" description: Can be used along with name to help characterize and describe the tasking request. Default: "" + collection_type: The collection type sets the collect mode, number of looks, and resolutions for the resulting imagery. The available collection types can be found by submitting: GET https://api.capellaspace.com/collectiontypes + collection_tier: Preference for data to be collected within a certain time after window_open. Can be one of "urgent", "priority", "standard", and "flexible". Default: "standard" window_open: Earliest time (in UTC) that you would like data to be collected. Default: Now window_close: Latest time (in UTC) that you would like data to be collected. Default: Seven days after window_open - collection_tier: Preference for data to be collected within a certain time after window_open. Can be one of "urgent", "priority", "standard", and "flexible". Default: "standard" - product_category: Category used to define image collection. "Extended" has broader look angles and "Custom" allows specifying advanced image acquisition parameters. More information on the specifics of each can be found at https://support.capellaspace.com/hc/en-us/articles/360049110852-SAR-Imagery-Product-Tasking-Categories. One of "standard", "extended", and "custom". Default: "standard" - product_types: List of analytics to add to the order along with the imagery. Currently available analytics are Amplitude Change Detection and Vessel Detection. One of "ACD", "VS". Default: None - archive_holdback: If defined will specify a time period during which the resulting imagery will be kept from the publicly accessible archive. One of "none", "one_year", "thirty_day", "permanent". Default: "none" - custom_attribute_1: Can be used along with custom_attribute_2 to help you track a Capella task with your own metadata or internal systems. Default: None - custom_attribute_2: Can be used along with custom_attribute_1 to help you track a Capella task with your own metadata or internal systems. Default: None - collect_mode: Collect mode to be used by the satellite when making the collect. One of "spotlight", "stripmap", "sliding_spotlight". Default: "spotlight" - look_direction: Constraint on view angle. One of "right", "left", "either". Default: "either" - asc_dsc: Constraint on ascending/descending pass. One of "ascending", "descending", "either". Default: "either" - orbital_planes: List of orbital planes allowed to service request. If empty any spacecraft in any plane can service request. One of 45, 53, 97. Default: None local_time: Times, in the timezone of the area where the image will be collected, during which the collect can be taken. Represented by a list of time ranges as seconds in the day. For example, [[21600, 64800]] would allow collects between 6 AM and 6 PM; [[0, 21600], [64800, 86400]] would allow collects between 6 PM and 12 AM as well as from 12 AM to 6 AM. Alternatively, you can pass string values of "day", "night", or "anytime" which are parsed to [[21600, 64800]], [[0, 21600], [64800, 86400]], and [[0, 86400]] respectively. Default: None + product_types: List of analytics to add to the order along with the imagery. Currently available analytics are Vessel classification (VC), Default: None off_nadir_min: Minimum off-nadir angle permitted. Must be less than off_nadir_max. Default: None off_nadir_max: Maximum off-nadir angle permitted. Must be greater than off_nadir_min. Default: None - elevation_min: Minimum elevation angle permitted. Default: None - elevation_max: Maximum elevation angle permitted. Default: None - image_length: Image length. Default: None - image_width: Image width. Default: None - azimuth: Azimuth angle at collect mid-time. Clockwise angle from North to the spacecraft in the target frame of reference. Default: None - grr_min: Minimum ground-range resolution. Minimum is in ordinal sense. Default: None - grr_max: Maximum ground-range resolution. Maximum is in ordinal sense. Default: None - srr_min: Minimum slant-range resolution. Minimum is in ordinal sense. Default: None - srr_max: Maximum slant-range resolution. Maximum is in ordinal sense. Default: None - azr_min: Minimum azimuth resolution. Minimum is in ordinal sense. Default: None - azr_max: Maximum azimuth resolution. Maximum is in ordinal sense. Default: None - nesz_max: Maximum allowable NESZ of resulting collect. Default: None - num_looks: Number of looks to use in processing collect. Default: None + image_width: Image width. Units: [m], Default: None + orbital_planes: List of orbital planes allowed to service request. If empty any spacecraft in any plane can service request. One of 45, 53, 97. Default: None + asc_dsc: Constraint on ascending/descending pass. One of "ascending", "descending", "either". Default: "either" + look_direction: Constraint on view angle. One of "right", "left", "either". Default: "either" polarization: Image polarization. One of "HH", "VV". Default: None + archive_holdback: If defined will specify a time period during which the resulting imagery will be kept from the publicly accessible archive. One of "none", "one_year", "thirty_day", "permanent". Default: "none" + custom_attribute_1: Can be used along with custom_attribute_2 to help you track a Capella task with your own metadata or internal systems. Default: None + custom_attribute_2: Can be used along with custom_attribute_1 to help you track a Capella task with your own metadata or internal systems. Default: None + pre_approval: will skip the tasking request cost review step if set to true. Default: false + azimuth_angle_min: clockwise angle with respect to North in a topocentric geodetic ENZ coordinate system from the target to the satellite. Default: None + azimuth_angle_max: clockwise angle with respect to North in a topocentric geodetic ENZ coordinate system from the target to the satellite. Default: None + squint: Determines if generated collects will be squinted. One of: enabled, forward, backward. Default: enabled for point requests, disabled for area requests + max_squint_angle: max. allowed absolute squint angle when generating collects. Units: [degrees]. Default: None Returns: Dict[str, Any]: created tasking request metadata @@ -221,38 +213,27 @@ def create_repeat_request(self, **kwargs): name: Can be used along with description to help characterize and describe the tasking request. Default: "" description: Can be used along with name to help characterize and describe the tasking request. Default: "" collection_tier: Preference for data to be collected within a certain time after window_open. Can be either "flexible" or "routine". Default: "routine" - product_category: Category used to define image collection. "Extended" has broader look angles and "Custom" allows specifying advanced image acquisition parameters. More information on the specifics of each can be found at https://support.capellaspace.com/hc/en-us/articles/360049110852-SAR-Imagery-Product-Tasking-Categories. One of "standard", "extended", and "custom". Default: "standard" - archive_holdback: If defined will specify a time period during which the resulting imagery will be kept from the publicly accessible archive. One of "none", "one_year", "thirty_day", "permanent". Default: "none" - custom_attribute_1: Can be used along with custom_attribute_2 to help you track a Capella task with your own metadata or internal systems. Default: None - custom_attribute_2: Can be used along with custom_attribute_1 to help you track a Capella task with your own metadata or internal systems. Default: None - product_types: List of analytics to add to the order along with the imagery. Currently available analytics are Amplitude Change Detection and Vessel Detection. One of "ACD", "VS". Default: None + collection_type: The collection type sets the collect mode, number of looks, and resolutions for the resulting imagery. The available collection types can be found by submitting: GET https://api.capellaspace.com/collectiontypes repeat_start: Starting date (in UTC) when you would like data to begin being collected. Default: Now repeat_end: Starting date (in UTC) when you would like data to stop being collected. This and repetition_count are mutually exclusive; only one of them can be defined per request. Default: None repetition_interval: Number of days between the start of each derived request. Default: 7 repetition_count: Total number of acquisitions in the repeat series. This and repeat_end are mutually exclusive; only one of them can be defined per request. Default: None - maintain_scene_framing: Whether to maintain consistent framing (look-direction, ascending/descending, orbital-plane) across all acquisitions. Default: False - look_angle_tolerance: Tolerance to look-angle deviations across all acquisitions. Default: None - collect_mode: Collect mode to be used by the satellite when making the collect. One of "spotlight", "stripmap", "sliding_spotlight". Default: "spotlight" - look_direction: Constraint on view angle. One of "right", "left", "either". Default: "either" - asc_dsc: Constraint on ascending/descending pass. One of "ascending", "descending", "either". Default: "either" - orbital_planes: List of orbital planes allowed to service request. If empty any spacecraft in any plane can service request. One of 45, 53, 97. Default: None local_time: Times, in the timezone of the area where the image will be collected, during which the collect can be taken. Represented by a list of time ranges as seconds in the day. For example, [[21600, 64800]] would allow collects between 6 AM and 6 PM; [[0, 21600], [64800, 86400]] would allow collects between 6 PM and 12 AM as well as from 12 AM to 6 AM. Alternatively, you can pass string values of "day", "night", or "anytime" which are parsed to [[21600, 64800]], [[0, 21600], [64800, 86400]], and [[0, 86400]] respectively. Default: None + product_types: List of analytics to add to the order along with the imagery. Currently available analytics are Vessel classification (VC), Default: None off_nadir_min: Minimum off-nadir angle permitted. Must be less than off_nadir_max. Default: None off_nadir_max: Maximum off-nadir angle permitted. Must be greater than off_nadir_min. Default: None - elevation_min: Minimum elevation angle permitted. Default: None - elevation_max: Maximum elevation angle permitted. Default: None - image_length: Image length. Default: None - image_width: Image width. Default: None - azimuth: Azimuth angle at collect mid-time. Clockwise angle from North to the spacecraft in the target frame of reference. Default: None - grr_min: Minimum ground-range resolution. Minimum is in ordinal sense. Default: None - grr_max: Maximum ground-range resolution. Maximum is in ordinal sense. Default: None - srr_min: Minimum slant-range resolution. Minimum is in ordinal sense. Default: None - srr_max: Maximum slant-range resolution. Maximum is in ordinal sense. Default: None - azr_min: Minimum azimuth resolution. Minimum is in ordinal sense. Default: None - azr_max: Maximum azimuth resolution. Maximum is in ordinal sense. Default: None - nesz_max: Maximum allowable NESZ of resulting collect. Default: None - num_looks: Number of looks to use in processing collect. Default: None + image_width: Image width. Units: [m], Default: None + orbital_planes: List of orbital planes allowed to service request. If empty any spacecraft in any plane can service request. One of 45, 53, 97. Default: None + asc_dsc: Constraint on ascending/descending pass. One of "ascending", "descending", "either". Default: "either" + look_direction: Constraint on view angle. One of "right", "left", "either". Default: "either" polarization: Image polarization. One of "HH", "VV". Default: None + archive_holdback: If defined will specify a time period during which the resulting imagery will be kept from the publicly accessible archive. One of "none", "one_year", "thirty_day", "permanent". Default: "none" + custom_attribute_1: Can be used along with custom_attribute_2 to help you track a Capella task with your own metadata or internal systems. Default: None + custom_attribute_2: Can be used along with custom_attribute_1 to help you track a Capella task with your own metadata or internal systems. Default: None + azimuth_angle_min: clockwise angle with respect to North in a topocentric geodetic ENZ coordinate system from the target to the satellite. Default: None + azimuth_angle_max: clockwise angle with respect to North in a topocentric geodetic ENZ coordinate system from the target to the satellite. Default: None + squint: Determines if generated collects will be squinted. One of: enabled, forward, backward. Default: enabled for point requests, disabled for area requests + max_squint_angle: max. allowed absolute squint angle when generating collects. Units: [degr Returns: Dict[str, Any]: created repeat request metadata diff --git a/capella_console_client/config.py b/capella_console_client/config.py index 1ba75f1..555a1d7 100644 --- a/capella_console_client/config.py +++ b/capella_console_client/config.py @@ -105,27 +105,18 @@ _COMMON_COLLECT_CONSTRAINTS_FIELDS = frozenset( [ - "collect_mode", - "look_direction", - "asc_dsc", - "orbital_planes", - "local_time", "off_nadir_min", "off_nadir_max", - "elevation_min", - "elevation_max", - "image_length", "image_width", - "azimuth", - "grr_min", - "grr_max", - "srr_min", - "srr_max", - "azr_min", - "azr_max", - "nesz_max", - "num_looks", + "orbital_planes", + "asc_dsc", + "look_direction", + "local_time", "polarization", + "azimuth_angle_min", + "azimuth_angle_max", + "squint", + "max_squint_angle", ] ) TASKING_REQUEST_COLLECT_CONSTRAINTS_FIELDS = _COMMON_COLLECT_CONSTRAINTS_FIELDS.copy() @@ -138,7 +129,5 @@ "repeat_end", "repetition_interval", "repetition_count", - "maintain_scene_framing", - "look_angle_tolerance", ] ) diff --git a/capella_console_client/enumerations.py b/capella_console_client/enumerations.py index b51ee10..c5886f0 100644 --- a/capella_console_client/enumerations.py +++ b/capella_console_client/enumerations.py @@ -107,10 +107,17 @@ class RepeatCollectionTier(str, BaseEnum): internal = "internal" +class RepeatCycle(int, BaseEnum): + DAILY = 1 + WEEKLY = 7 + BI_WEEKLY = 14 + MONTHLY = 30 + + class ArchiveHoldback(str, BaseEnum): none = "none" - one_year = "1 year" - thirty_day = "30 day" + one_year = "1_year" + thirty_day = "30_day" permanent = "permanent" @@ -134,3 +141,19 @@ class OwnershipOption(str, BaseEnum): @classmethod def is_valid(cls, option_str: str) -> bool: return option_str in list(cls) + + +class CollectionType(str, BaseEnum): + SPOTLIGHT = "spotlight" + SPOTLIGHT_ULTRA = "spotlight_ultra" + SPOTLIGHT_WIDE = "spotlight_wide" + STRIPMAP_20 = "stripmap_20" + STRIPMAP_50 = "stripmap_50" + STRIPMAP_100 = "stripmap_100" + + +class SquintMode(str, BaseEnum): + ENABLED = "enabled" + DISABLED = "disabled" + FORWARD = "forward" + BACKWARD = "backward" diff --git a/capella_console_client/repeat_request.py b/capella_console_client/repeat_request.py index 84a925e..ff3e2fa 100644 --- a/capella_console_client/repeat_request.py +++ b/capella_console_client/repeat_request.py @@ -1,12 +1,11 @@ -from typing import Optional, Dict, Any, List, Union -from datetime import datetime, timedelta +from typing import Optional, Dict, Any, List, Union, Tuple +from datetime import datetime import geojson -from dateutil.parser import parse, ParserError from capella_console_client.session import CapellaConsoleSession from capella_console_client.exceptions import RepeatRequestPayloadValidationError -from capella_console_client.validate import _snake_to_camel, _datetime_to_iso8601_str +from capella_console_client.validate import _snake_to_camel, _datetime_to_iso8601_str, _set_squint_default from capella_console_client.config import ( REPEAT_REQUEST_COLLECT_CONSTRAINTS_FIELDS, REPEAT_REQUESTS_REPETITION_PROPERTIES_FIELDS, @@ -15,69 +14,49 @@ ObservationDirection, OrbitState, ProductType, - ProductClass, OrbitalPlane, RepeatCollectionTier, - InstrumentMode, Polarization, ArchiveHoldback, LocalTimeOption, + CollectionType, + SquintMode, + RepeatCycle, ) def create_repeat_request( session: CapellaConsoleSession, geometry: geojson.geometry.Geometry, - name: Optional[str] = "", + name: str, description: Optional[str] = "", + collection_type: Optional[Union[CollectionType, str]] = CollectionType.SPOTLIGHT, collection_tier: Optional[Union[str, RepeatCollectionTier]] = RepeatCollectionTier.routine, - product_category: Optional[Union[str, ProductClass]] = ProductClass.standard, - archive_holdback: Optional[Union[str, ArchiveHoldback]] = ArchiveHoldback.none, - custom_attribute_1: Optional[str] = None, - custom_attribute_2: Optional[str] = None, - product_types: Optional[List[Union[str, ProductType]]] = None, - # Repetition properties repeat_start: Optional[Union[datetime, str]] = None, repeat_end: Optional[Union[datetime, str]] = None, - repetition_interval: Optional[int] = 7, + repetition_interval: Optional[Union[RepeatCycle, int]] = RepeatCycle.WEEKLY, repetition_count: Optional[int] = None, - maintain_scene_framing: Optional[bool] = False, - look_angle_tolerance: Optional[int] = None, - # Collect constraints - collect_mode: Optional[Union[str, InstrumentMode]] = InstrumentMode.spotlight, - look_direction: Optional[Union[str, ObservationDirection]] = ObservationDirection.either, - asc_dsc: Optional[Union[str, OrbitState]] = OrbitState.either, - orbital_planes: Optional[List[Union[int, OrbitalPlane]]] = None, - local_time: Optional[Union[List[int], LocalTimeOption]] = None, + local_time: Optional[Union[LocalTimeOption, List[int]]] = None, + product_types: Optional[List[Union[ProductType, str]]] = None, off_nadir_min: Optional[int] = None, off_nadir_max: Optional[int] = None, - elevation_min: Optional[int] = None, - elevation_max: Optional[int] = None, - image_length: Optional[int] = None, image_width: Optional[int] = None, - azimuth: Optional[int] = None, - grr_min: Optional[int] = None, - grr_max: Optional[int] = None, - srr_min: Optional[int] = None, - srr_max: Optional[int] = None, - azr_min: Optional[int] = None, - azr_max: Optional[int] = None, - nesz_max: Optional[int] = None, - num_looks: Optional[int] = None, - polarization: Optional[Union[str, Polarization]] = None, + orbital_planes: Optional[List[Union[OrbitalPlane, int]]] = None, + asc_dsc: Optional[Union[OrbitState, str]] = OrbitState.either, + look_direction: Optional[Union[ObservationDirection, str]] = ObservationDirection.either, + polarization: Optional[Union[Polarization, str]] = None, + archive_holdback: Optional[Union[str, ArchiveHoldback]] = ArchiveHoldback.none, + custom_attribute_1: Optional[str] = None, + custom_attribute_2: Optional[str] = None, + azimuth_angle_min: Optional[int] = None, + azimuth_angle_max: Optional[int] = None, + squint: Optional[Union[SquintMode, str]] = None, + max_squint_angle: Optional[int] = None, ) -> Dict[str, Any]: - if repeat_end is not None and repetition_count is not None: - raise RepeatRequestPayloadValidationError( - "Only one of 'repeat_end' and 'repetition_count' can be defined. Please remove one of those values from your request and try again." - ) - - if repeat_start is None: - repeat_start = datetime.utcnow() - - repeat_start = _datetime_to_iso8601_str(repeat_start) + repeat_start, repeat_end = _set_repetition_start_end(repeat_start, repeat_end, repetition_count) - if repeat_end is not None: - repeat_end = _datetime_to_iso8601_str(repeat_end) + if squint is None: + squint = _set_squint_default(geometry) loc = locals() collect_constraints = { @@ -96,7 +75,7 @@ def create_repeat_request( "repeatrequestName": name, "repeatrequestDescription": description, "collectionTier": collection_tier, - "productCategory": product_category, + "collectionType": collection_type, "archiveHoldback": archive_holdback, "customAttribute1": custom_attribute_1, "customAttribute2": custom_attribute_2, @@ -109,3 +88,24 @@ def create_repeat_request( payload["properties"]["processingConfig"] = {"productTypes": product_types} return session.post("/repeat-requests", json=payload).json() + + +def _set_repetition_start_end( + repeat_start: Optional[Union[datetime, str]], + repeat_end: Optional[Union[datetime, str]], + repetition_count: Optional[int], +) -> Tuple[str, Optional[str]]: + if repeat_end is not None and repetition_count is not None: + raise RepeatRequestPayloadValidationError( + "Only one of 'repeat_end' and 'repetition_count' can be defined. Please remove one of those values from your request and try again." + ) + + if repeat_start is None: + repeat_start = datetime.utcnow() + + repeat_start = _datetime_to_iso8601_str(repeat_start) + + if repeat_end is not None: + repeat_end = _datetime_to_iso8601_str(repeat_end) + + return (repeat_start, repeat_end) diff --git a/capella_console_client/tasking_request.py b/capella_console_client/tasking_request.py index 28766e0..9189581 100644 --- a/capella_console_client/tasking_request.py +++ b/capella_console_client/tasking_request.py @@ -1,73 +1,65 @@ -from typing import Optional, Dict, Any, List, Union +from typing import Optional, Dict, Any, List, Union, Tuple from datetime import datetime, timedelta import geojson -from dateutil.parser import parse, ParserError +from dateutil.parser import parse from capella_console_client.logconf import logger from capella_console_client.session import CapellaConsoleSession -from capella_console_client.validate import _validate_uuid, _snake_to_camel, _datetime_to_iso8601_str +from capella_console_client.validate import ( + _validate_uuid, + _snake_to_camel, + _datetime_to_iso8601_str, + _set_squint_default, +) from capella_console_client.config import TASKING_REQUEST_DEFAULT_PAGE_SIZE, TASKING_REQUEST_COLLECT_CONSTRAINTS_FIELDS from capella_console_client.enumerations import ( TaskingRequestStatus, ObservationDirection, OrbitState, ProductType, - ProductClass, OrbitalPlane, CollectionTier, - InstrumentMode, Polarization, ArchiveHoldback, LocalTimeOption, + SquintMode, + CollectionType, ) def create_tasking_request( session: CapellaConsoleSession, geometry: geojson.geometry.Geometry, - name: Optional[str] = "", + name: str, description: Optional[str] = "", + collection_type: Optional[Union[CollectionType, str]] = CollectionType.SPOTLIGHT, + collection_tier: Optional[Union[CollectionTier, str]] = CollectionTier.standard, window_open: Optional[Union[datetime, str]] = None, window_close: Optional[Union[datetime, str]] = None, - collection_tier: Optional[Union[str, CollectionTier]] = CollectionTier.standard, - product_category: Optional[Union[str, ProductClass]] = ProductClass.standard, - archive_holdback: Optional[Union[str, ArchiveHoldback]] = ArchiveHoldback.none, - custom_attribute_1: Optional[str] = None, - custom_attribute_2: Optional[str] = None, - product_types: Optional[List[Union[str, ProductType]]] = None, - # Collect constraints - collect_mode: Optional[Union[str, InstrumentMode]] = InstrumentMode.spotlight, - look_direction: Optional[Union[str, ObservationDirection]] = ObservationDirection.either, - asc_dsc: Optional[Union[str, OrbitState]] = OrbitState.either, - orbital_planes: Optional[List[Union[int, OrbitalPlane]]] = None, - local_time: Optional[Union[List[int], LocalTimeOption]] = None, + local_time: Optional[Union[LocalTimeOption, List[int]]] = None, + product_types: Optional[List[Union[ProductType, str]]] = None, off_nadir_min: Optional[int] = None, off_nadir_max: Optional[int] = None, - elevation_min: Optional[int] = None, - elevation_max: Optional[int] = None, - image_length: Optional[int] = None, image_width: Optional[int] = None, - azimuth: Optional[int] = None, - grr_min: Optional[int] = None, - grr_max: Optional[int] = None, - srr_min: Optional[int] = None, - srr_max: Optional[int] = None, - azr_min: Optional[int] = None, - azr_max: Optional[int] = None, - nesz_max: Optional[int] = None, - num_looks: Optional[int] = None, - polarization: Optional[Union[str, Polarization]] = None, + orbital_planes: Optional[List[Union[OrbitalPlane, int]]] = None, + asc_dsc: Optional[Union[OrbitState, str]] = OrbitState.either, + look_direction: Optional[Union[ObservationDirection, str]] = ObservationDirection.either, + polarization: Optional[Union[Polarization, str]] = None, + archive_holdback: Optional[Union[ArchiveHoldback, str]] = ArchiveHoldback.none, + custom_attribute_1: Optional[str] = None, + custom_attribute_2: Optional[str] = None, + pre_approval: bool = False, + azimuth_angle_min: Optional[int] = None, + azimuth_angle_max: Optional[int] = None, + squint: Optional[Union[SquintMode, str]] = None, + max_squint_angle: Optional[int] = None, ) -> Dict[str, Any]: - if window_open is None: - window_open = datetime.utcnow() - if window_close is None: - if isinstance(window_open, str): - window_open = parse(window_open) - window_close = window_open + timedelta(days=7) - window_open = _datetime_to_iso8601_str(window_open) - window_close = _datetime_to_iso8601_str(window_close) + window_open, window_close = _set_window_open_close(window_open, window_close) + + if squint is None: + squint = _set_squint_default(geometry) loc = locals() collect_constraints = { @@ -85,10 +77,11 @@ def create_tasking_request( "windowOpen": window_open, "windowClose": window_close, "collectionTier": collection_tier, - "productCategory": product_category, + "collectionType": collection_type, "archiveHoldback": archive_holdback, "customAttribute1": custom_attribute_1, "customAttribute2": custom_attribute_2, + "pre_approval": pre_approval, "collectConstraints": collect_constraints, }, } @@ -99,6 +92,22 @@ def create_tasking_request( return session.post("/task", json=payload).json() +def _set_window_open_close( + window_open: Optional[Union[datetime, str]], window_close: Optional[Union[datetime, str]] +) -> Tuple[str, str]: + if window_open is None: + window_open = datetime.utcnow() + + if window_close is None: + if isinstance(window_open, str): + window_open = parse(window_open) + window_close = window_open + timedelta(days=7) + + window_open = _datetime_to_iso8601_str(window_open) + window_close = _datetime_to_iso8601_str(window_close) + return (window_open, window_close) + + def get_tasking_requests( *tasking_request_ids: Optional[str], session: CapellaConsoleSession, diff --git a/capella_console_client/validate.py b/capella_console_client/validate.py index 11824bd..e908f37 100644 --- a/capella_console_client/validate.py +++ b/capella_console_client/validate.py @@ -4,9 +4,10 @@ from collections import Counter from dateutil.parser import parse, ParserError -from typing import no_type_check, Optional, List, Dict, Tuple, Any, Union +from typing import no_type_check, Optional, List, Dict, Any, Union +import geojson -from capella_console_client.enumerations import ProductType, AssetType +from capella_console_client.enumerations import ProductType, AssetType, SquintMode from capella_console_client.logconf import logger from capella_console_client.search import SearchResult @@ -92,4 +93,13 @@ def _datetime_to_iso8601_str(dt: Union[datetime, str]) -> str: dt = parse(dt) except ParserError: raise ValueError(f"Could not parse {dt} string into a datetime") - return dt.isoformat(timespec="milliseconds") + "Z" + return dt.replace(tzinfo=None).isoformat(timespec="milliseconds") + "Z" + + +def _set_squint_default(geometry: geojson.geometry.Geometry) -> SquintMode: + is_point_request = geometry["type"] == "Point" + + if is_point_request: + return SquintMode.ENABLED + + return SquintMode.DISABLED diff --git a/docs/pages/example_usage.rst b/docs/pages/example_usage.rst index b308b91..ab7b461 100644 --- a/docs/pages/example_usage.rst +++ b/docs/pages/example_usage.rst @@ -763,11 +763,17 @@ Issue the following snippet to view the ordering history create tasking request ###################### -Create a tasking request with basic parameters +NOTE: `geometry` and `name` are the only required properties to create a tasking request. .. code:: python3 - # Create basic tasking request with a geometry (only required parameter) + # create point tasking request + client.create_tasking_request( + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="point tasking request #127" + ) + + # area tasking request client.create_tasking_request( geometry=geojson.Polygon( [ @@ -779,10 +785,12 @@ Create a tasking request with basic parameters [11.148216220469152, 49.59672249842626], ] ] - ) + ), + name="area tasking request #127", + collection_type="stripmap_100", ) - # Add a couple of parameters to help you track/identify it better + # tasking request customization client.create_tasking_request( geometry=geojson.Polygon( [ @@ -795,68 +803,142 @@ Create a tasking request with basic parameters ] ] ), - name="I<3SAR", - description="My first tasking request" + name="highly customizable #127", + description="too many knobs", + collection_tier="urgent", + collection_type="spotlight_ultra", + local_time="day", + off_nadir_min=5, + off_nadir_max=50, + orbitalPlanes=[45, 53], + asc_dsc="ascending", + look_direction="right", + polarization="HH", + archive_holdback="30 day", + custom_attribute_1="correlation #1", + custom_attribute_2="correlation #2", + pre_approval=True, + azimuth_angle_min=340, + azimuth_angle_max=20, + squint="enabled", + max_squint_angle=25, + ) + + + # same as above but leveraging enums defined in `enumerations.py` (client-side validation) + from capella_console_client.enumerations import ( + CollectionTier, + CollectionType, + LocalTimeOption, + OrbitalPlane, + OrbitState, + Polarization, + ObservationDirection, + ArchiveHoldback, + SquintMode, ) + client.create_tasking_request( + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="highly customizable #127", + description="too many knobs", + collection_tier=CollectionTier.urgent, + collection_type=CollectionType.SPOTLIGHT_ULTRA, + local_time=LocalTimeOption.day, + off_nadir_min=5, + off_nadir_max=50, + orbital_planes=[OrbitalPlane.fortyfive, OrbitalPlane.fiftythree], + asc_dsc=OrbitState.ascending, + look_direction=ObservationDirection.right, + polarization=Polarization.HH, + archive_holdback=ArchiveHoldback.thirty_day, + custom_attribute_1="correlation #1", + custom_attribute_2="correlation #2", + pre_approval=True, + azimuth_angle_min=340, + azimuth_angle_max=20, + squint=SquintMode.ENABLED, + max_squint_angle=30, + ) + + +create repeat request +##################### + +NOTE: `geometry` and `name` are the only required properties to create a repeat request. + +.. code:: python3 -create repeating tasking request -################################ + # create above tasking request as repeat series + from capella_console_client.enumerations import ( + RepeatCollectionTier, + CollectionType, + LocalTimeOption, + OrbitalPlane, + OrbitState, + Polarization, + ObservationDirection, + ArchiveHoldback, + SquintMode, + ) -Create a repeating tasking request with basic parameters + client.create_repeat_request( + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="highly customizable repeat request #127", + description="too many knobs", + collection_tier=RepeatCollectionTier.flexible, + collection_type=CollectionType.SPOTLIGHT_ULTRA, + local_time=LocalTimeOption.day, + off_nadir_min=5, + off_nadir_max=50, + orbital_planes=[OrbitalPlane.fortyfive, OrbitalPlane.fiftythree], + asc_dsc=OrbitState.ascending, + look_direction=ObservationDirection.right, + polarization=Polarization.HH, + archive_holdback=ArchiveHoldback.thirty_day, + custom_attribute_1="correlation #1", + custom_attribute_2="correlation #2", + azimuth_angle_min=340, + azimuth_angle_max=20, + squint=SquintMode.ENABLED, + max_squint_angle=30, + ) + + +repeat requests repeat cadence can be configured in multiple ways .. code:: python3 - # Create basic repeating tasking request with a geometry (only required parameter) + # A) until cancelled, e.g. weekly starting now (defaults) client.create_repeat_request( - geometry=geojson.Polygon( - [ - [ - [11.148216220469152, 49.59672249842626], - [11.148216220469152, 49.55415435337187], - [11.219621049225651, 49.55415435337187], - [11.219621049225651, 49.59672249842626], - [11.148216220469152, 49.59672249842626], - ] - ] - ) + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="daily repeat", ) - # Add a couple of parameters to help you track/identify it better + # B) start + end datetime + frequency , e.g. daily for 27 days + from datetime import datetime, timezone, timedelta + from capella_console_client.enumerations import RepeatCycle + + repeat_start = datetime.now(tz=timezone.utc) + repeat_end = repeat_start + timedelta(days=27) + client.create_repeat_request( - geometry=geojson.Polygon( - [ - [ - [11.148216220469152, 49.59672249842626], - [11.148216220469152, 49.55415435337187], - [11.219621049225651, 49.55415435337187], - [11.219621049225651, 49.59672249842626], - [11.148216220469152, 49.59672249842626], - ] - ] - ), - name="I<3SAR", - description="My first repeat request" + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="daily repeat", + description="daily repeat", + repeat_start=repeat_start, + repeat_end=repeat_end, + repetition_interval=RepeatCycle.DAILY, ) - # Note that you can only define either repeat_end OR repetition_count, not both. The following request will fail: + # C) number of collects + frequency, e.g. 5 collects weekly starting from now client.create_repeat_request( - geometry=geojson.Polygon( - [ - [ - [11.148216220469152, 49.59672249842626], - [11.148216220469152, 49.55415435337187], - [11.219621049225651, 49.55415435337187], - [11.219621049225651, 49.59672249842626], - [11.148216220469152, 49.59672249842626], - ] - ] - ), - name="I<3SAR", - description="My first repeat request", - repeat_start="2023-12-24 3:30 PM" - repeat_end="2023-12-31 3:30 PM", - repetition_count=23 + geometry=geojson.Point([11.148216220469152, 49.59672249842626]), + name="repeat five weeks", + description="repeat five weeks", + repeat_start=repeat_start, + repetition_count=5, + repetition_interval=RepeatCycle.WEEKLY, ) search tasking request diff --git a/tests/test_repeat_request.py b/tests/test_repeat_request.py index debf521..d54fa6c 100644 --- a/tests/test_repeat_request.py +++ b/tests/test_repeat_request.py @@ -12,20 +12,22 @@ def test_create_repeat_request_returns_new_request(test_client, authed_repeat_request_mock): - repeat_request = test_client.create_repeat_request(geometry=mock_geojson) + repeat_request = test_client.create_repeat_request(geometry=mock_geojson, name="test") assert repeat_request == post_mock_responses("/repeat-requests") def test_create_repeat_request_invalid_repeat_start(test_client): with pytest.raises(ValueError): - test_client.create_repeat_request(geometry=mock_geojson, repeat_start="PANDA") + test_client.create_repeat_request(geometry=mock_geojson, name="test", repeat_start="PANDA") def test_create_repeat_request_invalid_repeat_end(test_client): with pytest.raises(ValueError): - test_client.create_repeat_request(geometry=mock_geojson, repeat_end="PANDA") + test_client.create_repeat_request(geometry=mock_geojson, name="test", repeat_end="PANDA") def test_create_repeat_request_end_and_count_defined(test_client): with pytest.raises(RepeatRequestPayloadValidationError): - test_client.create_repeat_request(geometry=mock_geojson, repeat_end="PANDA", repetition_count=12345) + test_client.create_repeat_request( + geometry=mock_geojson, name="test", repeat_end="PANDA", repetition_count=12345 + ) diff --git a/tests/test_tasking_request.py b/tests/test_tasking_request.py index b84ccc7..0055229 100644 --- a/tests/test_tasking_request.py +++ b/tests/test_tasking_request.py @@ -109,15 +109,15 @@ def test_get_collects_for_task_not_completed(test_client, auth_httpx_mock): def test_create_task_returns_new_task(test_client, authed_tasking_request_mock): - tasking_requests = test_client.create_tasking_request(geometry=mock_geojson) + tasking_requests = test_client.create_tasking_request(geometry=mock_geojson, name="test") assert tasking_requests == post_mock_responses("/task") def test_create_task_invalid_window_open(test_client): with pytest.raises(ValueError): - test_client.create_tasking_request(geometry=mock_geojson, window_open="PANDA") + test_client.create_tasking_request(geometry=mock_geojson, name="test", window_open="PANDA") def test_create_task_invalid_window_close(test_client): with pytest.raises(ValueError): - test_client.create_tasking_request(geometry=mock_geojson, window_close="PANDA") + test_client.create_tasking_request(geometry=mock_geojson, name="test", window_close="PANDA")