Skip to content

sarlo.io

Common file read/write operations for working with radar data

GeoXML

Stores spatial reference information from topophase XML data.

Attributes:

Name Type Description
xmin int

longitude minimum

ymin int

latitude minimum

xmax int

longitude maximum

ymax int

latitude maximum

xres

range resolution

yres

azimuth resolution

width int

x pixel numbers

height int

y pixel numbers

bounds list

[ymax, ymin, xmin, xmax]

Source code in sarlo/io.py
class GeoXML(object):
    """Stores spatial reference information from topophase XML data.

    Attributes:
        xmin: longitude minimum
        ymin: latitude minimum
        xmax: longitude maximum
        ymax: latitude maximum
        xres: range resolution
        yres: azimuth resolution
        width: x pixel numbers
        height: y pixel numbers
        bounds: [ymax, ymin, xmin, xmax]
    """

    def __init__(self, xmlfile: str):
        """Stores geospatial information from isce raster xml files

        Args:
            xmlfile: ISCE .xml file for a given raster

        Returns:
            self: the Sentinel1 object.
        """

        self.ymax: int = None
        self.xmax: int = None
        self.ymin: int = None
        self.xmin: int = None
        self.ysize: int = None
        self.xsize: int = None
        self.height: int = None
        self.width: int = None
        self.bounds: list = []
        self.xmlfile: str = xmlfile

    def parser(self):
        """Returns spatial reference data from Sentinel-1 phase correction XML

        Args:
            xmlfile: Str path to an xml file

        Returns:
            self: object with the spatial reference data as attributes
        """

        tree = ET.parse(self.xmlfile)
        root = tree.getroot()

        # parse the tree to extract georeferencing info
        delta_array = np.array([])
        start_array = np.array([])
        size_array = np.array([], dtype=np.int32)
        for size in root.iter("property"):
            if size.items()[0][1] == "size":
                size_array = np.append(size_array, int(size.find("value").text))
        for delta_val in root.iter("property"):
            if delta_val.items()[0][1] == "delta":
                delta_array = np.append(
                    delta_array, float(delta_val.find("value").text)
                )
        for start_val in root.iter("property"):
            if start_val.items()[0][1] == "startingvalue":
                start_array = np.append(
                    start_array, float(start_val.find("value").text)
                )
        end_array = start_array + size_array * delta_array

        # use standard geospatial terminology to reference file sizes and spatial bounds
        self.ymax = max(start_array[1], end_array[1])
        self.ymin = min(start_array[1], end_array[1])
        self.xmax = max(start_array[0], end_array[0])
        self.xmin = min(start_array[0], end_array[0])
        self.bounds = [self.ymax, self.ymin, self.xmin, self.xmax]
        self.width = size_array[0]
        self.height = size_array[1]
        self.ysize = delta_array[1]
        self.xsize = delta_array[0]

__init__(self, xmlfile) special

Stores geospatial information from isce raster xml files

Parameters:

Name Type Description Default
xmlfile str

ISCE .xml file for a given raster

required

Returns:

Type Description
self

the Sentinel1 object.

Source code in sarlo/io.py
def __init__(self, xmlfile: str):
    """Stores geospatial information from isce raster xml files

    Args:
        xmlfile: ISCE .xml file for a given raster

    Returns:
        self: the Sentinel1 object.
    """

    self.ymax: int = None
    self.xmax: int = None
    self.ymin: int = None
    self.xmin: int = None
    self.ysize: int = None
    self.xsize: int = None
    self.height: int = None
    self.width: int = None
    self.bounds: list = []
    self.xmlfile: str = xmlfile

parser(self)

Returns spatial reference data from Sentinel-1 phase correction XML

Parameters:

Name Type Description Default
xmlfile

Str path to an xml file

required

Returns:

Type Description
self

object with the spatial reference data as attributes

Source code in sarlo/io.py
def parser(self):
    """Returns spatial reference data from Sentinel-1 phase correction XML

    Args:
        xmlfile: Str path to an xml file

    Returns:
        self: object with the spatial reference data as attributes
    """

    tree = ET.parse(self.xmlfile)
    root = tree.getroot()

    # parse the tree to extract georeferencing info
    delta_array = np.array([])
    start_array = np.array([])
    size_array = np.array([], dtype=np.int32)
    for size in root.iter("property"):
        if size.items()[0][1] == "size":
            size_array = np.append(size_array, int(size.find("value").text))
    for delta_val in root.iter("property"):
        if delta_val.items()[0][1] == "delta":
            delta_array = np.append(
                delta_array, float(delta_val.find("value").text)
            )
    for start_val in root.iter("property"):
        if start_val.items()[0][1] == "startingvalue":
            start_array = np.append(
                start_array, float(start_val.find("value").text)
            )
    end_array = start_array + size_array * delta_array

    # use standard geospatial terminology to reference file sizes and spatial bounds
    self.ymax = max(start_array[1], end_array[1])
    self.ymin = min(start_array[1], end_array[1])
    self.xmax = max(start_array[0], end_array[0])
    self.xmin = min(start_array[0], end_array[0])
    self.bounds = [self.ymax, self.ymin, self.xmin, self.xmax]
    self.width = size_array[0]
    self.height = size_array[1]
    self.ysize = delta_array[1]
    self.xsize = delta_array[0]

Sentinel1

Class description.

Attributes:

Name Type Description
xml str

topsProc.xml file from ISCE processing

res int

range pixel size

lambda

wavelength

baseline int

distance between observation points (Satellite to Satellite)

center_range int

range between the antenna's phase center and the point on the ground

incid_angle int

The incidence angle is the angle defined by the incident radar beam and the vertical (normal) to the intercepting surface.

Source code in sarlo/io.py
class Sentinel1(object):
    """Class description.

    Attributes:
        xml: topsProc.xml file from ISCE processing
        res: range pixel size
        lambda: wavelength
        baseline: distance between observation points (Satellite to Satellite)
        center_range: range between the antenna's phase center and the point on the ground
        incid_angle:  The incidence angle is the angle defined by the incident radar beam and the vertical (normal) to the intercepting surface.
    """

    def __init__(self, xml: str):
        """Function description.

        Args:
            xml: some kind of xml file

        Returns:
            self: the Sentinel1 object.
        """

        self.xml: str = xml
        self.res: int = None
        self.llambda: int = None
        self.baseline: int = None
        self.center_range: int = None
        self.incid_angle: int = None

    def parse_xml(self):
        """Read Stripmap and TOPS xml files.

        Args:
            None.

        Returns:
            None. Updates the object attributes
        """

        tree = ET.parse(self.xml)
        root = tree.getroot()

        self.res = float(
            root.findall("./reference/instrument/range_pixel_size")[0].text
        )  # TOPS has
        self.llambda = float(
            root.findall("./reference/instrument/radar_wavelength")[0].text
        )  # TOPS has
        try:
            first_range = float(
                root.findall("./runTopo/inputs/range_first_sample")[0].text
            )
        except:
            first_range = float(
                root.findall("./runTopo/inputs/RANGE_FIRST_SAMPLE")[0].text
            )  # StartingRange is slant range to first pixel
        try:
            num_range_bin = int(root.findall("./runTopo/inputs/width")[0].text)
        except:
            num_range_bin = int(root.findall("./runTopo/inputs/WIDTH")[0].text)
        try:
            num_range_looks = int(
                root.findall("./runTopo/inputs/number_range_looks")[0].text
            )
        except:
            num_range_looks = int(
                root.findall("./runTopo/inputs/NUMBER_RANGE_LOOKS")[0].text
            )
        # Strange as the formula calls for the slant range not the center range. And this isn't how I would calculate center range either
        self.center_range = (
            first_range + (num_range_bin / 2 - 1) * self.res * num_range_looks
        )
        self.incid_angle = float(
            root.findall("./reference/instrument/incidence_angle")[0].text
        )
        baseline_top = float(root.findall("./baseline/perp_baseline_top")[0].text)
        baseline_bottom = float(root.findall("./baseline/perp_baseline_bottom")[0].text)
        self.baseline = (baseline_bottom + baseline_top) / 2

__init__(self, xml) special

Function description.

Parameters:

Name Type Description Default
xml str

some kind of xml file

required

Returns:

Type Description
self

the Sentinel1 object.

Source code in sarlo/io.py
def __init__(self, xml: str):
    """Function description.

    Args:
        xml: some kind of xml file

    Returns:
        self: the Sentinel1 object.
    """

    self.xml: str = xml
    self.res: int = None
    self.llambda: int = None
    self.baseline: int = None
    self.center_range: int = None
    self.incid_angle: int = None

parse_xml(self)

Read Stripmap and TOPS xml files.

Returns:

Type Description

None. Updates the object attributes

Source code in sarlo/io.py
def parse_xml(self):
    """Read Stripmap and TOPS xml files.

    Args:
        None.

    Returns:
        None. Updates the object attributes
    """

    tree = ET.parse(self.xml)
    root = tree.getroot()

    self.res = float(
        root.findall("./reference/instrument/range_pixel_size")[0].text
    )  # TOPS has
    self.llambda = float(
        root.findall("./reference/instrument/radar_wavelength")[0].text
    )  # TOPS has
    try:
        first_range = float(
            root.findall("./runTopo/inputs/range_first_sample")[0].text
        )
    except:
        first_range = float(
            root.findall("./runTopo/inputs/RANGE_FIRST_SAMPLE")[0].text
        )  # StartingRange is slant range to first pixel
    try:
        num_range_bin = int(root.findall("./runTopo/inputs/width")[0].text)
    except:
        num_range_bin = int(root.findall("./runTopo/inputs/WIDTH")[0].text)
    try:
        num_range_looks = int(
            root.findall("./runTopo/inputs/number_range_looks")[0].text
        )
    except:
        num_range_looks = int(
            root.findall("./runTopo/inputs/NUMBER_RANGE_LOOKS")[0].text
        )
    # Strange as the formula calls for the slant range not the center range. And this isn't how I would calculate center range either
    self.center_range = (
        first_range + (num_range_bin / 2 - 1) * self.res * num_range_looks
    )
    self.incid_angle = float(
        root.findall("./reference/instrument/incidence_angle")[0].text
    )
    baseline_top = float(root.findall("./baseline/perp_baseline_top")[0].text)
    baseline_bottom = float(root.findall("./baseline/perp_baseline_bottom")[0].text)
    self.baseline = (baseline_bottom + baseline_top) / 2

geo_data

Class description.

Attributes:

Name Type Description
file str

raster file to pull geo_data from

xmin int

longitude minimum

ymin int

latitute minimum

xmax int

longitude maximum

ymax int

longitude minimum

bounds list

[xmin, ymin, xmax, ymax]

xsize int

range resolution

ysize int

azimuth resolution

width int

number of columns

height int

number of rows

crs

coordinate resolution system

Source code in sarlo/io.py
class geo_data(object):
    """Class description.

    Attributes:
        file: raster file to pull geo_data from
        xmin: longitude minimum
        ymin: latitute minimum
        xmax: longitude maximum
        ymax: longitude minimum
        bounds: [xmin, ymin, xmax, ymax]
        xsize: range resolution
        ysize: azimuth resolution
        width: number of columns
        height: number of rows
        crs: coordinate resolution system
    """

    def __init__(self, file):
        """Create the geo_data reader object.

        Args:
            file: path to a raster file to open.

        Returns:
            self: the geo_data object.
        """

        self.file: str = file
        self.xmin: int = None
        self.ymin: int = None
        self.xmax: int = None
        self.ymax: int = None
        self.bounds: list = []
        self.xsize: int = None
        self.ysize: int = None
        self.width: int = None
        self.height: int = None
        self.crs = None

    def read_geotiff(self):
        """Reads raster spatial reference and projection data.

        Args:
            None.

        Returns:
            None. Updates the object's spatial attributes.
        """

        image = gdal.Open(self.file)
        self.width = image.RasterXSize
        self.height = image.RasterYSize

        xmin, xsize, xoff, ymax, yoff, ysize = image.GetGeoTransform()
        self.xmin = xmin
        self.xmax = xmin + xoff + (self.width * xsize)
        self.ymin = ymax + yoff + (self.height * ysize)
        self.ymax = ymax
        self.xsize = xsize
        self.ysize = ysize
        self.bounds = [self.xmin, self.ymin, self.xmax, self.ymax]

        proj = image.GetProjection()
        self.crs = proj.strip()

        # clear the file pointer to avoid memory issues
        image = None

__init__(self, file) special

Create the geo_data reader object.

Parameters:

Name Type Description Default
file

path to a raster file to open.

required

Returns:

Type Description
self

the geo_data object.

Source code in sarlo/io.py
def __init__(self, file):
    """Create the geo_data reader object.

    Args:
        file: path to a raster file to open.

    Returns:
        self: the geo_data object.
    """

    self.file: str = file
    self.xmin: int = None
    self.ymin: int = None
    self.xmax: int = None
    self.ymax: int = None
    self.bounds: list = []
    self.xsize: int = None
    self.ysize: int = None
    self.width: int = None
    self.height: int = None
    self.crs = None

read_geotiff(self)

Reads raster spatial reference and projection data.

Returns:

Type Description

None. Updates the object's spatial attributes.

Source code in sarlo/io.py
def read_geotiff(self):
    """Reads raster spatial reference and projection data.

    Args:
        None.

    Returns:
        None. Updates the object's spatial attributes.
    """

    image = gdal.Open(self.file)
    self.width = image.RasterXSize
    self.height = image.RasterYSize

    xmin, xsize, xoff, ymax, yoff, ysize = image.GetGeoTransform()
    self.xmin = xmin
    self.xmax = xmin + xoff + (self.width * xsize)
    self.ymin = ymax + yoff + (self.height * ysize)
    self.ymax = ymax
    self.xsize = xsize
    self.ysize = ysize
    self.bounds = [self.xmin, self.ymin, self.xmax, self.ymax]

    proj = image.GetProjection()
    self.crs = proj.strip()

    # clear the file pointer to avoid memory issues
    image = None

write_geotiff(directory, outfilename, geo, data)

write geotiff file

Parameters:

Name Type Description Default
directory str

directory outfile is to be written to

required
outfilename str

file name to be written

required
geo object

geospatial object with crs and size attributes

required
data

2D array of spatial data

required

Return None. Writes geotiff

Source code in sarlo/io.py
def write_geotiff(directory: str, outfilename: str, geo: object, data):
    """write geotiff file

    Args:
        directory: directory outfile is to be written to
        outfilename: file name to be written
        geo: geospatial object with crs and size attributes
        data: 2D array of spatial data

    Return
        None. Writes geotiff
    """

    driver = gdal.GetDriverByName("GTiff")
    outRaster = driver.Create(directory + outfilename + ".tif", geo.height, geo.width)
    outRaster.SetGeoTransform([geo.xmin, geo.xsize, 0, geo.ymax, 0, geo.ysize])
    outband = outRaster.GetRasterBand(1)
    outband.WriteArray(data)
    outRasterSRS = osr.SpatialReference()
    outRasterSRS.ImportFromEPSG(4326)
    outRaster.SetProjection(outRasterSRS.ExportToWkt())
    outband.FlushCache()

write_gif(data, directory, outfilename)

Write gif image file

Parameters:

Name Type Description Default
data

2D array to be written as a gif

required
directory str

directory in which file will be created

required
outfilename str

name of file to be created

required

Returns:

Type Description

None.

Source code in sarlo/io.py
def write_gif(data, directory: str, outfilename: str):
    """Write gif image file

    Args:
        data: 2D array to be written as a gif
        directory: directory in which file will be created
        outfilename: name of file to be created

    Return:
        None.
    """
    (row, col) = data.shape
    data = data.flatten()
    # Get the nonzerp indices and min/max
    nz_IND = np.nonzero(data)
    nz_min = data[nz_IND[0]].min()
    nz_max = data[nz_IND[0]].max()
    # Set the scaled values
    data255 = data.copy()
    data255[nz_IND[0]] = (data[nz_IND[0]] - nz_min) * 255 / (nz_max - nz_min) + 1
    # Reshape the array of scaled values
    data255 = np.reshape(data255, (row, col))

    gif_img = Image.open(os.path.join(directory, outfilename + ".tif"))
    gif_img.save(os.path.join(directory, outfilename + ".gif"), "GIF", transparency=0)

write_kml(directory, outfilename, geo, gif)

Writes kml overlay from gif file

Parameters:

Name Type Description Default
directory str

directory outfile is to be written to

required
outfilename str

file name to be written

required
geo object

geospatial object with crs and size attributes

required
gif str

gif file to be written to kml

required

Return None. Writes geotiff

Source code in sarlo/io.py
def write_kml(directory: str, outfilename: str, geo: object, gif: str):
    """Writes kml overlay from gif file

    Args:
        directory: directory outfile is to be written to
        outfilename: file name to be written
        geo: geospatial object with crs and size attributes
        gif: gif file to be written to kml

    Return
        None. Writes geotiff
    """

    kml = simplekml.Kml()
    arraykml = kml.newgroundoverlay(name=outfilename)
    arraykml.icon.href = os.path.join(directory, gif)
    arraykml.latlonbox.north = geo.ymax
    arraykml.latlonbox.south = geo.ymin
    arraykml.latlonbox.east = geo.xmax
    arraykml.latlonbox.west = geo.xmin
    kml.save(os.path.join(directory, outfilename + ".kml"))
Back to top