diff --git a/cdm/core/src/main/java/ucar/nc2/ft/DsgFeatureCollection.java b/cdm/core/src/main/java/ucar/nc2/ft/DsgFeatureCollection.java index f2e8175ff4..a333b73b35 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/DsgFeatureCollection.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/DsgFeatureCollection.java @@ -35,6 +35,15 @@ public interface DsgFeatureCollection { @Nonnull ucar.nc2.constants.FeatureType getCollectionFeatureType(); + /** + * The name of time unit. + * + * @return name of time unit string, may not be null + */ + @Nonnull + String getTimeName(); + + /** * The time unit. * @@ -43,6 +52,14 @@ public interface DsgFeatureCollection { @Nonnull CalendarDateUnit getTimeUnit(); + /** + * The altitude name string if it exists. + * + * @return altitude name string, may be null + */ + @Nullable + String getAltName(); + /** * The altitude unit string if it exists. * diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/DsgCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/DsgCollectionImpl.java index a51081ffc2..9a2200efd9 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/DsgCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/DsgCollectionImpl.java @@ -22,29 +22,54 @@ public abstract class DsgCollectionImpl implements DsgFeatureCollection { protected String name; + protected String timeName; protected CalendarDateUnit timeUnit; + protected String altName; protected String altUnits; protected CollectionInfo info; protected List extras; // variables needed to make CF/DSG writing work protected DsgCollectionImpl(String name, CalendarDateUnit timeUnit, String altUnits) { this.name = name; + this.timeName = "time"; + this.altName = "altitude"; this.timeUnit = timeUnit; this.altUnits = altUnits; } + protected DsgCollectionImpl(String name, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits) { + this.name = name; + this.timeName = timeName; + this.timeUnit = timeUnit; + this.altName = altName; + this.altUnits = altUnits; + } + @Nonnull @Override public String getName() { return name; } + @Nonnull + @Override + public String getTimeName() { + return timeName; + } + @Nonnull @Override public CalendarDateUnit getTimeUnit() { return timeUnit; } + @Nullable + @Override + public String getAltName() { + return altName; + } + @Nullable @Override public String getAltUnits() { diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/PointCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/PointCollectionImpl.java index 1325b27198..8209544f8d 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/PointCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/PointCollectionImpl.java @@ -28,6 +28,11 @@ protected PointCollectionImpl(String name, CalendarDateUnit timeUnit, String alt super(name, timeUnit, altUnits); } + protected PointCollectionImpl(String name, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits) { + super(name, timeName, timeUnit, altName, altUnits); + } + @Nonnull @Override public FeatureType getCollectionFeatureType() { @@ -56,7 +61,7 @@ protected static class PointCollectionSubset extends PointCollectionImpl { protected CalendarDateRange filter_date; public PointCollectionSubset(PointCollectionImpl from, LatLonRect filter_bb, CalendarDateRange filter_date) { - super(from.name, from.getTimeUnit(), from.getAltUnits()); + super(from.name, from.getTimeName(), from.getTimeUnit(), from.getAltName(), from.getAltUnits()); this.from = from; this.filter_bb = filter_bb; this.filter_date = filter_date; diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/PointFeatureCCImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/PointFeatureCCImpl.java index 58e50f7272..ff5411002d 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/PointFeatureCCImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/PointFeatureCCImpl.java @@ -29,6 +29,12 @@ protected PointFeatureCCImpl(String name, CalendarDateUnit timeUnit, String altU this.collectionFeatureType = collectionFeatureType; } + protected PointFeatureCCImpl(String name, String timeName, CalendarDateUnit timeUnit, String altName, String altUnits, + FeatureType collectionFeatureType) { + super(name, timeName, timeUnit, altName, altUnits); + this.collectionFeatureType = collectionFeatureType; + } + // All features in this collection have this feature type @Nonnull @Override diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/StationProfileFeatureImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/StationProfileFeatureImpl.java index fb18edff7b..b64e71d71a 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/StationProfileFeatureImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/StationProfileFeatureImpl.java @@ -43,6 +43,20 @@ public StationProfileFeatureImpl(String name, String desc, String wmoId, double this.timeSeriesNpts = npts; } + public StationProfileFeatureImpl(String name, String desc, String wmoId, double lat, double lon, double alt, + String timeName, CalendarDateUnit timeUnit, String altName, String altUnits, int npts) { + super(name, timeName, timeUnit, altName, altUnits, FeatureType.STATION_PROFILE); + station = new StationImpl(name, desc, wmoId, lat, lon, alt, npts); + this.timeSeriesNpts = npts; + } + + public StationProfileFeatureImpl(Station s, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits, int npts) { + super(s.getName(), timeName, timeUnit, altName, altUnits, FeatureType.STATION_PROFILE); + this.station = s; + this.timeSeriesNpts = npts; + } + public StationProfileFeatureImpl(Station s, CalendarDateUnit timeUnit, String altUnits, int npts) { super(s.getName(), timeUnit, altUnits, FeatureType.STATION_PROFILE); this.station = s; @@ -120,7 +134,7 @@ public static class StationProfileFeatureSubset extends StationProfileFeatureImp private final CalendarDateRange dateRange; public StationProfileFeatureSubset(StationProfileFeatureImpl from, CalendarDateRange filter_date) { - super(from.station, from.getTimeUnit(), from.getAltUnits(), -1); + super(from.station, from.getTimeName(), from.getTimeUnit(), from.getAltName(), from.getAltUnits(), -1); this.from = from; this.dateRange = filter_date; } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionFlattened.java b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionFlattened.java index 32ac793992..037e43807e 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionFlattened.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionFlattened.java @@ -19,7 +19,7 @@ public class StationTimeSeriesCollectionFlattened extends PointCollectionImpl { protected StationTimeSeriesCollectionImpl from; public StationTimeSeriesCollectionFlattened(StationTimeSeriesCollectionImpl from, CalendarDateRange dateRange) { - super(from.getName(), from.getTimeUnit(), from.getAltUnits()); + super(from.getName(), from.getTimeName(), from.getTimeUnit(), from.getAltName(), from.getAltUnits()); this.from = from; if (dateRange != null) { getInfo(); diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionImpl.java index 0bdc7a69f7..f6fe7ee815 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesCollectionImpl.java @@ -35,6 +35,11 @@ public StationTimeSeriesCollectionImpl(String name, CalendarDateUnit timeUnit, S super(name, timeUnit, altUnits, FeatureType.STATION); } + public StationTimeSeriesCollectionImpl(String name, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits) { + super(name, timeName, timeUnit, altName, altUnits, FeatureType.STATION); + } + // Double-check idiom for lazy initialization of instance fields. See Effective Java 2nd Ed, p. 283. protected StationHelper getStationHelper() { if (stationHelper == null) { @@ -130,7 +135,7 @@ private static class StationSubset extends StationTimeSeriesCollectionImpl { private final List stations; StationSubset(StationTimeSeriesCollectionImpl from, List stations) { - super(from.getName(), from.getTimeUnit(), from.getAltUnits()); + super(from.getName(), from.getTimeName(), from.getTimeUnit(), from.getAltName(), from.getAltUnits()); this.stations = stations; } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesFeatureImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesFeatureImpl.java index 64dbc9c600..d96053dc56 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesFeatureImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/StationTimeSeriesFeatureImpl.java @@ -31,6 +31,12 @@ public StationTimeSeriesFeatureImpl(String name, String desc, String wmoId, doub s = new StationFeatureImpl(name, desc, wmoId, lat, lon, alt, npts, sdata); } + public StationTimeSeriesFeatureImpl(String name, String desc, String wmoId, double lat, double lon, double alt, + String timeName, CalendarDateUnit timeUnit, String altName, String altUnits, int npts, StructureData sdata) { + super(name, timeName, timeUnit, altName, altUnits); + s = new StationFeatureImpl(name, desc, wmoId, lat, lon, alt, npts, sdata); + } + public StationTimeSeriesFeatureImpl(StationFeature s, CalendarDateUnit timeUnit, String altUnits, int nfeatures) { super(s.getName(), timeUnit, altUnits); this.s = s; @@ -40,6 +46,16 @@ public StationTimeSeriesFeatureImpl(StationFeature s, CalendarDateUnit timeUnit, } } + public StationTimeSeriesFeatureImpl(StationFeature s, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits, int nfeatures) { + super(s.getName(), timeName, timeUnit, altName, altUnits); + this.s = s; + if (nfeatures >= 0) { + getInfo(); // create the object + info.nfeatures = nfeatures; + } + } + @Override public String getWmoId() { return s.getWmoId(); diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/remote/StationCollectionStream.java b/cdm/core/src/main/java/ucar/nc2/ft/point/remote/StationCollectionStream.java index f6617fa8ed..9eeba1de47 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/remote/StationCollectionStream.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/remote/StationCollectionStream.java @@ -161,7 +161,8 @@ private class StationFeatureStream extends StationTimeSeriesFeatureImpl { PointIteratorStream riter; StationFeatureStream(StationTimeSeriesFeature s, CalendarDateRange dateRange) { - super(s, StationCollectionStream.this.getTimeUnit(), StationCollectionStream.this.getAltUnits(), -1); + super(s, s.getTimeName(), StationCollectionStream.this.getTimeUnit(), s.getAltName(), + StationCollectionStream.this.getAltUnits(), -1); this.stnFeature = s; if (dateRange != null) { getInfo(); diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/NestedTable.java b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/NestedTable.java index 0f3c3b25b8..9a32f99cc4 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/NestedTable.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/NestedTable.java @@ -467,6 +467,14 @@ public boolean hasCoords() { return (timeVE != null) && (latVE != null) && (lonVE != null); } + public String getTimeName() { + try { + return timeVE.axisName; + } catch (Exception e) { + throw new IllegalArgumentException("Error on time name string = " + timeVE.axisName + " == " + e.getMessage()); + } + } + public CalendarDateUnit getTimeUnit() { try { return CalendarDateUnit.of(null, timeVE.getUnitsString()); // LOOK dont know the calendar @@ -475,6 +483,14 @@ public CalendarDateUnit getTimeUnit() { } } + public String getAltName() { + if (altVE != null) + return altVE.axisName; + if (stnAltVE != null) + return stnAltVE.axisName; + return null; + } + public String getAltUnits() { if (altVE != null) return altVE.getUnitsString(); // fishy diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardPointCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardPointCollectionImpl.java index 557682a1df..7f60fd9c4c 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardPointCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardPointCollectionImpl.java @@ -20,7 +20,7 @@ public class StandardPointCollectionImpl extends PointCollectionImpl { private NestedTable ft; StandardPointCollectionImpl(NestedTable ft, CalendarDateUnit timeUnit, String altUnits) { - super(ft.getName(), timeUnit, altUnits); + super(ft.getName(), ft.getTimeName(), timeUnit, ft.getAltName(), altUnits); this.ft = ft; this.extras = ft.getExtras(); } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardProfileCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardProfileCollectionImpl.java index 2cffbb58df..e99ae7582c 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardProfileCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardProfileCollectionImpl.java @@ -37,12 +37,13 @@ public class StandardProfileCollectionImpl extends PointFeatureCCImpl implements ProfileFeatureCollection { private NestedTable ft; - protected StandardProfileCollectionImpl(String name, CalendarDateUnit timeUnit, String altUnits) { - super(name, timeUnit, altUnits, FeatureType.PROFILE); + protected StandardProfileCollectionImpl(String name, String timeName, CalendarDateUnit timeUnit, String altName, + String altUnits) { + super(name, timeName, timeUnit, altName, altUnits, FeatureType.PROFILE); } StandardProfileCollectionImpl(NestedTable ft, CalendarDateUnit timeUnit, String altUnits) { - super(ft.getName(), timeUnit, altUnits, FeatureType.PROFILE); + super(ft.getName(), ft.getTimeName(), timeUnit, ft.getAltName(), altUnits, FeatureType.PROFILE); this.ft = ft; this.extras = ft.getExtras(); } @@ -133,7 +134,7 @@ private static class StandardProfileCollectionSubset extends StandardProfileColl LatLonRect boundingBox; StandardProfileCollectionSubset(StandardProfileCollectionImpl from, LatLonRect boundingBox) { - super(from.getName() + "-subset", from.getTimeUnit(), from.getAltUnits()); + super(from.getName() + "-subset", from.getTimeName(), from.getTimeUnit(), from.getAltName(), from.getAltUnits()); this.from = from; this.boundingBox = boundingBox; } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationCollectionImpl.java index 0dd5a56c23..40e93df6d1 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationCollectionImpl.java @@ -32,7 +32,7 @@ public class StandardStationCollectionImpl extends StationTimeSeriesCollectionIm private NestedTable ft; StandardStationCollectionImpl(NestedTable ft, CalendarDateUnit timeUnit, String altUnits) { - super(ft.getName(), timeUnit, altUnits); + super(ft.getName(), ft.getTimeName(), timeUnit, ft.getAltName(), altUnits); this.ft = ft; this.extras = ft.getExtras(); } @@ -73,7 +73,8 @@ private class StandardStationFeatureImpl extends StationTimeSeriesFeatureImpl { StructureData stationData; StandardStationFeatureImpl(StationFeature s, CalendarDateUnit dateUnit, StructureData stationData, int recnum) { - super(s, dateUnit, StandardStationCollectionImpl.this.getAltUnits(), -1); + super(s, StandardStationCollectionImpl.this.getTimeName(), dateUnit, + StandardStationCollectionImpl.this.getAltName(), StandardStationCollectionImpl.this.getAltUnits(), -1); this.recnum = recnum; this.stationData = stationData; } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationProfileCollectionImpl.java b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationProfileCollectionImpl.java index 1c8a2293a1..93f7ce5b42 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationProfileCollectionImpl.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/standard/StandardStationProfileCollectionImpl.java @@ -135,7 +135,9 @@ private class StandardStationProfileFeature extends StationProfileFeatureImpl { Cursor cursor; StandardStationProfileFeature(Station s, Cursor cursor, StructureData stationProfileData, int recnum) { - super(s, StandardStationProfileCollectionImpl.this.getTimeUnit(), + super(s, StandardStationProfileCollectionImpl.this.getTimeName(), + StandardStationProfileCollectionImpl.this.getTimeUnit(), + StandardStationProfileCollectionImpl.this.getAltName(), StandardStationProfileCollectionImpl.this.getAltUnits(), -1); this.cursor = cursor; // this.recnum = recnum; diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer/CFPointWriter.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer/CFPointWriter.java index 79da58311a..abcdb296f9 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer/CFPointWriter.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer/CFPointWriter.java @@ -9,6 +9,8 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.ParameterException; +import com.google.common.collect.Iterators; +import com.google.common.collect.PeekingIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ucar.ma2.*; @@ -17,6 +19,7 @@ import ucar.nc2.dataset.CoordinateAxis; import ucar.nc2.ft.*; import ucar.nc2.ft.point.StationPointFeature; +import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl; import ucar.nc2.time.CalendarDate; import ucar.nc2.time.CalendarDateFormatter; import ucar.nc2.time.CalendarDateUnit; @@ -46,8 +49,8 @@ public abstract class CFPointWriter implements Closeable { public static final String recordDimName = "obs"; public static final String latName = "latitude"; public static final String lonName = "longitude"; - public static final String altName = "altitude"; - public static final String timeName = "time"; + public static String altName = "altitude"; + public static String timeName = "time"; public static final String stationStructName = "station"; public static final String stationDimName = "station"; @@ -147,22 +150,21 @@ private static int writeStationFeatureCollection(FeatureDatasetPoint dataset, St cfWriter.setExtraVariables(fc.getExtraVariables()); - ucar.nc2.ft.PointFeatureCollection pfc = fc.flatten(null, null, null); // all data, but no need to sort by station + cfWriter.writeHeader(fc); int count = 0; - for (PointFeature pf : pfc) { - StationPointFeature spf = (StationPointFeature) pf; - if (count == 0) - cfWriter.writeHeader(fc.getStationFeatures(), spf); + for (PointFeatureCollection pfc : fc) { + for (PointFeature pf : pfc) { + StationPointFeature spf = (StationPointFeature) pf; - cfWriter.writeRecord(spf.getStation(), pf, pf.getFeatureData()); - count++; - if (debug && count % 100 == 0) - System.out.printf("%d ", count); - if (debug && count % 1000 == 0) - System.out.printf("%n "); + cfWriter.writeRecord(spf.getStation(), pf, pf.getFeatureData()); + count++; + if (debug && count % 100 == 0) + System.out.printf("%d ", count); + if (debug && count % 1000 == 0) + System.out.printf("%n "); + } } - cfWriter.finish(); return count; } @@ -454,6 +456,55 @@ protected void makeMiddleVariables(StructureData middleData, boolean isExtended) // NOOP } + protected void writeHeader(List obsCoords, StationTimeSeriesCollectionImpl stationFeatures) + throws IOException { + + this.recordDim = writer.addUnlimitedDimension(recordDimName); + addExtraVariables(); + addCoordinatesClassic(recordDim, obsCoords, dataMap); + + for (StationTimeSeriesFeature stnFeature : stationFeatures) { + StructureData featureData = stnFeature.getFeatureData(); + if (writer.getVersion().isExtendedModel()) { + makeFeatureVariables(featureData, true); + record = (Structure) writer.addVariable(null, recordName, DataType.STRUCTURE, recordDimName); + addCoordinatesExtended(record, obsCoords); + + } else { + if (writer.findDimension(stationDimName) == null) + makeFeatureVariables(featureData, false); + } + PeekingIterator iter = Iterators.peekingIterator(stnFeature.iterator()); + if (iter.hasNext()) { + PointFeature pointFeat = iter.peek(); + assert pointFeat instanceof StationPointFeature : "Expected pointFeat to be a StationPointFeature, not a " + + pointFeat.getClass().getSimpleName(); + + StructureData obsData = pointFeat.getFeatureData(); + + timeName = pointFeat.getFeatureCollection().getTimeName(); + + Formatter coordNames = new Formatter().format("%s %s %s", timeName, latName, lonName); + if (!Double.isNaN(pointFeat.getLocation().getAltitude())) { + coordNames.format(" %s", altitudeCoordinateName); + } + + if (writer.getVersion().isExtendedModel()) { + addDataVariablesExtended(obsData, coordNames.toString()); + + } + addDataVariablesClassic(recordDim, obsData, dataMap, coordNames.toString()); + + } + } + + writer.create(); + if (!(writer.getVersion().isExtendedModel())) + record = writer.addRecordStructure(); // for netcdf3 + writeExtraVariables(); + + } + protected void writeHeader(List obsCoords, StructureData featureData, StructureData obsData, String coordNames) throws IOException { this.recordDim = writer.addUnlimitedDimension(recordDimName); @@ -606,7 +657,8 @@ protected void addDataVariablesClassic(Dimension recordDim, StructureData stnDat } else { VariableSimpleIF prevVar = writer.findVariable(oldVar.getShortName()); if (prevVar != null) { - if (extraMap.get(oldVar.getShortName()) != null) { // this is normal, extra got added but not actually needed + if (extraMap != null && extraMap.get(oldVar.getShortName()) != null) { // this is normal, extra got added but + // not actually needed writer.deleteVariable(oldVar.getShortName()); extraMap.remove(oldVar.getShortName()); } diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer/WriterCFStationCollection.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer/WriterCFStationCollection.java index 13b3985030..1ecb991345 100644 --- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer/WriterCFStationCollection.java +++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer/WriterCFStationCollection.java @@ -11,21 +11,25 @@ import ucar.nc2.constants.CDM; import ucar.nc2.constants.CF; import ucar.nc2.dataset.conv.CF1Convention; +import ucar.nc2.ft.DsgFeatureCollection; import ucar.nc2.ft.PointFeature; +import ucar.nc2.ft.StationTimeSeriesFeatureCollection; import ucar.nc2.ft.point.StationFeature; import ucar.nc2.ft.point.StationPointFeature; +import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl; import ucar.nc2.time.CalendarDate; import ucar.nc2.time.CalendarDateUnit; import ucar.unidata.geoloc.Station; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; /** * Write a CF "Discrete Sample" station file. * Example H.7. Timeseries of station data in the indexed ragged array representation. * *

- * + * *

  *   writeHeader()
  *   iterate { writeRecord() }
@@ -59,6 +63,58 @@ public WriterCFStationCollection(String fileOut, List atts, List coords = new ArrayList<>();
+
+    // see if there's altitude, wmoId for any stations
+    for (Station stn : stations) {
+      if (!Double.isNaN(stn.getAltitude()))
+        useAlt = true;
+      if ((stn.getWmoId() != null) && (!stn.getWmoId().trim().isEmpty()))
+        useWmoId = true;
+      if ((stn.getDescription() != null) && (!stn.getDescription().trim().isEmpty()))
+        useDesc = true;
+
+      // find string lengths
+      id_strlen = Math.max(id_strlen, stn.getName().length());
+      if (stn.getDescription() != null)
+        desc_strlen = Math.max(desc_strlen, stn.getDescription().length());
+      if (stn.getWmoId() != null)
+        wmo_strlen = Math.max(wmo_strlen, stn.getWmoId().length());
+
+      if (stn instanceof DsgFeatureCollection) {
+        DsgFeatureCollection dsgStation = (DsgFeatureCollection) stn;
+        if (coords.stream().noneMatch(x -> x.getShortName().equals(dsgStation.getTimeName()))) {
+          coords.add(VariableSimpleBuilder
+              .makeScalar(dsgStation.getTimeName(), "time of measurement", dsgStation.getTimeUnit().getUdUnit(),
+                  DataType.DOUBLE)
+              .addAttribute(CF.CALENDAR, dsgStation.getTimeUnit().getCalendar().toString()).build());
+        } else {
+          coords.add(
+              VariableSimpleBuilder.makeScalar(timeName, "time of measurement", timeUnit.getUdUnit(), DataType.DOUBLE)
+                  .addAttribute(CF.CALENDAR, timeUnit.getCalendar().toString()).build());
+        }
+      }
+    }
+
+    llbb = CFPointWriterUtils.getBoundingBox(stnList); // gets written in super.finish();
+
+    coords.add(VariableSimpleBuilder
+        .makeScalar(stationIndexName, "station index for this observation record", null, DataType.INT)
+        .addAttribute(CF.INSTANCE_DIMENSION, stationDimName).build());
+
+    super.writeHeader(coords, (StationTimeSeriesCollectionImpl) stations);
+
+    int count = 0;
+    stationIndexMap = new HashMap<>(2 * stnList.size());
+    for (StationFeature stn : stnList) {
+      writeStationData(stn);
+      stationIndexMap.put(stn.getName(), count);
+      count++;
+    }
+  }
+
   public void writeHeader(List stns, StationPointFeature spf) throws IOException {
     this.stnList = stns;
 
@@ -93,6 +149,7 @@ public void writeHeader(List stns, StationPointFeature spf) thro
         .makeScalar(stationIndexName, "station index for this observation record", null, DataType.INT)
         .addAttribute(CF.INSTANCE_DIMENSION, stationDimName).build());
 
+
     Formatter coordNames = new Formatter().format("%s %s %s", timeName, latName, lonName);
     if (useAlt)
       coordNames.format(" %s", stationAltName);
@@ -171,13 +228,25 @@ private void writeStationData(StationFeature stn) throws IOException {
   }
 
   public void writeRecord(Station s, PointFeature sobs, StructureData sdata) throws IOException {
-    writeRecord(s.getName(), sobs.getObservationTime(), sobs.getObservationTimeAsCalendarDate(), sdata);
+    if (s instanceof DsgFeatureCollection) {
+      DsgFeatureCollection dsgStation = (DsgFeatureCollection) s;
+      writeRecord(dsgStation.getName(), dsgStation.getTimeName(), sobs.getObservationTime(),
+          sobs.getObservationTimeAsCalendarDate(), dsgStation.getAltName(), sobs.getLocation().getAltitude(), sdata);
+    } else {
+      writeRecord(s.getName(), timeName, sobs.getObservationTime(), sobs.getObservationTimeAsCalendarDate(),
+          this.altitudeCoordinateName, sobs.getLocation().getAltitude(), sdata);
+    }
   }
 
   private int obsRecno;
 
   public void writeRecord(String stnName, double timeCoordValue, CalendarDate obsDate, StructureData sdata)
-      throws IOException {
+          throws IOException {
+    writeRecord(stnName, timeName, timeCoordValue, obsDate, altName, 0, sdata);
+  }
+
+  public void writeRecord(String stnName, String timeCoordName, double timeCoordValue, CalendarDate obsDate,
+      String altName, double altValue, StructureData sdata) throws IOException {
     trackBB(null, obsDate);
 
     Integer parentIndex = stationIndexMap.get(stnName);
@@ -185,7 +254,9 @@ public void writeRecord(String stnName, double timeCoordValue, CalendarDate obsD
       throw new RuntimeException("Cant find station " + stnName);
 
     StructureMembers.Builder smb = StructureMembers.builder().setName("Coords");
-    smb.addMemberScalar(timeName, null, null, DataType.DOUBLE, timeCoordValue);
+    smb.addMemberScalar(timeCoordName, null, null, DataType.DOUBLE, timeCoordValue);
+    if (useAlt)
+      smb.addMemberScalar(altName, null, null, DataType.DOUBLE, altValue);
     smb.addMemberScalar(stationIndexName, null, null, DataType.INT, parentIndex);
     StructureData coords = new StructureDataFromMember(smb.build());
 
@@ -193,5 +264,4 @@ public void writeRecord(String stnName, double timeCoordValue, CalendarDate obsD
     StructureDataComposite sdall = StructureDataComposite.create(ImmutableList.of(coords, sdata));
     obsRecno = super.writeStructureData(obsRecno, record, sdall, dataMap);
   }
-
 }
diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/CFPointWriter.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/CFPointWriter.java
index 639b6b89b3..878e9276da 100644
--- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/CFPointWriter.java
+++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/CFPointWriter.java
@@ -116,21 +116,19 @@ private static int writeStationFeatureCollection(FeatureDatasetPoint dataset, St
 
       cfWriter.setExtraVariables(fc.getExtraVariables());
 
-      // write all data, but no need to sort by station
-      PointFeatureCollection pfc = fc.flatten(null, null, null);
-
+      cfWriter.writeHeader(fc);
       int count = 0;
-      for (PointFeature pf : pfc) {
-        StationPointFeature spf = (StationPointFeature) pf;
-        if (count == 0)
-          cfWriter.writeHeader(fc.getStationFeatures(), spf);
-
-        cfWriter.writeRecord(spf.getStation(), pf, pf.getFeatureData());
-        count++;
-        if (debug && count % 100 == 0)
-          System.out.printf("%d ", count);
-        if (debug && count % 1000 == 0)
-          System.out.printf("%n ");
+      for (PointFeatureCollection pfc : fc) {
+        for (PointFeature pf : pfc) {
+          StationPointFeature spf = (StationPointFeature) pf;
+          cfWriter.writeRecord(spf.getStation().getName(), pf.getObservationTime(),
+              pf.getObservationTimeAsCalendarDate(), pf.getLocation().getAltitude(), pf.getFeatureData());
+          count++;
+          if (debug && count % 100 == 0)
+            System.out.printf("%d ", count);
+          if (debug && count % 1000 == 0)
+            System.out.printf("%n ");
+        }
       }
 
       cfWriter.finish();
diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointAbstract.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointAbstract.java
index 7c9d296675..6bbdd99a74 100644
--- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointAbstract.java
+++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointAbstract.java
@@ -6,15 +6,11 @@
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import javax.annotation.Nullable;
+
+import com.google.common.collect.Iterators;
+import com.google.common.collect.PeekingIterator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import ucar.ma2.Array;
@@ -39,6 +35,10 @@
 import ucar.nc2.constants.CF;
 import ucar.nc2.constants._Coordinate;
 import ucar.nc2.dataset.CoordinateAxis;
+import ucar.nc2.ft.PointFeature;
+import ucar.nc2.ft.StationTimeSeriesFeature;
+import ucar.nc2.ft.point.StationPointFeature;
+import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl;
 import ucar.nc2.time.CalendarDate;
 import ucar.nc2.time.CalendarDateFormatter;
 import ucar.nc2.time.CalendarDateUnit;
@@ -198,6 +198,63 @@ void makeMiddleVariables(StructureData middleData, boolean isExtended) {
     // NOOP
   }
 
+  protected void writeHeader(List obsCoords, StationTimeSeriesCollectionImpl stationFeatures,
+      @Nullable StructureData middleData) throws IOException {
+
+    this.recordDim = Dimension.builder().setName(recordDimName).setIsUnlimited(true).build();
+    writerb.addDimension(recordDim);
+
+    addExtraVariables();
+
+    PeekingIterator stIter = Iterators.peekingIterator(stationFeatures.iterator());
+    if (stIter.hasNext()) {
+      StationTimeSeriesFeature stationFeat = stIter.peek();
+
+      StructureData featureData = stationFeat.getFeatureData();
+      if (featureData != null) {
+        makeFeatureVariables(featureData, isExtendedModel);
+      }
+      if (middleData != null) {
+        makeMiddleVariables(middleData, isExtendedModel);
+      }
+    }
+    Structure.Builder recordb = null;
+    if (isExtendedModel) {
+      recordb = writerb.addStructure(recordName, recordDimName);
+      addCoordinatesExtended(recordb, obsCoords);
+    } else {
+      addCoordinatesClassic(recordDim, obsCoords, dataMap);
+    }
+
+    for (StationTimeSeriesFeature stnFeature : stationFeatures) {
+      PeekingIterator iter = Iterators.peekingIterator(stnFeature.iterator());
+      if (iter.hasNext()) {
+        PointFeature pointFeat = iter.peek();
+        assert pointFeat instanceof StationPointFeature : "Expected pointFeat to be a StationPointFeature, not a "
+            + pointFeat.getClass().getSimpleName();
+
+        StructureData obsData = pointFeat.getFeatureData();
+
+        String timeCoordName = pointFeat.getFeatureCollection().getTimeName();
+
+        Formatter coordNames = new Formatter().format("%s %s %s", timeCoordName, latName, lonName);
+        if (!Double.isNaN(pointFeat.getLocation().getAltitude())) {
+          coordNames.format(" %s", altitudeCoordinateName);
+        }
+        if (isExtendedModel) {
+          addDataVariablesExtended(recordb, obsData, coordNames.toString());
+
+        } else {
+          addDataVariablesClassic(recordDim, obsData, dataMap, coordNames.toString());
+        }
+      }
+    }
+    this.writer = writerb.build();
+
+    writeExtraVariables();
+    finishBuilding();
+  }
+
   void writeHeader(List obsCoords, StructureData featureData, @Nullable StructureData middleData,
       StructureData obsData, String coordNames) throws IOException {
     this.recordDim = Dimension.builder().setName(recordDimName).setIsUnlimited(true).build();
@@ -343,6 +400,8 @@ private void addDataVariablesExtended(Structure.Builder recordb, StructureDat
       VariableSimpleIF oldVar = findDataVar(m.getName());
       if (oldVar == null)
         continue;
+      if (recordb.findMemberVariable(m.getName()).isPresent())
+        continue;
 
       // make dimension list
       StringBuilder dimNames = new StringBuilder();
diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointCollection.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointCollection.java
index 2ecf5726d9..77206d9560 100644
--- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointCollection.java
+++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFPointCollection.java
@@ -88,7 +88,7 @@ private void writeRecord(double timeCoordValue, CalendarDate obsDate, EarthLocat
     smb.addMemberScalar(latName, null, null, DataType.DOUBLE, loc.getLatitude());
     smb.addMemberScalar(lonName, null, null, DataType.DOUBLE, loc.getLongitude());
     if (altUnits != null)
-      smb.addMemberScalar(altName, null, null, DataType.DOUBLE, loc.getAltitude());
+      smb.addMemberScalar(altitudeCoordinateName, null, null, DataType.DOUBLE, loc.getAltitude());
     StructureData coords = new StructureDataFromMember(smb.build());
 
     // coords first so it takes precedence
diff --git a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFStationCollection.java b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFStationCollection.java
index c148fc3be5..5120d8efec 100644
--- a/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFStationCollection.java
+++ b/cdm/core/src/main/java/ucar/nc2/ft/point/writer2/WriterCFStationCollection.java
@@ -8,10 +8,11 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Formatter;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.stream.Collectors;
+
 import ucar.ma2.DataType;
 import ucar.ma2.StructureData;
 import ucar.ma2.StructureDataComposite;
@@ -26,9 +27,10 @@
 import ucar.nc2.constants.CDM;
 import ucar.nc2.constants.CF;
 import ucar.nc2.dataset.conv.CF1Convention;
-import ucar.nc2.ft.PointFeature;
+import ucar.nc2.ft.DsgFeatureCollection;
+import ucar.nc2.ft.StationTimeSeriesFeatureCollection;
 import ucar.nc2.ft.point.StationFeature;
-import ucar.nc2.ft.point.StationPointFeature;
+import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl;
 import ucar.nc2.time.CalendarDate;
 import ucar.nc2.time.CalendarDateUnit;
 import ucar.unidata.geoloc.Station;
@@ -78,11 +80,12 @@ void finishBuilding() throws IOException {
     stationStruct = findStructure(stationStructName);
   }
 
-  void writeHeader(List stns, StationPointFeature spf) throws IOException {
-    this.stnList = stns;
+  protected void writeHeader(StationTimeSeriesFeatureCollection stations) throws IOException {
+    this.stnList = stations.getStationFeatures().stream().distinct().collect(Collectors.toList());
+    List coords = new ArrayList<>();
 
     // see if there's altitude, wmoId for any stations
-    for (Station stn : stnList) {
+    for (Station stn : stations) {
       if (!Double.isNaN(stn.getAltitude()))
         useAlt = true;
       if ((stn.getWmoId() != null) && (!stn.getWmoId().trim().isEmpty()))
@@ -96,36 +99,35 @@ void writeHeader(List stns, StationPointFeature spf) throws IOEx
         desc_strlen = Math.max(desc_strlen, stn.getDescription().length());
       if (stn.getWmoId() != null)
         wmo_strlen = Math.max(wmo_strlen, stn.getWmoId().length());
-    }
-
-    llbb = CFPointWriterUtils.getBoundingBox(stnList); // gets written in super.finish();
 
-    StationFeature sf = spf.getStation();
-    StructureData stnData = sf.getFeatureData();
-    StructureData obsData = spf.getFeatureData();
-
-    List coords = new ArrayList<>();
-    coords.add(VariableSimpleBuilder.makeScalar(timeName, "time of measurement", timeUnit.getUdUnit(), DataType.DOUBLE)
-        .addAttribute(CF.CALENDAR, timeUnit.getCalendar().toString()).build());
+      if (stn instanceof DsgFeatureCollection) {
+        DsgFeatureCollection dsgStation = (DsgFeatureCollection) stn;
+        if (coords.stream().noneMatch(x -> x.getShortName().equals(dsgStation.getTimeName()))) {
+          coords.add(VariableSimpleBuilder
+              .makeScalar(dsgStation.getTimeName(), "time of measurement", dsgStation.getTimeUnit().getUdUnit(),
+                  DataType.DOUBLE)
+              .addAttribute(CF.CALENDAR, dsgStation.getTimeUnit().getCalendar().toString()).build());
+        }
+      } else {
+        coords.add(
+            VariableSimpleBuilder.makeScalar(timeName, "time of measurement", timeUnit.getUdUnit(), DataType.DOUBLE)
+                .addAttribute(CF.CALENDAR, timeUnit.getCalendar().toString()).build());
+      }
+    }
 
+    llbb = ucar.nc2.ft.point.writer.CFPointWriterUtils.getBoundingBox(stnList); // gets written in super.finish();
     coords.add(VariableSimpleBuilder
         .makeScalar(stationIndexName, "station index for this observation record", null, DataType.INT)
         .addAttribute(CF.INSTANCE_DIMENSION, stationDimName).build());
 
-    Formatter coordNames = new Formatter().format("%s %s %s", timeName, latName, lonName);
-    if (useAlt)
-      coordNames.format(" %s", stationAltName);
-
-    super.writeHeader(coords, stnData, null, obsData, coordNames.toString());
-
+    super.writeHeader(coords, (StationTimeSeriesCollectionImpl) stations, null);
     int count = 0;
-    stationIndexMap = new HashMap<>(2 * stns.size());
+    stationIndexMap = new HashMap<>(2 * stnList.size());
     for (StationFeature stn : stnList) {
       writeStationData(stn);
       stationIndexMap.put(stn.getName(), count);
       count++;
     }
-
   }
 
   @Override
@@ -188,14 +190,10 @@ private void writeStationData(StationFeature stn) throws IOException {
     stnRecno = super.writeStructureData(stnRecno, stationStruct, sdall, featureVarMap);
   }
 
-  void writeRecord(Station s, PointFeature sobs, StructureData sdata) throws IOException {
-    writeRecord(s.getName(), sobs.getObservationTime(), sobs.getObservationTimeAsCalendarDate(), sdata);
-  }
-
   private int obsRecno;
 
-  private void writeRecord(String stnName, double timeCoordValue, CalendarDate obsDate, StructureData sdata)
-      throws IOException {
+  protected void writeRecord(String stnName, double timeCoordValue, CalendarDate obsDate, double altCoordValue,
+      StructureData sdata) throws IOException {
     trackBB(null, obsDate);
 
     Integer parentIndex = stationIndexMap.get(stnName);
@@ -204,6 +202,8 @@ private void writeRecord(String stnName, double timeCoordValue, CalendarDate obs
 
     StructureMembers.Builder smb = StructureMembers.builder().setName("Coords");
     smb.addMemberScalar(timeName, null, null, DataType.DOUBLE, timeCoordValue);
+    if (!Double.isNaN(altCoordValue))
+      smb.addMemberScalar(altitudeCoordinateName, null, null, DataType.DOUBLE, altCoordValue);
     smb.addMemberScalar(stationIndexName, null, null, DataType.INT, parentIndex);
     StructureData coords = new StructureDataFromMember(smb.build());
 
diff --git a/cdm/core/src/main/java/ucar/nc2/ft2/coverage/writer/CoverageAsPoint.java b/cdm/core/src/main/java/ucar/nc2/ft2/coverage/writer/CoverageAsPoint.java
index 94608726d0..ce29905eaf 100644
--- a/cdm/core/src/main/java/ucar/nc2/ft2/coverage/writer/CoverageAsPoint.java
+++ b/cdm/core/src/main/java/ucar/nc2/ft2/coverage/writer/CoverageAsPoint.java
@@ -161,6 +161,8 @@ private class CoverageAsStationProfileCollection extends StationProfileCollectio
 
     CoverageAsStationProfileCollection(VarGroup varGroup) {
       super(varGroup.name + " AsStationProfileCollection", varGroup.dateUnit, varGroup.zUnit);
+      this.timeName = varGroup.timeAxis != null ? varGroup.timeAxis.getName() : "time";
+      this.altName = varGroup.zAxis != null ? varGroup.zAxis.getName() : "altitude";
       this.varGroup = varGroup;
       this.collectionFeatureType = varGroup.fType;
     }
@@ -187,7 +189,8 @@ protected StationHelper createStationHelper() throws IOException {
     private StationFeature createStationFeature(String name) {
       double stationZ = varGroup.zAxis != null ? varGroup.zAxis.getCoordEdgeFirst() : 0.0;
       return new CoverageAsStationProfile(name, name, null, nearestLatLonPoint.getLatitude(),
-          nearestLatLonPoint.getLongitude(), stationZ, this.timeUnit, this.altUnits, -1, varGroup);
+          nearestLatLonPoint.getLongitude(), stationZ, this.timeName, this.timeUnit, this.altName, this.altUnits, -1,
+          varGroup);
     }
   }
 
@@ -197,6 +200,8 @@ private class CoverageAsStationFeatureCollection extends StationTimeSeriesCollec
 
     CoverageAsStationFeatureCollection(VarGroup varGroup) {
       super(varGroup.name + " AsStationFeatureCollection", varGroup.dateUnit, varGroup.zUnit);
+      this.timeName = varGroup.timeAxis != null ? varGroup.timeAxis.getName() : "time";
+      this.altName = varGroup.zAxis != null ? varGroup.zAxis.getName() : "altitude";
       this.varGroup = varGroup;
       this.collectionFeatureType = varGroup.fType;
     }
@@ -213,7 +218,8 @@ protected StationHelper createStationHelper() {
     private StationFeature createStationFeature(String name) {
       double stationZ = varGroup.zAxis != null ? varGroup.zAxis.getCoordMidpoint(0) : 0.0;
       return new CoverageAsStationFeature(name, name, null, nearestLatLonPoint.getLatitude(),
-          nearestLatLonPoint.getLongitude(), stationZ, this.timeUnit, null, -1, varGroup);
+          nearestLatLonPoint.getLongitude(), stationZ, this.timeName, this.timeUnit, this.altName, this.altUnits, -1,
+          varGroup);
     }
   }
 
@@ -223,9 +229,9 @@ private StationFeature createStationFeature(String name) {
   private class CoverageAsStationProfile extends StationProfileFeatureImpl {
     private VarGroup varGroup;
 
-    public CoverageAsStationProfile(String name, String desc, String wmoId, double lat, double lon, double alt,
-        CalendarDateUnit timeUnit, String altUnits, int npts, VarGroup varGroup) {
-      super(name, desc, wmoId, lat, lon, alt, timeUnit, altUnits, npts);
+    private CoverageAsStationProfile(String name, String desc, String wmoId, double lat, double lon, double alt,
+        String timeName, CalendarDateUnit timeUnit, String altName, String altUnits, int npts, VarGroup varGroup) {
+      super(name, desc, wmoId, lat, lon, alt, timeName, timeUnit, altName, altUnits, npts);
       this.varGroup = varGroup;
     }
 
@@ -456,11 +462,9 @@ private class CoverageAsStationFeature extends StationTimeSeriesFeatureImpl {
 
     private VarGroup varGroup;
 
-    CoverageAsStationFeature(String name, String desc, String wmoId, double lat, double lon, double alt,
-        CalendarDateUnit timeUnit, String altUnits, int npts, VarGroup varGroup) {
-      // String name, String desc, String wmoId, double lat, double lon, double alt, DateUnit timeUnit, String altUnits,
-      // int npts
-      super(name, desc, wmoId, lat, lon, alt, timeUnit, altUnits, npts, StructureData.EMPTY);
+    private CoverageAsStationFeature(String name, String desc, String wmoId, double lat, double lon, double alt,
+        String timeName, CalendarDateUnit timeUnit, String altName, String altUnits, int npts, VarGroup varGroup) {
+      super(name, desc, wmoId, lat, lon, alt, timeName, timeUnit, altName, altUnits, npts, StructureData.EMPTY);
       this.varGroup = varGroup;
     }
 
diff --git a/cdm/core/src/test/data/point/testGridAsPointAxes.ncml b/cdm/core/src/test/data/point/testGridAsPointAxes.ncml
new file mode 100644
index 0000000000..e093156a8c
--- /dev/null
+++ b/cdm/core/src/test/data/point/testGridAsPointAxes.ncml
@@ -0,0 +1,300 @@
+
+
+   
+   
+   
+   
+   
+   
+   
+      
+      0 3600
+   
+   
+      
+      
+      0 10 20 30
+   
+   
+      
+      0 3 6 9 12
+   
+   
+      
+      0 5 10
+   
+   
+      
+      0 3600
+   
+   
+      
+      
+      0 10
+   
+   
+      
+          0 1 2
+          10 11 12
+          20 21 22
+          30 31 32
+          40 41 42
+          100 101 102
+          110 111 112
+          120 121 122
+          130 131 132
+          140 141 142
+          200 201 202
+          210 211 212
+          220 221 222
+          230 231 232
+          240 241 242
+          300 301 302
+          310 311 312
+          320 321 322
+          330 331 332
+          340 341 342
+          1000 1001 1002
+          1010 1011 1012
+          1020 1021 1022
+          1030 1031 1032
+          1040 1041 1042
+          1100 1101 1102
+          1110 1111 1112
+          1120 1121 1122
+          1130 1131 1132
+          1140 1141 1142
+          1200 1201 1202
+          1210 1211 1212
+          1220 1221 1222
+          1230 1231 1232
+          1240 1241 1242
+          1300 1301 1302
+          1310 1311 1312
+          1320 1321 1322
+          1330 1331 1332
+          1340 1341 1342
+      
+   
+   
+      
+          0 1 2
+          10 11 12
+          20 21 22
+          30 31 32
+          40 41 42
+          1000 1001 1002
+          1010 1011 1012
+          1020 1021 1022
+          1030 1031 1032
+          1040 1041 1042
+           0 1 2
+           10 11 12
+           20 21 22
+           30 31 32
+           40 41 42
+           1000 1001 1002
+           1010 1011 1012
+           1020 1021 1022
+           1030 1031 1032
+           1040 1041 1042
+      
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100 101 102
+            110 111 112
+            120 121 122
+            130 131 132
+            140 141 142
+            200 201 202
+            210 211 212
+            220 221 222
+            230 231 232
+            240 241 242
+            300 301 302
+            310 311 312
+            320 321 322
+            330 331 332
+            340 341 342
+            1000 1001 1002
+            1010 1011 1012
+            1020 1021 1022
+            1030 1031 1032
+            1040 1041 1042
+            1100 1101 1102
+            1110 1111 1112
+            1120 1121 1122
+            1130 1131 1132
+            1140 1141 1142
+            1200 1201 1202
+            1210 1211 1212
+            1220 1221 1222
+            1230 1231 1232
+            1240 1241 1242
+            1300 1301 1302
+            1310 1311 1312
+            1320 1321 1322
+            1330 1331 1332
+            1340 1341 1342
+      
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100 101 102
+            110 111 112
+            120 121 122
+            130 131 132
+            140 141 142
+            200 201 202
+            210 211 212
+            220 221 222
+            230 231 232
+            240 241 242
+            300 301 302
+            310 311 312
+            320 321 322
+            330 331 332
+            340 341 342
+        
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100 101 102
+            110 111 112
+            120 121 122
+            130 131 132
+            140 141 142
+            200 201 202
+            210 211 212
+            220 221 222
+            230 231 232
+            240 241 242
+            300 301 302
+            310 311 312
+            320 321 322
+            330 331 332
+            340 341 342
+        
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100 101 102
+            110 111 112
+            120 121 122
+            130 131 132
+            140 141 142
+
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100  101  102
+            110  111  112
+            120  121  122
+            130  131  132
+            140  141  142
+      
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+      
+   
+   
+      
+            0  1  2
+            10  11  12
+            20  21  22
+            30  31  32
+            40  41  42
+            100  101  102
+            110  111  112
+            120  121  122
+            130  131  132
+            140  141  142
+            200  201  202
+            210  211  212
+            220  221  222
+            230  231  232
+            240  241  242
+            300  301  302
+            310  311  312
+            320  321  322
+            330  331  332
+            340  341  342
+      
+   
+   
+      
+            0 1 2
+            10 11 12
+            20 21 22
+            30 31 32
+            40 41 42
+            100 101 102
+            110 111 112
+            120 121 122
+            130 131 132
+            140 141 142
+            200 201 202
+            210 211 212
+            220 221 222
+            230 231 232
+            240 241 242
+            300 301 302
+            310 311 312
+            320 321 322
+            330 331 332
+            340 341 342
+            1000 1001 1002
+            1010 1011 1012
+            1020 1021 1022
+            1030 1031 1032
+            1040 1041 1042
+            1100 1101 1102
+            1110 1111 1112
+            1120 1121 1122
+            1130 1131 1132
+            1140 1141 1142
+            1200 1201 1202
+            1210 1211 1212
+            1220 1221 1222
+            1230 1231 1232
+            1240 1241 1242
+            1300 1301 1302
+            1310 1311 1312
+            1320 1321 1322
+            1330 1331 1332
+            1340 1341 1342
+      
+   
+
\ No newline at end of file
diff --git a/cdm/core/src/test/java/ucar/nc2/ft2/coverage/writer/TestCoverageAsPoint.java b/cdm/core/src/test/java/ucar/nc2/ft2/coverage/writer/TestCoverageAsPoint.java
index f94c4ef5dc..a98cc3c804 100644
--- a/cdm/core/src/test/java/ucar/nc2/ft2/coverage/writer/TestCoverageAsPoint.java
+++ b/cdm/core/src/test/java/ucar/nc2/ft2/coverage/writer/TestCoverageAsPoint.java
@@ -23,7 +23,7 @@
 /** Test CoverageAsPoint */
 public class TestCoverageAsPoint {
 
-  private static final String testFilePath = TestDir.cdmLocalTestDataDir + "rankTest.nc";
+  private static final String testFilePath = TestDir.cdmLocalTestDataDir + "point/testGridAsPointAxes.ncml";
 
   private static CoverageCollection gds;
 
@@ -68,6 +68,7 @@ public void testVarFeatureTypes() throws IOException {
     SubsetParams params = new SubsetParams();
     params.setVariables(stationVarNames);
     params.setLatLonPoint(latlon);
+    params.setVertCoord(0);
 
     FeatureDatasetPoint fdp1 = new CoverageAsPoint(gds, stationVarNames, params).asFeatureDatasetPoint();
     assertThat(fdp1.getFeatureType()).isEqualTo(FeatureType.STATION);
@@ -75,6 +76,8 @@ public void testVarFeatureTypes() throws IOException {
     // vars with z should be station profile
     List profileVarNames = Arrays.asList(new String[] {"full4", "withT1", "full3", "3D", "4D"});
 
+    params = new SubsetParams();
+    params.setLatLonPoint(latlon);
     params.setVariables(profileVarNames);
 
     FeatureDatasetPoint fdp2 = new CoverageAsPoint(gds, profileVarNames, params).asFeatureDatasetPoint();
@@ -95,8 +98,19 @@ public void testCoverageAsPoint() throws IOException {
     // test time series
     varNames = new ArrayList<>();
     varNames.add("T1noZ");
+    vals = Arrays.copyOfRange(expected, 0, 2);
     params.setVariables(varNames);
-    readCoverageAsPoint(varNames, params, alts[0], times, vals);
+    readCoverageAsPoint(varNames, params, alts[0], times, vals, 0, "time1");
+
+    // test multiple time axes
+    varNames = new ArrayList<>();
+    varNames.add("full4");
+    varNames.add("withT1");
+    vals = new double[] {11.0, 1011.0};
+    params.setVariables(varNames);
+    params.setVertCoord(alts[0]);
+    readCoverageAsPoint(varNames, params, alts[0], times, vals, 0, "time");
+    readCoverageAsPoint(varNames, params, alts[0], times, vals, 1, "time1");
   }
 
   @Test
@@ -129,6 +143,48 @@ public void testCoverageAsProfile() throws IOException {
     varNames.add("4D");
     params.setVariables(varNames);
     readCoverageAsProfile(varNames, params, alts, times, expected);
+
+    // test two different time axes
+    varNames = new ArrayList<>();
+    varNames.add("full4");
+    varNames.add("withT1");
+    params = new SubsetParams();
+    params.setVariables(varNames);
+    params.setLatLonPoint(latlon);
+    readCoverageAsProfile(varNames, params, alts, times, expected);
+    readCoverageAsProfile(varNames, params, alts, times, expected, 1, "time1", "z");
+
+    // test no time different z-axis names
+    varNames = new ArrayList<>();
+    varNames.add("full3");
+    varNames.add("Z1noT");
+    params = new SubsetParams();
+    params.setVariables(varNames);
+    params.setLatLonPoint(latlon);
+    readCoverageAsProfile(varNames, params, alts, times, Arrays.copyOfRange(expected, 0, 4));
+    readCoverageAsProfile(varNames, params, alts, times, Arrays.copyOfRange(expected, 0, 2), 1, "time", "z1");
+
+    // test single timeseries different z-axis names
+    varNames = new ArrayList<>();
+    varNames.add("withT1");
+    varNames.add("withT1Z1");
+    params = new SubsetParams();
+    params.setVariables(varNames);
+    params.setLatLonPoint(latlon);
+    readCoverageAsProfile(varNames, params, alts, times, expected, 0, "time1", "z");
+    readCoverageAsProfile(varNames, params, Arrays.copyOfRange(alts, 0, 2), times, Arrays.copyOfRange(expected, 0, 4),
+        1, "time1", "z1");
+
+    // test different time-axis names and different z-axis names
+    varNames = new ArrayList<>();
+    varNames.add("full4");
+    varNames.add("withT1Z1");
+    params = new SubsetParams();
+    params.setVariables(varNames);
+    params.setLatLonPoint(latlon);
+    readCoverageAsProfile(varNames, params, alts, times, expected);
+    readCoverageAsProfile(varNames, params, Arrays.copyOfRange(alts, 0, 2), times, Arrays.copyOfRange(expected, 0, 4),
+        1, "time1", "z1");
   }
 
   @Test
@@ -158,16 +214,22 @@ public void testSubset() throws IOException {
 
   private void readCoverageAsPoint(List varNames, SubsetParams params, double alt, double[] time,
       double[] expected) throws IOException {
+    readCoverageAsPoint(varNames, params, alt, time, expected, 0, "time");
+  }
+
+  private void readCoverageAsPoint(List varNames, SubsetParams params, double alt, double[] time,
+      double[] expected, int stationIndex, String timeName) throws IOException {
     FeatureDatasetPoint fdp = new CoverageAsPoint(gds, varNames, params).asFeatureDatasetPoint();
     assertThat(fdp.getFeatureType()).isEqualTo(FeatureType.STATION);
-    final String varName = varNames.get(0);
+    final String varName = varNames.get(stationIndex);
 
     StationTimeSeriesFeatureCollection fc =
-        (StationTimeSeriesFeatureCollection) fdp.getPointFeatureCollectionList().get(0);
+        (StationTimeSeriesFeatureCollection) fdp.getPointFeatureCollectionList().get(stationIndex);
     assertThat(fc).isNotNull();
     assertThat(fc.getCollectionFeatureType()).isEqualTo(FeatureType.STATION);
 
     StationTimeSeriesFeature stationFeature = (StationTimeSeriesFeature) fc.getStationFeatures().get(0);
+
     int i = 0;
     for (PointFeature feat : stationFeature) {
       assertThat(feat).isInstanceOf(StationPointFeature.class);
@@ -177,6 +239,7 @@ private void readCoverageAsPoint(List varNames, SubsetParams params, dou
       assertThat(station.getLatitude()).isEqualTo(lat);
       assertThat(station.getLongitude()).isEqualTo(lon);
       assertThat(station.getAltitude()).isEqualTo(alt);
+      assertThat(((StationTimeSeriesFeature) station).getTimeName()).isEqualTo(timeName);
       assertThat(feat.getObservationTime()).isEqualTo(time[i]);
 
       // verify point data
@@ -189,11 +252,17 @@ private void readCoverageAsPoint(List varNames, SubsetParams params, dou
 
   private void readCoverageAsProfile(List varNames, SubsetParams params, double[] alt, double[] time,
       double[] expected) throws IOException {
+    readCoverageAsProfile(varNames, params, alt, time, expected, 0, "time", "z");
+  }
+
+  private void readCoverageAsProfile(List varNames, SubsetParams params, double[] alt, double[] time,
+      double[] expected, int stationIndex, String timeName, String altName) throws IOException {
     FeatureDatasetPoint fdp = new CoverageAsPoint(gds, varNames, params).asFeatureDatasetPoint();
     assertThat(fdp.getFeatureType()).isEqualTo(FeatureType.STATION_PROFILE);
-    final String varName = varNames.get(0);
+    final String varName = varNames.get(stationIndex);
 
-    StationProfileFeatureCollection fc = (StationProfileFeatureCollection) fdp.getPointFeatureCollectionList().get(0);
+    StationProfileFeatureCollection fc =
+        (StationProfileFeatureCollection) fdp.getPointFeatureCollectionList().get(stationIndex);
     assertThat(fc).isNotNull();
     assertThat(fc.getCollectionFeatureType()).isEqualTo(FeatureType.STATION_PROFILE);
 
@@ -207,8 +276,10 @@ private void readCoverageAsProfile(List varNames, SubsetParams params, d
         Station station = ((StationPointFeature) feat).getStation();
         assertThat(station.getLatitude()).isEqualTo(lat);
         assertThat(station.getLongitude()).isEqualTo(lon);
-        assertThat(feat.getLocation().getAltitude()).isEqualTo(alts[i % alts.length]);
-        assertThat(feat.getObservationTime()).isEqualTo(time[i / alts.length]);
+        assertThat(feat.getLocation().getAltitude()).isEqualTo(alt[i % alt.length]);
+        assertThat(((StationProfileFeature) station).getTimeName()).isEqualTo(timeName);
+        assertThat(((StationProfileFeature) station).getAltName()).isEqualTo(altName);
+        assertThat(feat.getObservationTime()).isEqualTo(time[i / alt.length]);
         // verify point data
 
         Array data = feat.getDataAll().getArray(varName);