sascha@1749: package de.intevation.flys.artifacts.services;
sascha@1749: 
sascha@1749: import de.intevation.artifacts.CallMeta;
sascha@1749: import de.intevation.artifacts.GlobalContext;
sascha@1749: 
sascha@1749: import de.intevation.artifacts.common.utils.XMLUtils;
sascha@1749: 
sascha@1749: import de.intevation.flys.artifacts.cache.CacheFactory;
sascha@1749: 
sascha@1749: import de.intevation.flys.backend.SessionHolder;
sascha@1749: 
sascha@1749: import de.intevation.flys.model.CrossSection;
sascha@1749: import de.intevation.flys.model.CrossSectionLine;
sascha@1749: 
felix@1961: import java.util.AbstractMap;
sascha@1749: import java.util.ArrayDeque;
sascha@1749: import java.util.Deque;
sascha@1749: import java.util.List;
sascha@1749: import java.util.Map;
sascha@1749: import java.util.NavigableMap;
sascha@1749: 
sascha@1749: import java.util.concurrent.ConcurrentSkipListMap;
sascha@1749: 
sascha@1749: import net.sf.ehcache.Cache;
sascha@1749: 
sascha@1749: import org.apache.log4j.Logger;
sascha@1749: 
sascha@1749: import org.hibernate.Query;
sascha@1749: import org.hibernate.Session;
sascha@1749: 
sascha@1749: import org.w3c.dom.Document;
sascha@1749: import org.w3c.dom.Element;
sascha@1749: import org.w3c.dom.NodeList;
sascha@1749: 
felix@1961: 
felix@1961: /**
felix@1961:  * Service to find the next/previous km (measurement) of cross sections.
felix@1961:  * Looking at the query for a single cross-section id at a single km, the
felix@1961:  * service does the following:
felix@1961:  *
felix@1961:  * It returns the km itself if a measurement at that km was found and
felix@1961:  * the N nearest other measurement points in both directions.
felix@1961:  *
felix@1961:  * That means, you can pass N=0 to find out whether a measurement at given km
felix@1961:  * exists.
felix@1961:  *
felix@1961:  * If less than N neighbours exist in one direction, less are delivered
felix@1961:  * (e.g. given measurements at [0,2,3,4,5,7,8,9] a query for km=8, N=3 will
felix@1961:  * result in [4,5,7,8,9]).
felix@1961:  */
sascha@1749: public class CrossSectionKMService
sascha@1749: extends      FLYSService
sascha@1749: {
sascha@1749:     private static Logger logger =
sascha@1749:         Logger.getLogger(CrossSectionKMService.class);
sascha@1749: 
sascha@1749:     public static final String CACHE_NAME = "cross-section-kms";
sascha@1749: 
felix@1961: 
felix@1961:     /** Trivial constructor. */
sascha@1749:     public CrossSectionKMService() {
sascha@1749:     }
sascha@1749: 
felix@1961: 
felix@1961:     /**
felix@1961:      * @param data
felix@1961:      */
sascha@1749:     @Override
sascha@1749:     public Document doProcess(
sascha@1749:         Document      data,
sascha@1749:         GlobalContext globalContext,
sascha@1749:         CallMeta      callMeta
sascha@1749:     ) {
sascha@1749:         logger.debug("CrossSectionKMService.doProcess");
sascha@1749: 
sascha@1749:         NodeList crossSectionNodes =
felix@1961:             data.getElementsByTagName("art:cross-section");
sascha@1749: 
sascha@1749:         Document document = XMLUtils.newDocument();
sascha@1749: 
sascha@1749:         Element all = document.createElement("cross-sections");
sascha@1749: 
sascha@1749:         for (int i = 0, CS = crossSectionNodes.getLength(); i < CS; ++i) {
sascha@1749:             Element crossSectionElement = (Element)crossSectionNodes.item(i);
sascha@1749: 
sascha@1749:             String idString = crossSectionElement.getAttribute("id");
sascha@1749:             String kmString = crossSectionElement.getAttribute("km");
sascha@1749:             String neighborsString = crossSectionElement.getAttribute("n");
sascha@1749: 
sascha@1749:             if (idString.length() == 0 || kmString.length() == 0) {
sascha@1749:                 logger.debug("missing attributes in cross-section element");
sascha@1749:                 continue;
sascha@1749:             }
sascha@1749: 
sascha@1749:             double  km;
sascha@1749:             Integer crossSectionId;
sascha@1749:             int     N = 2;
sascha@1749: 
sascha@1749:             try {
sascha@1749:                 km             = Double.parseDouble(kmString);
sascha@1749:                 crossSectionId = Integer.valueOf(idString);
sascha@1749: 
sascha@1749:                 if (neighborsString.length() > 0) {
sascha@1749:                     N = Integer.parseInt(neighborsString);
sascha@1749:                 }
sascha@1749:             }
sascha@1749:             catch (NumberFormatException nfe) {
sascha@1749:                 logger.debug("converting number failed", nfe);
sascha@1749:                 continue;
sascha@1749:             }
sascha@1749: 
sascha@2120:             NavigableMap<Double, Integer> map = getKms(crossSectionId);
sascha@1749: 
sascha@1749:             if (map == null) {
sascha@1749:                 logger.debug("cannot find cross section " + crossSectionId);
sascha@1749:                 continue;
sascha@1749:             }
sascha@1749: 
sascha@1749:             Deque<Map.Entry<Double, Integer>> result =
sascha@1749:                 nearestNeighbors(map, km, N);
sascha@1749: 
sascha@1749:             if (!result.isEmpty()) {
sascha@1749:                 Element csE = document.createElement("cross-section");
sascha@1749:                 csE.setAttribute("id", idString);
sascha@1749:                 for (Map.Entry<Double, Integer> entry: result) {
sascha@1749:                     Element lineE = document.createElement("line");
sascha@1749:                     lineE.setAttribute(
sascha@1749:                         "line-id", String.valueOf(entry.getValue()));
sascha@1749:                     lineE.setAttribute(
sascha@1749:                         "km", String.valueOf(entry.getKey()));
sascha@1749:                     csE.appendChild(lineE);
sascha@1749:                 }
sascha@1749:                 all.appendChild(csE);
sascha@1749:             }
sascha@1749:         }
sascha@1749: 
sascha@1749:         document.appendChild(all);
sascha@1749: 
sascha@1749:         return document;
sascha@1749:     }
sascha@1749: 
sascha@2120:     public static NavigableMap<Double, Integer> getKms(int crossSectionId) {
sascha@2120: 
sascha@2120:         Cache cache = CacheFactory.getCache(CACHE_NAME);
sascha@2120: 
sascha@2120:         if (cache == null) {
sascha@2120:             return getUncached(crossSectionId);
sascha@2120:         }
sascha@2120: 
sascha@2120:         NavigableMap<Double, Integer> map;
sascha@2120: 
sascha@2120:         net.sf.ehcache.Element element = cache.get(crossSectionId);
sascha@2120:         if (element == null) {
sascha@2120:             map = getUncached(crossSectionId);
sascha@2120:             if (map != null) {
sascha@2120:                 element = new net.sf.ehcache.Element(
sascha@2120:                     crossSectionId, map);
sascha@2120:                 cache.put(element);
sascha@2120:             }
sascha@2120:         }
sascha@2120:         else {
sascha@2120:             map = (NavigableMap<Double, Integer>)element.getValue();
sascha@2120:         }
sascha@2120: 
sascha@2120:         return map;
sascha@2120:     }
sascha@2120: 
felix@1961: 
felix@1961:     /**
felix@1961:      * @param km  the kilometer from which to start searching for other
felix@1961:      *            measurements
felix@1961:      * @param N   number of neighboring measurements to find.
felix@1961:      */
sascha@1749:     public static Deque<Map.Entry<Double, Integer>> nearestNeighbors(
sascha@1749:         NavigableMap<Double, Integer> map,
sascha@1749:         double                        km,
sascha@1749:         int                           N
sascha@1749:     ) {
sascha@1749:         Deque<Map.Entry<Double, Integer>> result =
sascha@1749:             new ArrayDeque<Map.Entry<Double, Integer>>(2*N);
sascha@1749: 
sascha@2120:         Integer v = map.get(km);
felix@1961: 
sascha@2120:         if (v != null) {
sascha@2120:             result.add(new AbstractMap.SimpleEntry<Double, Integer>(km, v));
felix@1961:         }
felix@1961: 
sascha@1749:         int i = 0;
sascha@1749:         for (Map.Entry<Double, Integer> entry:
sascha@1749:              map.headMap(km, false).descendingMap().entrySet()) {
sascha@1749:             if (i++ >= N) {
sascha@1749:                 break;
sascha@1749:             }
sascha@1749:             result.addFirst(entry);
sascha@1749:         }
sascha@1749: 
sascha@1749:         i = 0;
sascha@1749:         for (Map.Entry<Double, Integer> entry:
sascha@1749:              map.tailMap(km, false).entrySet()) {
sascha@1749:             if (i++ >= N) {
sascha@1749:                 break;
sascha@1749:             }
sascha@1749:             result.addLast(entry);
sascha@1749:         }
sascha@1749: 
sascha@1749:         return result;
sascha@1749:     }
sascha@1749: 
felix@1961: 
felix@1961:     /**
felix@1961:      * @param crossSectionId id of queried cross-section (in db).
felix@1961:      * @return Mapping from kilometer to db-id.
felix@1961:      */
sascha@1749:     public static NavigableMap<Double, Integer> getUncached(
sascha@1749:         Integer crossSectionId
sascha@1749:     ) {
sascha@1749:         NavigableMap<Double, Integer> result =
sascha@1749:             new ConcurrentSkipListMap<Double, Integer>();
sascha@1749: 
sascha@1749:         Session session = SessionHolder.HOLDER.get();
sascha@1749:         Query query = session.createQuery(
sascha@1749:             "from CrossSection where id=:id");
sascha@1749:         query.setParameter("id", crossSectionId);
sascha@1749: 
sascha@1749:         List<CrossSection> crossSections = query.list();
sascha@1749:         if (crossSections.isEmpty()) {
sascha@1749:             return null;
sascha@1749:         }
sascha@1749: 
sascha@1749:         CrossSection crossSection = crossSections.get(0);
sascha@1749:         List<CrossSectionLine> lines = crossSection.getLines();
sascha@1749: 
sascha@1749:         for (CrossSectionLine line: lines) {
sascha@1749:             Double  km = line.getKm().doubleValue();
sascha@1749:             Integer id = line.getId();
sascha@1749:             result.put(km, id);
sascha@1749:         }
sascha@1749: 
sascha@1749:         return result;
sascha@1749:     }
sascha@1749: }
sascha@1749: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :