/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2016, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.imageio.netcdf.cv; import java.io.IOException; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.imageio.netcdf.cv.CoordinateHandlerSpi.CoordinateHandler; import org.geotools.imageio.netcdf.utilities.NetCDFUtilities; import org.geotools.util.Utilities; import org.geotools.util.logging.Logging; import org.opengis.referencing.crs.CoordinateReferenceSystem; import ucar.ma2.Array; import ucar.ma2.ArrayChar; import ucar.ma2.ArrayChar.StringIterator; import ucar.ma2.DataType; import ucar.ma2.IndexIterator; import ucar.nc2.Attribute; import ucar.nc2.constants.AxisType; import ucar.nc2.dataset.CoordinateAxis; import ucar.nc2.dataset.CoordinateAxis1D; import ucar.nc2.dataset.CoordinateAxis2D; /** * @author Simone Giannecchini GeoSolutions SAS * @author Niels Charlier * @param <T> * */ public abstract class CoordinateVariable<T> { protected interface AxisHelper<T> { int getSize(); T get(Map<String, Integer> indexMap); List<T> getAll(); T getMinimum(); T getMaximum(); } protected class CoordinateAxis1DNumericHelper implements AxisHelper<T> { private CoordinateAxis1D axis1D; public CoordinateAxis1DNumericHelper() { this.axis1D = (CoordinateAxis1D) coordinateAxis; } @Override public synchronized T get(Map<String, Integer> indexMap) { // Made it synchronized since axis1D values retrieval // does cached read on its underlying return convertValue(axis1D.getCoordValue(indexMap.get(coordinateAxis.getFullName()))); } @Override public int getSize() { return axis1D.getShape(0); } @Override public T getMinimum() { return convertValue(axis1D.getMinValue()); } @Override public T getMaximum() { return convertValue(axis1D.getMaxValue()); } @Override public List<T> getAll() { return new AbstractList<T>() { @Override public T get(int index) { return convertValue(axis1D.getCoordValue(index)); } @Override public int size() { return axis1D.getShape(0); } }; } } /** * To use in case that * (1) coordinate axis is not one-dimensional * (2) coordinate axis is not numerical */ protected class CoordinateAxisGeneralHelper implements AxisHelper<T> { private List<T> convertedData = new ArrayList<T>(); private SortedSet<T> orderedSet = new TreeSet<T>(); public CoordinateAxisGeneralHelper() { Array data; try { data = coordinateAxis.read(); } catch (IOException ioe) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Error reading coordinate values ", ioe); } throw new IllegalStateException(ioe); } if (data instanceof ArrayChar) { StringIterator it = ((ArrayChar) data).getStringIterator(); while (it.hasNext()) { String val = it.next(); if (val != null && !val.isEmpty()) { T convertedVal = convertValue(val); orderedSet.add(convertedVal); convertedData.add(convertedVal); } else { convertedData.add(null); } } } else { IndexIterator it = data.getIndexIterator(); while (it.hasNext()) { Object val = it.next(); if (!isMissing(val)) { T convertedVal = convertValue(val); orderedSet.add(convertedVal); convertedData.add(convertedVal); } else { convertedData.add(null); } } } } @Override public synchronized T get(Map<String, Integer> indexMap) { int i = indexMap.get(coordinateAxis.getFullName()); int j = coordinateAxis instanceof CoordinateAxis2D ? indexMap.get(coordinateAxis.getDimension(0).getFullName()) : 0; return convertedData.get(j * coordinateAxis.getDimension(1).getLength() + i); } @Override public int getSize() { return orderedSet.size(); } @Override public T getMinimum() { return orderedSet.first(); } @Override public T getMaximum() { return orderedSet.last(); } @Override public List<T> getAll() { return new ArrayList<T>(orderedSet); } } private static final double KM_TO_M = 1000d; private final static Logger LOGGER = Logging.getLogger(CoordinateVariable.class); public static Class<?> suggestBinding(CoordinateAxis coordinateAxis) { Utilities.ensureNonNull("coordinateAxis", coordinateAxis); final AxisType axisType = coordinateAxis.getAxisType(); switch (axisType) { case GeoX: case GeoY: case GeoZ: case Height: case Lat: case Lon: case Pressure: case Spectral: // numeric ? final DataType dataType = coordinateAxis.getDataType(); // scale and offset are there? Attribute scaleFactor = coordinateAxis.findAttribute("scale_factor"); Attribute offsetFactor = coordinateAxis.findAttribute("offset"); if (scaleFactor != null || offsetFactor != null) { return Double.class; } switch (dataType) { case DOUBLE: return Double.class; case BYTE: return Byte.class; case FLOAT: return Float.class; case INT: return Integer.class; case LONG: return Long.class; case SHORT: return Short.class; default: break; } break; case Time: case RunTime: // numeric LOGGER.log(Level.FINE, "Date mapping for axis:" + coordinateAxis.toString()); return java.util.Date.class; default: break; } // unable to recognize this one LOGGER.log(Level.FINE, "Unable to find mapping for axis:" + coordinateAxis.toString()); return null; } @SuppressWarnings({ "rawtypes", "unchecked" }) public static CoordinateVariable<?> create(CoordinateAxis coordinateAxis) { Utilities.ensureNonNull("coordinateAxis", coordinateAxis); final AxisType axisType = coordinateAxis.getAxisType(); if (coordinateAxis.isNumeric()) { // AxisType? switch (axisType) { case GeoX: case GeoY: case GeoZ: case Height: case Lat: case Lon: case Pressure: case Spectral: return new NumericCoordinateVariable(suggestBinding(coordinateAxis), coordinateAxis); case RunTime: case Time: return new TimeCoordinateVariable(coordinateAxis); default: throw new IllegalArgumentException("Unsupported axis type: " + axisType + " for coordinate variable: " + coordinateAxis.toStringDebug()); } } if (NetCDFUtilities.isCheckCoordinatePlugins()){ if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Checking for registered coordinate plugins"); } CoordinateHandler handler = CoordinateHandlerFinder.findHandler(coordinateAxis); if (handler != null){ return handler.createCoordinateVariable(coordinateAxis); } } // If the axis is not numeric and it isn't a parseable time, we can't process any further. throw new IllegalArgumentException("Unable to process non numeric coordinate variable: " + coordinateAxis.toString()); } protected final Class<T> binding; protected final CoordinateAxis coordinateAxis; private CoordinateReferenceSystem crs; private double conversionFactor = Double.NaN; private boolean convertAxis = false; private AxisHelper<T> axisHelper; /** * @param binding * @param coordinateAxis */ public CoordinateVariable(Class<T> binding, CoordinateAxis coordinateAxis) { Utilities.ensureNonNull("coordinateAxis", coordinateAxis); Utilities.ensureNonNull("binding", binding); this.binding = binding; this.coordinateAxis = coordinateAxis; this.conversionFactor = 1d; AxisType axisType = coordinateAxis.getAxisType(); // Special management for projected coordinates with unit = km if ((axisType == AxisType.GeoX || axisType == AxisType.GeoY) && coordinateAxis.getUnitsString().equalsIgnoreCase("km")) { conversionFactor = KM_TO_M; convertAxis = true; } } protected void init() { if (coordinateAxis.isNumeric() && coordinateAxis instanceof CoordinateAxis1D && !coordinateAxis.hasMissing()) { axisHelper = new CoordinateAxis1DNumericHelper(); } else { axisHelper = new CoordinateAxisGeneralHelper(); } } protected boolean isMissing(Object val) { if (val instanceof Number) { return coordinateAxis.isMissing( ((Number) val).doubleValue()); } else { return val == null; } } public Class<T> getType() { return binding; } public String getUnit() { return coordinateAxis.getUnitsString(); } public CoordinateAxis unwrap() { return coordinateAxis; } public AxisType getAxisType() { return coordinateAxis.getAxisType(); } public String getName() { return coordinateAxis.getShortName(); } public long getSize() throws IOException { return axisHelper.getSize(); } public boolean isRegular() { return coordinateAxis instanceof CoordinateAxis1D && ((CoordinateAxis1D) coordinateAxis).isRegular(); } public double getIncrement() { if (!(coordinateAxis instanceof CoordinateAxis1D)) { return Double.NaN; } return convertAxis ? ((CoordinateAxis1D) coordinateAxis).getIncrement() * conversionFactor : ((CoordinateAxis1D) coordinateAxis).getIncrement(); } public double getStart() { if (!(coordinateAxis instanceof CoordinateAxis1D)) { return Double.NaN; } return convertAxis ? ((CoordinateAxis1D) coordinateAxis).getStart() * conversionFactor : ((CoordinateAxis1D) coordinateAxis).getStart(); } public T getMinimum() throws IOException { return axisHelper.getMinimum(); } public T getMaximum() throws IOException { return axisHelper.getMaximum(); } public T read(Map<String, Integer> indexMap) throws IndexOutOfBoundsException { return axisHelper.get(indexMap); } public List<T> read() throws IndexOutOfBoundsException { return axisHelper.getAll(); } public final CoordinateReferenceSystem getCoordinateReferenceSystem() { if (crs == null) { crs = buildCoordinateReferenceSystem(); } return crs; } abstract public boolean isNumeric(); abstract protected T convertValue(Object o); abstract protected CoordinateReferenceSystem buildCoordinateReferenceSystem(); @Override public String toString() { try { return "CoordinateVariable [binding=" + binding + ", coordinateAxis=" + coordinateAxis + ", getType()=" + getType() + ", getUnit()=" + getUnit() + ", getAxisType()=" + getAxisType() + ", getName()=" + getName() + ", getSize()=" + getSize() + ", isRegular()=" + isRegular() + ", getIncrement()=" + getIncrement() + ", getStart()=" + getStart() + ", isNumeric()=" + isNumeric() + ", getMinimum()=" + getMinimum() + ", getMaximum()=" + getMaximum() + "]"; } catch (IOException e) { throw new RuntimeException(e); } } }