This was something I'd been thinking on for viewsheds, didn't realize Line of Sight was going to need z values. I included a global timer to find out if there is a particular step that slows it down. Its written to reduce by an offset factor, but I included comments on how to alter it to use variable heights from another raster instead, like a DEM.
# Version: ArcGIS 10.2 import sys, os import arcpy from arcpy import env from arcpy import da from arcpy.sa import * arcpy.CheckOutExtension("3D") arcpy.CheckOutExtension("Spatial") arcpy.env.parallelProcessingFactor = "100%" #Global Timer def exec_time(start, message): end = time.clock() comp_time = end - start arcpy.AddMessage("Run time for " + message + ": " + str(comp_time)) start = time.clock() return start ###Please set these params### #set your workspace path= env.workspace = path #input Elev Raster elevRaster = #input Raster (viewshed result) visibleRaster = #input observer points observerPnts = #intermediate observer points with z values observerPntsZ= #intermediate visible points, and with z values visPnts = visPntsZ = #output sight lines visLn = #ouput line of sight lines resultsLn = #Use adjFactor as correction height, adjRaster could be used instead for variable adjustment factor from a DEM adjFactor = ###### start=exec_time(start, "startup") #convert the raster into set of points for all visible locations OutRasVis = SetNull(visibleRaster, visibleRaster, "VALUE = 0") OutRasVis.save(path + "\\tempRasVis") start=exec_time(start, "removing nulls") arcpy.RasterToPoint_conversion(OutRasVis, visPnts) start=exec_time(start, "created visible points") #set heights for observers based on elevation raster (wasn't sure where this info was) arcpy.MakeFeatureLayer_management(observerPnts, observerPntsLyr) ExtractValuesToPoints(observerPntsLyr, elevRaster, observerPntsZ, "NONE", "VALUE_ONLY") #set heights for visible, use adjRaster in place of elevRaster if location based adjustment arcpy.MakeFeatureLayer_management(visPnts, visPntsLyr) ExtractValuesToPoints(visPntsLyr, elevRaster, visPntsZ, "NONE", "VALUE_ONLY") start=exec_time(start, "elevations to points") ###skip this if using location based adjustment### arcpy.AddField_management(visPntsZ, "Spot", "Double") with arcpy.da.UpdateCursor(visPntsZ, ["RASTERVALU", "Spot"]) as cursor: for row in cursor: if (row[1] == 0): row[1]= row[0] - adjFactor cursor.updateRow(row) start=exec_time(start, "adjustment factor") ###skip these is using location based adjustment### #create observer -> visible sight lines, change "Spot" to "RASTERVALU" if using location based adjustment arcpy.ConstructSightLines_3d (observerPntsZ, visPntsZ, visLn, "RASTERVALU", "Spot","","","") arcpy.LineOfSight_3d(elevRaster, outLn, resultsLn, "","","","","") #delete undesired intermediates (visPnts, visPntsZ, observerPntsZ, visLN) arcpy.Delete_management(visPnts)
I've not debugged it, so if you find anything let me know and I'll update this code for anyone in the future. One concern I have about this approach is that since the viewer location is lowered but not the surrounding cells any interior cells will no longer be visible(i.e. only the outside perimeter of a house can see out) vastly reducing viewers.