ingo@1095: package de.intevation.flys.utils;
ingo@1095:
felix@1150: import org.apache.log4j.Logger;
ingo@1740:
ingo@1740: import java.text.NumberFormat;
ingo@1105: import java.util.HashMap;
ingo@1740: import java.util.List;
ingo@1105: import java.util.Map;
ingo@2068: import java.util.regex.Matcher;
ingo@2068: import java.util.regex.Pattern;
ingo@1105:
ingo@1105: import javax.xml.xpath.XPathConstants;
ingo@1105:
ingo@1105: import org.w3c.dom.Document;
ingo@1105:
ingo@1845: import org.hibernate.SessionFactory;
ingo@1845: import org.hibernate.impl.SessionFactoryImpl;
ingo@1845:
ingo@1095: import gnu.trove.TDoubleArrayList;
ingo@1095:
ingo@1174: import de.intevation.artifacts.Artifact;
ingo@1118: import de.intevation.artifacts.CallContext;
ingo@1118:
ingo@1105: import de.intevation.artifacts.common.utils.Config;
ingo@1105: import de.intevation.artifacts.common.utils.XMLUtils;
ingo@1105:
ingo@1845: import de.intevation.flys.backend.SessionFactoryProvider;
ingo@1845:
felix@1777: import de.intevation.flys.artifacts.context.FLYSContext;
ingo@1095: import de.intevation.flys.artifacts.FLYSArtifact;
ingo@1740: import de.intevation.flys.artifacts.WINFOArtifact;
felix@1099: import de.intevation.flys.artifacts.model.RiverFactory;
ingo@2063: import de.intevation.flys.artifacts.model.LocationProvider;
ingo@2068: import de.intevation.flys.artifacts.model.WQ;
ingo@1740: import de.intevation.flys.model.Gauge;
ingo@1740: import de.intevation.flys.model.MainValue;
felix@1099: import de.intevation.flys.model.River;
ingo@1095:
ingo@1095: public class FLYSUtils {
ingo@1095:
felix@1150: /** The logger that is used in this utility. */
felix@1150: private static Logger logger = Logger.getLogger(FLYSUtils.class);
felix@1150:
ingo@1095: public static enum KM_MODE { RANGE, LOCATIONS, NONE };
ingo@1095:
ingo@2038: /**
ingo@2038: * An enum that represents the 5 possible WQ modes in FLYS. The 5 values are
ingo@2038: * QFREE QGAUGE WGAUGE WFREE and NONE.
ingo@2038: */
ingo@2038: public static enum WQ_MODE { QFREE, QGAUGE, WFREE, WGAUGE, NONE };
ingo@2038:
ingo@2068:
ingo@2068: public static final Pattern NUMBERS_PATTERN =
ingo@2068: Pattern.compile("\\D*(\\d++.\\d*)\\D*");
ingo@2068:
ingo@1105: public static final String XPATH_RIVER_PROJECTION =
ingo@1105: "/artifact-database/floodmap/river[@name=$name]/srid/@value";
ingo@1105:
ingo@1129: public static final String XPATH_SHAPEFILE_DIR =
ingo@1129: "/artifact-database/floodmap/shapefile-path/@value";
ingo@1129:
ingo@1129: public static final String XPATH_VELOCITY_LOGFILE =
ingo@1129: "/artifact-database/floodmap/velocity/logfile/@path";
ingo@1129:
ingo@1129: public static final String XPATH_MAPSERVER_URL =
ingo@1129: "/artifact-database/floodmap/mapserver/server/@path";
ingo@1129:
ingo@1129: public static final String XPATH_MAPFILE_PATH =
ingo@1129: "/artifact-database/floodmap/mapserver/mapfile/@path";
ingo@1129:
ingo@1129: public static final String XPATH_MAPFILE_TEMPLATE =
ingo@1129: "/artifact-database/floodmap/mapserver/map-template/@path";
ingo@1129:
ingo@1129: public static final String XPATH_MAPSERVER_TEMPLATE_PATH =
ingo@1129: "/artifact-database/floodmap/mapserver/templates/@path";
ingo@1129:
ingo@1095:
ingo@1095: private FLYSUtils() {
ingo@1095: }
ingo@1095:
ingo@1095:
felix@2017: /**
felix@2017: * Pulls Artifact with given UUID fromm database.
felix@2017: * @return FLYSArtifact with given UUID or null (in case of errors).
felix@2017: */
ingo@1118: public static FLYSArtifact getArtifact(String uuid, CallContext context) {
felix@1150: try {
ingo@1174: Artifact artifact = context.getDatabase().getRawArtifact(uuid);
ingo@1174:
ingo@1174: if (artifact == null) {
ingo@1174: logger.error("Artifact '" + uuid + "' does not exist.");
ingo@1174: return null;
ingo@1174: }
ingo@1174:
ingo@1174: if (!(artifact instanceof FLYSArtifact)) {
ingo@1174: logger.error("Artifact '" +uuid+ "' is no valid FLYSArtifact.");
ingo@1174: return null;
ingo@1174: }
ingo@1174:
ingo@1174: return (FLYSArtifact) artifact;
felix@1150: }
felix@1150: // TODO: catch more selective
felix@1150: catch (Exception e) {
felix@2017: logger.error("Cannot get FLYSArtifact " + uuid
felix@2017: + " from database (" + e.getMessage() + ").");
felix@1150: return null;
felix@1150: }
ingo@1118: }
ingo@1118:
ingo@1118:
ingo@1129: /**
felix@1777: * Returns the FLYSContext from context object.
felix@1777: *
felix@1777: * @param context The CallContext or the FLYSContext.
felix@1777: *
felix@1777: * @return the FLYSContext.
felix@1777: */
felix@1777: public static FLYSContext getFlysContext(Object context) {
felix@1777: return context instanceof FLYSContext
felix@1777: ? (FLYSContext) context
felix@1777: : (FLYSContext) ((CallContext) context).globalContext();
felix@1777: }
felix@1777:
felix@1777:
felix@1777: /**
ingo@1129: * Convinience function to retrieve an XPath as string with replaced config
ingo@1129: * directory.
ingo@1129: *
ingo@1129: * @param xpath The XPath expression.
ingo@1129: *
ingo@1129: * @return a string with replaced config directory.
ingo@1129: */
ingo@1129: public static String getXPathString(String xpath) {
ingo@1129: String tmp = Config.getStringXPath(xpath);
ingo@1129: tmp = Config.replaceConfigDir(tmp);
ingo@1129:
ingo@1129: return tmp;
ingo@1129: }
ingo@1129:
ingo@1129:
ingo@1845: public static boolean isUsingOracle() {
ingo@1845: SessionFactory sf = SessionFactoryProvider.getSessionFactory();
ingo@1845:
ingo@1845: String d = SessionFactoryProvider.getDriver((SessionFactoryImpl) sf);
ingo@1845:
ingo@1845: return d != null ? d.indexOf("Oracle") >= 0 : false;
ingo@1845: }
ingo@1845:
ingo@1845:
ingo@2038: /**
ingo@2038: * This method returns an WQ_MODE enum which is based on the parameters
ingo@2038: * stored in flys Artifact. If there is no wq_mode parameter
ingo@2038: * existing, WQ_MODE.NONE is returned.
ingo@2038: *
ingo@2038: * @param flys The FLYSArtifact that stores wq mode relevant parameters.
ingo@2038: *
ingo@2038: * @return an enum WQ_MODE.
ingo@2038: */
ingo@2038: public static WQ_MODE getWQMode(FLYSArtifact flys) {
ingo@2081: if (flys == null) {
ingo@2081: return WQ_MODE.NONE;
ingo@2081: }
ingo@2081:
ingo@2038: String mode = flys.getDataAsString("wq_mode");
ingo@2081: Boolean free = flys.getDataAsBoolean("wq_free");
ingo@2081:
ingo@2081: free = free != null ? free : false;
ingo@2038:
ingo@2038: if (mode != null && mode.equals("Q")) {
ingo@2038: return free ? WQ_MODE.QFREE : WQ_MODE.QGAUGE;
ingo@2038: }
ingo@2038: else if (mode != null && mode.equals("W")) {
ingo@2038: return free ? WQ_MODE.WFREE : WQ_MODE.WGAUGE;
ingo@2038: }
ingo@2038: else {
ingo@2038: return WQ_MODE.NONE;
ingo@2038: }
ingo@2038: }
ingo@2038:
ingo@2038:
ingo@1095: public static KM_MODE getKmRangeMode(FLYSArtifact flys) {
felix@1099: String mode = flys.getDataAsString("ld_mode");
ingo@1095:
ingo@1095: if (mode == null || mode.length() == 0) {
ingo@1095: return KM_MODE.NONE;
ingo@1095: }
ingo@1095: else if (mode.equals("distance")) {
ingo@1095: return KM_MODE.RANGE;
ingo@1095: }
ingo@1095: else if (mode.equals("locations")) {
ingo@1095: return KM_MODE.LOCATIONS;
ingo@1095: }
ingo@1095: else {
ingo@1095: return KM_MODE.NONE;
ingo@1095: }
ingo@1095: }
ingo@1095:
felix@2041: /**
felix@2041: * Get min and max kilometer, independent of parametization
felix@2041: * (ld_from/to vs ld_locations).
felix@2041: */
ingo@1095: public static double[] getKmRange(FLYSArtifact flys) {
ingo@1095: switch (getKmRangeMode(flys)) {
ingo@1095: case RANGE: {
ingo@1095: return getKmFromTo(flys);
ingo@1095: }
ingo@1095:
ingo@1095: case LOCATIONS: {
ingo@1095: double[] locs = getLocations(flys);
ingo@1095: return new double[] { locs[0], locs[locs.length-1] };
ingo@1095: }
ingo@1095:
ingo@1095: case NONE: {
ingo@1095: double[] locs = getLocations(flys);
ingo@1095: if (locs != null) {
ingo@1095: return new double[] { locs[0], locs[locs.length-1] };
ingo@1095: }
ingo@1095: else {
ingo@1095: return getKmFromTo(flys);
ingo@1095: }
ingo@1095: }
ingo@1095: }
ingo@1095:
ingo@1095: return new double[] { Double.NaN, Double.NaN };
ingo@1095: }
ingo@1095:
ingo@1095:
ingo@1095: public static double[] getKmFromTo(FLYSArtifact flys) {
ingo@1095: String strFrom = flys.getDataAsString("ld_from");
ingo@1095: String strTo = flys.getDataAsString("ld_to");
ingo@1095:
ingo@1095: if (strFrom == null || strTo == null) {
ingo@1095: return null;
ingo@1095: }
ingo@1095:
ingo@1095: try {
ingo@1095: return new double[] {
ingo@1095: Double.parseDouble(strFrom),
ingo@1095: Double.parseDouble(strTo) };
ingo@1095: }
ingo@1095: catch (NumberFormatException nfe) {
ingo@1095: return null;
ingo@1095: }
ingo@1095: }
ingo@1095:
ingo@1095:
felix@2041: /**
felix@2041: * Return sorted array of locations at which stuff was calculated
felix@2041: * (from ld_locations data), null if not parameterized this way.
felix@2041: */
ingo@1095: public static double[] getLocations(FLYSArtifact flys) {
ingo@1095: String locationStr = flys.getDataAsString("ld_locations");
ingo@1095:
ingo@1095: if (locationStr == null || locationStr.length() == 0) {
ingo@1095: return null;
ingo@1095: }
ingo@1095:
ingo@1095: String[] tmp = locationStr.split(" ");
ingo@1095: TDoubleArrayList locations = new TDoubleArrayList();
ingo@1095:
ingo@1095: for (String l: tmp) {
ingo@1095: try {
ingo@1095: locations.add(Double.parseDouble(l));
ingo@1095: }
ingo@1095: catch (NumberFormatException nfe) {
ingo@1095: }
ingo@1095: }
ingo@1095:
ingo@1095: locations.sort();
ingo@1095:
ingo@1095: return locations.toNativeArray();
ingo@1095: }
felix@1099:
felix@1099:
felix@1099: /**
ingo@2045: * Returns the Qs for a given FLYSArtifact. This method currently accepts
ingo@2045: * only instances of WINFOArtifact.
ingo@2045: *
ingo@2045: * @param flys A FLYSArtifact.
ingo@2045: *
ingo@2045: * @return the Qs.
ingo@2045: */
ingo@2045: public static double[] getQs(FLYSArtifact flys) {
ingo@2045: double[] kmRange = getKmRange(flys);
ingo@2045:
ingo@2045: // XXX this is not nice!
ingo@2045: if (flys instanceof WINFOArtifact) {
ingo@2045: return ((WINFOArtifact) flys).getQs(kmRange);
ingo@2045: }
ingo@2045:
ingo@2045: logger.warn("This method currently supports WINFOArtifact only!");
ingo@2045:
ingo@2045: return null;
ingo@2045: }
ingo@2045:
ingo@2045:
ingo@2045: /**
ingo@2045: * Returns the Ws for a given FLYSArtifact. This method currently accepts
ingo@2045: * only instances of WINFOArtifact.
ingo@2045: *
ingo@2045: * @param flys A FLYSArtifact.
ingo@2045: *
ingo@2045: * @return the Ws.
ingo@2045: */
ingo@2045: public static double[] getWs(FLYSArtifact flys) {
ingo@2045: double[] kmRange = getKmRange(flys);
ingo@2045:
ingo@2045: // XXX this is not nice!
ingo@2045: if (flys instanceof WINFOArtifact) {
ingo@2045: return ((WINFOArtifact) flys).getWs(kmRange);
ingo@2045: }
ingo@2045:
ingo@2045: logger.warn("This method currently supports WINFOArtifact only!");
ingo@2045:
ingo@2045: return null;
ingo@2045: }
ingo@2045:
ingo@2045:
ingo@2045: /**
felix@1099: * Returns the selected River object based on the 'river' data that might
felix@1099: * have been inserted by the user.
felix@1099: *
felix@1099: * @return the selected River or null if no river has been chosen yet.
felix@1099: */
felix@1099: public static River getRiver(FLYSArtifact flys) {
ingo@2045: String sRiver = getRivername(flys);
felix@1099:
felix@1099: return (sRiver != null)
felix@1099: ? RiverFactory.getRiver(sRiver)
felix@1099: : null;
felix@1099: }
ingo@1105:
ingo@1105:
ingo@1105: /**
ingo@2045: * Returns the name of the river specified in the given flys
ingo@2045: * Artifact.
ingo@2045: *
ingo@2045: * @param flys The FLYSArtifact that stores a river relevant information.
ingo@2045: *
ingo@2045: * @return the name of the specified river or null.
ingo@2045: */
ingo@2045: public static String getRivername(FLYSArtifact flys) {
ingo@2045: return flys != null ? flys.getDataAsString("river") : null;
ingo@2045: }
ingo@2045:
ingo@2045:
ingo@2045: /**
ingo@1105: * Extracts the SRID defined in the global configuration for the river
ingo@1105: * specified in artifact.
ingo@1105: *
ingo@1105: * @param artifact The FLYSArtifact that stores the name of the river.
ingo@1105: *
ingo@1105: * @return the SRID as string (e.g. "31466").
ingo@1105: */
ingo@1105: public static String getRiverSrid(FLYSArtifact artifact) {
ingo@1105: String river = artifact.getDataAsString("river");
ingo@1105:
ingo@1105: if (river == null || river.length() == 0) {
ingo@1105: return null;
ingo@1105: }
ingo@1105:
ingo@1805: return getRiverSrid(river);
ingo@1805: }
ingo@1805:
ingo@1805:
ingo@1805: public static String getRiverSrid(String rivername) {
ingo@1105: Map variables = new HashMap(1);
ingo@1805: variables.put("name", rivername);
ingo@1105:
ingo@1105: Document cfg = Config.getConfig();
ingo@1105:
ingo@1105: return (String) XMLUtils.xpath(
ingo@1105: cfg,
ingo@1105: XPATH_RIVER_PROJECTION,
ingo@1105: XPathConstants.STRING,
ingo@1105: null,
ingo@1105: variables);
ingo@1105: }
ingo@1740:
ingo@1740:
ingo@2045: public static Gauge getGauge(FLYSArtifact flys) {
ingo@2045: River river = getRiver(flys);
ingo@2045:
ingo@2045: if (river == null) {
ingo@2045: logger.debug("no river found");
ingo@2045: return null;
ingo@2045: }
ingo@2045:
ingo@2045: double[] dist = getKmRange(flys);
ingo@2045:
ingo@2045: if (dist == null) {
ingo@2045: logger.debug("no range found");
ingo@2045: return null;
ingo@2045: }
ingo@2045:
ingo@2045: if (logger.isDebugEnabled()) {
ingo@2045: logger.debug("Determine gauge for:");
ingo@2045: logger.debug("... river: " + river.getName());
ingo@2045: logger.debug("... distance: " + dist[0] + " - " + dist[1]);
ingo@2045: }
ingo@2045:
ingo@2045: Gauge gauge = river.determineGauge(dist[0], dist[1]);
ingo@2045:
ingo@2045: String name = gauge != null ? gauge.getName() : "'n/a";
ingo@2045: logger.debug("Found gauge: " + name);
ingo@2045:
ingo@2045: return gauge;
ingo@2045: }
ingo@2045:
ingo@2045:
ingo@2045: public static String getGaugename(FLYSArtifact flys) {
ingo@2045: Gauge gauge = getGauge(flys);
ingo@2045:
ingo@2045: return gauge != null ? gauge.getName() : null;
ingo@2045: }
ingo@2045:
ingo@2045:
ingo@2068: public static Double getValueFromWQ(WQ wq) {
ingo@2068: if (wq == null) {
ingo@2068: return null;
ingo@2068: }
ingo@2068:
ingo@2068: Matcher m = NUMBERS_PATTERN.matcher(wq.getName());
ingo@2068:
ingo@2068: if (m.matches()) {
ingo@2068: logger.debug("Found a number.");
ingo@2068:
ingo@2068: String raw = m.group(1);
ingo@2068:
ingo@2068: try {
ingo@2068: return Double.valueOf(raw);
ingo@2068: }
ingo@2068: catch (NumberFormatException nfe) {
ingo@2068: }
ingo@2068: }
ingo@2068:
ingo@2068: return null;
ingo@2068: }
ingo@2068:
ingo@2068:
ingo@1740: public static String createWspWTitle(
ingo@1740: WINFOArtifact winfo,
ingo@1740: CallContext cc,
ingo@1740: String name
ingo@1740: ) {
ingo@1740: String[] parts = name.split("=");
ingo@1740:
ingo@1740: NumberFormat nf = Formatter.getWaterlevelW(cc);
ingo@1740:
ingo@1740: String namedMainValue = null;
ingo@1740:
ingo@1740: boolean isQ = winfo.isQ();
ingo@1740: boolean isFree = winfo.isFreeQ();
ingo@1740:
ingo@1740: double v;
ingo@1740:
ingo@1740: try {
ingo@1740: v = Double.valueOf(parts[1]);
ingo@1740:
ingo@1740: namedMainValue = getNamedMainValue(winfo.getGauge(), v);
ingo@1740: }
ingo@1740: catch (NumberFormatException nfe) {
ingo@1740: logger.warn("Cannot parse Double of: '" + parts[1] + "'");
ingo@1740: return name;
ingo@1740: }
ingo@1740:
ingo@1740: String prefix = null;
ingo@1740:
ingo@1740: if (isQ && !isFree && namedMainValue != null) {
ingo@1740: return "W (" + namedMainValue + ")";
ingo@1740: }
ingo@1740:
ingo@1740: if (isQ) {
ingo@1740: prefix = "Q=";
ingo@1740: }
ingo@1740:
ingo@1740: return prefix == null
ingo@1740: ? "W(" + nf.format(v) + ")"
ingo@1740: : "W(" + prefix + nf.format(v) + ")";
ingo@1740: }
ingo@1740:
ingo@1740:
ingo@1740: public static String createWspQTitle(
ingo@1740: WINFOArtifact winfo,
ingo@1740: CallContext cc,
ingo@1740: String name
ingo@1740: ) {
ingo@1740: String[] parts = name.split("=");
ingo@1740:
ingo@1740: NumberFormat nf = Formatter.getWaterlevelQ(cc);
ingo@1740:
ingo@1740: String namedMainValue = null;
ingo@1740:
ingo@1740: boolean isQ = winfo.isQ();
ingo@1740: boolean isFree = winfo.isFreeQ();
ingo@1740:
ingo@1740: double v;
ingo@1740:
ingo@1740: try {
ingo@1740: v = Double.valueOf(parts[1]);
ingo@1740:
ingo@1740: namedMainValue = getNamedMainValue(winfo.getGauge(), v);
ingo@1740: }
ingo@1740: catch (NumberFormatException nfe) {
ingo@1740: logger.warn("Cannot parse Double of: '" + parts[1] + "'");
ingo@1740: return name;
ingo@1740: }
ingo@1740:
ingo@1740: String prefix = null;
ingo@1740:
ingo@1740: if (isQ && !isFree && namedMainValue != null) {
ingo@1740: return namedMainValue;
ingo@1740: }
ingo@1740:
ingo@1740: if (!isQ) {
ingo@1740: prefix = "W=";
ingo@1740: }
ingo@1740:
ingo@1740: return prefix == null
ingo@1740: ? "Q(" + nf.format(v) + ")"
ingo@1740: : "Q(" + prefix + nf.format(v) + ")";
ingo@1740: }
ingo@1740:
ingo@1740:
ingo@2038: /**
ingo@2038: * Returns the named main value if a Q was selected and if this Q fits to a
ingo@2038: * named main value. Otherwise, this function returns null.
ingo@2038: *
ingo@2038: * @param winfo The WINFO Artifact.
ingo@2038: * @param value The Q (or W) value.
ingo@2038: *
ingo@2038: * @return a named main value or null.
ingo@2038: */
ingo@2038: public static String getNamedMainValue(WINFOArtifact winfo, double value) {
ingo@2038: WQ_MODE wqmode = getWQMode(winfo);
ingo@2038:
ingo@2038: if (wqmode != WQ_MODE.QGAUGE) {
ingo@2038: return null;
ingo@2038: }
ingo@2038: else {
ingo@2038: return getNamedMainValue(winfo.getGauge(), value);
ingo@2038: }
ingo@2038: }
ingo@2038:
ingo@2038:
ingo@1740: public static String getNamedMainValue(Gauge gauge, double value) {
ingo@1740: List mainValues = gauge.getMainValues();
ingo@1740: logger.debug("Search named main value for: " + value);
ingo@1740:
ingo@1740: for (MainValue mv: mainValues) {
ingo@1740: if (mv.getValue().doubleValue() == value) {
ingo@1740: logger.debug("Found named main value: " + mv.getMainValue().getName());
ingo@1740: return mv.getMainValue().getName();
ingo@1740: }
ingo@1740: }
ingo@1740:
ingo@1740: logger.debug("Did not find a named main value for: " + value);
ingo@1740: return null;
ingo@1740: }
ingo@1792:
ingo@1792:
ingo@1792: /**
ingo@2035: *
ingo@2035: * @param nmv A string that represents a named main value.
ingo@2035: *
ingo@2035: * @throws NullPointerException if nmv is null.
ingo@2035: */
ingo@2035: public static String stripNamedMainValue(String nmv) {
ingo@2035: int startIndex = nmv.indexOf("(");
ingo@2035: int endIndex = nmv.indexOf(")");
ingo@2035:
ingo@2035: if (startIndex > 0 && endIndex > 0 && startIndex < endIndex) {
ingo@2035: return nmv.substring(0, startIndex);
ingo@2035: }
ingo@2035:
ingo@2035: return nmv;
ingo@2035: }
ingo@2035:
ingo@2035:
ingo@2035: /**
ingo@1792: * Returns the URL of user mapfile for the owner of Artifact
ingo@1792: * artifactId.
ingo@1792: *
ingo@1792: * @param artifactId The UUID of an artifact.
ingo@1792: *
ingo@1792: * @return the URL of the user wms.
ingo@1792: */
ingo@1792: public static String getUserWMSUrl(String artifactId) {
ingo@1792: String url = getXPathString(XPATH_MAPSERVER_URL);
ingo@1792: url = url + "user-wms";
ingo@1792:
ingo@1792: return url;
ingo@1792: }
ingo@2063:
ingo@2063:
ingo@2063: /**
ingo@2063: * This method returns the description for a given km for a specific
ingo@2063: * river. The river is provided by the FLYSArtifact flys.
ingo@2063: *
ingo@2063: * @param flys The FLYSArtifact that provides a river.
ingo@2063: * @param km The kilometer.
ingo@2063: *
ingo@2063: * @return the description for km or an empty string if no
ingo@2063: * description was found.
ingo@2063: */
ingo@2063: public static String getLocationDescription(FLYSArtifact flys, double km) {
ingo@2063: String river = getRivername(flys);
ingo@2063:
ingo@2063: if (river == null) {
ingo@2063: return "";
ingo@2063: }
ingo@2063:
ingo@2063: return LocationProvider.getLocation(river, km);
ingo@2063: }
ingo@1095: }
ingo@1105: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :