{"action":"created","comment":{"url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/comments/69574098","id":69574098,"diff_hunk":"@@ -0,0 +1,1431 @@\n+#! /usr/bin/env python\n+\n+\"\"\"\n+License:\n+    NASA Open Source Agreement 1.3\n+\n+Usage:\n+    build_elevation_band.py --help prints the help message\n+\"\"\"\n+\n+import os\n+import sys\n+import commands\n+import logging\n+import glob\n+import math\n+import datetime\n+from argparse import ArgumentParser\n+\n+\n+import numpy as np\n+from lxml import objectify as objectify\n+from osgeo import gdal, osr\n+\n+\n+from espa import Metadata\n+from espa import ENVIHeader\n+\n+\n+SOFTWARE_VERSION = 'ELEVATION_2.0.1'\n+\n+# Environment variable for the location of the elevation sources\n+ESPA_ELEVATION_DIR = 'ESPA_ELEVATION_DIR'\n+\n+\n+class GeoError(Exception):\n+    \"\"\"Exception to capture errors from the Geo class\"\"\"\n+    pass\n+\n+\n+class Geo(object):\n+    \"\"\"Provides methods for interfacing with geographic projections\"\"\"\n+\n+    @staticmethod\n+    def convert_imageXY_to_mapXY(image_x, image_y, transform):\n+        \"\"\"Translate image coordinates into map coordinates\"\"\"\n+\n+        map_x = (transform[0] +\n+                 image_x * transform[1] +\n+                 image_y * transform[2])\n+        map_y = (transform[3] +\n+                 image_x * transform[4] +\n+                 image_y * transform[5])\n+\n+        return (map_x, map_y)\n+\n+    @staticmethod\n+    def get_proj4_projection_string(img_filename):\n+        \"\"\"Determine the proj4 projection parameters for the specified image\n+\n+        Proj4 projection parameters are extracted from the specified image.\n+\n+        Args:\n+            img_filename (str): The image to extract the projection\n+                                information from.\n+\n+        Returns:\n+            proj4 (str): The proj4 projection string for the image.\n+        \"\"\"\n+\n+        data_set = gdal.Open(img_filename)\n+        if data_set is None:\n+            raise RuntimeError('GDAL failed to open ({0})'\n+                               .format(img_filename))\n+\n+        ds_srs = osr.SpatialReference()\n+        ds_srs.ImportFromWkt(data_set.GetProjection())\n+\n+        proj4 = ds_srs.ExportToProj4()\n+\n+        del ds_srs\n+        del data_set\n+\n+        return proj4\n+\n+    @staticmethod\n+    def warp(resampling_method=None,\n+             resolution_x=None,\n+             resolution_y=None,\n+             target_srs=None,\n+             image_extents=None,\n+             destination_no_data=None,\n+             output_data_type=None,\n+             output_format=None,\n+             source_data=None,\n+             output_filename=None):\n+\n+        logger = logging.getLogger(__name__)\n+\n+        # Base Command\n+        cmd = ['gdalwarp', '-wm', '2048', '-multi', '-overwrite']\n+\n+        # Add resampling\n+        if resampling_method is not None:\n+            cmd.extend(['-r', resampling_method])\n+\n+        # Add resolution\n+        if ((resolution_x is not None and resolution_y is None) or\n+                (resolution_x is None and resolution_y is not None)):\n+            raise GeoError('Must specify both X and Y resolution for warping')\n+\n+        if resolution_x is not None and resolution_y is not None:\n+            cmd.extend(['-tr', str(resolution_x), str(resolution_y)])\n+\n+        # Add target projection\n+        if target_srs is not None:\n+            cmd.extend(['-t_srs', ''.join(['\"', target_srs, '\"'])])\n+\n+        # Add image extents\n+        if image_extents is not None:\n+            cmd.extend(['-te',\n+                        str(image_extents['min_x']),\n+                        str(image_extents['min_y']),\n+                        str(image_extents['max_x']),\n+                        str(image_extents['max_y'])])\n+\n+        # Add output data type\n+        if destination_no_data is not None:\n+            cmd.extend(['-dstnodata', str(destination_no_data)])\n+\n+        # Add output data type\n+        if output_data_type is not None:\n+            cmd.extend(['-ot', output_data_type])\n+\n+        # Add output format\n+        if output_format is not None:\n+            cmd.extend(['-of', output_format])\n+\n+        # Add the source data\n+        if source_data is None:\n+            raise GeoError('Must provide source data')\n+\n+        if type(source_data) is list:\n+            cmd.extend(source_data)\n+        else:\n+            cmd.append(source_data)\n+\n+        # Add the output filename\n+        if output_filename is None:\n+            raise GeoError('Must provide the output filename')\n+\n+        cmd.append(output_filename)\n+\n+        # Convert to a string for the execution\n+        cmd = ' '.join(cmd)\n+\n+        output = ''\n+        try:\n+            logger.info('EXECUTING WARP COMMAND [{0}]'.format(cmd))\n+            output = execute_cmd(cmd)\n+        finally:\n+            if len(output) > 0:\n+                logger.info(output)\n+\n+\n+class MathError(Exception):\n+    \"\"\"Exception to capture errors from the Math class\"\"\"\n+    pass\n+\n+\n+class Math(object):\n+    \"\"\"Provides methods for mathematical algorithms\"\"\"\n+\n+    @staticmethod\n+    def point_in_polygon(vertices, x_c, y_c):\n+        \"\"\"Determines if a given point is within a closed polygon\n+\n+        Algorithm:\n+            Based on W. Randolph Franklin's algorithm:\n+                http://www.ecse.rpi.edu\n+                /Homepages/wrf/Research/Short_Notes/pnpoly.html\n+\n+            See also the Wikipedia page:\n+                http://eni.wikipedia.org\n+                /wiki/Point_in_polygon\n+\n+        Notes:\n+            Derived from Landsat IAS code, which is based on above algorithm.\n+            A closed list is expected.  Where v[N] = v[0]\n+            The polygon is always expected to be clock-wise segments.\n+        \"\"\"\n+\n+        count = len(vertices)\n+        if count < 4:\n+            raise MathError('Insufficient Line Segments')\n+\n+        # Convert to x and y lists\n+        (x_v, y_v) = map(list, zip(*vertices))\n+\n+        if x_v[0] != x_v[count - 1] and y_v[0] != y_v[count - 1]:\n+            raise MathError('Not A Closed Polygon Vertex List')\n+\n+        # Start off by saying outside the polygon\n+        inside_polygon = False\n+\n+        # Test each segment\n+        for index in xrange(count - 1):\n+            if (((x_v[index] > x_c) != (x_v[index + 1] > x_c)) and\n+                (y_c < ((y_v[index + 1] - y_v[index]) *\n+                        (x_c - x_v[index]) /\n+                        (x_v[index + 1] - x_v[index]) +\n+                        y_v[index]))):\n+\n+                inside_polygon = not inside_polygon\n+\n+        return inside_polygon\n+\n+    @staticmethod\n+    def longitude_norm(longitude):\n+        \"\"\"Calculates the \"canonical longitude\" for the longitude value\n+\n+        Returns:\n+            result (float): Equivalent longitude value in the range\n+                            [-180.0, 180.0) degrees\n+        \"\"\"\n+\n+        result = longitude\n+\n+        while result < -180.0:\n+            result += 360.0\n+\n+        while result >= 180.0:\n+            result -= 360.0\n+\n+        return result\n+\n+\n+def execute_cmd(cmd):\n+    \"\"\"Execute a command line\n+\n+    The specified command line is executed and the terminal output is returned\n+    or an exception is raised.\n+\n+    Returns:\n+        output (str): The stdout and/or stderr from the executed command.\n+    \"\"\"\n+\n+    output = ''\n+\n+    (status, output) = commands.getstatusoutput(cmd)\n+\n+    if status < 0:\n+        message = 'Application terminated by signal [{0}]'.format(cmd)\n+        if len(output) > 0:\n+            message = ' Stdout/Stderr is: '.join([message, output])\n+        raise Exception(message)\n+\n+    if status != 0:\n+        message = 'Application failed to execute [{0}]'.format(cmd)\n+        if len(output) > 0:\n+            message = ' Stdout/Stderr is: '.join([message, output])\n+        raise Exception(message)\n+\n+    if os.WEXITSTATUS(status) != 0:\n+        message = ('Application [{0}] returned error code [{1}]'\n+                   .format(cmd, os.WEXITSTATUS(status)))\n+        if len(output) > 0:\n+            message = ' Stdout/Stderr is: '.join([message, output])\n+        raise Exception(message)\n+\n+    return output\n+\n+\n+class RAMPCoverageError(Exception):\n+    \"\"\"Exception to capture RAMP data not covering the input data\"\"\"\n+    pass\n+\n+\n+class GLSOverWaterError(Exception):\n+    \"\"\"Exception to capture when GLS data is over water\"\"\"\n+    pass\n+\n+\n+class BaseElevation(object):\n+    \"\"\"Defines the base class object for elevation generation/processing\"\"\"\n+\n+    def __init__(self):\n+        \"\"\"Class initialization\"\"\"\n+        super(BaseElevation, self).__init__()\n+\n+        # Grab what we need from the environment first\n+        if ESPA_ELEVATION_DIR not in os.environ:\n+            raise RuntimeError('{0} environment variable not defined'\n+                               .format(ESPA_ELEVATION_DIR))\n+        self.espa_elevation_dir = os.environ.get(ESPA_ELEVATION_DIR)\n+\n+        # Padding to add to the max box\n+        self.maxbox_padding = 0.2\n+\n+        # Latitude coordinate limits\n+        self.north_latitude_limit = 90.0\n+        self.south_latitude_limit = -90.0\n+        # Longitude coordinate limits\n+        self.east_longitude_limit = 180.0\n+        self.west_longitude_limit = -180.0\n+\n+        # GLS latitude coordinate limits\n+        self.glsdem_north_limit = 83.0\n+        self.glsdem_south_limit = -53.0\n+\n+        # RAMP lat coordinate limits\n+        self.ramp_south_limit = -60.0\n+\n+        # WGS84 GEOID Adjustment Information\n+        self.wgs84_dir = 'geoid'\n+        self.wgs84_header_name = 'geoid.hdr'\n+        self.wgs84_image_name = 'geoid.img'\n+\n+        self.wgs84_header_path = os.path.join(self.espa_elevation_dir,\n+                                              self.wgs84_dir,\n+                                              self.wgs84_header_name)\n+        self.wgs84_image_path = os.path.join(self.espa_elevation_dir,\n+                                             self.wgs84_dir,\n+                                             self.wgs84_image_name)\n+\n+        # RAMP Information\n+        self.ramp_dir = 'ramp'\n+        self.ramp_header_name = 'ramp200dem_wgs_v2.hdr'\n+        self.ramp_image_name = 'ramp200dem_wgs_v2.img'\n+\n+        self.ramp_header_path = os.path.join(self.espa_elevation_dir,\n+                                             self.ramp_dir,\n+                                             self.ramp_header_name)\n+        self.ramp_image_path = os.path.join(self.espa_elevation_dir,\n+                                            self.ramp_dir,\n+                                            self.ramp_image_name)\n+\n+        # GLS Information\n+        self.gls_dir = 'gls'\n+        self.gls_projection_template = 'gls_projection.prj'\n+\n+        # GTOPT30 Information\n+        self.gtopo30_dir = 'gtopo30'\n+        self.gtopo30_dems_regexp = '[EW]???[NS]??.DEM'\n+        self.gtopo30_files_regexp = '[EW]???[NS]??.*'\n+        self.gtopo30_padding = 1.0  # Degrees, since we are in geographic\n+\n+        # Elevation format and naming\n+        self.elevation_format = 'ENVI'\n+        self.elevation_type_int16 = 'Int16'\n+        self.elevation_header_name_fmt = '{0}_elevation.hdr'\n+        self.elevation_image_name_fmt = '{0}_elevation.img'\n+        # Landsat uses bi-linear for all elevation warping\n+        self.elevation_resampling_method = 'bilinear'\n+\n+        # MOSAIC Filenames\n+        self.mosaic_header_name = 'espa-mosaic-elevation.hdr'\n+        self.mosaic_image_name = 'espa-mosaic-elevation.img'\n+\n+        # GDAL AUX files to remove\n+        self.gdal_aux_regexp = '*.img.aux.xml'\n+\n+        # Initialize the following to something bad\n+        self.bounding_north_latitude = -9999.0\n+        self.bounding_south_latitude = 9999.0\n+        self.bounding_east_longitude = -9999.0\n+        self.bounding_west_longitude = 9999.0\n+\n+        self.min_x_extent = None\n+        self.max_y_extent = None\n+        self.max_x_extent = None\n+        self.min_y_extent = None\n+\n+        self.pixel_resolution_x = None\n+        self.pixel_resolution_y = None\n+\n+        self.target_srs = None\n+\n+        self.elevation_header_name = None\n+        self.elevation_image_name = None\n+\n+    def parse_metadata(self):\n+        \"\"\"Implement this to parse the metadata file\"\"\"\n+        raise NotImplementedError('Please Implement Me In {0}'\n+                                  .format(str(type(self))))\n+\n+    def mosaic_tiles(self, tiles):\n+        \"\"\"MOSAIC the specified tiles into one file\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        '''\n+        Set the no data value to 0 so we fill-in with sea-level,\n+        because missing tiles in GLS will be water(ocean)\n+        NOTE - This is abusing GDAL, which does not correctly\n+               output ENVI headers.  The header fixing code, should\n+               be taking care of it.\n+        '''\n+        Geo.warp(destination_no_data=0,\n+                 output_data_type=self.elevation_type_int16,\n+                 output_format=self.elevation_format,\n+                 source_data=tiles,\n+                 output_filename=self.mosaic_image_name)\n+\n+    def mosaic_cleanup(self):\n+        \"\"\"Remove the MOSAIC files\"\"\"\n+\n+        os.unlink(self.mosaic_header_name)\n+        os.unlink(self.mosaic_image_name)\n+\n+    def warp_to_source_data(self, source_name):\n+        \"\"\"Warp to the source data\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        image_extents = {'min_x': self.min_x_extent,\n+                         'min_y': self.min_y_extent,\n+                         'max_x': self.max_x_extent,\n+                         'max_y': self.max_y_extent}\n+\n+        Geo.warp(resampling_method=self.elevation_resampling_method,\n+                 resolution_x=self.pixel_resolution_x,\n+                 resolution_y=self.pixel_resolution_y,\n+                 target_srs=self.target_srs,\n+                 image_extents=image_extents,\n+                 output_data_type=self.elevation_type_int16,\n+                 output_format=self.elevation_format,\n+                 source_data=source_name,\n+                 output_filename=self.elevation_image_name)\n+\n+    def _verify_ramp_overlap(self,\n+                             ramp_lines,\n+                             ramp_samples,\n+                             ramp_transform,\n+                             map_ul_x, map_ul_y,\n+                             map_ur_x, map_ur_y,\n+                             map_lr_x, map_lr_y,\n+                             map_ll_x, map_ll_y,\n+                             map_center_x, map_center_y):\n+        \"\"\"Verify that the input data is covered by the RAMP DEM\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        # Setup the X and Y polygons values\n+        vertices = list()\n+\n+        # UL (X, Y)\n+        (ramp_x, ramp_y) = Geo.convert_imageXY_to_mapXY(0.0, 0.0,\n+                                                        ramp_transform)\n+        vertices.append([ramp_x, ramp_y])\n+\n+        # UR (X, Y)\n+        (ramp_x, ramp_y) = Geo.convert_imageXY_to_mapXY(ramp_samples-1, 0.0,\n+                                                        ramp_transform)\n+        vertices.append([ramp_x, ramp_y])\n+\n+        # LR (X, Y)\n+        (ramp_x, ramp_y) = Geo.convert_imageXY_to_mapXY(ramp_samples-1,\n+                                                        ramp_lines-1,\n+                                                        ramp_transform)\n+        vertices.append([ramp_x, ramp_y])\n+\n+        # LL (X, Y)\n+        (ramp_x, ramp_y) = Geo.convert_imageXY_to_mapXY(0.0, ramp_lines-1,\n+                                                        ramp_transform)\n+        vertices.append([ramp_x, ramp_y])\n+\n+        # Close the polygon\n+        vertices.append(vertices[0])\n+\n+        logger.debug('Vertices: {0}'.format(str(vertices)))\n+\n+        # No longer needed\n+        del ramp_transform\n+\n+        # Determine how many points are within the RAMP DEM and error\n+        # accordingly\n+        points_in_polygon = 0\n+        if Math.point_in_polygon(vertices, map_ul_x, map_ul_y):\n+            points_in_polygon += 1\n+\n+        if Math.point_in_polygon(vertices, map_ur_x, map_ur_y):\n+            points_in_polygon += 1\n+\n+        if Math.point_in_polygon(vertices, map_lr_x, map_lr_y):\n+            points_in_polygon += 1\n+\n+        if Math.point_in_polygon(vertices, map_ll_x, map_ll_y):\n+            points_in_polygon += 1\n+\n+        if Math.point_in_polygon(vertices, map_center_x, map_center_y):\n+            points_in_polygon += 1\n+\n+        if points_in_polygon < 3:\n+            raise RAMPCoverageError('Insufficient overlap:'\n+                                    ' Only found {0} points within'\n+                                    ' data when at least 3 are'\n+                                    ' required'.format(points_in_polygon))\n+\n+    def generate_using_ramp(self):\n+        \"\"\"Retrieve the RAMP DEM data\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        # Link the RAMP data to the current directory\n+        if not os.path.exists(self.ramp_image_name):\n+            # Should only need to test for one of them\n+            os.symlink(self.ramp_header_path, self.ramp_header_name)\n+            os.symlink(self.ramp_image_path, self.ramp_image_name)\n+\n+        # Open the RAMP dataset\n+        ramp_ds = gdal.Open(self.ramp_image_name)\n+\n+        # Create the RAMP SRS\n+        ramp_srs = osr.SpatialReference()\n+        ramp_srs.ImportFromWkt(ramp_ds.GetProjection())\n+        logger.debug('RAMP WKT: {0}'.format(ramp_ds.GetProjection()))\n+\n+        # Create the Geographic SRS\n+        latlon_srs = ramp_srs.CloneGeogCS()\n+\n+        # Create the coordinate transformation\n+        ll_to_ramp = osr.CoordinateTransformation(latlon_srs, ramp_srs)\n+\n+        # Find the center of the max box\n+        center_latitude = ((self.bounding_north_latitude +\n+                            self.bounding_south_latitude) / 2.0)\n+        center_longitude = ((self.bounding_east_longitude +\n+                             self.bounding_west_longitude) / 2.0)\n+\n+        # Find the max box and center in map coordinates\n+        (map_ul_x, map_ul_y, height) = (\n+            ll_to_ramp.TransformPoint(self.bounding_west_longitude,\n+                                      self.bounding_north_latitude))\n+        (map_ur_x, map_ur_y, height) = (\n+            ll_to_ramp.TransformPoint(self.bounding_east_longitude,\n+                                      self.bounding_north_latitude))\n+        (map_lr_x, map_lr_y, height) = (\n+            ll_to_ramp.TransformPoint(self.bounding_east_longitude,\n+                                      self.bounding_south_latitude))\n+        (map_ll_x, map_ll_y, height) = (\n+            ll_to_ramp.TransformPoint(self.bounding_west_longitude,\n+                                      self.bounding_south_latitude))\n+        (map_center_x, map_center_y, height) = (\n+            ll_to_ramp.TransformPoint(center_longitude, center_latitude))\n+        logger.debug('map_ul_x, map_ul_y: {0}, {1}'\n+                     .format(map_ul_x, map_ul_y))\n+        logger.debug('map_ur_x, map_ur_y: {0}, {1}'\n+                     .format(map_ur_x, map_ur_y))\n+        logger.debug('map_lr_x, map_lr_y: {0}, {1}'\n+                     .format(map_lr_x, map_lr_y))\n+        logger.debug('map_ll_x, map_ll_y: {0}, {1}'\n+                     .format(map_ll_x, map_ll_y))\n+        logger.debug('map_center_x, map_center_y: {0}, {1}'\n+                     .format(map_center_x, map_center_y))\n+\n+        # Get the RAMP transform\n+        ramp_transform = ramp_ds.GetGeoTransform()\n+\n+        # Get the lines and samples from the RAMP band data\n+        ramp_band = ramp_ds.GetRasterBand(1)\n+        logger.debug('Lines, Samples: {0}, {1}'\n+                     .format(ramp_band.YSize, ramp_band.XSize))\n+\n+        # This causes an exception if they do not overlap\n+        self._verify_ramp_overlap(ramp_band.YSize, ramp_band.XSize,\n+                                  ramp_transform,\n+                                  map_ul_x, map_ul_y,\n+                                  map_ur_x, map_ur_y,\n+                                  map_lr_x, map_lr_y,\n+                                  map_ll_x, map_ll_y,\n+                                  map_center_x, map_center_y)\n+\n+        # Cleanup memory before warping\n+        del ll_to_ramp\n+        del latlon_srs\n+        del ramp_transform\n+        del ramp_band\n+        del ramp_srs\n+        del ramp_ds\n+\n+        # Warp to the source data\n+        self.warp_to_source_data(self.ramp_image_name)\n+\n+        # Remove the symlink to the RAMP DEM\n+        os.unlink(self.ramp_header_name)\n+        os.unlink(self.ramp_image_name)\n+\n+    def get_gtopo30_tile_list(self):\n+        \"\"\"Generate the list of GTOPO30 DEM tiles\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        # GTOPO30 tile information\n+        NORTH_LON_LOCATIONS = np.array([-180.0, -140.0, -100.0, -60.0, -20.0,\n+                                        20.0, 60.0, 100.0, 140.0])\n+\n+        SOUTH_LON_LOCATIONS = np.array([-180.0, -120.0, -60.0,\n+                                        0.0, 60.0, 120.0])\n+\n+        LAT_LOCATIONS = np.array([90.0, 40.0, -10.0, -60.0])\n+\n+        GTOPO30_TILE_SET_CUTOFF_LATITUDE = -60.0\n+\n+        '''\n+        The tiles do not necessarily go all the way to the boundary.  For\n+        example, the w100n90 tile only goes to -99.995833333334 longitude.\n+        Thus, if our UL longitude falls between -99.995833333334 and -100.0\n+        then we will need to use w140n90 to get the far left edge.  To\n+        account for this we'll artificially pad the East and West boundaries\n+        by 1 degree.\n+\n+        Note: this does not seem to cause problems on the North and South\n+        borders, so we will no worry about them.\n+        '''\n+        ul_lon = Math.longitude_norm(self.bounding_west_longitude -\n+                                     self.gtopo30_padding)\n+        lr_lon = Math.longitude_norm(self.bounding_east_longitude +\n+                                     self.gtopo30_padding)\n+        logger.debug('ul_lon = {0}'.format(ul_lon))\n+        logger.debug('lr_lon = {0}'.format(lr_lon))\n+\n+        '''\n+        There is a condition which could cause problems on the North and South\n+        boundaries as well.  This is if a scene comes very near the edge, but\n+        does not quite get there.  It could be that adding 1.5 to nrows before\n+        taking the ceiling (see calculation of nrows in get_gtopo30_data.c)\n+        causes the nrows to be large enough that we need data from the tile to\n+        the South, even though the scene stopped just before that tile.  To\n+        address this we will grow North and South by 1 degree as we did in the\n+        East and West directions above.  Note, the same problem can occur in\n+        the East and West directions, but was already addressed when adding\n+        one to the longitudes above.\n+        '''\n+        ul_lat = min((self.bounding_north_latitude + self.gtopo30_padding),\n+                     self.north_latitude_limit)\n+        lr_lat = max((self.bounding_south_latitude - self.gtopo30_padding),\n+                     self.south_latitude_limit)\n+        logger.debug('ul_lat = {0}'.format(ul_lat))\n+        logger.debug('lr_lat = {0}'.format(lr_lat))\n+\n+        # Determine the unique latitudes where the input data resides\n+        lat_list = list(set([min(LAT_LOCATIONS[\n+                                 np.where(ul_lat < LAT_LOCATIONS)]),\n+                             min(LAT_LOCATIONS[\n+                                 np.where(lr_lat < LAT_LOCATIONS)])]))\n+        logger.debug('lat_list = {0}'.format(lat_list))\n+\n+        tile_list = list()\n+        # Determine the unique longitudes where the input data resides\n+        for lat in lat_list:\n+            lon_list = list()\n+            if lat > GTOPO30_TILE_SET_CUTOFF_LATITUDE:\n+                lon_list.append(\n+                    max(NORTH_LON_LOCATIONS[\n+                        np.where(ul_lon > NORTH_LON_LOCATIONS)]))\n+                lon_list.append(\n+                    max(NORTH_LON_LOCATIONS[\n+                        np.where(lr_lon > NORTH_LON_LOCATIONS)]))\n+            else:\n+                lon_list.append(\n+                    max(SOUTH_LON_LOCATIONS[\n+                        np.where(ul_lon > SOUTH_LON_LOCATIONS)]))\n+                lon_list.append(\n+                    max(SOUTH_LON_LOCATIONS[\n+                        np.where(lr_lon > SOUTH_LON_LOCATIONS)]))\n+            lon_list = list(set(lon_list))\n+            logger.debug('lon_list = {0}'.format(lon_list))\n+\n+            n_s = 'n'\n+            if lat < 0:\n+                n_s = 's'\n+\n+            for lon in lon_list:\n+                e_w = 'e'\n+                if lon <= 0:\n+                    e_w = 'w'\n+\n+                # Already know if north/south, east/west so we just need\n+                # the positive value for the filename\n+                abs_lat = abs(lat)\n+                abs_lon = abs(lon)\n+\n+                tile_list.append('{0}{1:03}{2}{3:02}'\n+                                 .format(e_w, abs_lon, n_s, abs_lat))\n+\n+        return tile_list\n+\n+    def get_gtopo30_dems(self):\n+        \"\"\"Retrieves the GTOPO30 DEM archives and extracts them\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        elevation_dir = os.path.join(self.espa_elevation_dir, self.gtopo30_dir)\n+\n+        # Determine the GTOPO30 tiles\n+        tile_list = self.get_gtopo30_tile_list()\n+        logger.info('GTOPO30 Tile Names: {0}'.format(', '.join(tile_list)))\n+\n+        for tile in tile_list:\n+            tile_arch = '{0}.tar.gz'.format(tile)\n+            tile_path = os.path.join(elevation_dir, tile_arch)\n+\n+            output = ''\n+            try:\n+                cmd = 'cp {0} .'.format(tile_path)\n+                output = execute_cmd(cmd)\n+            finally:\n+                if len(output) > 0:\n+                    logger.info(output)\n+\n+            output = ''\n+            try:\n+                cmd = 'tar -xvf {0}'.format(tile_arch)\n+                output = execute_cmd(cmd)\n+            finally:\n+                if len(output) > 0:\n+                    logger.info(output)\n+\n+            os.unlink(tile_arch)\n+\n+        # Grab the tile DEM filenames from the extracted archives\n+        tile_elevation_list = glob.glob(self.gtopo30_dems_regexp)\n+        logger.info('GTOPO30 DEM Files: {0}'\n+                    .format(', '.join(tile_elevation_list)))\n+\n+        return tile_elevation_list\n+\n+    def generate_using_gtopo30(self):\n+        \"\"\"Generate the DEM using GTOPO30 data\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        # Retireve the GTOPO30 tiles\n+        tile_elevation_list = self.get_gtopo30_dems()\n+\n+        # MOSAIC the tiles together\n+        self.mosaic_tiles(tile_elevation_list)\n+\n+        # Warp to the source data\n+        self.warp_to_source_data(self.mosaic_image_name)\n+\n+        # Cleanup intermediate data\n+        self.mosaic_cleanup()\n+\n+        remove_list = glob.glob(self.gtopo30_files_regexp)\n+        for file_name in remove_list:\n+            os.unlink(file_name)\n+\n+    def generate_using_gls(self):\n+        \"\"\"Retrieve the GLS DEM data\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        elevation_dir = os.path.join(self.espa_elevation_dir, self.gls_dir)\n+\n+        '''\n+        GLS tiles are named with a format like n47w118 where the lat/long\n+        in the file name are the lower-left corner of the tile.  Each tile\n+        covers a 1-degree by 1-degree area. */\n+        '''\n+        start_latitude = int(math.floor(self.bounding_north_latitude))\n+        end_latitude = int(math.floor(self.bounding_south_latitude))\n+        start_longitude = int(math.floor(self.bounding_west_longitude))\n+        end_longitude = int(math.floor(self.bounding_east_longitude))\n+        logger.debug('Start Latitude: {0}'.format(start_latitude))\n+        logger.debug('End Latitude: {0}'.format(end_latitude))\n+        logger.debug('Start Longitude: {0}'.format(start_longitude))\n+        logger.debug('End Longitude: {0}'.format(end_longitude))\n+\n+        tile_list = list()\n+        for lat in xrange(end_latitude, start_latitude + 1):\n+            logger.debug('Latitude: {0}'.format(lat))\n+            n_s = 'n'\n+            if lat < 0:\n+                n_s = 's'\n+\n+            for lon in xrange(start_longitude, end_longitude + 1):\n+                logger.debug('Longitude: {0}'.format(lon))\n+\n+                e_w = 'e'\n+                if lon < 0:\n+                    e_w = 'w'\n+\n+                # Already know if north/south, east/west so we just need\n+                # the positive value for the filename\n+                abs_lat = abs(lat)\n+                abs_lon = abs(lon)\n+\n+                tile_list.append('{0}{1:02}{2}{3:03}'\n+                                 .format(n_s, abs_lat, e_w, abs_lon))\n+\n+        tile_count = len(tile_list)\n+        if tile_count == 0:\n+            raise RuntimeError('Unable to determine tiles while retrieving'\n+                               ' required GLS DEM tile list')\n+\n+        '''\n+        Generate lists of the BIL, HDR, and PRJ filenames as well as\n+        determining any missing tiles\n+        '''\n+        missing_count = 0\n+        bil_list = list()\n+        hdr_list = list()\n+        prj_list = list()\n+        prj_path = os.path.join(elevation_dir, self.gls_projection_template)\n+        logger.debug('PRJ Path: {0}'.format(prj_path))\n+        for tile in tile_list:\n+            bil_name = '{0}.bil'.format(tile)\n+            hdr_name = '{0}.hdr'.format(tile)\n+            prj_name = '{0}.prj'.format(tile)\n+\n+            bil_path = os.path.join(elevation_dir, bil_name)\n+            hdr_path = os.path.join(elevation_dir, hdr_name)\n+            logger.debug('BIL Path: {0}'.format(bil_path))\n+            logger.debug('HDR Path: {0}'.format(hdr_path))\n+\n+            if (os.path.isfile(bil_path) and\n+                    os.path.isfile(hdr_path)):\n+\n+                # Link them to the current directory\n+                os.symlink(bil_path, bil_name)\n+                os.symlink(hdr_path, hdr_name)\n+                os.symlink(prj_path, prj_name)\n+\n+                bil_list.append(bil_name)\n+                hdr_list.append(hdr_name)\n+                prj_list.append(prj_name)\n+\n+            else:\n+                logger.debug('Missing Tile: {0}'.format(bil_name))\n+                missing_count += 1\n+\n+        logger.debug('Expected Tile Count: {0}'.format(tile_count))\n+        logger.debug('Missing Tile Count: {0}'.format(missing_count))\n+\n+        # Check if we are missing all the tiles, which indicates over water\n+        if missing_count >= tile_count:\n+            # Cleanup the tile links\n+            for file_name in bil_list:\n+                os.unlink(file_name)\n+            for file_name in hdr_list:\n+                os.unlink(file_name)\n+            for file_name in prj_list:\n+                os.unlink(file_name)\n+            raise GLSOverWaterError('GLS DEM is over water')\n+\n+        logger.info('GLS DEM Files: {0}'.format(', '.join(bil_list)))\n+\n+        # MOSAIC the tiles together\n+        self.mosaic_tiles(bil_list)\n+\n+        # Warp to the source data\n+        self.warp_to_source_data(self.mosaic_image_name)\n+\n+        # Cleanup intermediate data\n+        self.mosaic_cleanup()\n+\n+        for file_name in bil_list:\n+            os.unlink(file_name)\n+        for file_name in hdr_list:\n+            os.unlink(file_name)\n+        for file_name in prj_list:\n+            os.unlink(file_name)\n+\n+    def adjust_elevation_to_wgs84(self):\n+        \"\"\"Adjusts the warped elevation to the WGS84 GEOID\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        geoid_header_name = 'espa-geoid.hdr'\n+        geoid_image_name = 'espa-geoid.img'\n+\n+        # Link the WGS84 GEOID data to the current directory\n+        if not os.path.exists(self.wgs84_image_name):\n+            # Should only need to test for one of them\n+            os.symlink(self.wgs84_header_path, self.wgs84_header_name)\n+            os.symlink(self.wgs84_image_path, self.wgs84_image_name)\n+\n+        image_extents = {'min_x': self.min_x_extent,\n+                         'min_y': self.min_y_extent,\n+                         'max_x': self.max_x_extent,\n+                         'max_y': self.max_y_extent}\n+\n+        # Warp the GEOID to the elevation/product projection\n+        Geo.warp(resampling_method=self.elevation_resampling_method,\n+                 resolution_x=self.pixel_resolution_x,\n+                 resolution_y=self.pixel_resolution_y,\n+                 target_srs=self.target_srs,\n+                 image_extents=image_extents,\n+                 output_data_type=self.elevation_type_int16,\n+                 output_format=self.elevation_format,\n+                 source_data=self.wgs84_image_name,\n+                 output_filename=geoid_image_name)\n+\n+        # Remove the symlink to the WGS84 GEOID\n+        os.unlink(self.wgs84_header_name)\n+        os.unlink(self.wgs84_image_name)\n+\n+        # Open the WGS84 and elevation datasets\n+        geoid_ds = gdal.Open(geoid_image_name)\n+        elevation_ds = gdal.Open(self.elevation_image_name)\n+\n+        # Get the WGS84 and elevation band information\n+        geoid_band = geoid_ds.GetRasterBand(1)\n+        elevation_band = elevation_ds.GetRasterBand(1)\n+\n+        # Verify they are the same size, otherwise something broke\n+        if (geoid_band.XSize != elevation_band.XSize or\n+                geoid_band.YSize != elevation_band.YSize):\n+            raise Exception('The size of the GEOID and elevation do not match')\n+\n+        # Read the data from both into memory\n+        geoid_data = geoid_band.ReadAsArray(0, 0,\n+                                            geoid_band.XSize,\n+                                            geoid_band.YSize).astype(np.int16)\n+        elevation_data = (elevation_band\n+                          .ReadAsArray(0, 0,\n+                                       elevation_band.XSize,\n+                                       elevation_band.YSize).astype(np.int16))\n+\n+        # Use numpy math to add the datasets together\n+        new_data = (elevation_data + geoid_data).astype(np.int16)\n+\n+        # Write the updated data to the elevation filename\n+        with open(self.elevation_image_name, 'wb') as img_fd:\n+            new_data.tofile(img_fd)\n+\n+        # Cleanup memory\n+        del new_data\n+        del elevation_data\n+        del elevation_band\n+        del elevation_ds\n+        del geoid_data\n+        del geoid_band\n+        del geoid_ds\n+\n+        # Remove the warped GEOID data\n+        os.unlink(geoid_header_name)\n+        os.unlink(geoid_image_name)\n+\n+    def add_elevation_band_to_xml(self, elevation_source):\n+        \"\"\"Adds the elevation band to the ESPA Metadata XML file\"\"\"\n+\n+        espa_metadata = Metadata()\n+        espa_metadata.parse(xml_filename=self.xml_filename)\n+\n+        # Create an element maker\n+        em = objectify.ElementMaker(annotate=False,\n+                                    namespace=None,\n+                                    nsmap=None)\n+\n+        # Create a band element\n+        band = em.band()\n+\n+        # Set attributes for the band element\n+        band.set('product', 'elevation')\n+        band.set('source', elevation_source)\n+        band.set('name', 'elevation')\n+        band.set('category', 'image')\n+        band.set('data_type', 'INT16')\n+        band.set('nlines', str(self.number_of_lines))\n+        band.set('nsamps', str(self.number_of_samples))\n+        # Don't really have a fill value, but setting to -9999 for consistency\n+        # with our other INT16 products\n+        band.set('fill_value', '-9999')\n+\n+        # Add elements to the band object\n+        band.short_name = em.element('ELEVATION')\n+        band.long_name = em.element('elevation')\n+        band.file_name = em.element(self.elevation_image_name)\n+\n+        # Create a pixel size element\n+        band.pixel_size = em.element()\n+        # Add attrbutes to the pixel size object\n+        band.pixel_size.set('x', str(self.pixel_resolution_x))\n+        band.pixel_size.set('y', str(self.pixel_resolution_x))\n+        band.pixel_size.set('units', self.pixel_units)\n+\n+        band.resample_method = em.element('bilinear')\n+        band.data_units = em.element('meters')\n+\n+        # Set the software version\n+        band.app_version = em.element(SOFTWARE_VERSION)\n+\n+        # Get the production date and time in string format\n+        # Strip the microseconds and add a Z\n+        date_now = ('{0}Z'.format(datetime.datetime.now()\n+                                  .strftime('%Y-%m-%dT%H:%M:%S')))\n+        band.production_date = em.element(date_now)\n+\n+        # Append the band to the XML\n+        espa_metadata.xml_object.bands.append(band)\n+\n+        # Validate the XML\n+        espa_metadata.validate()\n+\n+        # Write it to the XML file\n+        espa_metadata.write(xml_filename=self.xml_filename)\n+\n+        # Memory cleanup\n+        del espa_metadata\n+\n+    def generate(self):\n+        \"\"\"Generates the elevation\"\"\"\n+\n+        logger = logging.getLogger(__name__)\n+\n+        self.parse_metadata()\n+\n+        # Just a bunch of debug reporting follows\n+        logger.debug('bounding_north_latitude = {0}'\n+                     .format(self.bounding_north_latitude))\n+        logger.debug('bounding_south_latitude = {0}'\n+                     .format(self.bounding_south_latitude))\n+        logger.debug('bounding_east_longitude = {0}'\n+                     .format(self.bounding_east_longitude))\n+        logger.debug('bounding_west_longitude = {0}'\n+                     .format(self.bounding_west_longitude))\n+\n+        logger.debug('min_x_extent = {0}'.format(self.min_x_extent))\n+        logger.debug('max_y_extent = {0}'.format(self.max_y_extent))\n+        logger.debug('max_x_extent = {0}'.format(self.max_x_extent))\n+        logger.debug('min_y_extent = {0}'.format(self.min_y_extent))\n+\n+        logger.debug('pixel_resolution_x = {0}'\n+                     .format(self.pixel_resolution_x))\n+        logger.debug('pixel_resolution_y = {0}'\n+                     .format(self.pixel_resolution_y))\n+\n+        logger.debug('elevation_header_name = {0}'\n+                     .format(self.elevation_header_name))\n+        logger.debug('elevation_image_name = {0}'\n+                     .format(self.elevation_image_name))\n+\n+        logger.debug('espa_elevation_dir = {0}'\n+                     .format(self.espa_elevation_dir))\n+\n+        # Pad the max box coordinate values\n+        self.bounding_north_latitude += self.maxbox_padding\n+        self.bounding_south_latitude -= self.maxbox_padding\n+        self.bounding_east_longitude += self.maxbox_padding\n+        self.bounding_west_longitude -= self.maxbox_padding\n+\n+        elevation_source = 'gtopo30'\n+        # Retrieve the tiles, mosaic, and warp to the source data\n+        if self.bounding_north_latitude <= self.ramp_south_limit:\n+            try:\n+                logger.info('Attempting to use RAMP DEM')\n+                self.generate_using_ramp()\n+                '''\n+                According to Landsat the RAMP DEM does not need adjusting to\n+                the WGS84 GEOID\n+                '''\n+                elevation_source = 'ramp'\n+            except RAMPCoverageError:\n+                logger.exception('RAMP DEM failed to cover input data'\n+                                 ' defaulting to GTOPO30')\n+                self.generate_using_gtopo30()\n+                self.adjust_elevation_to_wgs84()\n+\n+        elif ((self.bounding_north_latitude <= self.glsdem_south_limit and\n+               self.bounding_north_latitude > self.ramp_south_limit and\n+               self.bounding_south_latitude <= self.glsdem_south_limit and\n+               self.bounding_south_latitude > self.ramp_south_limit) or\n+              (self.bounding_north_latitude >= self.glsdem_north_limit and\n+               self.bounding_south_latitude >= self.glsdem_north_limit)):\n+\n+            logger.info('Using GTOPO30 DEM')\n+            self.generate_using_gtopo30()\n+            self.adjust_elevation_to_wgs84()\n+\n+        else:\n+            try:\n+                logger.info('Attempting to use GLS DEM')\n+                self.generate_using_gls()\n+                elevation_source = 'gls'\n+            except GLSOverWaterError:\n+                logger.exception('GLS DEM over water defaulting to GTOPO30')\n+                self.generate_using_gtopo30()\n+\n+            self.adjust_elevation_to_wgs84()\n+\n+        # Cleanup the GDAL generated auxiliary files\n+        remove_list = glob.glob(self.gdal_aux_regexp)\n+        for file_name in remove_list:\n+            os.unlink(file_name)\n+\n+        # Update the ENVI header\n+        # Specify the data type, because we were using Float32, but the final\n+        # was written as Int16.\n+        envi_header = ENVIHeader(self.elevation_header_name)\n+        envi_header.update_envi_header(band_names='band 1 - elevation',\n+                                       data_type=2,\n+                                       no_data_value=-9999)\n+\n+        # Only add the elevation band to the XML file\n+        try:\n+            # Determine if we are processing using the XML\n+            getattr(self, 'xml_filename')\n+        except AttributeError:\n+            pass\n+        else:\n+            # We are processing using XML so add the band\n+            self.add_elevation_band_to_xml(elevation_source)\n+\n+\n+XML_PRODUCT_CODES = ['L1T', 'L1G',\n+                     'L1TP', 'L1GT', 'L1GS']\n+\n+\n+class XMLElevation(BaseElevation):\n+    \"\"\"Defines the class object for XML based elevation generation\"\"\"\n+\n+    def __init__(self, xml_filename):\n+        \"\"\"Class initialization\"\"\"\n+        super(XMLElevation, self).__init__()\n+\n+        self.xml_filename = xml_filename\n+\n+    def parse_metadata(self):\n+        \"\"\"Parse the input metadata file\n+\n+        Search for the desired fields in the metadata file, and set the\n+        associated class variables with the values read for that field.\n+\n+        Notes:\n+          It is expected the input metadata file is an ESPA *.xml file and\n+          follows the format of those files.\n+        \"\"\"\n+\n+        espa_metadata = Metadata()\n+        espa_metadata.parse(xml_filename=self.xml_filename)\n+\n+        self.bounding_north_latitude = float(espa_metadata.xml_object\n+                                             .global_metadata\n+                                             .bounding_coordinates.north)\n+        self.bounding_south_latitude = float(espa_metadata.xml_object\n+                                             .global_metadata\n+                                             .bounding_coordinates.south)\n+        self.bounding_east_longitude = float(espa_metadata.xml_object\n+                                             .global_metadata\n+                                             .bounding_coordinates.east)\n+        self.bounding_west_longitude = float(espa_metadata.xml_object\n+                                             .global_metadata\n+                                             .bounding_coordinates.west)\n+\n+        for corner_point in (espa_metadata.xml_object\n+                             .global_metadata\n+                             .projection_information.corner_point):\n+            if corner_point.attrib['location'] == 'UL':\n+                self.min_x_extent = float(corner_point.attrib['x'])\n+                self.max_y_extent = float(corner_point.attrib['y'])\n+            if corner_point.attrib['location'] == 'LR':\n+                self.max_x_extent = float(corner_point.attrib['x'])\n+                self.min_y_extent = float(corner_point.attrib['y'])\n+\n+        product_id = None\n+        try:\n+            product_id = espa_metadata.xml_object.global_metadata.product_id\n+        except:\n+            product_id = espa_metadata.xml_object.global_metadata.scene_id\n+\n+        self.elevation_header_name = (self.elevation_header_name_fmt\n+                                      .format(product_id))\n+        self.elevation_image_name = (self.elevation_image_name_fmt\n+                                     .format(product_id))\n+\n+        for band in espa_metadata.xml_object.bands.band:\n+            if (band.attrib['product'] in XML_PRODUCT_CODES and\n+                    band.attrib['name'] == 'band1'):\n+                self.target_srs = (\n+                    Geo.get_proj4_projection_string(str(band.file_name)))\n+                self.pixel_resolution_x = float(band.pixel_size.attrib['x'])\n+                self.pixel_resolution_y = float(band.pixel_size.attrib['y'])\n+                self.pixel_units = band.pixel_size.attrib['units']\n+                self.number_of_lines = band.attrib['nlines']\n+                self.number_of_samples = band.attrib['nsamps']\n+                break\n+\n+        # Adjust the coordinates for image extents becuse they are in\n+        # center of pixel, and we need to supply the warping with actual\n+        # extents\n+        self.min_x_extent = (self.min_x_extent -\n+                             self.pixel_resolution_x * 0.5)\n+        self.max_x_extent = (self.max_x_extent +\n+                             self.pixel_resolution_x * 0.5)\n+        self.min_y_extent = (self.min_y_extent -\n+                             self.pixel_resolution_y * 0.5)\n+        self.max_y_extent = (self.max_y_extent +\n+                             self.pixel_resolution_y * 0.5)\n+\n+        del espa_metadata\n+\n+\n+class MTLElevation(BaseElevation):\n+    \"\"\"Defines the class object for MTL based elevation generation\"\"\"\n+\n+    def __init__(self, mtl_filename):\n+        \"\"\"Class initialization\"\"\"\n+        super(MTLElevation, self).__init__()\n+\n+        self.mtl_filename = mtl_filename\n+\n+    def parse_metadata(self):\n+        \"\"\"Parse the input metadata file\n+\n+        Search for the desired fields in the metadata file, and set the\n+        associated class variables with the values read for that field.\n+\n+        Notes:\n+          It is expected the input metadata file is an LPGS _MTL.txt file and\n+          follows the format (contains the same fields) of those files.\n+        \"\"\"\n+\n+        # Get the logger\n+        logger = logging.getLogger(__name__)\n+\n+        file_name_band_1 = None\n+        product_id = None\n+\n+        # open the metadata file for reading\n+        with open(self.mtl_filename, 'r') as mtl_fd:\n+\n+            # read and process one line at a time, using the = sign as the\n+            # deliminator for the desired parameters.  remove the trailing\n+            # end of line and leading/trailing white space\n+            for line in mtl_fd:\n+                myline = line.rstrip('\\r\\n').strip()\n+                # logger.debug('DEBUG: [{0}]'.format(myline))\n+                # break out if at the end of the metadata in the metadata file\n+                if myline == 'END':\n+                    break\n+\n+                # process the myline which should be parameter = value strings\n+                # some strings may need the \"s stripped from the value itself\n+                (param, value) = myline.split('=', 1)\n+                param = param.strip()\n+                value = value.strip()\n+                try:\n+                    f_value = float(value)\n+                except ValueError:\n+                    pass\n+                # logger.debug('    DEBUG: param [{0}]'.format(param))\n+                # logger.debug('    DEBUG: value [{0}]'\n+                #              .format(value.strip('\\\"')))\n+                if param == 'CORNER_UL_LAT_PRODUCT':\n+                    if f_value > self.bounding_north_latitude:\n+                        self.bounding_north_latitude = f_value\n+                elif param == 'CORNER_UR_LAT_PRODUCT':\n+                    if f_value > self.bounding_north_latitude:\n+                        self.bounding_north_latitude = f_value\n+                elif param == 'CORNER_LL_LAT_PRODUCT':\n+                    if f_value < self.bounding_south_latitude:\n+                        self.bounding_south_latitude = f_value\n+                elif param == 'CORNER_LR_LAT_PRODUCT':\n+                    if f_value < self.bounding_south_latitude:\n+                        self.bounding_south_latitude = f_value\n+                elif param == 'CORNER_UL_LON_PRODUCT':\n+                    if f_value < self.bounding_west_longitude:\n+                        self.bounding_west_longitude = f_value\n+                elif param == 'CORNER_UR_LON_PRODUCT':\n+                    if f_value > self.bounding_east_longitude:\n+                        self.bounding_east_longitude = f_value\n+                elif param == 'CORNER_LL_LON_PRODUCT':\n+                    if f_value < self.bounding_west_longitude:\n+                        self.bounding_west_longitude = f_value\n+                elif param == 'CORNER_LR_LON_PRODUCT':\n+                    if f_value > self.bounding_east_longitude:\n+                        self.bounding_east_longitude = f_value\n+                elif param == 'CORNER_UL_PROJECTION_X_PRODUCT':\n+                    self.min_x_extent = f_value\n+                elif param == 'CORNER_UL_PROJECTION_Y_PRODUCT':\n+                    self.max_y_extent = f_value\n+                elif param == 'CORNER_LR_PROJECTION_X_PRODUCT':\n+                    self.max_x_extent = f_value\n+                elif param == 'CORNER_LR_PROJECTION_Y_PRODUCT':\n+                    self.min_y_extent = f_value\n+                elif param == 'GRID_CELL_SIZE_REFLECTIVE':\n+                    self.pixel_resolution_x = f_value\n+                    self.pixel_resolution_y = f_value\n+                elif param == 'LANDSAT_SCENE_ID':\n+                    product_id = value.strip('\\\"')\n+                elif param == 'FILE_NAME_BAND_1':\n+                    file_name_band_1 = value.strip('\\\"')\n+\n+        # validate retrieval of parameters\n+        if self.bounding_north_latitude == -9999.0:\n+            raise RuntimeError('Obtaining north bounding metadata field from:'\n+                               ' [{0}]'.format(self.mtl_filename))\n+\n+        if self.bounding_south_latitude == 9999.0:\n+            raise RuntimeError('Obtaining south bounding metadata field from:'\n+                               ' [{0}]'.format(self.mtl_filename))\n+\n+        if self.bounding_east_longitude == -9999.0:\n+            raise RuntimeError('Obtaining east bounding metadata field from:'\n+                               ' [{0}]'.format(self.mtl_filename))\n+\n+        if self.bounding_west_longitude == 9999.0:\n+            raise RuntimeError('Obtaining west bounding metadata field from:'\n+                               ' [{0}]'.format(self.mtl_filename))\n+\n+        if self.min_x_extent is None:\n+            raise RuntimeError('Obtaining UL Map X field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        if self.max_y_extent is None:\n+            raise RuntimeError('Obtaining UL Map Y field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        if self.max_x_extent is None:\n+            raise RuntimeError('Obtaining LR Map X field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        if self.min_y_extent is None:\n+            raise RuntimeError('Obtaining LR Map Y field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        if self.pixel_resolution_x is None:\n+            raise RuntimeError('Obtaining reflective grid cell size field'\n+                               ' from: [{0}]'.format(self.mtl_filename))\n+\n+        if self.pixel_resolution_y is None:\n+            raise RuntimeError('Obtaining reflective grid cell size field'\n+                               ' from: [{0}]'.format(self.mtl_filename))\n+\n+        if file_name_band_1 is None:\n+            raise RuntimeError('Obtaining FILE_NAME_BAND_1 field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        if product_id is None:\n+            raise RuntimeError('Obtaining LANDSAT_SCENE_ID field from: [{0}]'\n+                               .format(self.mtl_filename))\n+\n+        self.elevation_header_name = (self.elevation_header_name_fmt\n+                                      .format(product_id))\n+        self.elevation_image_name = (self.elevation_image_name_fmt\n+                                     .format(product_id))\n+\n+        self.target_srs = Geo.get_proj4_projection_string(file_name_band_1)\n+\n+        # Adjust the coordinates for image extents becuse they are in\n+        # center of pixel, and we need to supply the warping with actual\n+        # extents\n+        self.min_x_extent = (self.min_x_extent -\n+                             self.pixel_resolution_x * 0.5)\n+        self.max_x_extent = (self.max_x_extent +\n+                             self.pixel_resolution_x * 0.5)\n+        self.min_y_extent = (self.min_y_extent -\n+                             self.pixel_resolution_y * 0.5)\n+        self.max_y_extent = (self.max_y_extent +\n+                             self.pixel_resolution_y * 0.5)\n+\n+\n+def main():\n+    \"\"\"Provides the main processing for the script\"\"\"\n+\n+    # get the command line argument for the metadata file\n+    description = ('Create an elevation band using either the MTL or XML'\n+                   ' metadata as the information source')\n+    parser = ArgumentParser(description=description)\n+\n+    parser.add_argument('--mtl', '--mtl_filename',\n+                        action='store',\n+                        dest='mtl_filename',\n+                        default=None,\n+                        help='name of Landsat MTL file',\n+                        metavar='FILE')\n+\n+    parser.add_argument('--xml', '--xml_filename',\n+                        action='store',\n+                        dest='xml_filename',\n+                        default=None,\n+                        help='name of ESPA XML Metadata file',\n+                        metavar='FILE')\n+\n+    parser.add_argument('--debug',\n+                        action='store_true',\n+                        dest='debug',\n+                        default=False,\n+                        help='turn debug logging on')\n+\n+    args = parser.parse_args()\n+\n+    # Check logging level\n+    logging_level = logging.INFO\n+    if args.debug:\n+        logging_level = logging.DEBUG\n+\n+    # Setup the default logger format and level.  Log to STDOUT.\n+    logging.basicConfig(format=('%(asctime)s.%(msecs)03d %(process)d'\n+                                ' %(levelname)-8s'\n+                                ' %(filename)s:%(lineno)d:'\n+                                '%(funcName)s -- %(message)s'),\n+                        datefmt='%Y-%m-%d %H:%M:%S',\n+                        level=logging_level,\n+                        stream=sys.stdout)\n+\n+    logger = logging.getLogger(__name__)\n+\n+    if ESPA_ELEVATION_DIR not in os.environ:\n+        print('{0} environment variable not defined'\n+              .format(ESPA_ELEVATION_DIR))\n+        sys.exit(1)  # EXIT_FAILURE\n+\n+    elevation = None\n+    # Call the core processing\n+    if args.mtl_filename is not None:","path":"scripts/build_elevation_band.py","position":1409,"original_position":1409,"commit_id":"11dace2451ec57612ac3bb7d066e43ae706d31ed","original_commit_id":"11dace2451ec57612ac3bb7d066e43ae706d31ed","user":{"login":"ronaldddilley","id":12140146,"avatar_url":"https://avatars.githubusercontent.com/u/12140146?v=3","gravatar_id":"","url":"https://api.github.com/users/ronaldddilley","html_url":"https://github.com/ronaldddilley","followers_url":"https://api.github.com/users/ronaldddilley/followers","following_url":"https://api.github.com/users/ronaldddilley/following{/other_user}","gists_url":"https://api.github.com/users/ronaldddilley/gists{/gist_id}","starred_url":"https://api.github.com/users/ronaldddilley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ronaldddilley/subscriptions","organizations_url":"https://api.github.com/users/ronaldddilley/orgs","repos_url":"https://api.github.com/users/ronaldddilley/repos","events_url":"https://api.github.com/users/ronaldddilley/events{/privacy}","received_events_url":"https://api.github.com/users/ronaldddilley/received_events","type":"User","site_admin":false},"body":"Swapped to check for XML first since we will always call it that way.\r\n\r\nUpdated ArgumentParser setup/configuration to make them mutually exclusive and required.\r\n\r\nTurned the if,elif,else to if,else","created_at":"2016-07-05T14:37:45Z","updated_at":"2016-07-05T14:37:45Z","html_url":"https://github.com/USGS-EROS/espa-elevation/pull/2#discussion_r69574098","pull_request_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/comments/69574098"},"html":{"href":"https://github.com/USGS-EROS/espa-elevation/pull/2#discussion_r69574098"},"pull_request":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2","id":75013535,"html_url":"https://github.com/USGS-EROS/espa-elevation/pull/2","diff_url":"https://github.com/USGS-EROS/espa-elevation/pull/2.diff","patch_url":"https://github.com/USGS-EROS/espa-elevation/pull/2.patch","issue_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/2","number":2,"state":"open","locked":false,"title":"Dev v2.0.1  For August 2016 release","user":{"login":"ronaldddilley","id":12140146,"avatar_url":"https://avatars.githubusercontent.com/u/12140146?v=3","gravatar_id":"","url":"https://api.github.com/users/ronaldddilley","html_url":"https://github.com/ronaldddilley","followers_url":"https://api.github.com/users/ronaldddilley/followers","following_url":"https://api.github.com/users/ronaldddilley/following{/other_user}","gists_url":"https://api.github.com/users/ronaldddilley/gists{/gist_id}","starred_url":"https://api.github.com/users/ronaldddilley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ronaldddilley/subscriptions","organizations_url":"https://api.github.com/users/ronaldddilley/orgs","repos_url":"https://api.github.com/users/ronaldddilley/repos","events_url":"https://api.github.com/users/ronaldddilley/events{/privacy}","received_events_url":"https://api.github.com/users/ronaldddilley/received_events","type":"User","site_admin":false},"body":"This code was copied from espa-product-formatter.\r\n\r\nIt was also modified where needed for collection processsing and a few other things.","created_at":"2016-06-23T19:33:06Z","updated_at":"2016-07-05T14:37:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4cf0948be7bdc5ed72a7b85e7c6f9c11daca2f83","assignee":{"login":"gschmidt-usgs","id":12549375,"avatar_url":"https://avatars.githubusercontent.com/u/12549375?v=3","gravatar_id":"","url":"https://api.github.com/users/gschmidt-usgs","html_url":"https://github.com/gschmidt-usgs","followers_url":"https://api.github.com/users/gschmidt-usgs/followers","following_url":"https://api.github.com/users/gschmidt-usgs/following{/other_user}","gists_url":"https://api.github.com/users/gschmidt-usgs/gists{/gist_id}","starred_url":"https://api.github.com/users/gschmidt-usgs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gschmidt-usgs/subscriptions","organizations_url":"https://api.github.com/users/gschmidt-usgs/orgs","repos_url":"https://api.github.com/users/gschmidt-usgs/repos","events_url":"https://api.github.com/users/gschmidt-usgs/events{/privacy}","received_events_url":"https://api.github.com/users/gschmidt-usgs/received_events","type":"User","site_admin":false},"assignees":[{"login":"gschmidt-usgs","id":12549375,"avatar_url":"https://avatars.githubusercontent.com/u/12549375?v=3","gravatar_id":"","url":"https://api.github.com/users/gschmidt-usgs","html_url":"https://github.com/gschmidt-usgs","followers_url":"https://api.github.com/users/gschmidt-usgs/followers","following_url":"https://api.github.com/users/gschmidt-usgs/following{/other_user}","gists_url":"https://api.github.com/users/gschmidt-usgs/gists{/gist_id}","starred_url":"https://api.github.com/users/gschmidt-usgs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gschmidt-usgs/subscriptions","organizations_url":"https://api.github.com/users/gschmidt-usgs/orgs","repos_url":"https://api.github.com/users/gschmidt-usgs/repos","events_url":"https://api.github.com/users/gschmidt-usgs/events{/privacy}","received_events_url":"https://api.github.com/users/gschmidt-usgs/received_events","type":"User","site_admin":false},{"login":"raydittmeier","id":18680161,"avatar_url":"https://avatars.githubusercontent.com/u/18680161?v=3","gravatar_id":"","url":"https://api.github.com/users/raydittmeier","html_url":"https://github.com/raydittmeier","followers_url":"https://api.github.com/users/raydittmeier/followers","following_url":"https://api.github.com/users/raydittmeier/following{/other_user}","gists_url":"https://api.github.com/users/raydittmeier/gists{/gist_id}","starred_url":"https://api.github.com/users/raydittmeier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raydittmeier/subscriptions","organizations_url":"https://api.github.com/users/raydittmeier/orgs","repos_url":"https://api.github.com/users/raydittmeier/repos","events_url":"https://api.github.com/users/raydittmeier/events{/privacy}","received_events_url":"https://api.github.com/users/raydittmeier/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2/commits","review_comments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2/comments","review_comment_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/comments{/number}","comments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/2/comments","statuses_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/statuses/11dace2451ec57612ac3bb7d066e43ae706d31ed","head":{"label":"USGS-EROS:dev_v2.0.1","ref":"dev_v2.0.1","sha":"11dace2451ec57612ac3bb7d066e43ae706d31ed","user":{"login":"USGS-EROS","id":12549192,"avatar_url":"https://avatars.githubusercontent.com/u/12549192?v=3","gravatar_id":"","url":"https://api.github.com/users/USGS-EROS","html_url":"https://github.com/USGS-EROS","followers_url":"https://api.github.com/users/USGS-EROS/followers","following_url":"https://api.github.com/users/USGS-EROS/following{/other_user}","gists_url":"https://api.github.com/users/USGS-EROS/gists{/gist_id}","starred_url":"https://api.github.com/users/USGS-EROS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/USGS-EROS/subscriptions","organizations_url":"https://api.github.com/users/USGS-EROS/orgs","repos_url":"https://api.github.com/users/USGS-EROS/repos","events_url":"https://api.github.com/users/USGS-EROS/events{/privacy}","received_events_url":"https://api.github.com/users/USGS-EROS/received_events","type":"Organization","site_admin":false},"repo":{"id":58960539,"name":"espa-elevation","full_name":"USGS-EROS/espa-elevation","owner":{"login":"USGS-EROS","id":12549192,"avatar_url":"https://avatars.githubusercontent.com/u/12549192?v=3","gravatar_id":"","url":"https://api.github.com/users/USGS-EROS","html_url":"https://github.com/USGS-EROS","followers_url":"https://api.github.com/users/USGS-EROS/followers","following_url":"https://api.github.com/users/USGS-EROS/following{/other_user}","gists_url":"https://api.github.com/users/USGS-EROS/gists{/gist_id}","starred_url":"https://api.github.com/users/USGS-EROS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/USGS-EROS/subscriptions","organizations_url":"https://api.github.com/users/USGS-EROS/orgs","repos_url":"https://api.github.com/users/USGS-EROS/repos","events_url":"https://api.github.com/users/USGS-EROS/events{/privacy}","received_events_url":"https://api.github.com/users/USGS-EROS/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/USGS-EROS/espa-elevation","description":"Provides a script which implements the algorithm we use to generate an elevation image from various sources.","fork":false,"url":"https://api.github.com/repos/USGS-EROS/espa-elevation","forks_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/forks","keys_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/keys{/key_id}","collaborators_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/teams","hooks_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/hooks","issue_events_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/events{/number}","events_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/events","assignees_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/assignees{/user}","branches_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/branches{/branch}","tags_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/tags","blobs_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/refs{/sha}","trees_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/trees{/sha}","statuses_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/statuses/{sha}","languages_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/languages","stargazers_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/stargazers","contributors_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/contributors","subscribers_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/subscribers","subscription_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/subscription","commits_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/commits{/sha}","git_commits_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/commits{/sha}","comments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/comments{/number}","issue_comment_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/comments{/number}","contents_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/contents/{+path}","compare_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/compare/{base}...{head}","merges_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/merges","archive_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/downloads","issues_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues{/number}","pulls_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls{/number}","milestones_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/milestones{/number}","notifications_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/labels{/name}","releases_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/releases{/id}","deployments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/deployments","created_at":"2016-05-16T19:23:41Z","updated_at":"2016-05-16T19:23:41Z","pushed_at":"2016-06-30T16:36:04Z","git_url":"git://github.com/USGS-EROS/espa-elevation.git","ssh_url":"git@github.com:USGS-EROS/espa-elevation.git","clone_url":"https://github.com/USGS-EROS/espa-elevation.git","svn_url":"https://github.com/USGS-EROS/espa-elevation","homepage":null,"size":24,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"USGS-EROS:master","ref":"master","sha":"931ad0dad5f6c5a1d4d7b695098b01f85bb6e5de","user":{"login":"USGS-EROS","id":12549192,"avatar_url":"https://avatars.githubusercontent.com/u/12549192?v=3","gravatar_id":"","url":"https://api.github.com/users/USGS-EROS","html_url":"https://github.com/USGS-EROS","followers_url":"https://api.github.com/users/USGS-EROS/followers","following_url":"https://api.github.com/users/USGS-EROS/following{/other_user}","gists_url":"https://api.github.com/users/USGS-EROS/gists{/gist_id}","starred_url":"https://api.github.com/users/USGS-EROS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/USGS-EROS/subscriptions","organizations_url":"https://api.github.com/users/USGS-EROS/orgs","repos_url":"https://api.github.com/users/USGS-EROS/repos","events_url":"https://api.github.com/users/USGS-EROS/events{/privacy}","received_events_url":"https://api.github.com/users/USGS-EROS/received_events","type":"Organization","site_admin":false},"repo":{"id":58960539,"name":"espa-elevation","full_name":"USGS-EROS/espa-elevation","owner":{"login":"USGS-EROS","id":12549192,"avatar_url":"https://avatars.githubusercontent.com/u/12549192?v=3","gravatar_id":"","url":"https://api.github.com/users/USGS-EROS","html_url":"https://github.com/USGS-EROS","followers_url":"https://api.github.com/users/USGS-EROS/followers","following_url":"https://api.github.com/users/USGS-EROS/following{/other_user}","gists_url":"https://api.github.com/users/USGS-EROS/gists{/gist_id}","starred_url":"https://api.github.com/users/USGS-EROS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/USGS-EROS/subscriptions","organizations_url":"https://api.github.com/users/USGS-EROS/orgs","repos_url":"https://api.github.com/users/USGS-EROS/repos","events_url":"https://api.github.com/users/USGS-EROS/events{/privacy}","received_events_url":"https://api.github.com/users/USGS-EROS/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/USGS-EROS/espa-elevation","description":"Provides a script which implements the algorithm we use to generate an elevation image from various sources.","fork":false,"url":"https://api.github.com/repos/USGS-EROS/espa-elevation","forks_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/forks","keys_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/keys{/key_id}","collaborators_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/teams","hooks_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/hooks","issue_events_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/events{/number}","events_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/events","assignees_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/assignees{/user}","branches_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/branches{/branch}","tags_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/tags","blobs_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/refs{/sha}","trees_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/trees{/sha}","statuses_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/statuses/{sha}","languages_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/languages","stargazers_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/stargazers","contributors_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/contributors","subscribers_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/subscribers","subscription_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/subscription","commits_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/commits{/sha}","git_commits_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/git/commits{/sha}","comments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/comments{/number}","issue_comment_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/comments{/number}","contents_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/contents/{+path}","compare_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/compare/{base}...{head}","merges_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/merges","archive_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/downloads","issues_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues{/number}","pulls_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls{/number}","milestones_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/milestones{/number}","notifications_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/labels{/name}","releases_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/releases{/id}","deployments_url":"https://api.github.com/repos/USGS-EROS/espa-elevation/deployments","created_at":"2016-05-16T19:23:41Z","updated_at":"2016-05-16T19:23:41Z","pushed_at":"2016-06-30T16:36:04Z","git_url":"git://github.com/USGS-EROS/espa-elevation.git","ssh_url":"git@github.com:USGS-EROS/espa-elevation.git","clone_url":"https://github.com/USGS-EROS/espa-elevation.git","svn_url":"https://github.com/USGS-EROS/espa-elevation","homepage":null,"size":24,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2"},"html":{"href":"https://github.com/USGS-EROS/espa-elevation/pull/2"},"issue":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/2"},"comments":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/USGS-EROS/espa-elevation/statuses/11dace2451ec57612ac3bb7d066e43ae706d31ed"}}}}
{"actor":{"display_login":"ronaldddilley"}}