-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathnvgWriter.py
250 lines (201 loc) · 9.46 KB
/
nvgWriter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#-------------------------------------------------------------------------------
# Name: nvgWriter.py
# Purpose: Provide a way to import data in NVG format into ArcGIS File
# Geodatabase format.
#
# Author: Dave Barrett
#
# Created: 02/09/2014
# Copyright: (c) Dave 2014
# Licence: <your licence>
#-------------------------------------------------------------------------------
"""
This module provides a means to create NVG files from ArcGIS geodatabase feature
classes.
"""
from xml.etree.ElementTree import ElementTree, Element, SubElement, Comment, tostring
import sys
from xml.dom import minidom
import arcpy
def prettify(elem):
"""Return a pretty-printed XML string for the element
"""
rough_string = tostring(elem, 'utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent="\t")
nvg = Element('nvg')
nvg.set('version', '1.4.0')
nvg.set('xmlns','http://tide.act.nato.int/schemas/2008/10/nvg')
##
##points = [Element('point',num=str(i)) for i in xrange(100)]
##
##nvg.extend(points)
##
##print prettify(nvg)
class Writer(object):
"""NATO Vector Graphic Writer instance. Reads ESRI Feature Class into NVG.
"""
def __init__(self):
"""Create the main NVG document element.
"""
# setup the NVG document elments with basic attributes. All features
# will be appended to this.
self.nvg = Element('nvg')
self.nvg.set('version', '1.4.0')
self.nvg.set('xmlns', 'http://tide.act.nato.int/schemas/2008/10/nvg')
#self.nvg.append(Comment('NVG generated by nvgWriter.py'))
return
def _generateStyle(self,geometryType,colour=None,width=None,fill=None):
"""generates a style string based on the colour, width and fill
parameters.
"""
# ComBAT specific
# these parameters are used to specify the style information for nvg features
# on the ComBAT system.
colours = {1: '#000000', 2: '#993333', 3: '#000066', 4: '#336600', 5: '#009900',
6: '#ccffff', 7: '#c9c9c9', 8: '#ccffcc', 9: '#ffffcc', 10: '#3333cc',
11: '#868686', 12: '#ff6600', 13: '#ffcccc', 14: '#ff0000',
15: '#e6e6e6', 16: '#cc9966', 17: '#ffffff', 18: '#ffff00'}
fills = {1: "fill:none;stroke:{0};stroke-opacity:1.000000;stroke-width:{1};",
2: "fill:{0};fill-opacity:0.333333;stroke:{0};stroke-opacity:1.000000;stroke-width:{1};",
3: "fill:{0};fill-opacity:0.686275;stroke:{0};stroke-opacity:1.000000;stroke-width:{1};",
4: "fill:{0};fill-opacity:0.333333;stroke:{0};stroke-opacity:1.000000;stroke-width:{1};",
5: "fill:{0};fill-opacity:1.000000;stroke:{0};stroke-opacity:1.000000;stroke-width:{1};"}
if geometryType == "Polyline":
try:
style = "stroke:{0};stroke-opacity:1.000000;stroke-width:{1};".format(colours[colour],str(width))
except:
# default to 1 pt black stroke
style = "stroke:#000000;stroke-opacity:1.000000;stroke-width:1;"
elif geometryType == "Polygon":
try:
# build styles based on the fill pattern selected
style = fills[fill].format(colours[colour],str(width))
except:
# default to 1 pt black stroke with clear fill
style = "fill:none;stroke:#000000;stroke-opacity:1.000000;stroke-width:1;"
return style
def _describe(self,fc):
"""Returns an arcpy Describe object.
"""
return arcpy.Describe(fc)
def _pointString(self,points):
"""Returns a string in the format required by NVG for point coordinates.
This method is used to parse the coordinates from each geometry into a
string sutiable for writing into the NVG file.
"""
s = ''
for pnt in points:
pnt = str(pnt).strip('[]').replace(" ","")
s = s + pnt + " "
return s.rstrip()
def _fieldCheck(self,fc):
"""Returns True if the required fields are present in the feature class.
The Label, Colour, Width and Fill fields are required to generate an NVG
file that can be read by ComBAT.
"""
result = False
shapeType = self._describe(fc).shapeType
fields = arcpy.ListFields(fc)
fieldNames = [field.name for field in fields]
# check required fileds are present
if shapeType == 'Point':
if 'LABEL' in fieldNames:
result = True
elif shapeType == 'Polyline':
lineFields = ['LABEL', 'COLOUR', 'WIDTH']
setFields = set(lineFields)
intersect = set(fieldNames).intersection(setFields)
if lineFields.sort() == list(intersect).sort():
result = True
elif shapeType == 'Polygon':
polyFields = ['LABEL', 'COLOUR', 'WIDTH', 'FILL']
setFields = set(polyFields)
intersect = set(fieldNames).intersection(setFields)
if polyFields.sort() == list(intersect).sort():
result = True
return result
def _getFeatures(self,fc):
"""Returns the features and attributes from the input feature class.
"""
pntFields = ['SHAPE@XY', 'LABEL']
lineFields = ['SHAPE@','LABEL', 'COLOUR', 'WIDTH']
polyFields = ['SHAPE@','LABEL', 'COLOUR', 'WIDTH', 'FILL']
shapeType = self._describe(fc).shapeType
# check the fields
if self._fieldCheck(fc):
# extract the feature and attribute data
if shapeType == 'Point':
# read point information
with arcpy.da.SearchCursor(fc,pntFields) as cursor:
for row in cursor:
x = str(row[0][0])
y = str(row[0][1])
label = str(row[1])
if label is None:
label = ""
# write the point element
self._writeElement('point',x=x,y=y,label=label)
elif shapeType == 'Polyline':
with arcpy.da.SearchCursor(fc,lineFields) as cursor:
for row in cursor:
geom = eval(row[0].JSON)
# return a string of point coordinates
points = self._pointString(geom['paths'][0])
style = self._generateStyle(shapeType,colour=row[2],width=row[3])
label = row[1]
if label is None:
label = ""
# create the element
self._writeElement('polyline',points=points,label=label,style=style)
elif shapeType == 'Polygon':
with arcpy.da.SearchCursor(fc,polyFields) as cursor:
for row in cursor:
geom = eval(row[0].JSON)
# return a string of point coordinates
points = self._pointString(geom['rings'][0])
style = self._generateStyle(shapeType,colour=row[2],width=row[3],fill=row[4])
label = row[1]
if label is None:
label = ""
# create the element
self._writeElement('polygon',points=points,label=label,style=style)
else:
# need to raise an error and terminate the script
raise arcpy.ExecuteError()
return
def _writeElement(self,element,**kwargs):
"""Sets keyword attributes for the supplied element.
The element is the NVG element to write, for example point, polyline,
polygon.
**kwargs is a set of key value pairs for each of the attributes. For
example a point will have the attributes x,y and label supplied.
"""
# creates a cild element of NVG with the element name and attributes
# from the kwargs. Each keyword will become an attribute.
# currently no checking of the kwargs to determine if they are valid
# for the supplied nvg element.
# need to ensure that the geometry tags are written first due to ComBAT
# failing to read the items if this is not the case.
return SubElement(self.nvg,element,kwargs)
def write(self,inFC,outFile,prettyXML=True):
"""Writes the contents of the input feature class(es) to NVG format.
inFC - can be a single or list of File GeoDatabase Feature Classes. These
should be based on the ComBAT layer pack which contains features
with the appropriate templates for editing in ArcGIS.
outFile - location and filename for the output NVG document. This should
.nvg file extension supplied.
The method does not currently handle circle and ellipse polygons. This is
due to ArcGIS representing these as curved lines with only 2 points.
Future versions will add support by densifying the lines.
"""
fcs = list(inFC)
for fc in fcs:
self._getFeatures(fc)
if prettyXML:
with open(outFile,'wb') as nvgFile:
nvgFile.write(prettify(self.nvg))
else:
with open(outFile,'wb') as nvgFile:
ElementTree(self.nvg).write(nvgFile,encoding="UTF-8", xml_declaration=True)
return True