Java tutorial
/*--------------- Kalypso-Header -------------------------------------------------------------------- This file is part of kalypso. Copyright (C) 2004, 2005 by: Technical University Hamburg-Harburg (TUHH) Institute of River and coastal engineering Denickestr. 22 21073 Hamburg, Germany http://www.tuhh.de/wb and Bjoernsen Consulting Engineers (BCE) Maria Trost 3 56070 Koblenz, Germany http://www.bjoernsen.de 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; either version 2.1 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: E-Mail: belger@bjoernsen.de schlienger@bjoernsen.de v.doemming@tuhh.de ---------------------------------------------------------------------------------------------------*/ package org.kalypso.ogc.sensor.timeseries; import java.awt.Color; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.DateFormat; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.joda.time.LocalTime; import org.joda.time.Period; import org.kalypso.commons.java.util.StringUtilities; import org.kalypso.contribs.eclipse.core.runtime.StatusUtilities; import org.kalypso.contribs.java.awt.ColorUtilities; import org.kalypso.contribs.java.util.DateUtilities; import org.kalypso.contribs.java.util.PropertiesUtilities; import org.kalypso.core.KalypsoCorePlugin; import org.kalypso.core.i18n.Messages; import org.kalypso.ogc.sensor.DateRange; import org.kalypso.ogc.sensor.IAxis; import org.kalypso.ogc.sensor.IObservation; import org.kalypso.ogc.sensor.ITupleModel; import org.kalypso.ogc.sensor.SensorException; import org.kalypso.ogc.sensor.TIMESERIES_TYPE; import org.kalypso.ogc.sensor.impl.DefaultAxis; import org.kalypso.ogc.sensor.metadata.ITimeseriesConstants; import org.kalypso.ogc.sensor.metadata.MetadataList; import org.kalypso.ogc.sensor.request.IRequest; import org.kalypso.ogc.sensor.timeseries.wq.IWQConverter; import org.kalypso.ogc.sensor.timeseries.wq.WQException; import org.kalypso.ogc.sensor.timeseries.wq.WQFactory; import org.kalypsodeegree.KalypsoDeegreePlugin; /** * Utility for dealing Kalypso time series. * * @author schlienger */ public final class TimeseriesUtils implements ITimeseriesConstants { public static final String[] TYPES_ALL; /** * to enable searching in types the array must be sorted */ static { final String[] types = new String[] { TYPE_DATE, TYPE_EVAPORATION, TYPE_RAINFALL, TYPE_RUNOFF, TYPE_TEMPERATURE, TYPE_VOLUME, TYPE_WATERLEVEL, TYPE_NORM, TYPE_AREA, TYPE_HOURS, TYPE_NORMNULL, TYPE_KC, TYPE_WT, TYPE_LAI, TYPE_HUMIDITY, TYPE_VELOCITY, TYPE_SUNSHINE_HOURS, TYPE_MEAN_WIND_VELOCITY, TYPE_MEAN_TEMPERATURE, TYPE_MEAN_HUMIDITY, TYPE_EVAPORATION_LAND_BASED, TYPE_EVAPORATION_WATER_BASED }; Arrays.sort(types); TYPES_ALL = types; } /** default date format used within some of the time series dependent properties */ /** @deprecated Should not be used any more. We use xs:dateTime format now for printing times into zml files. */ @Deprecated private static final DateFormat FORECAST_DF = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.GERMANY); private static final String PROP_TIMESERIES_CONFIG = "kalypso.timeseries.properties"; //$NON-NLS-1$ private static URL CONFIG_BASE_URL = TimeseriesUtils.class.getResource("resource/"); //$NON-NLS-1$ private static String BASENAME = "config"; //$NON-NLS-1$ private static Properties CONFIG; private static HashMap<String, NumberFormat> FORMAT_MAP = new HashMap<>(); private static NumberFormat DEFAULT_FORMAT = null; private TimeseriesUtils() { // no instantiation } /** * Finds out which metadata of the given observation begin with the given prefix. * <p> * This is for instance useful for the Alarmstufen * * @param obs * @param mdPrefix * @return list of metadata keys or empty array if nothing found */ public static String[] findOutMDBeginningWith(final IObservation obs, final String mdPrefix) { if (obs == null) return ArrayUtils.EMPTY_STRING_ARRAY; final MetadataList mdl = obs.getMetadataList(); final ArrayList<String> mds = new ArrayList<>(); final Set<Object> keySet = mdl.keySet(); for (final Object object : keySet) { final String md = object.toString(); if (md.startsWith(mdPrefix)) mds.add(md); } return mds.toArray(new String[mds.size()]); } /** * Finds out the list of alarmstufen metadata keys * * @return list of metadata keys */ public static String[] findOutMDAlarmLevel(final IObservation obs) { return findOutMDBeginningWith(obs, "Alarmstufe"); //$NON-NLS-1$ } /** * Returns the color to use when displaying the value of the given Alarmstufe. * * @return color */ public static Color getColorForAlarmLevel(final String mdAlarm) { final String strColor = getProperties().getProperty("COLOR_" + mdAlarm); //$NON-NLS-1$ if (strColor == null) return Color.RED; return StringUtilities.stringToColor(strColor); } /** * Lazy loading of the properties * * @return config of the timeseries package */ private static synchronized Properties getProperties() { if (CONFIG == null) { CONFIG = new Properties(); final Properties defaultConfig = new Properties(); CONFIG = new Properties(defaultConfig); // The config file in the sources is used as defaults PropertiesUtilities.loadI18nProperties(defaultConfig, CONFIG_BASE_URL, BASENAME); // TODO: also load configured properties via i18n mechanism InputStream configIs = null; try { // If we have a configured config file, use it as standard final URL configUrl = Platform.isRunning() ? Platform.getConfigurationLocation().getURL() : null; final String timeseriesConfigLocation = System.getProperty(PROP_TIMESERIES_CONFIG); final URL timeseriesConfigUrl = timeseriesConfigLocation == null ? null : new URL(configUrl, timeseriesConfigLocation); // TODO: load time series ini from local config: ni order to support debugging correctly, sue this pattern: // final URL proxyConfigLocation = HwvProductSachsenAnhalt.findConfigLocation( CONFIG_PROXY_PATH ); try { if (timeseriesConfigUrl != null) configIs = timeseriesConfigUrl.openStream(); } catch (final Throwable t) { // ignore: there is no config file; we are using standard instead final IStatus status = StatusUtilities.createStatus(IStatus.WARNING, "Specified timeseries config file at " + timeseriesConfigUrl.toExternalForm() //$NON-NLS-1$ + " does not exist. Using default settings.", //$NON-NLS-1$ null); KalypsoCorePlugin.getDefault().getLog().log(status); t.printStackTrace(); } if (configIs != null) { CONFIG.load(configIs); configIs.close(); } } catch (final IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(configIs); } } return CONFIG; } /** * Returns a new instance of DateRangeArgument containing the beginning and the end of the forecast, given the * observation is a forecast. * <p> * An observation is a forecast when it has the MD_VORHERSAGE Metadata. * * @param obs * @return date range of the forecast or null if obs isn't a forecast. */ public static DateRange isTargetForecast(final IObservation obs) { if (obs == null) return null; final MetadataList mdl = obs.getMetadataList(); final String forecastFrom = mdl.getProperty(ITimeseriesConstants.MD_VORHERSAGE_START); final String forecastTo = mdl.getProperty(ITimeseriesConstants.MD_VORHERSAGE_ENDE); if (forecastFrom != null || forecastTo != null) { // new version: if one of the two is set, we assume that the zml is in new format final Date from = forecastFrom == null ? null : DateUtilities.parseDateTime(forecastFrom); final Date to = forecastTo == null ? null : DateUtilities.parseDateTime(forecastTo); return new DateRange(from, to); } // Backwards compability: still try to parse old 'Vorhersage' metadata final String range = mdl.getProperty(ITimeseriesConstants.MD_VORHERSAGE); if (range != null) { final String[] splits = range.split(";"); //$NON-NLS-1$ if (splits.length == 2) { final String fromStr = splits[0]; final String toStr = splits[1]; try { final Date from = DateUtilities.parseDateTime(fromStr); final Date to = DateUtilities.parseDateTime(toStr); return new DateRange(from, to); } catch (final IllegalArgumentException e) { // ignore, probably it is an old zml } // TRICKY: in order to support backwards compatibility, we still try to parse the old format try { final Date from = FORECAST_DF.parse(fromStr); final Date to = FORECAST_DF.parse(toStr); return new DateRange(from, to); } catch (final ParseException e) { e.printStackTrace(); } } } return null; } /** * Units are read from the config.properties file. * * @param type * @return corresponding unit */ public static String getUnit(final String type) { return getProperties().getProperty("AXISUNIT_" + type, ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Returns a user-friendly name for the given type. * <p> * Note to Developer: keep the config.properties file up-to-date * * @return corresponding name (user friendly) */ public static String getName(final String type) { return getProperties().getProperty("AXISNAME_" + type, ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Returns a color for the given type. * <p> * Note to Developer: keep the config.properties file up-to-date * * @return a Color that is defined to be used with the given axis type, or a random color when no fits */ public static Color[] getColorsFor(final String type) { final String strColor = getProperties().getProperty("AXISCOLOR_" + type); //$NON-NLS-1$ if (strColor == null) return new Color[] { ColorUtilities.random() }; final String[] strings = strColor.split("#"); //$NON-NLS-1$ if (strings.length == 0) return new Color[] { ColorUtilities.random() }; final Color[] colors = new Color[strings.length]; for (int i = 0; i < colors.length; i++) colors[i] = StringUtilities.stringToColor(strings[i]); return colors; } /** * @param mdKey * @return color for the given Metadata information */ public static Color getColorForMD(final String mdKey) { final String strColor = getProperties().getProperty("MDCOLOR_" + mdKey); //$NON-NLS-1$ if (strColor != null) return StringUtilities.stringToColor(strColor); // no color found? so return random one return ColorUtilities.random(); } /** * @return true if the axis type is known to be a key axis */ public static boolean isKey(final String type) { return Boolean.valueOf(getProperties().getProperty("IS_KEY_" + type, "false")).booleanValue(); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Create a default axis for the given type. */ public static IAxis createDefaultAxis(final String type) { return new DefaultAxis(getName(type), type, getUnit(type), getDataClass(type), isKey(type)); } /** * Create a default axis for the given type and the key flag. */ public static IAxis createDefaultAxis(final String type, final boolean isKey) { return new DefaultAxis(getName(type), type, getUnit(type), getDataClass(type), isKey); } /** * Returns a NumberFormat instance according to the given timeserie type. If there is no specific instance for the * given type, then a default number format is returned. * * @return instance of NumberFormat that can be used to display the values to the user */ public static NumberFormat getNumberFormatFor(final String type) { return getNumberFormat(getDefaultFormatString(type)); } /** * Returns the adequate NumberFormat for the given format-string. It currently only supports formats of the form %X.Yf * where actually only the Y is used to build a NumberFormat with Y minimum/maximum-fraction-digits. * <p> * The plan is, once we'll be using JDK 5.0, we'll try to replace this with the built-in functionality provided with * formated printing. * <p> * TODO once on JDK 5.0 use formated printing if possible. Note that some refactoring might need to be done since we * currently work with NumberFormats. */ public static synchronized NumberFormat getNumberFormat(final String format) { final NumberFormat nf = FORMAT_MAP.get(format); if (nf != null) return nf; if ("%d".equals(format)) //$NON-NLS-1$ { final NumberFormat wf = NumberFormat.getIntegerInstance(); wf.setGroupingUsed(false); FORMAT_MAP.put(format, wf); return wf; } // parse the format spec and only take the min-fraction-digit part final String regex = "%([0-9]*)\\.?([0-9]*)f"; //$NON-NLS-1$ final Pattern pattern = Pattern.compile(regex); final Matcher matcher = pattern.matcher(format); if (matcher.matches()) { final String minfd = matcher.group(2); final NumberFormat wf = NumberFormat.getInstance(); final int intValue = Integer.valueOf(minfd).intValue(); wf.setMinimumFractionDigits(intValue); wf.setMaximumFractionDigits(intValue); FORMAT_MAP.put(format, wf); return wf; } return getDefaultFormat(); } private static synchronized NumberFormat getDefaultFormat() { if (DEFAULT_FORMAT == null) { DEFAULT_FORMAT = NumberFormat.getNumberInstance(); DEFAULT_FORMAT.setMinimumFractionDigits(3); } return DEFAULT_FORMAT; } /** * It is currently fix and is: "dd.MM.yy HH:mm" * * @return the date format to use when displaying dates for observations/timeseries */ public static DateFormat getDateFormat() { final DateFormat sdf = new SimpleDateFormat("dd.MM.yy HH:mm"); //$NON-NLS-1$ final TimeZone timeZone = KalypsoCorePlugin.getDefault().getTimeZone(); sdf.setTimeZone(timeZone); return sdf; } public static Class<?> getDataClass(final String type) { try { return Class.forName(getProperties().getProperty("AXISCLASS_" + type, "")); //$NON-NLS-1$ //$NON-NLS-2$ } catch (final ClassNotFoundException e) { throw new IllegalArgumentException( Messages.getString("org.kalypso.ogc.sensor.timeseries.TimeserieUtils.19") + type); //$NON-NLS-1$ } } public static IAxis[] createDefaultAxes(final String[] axisTypes, final boolean firstWithKey) { final List<IAxis> axisList = new ArrayList<>(); if (axisTypes != null && axisTypes.length > 0) { axisList.add(TimeseriesUtils.createDefaultAxis(axisTypes[0], firstWithKey)); for (int i = 1; i < axisTypes.length; i++) { axisList.add(TimeseriesUtils.createDefaultAxis(axisTypes[i], false)); } } return axisList.toArray(new IAxis[axisList.size()]); } /** * @return the default format string for the given type */ public static String getDefaultFormatString(final String type) { return getProperties().getProperty("FORMAT_" + type); //$NON-NLS-1$ } /** * @return the default top margin defined for the given type or null if none */ public static Double getTopMargin(final String type) { final String margin = getProperties().getProperty("TOP_MARGIN_" + type); //$NON-NLS-1$ if (margin == null) return null; return Double.valueOf(margin); } /** * @param gkr * the Gausskrger Rechtswert as string * @return the corresponding Gausskrger Coordinate System Name */ public static String getCoordinateSystemNameForGkr(final String gkr) { final String crsName = getProperties().getProperty("GK_" + gkr.substring(0, 1), null); //$NON-NLS-1$ if (crsName == null) KalypsoDeegreePlugin.getDefault().getCoordinateSystem(); return crsName; } /** * Return the value of the alarmLevel in regard to the given axisType. The alarm-levels are stored according to the * W-axis. If you want the value according to the Q-axis you should call this function with axisType = Q * * @param axisType * the type of the axis for which to convert the alarm-level * @throws WQException */ public static Double convertAlarmLevel(final IObservation obs, final ITupleModel model, final Integer index, final String axisType, final Double alarmLevel) throws SensorException, WQException { if (axisType.equals(ITimeseriesConstants.TYPE_WATERLEVEL)) return alarmLevel; final IWQConverter converter = WQFactory.createWQConverter(obs); if (axisType.equals(ITimeseriesConstants.TYPE_RUNOFF) || axisType.equals(ITimeseriesConstants.TYPE_VOLUME)) return new Double(converter.computeQ(model, index, alarmLevel.doubleValue())); throw new WQException(Messages.getString("org.kalypso.ogc.sensor.timeseries.TimeserieUtils.22") + axisType //$NON-NLS-1$ + Messages.getString("org.kalypso.ogc.sensor.timeseries.TimeserieUtils.23")); //$NON-NLS-1$ } /** * Returns the class name for the given axis-type. The class must inherit from * <code>org.jfree.chart.axis.ValueAxis</code>. * * @return The class name for the given axis-type. The class must inherit from * <code>org.jfree.chart.axis.ValueAxis</code>. */ public static String getAxisClassFor(final String type) { return getProperties().getProperty("AXISJFREECHARTCLASS_" + type, null); //$NON-NLS-1$ } public static DateRange getDateRange(final IRequest args) { if (args == null) return null; return args.getDateRange(); } /** * This function guesses the timestep of a given timeseries from several timsteps. * * @param timeseries * The tuple model of a timeseries. * @return The timestep or null. */ public static Period guessTimestep(final ITupleModel timeseries) throws SensorException { final TimestepGuesser timestepGuesser = new TimestepGuesser(timeseries, -1); return timestepGuesser.execute(); } /** * This function guesses the timestamp of a given timeseries from several timsteps. Only timeseries with a timestep of * 1 day do have a timestamp. * * @param timeseries * The tuple model of a timeseries. * @param timestep * The timestep of the timeseries. * @return The timestamp in UTC or null. */ public static LocalTime guessTimestamp(final ITupleModel timeseries, final Period timestep) throws SensorException { /* The timestamp is only relevant for day values. */ if (timestep != null && timestep.toStandardMinutes().getMinutes() == 1440) { /* Guess the timestamp of the timeseries. */ final TimestampGuesser guesser = new TimestampGuesser(timeseries, -1); return guesser.execute(); } return null; } public static TIMESERIES_TYPE getType(final String axisType) { return TIMESERIES_TYPE.getType(axisType); } }