arcgis 方向分布
还记得大学老师给我们讲地理信息系统原理的时候,有一段示列有关井水污染源扩散,那时候没有学ArcGIS课后也没有实践,所以似懂非懂,
今天就做一个简单的井水污染源扩散分布图,arctoolbox工具下空间统计的方向分布(标准差椭圆),他是以空间要素的离散分布的中心趋势、方向趋势。
方向分布(标准差椭圆)工作原理是:
测量一组点或区域的趋势的一种常用方法便是分别计算 x 和 y 方向上的标准距离。
由于该方法是由平均中心作为起点对 x 坐标和 y 坐标的标准差进行计算,从而定义椭圆的轴,因此该椭圆被称为标准差椭圆
1.首先我们先有一份点数据,打开工具。
. 权重字段指是根据输入要素的属性数值来计算。
. 用于分析前对输入要素进行分组,再对分组进行计算,案例分组字段:日期型、整数型、字符型。
可见水井污染源方向分布呈西南——东北,接下来加上加权字段来计算。
虽然图形渲染效果并不是很好,但是arcgis10.4引进R语言无疑对arcgis数据可视化插上翅膀。
有兴趣的可以研究下面的源码。
方向分布(标准差椭圆)工具源码
# coding: utf-8
"""
Tool Name: Directional Trends (Standard Deviational Ellipse)
Source Name: StandardEllipse.py
Version: ArcGIS 10.1
Author: ESRI
This tool measures whether a distribution of features exhibits a
directional trend (that is, whether features are farther from
a specified center point in one direction than in another). The
user may specify an optional weight field and/or an optional
case field.
"""
################### Imports ########################
import os as OS
import sys as SYS
import collections as COLL
import numpy as NUM
import math as MATH
import arcpy as ARCPY
import arcpy.management as DM
import arcpy.da as DA
import ErrorUtils as ERROR
import SSUtilities as UTILS
import SSDataObject as SSDO
import locale as LOCALE
LOCALE.setlocale(LOCALE.LC_ALL, '')
circleDict = {"1_STANDARD_DEVIATION": 1.0,
"2_STANDARD_DEVIATIONS": 2.0,
"3_STANDARD_DEVIATIONS": 3.0}
################ Output Field Names #################
seFieldNames = ["CenterX", "CenterY", "XStdDist", "YStdDist", "Rotation"]
################### GUI Interface ###################
def setupStandardEllipse():
"""Retrieves the parameters from the User Interface and executes the
appropriate commands."""
inputFC = ARCPY.GetParameterAsText(0)
outputFC = ARCPY.GetParameterAsText(1)
stdDeviations = ARCPY.GetParameterAsText(2).upper().replace(" ", "_")
weightField = UTILS.getTextParameter(3, fieldName = True)
caseField = UTILS.getTextParameter(4, fieldName = True)
fieldList = []
if weightField:
fieldList.append(weightField)
if caseField:
fieldList.append(caseField)
stdDeviations = circleDict[stdDeviations]
#### Create a Spatial Stats Data Object (SSDO) ####
ssdo = SSDO.SSDataObject(inputFC, templateFC = outputFC,
useChordal = False)
#### Populate SSDO with Data ####
ssdo.obtainData(ssdo.oidName, fieldList, minNumObs = 3)
#### Run Analysis ####
se = StandardEllipse(ssdo, weightField = weightField,
caseField = caseField,
stdDeviations = stdDeviations)
#### Create Output ####
se.createOutput(outputFC)
class StandardEllipse(object):
"""This tool measures whether a distribution of features exhibits a
directional trend (that is, whether features are farther from
a specified center point in one direction than in another). The
user may specify an optional weight field and/or an optional
case field.
INPUTS:
ssdo (obj): instance of SSDataObject
weightField {str, None}: name of weight field
caseField {str, None} name of case field
stdDeviations {float, 1.0}: number of standard devs around center
ATTRIBUTES:
meanCenter (dict): [case field value] = mean center (1)
se (dict): [case field value] = standard distance (1)
badCases (list): list of cases that were unsuccessful.
ssdo (class): instance of SSDataObject
uniqueCases (array): sorted list of all cases for print/output
METHODS:
createOutput: creates a feature class with standard distances.
report: reports results as a printed message or to a file
NOTES:
(1) The key for the mean center dicts is "ALL" if no case field is
provided
"""
def __init__(self, ssdo, weightField = None, caseField = None,
stdDeviations = 1.0):
#### Set Initial Attributes ####
UTILS.assignClassAttr(self, locals())
#### Set Data ####
self.xyCoords = self.ssdo.xyCoords
#### Verify Weights ####
if weightField:
self.weights = self.ssdo.fields[weightField].returnDouble()
#### Report Negative Weights ####
lessThanZero = NUM.where(self.weights < 0.0)
if len(lessThanZero[0]):
self.weights[lessThanZero] = 0.0
ARCPY.AddIDMessage("Warning", 941)
#### Verify Weight Sum ####
self.weightSum = self.weights.sum()
if not self.weightSum > 0.0:
ARCPY.AddIDMessage("ERROR", 898)
raise SystemExit()
else:
self.weights = NUM.ones((self.ssdo.numObs,))
#### Set Case Field ####
if caseField:
caseType = ssdo.allFields[caseField].type.upper()
self.caseIsString = caseType == "STRING"
self.caseVals = self.ssdo.fields[caseField].data
cases = NUM.unique(self.caseVals)
if self.caseIsString:
self.uniqueCases = cases[NUM.where(cases != "")]
else:
self.uniqueCases = cases
else:
self.caseIsString = False
self.caseVals = NUM.ones((self.ssdo.numObs, ), int)
self.uniqueCases = [1]
#### Set Result Dict ####
meanCenter = COLL.defaultdict(NUM.array)
se = COLL.defaultdict(float)
#### Keep Track of Bad Cases ####
badCases = []
badCaseInd = []
#### Calculate Mean Center and Standard Distance ####
for ind, case in enumerate(self.uniqueCases):
indices = NUM.where(self.caseVals == case)
numFeatures = len(indices[0])
xy = self.xyCoords[indices]
w = self.weights[indices]
w.shape = numFeatures, 1
weightSum = w.sum()
if (weightSum != 0.0) and (numFeatures > 2):
xyWeighted = w * xy
#### Mean Center ####
centers = xyWeighted.sum(0) / weightSum
meanX, meanY = centers
meanCenter[case] = centers
#### Standard Ellipse ####
devXY = xy - centers
flatW = w.flatten()
sigX = (flatW * devXY[:,0]**2.0).sum()
sigY = (flatW * devXY[:,1]**2.0).sum()
sigXY = (flatW * devXY[:,0] * devXY[:,1]).sum()
denom = 2.0 * sigXY
diffXY = sigX - sigY
sum1 = diffXY**2.0 + 4.0 * sigXY**2.0
if not abs(denom) > 0:
arctanVal = 0.0
else:
tempVal = (diffXY + NUM.sqrt(sum1)) / denom
arctanVal = NUM.arctan(tempVal)
if arctanVal < 0.0:
arctanVal += (NUM.pi / 2.0)
sinVal = NUM.sin(arctanVal)
cosVal = NUM.cos(arctanVal)
sqrt2 = NUM.sqrt(2.0)
sigXYSinCos = 2.0 * sigXY * sinVal * cosVal
seX = (sqrt2 *
NUM.sqrt(((sigX * cosVal**2.0) - sigXYSinCos +
(sigY * sinVal**2.0)) /
weightSum) * stdDeviations)
seY = (sqrt2 *
NUM.sqrt(((sigX * sinVal**2.0) + sigXYSinCos +
(sigY * cosVal**2.0)) /
weightSum) * stdDeviations)
#### Counter Clockwise from Noon ####
degreeRotation = 360.0 - (arctanVal * 57.2957795)
#### Convert to Radians ####
radianRotation1 = UTILS.convert2Radians(degreeRotation)
#### Add Rotation ####
radianRotation2 = 360.0 - degreeRotation
if seX > seY:
radianRotation2 += 90.0
if radianRotation2 > 360.0:
radianRotation2 = radianRotation2 - 180.0
se[case] = (seX, seY, degreeRotation,
radianRotation1, radianRotation2)
else:
badCases.append(case)
badCaseInd.append(ind)
#### Report Bad Cases ####
nCases = len(self.uniqueCases)
nBadCases = len(badCases)
badCases.sort()
if nBadCases:
self.uniqueCases = NUM.delete(self.uniqueCases, badCaseInd)
cBool = self.caseIsString
if not self.caseIsString:
badCases = [UTILS.caseValue2Print(i, cBool) for i in badCases]
ERROR.reportBadCases(nCases, nBadCases, badCases,
label = caseField)
#### Set Attributes ####
self.meanCenter = meanCenter
self.se = se
self.badCases = badCases
self.caseField = caseField
self.stdDeviations = stdDeviations
self.weightField = weightField
def report(self, fileName = None):
"""Reports the Standard Ellipse results as a message or to a file.
INPUTS:
fileName {str, None}: path to a text file to populate with results
"""
header = ARCPY.GetIDMessage(84210)
columns = [ARCPY.GetIDMessage(84191), ARCPY.GetIDMessage(84211),
ARCPY.GetIDMessage(84212), ARCPY.GetIDMessage(84213),
ARCPY.GetIDMessage(84214), ARCPY.GetIDMessage(84215)]
results = [ columns ]
for case in self.uniqueCases:
if not self.caseField:
strCase = "ALL"
else:
strCase = UTILS.caseValue2Print(case, self.caseIsString)
meanX, meanY = self.meanCenter[case]
seX, seY, degreeRotation, radianR1, radianR2 = self.se[case]
rowResult = [ strCase, LOCALE.format("%0.6f", meanX),
LOCALE.format("%0.6f", meanY),
LOCALE.format("%0.6f", seX),
LOCALE.format("%0.6f", seY),
LOCALE.format("%0.6f", radianR2) ]
results.append(rowResult)
outputTable = UTILS.outputTextTable(results, header = header)
if fileName:
f = UTILS.openFile(fileName, "w")
UTILS.writeText(f, outputTable)
f.close()
else:
ARCPY.AddMessage(outputTable)
def createOutput(self, outputFC):
"""Creates an Output Feature Class with the Standard Distances.
INPUTS:
outputFC (str): path to the output feature class
"""
#### Validate Output Workspace ####
ERROR.checkOutputPath(outputFC)
#### Shorthand Attributes ####
ssdo = self.ssdo
caseField = self.caseField
#### Increase Extent if not Projected ####
if ssdo.spatialRefType != "Projected":
seValues = self.se.values()
if len(seValues):
maxSE = NUM.array([ i[0:2] for i in seValues ]).max()
largerExtent = UTILS.increaseExtentByConstant(ssdo.extent,
constant = maxSE)
largerExtent = [ LOCALE.str(i) for i in largerExtent ]
ARCPY.env.XYDomain = " ".join(largerExtent)
#### Create Output Feature Class ####
ARCPY.SetProgressor("default", ARCPY.GetIDMessage(84003))
outPath, outName = OS.path.split(outputFC)
try:
DM.CreateFeatureclass(outPath, outName, "POLYGON",
"", ssdo.mFlag, ssdo.zFlag,
ssdo.spatialRefString)
except:
ARCPY.AddIDMessage("ERROR", 210, outputFC)
raise SystemExit()
#### Add Fields to Output FC ####
dataFieldNames = UTILS.getFieldNames(seFieldNames, outPath)
shapeFieldNames = ["SHAPE@"]
for fieldName in dataFieldNames:
UTILS.addEmptyField(outputFC, fieldName, "DOUBLE")
if caseField:
fcCaseField = ssdo.allFields[caseField]
validCaseName = UTILS.validQFieldName(fcCaseField, outPath)
caseType = UTILS.convertType[fcCaseField.type]
UTILS.addEmptyField(outputFC, validCaseName, caseType)
dataFieldNames.append(validCaseName)
#### Write Output ####
badCaseRadians = []
allFieldNames = shapeFieldNames + dataFieldNames
rows = DA.InsertCursor(outputFC, allFieldNames)
for ind, case in enumerate(self.uniqueCases):
#### Get Results ####
xVal, yVal = self.meanCenter[case]
seX, seY, degreeRotation, radianR1, radianR2 = self.se[case]
seX2 = seX**2.0
seY2 = seY**2.0
#### Create Empty Polygon Geomretry ####
poly = ARCPY.Array()
#### Check for Valid Radius ####
seXZero = UTILS.compareFloat(0.0, seX, rTol = .0000001)
seXNan = NUM.isnan(seX)
seXBool = seXZero + seXNan
seYZero = UTILS.compareFloat(0.0, seY, rTol = .0000001)
seYNan = NUM.isnan(seY)
seYBool = seYZero + seYNan
if seXBool or seYBool:
badRadian = 6
badCase = UTILS.caseValue2Print(case, self.caseIsString)
badCaseRadians.append(badCase)
else:
badRadian = 0
cosRadian = NUM.cos(radianR1)
sinRadian = NUM.sin(radianR1)
#### Calculate a Point For Each ####
#### Degree in Ellipse Polygon ####
for degree in NUM.arange(0, 360):
try:
radians = UTILS.convert2Radians(degree)
tanVal2 = NUM.tan(radians)**2.0
dX = MATH.sqrt((seX2 * seY2) /
(seY2 + (seX2 * tanVal2)))
dY = MATH.sqrt((seY2 * (seX2 - dX**2.0)) /
seX2)
#### Adjust for Quadrant ####
if 90 <= degree < 180:
dX = -dX
elif 180 <= degree < 270:
dX = -dX
dY = -dY
elif degree >= 270:
dY = -dY
#### Rotate X and Y ####
dXr = dX * cosRadian - dY * sinRadian
dYr = dX * sinRadian + dY * cosRadian
#### Create Point Shifted to ####
#### Ellipse Centroid ####
pntX = dXr + xVal
pntY = dYr + yVal
pnt = ARCPY.Point(pntX, pntY, ssdo.defaultZ)
poly.add(pnt)
except:
badRadian += 1
if badRadian == 6:
badCase = UTILS.caseValue2Print(case,
self.caseIsString)
badCaseRadians.append(badCase)
break
if badRadian < 6:
#### Create and Populate New Feature ####
poly = ARCPY.Polygon(poly, None, True)
rowResult = [poly, xVal, yVal, seX, seY, radianR2]
if caseField:
caseValue = self.uniqueCases.item(ind)
rowResult.append(caseValue)
rows.insertRow(rowResult)
#### Report Bad Cases Due to Geometry (coincident pts) ####
nBadRadians = len(badCaseRadians)
if nBadRadians:
if caseField:
badCaseRadians = " ".join(badCaseRadians)
ARCPY.AddIDMessage("WARNING", 1011, caseField, badCaseRadians)
else:
ARCPY.AddIDMessage("ERROR", 978)
raise SystemExit()
#### Return Extent to Normal if not Projected ####
if ssdo.spatialRefType != "Projected":
ARCPY.env.XYDomain = ""
#### Clean Up ####
del rows
#### Set Attribute ####
self.outputFC = outputFC
#### Set the Default Symbology ####
params = ARCPY.gp.GetParameterInfo()
try:
renderLayerFile = 'StandardDeviationalEllipse.lyr'
templateDir = OS.path.dirname(OS.path.dirname(SYS.argv[0]))
fullRLF = OS.path.join(templateDir, "Templates",
"Layers", renderLayerFile)
params[1].Symbology = fullRLF
except:
ARCPY.AddIDMessage("WARNING", 973)
if __name__ == "__main__":
setupStandardEllipse()
转载自:https://blog.csdn.net/wywywywywywy123456/article/details/51798835