Skip to content

Commit

Permalink
i.sentinel.coverage + i.sentinel.parallel.download: new modules added
Browse files Browse the repository at this point in the history
  • Loading branch information
neteler committed Nov 29, 2020
2 parents d7db4de + b2be018 commit 3910ed0
Show file tree
Hide file tree
Showing 6 changed files with 617 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
MODULE_TOPDIR = ../..

PGM = i.sentinel.coverage

include $(MODULE_TOPDIR)/include/Make/Script.make

default: script
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<h2>DESCRIPTION</h2>

<em>i.sentinel.coverage</em> is a GRASS GIS addon Python script to
check the area coverage by Sentinel scenes selected by a filter.
<p>
The coverage test considers only the geometric coverage by Sentinel
scene footprints and does not include the cloud covered pixels.

<h2>EXAMPLE</h2>

<h3>Check Sentinel-2 scenes by region, cloud coverage, start and end time</h3>

<div class="code"><pre>
i.sentinel.coverage output=s2names.txt settings=/mnt/pgpass/.sentinel.txt \
start=2020-01-01 end=2020-01-31 clouds=70 area=mangkawuk@mangkawuk type=s2 minpercent=95
</pre></div>

<h3>Check Sentinel-2 scenes by names</h3>

<div class="code"><pre>
i.sentinel.coverage settings=/mnt/pgpass/.sentinel.txt output=s2names2.txt \
names=S2A_MSIL2A_20200104T024111_N0213_R089_T49MGU_20200104T061337,S2B_MSIL2A_20200129T023939_N0213_R089_T49MGU_20200201T153252 \
type=s2 minpercent=95 area=mangkawuk@mangkawuk
</pre></div>

<h2>SEE ALSO</h2>

<em>
<a href="i.sentinel.download.html">i.sentinel.download</a>,
<a href="v.dissolve.html">v.dissolve</a>,
<a href="v.overlay.html">v.overlay</a>,
<a href="v.to.db.html">v.to.db</a>
</em>

<h2>AUTHOR</h2>

Anika Weinmann, mundialis, weinmann at mundialis.de


<!--
<p>
<i>Last changed: $Date$</i>
-->
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
#!/usr/bin/env python3

############################################################################
#
# MODULE: i.sentinel.coverage
#
# AUTHOR(S): Anika Weinmann <weinmann at mundialis.de>
#
# PURPOSE: Checks the area coverage of the by filters selected Sentinel
# scenes
#
# COPYRIGHT: (C) 2020 by mundialis and the GRASS Development Team
#
# This program is free software under the GNU General Public
# License (>=v2). Read the file COPYING that comes with GRASS
# for details.
#
#############################################################################

#%Module
#% description: Checks the area coverage of Sentinel scenes selected by filters.
#% keyword: imagery
#% keyword: satellite
#% keyword: Sentinel
#% keyword: geometry
#% keyword: spatial query
#% keyword: area
#%end

#%option G_OPT_F_INPUT
#% key: settings
#% label: Full path to settings file (user, password)
#%end

#%option G_OPT_V_INPUT
#% key: area
#% description: Area input vector maps
#%end

#%option
#% key: start
#% type: string
#% description: Start date ('YYYY-MM-DD')
#% guisection: Filter
#%end

#%option
#% key: end
#% type: string
#% description: End date ('YYYY-MM-DD')
#% guisection: Filter
#%end

#%option
#% key: type
#% type: string
#% description: Sentinel-1 or Sentinel-2
#% required: no
#% multiple: no
#% options: s1,s2
#% answer: s2
#% guisection: Filter
#%end

#%option
#% key: clouds
#% type: integer
#% required: no
#% multiple: no
#% description: Maximum cloud cover percentage for Sentinel scene
#% guisection: Filter
#%end

#%option
#% key: minpercent
#% type: integer
#% required: no
#% multiple: no
#% description: Minimal percentage of coverage for Sentinel scene; error otherwise
#% guisection: Filter
#%end

#%option
#% key: names
#% type: string
#% description: Sentinel-1 or Sentinel-2 names
#% guisection: Filter
#% required: no
#% multiple: yes
#%end

#%option G_OPT_F_OUTPUT
#% key: output
#% label: Output file with a list of Sentinel-1 or Sentinel-2 scene names
#% required: no
#%end

import atexit
import os
from datetime import datetime, timedelta

import grass.script as grass

# initialize global vars
rm_regions = []
rm_vectors = []
rm_rasters = []


def cleanup():
nuldev = open(os.devnull, 'w')
kwargs = {
'flags': 'f',
'quiet': True,
'stderr': nuldev
}
for rmr in rm_regions:
if rmr in [x for x in grass.parse_command('g.list', type='region')]:
grass.run_command(
'g.remove', type='region', name=rmr, **kwargs)
for rmv in rm_vectors:
if grass.find_file(name=rmv, element='vector')['file']:
grass.run_command(
'g.remove', type='vector', name=rmv, **kwargs)
for rmrast in rm_rasters:
if grass.find_file(name=rmrast, element='raster')['file']:
grass.run_command(
'g.remove', type='raster', name=rmrast, **kwargs)


def scenename_split(scenename, sensor='s2'):
'''
When using the query option in i.sentinel.coverage and defining
specific filenames, the parameters Producttype, Start-Date, and End-Date
have to be definied as well. This function extracts these parameters from a
Sentinel-2 filename and returns the proper string to be passed to the query
option.
Args:
scenename(string): Name of the scene in the format
S2A_MSIL1C_20180822T155901_N0206_R097_T17SPV_20180822T212023
Returns:
producttype(string): Sentinel-2 producttype in the required parameter
format for i.sentinel.download, e.g. S2MSI2A
start_day(string): Date in the format YYYY-MM-DD, it is the acquisition
date -1 day
end_day(string): Date in the format YYYY-MM-DD, it is the acquisition
date +1 day
'''
try:
### get producttype
name_split = scenename.split('_')

if sensor == 's2':
type_string = name_split[1]
level_string = type_string.split('L')[1]
producttype = 'S2MSI' + level_string
date_string = name_split[2].split('T')[0]
elif sensor == 's1':
producttype = name_split[2][:3]
date_string = name_split[4].split('T')[0]
else:
grass.fatal(_("Unknown sensor %s" % sensor))
dt_obj = datetime.strptime(date_string, "%Y%m%d")
start_day_dt = dt_obj - timedelta(days=1)
end_day_dt = dt_obj + timedelta(days=1)
start_day = start_day_dt.strftime('%Y-%m-%d')
end_day = end_day_dt.strftime('%Y-%m-%d')
except:
grass.fatal("The name of the scene must have a format of e.g. S2A_MSIL1C_YYYYMMDDT155901_N0206_R097_T17SPV_20180822T212023")
return producttype, start_day, end_day


def get_size(vector):
tmpvector = 'tmp_getsize_%s' % str(os.getpid())
rm_vectors.append(tmpvector)
grass.run_command(
'g.copy', vector="%s,%s" % (vector, tmpvector), quiet=True)
if len(grass.vector_db(tmpvector)) == 0:
grass.run_command('v.db.addtable', map=tmpvector, quiet=True)
grass.run_command(
'v.db.addcolumn', map=tmpvector,
columns="tmparea DOUBLE PRECISION", quiet=True)
grass.run_command(
'v.to.db', map=tmpvector, columns='tmparea', option='area',
units='meters', quiet=True, overwrite=True)
sizeselected = grass.parse_command('v.db.select', map=tmpvector, flags="v")
sizesstr = [x.split('|')[1:] for x in sizeselected if x.startswith('tmparea|')][0]
sizes = [float(x) for x in sizesstr]
return sum(sizes)


def main():

global rm_regions, rm_rasters, rm_vectors

### check if the i.sentinel.download + i.sentinel.import addons are installed
if not grass.find_program('i.sentinel.download', '--help'):
grass.fatal(_("The 'i.sentinel.download' module was not found, install it first:") +
"\n" +
"g.extension i.sentinel")
if not grass.find_program('i.sentinel.import', '--help'):
grass.fatal(_("The 'i.sentinel.import' module was not found, install it first:") +
"\n" +
"g.extension i.sentinel")

# parameters
settings = options['settings']
output = options['output']
area = options['area']
if not grass.find_file(area, element='vector')['file']:
grass.fatal(_("Vector map <%s> not found") % area)
type = options['type']
if type == 's1':
producttype = 'GRD'
else:
producttype = 'S2MSI2A'

grass.message(_("Retrieving Sentinel footprints from ESA hub ..."))
fps = 'tmp_fps_%s' % str(os.getpid())
rm_vectors.append(fps)
if not options['names']:
s_list = grass.parse_command(
'i.sentinel.download',
settings=settings,
map=area,
clouds=options['clouds'],
producttype=producttype,
start=options['start'],
end=options['end'],
footprints=fps,
flags='lb',
quiet=True)
if len(s_list) == 0:
grass.fatal('No products found')
name_list = [x.split(' ')[1] for x in s_list]
else:
name_list = []
fp_list = []
for name in options['names'].split(','):
real_producttype, start_day, end_day = scenename_split(name, type)
if real_producttype != producttype:
grass.fatal("Producttype of ")
fpi = 'tmp_fps_%s_%s' % (name, str(os.getpid()))
try:
grass.run_command(
'i.sentinel.download',
settings=settings,
map=area,
producttype=producttype,
footprints=fpi,
start=start_day,
end=end_day,
flags='bl',
quiet=True)
name_list.append(name)
fp_list.append(fpi)
rm_vectors.append(fpi)
except:
grass.warning('%s was not found in %s' % (name, area))
grass.run_command(
'v.patch', input=','.join(fp_list), output=fps, quiet=True)

grass.message(_("Getting size of <%s> area ...") % area)
areasize = get_size(area)

grass.message(_("Getting size of footprints in area <%s> ...") % area)
fps_in_area = 'tmp_fps_in_area_%s' % str(os.getpid())
rm_vectors.append(fps_in_area)
grass.run_command(
'v.overlay', ainput=fps, atype='area', binput=area, operator='and',
output=fps_in_area, quiet=True)
grass.run_command(
'v.db.addcolumn', map=fps_in_area, columns="tmp INTEGER", quiet=True)
grass.run_command(
'v.db.update', map=fps_in_area, column='tmp', value=1, quiet=True)
fps_in_area_dis = 'tmp_fps_in_area_dis_%s' % str(os.getpid())
rm_vectors.append(fps_in_area_dis)
grass.run_command(
'v.dissolve', input=fps_in_area, output=fps_in_area_dis,
column='tmp', quiet=True)
grass.run_command(
'v.db.addtable', map=fps_in_area_dis, quiet=True)
fpsize = get_size(fps_in_area_dis)

percent = fpsize / areasize * 100.0
grass.message(_("%.2f percent of the area <%s> is covered") % (percent, area))

if options['minpercent']:
if percent < int(options['minpercent']):
grass.fatal("The percentage of coverage is too low (expected: %s)" % options['minpercent'])

# save list of Sentinel names
if output:
with open(output, 'w') as f:
f.write(','.join(name_list))
grass.message(_(
"Name of Sentinel scenes are written to file <%s>") % (output))

# TODO Sentinel-1 select only "one" scene (no overlap)


if __name__ == "__main__":
options, flags = grass.parser()
atexit.register(cleanup)
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
MODULE_TOPDIR = ../..

PGM = i.sentinel.parallel.download

include $(MODULE_TOPDIR)/include/Make/Script.make

default: script
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<h2>DESCRIPTION</h2>

<em>i.sentinel.parallel.download</em> downloads Sentinel-2 data defined
by scene names in parallel using <em>i.sentinel.download</em>.

<h2>SEE ALSO</h2>

<em>
<a href="i.sentinel.html">i.sentinel</a> module set,
<a href="i.sentinel.download.html">i.sentinel.download</a>
</em>

<h2>AUTHOR</h2>

Guido Riembauer, mundialis

<!--
<p>
<i>Last changed: $Date$</i>
-->

0 comments on commit 3910ed0

Please sign in to comment.