1 | ''' ops_GridSeriesFeature contains operations for GridSeriesFeatures''' |
---|
2 | |
---|
3 | import csml.parser |
---|
4 | import csml.csmllibs.csmltime |
---|
5 | import csml.csmllibs.csmldocument |
---|
6 | import csml.API.ops_AbstractFeature |
---|
7 | import csml.API.genSubset |
---|
8 | import csml.csmllibs.netCDFWriter |
---|
9 | import csml.csmllibs.csmlcrs |
---|
10 | import csmlutils |
---|
11 | |
---|
12 | |
---|
13 | import sys #remove later |
---|
14 | |
---|
15 | def testmethod(self): |
---|
16 | #print 'testmethod for gridseries feature' |
---|
17 | return 'testmethod - gridseries' |
---|
18 | |
---|
19 | def getAllowedSubsettings(self): |
---|
20 | return ['subsetToGridSeries', 'subsetToProfileSeries'] #other operations |
---|
21 | |
---|
22 | def getSliceIndices(self, selection): |
---|
23 | ''' Calculates indices to use in slicing (eg for RawFileExtracts) and adds them to the selection dictionary |
---|
24 | ''' |
---|
25 | selLower=[] |
---|
26 | selUpper=[] |
---|
27 | |
---|
28 | #create artificial range for single selections so that (60) becomes (60,60) |
---|
29 | for sel in selection: |
---|
30 | if type(selection[sel]) in [int,float]: #ie. single value as opposed to a list containing a range |
---|
31 | tmp=selection[sel] |
---|
32 | selection[sel]=[tmp,tmp] |
---|
33 | |
---|
34 | for sel in selection: |
---|
35 | for item in enumerate(self.domain[sel]): |
---|
36 | index=item[0] |
---|
37 | value=item[1] |
---|
38 | if value==selection[sel][0]: |
---|
39 | #selLower.append(self.domain[sel].index(item)) |
---|
40 | limit1=index |
---|
41 | #selLower.append(item[0]) |
---|
42 | if value==selection[sel][1]: |
---|
43 | #selUpper.append(self.domain[sel].index(item)) |
---|
44 | limit2=index |
---|
45 | |
---|
46 | #make sure the lower limit is the smaller of the two selection criteria |
---|
47 | if limit1 <= limit2: |
---|
48 | selLower.append(limit1) |
---|
49 | selUpper.append(limit2+1)#plus 1 to take account of the fact that Numeric (and python) slicing returns only 6 values for [0:6] so, to get the first 7 values say you need to pass [0:7] |
---|
50 | else: |
---|
51 | selLower.append(limit2) |
---|
52 | selUpper.append(limit1 +1) |
---|
53 | |
---|
54 | selection['lower']=tuple(selLower) |
---|
55 | selection['upper']=tuple(selUpper) |
---|
56 | return selection |
---|
57 | |
---|
58 | |
---|
59 | def getDomain(self): |
---|
60 | #returns domain as a dictionary of ordinates {name: [values], ...} |
---|
61 | self.domain={} |
---|
62 | self.gridnames={} |
---|
63 | for gridOrd in self.value.gridSeriesDomain.coordTransformTable.gridOrdinates: |
---|
64 | name=gridOrd.coordAxisLabel.CONTENT |
---|
65 | self.gridnames[gridOrd.gridAxesSpanned.CONTENT]=name |
---|
66 | if hasattr(gridOrd.coordAxisValues, 'insertedExtract'): |
---|
67 | self.domain[name], fill, axisorder, units=gridOrd.coordAxisValues.insertedExtract.getData() |
---|
68 | else: |
---|
69 | valList=[] |
---|
70 | try: |
---|
71 | vals=gridOrd.coordAxisValues.coordinateList.CONTENT |
---|
72 | for val in vals.split(): |
---|
73 | valList.append(eval(val)) |
---|
74 | except: |
---|
75 | vals=gridOrd.coordAxisValues.timePositionList.CONTENT |
---|
76 | for val in vals.split(): |
---|
77 | valList.append(val) |
---|
78 | self.domain[name]=valList |
---|
79 | return self.domain |
---|
80 | |
---|
81 | def getUom(self): |
---|
82 | uom=None |
---|
83 | #returns the uom of the phenomenon. |
---|
84 | try: |
---|
85 | uom=self.value.rangeSet.valueArray.valueComponent.quantityList.insertedExtract.uom.CONTENT |
---|
86 | except AttributeError: |
---|
87 | uom =None |
---|
88 | return uom |
---|
89 | |
---|
90 | def getDomainUnits(self): |
---|
91 | srsname=self.value.gridSeriesDomain.srsName |
---|
92 | cat=csml.csmllibs.csmlcrs.CRSCatalogue() |
---|
93 | crs=cat.getCRS(srsname) |
---|
94 | return crs.units |
---|
95 | |
---|
96 | def __getAxis(self, name): |
---|
97 | '''called by getLongitudeAxis, getTimeAxis and getLatitudeAxis''' |
---|
98 | srsname=self.value.gridSeriesDomain.srsName |
---|
99 | cat=csml.csmllibs.csmlcrs.CRSCatalogue() |
---|
100 | crs=cat.getCRS(srsname) |
---|
101 | axID=None |
---|
102 | if name == 'lon': |
---|
103 | axID = crs.lonAxis |
---|
104 | elif name == 'lat': |
---|
105 | axID = crs.latAxis |
---|
106 | elif name == 'time': |
---|
107 | axID = crs.timeAxis |
---|
108 | if axID is not None: |
---|
109 | return crs.getAxisLabels()[axID] |
---|
110 | else: |
---|
111 | return None |
---|
112 | |
---|
113 | def getLongitudeAxis(self): |
---|
114 | return self.__getAxis('lon') |
---|
115 | |
---|
116 | def getLatitudeAxis(self): |
---|
117 | return self.__getAxis('lat') |
---|
118 | |
---|
119 | def getTimeAxis(self): |
---|
120 | return self.__getAxis('time') |
---|
121 | |
---|
122 | |
---|
123 | def _subsetGrid(self, **kwargs): |
---|
124 | '''this takes a selection from a gridseries object (be it a profile, a grid, whatever, it is still just a selection''' |
---|
125 | |
---|
126 | #set self.domain: |
---|
127 | self.getDomain() |
---|
128 | |
---|
129 | #get the CRS from a the catalogue |
---|
130 | cat=csml.csmllibs.csmlcrs.CRSCatalogue() |
---|
131 | crs=cat.getCRS(self.value.gridSeriesDomain.srsName, self.value.gridSeriesDomain.axisLabels) |
---|
132 | |
---|
133 | #non-feature specific setup code, mainly handles the time dimension/calendar |
---|
134 | |
---|
135 | pathToSubsetNetCDF, kwargs, timeAxis, timeName, caltype, times=csml.API.genSubset.genericSubset(self, self.outputdir, self.ncname, self.domain, kwargs) |
---|
136 | |
---|
137 | ##Get names of variables in file and relate them to the subset selection |
---|
138 | selection={} |
---|
139 | frame='' |
---|
140 | for gridOrd in self.value.gridSeriesDomain.coordTransformTable.gridOrdinates: |
---|
141 | try: |
---|
142 | selection[gridOrd.gridAxesSpanned.CONTENT]=kwargs[gridOrd.coordAxisLabel.CONTENT] |
---|
143 | except KeyError: |
---|
144 | allValues=tuple(self.domain[gridOrd.coordAxisLabel.CONTENT]) |
---|
145 | if hasattr(gridOrd.coordAxisValues, 'timePositionList'): |
---|
146 | if hasattr(gridOrd.coordAxisValues, 'frame'): |
---|
147 | frame=gridOrd.coordAxisValues.frame |
---|
148 | |
---|
149 | try: |
---|
150 | del selection[timeAxis] #NOTE: Haven't resolved the single CRS issue so cannot subset by time within an individual file for now |
---|
151 | except KeyError: |
---|
152 | pass |
---|
153 | |
---|
154 | #get slice indices |
---|
155 | selection=self.getSliceIndices(selection) |
---|
156 | |
---|
157 | |
---|
158 | strTimes, axisorder, units, fulldata, fillvalue =csml.API.genSubset.getTheData(self, selection, times, timeName) |
---|
159 | return pathToSubsetNetCDF, crs, frame, timeName, times, strTimes, caltype, axisorder,units, fulldata, fillvalue, kwargs |
---|
160 | |
---|
161 | def subsetToGridSeries(self, outputdir=None, ncname='gridseries.nc',**kwargs): |
---|
162 | #perform the subset (note this included nearest neighbour searching, so may return a different set of kwargs |
---|
163 | if outputdir is not None: |
---|
164 | self.outputdir=outputdir |
---|
165 | elif csml.API.csmlContainer.globalContainer.OUTPUTDIR is not None: |
---|
166 | self.outputdir=csml.API.csmlContainer.globalContainer.OUTPUTDIR |
---|
167 | csml.csmllibs.csmlextra.checkDirExists(self.outputdir) |
---|
168 | self.ncname=ncname |
---|
169 | pathToSubsetNetCDF, crs, frame, timeName, times, strTimes, caltype, axisorder,units, fulldata, fillvalue, kwargs=self._subsetGrid(**kwargs) |
---|
170 | #Okay, got the data now. Need to write CSML feature and NetCDF files. |
---|
171 | #Writing out the CSML feature |
---|
172 | |
---|
173 | # define domain/coverage to use in 'value' attribute |
---|
174 | newdomain=csml.parser.GridSeriesDomain() |
---|
175 | domainSubset, totalArraySize=csml.API.genSubset.subsetDomain(timeName,strTimes,self.domain, **kwargs) |
---|
176 | cTT=csml.API.genSubset.getCoordTransformTable(domainSubset, crs, frame) |
---|
177 | newdomain.id=csml.csmllibs.csmlextra.getRandomID() |
---|
178 | newdomain.coordTransformTable=cTT |
---|
179 | newdomain.srsName=self.value.gridSeriesDomain.srsName |
---|
180 | newdomain.axisLabels=self.value.gridSeriesDomain.axisLabels |
---|
181 | newdomain.srsDimension=self.value.gridSeriesDomain.srsDimension |
---|
182 | newdomain.dimension=self.value.gridSeriesDomain.dimension |
---|
183 | env=csml.parser.GridEnvelope() |
---|
184 | env.low=csml.parser.csString('0 0 0') #TODO |
---|
185 | env.high=csml.parser.csString('0 0 0') |
---|
186 | newdomain.limits=env |
---|
187 | newdomain.aLabels=self.value.gridSeriesDomain.aLabels |
---|
188 | rs=csml.parser.RangeSet() |
---|
189 | sdid=csml.csmllibs.csmlextra.getRandomID() |
---|
190 | descriptor=csml.parser.NetCDFExtract(id=sdid,fileName=csml.parser.csString(self.ncname),variableName=self.name,arraySize=csml.parser.csString(totalArraySize)) |
---|
191 | rs=csml.parser.RangeSet() |
---|
192 | va=csml.parser.ValueArray() |
---|
193 | vc=csml.parser.MeasureOrNullList() |
---|
194 | vc.href='#%s'%sdid |
---|
195 | vc.arcrole="http://ndg.nerc.ac.uk/xlinkUsage/insert#QuantityList" |
---|
196 | vc.role="http://ndg.nerc.ac.uk/fileFormat/csmlStorageDescriptor" |
---|
197 | vc.show='embed' |
---|
198 | try: |
---|
199 | vc.uom=self.value.rangeSet.valueArray.valueComponent.uom |
---|
200 | except: |
---|
201 | vc.uom = 'unknown' #TODO, |
---|
202 | va.valueComponent=vc |
---|
203 | va.id=csml.csmllibs.csmlextra.getRandomID() |
---|
204 | rs.valueArray=va |
---|
205 | #gridseries coverage |
---|
206 | cvg=csml.parser.GridSeriesCoverage() |
---|
207 | cvg.id=csml.csmllibs.csmlextra.getRandomID() |
---|
208 | cvg.rangeSet=rs |
---|
209 | cvg.gridSeriesDomain=newdomain |
---|
210 | |
---|
211 | #parameter, as before subsetting. |
---|
212 | param = self.parameter |
---|
213 | |
---|
214 | #create a stand alone gridseries feature containing this coverage |
---|
215 | csmlWrap=csml.csmllibs.csmlfeaturewrap.CSMLWrapper() |
---|
216 | subsettedFeature=csmlWrap.createGridSeriesFeature(value=cvg,parameter=param,featureID=csml.csmllibs.csmlextra.getRandomID(),name=self.name,description=self.description) |
---|
217 | |
---|
218 | ### write netcdf using NCWriter class (wraps cdms) ### |
---|
219 | nc=csml.csmllibs.netCDFWriter.NCwriter(pathToSubsetNetCDF) |
---|
220 | ords=cTT.gridOrdinates |
---|
221 | axislist=[] |
---|
222 | for a in axisorder: |
---|
223 | axislist.append(self.gridnames[a]) |
---|
224 | nc.genWriteVar(self.name.CONTENT,ords, times, caltype, axislist, units, fulldata, fillvalue) |
---|
225 | nc.closeFinishedFile() |
---|
226 | print 'NetCDF file written to %s'%pathToSubsetNetCDF |
---|
227 | return subsettedFeature, pathToSubsetNetCDF, descriptor |
---|
228 | |
---|
229 | |
---|
230 | def subsetToProfileSeries(self, outputdir=None, ncname='profileseries.nc',**kwargs): |
---|
231 | #TODO !!!!!!!!! Need to perform some sort of testing on the kwargs to check it is a profileseries request. |
---|
232 | if outputdir is not None: |
---|
233 | self.outputdir=outputdir |
---|
234 | elif csml.API.csmlContainer.globalContainer.OUTPUTDIR is not None: |
---|
235 | self.outputdir=csml.API.csmlContainer.globalContainer.OUTPUTDIR |
---|
236 | csml.csmllibs.csmlextra.checkDirExists(self.outputdir) |
---|
237 | self.ncname=ncname |
---|
238 | |
---|
239 | #perform the subset (note this included nearest neighbour searching, so may return a different set of kwargs |
---|
240 | pathToSubsetNetCDF, crs,frame, timeName, times, strTimes, caltype, axisorder,units, fulldata, fillvalue, kwargs=self._subsetGrid(**kwargs) |
---|
241 | latName=crs.axes[crs.latAxis] |
---|
242 | lonName=crs.axes[crs.lonAxis] |
---|
243 | #Okay, got the data now. Need to write CSML feature and NetCDF files. |
---|
244 | #Writing out the CSML feature |
---|
245 | |
---|
246 | # define domain/coverage to use in 'value' attribute |
---|
247 | newdomain=csml.parser.ProfileSeriesDomain() |
---|
248 | |
---|
249 | |
---|
250 | domainSubset, totalArraySize=csml.API.genSubset.subsetDomain(timeName,strTimes,self.domain, **kwargs) |
---|
251 | cTT=csml.API.genSubset.getCoordTransformTable(domainSubset, crs, frame) |
---|
252 | |
---|
253 | #Need to remove location (lat/lon) axes from the cTT |
---|
254 | newGridOrds=[] |
---|
255 | for gridOrd in cTT.gridOrdinates: |
---|
256 | if gridOrd.coordAxisLabel.CONTENT not in [latName,lonName]: |
---|
257 | newGridOrds.append(gridOrd) |
---|
258 | cTT.gridOrdinates=newGridOrds |
---|
259 | axes=[] |
---|
260 | for ord in newGridOrds: |
---|
261 | axes.append(ord.coordAxisLabel.CONTENT) |
---|
262 | cat = csml.csmllibs.csmlcrs.CRSCatalogue() |
---|
263 | crsInfo=cat.determineCRS(knownCRSAxes=axes) |
---|
264 | crs=cat.getCRS(crsInfo[0].srsName, labels=axes) |
---|
265 | |
---|
266 | newdomain.coordTransformTable=cTT |
---|
267 | newdomain.id=csml.csmllibs.csmlextra.getRandomID() |
---|
268 | #TODO need to remove 'location' from this srs |
---|
269 | newdomain.srsName=crsInfo[0].srsName |
---|
270 | axisLabels=csml.csmllibs.csmlextra.stringify(crsInfo[1].keys()) |
---|
271 | newdomain.axisLabels=axisLabels |
---|
272 | newdomain.srsDimension=2 |
---|
273 | newdomain.dimension=2 |
---|
274 | env=csml.parser.GridEnvelope() |
---|
275 | env.low=csml.parser.csString('0 0 0') #TODO |
---|
276 | env.high=csml.parser.csString('0 0 0') |
---|
277 | newdomain.limits=env |
---|
278 | newdomain.aLabels=self.value.gridSeriesDomain.aLabels #todo |
---|
279 | rangeSet=csml.parser.RangeSet() |
---|
280 | descid=csml.csmllibs.csmlextra.getRandomID() |
---|
281 | descriptor=csml.parser.NetCDFExtract(id=descid,fileName=csml.parser.csString(ncname),variableName=self.name,arraySize=csml.parser.csString(totalArraySize)) |
---|
282 | rs=csml.parser.RangeSet() |
---|
283 | va=csml.parser.ValueArray() |
---|
284 | vc=csml.parser.MeasureOrNullList() |
---|
285 | vc.href='#%s'%descid |
---|
286 | vc.arcrole="http://ndg.nerc.ac.uk/xlinkUsage/insert#QuantityList" |
---|
287 | vc.role="http://ndg.nerc.ac.uk/fileFormat/csmlStorageDescriptor" |
---|
288 | vc.show='embed' |
---|
289 | vc.insertedExtract=descriptor |
---|
290 | try: |
---|
291 | vc.uom=self.value.rangeSet.valueArray.valueComponent.uom |
---|
292 | except: |
---|
293 | vc.uom = 'unknown' #TODO, |
---|
294 | |
---|
295 | va.valueComponent=vc |
---|
296 | va.id=csml.csmllibs.csmlextra.getRandomID() |
---|
297 | rs.valueArray=va |
---|
298 | #profileseries coverage |
---|
299 | cvg=csml.parser.ProfileSeriesCoverage() |
---|
300 | cvg.id=csml.csmllibs.csmlextra.getRandomID() |
---|
301 | cvg.rangeSet=rs |
---|
302 | cvg.profileSeriesDomain=newdomain |
---|
303 | csmlWrap=csml.csmllibs.csmlfeaturewrap.CSMLWrapper() |
---|
304 | |
---|
305 | #parameter, as before subsetting. |
---|
306 | param = self.parameter |
---|
307 | |
---|
308 | #create 'location' attribute: |
---|
309 | if (latName, lonName) is not (None, None): |
---|
310 | locStr ='%s %s'%(kwargs[latName], kwargs[lonName]) |
---|
311 | loc=csml.parser.csString(locStr) |
---|
312 | else: |
---|
313 | loc = csml.parser.csString('unknown location') |
---|
314 | |
---|
315 | |
---|
316 | #create a stand alone gridseries feature containing this coverage |
---|
317 | subsettedFeature=csmlWrap.createProfileSeriesFeature(value=cvg,parameter=param, location=loc, featureID=csml.csmllibs.csmlextra.getRandomID(),name=self.name,description=self.description) |
---|
318 | |
---|
319 | |
---|
320 | ### write netcdf using NCWriter class (wraps cdms) ### |
---|
321 | nc=csml.csmllibs.netCDFWriter.NCwriter(pathToSubsetNetCDF) |
---|
322 | ords=cTT.gridOrdinates |
---|
323 | axislist=[] |
---|
324 | |
---|
325 | for a in axisorder: |
---|
326 | axislist.append(self.gridnames[a]) |
---|
327 | |
---|
328 | nc.genWriteVar(self.name.CONTENT,ords, times, caltype, axislist, units, fulldata, fillvalue, latitude=kwargs[latName], longitude=kwargs[lonName]) |
---|
329 | nc.closeFinishedFile() |
---|
330 | |
---|
331 | return subsettedFeature, pathToSubsetNetCDF, descriptor |
---|
332 | |
---|
333 | def subsetToProfile(self, outputdir = None, ncname='profile.nc',**kwargs): |
---|
334 | #Two step process - subset GridSeries to ProfileSeries, then ProfileSeries to Profile |
---|
335 | profileSeries, pSfile=self.subsetToProfileSeries(outputdir, ncname, **kwargs) |
---|
336 | del kwargs['latitude'] #TODO - need to remove excess kwargs based on the domain of the temporary profileSeries feature |
---|
337 | del kwargs['longitude'] |
---|
338 | subsettedFeature, pathToSubsetNetCDF=profileSeries.subsetToProfile(ouputdir,ncname, **kwargs) |
---|
339 | return subsettedFeature, pathToSubsetNetCDF |
---|
340 | |
---|
341 | def subsetToPointSeries(self, outputdir =None, ncname='pointseries.nc',**kwargs): |
---|
342 | #Two step process - subset GridSeries to ProfileSeries, then ProfileSeries to PointSeries |
---|
343 | profileSeries, pSfile=self.subsetToProfileSeries(outputdir, ncname, **kwargs) |
---|
344 | del kwargs['latitude'] #TODO - need to remove excess kwargs based on the domain of the temporary profileSeries feature |
---|
345 | del kwargs['longitude'] |
---|
346 | subsettedFeature, pathToSubsetNetCDF=profileSeries.subsetToPointSeries(outputdir, ncname, **kwargs) |
---|
347 | return subsettedFeature, pathToSubsetNetCDF |
---|