teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5863: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5863: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.artifacts.services; sascha@1749: teichmann@5831: import org.dive4elements.artifacts.CallMeta; teichmann@5831: import org.dive4elements.artifacts.GlobalContext; sascha@1749: teichmann@5831: import org.dive4elements.artifacts.common.utils.XMLUtils; sascha@1749: teichmann@5831: import org.dive4elements.river.artifacts.cache.CacheFactory; teichmann@5831: teichmann@5831: import org.dive4elements.river.backend.SessionHolder; teichmann@5831: teichmann@5831: import org.dive4elements.river.model.CrossSection; teichmann@5831: import org.dive4elements.river.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 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> 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 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 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 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)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> nearestNeighbors( sascha@1749: NavigableMap map, sascha@1749: double km, sascha@1749: int N sascha@1749: ) { sascha@1749: Deque> result = sascha@1749: new ArrayDeque>(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(km, v)); felix@1961: } felix@1961: sascha@1749: int i = 0; sascha@1749: for (Map.Entry 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 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 getUncached( sascha@1749: Integer crossSectionId sascha@1749: ) { sascha@1749: NavigableMap result = sascha@1749: new ConcurrentSkipListMap(); 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 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 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 :