Commit c3b3905d authored by Jay's avatar Jay
Browse files

Initial commit

parent c8e44e48
Loading
Loading
Loading
Loading

requirements.txt

0 → 100644
+7 −0
Original line number Diff line number Diff line
csmapi
jinja
pyproj
numpy
scipy
gdal
pvl

setup.py

0 → 100644
+10 −0
Original line number Diff line number Diff line
from setuptools import setup, find_packages
setup(
    name='spaetzle',
    version='0.1.0',
    long_description='',
    packages=find_packages(),
    include_package_data=True,
    zip_safe=True,
    scripts=[]
)

spaetzle/__init__.py

0 → 100644
+12 −0
Original line number Diff line number Diff line
import ctypes
from distutils import sysconfig
import os
import warnings

from csmapi import csmapi

# Register the usgscam plugin with the csmapi

lib = ctypes.CDLL(os.path.abspath(os.path.join(sysconfig.get_python_lib(), '../../libusgscsm.so')))
if not lib:
    warnings.warn('Unable to load usgscsm shared library')

spaetzle/csm.py

0 → 100644
+308 −0
Original line number Diff line number Diff line
import datetime
import json
import os

from csmapi import csmapi
import jinja2

import numpy as np
import scipy.stats
import pyproj
import gdal
from gdal import ogr
import pvl


def generate_boundary(isize, nnodes=5, n_points=10):
    '''
    Generates a bounding box given a camera model

    Parameters
    ----------
    camera : object
             csmapi generated camera model

    nnodes : int
             Not sure

    n_points : int
               Number of points to generate between the corners of the bounding
               box per side.

    Returns
    -------
    boundary : list
               List of full bounding box
    '''
    x = np.linspace(0, isize[0], n_points)
    y = np.linspace(0, isize[1], n_points)
    boundary = [(i,0.) for i in x] + [(isize[0], i) for i in y[1:]] +\
               [(i, isize[1]) for i in x[::-1][1:]] + [(0.,i) for i in y[::-1][1:]]

    return boundary

def generate_latlon_boundary(camera, nnodes=5, semi_major=3396190, semi_minor=3376200, n_points=10):
    '''
    Generates a latlon bounding box given a camera model

    Parameters
    ----------
    camera : object
             csmapi generated camera model

    nnodes : int
             Not sure

    semi_major : int
                 Semimajor axis of the target body

    semi_minor : int
                 Semiminor axis of the target body

    n_points : int
               Number of points to generate between the corners of the bounding
               box per side.

    Returns
    -------
    lons : list
           List of longitude values

    lats : list
           List of latitude values

    alts : list
           List of altitude values
    '''
    isize = camera.getImageSize()
    isize = (isize.line, isize.samp)

    boundary = generate_boundary(isize, nnodes=nnodes, n_points = n_points)

    ecef = pyproj.Proj(proj='geocent', a=semi_major, b=semi_minor)
    lla = pyproj.Proj(proj='latlon', a=semi_major, b=semi_minor)

    gnds = np.empty((len(boundary), 3))

    for i, b in enumerate(boundary):
        # Could be potential errors or warnings from imageToGround
        try:
            gnd = camera.imageToGround(csmapi.ImageCoord(*b), 0)
        except:
            pass

        gnds[i] = [gnd.x, gnd.y, gnd.z]

    lons, lats, alts = pyproj.transform(ecef, lla, gnds[:,0], gnds[:,1], gnds[:,2])
    return lons, lats, alts

def generate_gcps(camera, nnodes=5, semi_major=3396190, semi_minor=3376200, n_points=10):
    '''
    Generates an area of ground control points formated as:
    <GCP Id="" Info="" Pixel="" Line="" X="" Y="" Z="" /> per record

    Parameters
    ----------
    camera : object
             csmapi generated camera model

    nnodes : int
             Not sure

    semi_major : int
                 Semimajor axis of the target body

    semi_minor : int
                 Semiminor axis of the target body

    n_points : int
               Number of points to generate between the corners of the bounding
               box per side.

    Returns
    -------
    gcps : list
           List of all gcp records generated
    '''
    lons, lats, alts = generate_latlon_boundary(camera, nnodes=nnodes,
                                                semi_major=semi_major,
                                                semi_minor=semi_minor,
                                                n_points=n_points)

    lla = np.vstack((lons, lats, alts)).T

    tr = zip(boundary, lla)

    gcps = []
    for i, t in enumerate(tr):
        l = '<GCP Id="{}" Info="{}" Pixel="{}" Line="{}" X="{}" Y="{}" Z="{}" />'.format(i, i, t[0][1], t[0][0], t[1][0], t[1][1], t[1][2])
        gcps.append(l)

    return gcps

def generate_latlon_footprint(camera, nnodes=5, semi_major=3396190, semi_minor=3376200, n_points=10):
    '''
    Generates a latlon footprint from a csmapi generated camera model

    Parameters
    ----------
    camera : object
             csmapi generated camera model

    nnodes : int
             Not sure

    semi_major : int
                 Semimajor axis of the target body

    semi_minor : int
                 Semiminor axis of the target body

    n_points : int
               Number of points to generate between the corners of the bounding
               box per side.

    Returns
    -------
    : object
      ogr multipolygon containing between one and two polygons
    '''
    lons, lats, _ = generate_latlon_boundary(camera, nnodes=nnodes,
                                                semi_major=semi_major,
                                                semi_minor=semi_minor,
                                                n_points=n_points)

    # Transform coords from -180, 180 to 0, 360
    # Makes crossing the maridian easier to identify
    ll_coords = [*zip(((lons + 180) % 360), lats)]

    ring = ogr.Geometry(ogr.wkbLinearRing)
    wrap_ring = ogr.Geometry(ogr.wkbLinearRing)

    poly = ogr.Geometry(ogr.wkbPolygon)
    wrap_poly = ogr.Geometry(ogr.wkbPolygon)

    multipoly = ogr.Geometry(ogr.wkbMultiPolygon)

    current_ring = ring
    switch_point = None
    previous_point = ll_coords[0]

    for coord in ll_coords:

        coord_diff = previous_point[0] - coord[0]

        if coord_diff > 0 and np.isclose(previous_point[0], 360, rtol = 1e-03) and \
                              np.isclose(coord[0], 0, atol=1e0, rtol=1e-01):
            regression = scipy.stats.linregress([previous_point[0], coord[0]], [previous_point[1], coord[1]])
            slope, b = regression.slope, regression.intercept
            current_ring.AddPoint(360 - 180, (slope*360 + b))
            current_ring = wrap_ring
            switch_point = 0 - 180, (slope*0 + b)
            current_ring.AddPoint(*switch_point)

        elif coord_diff < 0 and np.isclose(previous_point[0], 0, atol=1e0, rtol=1e-01) and \
                                np.isclose(coord[0], 360, rtol = 1e-03):
            regression = scipy.stats.linregress([previous_point[0], coord[0]], [previous_point[1], coord[1]])
            slope, b = regression.slope, regression.intercept
            current_ring.AddPoint(0 - 180, (slope*0 + b))
            current_ring.AddPoint(*switch_point)
            current_ring = ring
            current_ring.AddPoint(360 - 180, (slope*360 + b))

        lat, lon = coord
        current_ring.AddPoint(lat - 180, lon)
        previous_point = coord

    poly.AddGeometry(ring)
    wrap_poly.AddGeometry(wrap_ring)

    if not wrap_poly.IsEmpty():
        multipoly.AddGeometry(wrap_poly)

    if not poly.IsEmpty():
        multipoly.AddGeometry(poly)

    return multipoly

def generate_bodyfixed_footprint(camera, nnodes=5, semi_major=3396190, semi_minor=3376200, n_points=10):
    '''
    Generates a bodyfixed footprint from a csmapi generated camera model

    Parameters
    ----------
    camera : object
             csmapi generated camera model

    nnodes : int
             Not sure

    n_points : int
               Number of points to generate between the corners of the bounding
               box per side.

    Returns
    -------
    : object
      ogr polygon
    '''
    latlon_fp = generate_latlon_footprint(camera, nnodes=5, semi_major = semi_major, semi_minor = semi_minor, n_points = n_points)

    ecef = pyproj.Proj(proj='geocent', a=semi_major, b=semi_minor)
    lla = pyproj.Proj(proj='latlon', a=semi_major, b=semi_minor)

    # Step over all geometry objects in the latlon footprint
    for i in range(latlon_fp.GetGeometryCount()):
        latlon_coords = np.array(latlon_fp.GetGeometryRef(i).GetGeometryRef(0).GetPoints())

        # Check if the geometry object is populated with points
        if len(latlon_coords) > 0:
            x, y, z = pyproj.transform(lla, ecef,  latlon_coords[:,0], latlon_coords[:,1], latlon_coords[:,2])

            # Step over all coordinate points in a geometry object and update said point
            for j, _ in enumerate(latlon_coords):
                latlon_fp.GetGeometryRef(i).GetGeometryRef(0).SetPoint(j, x[j], y[j], 0)

    return latlon_fp

def generate_vrt(camera, raster_size, fpath, outpath=None, no_data_value=0):
    gcps = generate_gcps(camera)
    xsize, ysize = raster_size

    if outpath is None:
        outpath = os.path.dirname(fpath)
    outname = os.path.splitext(os.path.basename(fpath))[0] + '.vrt'
    outname = os.path.join(outpath, outname)

    xsize, ysize = raster_size
    vrt = r'''<VRTDataset rasterXSize="{{ xsize }}" rasterYSize="{{ ysize }}">
     <Metadata/>
     <GCPList Projection="{{ proj }}">
     {% for gcp in gcps -%}
       {{gcp}}
     {% endfor -%}
    </GCPList>
     <VRTRasterBand dataType="Float32" band="1">
       <NoDataValue>{{ no_data_value }}</NoDataValue>
       <Metadata/>
       <ColorInterp>Gray</ColorInterp>
       <SimpleSource>
         <SourceFilename relativeToVRT="0">{{ fpath }}</SourceFilename>
         <SourceBand>1</SourceBand>
         <SourceProperties rasterXSize="{{ xsize }}" rasterYSize="{{ ysize }}"
    DataType="Float32" BlockXSize="512" BlockYSize="512"/>
         <SrcRect xOff="0" yOff="0" xSize="{{ xsize }}" ySize="{{ ysize }}"/>
         <DstRect xOff="0" yOff="0" xSize="{{ xsize }}" ySize="{{ ysize }}"/>
       </SimpleSource>
     </VRTRasterBand>
    </VRTDataset>'''

    context = {'xsize':xsize, 'ysize':ysize,
               'gcps':gcps,
               'proj':'+proj=longlat +a=3396190 +b=3376200 +no_defs',
               'fpath':fpath,
               'no_data_value':no_data_value}
    template = jinja2.Template(vrt)
    tmp = template.render(context)
    warp_options = gdal.WarpOptions(format='VRT', dstNodata=0)
    gdal.Warp(outname, tmp, options=warp_options)