Skip to content

Commit

Permalink
Merge pull request #33 from pflickin/feature/corine
Browse files Browse the repository at this point in the history
Add method for creating CORINE Land Cover.
  • Loading branch information
lossyrob authored Jan 19, 2021
2 parents 980bb40 + bbe8f94 commit c9f82ed
Show file tree
Hide file tree
Showing 19 changed files with 393 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ python:
path: stactools_cli/
- method: pip
path: stactools_aster/
- method: pip
path: stactools_corine/
- method: pip
path: stactools_landsat/
- method: pip
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ See the [documentation page](https://stactools.readthedocs.io/en/latest/) for th
| `stactools_core` | Contains core library functionality that is used across the other projects |
| `stactools_cli` | Contains the command line interface (cli) for running the `stactools` command |
| `stactools_aster` | Methods and commands for working with ASTER data |
| `stactools_corine` | Methods and commands for working with CORINE Land Cover data |
| `stactools_planet` | Methods and commands for working with planet data |
| `stactools_landsat` | Methods and commands for working with landsat data (TODO) |
| `stactools_browse` | Contains a command for launching stac-browser against a local STAC |
Expand Down
4 changes: 4 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ RUN pip install -r /tmp/cli/requirements.txt
COPY stactools_aster/requirements.txt /tmp/aster/requirements.txt
RUN pip install -r /tmp/aster/requirements.txt

# Corine
COPY stactools_corine/requirements.txt /tmp/corine/requirements.txt
RUN pip install -r /tmp/corine/requirements.txt

# Landsat
COPY stactools_landsat/requirements.txt /tmp/landsat/requirements.txt
RUN pip install -r /tmp/landsat/requirements.txt
Expand Down
2 changes: 2 additions & 0 deletions scripts/env
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ STACTOOLS_SUBPACKAGE_DIRS=(
"stactools_core"
"stactools_cli"
"stactools_aster"
"stactools_corine"
"stactools_landsat"
"stactools_planet"
"stactools_browse"
Expand All @@ -13,6 +14,7 @@ STACTOOLS_COVERAGE_DIRS=(
"stactools_core/stactools/core"
"stactools_cli/stactools/cli"
"stactools_aster/stactools/aster"
"stactools_corine/stactools/corine"
"stactools_landsat/stactools/landsat"
"stactools_planet/stactools/planet"
"stactools_browse/stactools/browse"
Expand Down
1 change: 1 addition & 0 deletions stactools/corine
59 changes: 59 additions & 0 deletions stactools_core/stactools/core/projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from collections import abc
from copy import deepcopy

import pyproj


def epsg_from_utm_zone_number(utm_zone_number, south):
"""Return the EPSG code for a UTM zone number.
Args:
utm_zone_number (int): The UTM zone number.
south (bool): Whether this UTM zone is a south zone.
Returns:
int: The EPSG code number for the UTM zone.
"""
crs = pyproj.CRS.from_dict({
'proj': 'utm',
'zone': utm_zone_number,
'south': south
})

return int(crs.to_authority()[1])


def reproject_geom(src_crs, dest_crs, geom):
"""Reprojects a geometry represented as GeoJSON
from the src_crs to the dest crs.
Args:
src_crs (str): String that can be passed into
pyproj.Transformer.from_crs, e.g. "epsg:4326"
representing the current CRS of the geometry.
dest_crs (str): Similar to src_crs, representing the
desired CRS of the returned geometry.
geom (dict): The GeoJSON geometry
Returns:
dict: The reprojected geometry
"""
transformer = pyproj.Transformer.from_crs(src_crs,
dest_crs,
always_xy=True)
result = deepcopy(geom)

def fn(coords):
coords = list(coords)
for i in range(0, len(coords)):
coord = coords[i]
if isinstance(coord[0], abc.Sequence):
coords[i] = fn(coord)
else:
x, y = coord
coords[i] = transformer.transform(x, y)
return coords

result['coordinates'] = fn(result['coordinates'])

return result
3 changes: 3 additions & 0 deletions stactools_corine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# stactools_corine

A subpackage of stactools for working with CORINE data.
2 changes: 2 additions & 0 deletions stactools_corine/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rasterio==1.1.8 --no-binary=rasterio
pyproj==3.0.0.post1
49 changes: 49 additions & 0 deletions stactools_corine/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os
from imp import load_source
from setuptools import setup, find_namespace_packages
from glob import glob
import io

name = 'stactools_corine'
description = (
"Subpackage for working with CORINE Land Cover data in stactools, "
"a command line tool and Python library for working with STAC.")

__version__ = load_source(
'stactools.corine.version',
os.path.join(os.path.dirname(__file__),
'stactools/corine/version.py')).__version__

from os.path import (basename, splitext)

here = os.path.abspath(os.path.dirname(__file__))

# get the dependencies and installs
with io.open(os.path.join(here, 'requirements.txt'), encoding='utf-8') as f:
install_requires = [line.split(' ')[0] for line in f.read().split('\n')]

# Add stactools subpackage dependencies
install_requires.extend(['stactools_core=={}'.format(__version__)])

with open(os.path.join(here, 'README.md')) as readme_file:
readme = readme_file.read()

setup(name=name,
description=description,
version=__version__,
long_description=readme,
long_description_content_type="text/markdown",
author="stac-utils",
author_email='stac@radiant.earth',
url='https://github.com/stac-utils/stactools.git',
packages=find_namespace_packages(),
py_modules=[
splitext(basename(path))[0] for path in glob('stactools/*.py')
],
include_package_data=False,
install_requires=install_requires,
license="Apache Software License 2.0",
keywords=[
'stactools', 'psytac', 'corine', 'imagery', 'landcover',
'land cover', 'catalog', 'STAC'
])
16 changes: 16 additions & 0 deletions stactools_corine/stactools/corine/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# flake8: noqa

from stactools.corine.stac import create_item
from stactools.corine.cog import create_cogs

import stactools.core

stactools.core.use_fsspec()


def register_plugin(registry):
# Register subcommands

from stactools.corine import commands

registry.register_subcommand(commands.create_corine_command)
73 changes: 73 additions & 0 deletions stactools_corine/stactools/corine/cog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging
import os
from subprocess import Popen, PIPE, STDOUT

import pystac
from shapely.geometry import shape

from stactools.corine.constants import (ITEM_COG_IMAGE_NAME,
ITEM_TIF_IMAGE_NAME)
from stactools.core.projection import reproject_geom

logger = logging.getLogger(__name__)


def call(command):
def log_subprocess_output(pipe):
for line in iter(pipe.readline, b''): # b'\n'-separated lines
logger.info(line.decode("utf-8").strip('\n'))

process = Popen(command, stdout=PIPE, stderr=STDOUT)
with process.stdout:
log_subprocess_output(process.stdout)
return process.wait() # 0 means success


def cogify(input_path, output_path):
call([
'gdal_translate', '-of', 'COG', '-co', 'compress=deflate', input_path,
output_path
])


def _create_cog(item, cog_directory):
geom = item.geometry
crs = item.ext.projection.epsg
reprojected_geom = reproject_geom('epsg:4326', crs, geom)
bounds = shape(reprojected_geom).bounds
print(bounds)

tif_asset = item.assets.get(ITEM_TIF_IMAGE_NAME)
cogify(tif_asset.href, cog_directory)

return cog_directory


def create_cogs(item, cog_directory=None):
"""Create COGs from the HDF asset contained in the passed in STAC item.
Args:
item (pystac.Item): CORINE Item that contains an asset
with key equal to stactools.corine.constants.ITEM_METADATA_NAME,
which will be converted to COGs.
cog_directory (str): A URI of a directory to store COGs. This will be used
in conjunction with the file names based on the COG asset to store
the COG data. If not supplied, the directory of the Item's self HREF
will be used.
Returns:
pystac.Item: The same item, mutated to include assets for the
new COGs.
"""
if cog_directory is None:
cog_directory = os.path.dirname(item.get_self_href())

cog_href = os.path.join(cog_directory, '{}-cog.tif'.format(item.id))
_create_cog(item, cog_href)

asset = pystac.Asset(href=cog_href,
media_type=pystac.MediaType.COG,
roles=['data'],
title='Raster Dataset')

item.assets[ITEM_COG_IMAGE_NAME] = asset
51 changes: 51 additions & 0 deletions stactools_corine/stactools/corine/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import logging
import os

import click

from stactools.corine import stac, cog

logger = logging.getLogger(__name__)


def create_corine_command(cli):
"""Creates a command group for commands working with
CORINE Land Cover data.
"""
@cli.group('corine',
short_help=("Commands for working with "
"CORINE Land Cover data."))
def corine():
pass

@corine.command('create-item',
short_help='Create a STAC Item from a CORINE metadata file'
)
@click.argument('metadata_href')
@click.argument('dst')
@click.option('-c',
'--cogify',
is_flag=True,
help='Convert the tif into COG.')
def create_item_command(metadata_href, dst, cogify):
"""Creates a STAC Item based on metadata from an tif
CORINE Land Cover file.
METADATA_REF is the CORINE Land Cover metadata file. The
tif file will be located in the same directory as that of METADATA_REF.
DST is directory that a STAC Item JSON file will be created
in. This will have a filename that matches the ID, which will
is the name of the METADATA_REF, without it's file extension.
"""

item = stac.create_item(metadata_href)

item_path = os.path.join(dst, '{}.json'.format(item.id))
item.set_self_href(item_path)

if cogify:
cog.create_cogs(item)

item.save_object()

return corine
11 changes: 11 additions & 0 deletions stactools_corine/stactools/corine/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pystac

provider_name = 'European Environment Agency (EEA) under the framework of the Copernicus programme'

COPERNICUS_PROVIDER = pystac.Provider(name=provider_name,
url=('http://www.eea.europa.eu'),
roles=['producer', 'licensor'])

ITEM_COG_IMAGE_NAME = 'cog_image'
ITEM_TIF_IMAGE_NAME = 'tif_image'
ITEM_METADATA_NAME = 'metadata'
Loading

0 comments on commit c9f82ed

Please sign in to comment.