text: import os
  from shutil import copyfile
  import urllib
  import json
  from os import listdir
  from os.path import isfile, join
  import arcpy
  from arcpy.sa import *
  
  # Author: Aaron O'Hehir - ACTmapi administrator/ICSM Taskforce Representitive for ACT
  # Date: 29/07/2019
  # Details: Developed the following functions to assist with processing historical imagery.
  #          This script has been shared in the ACT Govenrment github to assist other 
  #          other Australian jurisdictions.
  #
  #          Requires: ArcGIS and Python 2.7
  #
  
  
  """
  The purpose of this function is to rename the scans supplied by the 
  private sector to a name which is more user friendly in the 
  Context Capture GCP selection interface.
  The files are renamed to the ID which is 'RUN_NUMBER-PHOTO_NUMBER' 
  i.e. '10-111' for run 10 photo 111
  
  Parameters:
  input_xyz = input Context Capture xyz file. 
              This file is a csv that contains ID.tif,x,y,HAGL,ID
              see 'sample-xyz-file.xyz' for example
              The file is renamed to 'ID'
  """
  
  def rename_scans(input_xyz):
      # Open xyz file
      with open(input_xyz) as fobj:
          frame_x_y_z_fname = fobj.read().splitlines()
         
      # Skip header line
      frame_x_y_z_fname = frame_x_y_z_fname[1:]
      
      # Iterate through photos and rename
      for tif in frame_x_y_z_fname:
          try:
              fname = tif.split(',')[4]
              src = tif.split(',')[0]
              p, fn = os.path.split(src)
              dst = os.path.join(p,fname) + '.tif'
              print('Copying: ' + src)
              print('To:' + dst)
              copyfile(src, dst)
          except: 
              pass
  
  """
  
  The purpose of this function is to aid GCP selection in Context Capture. 
  Trying to find what photo corresponds with what GCP can be like trying to 
  find a needle in a haystack.
  
  The script takes a list of input ground control points and 
  intersects them with aerial photograph footprints. The ground control point that do 
  intersect with the footprints are printed to a text file with the associated 
  row and photo information. 
  
  The footprints are served as an ESRI mapservice:
  http://data.actmapi.act.gov.au/arcgis/rest/services/actmapi/static_historic_aerial_photo_footprints/MapServer/0
  
  Fields:
  OBJECTID ( type: esriFieldTypeOID , alias: OBJECTID )
  SHAPE ( type: esriFieldTypeGeometry , alias: Shape )
  CAPTURE ( type: esriFieldTypeSmallInteger , alias: CAPTURE )
  ACQ_MONTH ( type: esriFieldTypeString , alias: ACQ_MONTH , length: 50 )
  ACQ_YEAR ( type: esriFieldTypeSmallInteger , alias: ACQ_YEAR )
  RUN ( type: esriFieldTypeSmallInteger , alias: RUN )
  RUN_ALPHA ( type: esriFieldTypeString , alias: RUN_ALPHA , length: 50 )
  PHOTO ( type: esriFieldTypeSmallInteger , alias: PHOTO )
  F_PLANE ( type: esriFieldTypeSingle , alias: F_PLANE )
  F ( type: esriFieldTypeSingle , alias: F )
  HAGL ( type: esriFieldTypeSmallInteger , alias: HAGL )
  SCALE ( type: esriFieldTypeSmallInteger , alias: SCALE )
  SHAPE_Length ( type: esriFieldTypeDouble , alias: Shape_Length )
  SHAPE_Area ( type: esriFieldTypeDouble , alias: Shape_Area )
  MISSING ( type: esriFieldTypeString , alias: MISSING , length: 50 )
  
  Sample GCP input:
  Mark_Name X Y Z
  BALDY 688869.8583 6107133.402 757.071
  OAK 695803.1383 6109114.452 799.38
  OLD_JOE 699415.8926 6103208.357 813.06
  ONE_TREE 690522.0134 6109198.174 875.948
  
  Sample output:
  BALDY: R3-F40, R3-F41
  OAK: R1-F4, R1-F5, R2-F23, R2-F24, R2-F25
  OLD_JOE: R5-F126, R5-F127, R5-F128, R6-F180, R6-F181, R6-F182
  ONE_TREE: R1-F9, R1-F10, R1-F11, R2-F30, R2-F31
  
  Parameters:
  input_gcp_path            = input Context Capture gcp file. 
                              This file is a space seperated file that contains 'Mark_Name X Y Z'
                              see 'sample-gcp-file.txt' for example
  
  input_footprints_url      = The footprints REST end point
                              i.e. 'http://data.actmapi.act.gov.au/arcgis/rest/services/actmapi/static_historic_aerial_photo_footprints/MapServer/0'
  
  output_gcp_run_photo_path = The output file that contains ground control points 
                              and their corresponding photos. 
                              see 'sample-gcp-run-photo.txt' for example
  
  year                      = the year of the capture as an int
  
  """
  
  def prepare_ground_control_points(input_gcp_path, input_footprints_url, output_gcp_run_photo_path, year)
      # Constants
      NAME = 0
      X = 1
      Y = 2
      Z = 3
      
      # Initialise lists
      gcp_list = []
      output = []
      
      # Open gcp file and read in data
      gcp_file = open(input_gcp_path).readlines()
      for row in gcp_file:
          gcp_list.append(row.replace('\n','').split(' '))
      
      for gcp in gcp_list:
          row = gcp[NAME] + ': '
          url =   'http://data.actmapi.act.gov.au/arcgis/rest/services/actmapi/static_historic_aerial_photo_footprints/MapServer/0/query?where=CAPTURE%3D' + \
                  str(year) +         \
                  '&geometry=' +      \
                  gcp[X] +            \
                  '%2C' +             \
                  gcp[Y] +            \
                  '&geometryType=esriGeometryPoint&inSR=28355&spatialRel=esriSpatialRelIntersects&outFields=*&returnGeometry=true&f=pjson'
          print('URL: ' + url)
          # Send the server request and return the JSON response
          jsonResponse = urllib.urlopen(url)
          photos = json.loads(jsonResponse.read())
          photo_list = []
          for feature in photos[u'features']:
              run = feature[u'attributes'][u'RUN']
              frame = feature[u'attributes'][u'PHOTO']
              photo_list.append('R'+str(run)+'-F'+str(frame))
          for photo in photo_list:
              row += photo + ', '
          row = row +'\n'
          output.append(row)
      
      fileobj = open(str(year) + 'gcp.txt', 'w+')
      fileobj.writelines(output)
      fileobj.close()
  
  
  """
  
  The purpose of this function is to find areas of nodata within an output ortho mosaic. 
  The mosaics are cut into tiles and are fairly large file size wise. It is not practicle 
  loading a whole ortho capture into ArcGIS, so instead this function is used to find the 
  nodata areas, so further processing, such as filling in the holes, can be conducted. 
  
  Parameters:
  input_tif_imagery   = String representing the path to the input tif image directory.
  temp_dir            = String representing the path to the working directory where the 
                        reclassified tifs are written to.
  output_shp          = String representing the path ot the output directory where the 
                        shapefiles are written to. 
  
  """
  
  def classify_nodata()
  
  # Get list of files with the input directory
      dir_list = [f for f in listdir(input_tif_imagery) if isfile(join(input_tif_imagery, f))]
      
      # strip tfw files form list
      images = []
      for fname in dir_list:
          if '.tif' in fname and 'DSM' not in fname:
              images.append(fname)
      
      for tif in images:
          # Get file name for output csv
          inRaster = os.path.join(input_tif_imagery, tif)
          reclassField = "Value"
          remap = "0 1;0 255 2"
          missing_values = "DATA"
          
          out_raster = Reclassify(inRaster, reclassField, remap, missing_values)
          reclassified = os.path.join(temp_dir, tif)
          out_raster.save(reclassified)
          
          arcpy.RasterToPolygon_conversion(reclassified, os.path.join(output_shp, tif.replace('.tif', '.shp')), "SIMPLIFY", "VALUE")