Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/forge py implementation #8

Merged
merged 2 commits into from
Apr 30, 2024
Merged
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
425 changes: 0 additions & 425 deletions .github/build.yml

This file was deleted.

63 changes: 0 additions & 63 deletions .github/release-created.yml

This file was deleted.

34 changes: 0 additions & 34 deletions .github/wait-for-pypi.py

This file was deleted.

15 changes: 7 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.11'

- name: Install Poetry
uses: abatilo/actions-poetry@v3
with:
Expand Down Expand Up @@ -191,18 +192,16 @@ jobs:
# These are gradle-specific steps for installing the application
- name: Install Software
run: |
pip install setuptools -U
pip install pylint
pip install pytest
poetry install

# This is where tests go
#- name: Run Poetry Tests
# run: |
# poetry run pylint podaac
# poetry run flake8 podaac
# poetry run pytest --junitxml=build/reports/pytest.xml --cov=podaac/ --cov-report=html -m "not aws and not integration" tests/
# poetry run pytest --junitxml=build/reports/pytest.xml --cov=podaac/ --cov-report=xml:build/reports/coverage.xml -m "not aws and not integration" tests/
- name: Run Poetry Tests
run: |
poetry run pylint podaac
poetry run flake8 podaac
poetry run pytest --junitxml=build/reports/pytest.xml --cov=podaac/ --cov-report=html -m "not aws and not integration" tests/

## TODO: Find out where the test report goes

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release-created.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ jobs:
ref: 'refs/heads/develop'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.11'

- name: Install Poetry
uses: abatilo/actions-poetry@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion docker/lambdaDockerfileArm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#ARG FUNCTION_DIR="/function"

FROM public.ecr.aws/lambda/python:3.12-arm64
FROM public.ecr.aws/lambda/python:3.11-arm64

# Include global arg in this stage of the build
ARG SOURCE
Expand Down
Empty file added podaac/forge_py/__init__.py
Empty file.
5 changes: 3 additions & 2 deletions podaac/forge_py/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ def create_parser():
"""Create a argparse parser for the backfill cli"""

parser = ArgumentParser()
parser.add_argument("--config")
parser.add_argument("-g", "--granule")
parser.add_argument("-c", "--config", required=True)
parser.add_argument("-g", "--granule", required=True)
parser.add_argument("-o", "--output_file")
parser.add_argument("--log-file")
parser.add_argument("--log-level")

Expand Down
30 changes: 29 additions & 1 deletion podaac/forge_py/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"""Script to run forge as a cli command"""
# pylint: disable=R0801, E1101

import logging
import sys
import copy
import json
from datetime import datetime, timezone
import xarray as xr
from shapely.wkt import dumps

from podaac.forge_py.args import parse_args
from podaac.forge_py.file_util import make_absolute
from podaac.forge_py import forge


def logger_from_args(args):
"""Return configured logger from parsed cli args."""
Expand Down Expand Up @@ -49,7 +55,29 @@ def main(args=None):

safe_log_args(logger, args)

print('forge main here again')
config_file = args.config
local_file = args.granule

with open(config_file) as config_f:
read_config = json.load(config_f)

longitude_var = read_config.get('lonVar')
latitude_var = read_config.get('latVar')
is360 = read_config.get('is360', False)

# Generate footprint
with xr.open_dataset(local_file, decode_times=False) as ds:
lon_data = ds[longitude_var]
lat_data = ds[latitude_var]
alpha_shape = forge.fit_footprint(lon_data, lat_data, is360=is360)

wkt_representation = dumps(alpha_shape)

if args.output_file:
with open(args.output_file, "w") as json_file:
json.dump(wkt_representation, json_file)

print(wkt_representation)

logger.info(f"Finished forge-py: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S %Z')}") # pylint: disable=W1203

Expand Down
66 changes: 54 additions & 12 deletions podaac/forge_py/forge.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,83 @@
"""Python footprint generator"""

import xarray as xr
import numpy as np
import alphashape
from shapely.wkt import dumps
from shapely.geometry import Polygon


def fit_footprint(lon, lat, thinning_fac=100, alpha=0.05, return_xythin=False):
def fit_footprint(lon, lat, thinning_fac=100, alpha=0.05, return_xythin=False, is360=False):
"""
lon, lon: list/array-like
Latitudes and longitudes.
thinning_fac: int
Factor to thin out data by (makes alphashape fit faster).
alpha: float
The alpha parameter passed to alphashape.
is360: bool
Tell us if the logitude data is between 0-360
"""

lon_array = lon
if is360:
lon_array = ((lon + 180) % 360.0) - 180

# lat, lon need to be 1D:
x = np.array(lon).flatten()
x = np.array(lon_array).flatten()
y = np.array(lat).flatten()

# Thinning out the number of data points helps alphashape fit faster
x_thin = x[np.arange(0, len(x), thinning_fac)]
y_thin = y[np.arange(0, len(y), thinning_fac)]

xy = np.array(list(zip(x_thin, y_thin))) # Reshape coords to use with alphashape
xy = np.array(list(zip(x_thin, y_thin))) # Reshape coords to use with alphashape
alpha_shape = alphashape.alphashape(xy, alpha=alpha)
if return_xythin:
return alpha_shape, x_thin, y_thin
return alpha_shape

if __name__ == "__main__":

FILE = "/Users/simonl/Desktop/forge_py/measures_esdr_scatsat_l2_wind_stress_23433_v1.1_s20210228-054653-e20210228-072612.nc"
def scatsat_footprint(lon, lat, thinning_fac=30, alpha=0.035, return_xythin=False, is360=False):
"""
Fits footprint g-polygon for level 2 data set SCATSAT1_ESDR_L2_WIND_STRESS_V1.1. Uses the
alphashape package for the fit, which returns a shapely.geometry.polygon.Polygon object.

data = xr.open_dataset(FILE)
lon, lon: list/array-like
Latitudes and longitudes.
thinning_fac: int
Factor to thin out data by (makes alphashape fit faster).
alpha: float
The alpha parameter passed to alphashape.
"""
# lat, lon need to be 1D:

alpha_shape_ = fit_footprint(data['lon'], data['lat'], thinning_fac=70, alpha=0.03, return_xythin=False)
wkt_representation = dumps(alpha_shape_)
lon_array = lon
if is360:
lon_array = ((lon + 180) % 360.0) - 180

print(wkt_representation)
x = np.array(lon_array).flatten()
y = np.array(lat).flatten()

# Outlying data near the poles. As a quick fix, remove all data near the poles, at latitudes higher than
# 87 degrees. This quick fix has impact on footprint shape.
i_lolats = np.where(abs(y) < 86)
x = x[i_lolats]
y = y[i_lolats]

# Thinning out the number of data points helps alphashape fit faster
x_thin = x[np.arange(0, len(x), thinning_fac)]
y_thin = y[np.arange(0, len(y), thinning_fac)]

# Fit with alphashape
xy = np.array(list(zip(x_thin, y_thin))) # Reshape coords to use with alphashape
alpha_shape = alphashape.alphashape(xy, alpha=alpha)

# Because of the thinning processes, the pole-edges of the footprint are jagged rather than
# flat, quick fix this by making all latitude points above 85 degrees a constant value:
fp_lon, fp_lat = alpha_shape.exterior.coords.xy
fp_lat = np.array(fp_lat)
fp_lat[np.where(fp_lat > 82)] = 88
fp_lat[np.where(fp_lat < -82)] = -88
footprint = Polygon(list(zip(fp_lon, np.asarray(fp_lat, dtype=np.float64))))

if return_xythin:
return footprint, x_thin, y_thin
return footprint
Loading