def read_color_table(image, sigevent_url):
    """
    Read color table from an input image and returns list of colors
    Argument:
        image -- Image to read color table
    """
    log_info_mssg("Checking for color table in " + image)
    colortable = []
    idx = 0
    has_color_table = False
    gdalinfo_command_list=['gdalinfo', image]
    gdalinfo = subprocess.Popen(gdalinfo_command_list,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    for line in gdalinfo.stdout.readlines():
        if has_color_table == True and (" " + str(idx) + ":") in line:
            rgb = line.replace(str(idx) + ":", "").strip().split(",")
            if len(rgb) < 4:
                rgb[3] = "255" # default if alpha not define
            colorEntry = ColorEntry(idx, rgb[0], rgb[1], rgb[2], rgb[3])
            colortable.append(colorEntry)
            idx+=1
        if "Color Table" in line:
            has_color_table = True
    if has_color_table == False:
        log_sig_exit("Error", "No color table found in " + image, sigevent_url)
    return colortable
def read_color_table(image, sigevent_url):
    """
    Read color table from an input image and returns list of colors
    Argument:
        image -- Image to read color table
    """
    log_info_mssg("Checking for color table in " + image)
    colortable = []
    idx = 0
    has_color_table = False
    gdalinfo_command_list = ['gdalinfo', image]
    gdalinfo = subprocess.Popen(gdalinfo_command_list,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
    for line in gdalinfo.stdout.readlines():
        if has_color_table == True and (" " + str(idx) + ":") in line:
            rgb = line.replace(str(idx) + ":", "").strip().split(",")
            if len(rgb) < 4:
                rgb[3] = "255"  # default if alpha not define
            colorEntry = ColorEntry(idx, rgb[0], rgb[1], rgb[2], rgb[3])
            colortable.append(colorEntry)
            idx += 1
        if "Color Table" in line:
            has_color_table = True
    if has_color_table == False:
        log_sig_exit("Error", "No color table found in " + image, sigevent_url)
    return colortable
def read_colormap(colormap_filename, sigevent_url):
    """
    Read color tables from GIBS color map and returns a list of colors
    Argument:
        colormap_filename -- GIBS color map file to read color tables
    """
    colortable = []
    try:
        # Open colormap file.
        colormap_file=open(colormap_filename, 'r')
        dom = xml.dom.minidom.parse(colormap_file)
        log_info_mssg("Opening file " + colormap_filename)
        colormap_file.close()
    except IOError: # try http URL
        log_info_mssg("Unable to find file, trying as URL: " + colormap_filename)
        try:
            dom = xml.dom.minidom.parse(urllib.urlopen(colormap_filename))
        except IOError, e:
            log_sig_exit("ERROR", str(e), sigevent_url)
def read_colormap(colormap_filename, sigevent_url):
    """
    Read color tables from GIBS color map and returns a list of colors
    Argument:
        colormap_filename -- GIBS color map file to read color tables
    """
    colortable = []
    try:
        # Open colormap file.
        colormap_file = open(colormap_filename, 'r')
        dom = xml.dom.minidom.parse(colormap_file)
        log_info_mssg("Opening file " + colormap_filename)
        colormap_file.close()
    except IOError:  # try http URL
        log_info_mssg("Unable to find file, trying as URL: " +
                      colormap_filename)
        try:
            dom = xml.dom.minidom.parse(urllib.urlopen(colormap_filename))
        except IOError, e:
            log_sig_exit("ERROR", str(e), sigevent_url)
Exemple #5
0
            colortable.append(colorEntry)
            idx += 1

# output color table as template if no merge VRT is provided
if merge_vrt == None:
    output_file = open(output_vrt, 'w')
    for colorEntry in colortable:
        output_file.writelines('        ' + str(colorEntry) + '\n')
    output_file.close()
else:  # merge SLD into VRT
    merge_file = open(merge_vrt, 'r')
    # check if VRT is uses palette
    if "<ColorInterp>Palette</ColorInterp>" not in merge_file.read():
        merge_file.close()
        message = merge_vrt + " is NOT paletted VRT."
        log_sig_exit('ERROR', message, sigevent_url)
    else:
        # copy merge VRT to output
        merge_file.seek(0)
        output_file = open(output_vrt, 'w')
        for line in merge_file:  # stop at ColorTable
            if "<ColorTable>" in line:
                output_file.writelines(line)
                break
            else:
                output_file.writelines(line)
        for colorEntry in colortable:
            output_file.writelines('        ' + str(colorEntry) + '\n')
        for line in merge_file:  # go to end of ColorTable
            if "</ColorTable>" in line:
                output_file.writelines(line)
Exemple #6
0
            colortable.append(colorEntry)
            idx+=1

# output color table as template if no merge VRT is provided
if merge_vrt == None:
    output_file = open(output_vrt, 'w')
    for colorEntry in colortable:
        output_file.writelines('        '+str(colorEntry)+'\n')
    output_file.close();
else: # merge SLD into VRT
    merge_file = open(merge_vrt, 'r')
    # check if VRT is uses palette
    if "<ColorInterp>Palette</ColorInterp>" not in merge_file.read():
        merge_file.close()
        message = merge_vrt + " is NOT paletted VRT."
        log_sig_exit('ERROR', message, sigevent_url)
    else:
        # copy merge VRT to output
        merge_file.seek(0)
        output_file = open(output_vrt, 'w')
        for line in merge_file: # stop at ColorTable
            if "<ColorTable>" in line:
                output_file.writelines(line);
                break
            else:
                output_file.writelines(line);
        for colorEntry in colortable:
            output_file.writelines('        '+str(colorEntry)+'\n')
        for line in merge_file: # go to end of ColorTable
            if "</ColorTable>" in line:
                output_file.writelines(line);
# Email logging level
logging_level = options.email_logging_level.upper()
# Email metadata replaces sigevent_url
if send_email:
    sigevent_url = (email_server, email_recipient, email_sender, logging_level)
    if email_recipient == '':
        log_sig_err("No email recipient provided for notifications.",
                    sigevent_url)
else:
    sigevent_url = ''

# fill color value
fill_value = str(options.fill_value).strip()
r_color = re.compile(r'\d+,\d+,\d+,\d+')
if r_color.match(fill_value) is None:
    log_sig_exit("Error", "fill_value format must be %d,%d,%d,%d",
                 sigevent_url)

# Colors to ignore
if not options.ignore_colors:
    ignore_colors = []
else:
    ignore_colors = options.ignore_colors.strip().split("|")
    for ignore_color in ignore_colors:
        if r_color.match(ignore_color) is None:
            log_sig_exit(
                "Error",
                ignore_color + " ignore_color format must be %d,%d,%d,%d",
                sigevent_url)

# verbose logging
if verbose:
def build_reproject_configs(layer_config_path, tilematrixsets_config_path, wmts=True, twms=True, create_gc=True, sigevent_url=None, stage_only=False, debug=False, base_wmts_gc=None,
                            base_twms_gc=None, base_twms_get_tile_service=None):
    # Track errors and warnings
    warnings = []
    errors = []
    
    # Parse the config XML
    try:
        config_xml = etree.parse(layer_config_path)
    except IOError:
        log_sig_exit('ERROR', "Can't open reproject layer config file: {0}".format(layer_config_path), sigevent_url)
    except etree.XMLSyntaxError:
        log_sig_exit('ERROR', "Can't parse reproject layer config file: {0}".format(layer_config_path), sigevent_url)

    src_locations = config_xml.findall('SrcLocation')
    gc_uri = config_xml.findtext('GetCapabilitiesURI')
    if not gc_uri:
        log_sig_exit('ERROR:', '<GetCapabilitiesURI> not present in reprojection config file: {0}'.format(layer_config_path), sigevent_url)
    layer_exclude_list = [name.text for name in config_xml.findall('ExcludeLayer')]
    layer_include_list = [name.text for name in config_xml.findall('IncludeLayer')]

    # Parse the tilematrixsets definition file
    try:
        tilematrixset_defs_xml = etree.parse(tilematrixsets_config_path)
    except IOError:
        log_sig_exit('ERROR', "Can't open tile matrix sets config file: {0}".format(tilematrixsets_config_path), sigevent_url)
    except etree.XMLSyntaxError:
        log_sig_exit('ERROR', "Can't parse tile matrix sets config file: {0}".format(tilematrixsets_config_path), sigevent_url)

    # Parse the environment config and get required info
    environment_config_path = config_xml.findtext('EnvironmentConfig')
    if not environment_config_path:
        log_sig_exit('ERROR', "No environment configuration file specified: " + layer_config_path, sigevent_url)
    try:
        environment_xml = etree.parse(environment_config_path)
    except IOError:
        log_sig_exit('ERROR', "Can't open environment config file: {0}".format(environment_config_path), sigevent_url)
    except etree.XMLSyntaxError:
        log_sig_exit('ERROR', "Can't parse environment config file: {0}".format(environment_config_path), sigevent_url)
     
    wmts_staging_location = next(elem.text for elem in environment_xml.findall('StagingLocation') if elem.get('service') == 'wmts')
    if not wmts_staging_location:
        mssg = 'no wmts staging location specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    twms_staging_location = next(elem.text for elem in environment_xml.findall('StagingLocation') if elem.get('service') == 'twms')
    if not twms_staging_location:
        mssg = 'no twms staging location specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    if len(environment_xml.findall('ReprojectEndpoint')) > 0:
        wmts_reproject_endpoint = next(elem.text for elem in environment_xml.findall('ReprojectEndpoint') if elem.get('service') == 'wmts')
    else:
        log_sig_exit('ERROR', 'no ReprojectEndpoint specified in ' + environment_config_path, sigevent_url)

    wmts_service_url = next(elem.text for elem in environment_xml.findall('ServiceURL') if elem.get('service') == 'wmts')
    if not wmts_service_url:
        mssg = 'no wmts service URL specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    wmts_base_endpoint = next(elem.text for elem in environment_xml.findall('ReprojectLayerConfigLocation') if elem.get('service') == 'wmts')
    if not wmts_base_endpoint:
        mssg = 'no wmts GC URL specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    if len(environment_xml.findall('ReprojectApacheConfigLocation')) > 1:
        wmts_apache_config_location_elem = next(elem for elem in environment_xml.findall('ReprojectApacheConfigLocation') if elem.get('service') == 'wmts')
        twms_apache_config_location_elem = next(elem for elem in environment_xml.findall('ReprojectApacheConfigLocation') if elem.get('service') == 'twms')
    else:
        log_sig_exit('ERROR', 'ReprojectApacheConfigLocation missing in ' + environment_config_path, sigevent_url)
    wmts_apache_config_location = wmts_apache_config_location_elem.text
    if not wmts_apache_config_location:
        mssg = 'no wmts Apache config location specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)
    wmts_apache_config_basename = wmts_apache_config_location_elem.get('basename')
    if not wmts_apache_config_basename:
        mssg = 'no wmts Apache config basename specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    twms_base_endpoint = next(elem.text for elem in environment_xml.findall('ReprojectLayerConfigLocation') if elem.get('service') == 'twms')
    if not twms_base_endpoint:
        mssg = 'no twms GC URL specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)
    if os.path.split(os.path.dirname(twms_base_endpoint))[1] == '.lib':
        twms_base_endpoint = os.path.split(os.path.dirname(twms_base_endpoint))[0]

    twms_apache_config_location = twms_apache_config_location_elem.text
    if not twms_apache_config_location:
        mssg = 'no twms Apache config location specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)
    twms_apache_config_basename = twms_apache_config_location_elem.get('basename')
    if not twms_apache_config_basename:
        mssg = 'no twms Apache config basename specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    wmts_service_url = next(elem.text for elem in environment_xml.findall('ServiceURL') if elem.get('service') == 'wmts')
    if not wmts_service_url and create_gc:
        mssg = 'no WMTS ServiceURL specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    twms_service_url = next(elem.text for elem in environment_xml.findall('ServiceURL') if elem.get('service') == 'twms')
    if not twms_service_url and create_gc:
        mssg = 'no TWMS ServiceURL specified'
        warnings.append(asctime() + " " + mssg)
        log_sig_warn(mssg, sigevent_url)

    # Get all the TMSs for this projection
    available_tilematrixsets = []
    for tms_list in [elem.findall('{*}TileMatrixSet') for elem in tilematrixset_defs_xml.findall('{*}Projection') if elem.get('id') == 'EPSG:3857']:
        for tms in tms_list:
            available_tilematrixsets.append(tms)

    # Download and parse GC file from endpoint
    try:
        r = requests.get(gc_uri)
        if r.status_code != 200:
            log_sig_exit('ERROR', 'Can\'t download GetCapabilities from URL: ' + gc_uri, sigevent_url)
    except:
        log_sig_exit('ERROR', 'Can\'t download GetCapabilities from URL: ' + gc_uri, sigevent_url)

    # Get the layers and source TMSs from the source GC file
    try:
        gc_xml = etree.fromstring(r.content)
    except etree.XMLSyntaxError:
        log_sig_exit('ERROR', "Can't parse GetCapabilities file (invalid syntax): " + gc_uri, sigevent_url)
    gc_layers = gc_xml.find('{*}Contents').findall('{*}Layer')
    tilematrixsets = gc_xml.find('{*}Contents').findall('{*}TileMatrixSet')
    ows = '{' + gc_xml.nsmap['ows'] + '}'
    xlink = '{http://www.w3.org/1999/xlink}'
    apache_staging_conf_path = ""

    for layer in gc_layers:
        identifier = layer.findtext(ows + 'Identifier')
        if debug:
            print 'Configuring layer: ' + identifier
        if identifier in layer_exclude_list:
            if debug:
                print 'Skipping layer: ' + identifier
            continue
        if layer_include_list and identifier not in layer_include_list:
            continue
        layer_endpoint = os.path.join(wmts_base_endpoint, identifier)
        layer_style_endpoint = os.path.join(layer_endpoint, 'default')
        layer_tms_apache_configs = []
        png_bands = None

        # Get TMSs for this layer and build a config for each
        tms_list = [elem for elem in tilematrixsets if elem.findtext(ows + 'Identifier') == layer.find('{*}TileMatrixSetLink').findtext('{*}TileMatrixSet')]
        layer_tilematrixsets = sorted(tms_list, key=get_max_scale_dem)
        out_tilematrixsets = []
        for tilematrixset in layer_tilematrixsets:
            # Start by getting the source info we need to configure this layer -- right now we are assuming geographic source projection
            src_tilematrixset_name = tilematrixset.findtext(ows + 'Identifier')
            matrices = sort_tilematrixset(tilematrixset)
            src_scale_denominator = float(matrices[0].findtext('{*}ScaleDenominator'))
            src_width = int(round(2 * math.pi * EARTH_RADIUS / (src_scale_denominator * 0.28E-3)))
            src_height = src_width / 2
            src_levels = len(matrices)
            src_pagesize_width = matrices[0].findtext('{*}TileWidth')
            src_pagesize_height = matrices[0].findtext('{*}TileHeight')
            src_bbox_elem = layer.find(ows + 'WGS84BoundingBox')
            src_bbox = ','.join(src_bbox_elem.findtext(ows + 'LowerCorner').split(' ')) + ',' + ','.join(src_bbox_elem.findtext(ows + 'UpperCorner').split(' '))
            src_format = layer.findtext('{*}Format')
            src_title = layer.findtext(ows + 'Title')

            # If it's a PNG, we need to grab a tile and check if it's paletted
            if 'image/png' in src_format and not png_bands:
                sample_tile_url = layer.find('{*}ResourceURL').get('template').replace('{Time}', 'default')\
                    .replace('{TileMatrixSet}', src_tilematrixset_name).replace('{TileMatrix}', '0').replace('{TileRow}', '0').replace('{TileCol}', '0')
                print 'Checking for palette for PNG layer: ' + identifier
                r = requests.get(sample_tile_url)
                if r.status_code != 200:
                    if "Invalid time format" in r.text: # Try taking out TIME if server doesn't like the request
                        sample_tile_url = sample_tile_url.replace('default/default', 'default')
                        r = requests.get(sample_tile_url)
                        if r.status_code != 200:
                            mssg = 'Can\'t get sample PNG tile from URL: ' + sample_tile_url
                            errors.append(asctime() + " " + mssg)
                            log_sig_err(mssg, sigevent_url)
                            continue
                    else:
                        mssg = 'Can\'t get sample PNG tile from URL: ' + sample_tile_url
                        errors.append(asctime() + " " + mssg)
                        log_sig_err(mssg, sigevent_url)
                        continue
                sample_png = png.Reader(BytesIO(r.content))
                sample_png.read()
                try:
                    if sample_png.palette():
                        png_bands = 1
                        print identifier + ' contains palette'
                except png.FormatError:
                    # No palette, check for greyscale
                    if sample_png.asDirect()[3]['greyscale'] == True:
                        png_bands = 1
                        print identifier + ' is greyscale'
                    else: # Check for alpha
                        if sample_png.asDirect()[3]['alpha'] == True:
                            png_bands = 4
                            print identifier + ' is RGBA'
                        else:
                            png_bands = 3
                            print identifier + ' is RGB'

            # Now figure out the configuration for the destination layer.
            # Start by getting the output TileMatrixSet that most closely matches the scale denominator of the source.
            dest_tilematrixset = reduce(functools.partial(scale_denominator_reduce, src_scale_denominator=src_scale_denominator), available_tilematrixsets, None)
            out_tilematrixsets.append(dest_tilematrixset)
            dest_tilematrixset_name = dest_tilematrixset.findtext(ows + 'Identifier')
            dest_scale_denominator = float(sort_tilematrixset(dest_tilematrixset)[0].findtext('{*}ScaleDenominator'))
            dest_width = dest_height = int(round(2 * math.pi * EARTH_RADIUS / (dest_scale_denominator * 0.28E-3)))
            dest_levels = len(dest_tilematrixset.findall('{*}TileMatrix'))
            dest_pagesize_width = sort_tilematrixset(dest_tilematrixset)[0].findtext('{*}TileWidth')
            dest_pagesize_height = sort_tilematrixset(dest_tilematrixset)[0].findtext('{*}TileHeight')
            dest_top_left_corner = [float(value) for value in sort_tilematrixset(dest_tilematrixset)[0].findtext('{*}TopLeftCorner').split(' ')]
            dest_bbox = '{0},{1},{2},{3}'.format(dest_top_left_corner[0], dest_top_left_corner[0], -dest_top_left_corner[0], -dest_top_left_corner[0])
            dest_resource_url_elem = layer.find('{*}ResourceURL')
            dest_url = dest_resource_url_elem.get('template')
            for location in src_locations:
                dest_url = dest_url.replace(location.get('external'), location.get('internal'))
            dest_url = re.match('(.*default)', dest_url).group()
            dest_dim_elem = layer.find('{*}Dimension')
            if dest_dim_elem is not None and dest_dim_elem.findtext(ows + 'Identifier') == 'time':
                static = False
                dest_url = '{0}/{1}/{2}'.format(dest_url, '${date}', src_tilematrixset_name)
            else:
                static = True
                dest_url = '{0}/{1}'.format(dest_url, src_tilematrixset_name)
            dest_file_type = dest_resource_url_elem.get('format')
            dest_file_ext = get_ext_for_file_type(dest_file_type)
            if dest_file_ext is None:
                mssg = identifier + " file type is not supported for OnEarth: " + dest_file_type
                warnings.append(asctime() + " " + mssg)
                log_sig_warn(mssg, sigevent_url)
                break
            if dest_file_ext in ['.tif', '.lerc', '.mvt', '.pbf']:
                mssg = identifier + " file type is not supported for reproject: " + dest_file_type
                warnings.append(asctime() + " " + mssg)
                log_sig_warn(mssg, sigevent_url)
                break

            if wmts:
                # Write the source and reproject (output) configuration files
                wmts_staging_layer_path = os.path.join(wmts_staging_location, identifier)
                wmts_staging_style_path = os.path.join(wmts_staging_layer_path, "default")
                wmts_staging_path = os.path.join(wmts_staging_style_path, dest_tilematrixset_name)

                try:
                    os.makedirs(wmts_staging_path)
                except OSError:
                    if not os.path.exists(wmts_staging_path):
                        log_sig_exit('ERROR', 'WMTS staging location: ' + wmts_staging_path + ' cannot be created.', sigevent_url)
                    pass

                src_cfg_filename = identifier + '_source.config'
                with open(os.path.join(wmts_staging_path, src_cfg_filename), 'w+') as src_cfg:
                    if 'image/png' in src_format:
                        src_cfg.write('Size {0} {1} 1 {2}\n'.format(src_width, src_height, png_bands))
                    else:
                        src_cfg.write('Size {0} {1}\n'.format(src_width, src_height))
                    src_cfg.write('PageSize {0} {1}\n'.format(src_pagesize_width, src_pagesize_height))
                    src_cfg.write('Projection {0}\n'.format('EPSG:4326'))
                    src_cfg.write('SkippedLevels 1\n')
                    src_cfg.write('BoundingBox {0}\n'.format(src_bbox))

                dest_cfg_filename = identifier + '_reproject.config'
                with open(os.path.join(wmts_staging_path, dest_cfg_filename), 'w+') as dest_cfg:
                    if 'image/png' in src_format:
                        dest_cfg.write('Size {0} {1} 1 {2}\n'.format(dest_width, dest_height, png_bands))
                        dest_cfg.write('Nearest On\n')
                    else:
                        dest_cfg.write('Size {0} {1}\n'.format(dest_width, dest_height))
                    dest_cfg.write('PageSize {0} {1}\n'.format(dest_pagesize_width, dest_pagesize_height))
                    dest_cfg.write('Projection {0}\n'.format('EPSG:3857'))
                    dest_cfg.write('BoundingBox {0}\n'.format(dest_bbox))
                    dest_cfg.write('SourcePath {0}\n'.format(dest_url))
                    dest_cfg.write('SourcePostfix {0}\n'.format(dest_file_ext))
                    dest_cfg.write('MimeType {0}\n'.format(src_format))
                    dest_cfg.write('Oversample On\n')
                    dest_cfg.write('ExtraLevels 1\n')
                    # dest_cfg.write('SkippedLevels 1\n')

                # Build Apache config snippet for TMS
                tms_path = os.path.join(layer_style_endpoint, dest_tilematrixset_name)
                tms_apache_config = '<Directory {0}>\n'.format(tms_path)
                tms_apache_config += '\tWMTSWrapperRole tilematrixset\n'
                tms_apache_config += '\tWMTSWrapperMimeType {0}\n'.format(src_format)
                regexp_file_ext = dest_file_ext if dest_file_type != 'image/jpeg' else '.(jpg|jpeg)'
                regexp_str = dest_tilematrixset_name + '/\d{1,2}/\d{1,3}/\d{1,3}' + regexp_file_ext + '$'
                tms_apache_config += '\tReproject_RegExp {0}\n'.format(regexp_str)
                tms_apache_config += '\tReproject_ConfigurationFiles {0} {1}\n'.format(os.path.join(tms_path, src_cfg_filename), os.path.join(tms_path, dest_cfg_filename))
                tms_apache_config += '</Directory>\n'
                layer_tms_apache_configs.append(tms_apache_config)

            # Build a TWMS configuration for the highest available TMS for this layer
            if twms and tilematrixset == layer_tilematrixsets[0]:
                reproj_twms_config_filename = 'twms.config'
                twms_staging_path = os.path.join(twms_staging_location, identifier)

                try:
                    os.makedirs(twms_staging_path)
                except OSError:
                    if not os.path.exists(twms_staging_path):
                        log_sig_exit('ERROR', 'TWMS staging location: ' + twms_staging_path + ' cannot be created.', sigevent_url)
                    pass

                with open(os.path.join(twms_staging_path, reproj_twms_config_filename), 'w+') as twms_cfg:
                    twms_cfg.write('Size {0} {1} {2}\n'.format(dest_width, dest_height, dest_levels))
                    twms_cfg.write('PageSize {0} {1}\n'.format(dest_pagesize_width, dest_pagesize_height))
                    twms_cfg.write('BoundingBox {0}\n'.format(dest_bbox))

                    # Build endpoint path for WMTS source
                    twms_src_layer_path = os.path.join(wmts_reproject_endpoint, identifier)
                    twms_src_style_path = os.path.join(twms_src_layer_path, 'default')
                    if static:
                        twms_src_path = os.path.join(twms_src_style_path, dest_tilematrixset_name)
                    else:
                        twms_src_time_path = os.path.join(twms_src_style_path, '${date}')
                        twms_src_path = os.path.join(twms_src_time_path, dest_tilematrixset_name)
                    twms_cfg.write('SourcePath {0}\n'.format(twms_src_path))
                    twms_cfg.write('SourcePostfix {0}'.format(dest_file_ext))
                    
        if dest_file_ext is None:  # Skip layer if unsupported file type
            continue
        
        if wmts:
            # Finish building the layer Apache config
            layer_apache_config = '<Directory {0}>\n'.format(layer_endpoint)
            layer_apache_config += '\tWMTSWrapperRole layer\n'
            layer_apache_config += '\tWMTSWrapperEnableTime on\n'
            layer_apache_config += '</Directory>\n'
            layer_apache_config += '<Directory {0}>\n'.format(layer_style_endpoint)
            layer_apache_config += '\tWMTSWrapperRole style\n'
            layer_apache_config += '</Directory>\n'
            for layer_config in layer_tms_apache_configs:
                layer_apache_config += layer_config

            layer_apache_config_filename = identifier + '_reproject.conf'
            layer_apache_config_path = os.path.join(wmts_staging_location, layer_apache_config_filename)
            try:
                with open(layer_apache_config_path, 'w+') as f:
                    f.write(layer_apache_config)
            except IOError:
                log_sig_exit('ERROR', 'Cannot write layer config file: ' + layer_apache_config_path, sigevent_url)

            # Create final Apache configs (WMTS)
            endpoint_apache_config = '<Directory {0}>\n'.format(wmts_base_endpoint)
            endpoint_apache_config += '\tWMTSWrapperRole root\n'
            endpoint_apache_config += '</Directory>\n'
            apache_staging_conf_path = os.path.join(wmts_staging_location, wmts_apache_config_basename + '.conf')
            try:
                with open(apache_staging_conf_path, 'w+') as wmts_apache_config_file:

                    # Write endpoint Apache stuff
                    wmts_apache_config_file.write(endpoint_apache_config)

                    # Write individual layer chunks
                    layer_snippets = [os.path.join(wmts_staging_location, sfile)
                              for sfile in sorted(os.listdir(wmts_staging_location), key=str.lower)
                              if sfile.endswith('.conf') and not sfile.startswith(wmts_apache_config_basename)]
                    for layer_path in layer_snippets:
                        with open(layer_path, 'r') as f:
                            wmts_apache_config_file.write(f.read())
            except IOError:
                log_sig_exit('ERROR', "Can't write WMTS staging apache config: " + apache_staging_conf_path, sigevent_url)

            # Modify layer GC xml for webmercator projection
            # Insert additional bounding box for WebMercator
            layer.find(ows + 'WGS84BoundingBox').find(ows + 'LowerCorner').text = '-180 -85.051129'
            layer.find(ows + 'WGS84BoundingBox').find(ows + 'UpperCorner').text = '180 85.051129'
            bb_elem_idx = layer.index(layer.find(ows + 'WGS84BoundingBox')) + 1
            bbox_elem = etree.Element(ows + 'BoundingBox', crs='urn:ogc:def:crs:EPSG::3857')
            bbox_upper_corner_elem = etree.Element(ows + 'UpperCorner')
            bbox_upper_corner_elem.text = '{0} {1}'.format(-dest_top_left_corner[0], -dest_top_left_corner[0])
            bbox_lower_corner_elem = etree.Element(ows + 'LowerCorner')
            bbox_lower_corner_elem.text = '{0} {1}'.format(dest_top_left_corner[0], dest_top_left_corner[0])
            bbox_elem.append(bbox_lower_corner_elem)
            bbox_elem.append(bbox_upper_corner_elem)
            layer.insert(bb_elem_idx, bbox_elem)

            # Add the TMSs we are using (clearing the ones from the source GC)
            tms_link_elem = layer.find('{*}TileMatrixSetLink')
            tms_link_elem.clear()
            for tms in out_tilematrixsets:
                tms_elem = etree.Element('TileMatrixSet')
                tms_elem.text = tms.findtext('{*}Identifier')
                tms_link_elem.append(tms_elem)

            # Modify the ResourceURL template
            base_url = wmts_service_url + identifier
            if static:
                wmts_service_template = base_url + '/default/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}' + dest_file_ext
            else:
                wmts_service_template = base_url + '/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}' + dest_file_ext
            dest_resource_url_elem.set('template', wmts_service_template)

            wmts_gc_snippet_filename = '{0}_reproject.xml'.format(identifier)
            try:
                with open(os.path.join(wmts_staging_location, wmts_gc_snippet_filename), 'w+') as wmts_gc_snippet:
                    wmts_gc_snippet.write(etree.tostring(layer, pretty_print=True))
            except IOError:
                log_sig_exit('ERROR', 'Could not create staging XML snippet', sigevent_url)

        if twms:
            try:
                twms_gc_staging_snippet = os.path.join(twms_staging_location, identifier + '_gc.xml')
                # Open layer XML file
                twms_gc_xml = open(twms_gc_staging_snippet, 'w+')
            except IOError:
                mssg=str().join(['Cannot read layer XML file:  ', 
                                 twms_gc_staging_snippet])
                log_sig_exit('ERROR', mssg, sigevent_url)

            twms_gc_layer_template = """<Layer queryable=\"0\">
            <Name>$Identifier</Name>
            <Title xml:lang=\"en\">$Title</Title>
            <Abstract xml:lang=\"en\">$Abstract</Abstract>
            <LatLonBoundingBox minx=\"$minx\" miny=\"$miny\" maxx=\"$maxx\" maxy=\"$maxy\"/>
            <Style>
                <Name>default</Name> <Title xml:lang=\"en\">(default) Default style</Title>
            </Style>
            <ScaleHint min=\"10\" max=\"100\"/> <MinScaleDenominator>100</MinScaleDenominator>
            </Layer>"""

            layer_output = ""
            lines = twms_gc_layer_template.splitlines(True)
            for line in lines:
                # replace lines in template
                if '</Layer>' in line:
                    line = ' '+line+'\n'  
                if '$Identifier' in line:
                    line = line.replace("$Identifier",identifier)              
                if '$Title' in line:
                    line = line.replace("$Title", src_title)
                # if '$Abstract' in line:
                #     line = line.replace("$Abstract", abstract)
                if '$minx' in line:
                    line = line.replace("$minx", str(-dest_top_left_corner[0]))
                if '$miny' in line:
                    line = line.replace("$miny", str(-dest_top_left_corner[0]))
                if '$maxx' in line:
                    line = line.replace("$maxx", str(dest_top_left_corner[0]))
                if '$maxy' in line:
                    line = line.replace("$maxy", str(dest_top_left_corner[0]))
                layer_output = layer_output + line
            twms_gc_xml.writelines(layer_output)
            twms_gc_xml.close()

            twms_gts_layer_template = """<TiledGroup>
            <Name>$TiledGroupName</Name>
            <Title xml:lang=\"en\">$Title</Title>
            <Abstract xml:lang=\"en\">$Abstract</Abstract>
            <Projection>$Projection</Projection>
            <Pad>0</Pad>
            <Bands>$Bands</Bands>
            <LatLonBoundingBox minx=\"$minx\" miny=\"$miny\" maxx=\"$maxx\" maxy=\"$maxy\" />
            <Key>${time}</Key>
            $Patterns</TiledGroup>"""
        
            try:
                twms_gts_staging_snippet = os.path.join(twms_staging_location, identifier + '_gts.xml')
                # Open layer XML file
                twms_gts_xml = open(twms_gts_staging_snippet, 'w+')
            except IOError:
                mssg=str().join(['Cannot read layer XML file:  ', 
                                 twms_gts_staging_snippet])
                log_sig_exit('ERROR', mssg, sigevent_url)

            proj = osr.SpatialReference()
            proj.ImportFromEPSG(3857)

            layer_output = ""
            lines = twms_gts_layer_template.splitlines(True)
            for line in lines:
                # replace lines in template 
                if '</TiledGroup>' in line:
                    line = ' '+line+'\n'              
                if '$TiledGroupName' in line:
                    line = line.replace('$TiledGroupName', identifier + 'tileset')
                if '$Title' in line:
                    line = line.replace("$Title", src_title)
                if '$Abstract' in line:
                    # line = line.replace("$Abstract",abstract)
                    line = line.replace("$Abstract", '')
                if '$Projection' in line:
                    line = line.replace("$Projection", proj.ExportToWkt())
                if '$Bands' in line:
                    if src_format == 'image/png':
                        line = line.replace("$Bands","4") # GDAL wants 4 for PNGs
                    else:
                        line = line.replace("$Bands","3")
                if '$minx' in line:
                    line = line.replace("$minx", str(-dest_top_left_corner[0]))
                if '$miny' in line:
                    line = line.replace("$miny", str(-dest_top_left_corner[0]))
                if '$maxx' in line:
                    line = line.replace("$maxx", str(dest_top_left_corner[0]))
                if '$maxy' in line:
                    line = line.replace("$maxy", str(dest_top_left_corner[0]))
                if '$Patterns' in line:
                    patterns = ""
                #     cmd = depth + '/oe_create_cache_config -p ' + twms_mrf_filename
                #     try:
                #         print '\nRunning command: ' + cmd
                #         process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                #         process.wait()
                #         for output in process.stdout:
                #             patterns = patterns + output
                #     except:
                #         log_sig_err("Error running command " + cmd, sigevent_url)
                    line = line.replace("$Patterns",patterns)
                layer_output = layer_output + line
            twms_gts_xml.writelines(layer_output)
            twms_gts_xml.close()

    # Final routines (after all layers have been processed)
    if wmts:
        if stage_only == False:
            # Copy all the WMTS config files to the endpoint
            layer_dirs = [subdir for subdir in os.listdir(wmts_staging_location) if os.path.isdir(os.path.join(wmts_staging_location, subdir))]
            for layer_dir in layer_dirs:
                layer_endpoint = os.path.join(wmts_base_endpoint, layer_dir)
                if os.path.exists(layer_endpoint):
                    shutil.rmtree(layer_endpoint)
                layer_staging_path = os.path.join(wmts_staging_location, layer_dir)
                print '\nCopying reprojected WMTS layer directories: {0} -> {1} '.format(layer_staging_path, layer_endpoint)
                shutil.copytree(layer_staging_path, layer_endpoint)
    
            # Copy the WMTS Apache config to the specified directory (like conf.d)
            apache_conf_path = os.path.join(wmts_apache_config_location, wmts_apache_config_basename + '.conf')
            print '\nCopying reprojected WMTS layer Apache config {0} -> {1}'.format(apache_staging_conf_path, apache_conf_path)
            try:
                shutil.copyfile(apache_staging_conf_path, apache_conf_path)
            except IOError, e: # Try with sudo if permission denied
                if 'Permission denied' in str(e):
                    cmd = ['sudo', 'cp', apache_staging_conf_path, apache_conf_path]
                    try:
                        run_command(cmd, sigevent_url)
                    except Exception, e:
                        log_sig_exit('ERROR', str(e), sigevent_url)
                shutil.copyfile(apache_staging_conf_path, apache_conf_path)
            except IOError, e: # Try with sudo if permission denied
                if 'Permission denied' in str(e):
                    cmd = ['sudo', 'cp', apache_staging_conf_path, apache_conf_path]
                    try:
                        run_command(cmd, sigevent_url)
                    except Exception, e:
                        log_sig_exit('ERROR', str(e), sigevent_url)

        if create_gc:
            # Configure GetCapabilties (routine copied from oe_configure_layer)
            try:
                # Copy and open base GetCapabilities.
                getCapabilities_file = os.path.join(wmts_staging_location, 'getCapabilities.xml')
                if not base_wmts_gc:
                    log_sig_exit('ERROR', 'WMTS GetCapabilities creation selected but no base GC file specified.', sigevent_url)
                shutil.copyfile(base_wmts_gc, getCapabilities_file)
                getCapabilities_base = open(getCapabilities_file, 'r+')
            except IOError:
                log_sig_exit('ERROR', 'Cannot read getcapabilities_base_wmts.xml file: ' + base_wmts_gc, sigevent_url)
            else:
                lines = getCapabilities_base.readlines()
                for idx in range(0, len(lines)):
                    if '<ows:Get' in lines[idx]:
                        spaces = lines[idx].index('<')
                        getUrlLine = lines[idx].replace('ows:Get','Get xmlns:xlink="http://www.w3.org/1999/xlink"').replace('>','/>')
                        getUrl = etree.fromstring(getUrlLine)
                        if '1.0.0/WMTSCapabilities.xml' in lines[idx]:
                            getUrl.attrib[xlink + 'href'] = wmts_service_url + '1.0.0/WMTSCapabilities.xml'
                        elif 'wmts.cgi?' in lines[idx]:
                            getUrl.attrib[xlink + 'href'] = wmts_service_url + 'wmts.cgi?'
email_sender=options.email_sender
# Email logging level
logging_level = options.email_logging_level.upper()
# Email metadata replaces sigevent_url
if send_email:
    sigevent_url = (email_server, email_recipient, email_sender, logging_level)
    if email_recipient == '':
        log_sig_err("No email recipient provided for notifications.", sigevent_url)
else:
    sigevent_url = ''

# fill color value
fill_value = str(options.fill_value).strip()
r_color = re.compile(r'\d+,\d+,\d+,\d+')
if r_color.match(fill_value) is None:
    log_sig_exit("Error", "fill_value format must be %d,%d,%d,%d", sigevent_url)

# Colors to ignore
if not options.ignore_colors:
    ignore_colors = []
else:
    ignore_colors = options.ignore_colors.strip().split("|")
    for ignore_color in ignore_colors:
        if r_color.match(ignore_color) is None:
            log_sig_exit("Error", ignore_color + " ignore_color format must be %d,%d,%d,%d", sigevent_url)

# verbose logging
if verbose:
    log_info_mssg('Colormap: ' + colormap_filename)
    log_info_mssg('Input Image: ' + input_filename)
    log_info_mssg('Fill Value: ' + fill_value)