'''
Name: transfer_atts_by_pt.py

Purpose: Transfers an attribute from one feature class to another using largest area of intersecting
         polygon

Description:
    
Notes:
    
Author: Doug Crane, Nov, 2012

Modifications:

'''

import sys
import os
import pdb
import shelve
import tempfile

import arcpy

from srd.srd_exception import *
import srd.srd_logging as srd_logging

import srd.srd_fields as srd_fields
import srd.srd_temp_ws as srd_temp_ws
import srd.srd_featureclass_tools as srd_featureclass_tools

# Add any module variables you wish imported.
__all__ = ['TransferAttsByPt']

__author__ = 'Doug Crane'
__version__ = '1.0'

# ---------------------------------------------------------------
class TransferAttsByPt(object):
    '''
    '''

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self):
        '''

        Parameters:

        Properties:
        '''

        self._logChn = srd_logging.SRD_Log()
        
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def addField(self, featureClass, fieldDefObj):
        '''
        Adds required fields for attribute transfer

        Parameters:
            featureClass - feature class to add fields to.
            
            fieldDefObj - field definition for field to add.
        '''
        
        self._logChn.logMsg('Adding Transfer Field')

        fieldDefList = []
        fieldDefList.append(fieldDefObj)
        
        fieldDefObj = srd_fields.SRD_FieldDefs(featureClass)
        fieldDefObj.setFieldList(fieldDefList)
        fieldDefObj.addFields()

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def calcAttByPt(self, fromFeatureClass, toFeatureClass, fieldMapDict):
        '''
        This method takes a source feature class and transfers an attribute to
        the output feature class using a point method. A unique identifier is
        first added to the TO feature class so its polygons can be linked to the FROM
        polygon points. The FROM feature class is processed to a point feaute class
        and an intersection is performed to the TO feature class. The intersection uses the
        unique identifier that was added to the TO feature class to link each intersected
        feature to its related TO polygon. Attributes are copied to the TO feature class
        based on this identifier.

        Note that the fromField and toField must be similar in structure so that an
        attribue from the fromField can be assigned to the toField.
        
        Parameters:
            fromFeatureClass - the feature class that the attribute will be taken from

            outFeatueClass - the feature class that the attribute will be transfered to.

            fieldMapDict - a dictionary with the key being the FROM field name and the value
                           being the TO field name to recieve attribute values.

        '''

        self._logChn.logMsg('Transfering atributes using point method...')

        # Make sure the feature class exists and add the fields
        # that will be calcualted.
        if not arcpy.Exists(toFeatureClass):
            raise SRD_Exception('Feature class: %s not found' % toFeatureClass)

        # Make sure that the source feature class has a known spatial reference.
        sr = arcpy.Describe(toFeatureClass).SpatialReference
        if sr.Name.lower() == 'unknown':
            raise SRD_Exception('No Spatial Reference Found For Input Feature Class %s' % toFeatureClass)

        idFieldName = '_ATT_PT_ID'
        if not arcpy.ListFields(toFeatureClass, idFieldName):
            arcpy.AddField_management(toFeatureClass, idFieldName, 'LONG')

        if not arcpy.Describe(toFeatureClass).HasOID:
            raise SRD_Exception('No OID Field For Input Feature Class %s' % toFeatureClass)

        # Add unique identifier to use to link FROM attributes to TO attributes.
        oidFieldName = '[' + arcpy.Describe(toFeatureClass).OIDFieldName + ']'
        self._logChn.logMsg('Calculating Feature Identifier ID...')
        arcpy.CalculateField_management(toFeatureClass, idFieldName, oidFieldName)

        tmpFromPt = arcpy.CreateUniqueName('tmp_from_pt', arcpy.env.scratchGDB)
        self._logChn.logMsg('Creating point feature class using FROM features...(%s)' % tmpFromPt)
        arcpy.FeatureToPoint_management(fromFeatureClass, tmpFromPt, 'INSIDE')
                            
        # Create a feature layer with only the basic fields.
        # Only the idFieldName will be needed with the intersected dataset
        # since this is what we will use to assign the fromField value
        # to the toField.
        fcObj = srd_featureclass_tools.SRD_FeatureClassTools()
        fieldInfoObj = fcObj.getFieldInfoList(toFeatureClass, [idFieldName,], excludeFields=False)

        toFL = 'to_fl'
        arcpy.MakeFeatureLayer_management(toFeatureClass, toFL, '', '', fieldInfoObj)

        tmpInt = arcpy.CreateUniqueName('tmp_int', arcpy.env.scratchGDB)
        self._logChn.logMsg('Intersecting with source polygons (%s)...' % tmpInt)
        arcpy.Intersect_analysis([tmpFromPt, toFL], tmpInt, '', 0.001 )

        selCnt = int(arcpy.GetCount_management(tmpInt).getOutput(0))
        if selCnt < 1:
            self._logChn.logWarning('\n***\n*** No points intersected with TO feature class, nothing to transfer\n***')
            return False

        self._logChn.logMsg('Accumulating attributes...')
        arcpy.SetProgressor("step", "Accumulating source field attributes...", 0, selCnt, 1)

        fromFieldList = fieldMapDict.keys()
        fromFieldList.append(idFieldName)
        fromFldIdx = {value: idx for idx, value in enumerate(fromFieldList)}

        # Build the dictionary of FROM field values that are associated with the
        # Polygon ID field. This will be used to update the TO field values.
        polyIdRecDict = {}
        with arcpy.da.SearchCursor(tmpInt, fromFieldList) as cursor:
            for row in cursor:
                rec = {}
                
                polyID =  row[fromFldIdx[idFieldName]]
                
                # Polygon ID is required to link the FROM and TO values.
                if polyID:
                    for fromFieldName in fromFieldList:
                        rec[fromFieldName] = row[fromFldIdx[fromFieldName]]
                    polyIdRecDict[polyID] = rec
                else:
                    self._logChn.logError('Unable to obtain value for field %s to accumulate FROM values' % idFieldName)
                    
                arcpy.SetProgressorPosition()

        arcpy.ResetProgressor()

        self._logChn.logMsg('Transfering attribute...')
        selCnt = int(arcpy.GetCount_management(toFeatureClass).getOutput(0))
        arcpy.SetProgressor("step", "Assigning attributes...", 0, selCnt, 1)        

        toFieldList = fieldMapDict.values()
        toFieldList.append(idFieldName)
        toFldIdx = {value: idx for idx, value in enumerate(toFieldList)}

        with arcpy.da.UpdateCursor(toFeatureClass, toFieldList) as cursor:
            for row in cursor:
                polyID =  row[toFldIdx[idFieldName]]

                if polyID in polyIdRecDict:
                    fromRecDict = polyIdRecDict[polyID]

                    for fromFld,toFld in fieldMapDict.items():
                        row[toFldIdx[toFld]] = fromRecDict[fromFld]
                        
                    cursor.updateRow(row)

                arcpy.SetProgressorPosition()
                
        arcpy.ResetProgressor()
        arcpy.SetProgressorLabel('Attribute assigment completed...')

        # Clean-up
        if arcpy.ListFields(toFeatureClass, idFieldName):
            arcpy.DeleteField_management(toFeatureClass, idFieldName)

        if arcpy.Exists(toFL):
            arcpy.Delete_management(toFL)
            
            
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    
if __name__ == '__main__':

    arcpy.env.overwriteOutput = True

    arcpy.env.XYTolerance = .001
    arcpy.env.XYResolution = .0001
    arcpy.gp.logHistory = False

    myObj = TransferAttsByPt()
    
    fromFcArg = r'D:\pythonscripts\avi_class\test_data\tr_att_by_pt.gdb\AVI'
    toFcArg = r'D:\pythonscripts\avi_class\test_data\tr_att_by_pt.gdb\AVIE_prep'
    fromToDict = {'PHOTO_YR':'NEW_PHOTO_YR', 'INITIALS':'NEW_INITIALS'}

    tstFC = r'D:\pythonscripts\avi_class\test_data\tr_att_by_pt.gdb\AVIE_prep_tst'
    if arcpy.Exists(tstFC):
        arcpy.Delete_management(tstFC)
    arcpy.CopyFeatures_management(toFcArg, tstFC)
    toFcArg = tstFC
    
    ##toFcArg = r'D:\pythonscripts\avi_class\test_data\tr_att_by_pt.gdb\NO_OVERLAP'
    
    fromToDict = {'PHOTO_YR':'PHOTO_YR', 'INITIALS':'INITIALS'}
    myObj.calcAttByPt(fromFcArg, toFcArg, fromToDict)
    
