Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
426 changes: 425 additions & 1 deletion WAL.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions WAL_6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# WAL_6 implementation plan

## Scope
- Add spec for planned processing API.
- Add template schema models and API methods.
- Add focused tests for new schema and API path/body behavior.

## Steps
1. Add `spec/002_F_plan_processing_api.md`.
2. Update `spec/index.md` and `spec/002_api.md` to reference 002_F.
3. Extend `mapflow/schema/processing.py` with template dataclasses and request schemas.
4. Extend `mapflow/functional/api/processing_api.py` with template endpoint methods.
5. Add/update tests under `tests/`.
6. Run focused pytest on updated tests; then run full suite if feasible.

## Assumptions
- Remote `origin/dev` fetch issue remains unresolved; work proceeds from local branch state.
- Existing local modifications in `WAL.md` and `mapflow/schema/processing.py` are user-owned and preserved.
9 changes: 7 additions & 2 deletions mapflow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from PyQt5.QtCore import QCoreApplication
from qgis.core import QgsSettings


SEARCH_CAPTURE_TIMEZONE = 'UTC'

@dataclass
class ConfigColumns():
def __init__(self):
Expand All @@ -15,7 +18,7 @@ def __init__(self):
QCoreApplication.translate('Config', 'Band Order'): 'colorBandOrder',
QCoreApplication.translate('Config', 'Cloud %'): 'cloudCover',
QCoreApplication.translate('Config', 'Off Nadir') + f' \N{DEGREE SIGN}': 'offNadirAngle',
QCoreApplication.translate('Config', 'Date & Time') + ' ({t})'.format(t=time.localtime().tm_zone): 'acquisitionDate',
QCoreApplication.translate('Config', 'Date & Time') + ' ({t})'.format(t=SEARCH_CAPTURE_TIMEZONE): 'acquisitionDate',
QCoreApplication.translate('Config', 'Zoom level'): 'zoom',
QCoreApplication.translate('Config', 'Spatial Resolution, m'): 'pixelResolution',
QCoreApplication.translate('Config', 'Image ID'): 'id',
Expand Down Expand Up @@ -80,7 +83,9 @@ class Config:
PPRVIEW_INDEX_COLUMN = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.values()).index('preview')
NAME_COLUMN_INDEX = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.values()).index('providerName')
ZOOM_COLUMN_INDEX = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.values()).index('zoom')
MAXAR_DATETIME_COLUMN_INDEX = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.keys()).index(QCoreApplication.translate('Config', 'Date & Time') + ' ({t})'.format(t=TIMEZONE))
MAXAR_DATETIME_COLUMN_INDEX = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.keys()).index(
QCoreApplication.translate('Config', 'Date & Time') + ' ({t})'.format(t=SEARCH_CAPTURE_TIMEZONE)
)
MAXAR_CLOUD_COLUMN_INDEX = tuple(ConfigColumns().METADATA_TABLE_ATTRIBUTES.keys()).index(QCoreApplication.translate('Config', 'Cloud %'))
MAXAR_MAX_FREE_ZOOM = 12

Expand Down
2 changes: 2 additions & 0 deletions mapflow/dialogs/main_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def __init__(self, parent: QWidget, settings: QgsSettings) -> None:
self.save_result_action = QAction(self.tr("Save results"))
self.download_aoi_action = QAction(self.tr("Download AOI"))
self.see_details_action = QAction(self.tr("See details"))
self.see_processings_action = QAction(self.tr("See processings"))
self.see_search_results_action = QAction(self.tr("See search results"))
self.processing_update_action = QAction(self.tr("Rename"))
self.processing_restart_action = QAction(self.tr("Restart"))
self.processing_duplicate_action = QAction(self.tr("Duplicate"))
Expand Down
5 changes: 4 additions & 1 deletion mapflow/dialogs/static/ui/main_dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1648,7 +1648,7 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="getMetadata">
<widget class="QToolButton" name="getMetadata">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
Expand All @@ -1670,6 +1670,9 @@
<property name="toolTip">
<string>Click and wait for a few seconds until the table below is filled out</string>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="text">
<string>Search </string>
</property>
Expand Down
17 changes: 9 additions & 8 deletions mapflow/entity/processing.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import sys
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Optional, Tuple

from .status import ProcessingStatus, ProcessingReviewStatus
from ..errors import ErrorMessage
from ..schema.processing import BlockOption, ProcessingParams
from ..schema.base import parse_api_datetime_utc


class Processing:
Expand Down Expand Up @@ -34,7 +35,7 @@ def __init__(self,
self.workflow_def = workflow_def
self.aoi_area = aoi_area
self.cost = int(cost)
self.created = created.astimezone()
self.created = parse_api_datetime_utc(created)
self.percent_completed = int(percent_completed)
self.errors = errors
self.raster_layer = raster_layer
Expand All @@ -61,7 +62,7 @@ def from_response(cls, processing):
created = processing['created'].replace('Z', '+0000')
else:
created = processing['created']
created = datetime.strptime(created, '%Y-%m-%dT%H:%M:%S.%f%z').astimezone()
created = parse_api_datetime_utc(created)
percent_completed = processing['percentCompleted']
messages = processing.get('messages', [])
errors = [ErrorMessage.from_response(message) for message in messages]
Expand All @@ -71,7 +72,7 @@ def from_response(cls, processing):
review_status = processing.get('reviewStatus', {}).get('reviewStatus')
in_review_until_str = processing.get('reviewStatus', {}).get('inReviewUntil')
if in_review_until_str:
in_review_until = datetime.strptime(in_review_until_str, '%Y-%m-%dT%H:%M:%S.%f%z').astimezone()
in_review_until = parse_api_datetime_utc(in_review_until_str)
else:
in_review_until = None
else:
Expand Down Expand Up @@ -100,7 +101,7 @@ def from_response(cls, processing):

@property
def is_new(self):
now = datetime.now().astimezone()
now = datetime.now(timezone.utc)
one_day = timedelta(1)
return now - self.created < one_day

Expand All @@ -109,7 +110,7 @@ def review_expires(self):
if not isinstance(self.in_review_until, datetime)\
or not self.review_status.is_in_review:
return False
now = datetime.now().astimezone()
now = datetime.now(timezone.utc)
one_day = timedelta(1)
return self.in_review_until - now < one_day

Expand All @@ -129,9 +130,9 @@ def asdict(self):
'percentCompleted': self.percent_completed,
'errors': self.errors,
# Serialize datetime and drop seconds for brevity
'created': self.created.strftime('%Y-%m-%d %H:%M'),
'created': self.created.astimezone().strftime('%Y-%m-%d %H:%M'),
'rasterLayer': self.raster_layer,
'reviewUntil': self.in_review_until.strftime('%Y-%m-%d %H:%M') if self.in_review_until else "",
'reviewUntil': self.in_review_until.astimezone().strftime('%Y-%m-%d %H:%M') if self.in_review_until else "",
'description': self.description,
'meta': self.meta
}
Expand Down
2 changes: 1 addition & 1 deletion mapflow/entity/provider/basemap_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from urllib.parse import urlparse, parse_qs

from .provider import SourceType, CRS, UsersProvider, staticproperty
from ...functional.layer_utils import maxar_tile_url, add_connect_id
from .url_utils import add_connect_id, maxar_tile_url
from ...requests.maxar_metadata_request import MAXAR_REQUEST_BODY, MAXAR_META_URL
from ...schema.processing import PostSourceSchema, UserDefinedParams, UserDefinedSchema, ProcessingParams

Expand Down
2 changes: 1 addition & 1 deletion mapflow/entity/provider/factory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .basemap_provider import XYZProvider, TMSProvider, QuadkeyProvider, MaxarProvider
from .url_utils import add_connect_id
from ...constants import MAXAR_BASE_URL
from ...functional.layer_utils import add_connect_id

provider_options = {XYZProvider.option_name: XYZProvider,
TMSProvider.option_name: TMSProvider,
Expand Down
34 changes: 34 additions & 0 deletions mapflow/entity/provider/url_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
def add_image_id(url: str, image_id: str):
if not image_id:
return url
if not url.endswith('?'):
url = url + '&'
return url + f"CQL_FILTER=feature_id='{image_id}'"


def add_connect_id(url: str, connect_id: str):
if not url.endswith('?'):
url = url + '&'
return url + f'CONNECTID={connect_id}'


def maxar_tile_url(base_url, image_id=None):
"""
base_url is copied from maxar website and looks like
https://securewatch.digitalglobe.com/earthservice/wmtsaccess?connectid=<UUID>
we need to return TileUrl with TileMatrix set and so on
"""
if not base_url.endswith('?'):
base_url = base_url + '&'
url = base_url + "SERVICE=WMTS" \
"&VERSION=1.0.0" \
"&STYLE=" \
"&REQUEST=GetTile" \
"&LAYER=DigitalGlobe:ImageryTileService" \
"&FORMAT=image/jpeg" \
"&TileRow={y}" \
"&TileCol={x}" \
"&TileMatrixSet=EPSG:3857" \
"&TileMatrix=EPSG:3857:{z}"
url = add_image_id(url, image_id)
return url
4 changes: 2 additions & 2 deletions mapflow/entity/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from PyQt5.QtCore import QObject

from ..schema.base import Serializable, SkipDataClass
from ..schema.base import Serializable, SkipDataClass, parse_api_datetime_utc


class ProcessingStatusDict(QObject):
Expand Down Expand Up @@ -118,7 +118,7 @@ def from_dict(cls, data: Optional[dict]):

def __post_init__(self):
if self.inReviewUntil:
self.inReviewUntil = datetime.strptime(self.inReviewUntil, '%Y-%m-%dT%H:%M:%S.%f%z').astimezone()
self.inReviewUntil = parse_api_datetime_utc(self.inReviewUntil)
self.reviewStatus = ProcessingReviewStatusEnum(self.reviewStatus)

@property
Expand Down
Loading
Loading