001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2025
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data.hydra;
030
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import ucar.nc2.*;
034import ucar.nc2.ncml.NcMLReader;
035import ucar.ma2.*;
036
037import java.util.HashMap;
038import java.util.List;
039import java.util.ArrayList;
040import java.util.Iterator;
041import java.net.URL;
042import java.io.InputStream;
043import java.io.ByteArrayInputStream;
044import java.util.Map;
045
046import org.jdom2.input.SAXBuilder;
047import org.jdom2.output.XMLOutputter;
048import org.jdom2.Document;
049import org.jdom2.Element;
050
051
052public class NetCDFFile implements MultiDimensionReader {
053   private static final Logger logger = LoggerFactory.getLogger(NetCDFFile.class);
054   private final Map<String, Variable> varMap = new HashMap<>();
055   private final Map<String, String[]> varDimNames = new HashMap<>();
056   private final Map<String, int[]> varDimLengths = new HashMap<>();
057//   private final Map<String, Class> varDataType = new HashMap<>();
058   private final Map<String, DataType> varDataType = new HashMap<>();
059   private final Map<String, String> varUnits = new HashMap<>();
060
061   NetcdfFile ncfile = null;
062
063   public static NetCDFFile makeUnion(String filename, String other) throws Exception {
064     Object obj = new Object();
065     URL url = obj.getClass().getResource("/edu/wisc/ssec/mcidasv/data/hydra/resources/union.ncml");
066     SAXBuilder builder = new SAXBuilder(false);
067     Document doc = null;
068
069     try {
070       doc = builder.build(url);
071     } catch (Exception e) {
072       logger.error("Problem creating SAXBuilder", e);
073     }
074     Element root = doc.getRootElement();
075
076     List list = root.getChildren();
077
078     list = ((Element)list.get(1)).getChildren();
079
080     org.jdom2.Attribute attr1 = (((Element)list.get(0)).getAttributes()).get(0);
081     attr1.setValue(filename);
082
083     org.jdom2.Attribute attr2 = (((Element)list.get(1)).getAttributes()).get(0);
084     attr2.setValue(other);
085
086     XMLOutputter xmlOut = new XMLOutputter();
087     String newStr = xmlOut.outputString(doc);
088       logger.trace("union string:\n{}", newStr);
089     ByteArrayInputStream is = new ByteArrayInputStream(newStr.getBytes());
090     return new NetCDFFile(is);
091   }
092
093   public NetCDFFile(InputStream is) throws Exception {
094     ncfile = NcMLReader.readNcML(is, null);
095     init();
096   }
097
098   public NetCDFFile(String filename) throws Exception {
099     if (filename.endsWith(".ncml")) {
100       java.io.FileReader rdr = new java.io.FileReader(filename);
101       ncfile = NcMLReader.readNcML(rdr, null);
102     }
103     else {
104       ncfile = NetcdfFile.open(filename);
105     }
106     init();
107   }
108     
109   public NetCDFFile(String filename, org.jdom2.Element root) throws Exception {
110          ncfile = NcMLReader.readNcML(filename, root, null);
111          init();
112   }
113   
114   private void init() throws Exception {
115     Iterator varIter = ncfile.getVariables().iterator();
116     while(varIter.hasNext()) {
117       Variable var = (Variable) varIter.next();
118
119       if (var instanceof Structure) {
120         analyzeStructure((Structure) var);
121         continue;
122       }
123
124       int rank = var.getRank();
125       String varName = var.getFullName();
126       varMap.put(varName, var);
127       Iterator dimIter = var.getDimensions().iterator();
128       String[] dimNames = new String[rank];
129       int[] dimLengths = new int[rank];
130       int cnt = 0;
131       while(dimIter.hasNext()) {
132         Dimension dim = (Dimension) dimIter.next();
133         String dim_name = dim.getShortName();
134         if (dim_name == null) dim_name = "dim"+cnt;
135         dimNames[cnt] = dim_name;
136         dimLengths[cnt] = dim.getLength();
137         cnt++;
138       }
139       varDimNames.put(varName, dimNames);
140       varDimLengths.put(varName, dimLengths);
141//       varDataType.put(varName, var.getDataType().getPrimitiveClassType());
142       varDataType.put(varName, var.getDataType());
143       
144       Attribute attr = var.findAttribute("units");
145       if (attr != null) {
146         String unitStr = attr.getStringValue();
147         varUnits.put(varName, unitStr);
148       }
149     }
150   }
151
152   void analyzeStructure(Structure var) throws Exception {
153           if ((var.getShape()).length == 0) {
154                   return;
155           }
156           String varName = var.getFullName();
157           String[] dimNames = new String[2];
158           int[] dimLengths = new int[2];
159           int cnt = 0;
160           dimLengths[0] = (var.getShape())[0];
161           dimNames[0] = "dim" + cnt;
162
163           cnt++;
164           StructureData sData = var.readStructure(0);
165           List memList = sData.getMembers();
166           dimLengths[1] = memList.size();
167           dimNames[1] = "dim" + cnt;
168
169           varDimNames.put(varName, dimNames);
170           varDimLengths.put(varName, dimLengths);
171           varMap.put(varName, var);
172
173           StructureMembers sMembers = sData.getStructureMembers();
174           Object obj = sData.getScalarObject(sMembers.getMember(0));
175
176//         varDataType.put(varName, obj.getClass());
177       varDataType.put(varName, var.getDataType());
178   }
179
180//   public Class getArrayType(String array_name) {
181//         return varDataType.get(array_name);
182//   }
183    public DataType getArrayType(String array_name) {
184       return varDataType.get(array_name);
185    }
186
187   public String[] getDimensionNames(String array_name) {
188     return varDimNames.get(array_name);
189   }
190
191   public int[] getDimensionLengths(String array_name) {
192     return varDimLengths.get(array_name);
193   }
194
195   public String getArrayUnitString(String array_name) {
196     return varUnits.get(array_name);
197   }
198
199   public int getDimensionLength(String dimName) {
200     Dimension dim = ncfile.findDimension(dimName);
201     return (dim != null) ? dim.getLength() : -1;
202   }
203
204   public float[] getFloatArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
205       Variable var = varMap.get(array_name);
206       if (var instanceof Structure) {
207           Array array = Array.factory(getArrayType(array_name), count);
208           Index2D idx = new Index2D(count);
209           for (int i=0; i<count[0]; i++) {
210               StructureData sData = ((Structure)var).readStructure(start[0]+i);
211               StructureMembers sMembers = sData.getStructureMembers();
212               for (int j=0; j<count[1]; j++) {
213                   Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
214                   idx.set(i,j);
215                   array.setObject(idx, obj);
216               }
217           }
218           return (float[]) array.get1DJavaArray(DataType.FLOAT);
219       }
220       else {
221           List<Range> rangeList = new ArrayList<>(start.length);
222           for (int i=0;i<start.length;i++) {
223               Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
224               rangeList.add(i, rng);
225           }
226           Array array = var.read(rangeList);
227           return (float[]) array.get1DJavaArray(DataType.FLOAT);
228       }
229   }
230
231   public int[] getIntArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
232       Variable var = varMap.get(array_name);
233       if (var instanceof Structure) {
234           Array array = Array.factory(getArrayType(array_name), count);
235           Index2D idx = new Index2D(count);
236           for (int i=0; i<count[0]; i++) {
237               StructureData sData = ((Structure)var).readStructure(start[0]+i);
238               StructureMembers sMembers = sData.getStructureMembers();
239               for (int j=0; j<count[1]; j++) {
240                   Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
241                   idx.set(i,j);
242                   array.setObject(idx, obj);
243               }
244           }
245           return (int[]) array.get1DJavaArray(DataType.INT);
246       }
247       else {
248           List<Range> rangeList = new ArrayList<>(start.length);
249           for (int i=0;i<start.length;i++) {
250               Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
251               rangeList.add(i, rng);
252           }
253           Array array = var.read(rangeList);
254           return (int[]) array.get1DJavaArray(DataType.INT);
255       }
256   }
257
258   public double[] getDoubleArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
259       Variable var = varMap.get(array_name);
260       if (var instanceof Structure) {
261           Array array = Array.factory(getArrayType(array_name), count);
262           Index2D idx = new Index2D(count);
263           for (int i=0; i<count[0]; i++) {
264               StructureData sData = ((Structure)var).readStructure(start[0]+i);
265               StructureMembers sMembers = sData.getStructureMembers();
266               for (int j=0; j<count[1]; j++) {
267                   Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
268                   idx.set(i,j);
269                   array.setObject(idx, obj);
270               }
271           }
272           return (double[]) array.get1DJavaArray(DataType.DOUBLE);
273       }
274       else {
275           List<Range> rangeList = new ArrayList<>(start.length);
276           for (int i=0;i<start.length;i++) {
277               Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
278               rangeList.add(i, rng);
279           }
280           Array array = var.read(rangeList);
281           return (double[]) array.get1DJavaArray(DataType.DOUBLE);
282       }
283   }
284
285   public short[] getShortArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
286       Variable var = varMap.get(array_name);
287       if (var instanceof Structure) {
288           Array array = Array.factory(getArrayType(array_name), count);
289           Index2D idx = new Index2D(count);
290           for (int i=0; i<count[0]; i++) {
291               StructureData sData = ((Structure)var).readStructure(start[0]+i);
292               StructureMembers sMembers = sData.getStructureMembers();
293               for (int j=0; j<count[1]; j++) {
294                   Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
295                   idx.set(i,j);
296                   array.setObject(idx, obj);
297               }
298           }
299           return (short[]) array.get1DJavaArray(DataType.SHORT);
300       }
301       else {
302           List<Range> rangeList = new ArrayList<>(start.length);
303           for (int i=0;i<start.length;i++) {
304               Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
305               rangeList.add(i, rng);
306           }
307           Array array = var.read(rangeList);
308           return (short[]) array.get1DJavaArray(DataType.SHORT);
309       }
310   }
311
312   public byte[] getByteArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
313       Variable var = varMap.get(array_name);
314       if (var instanceof Structure) {
315           Array array = Array.factory(getArrayType(array_name), count);
316           Index2D idx = new Index2D(count);
317           for (int i=0; i<count[0]; i++) {
318               StructureData sData = ((Structure)var).readStructure(start[0]+i);
319               StructureMembers sMembers = sData.getStructureMembers();
320               for (int j=0; j<count[1]; j++) {
321                   Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
322                   idx.set(i,j);
323                   array.setObject(idx, obj);
324               }
325           }
326           return (byte[]) array.get1DJavaArray(DataType.BYTE);
327       }
328       else {
329           List<Range> rangeList = new ArrayList<>(start.length);
330           for (int i=0;i<start.length;i++) {
331               Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
332               rangeList.add(i, rng);
333           }
334           Array array = var.read(rangeList);
335           return (byte[]) array.get1DJavaArray(DataType.BYTE);
336       }
337   }
338
339   public Object getArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
340     return readArray(array_name, start, count, stride);
341   }
342
343   protected synchronized Object readArray(String array_name, int[] start, int[] count, int[] stride) throws Exception {
344     Variable var = varMap.get(array_name);
345     if (var instanceof Structure) {
346       Array array = Array.factory(getArrayType(array_name), count);
347       Index2D idx = new Index2D(count);
348       for (int i=0; i<count[0]; i++) {
349         StructureData sData = ((Structure)var).readStructure(start[0]+i);
350         StructureMembers sMembers = sData.getStructureMembers();
351         for (int j=0; j<count[1]; j++) {
352           Object obj = sData.getScalarObject(sMembers.getMember(start[1]+j));
353           idx.set(i,j);
354           array.setObject(idx, obj);
355         }
356       }
357       return array.copyTo1DJavaArray();
358     }
359     else {
360       List<Range> rangeList = new ArrayList<>(start.length);
361       for (int i=0;i<start.length;i++) {
362         Range rng = new Range(start[i], start[i]+(count[i]-1)*stride[i], stride[i]);
363         rangeList.add(i, rng);
364       }
365       Array array = var.read(rangeList);
366       return array.copyTo1DJavaArray();
367     }
368   }
369
370   public HDFArray getGlobalAttribute(String attr_name) throws Exception {
371     throw new Exception("NetCDFFile.getGlobalAttributes: Unimplemented");
372   }
373
374   public HDFArray getArrayAttribute(String array_name, String attr_name) throws Exception {
375     Object array = null;
376     DataType dataType = null;
377
378     Variable var = varMap.get(array_name);
379     if (var != null) {
380        Attribute attr = var.findAttribute(attr_name);
381        if (attr != null) {
382           Array attrVals = attr.getValues();
383           dataType = attr.getDataType();
384           array = attrVals.copyTo1DJavaArray();
385        }
386     }
387
388     if (array == null) {
389        return null;
390     }
391     
392     HDFArray harray = null;
393
394     if (dataType.getPrimitiveClassType() == Float.TYPE) {
395       harray = HDFArray.make((float[])array);
396     }
397     else if (dataType.getPrimitiveClassType() == Double.TYPE) {
398       harray = HDFArray.make((double[])array);
399     }
400     else if (dataType == DataType.STRING) {
401       harray = HDFArray.make((String[])array);
402     }
403     else if (dataType.getPrimitiveClassType() == Short.TYPE) {
404       harray = HDFArray.make((short[])array);
405     }
406     else if (dataType.getPrimitiveClassType() == Integer.TYPE) {
407       harray = HDFArray.make((int[])array);
408     }
409     return harray;
410   }
411
412   public void close() throws Exception {
413     ncfile.close();
414   }
415
416   public Map<String, Variable> getVarMap() {
417     return varMap;
418   }
419
420   public boolean hasArray(String name) {
421       return varMap.get(name) != null;
422   }
423
424   public boolean hasDimension(String name) {
425       return ncfile.findDimension(name) != null;
426   }
427
428   public NetcdfFile getNetCDFFile() {
429           return ncfile;
430   }
431   
432   public static void main(String[] args) throws Exception {
433     NetCDFFile ncfile = new NetCDFFile(args[0]);
434     ncfile.close();
435   }
436}