Skip to content

Commit

Permalink
Merge pull request #544 from EOxServer/zarr-single-collection
Browse files Browse the repository at this point in the history
Zarr single collection
  • Loading branch information
jankovicgd committed Nov 24, 2022
2 parents 053b78d + b25a45b commit d3b10bb
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 36 deletions.
11 changes: 6 additions & 5 deletions autotest/autotest/settings.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
#
# Project: EOxServer <http://eoxserver.org>
# Authors: Stephan Krause <stephan.krause@eox.at>
# Stephan Meissl <stephan.meissl@eox.at>
#
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
# Copyright (C) 2011 EOX IT Services GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
Expand All @@ -24,7 +24,7 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------

"""
Django settings for EOxServer's autotest instance.
Expand Down Expand Up @@ -301,9 +301,9 @@
'filters': [],
},
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'verbose',
'filters': [],
}
},
'loggers': {
Expand Down Expand Up @@ -331,3 +331,4 @@
# Set this variable if the path to the instance cannot be resolved
# automatically, e.g. in case of redirects
#FORCE_SCRIPT_NAME="/path/to/instance/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
172 changes: 172 additions & 0 deletions eoxserver/resources/coverages/management/commands/timeseries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# ------------------------------------------------------------------------------
#
# Project: EOxServer <http://eoxserver.org>
# Authors: Bernhard Mallinger <bernhard.mallinger@eox.at>
#
# ------------------------------------------------------------------------------
# Copyright (C) 2022 EOX IT Services GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies of this Software or works derived from this Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ------------------------------------------------------------------------------


from django.core.management.base import BaseCommand
from django.db import transaction

from eoxserver.resources.coverages.registration.timeseries import register_time_series
from eoxserver.resources.coverages import models
from eoxserver.resources.coverages.management.commands import (
CommandOutputMixIn,
SubParserMixIn,
)


def parse_collection(identifier):
try:
return models.Collection.objects.get(identifier=identifier)
except models.Collection.DoesNotExist:
raise ValueError(f'No collection with identifier "{identifier}" found.')


def parse_coverage_type_mapping(mapping):
# raises value error if not exactly 1 ":"
dim, coverage_type_name = mapping.split(":")
return (dim, coverage_type_name)


class Command(CommandOutputMixIn, SubParserMixIn, BaseCommand):
"""Command to manage time series. This command uses sub-commands for the
specific tasks: register, deregister
"""

def add_arguments(self, parser):
register_parser = self.add_subparser(parser, "register")
deregister_parser = self.add_subparser(parser, "deregister")

register_parser.add_argument(
"--collection",
"--collection-identifier",
"-c",
dest="collection",
required=True,
type=parse_collection,
help="Register timeseries for this collection",
)
register_parser.add_argument(
"--storage",
help="The storage to use",
)
register_parser.add_argument(
"--path",
required=True,
help="Path to timeseries file",
)
register_parser.add_argument(
"--product-type-name",
required=True,
help="The product type name",
)
register_parser.add_argument(
"--coverage-type-mapping",
action="append",
type=parse_coverage_type_mapping,
required=True,
help="Which dimension to map to which coverage type. "
'Use : as separator, e.g. --coverage-type-mapping "/Band1:b1"',
)
register_parser.add_argument(
"--x-dim-name",
required=True,
help="Name of the X dimension",
)
register_parser.add_argument(
"--y-dim-name",
required=True,
help="Name of the Y dimension",
)
register_parser.add_argument(
"--time-dim-name",
required=True,
help="Name of the time dimension",
)
register_parser.add_argument(
"--product-template",
required=True,
help="Format string for product identifier. "
"Can use the following template variables: "
"collection_identifier, file_identifier, index, "
"product_type, begin_time, end_time",
)
register_parser.add_argument(
"--replace",
"-r",
dest="replace",
action="store_true",
default=False,
help=(
"Optional. If the time series with the given identifier already "
"exists, replace it. Without this flag, this would result in "
"an error."
),
)

@transaction.atomic
def handle(self, subcommand, *args, **kwargs):
"""Dispatch sub-commands: register, deregister."""
if subcommand == "register":
self.handle_register(*args, **kwargs)
elif subcommand == "deregister":
self.handle_deregister(*args, **kwargs)

def handle_register(
self,
collection,
storage,
path,
product_type_name,
coverage_type_mapping,
x_dim_name,
y_dim_name,
time_dim_name,
product_template,
replace,
**kwargs,
):
timeseries_path, replaced = register_time_series(
collection=collection,
storage=storage,
path=path,
product_type_name=product_type_name,
coverage_type_mapping=dict(coverage_type_mapping),
x_dim_name=x_dim_name,
y_dim_name=y_dim_name,
time_dim_name=time_dim_name,
product_template=product_template,
replace=replace,
)

self.print_msg(
(
f"Successfully {'replaced' if replaced else 'registered'}"
f" timeseries {timeseries_path}"
)
)

def handle_deregister(self, **kwargs):
raise NotImplementedError()
2 changes: 1 addition & 1 deletion eoxserver/resources/coverages/registration/stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
)


logger = logging.getLogger()
logger = logging.getLogger(__name__)


def get_product_type_name(stac_item):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
GDALRegistrator
)

logger = logging.getLogger()
logger = logging.getLogger(__name__)


def create_product(
Expand All @@ -66,16 +66,27 @@ def create_product(
path,
index,
all_overrides,
file_identifier,
product_template,
):
product_identifier = '%s_%d' % (collection.identifier, index)
template_values = {
"collection_identifier": collection.identifier,
"file_identifier": file_identifier,
"index": index,
"product_type": product_type,
"begin_time": begin_time,
"end_time": end_time
}

product_identifier = product_template.format(**template_values)

replaced = False

# check if the product already exists
if models.Product.objects.filter(
identifier=product_identifier).exists():
if replace:
logger.debug('Deleting existing Product %s', product_identifier)
logger.info('Deleting existing Product %s', product_identifier)
models.Product.objects.filter(
identifier=product_identifier).delete()
replaced = True
Expand All @@ -93,7 +104,7 @@ def create_product(
)
models.collection_insert_eo_object(collection, product)

logger.debug('Successfully created product %s', product_identifier)
logger.info('Successfully created product %s', product_identifier)

registrator = GDALRegistrator()

Expand Down Expand Up @@ -136,7 +147,7 @@ def create_product(
all_overrides['size'] = coverage.size
all_overrides['origin'] = coverage.origin

logger.debug('Successfully created coverage %s' % overrides['identifier'])
logger.info('Successfully created coverage %s' % overrides['identifier'])

return (product, replaced)

Expand Down Expand Up @@ -167,7 +178,7 @@ def extent_to_footprint(crs_wkt, extent):

def compute_min_max(dim):
dimension = dim.ReadAsArray()
return [dimension[0, 0], dimension[0, dimension.size-1]]
return [dimension[0, 0], dimension[0, dimension.size - 1]]


def compute_extent(x_path, y_path):
Expand All @@ -186,7 +197,8 @@ def create_dates_array(time_path, begin_time):
ds_arr = np.nditer(time_ds.ReadAsArray(), flags=['f_index'])
dates_array = []

begin_dt = datetime.datetime.combine(begin_time, datetime.time.min, datetime.timezone.utc)
begin_dt = datetime.datetime.combine(
begin_time, datetime.time.min, datetime.timezone.utc)
for time in ds_arr:
dt = begin_dt + datetime.timedelta(days=time.item())
dates_array.append(dt)
Expand All @@ -198,15 +210,20 @@ def create_dates_array(time_path, begin_time):


@transaction.atomic
def register_time_series(identifier, storage, path, collection_type_name, product_type_name, coverage_type_mapping, x_dim_name, y_dim_name, time_dim_name, replace=True):
collection = models.Collection.objects.create(
identifier=identifier,
collection_type=(
models.CollectionType.objects.get(name=collection_type_name)
if collection_type_name is not None else None
),
grid=None
)
def register_time_series(
collection,
storage,
path,
product_type_name,
coverage_type_mapping,
x_dim_name,
y_dim_name,
time_dim_name,
product_template,
replace=True
):

file_identifier = path.split("/")[-1].split(".")[0]

if isinstance(storage, str):
storage = backends.Storage.objects.get(name=storage)
Expand All @@ -217,7 +234,7 @@ def register_time_series(identifier, storage, path, collection_type_name, produc
driver_name = metadata['driver'].upper()

# TODO: get time array dynamically
match = re.search('\d{4}-\d{2}-\d{2}', metadata['arrays']['time']['unit'])
match = re.search(r'\d{4}-\d{2}-\d{2}', metadata['arrays']['time']['unit'])
start_date = datetime.datetime.strptime(match.group(), '%Y-%m-%d').date()

fixed_dimensions = []
Expand Down Expand Up @@ -258,12 +275,16 @@ def register_time_series(identifier, storage, path, collection_type_name, produc
storage,
path,
i,
overrides
overrides,
file_identifier,
product_template
)

return path, replace

# python3 manage.py shell -c "from eoxserver.resources.coverages.registration.zarr import register_time_series; register_time_series('test', 's3', 'filtered_zarr/32N.zarr', 'sample_collection', 'sample_type', {});"

# register_time_series('test', 's3', 'filtered_zarr/32N.zarr', 'sample_collection', 'sample_type', {'/Band1': 'Band1', '/Band2': 'Band2', '/Band3': 'Band3', '/Band4': 'Band4', '/Band5': 'Band5'}, '/X', '/Y', '/time');
# register_time_series(Collection(),'s3', 'filtered_zarr/32N.zarr', 'sample_type', {'/Band1': 'Band1', '/Band2': 'Band2', '/Band3': 'Band3', '/Band4': 'Band4', '/Band5': 'Band5'}, '/X', '/Y', '/time', "{collection_identifier}_{file_identifier}_{index}");


# python3 manage.py shell -c "from eoxserver.resources.coverages.registration.zarr import register_time_series ; register_time_series('test', 's3', 'filtered_zarr/32N.zarr', 'sample_collection', 'sample_type', {'/Band1': 'Band1', '/Band2': 'Band2', '/Band3': 'Band3', '/Band4': 'Band4', '/Band5': 'Band5'}, '/X', '/Y', '/time');"
# python3 manage.py shell -c "from eoxserver.resources.coverages.registration.zarr import register_time_series ; register_time_series('test', 's3', 'filtered_zarr/32N.zarr', 'sample_collection', 'sample_type', {'/Band1': 'Band1', '/Band2': 'Band2', '/Band3': 'Band3', '/Band4': 'Band4', '/Band5': 'Band5'}, '/X', '/Y', '/time');"
9 changes: 9 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ requires = Django >= 1.4
libxml2-python
python-lxml
python ply

[flake8]
max-line-length = 90
exclude = .venv, build, tests, docs, autotest
ignore = W503,E203

[mypy]
exclude = (.venv|build|tests|docs|autotest)
ignore_missing_imports = True
23 changes: 13 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,19 @@ def fullsplit(path, result=None):
"tools/eoxserver-preprocess.py"
],
install_requires=[
'django<4',
'python-dateutil',
'django-model-utils<5.0.0',
'zipstream',
'psycopg2',
'lxml',
'pycql==0.0.8',
'matplotlib',
'pyows>=0.2.6',
'jsonfield',
"django<4",
"python-dateutil",
"django-model-utils<5.0.0",
"django-utils-six==2.0",
"zipstream",
"psycopg2",
"lxml",
"pycql==0.0.8",
"matplotlib",
"pyows>=0.2.6",
"python-keystoneclient<6.0.0",
"python-swiftclient<5.0.0",
"jsonfield",
],
extras_require={
'dev': ['scipy'],
Expand Down

0 comments on commit d3b10bb

Please sign in to comment.