| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.storage.netcdf.base; |
| |
| import java.io.IOException; |
| import java.time.Instant; |
| import java.util.TimeZone; |
| import java.util.GregorianCalendar; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.apache.sis.math.Vector; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.util.privy.Constants; |
| |
| |
| /** |
| * Handles particularity of HYCOM format. It is not yet clear whether those particularities are used elsewhere or not. |
| * We handle them in a separated class for now and may refactor later in a more general mechanism for providing extensions. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-315">SIS-315</a> |
| */ |
| final class HYCOM { |
| /** |
| * The pattern to use for identifying temporal units of the form "day as %Y%m%d.%f". |
| * "%Y" is year formatted as at least four digits, "%m" is month formatted as two digits, |
| * and "%d" is day of month formatted as two digits. |
| * |
| * Example: 20181017.0000 for 2018-10-17. |
| */ |
| private static final Pattern DATE_PATTERN = Pattern.compile("days?\\s+as\\s+(?-i)%Y%m%d.*", Pattern.CASE_INSENSITIVE); |
| |
| /** |
| * Do not allow instantiation of this class. |
| */ |
| private HYCOM() { |
| } |
| |
| /** |
| * If any variable uses the "day as %Y%m%d.%f" pseudo-units, converts to a number of days since the epoch. |
| * The epoch is taken from the unit of the dimension. Example of netCDF file header: |
| * |
| * <pre class="text"> |
| * dimensions: |
| * MT = UNLIMITED ; // (1 currently) |
| * Y = 3298 ; |
| * X = 4500 ; |
| * variables: |
| * double MT(MT) ; |
| * MT:long_name = "time" ; |
| * MT:units = "days since 1900-12-31 00:00:00" ; |
| * MT:calendar = "standard" ; |
| * MT:axis = "T" ; |
| * double Date(MT) ; |
| * Date:long_name = "date" ; |
| * Date:units = "day as %Y%m%d.%f" ; |
| * Date:C_format = "%13.4f" ; |
| * Date:FORTRAN_format = "(f13.4)" ; |
| * data: |
| * MT = 43024 ; |
| * Date = 20181017.0000 ;</pre> |
| * |
| * In this example, the real units of {@code Date(MT)} will be taken from {@code MT(MT)}, which is |
| * "days since 1900-12-31 00:00:00". |
| */ |
| static void convert(final Decoder decoder, final Variable[] variables) throws IOException, DataStoreException { |
| Matcher matcher = null; |
| for (final Variable variable : variables) { |
| if (variable.getNumDimensions() == 1) { |
| final String units = variable.getUnitsString(); |
| if (units != null) { |
| if (matcher == null) { |
| matcher = DATE_PATTERN.matcher(units); |
| } else { |
| matcher.reset(units); |
| } |
| if (matcher.matches()) { |
| final Dimension dimension = variable.getGridDimensions().get(0); |
| Instant epoch = variable.setUnit(decoder.findVariable(dimension.getName()), Units.DAY); |
| if (epoch == null) { |
| epoch = Instant.EPOCH; |
| } |
| final long origin = epoch.toEpochMilli(); |
| /* |
| * Convert all dates into numbers of days since the epoch. |
| */ |
| Vector values = variable.read(); |
| final double[] times = new double[values.size()]; |
| final GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone(decoder.getTimeZone()), Decoder.DATA_LOCALE); |
| calendar.clear(); |
| for (int i=0; i<times.length; i++) { |
| double time = values.doubleValue(i); // Date encoded as a double (e.g. 20181017) |
| long date = (long) time; // Round toward zero. |
| time -= date; // Fractional part of the day. |
| int day = (int) (date % 100); date /= 100; |
| int month = (int) (date % 100); date /= 100; |
| calendar.set(Math.toIntExact(date), month - 1, day, 0, 0, 0); |
| date = calendar.getTimeInMillis() - origin; // Milliseconds since epoch. |
| time += date / (double) Constants.MILLISECONDS_PER_DAY; |
| times[i] = time; |
| } |
| variable.setValues(times); |
| } |
| } |
| } |
| } |
| } |
| } |