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.