teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.artifacts; felix@1964: felix@1964: import java.util.ArrayList; felix@1964: import java.util.List; sascha@2120: import java.util.NavigableMap; felix@1964: felix@1964: import org.apache.log4j.Logger; felix@1964: felix@1964: import org.w3c.dom.Document; felix@1964: teichmann@5831: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.ArtifactFactory; teichmann@5831: import org.dive4elements.artifacts.CallMeta; sascha@2126: teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess; teichmann@5831: import org.dive4elements.river.artifacts.model.CrossSectionFacet; teichmann@5831: import org.dive4elements.river.artifacts.model.FastCrossSectionLineFactory; felix@1964: teichmann@5831: import org.dive4elements.river.model.FastCrossSectionLine; felix@1964: teichmann@5831: import org.dive4elements.river.model.CrossSection; teichmann@5831: import org.dive4elements.river.model.CrossSectionLine; teichmann@5831: import org.dive4elements.river.artifacts.model.CrossSectionFactory; felix@1964: teichmann@5831: import org.dive4elements.river.artifacts.states.StaticState; teichmann@5831: teichmann@5831: import org.dive4elements.artifactdatabase.state.Facet; teichmann@5831: import org.dive4elements.artifactdatabase.state.FacetActivity; teichmann@5831: import org.dive4elements.artifactdatabase.state.State; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.services.CrossSectionKMService; sascha@2120: felix@1964: felix@1985: /** felix@1985: * Artifact describing a cross-section. felix@1985: */ teichmann@5867: public class CrossSectionArtifact extends StaticD4EArtifact { felix@1964: felix@1964: /** Name of Artifact. */ felix@1964: public static final String CS_ARTIFACT_NAME = "cross_section"; felix@1964: felix@1964: /** Name of state. */ felix@1964: public static final String STATIC_STATE_NAME = "state.cross_section"; felix@1964: felix@1967: /** Name of data item keeping the position. */ felix@1967: public static final String DATA_KM = "cross_section.km"; felix@1967: felix@6537: /** Name of data item keeping the 'parents' km. */ felix@6537: public static final String PARENT_KM = "cross_section.parent.km"; felix@6537: felix@1967: /** Name of data item keeping the database id of this c.s.. */ felix@1967: public static final String DATA_DBID = "cross_section.dbid"; felix@1967: felix@2040: /** Name of data item flagging whether we think that we are master. */ felix@1967: public static final String DATA_IS_MASTER = "cross_section.master?"; felix@1967: felix@2040: /** Name of data item flagging whether we are the newest. */ felix@2040: public static final String DATA_IS_NEWEST = "cross_section.newest?"; felix@2040: felix@3272: /** Name of data item storing the previous possible km. */ felix@3272: public static final String DATA_PREV_KM = "cross_section.km.previous"; felix@3272: felix@3272: /** Name of data item storing the next possible km. */ felix@3272: public static final String DATA_NEXT_KM = "cross_section.km.next"; felix@3272: teichmann@8202: /** Own log. */ teichmann@8202: private static final Logger log = felix@1964: Logger.getLogger(CrossSectionArtifact.class); felix@1964: sascha@3556: static { sascha@3556: // TODO: Move to configuration. sascha@3556: FacetActivity.Registry.getInstance().register( sascha@3556: CS_ARTIFACT_NAME, sascha@3556: new FacetActivity() { sascha@3556: @Override sascha@3558: public Boolean isInitialActive( sascha@3556: Artifact artifact, sascha@3556: Facet facet, sascha@3556: String outputName sascha@3556: ) { teichmann@5867: if (artifact instanceof D4EArtifact) { teichmann@5867: D4EArtifact flys = (D4EArtifact)artifact; sascha@3556: String data = flys.getDataAsString(DATA_IS_NEWEST); sascha@3556: return data != null && data.equals("1"); sascha@3556: } sascha@3558: return null; sascha@3556: } sascha@3556: }); sascha@3556: } felix@1964: felix@1964: /** Return given name. */ felix@1964: @Override felix@1964: public String getName() { felix@1964: return CS_ARTIFACT_NAME; felix@1964: } felix@1964: felix@1964: felix@1985: /** Store ids, create a CrossSectionFacet. */ felix@1964: @Override felix@1964: public void setup( felix@1964: String identifier, felix@1964: ArtifactFactory factory, felix@1964: Object context, felix@1964: CallMeta callMeta, rrenkert@7842: Document data, rrenkert@7842: List loadFacets) felix@1964: { teichmann@8202: log.info("CrossSectionArtifact.setup"); felix@1964: rrenkert@7842: super.setup(identifier, factory, context, callMeta, data, loadFacets); felix@1964: felix@2741: String ids = getDatacageIDValue(data); felix@1964: felix@1964: if (ids != null && ids.length() > 0) { felix@1967: addStringData(DATA_DBID, ids); teichmann@8202: log.debug("CrossSectionArtifacts db-id: " + ids); felix@1964: } felix@1964: else { felix@1964: throw new IllegalArgumentException("No attribute 'ids' found!"); felix@1964: } felix@1964: felix@1964: List fs = new ArrayList(); sascha@2120: CrossSection cs = CrossSectionFactory.getCrossSection( sascha@3405: Integer.parseInt(ids)); sascha@2120: sascha@2120: List csls = cs.getLines(); sascha@2120: if (!csls.isEmpty()) { sascha@2120: CrossSectionLine csl = csls.get(0); sascha@2120: // Find min-km of cross sections, sascha@2120: // then set DATA_KM to min(DATA_KM, minCross). felix@6936: String dataKmValue = getDataAsString(DATA_KM); felix@6936: double dataKm = (dataKmValue != null) ? Double.valueOf(dataKmValue) : Double.MIN_VALUE; felix@3272: if (dataKm < csl.getKm().doubleValue()) { felix@2036: addStringData(DATA_KM, csl.getKm().toString()); felix@2036: } felix@2036: } felix@1971: fs.add(new CrossSectionFacet(0, cs.getDescription())); felix@1964: felix@3272: // Find out if we are newest and become master if so. felix@6539: boolean isNewest = cs.shouldBeMaster(getParentKm()); felix@2040: String newString = (isNewest) ? "1" : "0"; felix@2040: addStringData(DATA_IS_NEWEST, newString); felix@2040: addStringData(DATA_IS_MASTER, newString); felix@2040: felix@1964: if (!fs.isEmpty()) { bjoern@4497: addFacets(getCurrentStateId(), fs); felix@1964: } felix@1964: } felix@1964: felix@1964: felix@2036: /** Copy km where master-artifact "starts". */ felix@1964: @Override felix@1964: protected void initialize( felix@6574: Artifact master, felix@1964: Object context, felix@1964: CallMeta callMeta) felix@1964: { felix@6574: D4EArtifact masterArtifact = (D4EArtifact) master; felix@4857: felix@6574: RangeAccess rangeAccess = new RangeAccess(masterArtifact); felix@4857: double[] range = rangeAccess.getKmRange(); felix@2037: if (range != null && range.length > 0) { felix@6539: this.addStringData(DATA_KM, Double.toString(range[0])); felix@6539: this.addStringData(PARENT_KM, Double.toString(range[0])); felix@2037: } felix@1964: } felix@1964: felix@1964: felix@3284: /** Returns next possible km for a cross-section. */ felix@3272: public Double getNextKm() { felix@3272: return getDataAsDouble(DATA_NEXT_KM); felix@3272: } felix@3272: felix@3284: felix@3284: /** Returns previous possible km for a cross-section. */ felix@3272: public Double getPrevKm() { felix@3272: return getDataAsDouble(DATA_PREV_KM); felix@3272: } felix@3272: felix@3272: felix@1964: /** felix@1964: * Create and return a new StaticState with charting output. felix@1964: */ felix@1964: @Override felix@1964: public State getCurrentState(Object cc) { bjoern@4497: final List fs = getFacets(getCurrentStateId()); felix@1964: felix@1971: StaticState state = new StaticState(STATIC_STATE_NAME) { felix@1982: @Override felix@1971: public Object staticCompute(List facets) { felix@1971: if (facets != null) { felix@1971: facets.addAll(fs); felix@1971: } felix@1971: return null; felix@1971: } felix@1971: }; felix@1964: felix@1967: state.addDefaultChartOutput("cross_section", fs); felix@1964: felix@1964: return state; felix@1964: } felix@1964: felix@1964: felix@1964: /** felix@1964: * Get a list containing the one and only State. felix@1964: * @param context ignored. felix@1964: * @return list with one and only state. felix@1964: */ felix@1964: @Override felix@1964: protected List getStates(Object context) { felix@1964: ArrayList states = new ArrayList(); felix@1964: states.add(getCurrentState(context)); felix@1964: felix@1964: return states; felix@1964: } felix@1967: felix@1967: // TODO all data access needs proper caching. felix@1967: felix@1967: /** felix@1967: * Get a DataItem casted to int (0 if fails). felix@1967: */ felix@1967: public int getDataAsIntNull(String dataName) { felix@1967: String val = getDataAsString(dataName); felix@1967: try { sascha@3405: return Integer.parseInt(val); felix@1967: } felix@1967: catch (NumberFormatException e) { teichmann@8202: log.warn("Could not get data " + dataName + " as int", e); felix@1967: return 0; felix@1967: } felix@1967: } felix@1967: felix@1967: felix@1967: /** Returns database-id of cross-section (from data). */ felix@1967: protected int getDBID() { felix@1967: return getDataAsIntNull(DATA_DBID); felix@1967: } felix@1967: felix@1967: felix@1967: /** felix@6537: * Return position (km) from parent (initial km), 0 if not found. felix@6537: */ felix@6537: private double getParentKm() { felix@6537: String val = getDataAsString(PARENT_KM); felix@6936: if (val == null) { teichmann@8202: log.warn("Empty data: " + PARENT_KM); felix@6936: return 0; felix@6936: } felix@6537: try { felix@6537: return Double.valueOf(val); felix@6537: } felix@6537: catch (NumberFormatException e) { teichmann@8202: log.warn("Could not get data " + PARENT_KM + " as double", e); felix@6537: return 0; felix@6537: } felix@6537: } felix@6537: felix@6537: /** felix@1967: * Return position (km) from data, 0 if not found. felix@1967: */ felix@1967: protected double getKm() { felix@1967: String val = getDataAsString(DATA_KM); felix@1967: try { felix@1967: return Double.valueOf(val); felix@1967: } felix@1967: catch (NumberFormatException e) { teichmann@8202: log.warn("Could not get data " + DATA_KM + " as double", e); felix@1967: return 0; felix@1967: } felix@1967: } felix@1967: felix@1967: felix@1967: /** Returns true if artifact is set to be a "master" (other facets will felix@1967: * refer to this). */ felix@1967: public boolean isMaster() { felix@1967: return !getDataAsString(DATA_IS_MASTER).equals("0"); felix@1967: } felix@1967: felix@1967: felix@1967: /** felix@1967: * Get points of Profile of cross section at given kilometer. felix@1967: * felix@1967: * @return an array holding coordinates of points of profile ( felix@1967: * in the form {{x1, x2} {y1, y2}} ). felix@1967: */ felix@1967: public double [][] getCrossSectionData() { teichmann@8202: log.info("getCrossSectionData() for cross_section.km " felix@1967: + getDataAsString(DATA_KM)); sascha@2120: FastCrossSectionLine line = searchCrossSectionLine(); felix@1967: felix@1967: return line != null felix@1967: ? line.fetchCrossSectionProfile() felix@1967: : null; felix@1967: } felix@1967: felix@1967: felix@1967: /** felix@1967: * Get CrossSectionLine spatially closest to what is specified in the data felix@2678: * "cross_section.km", null if considered too far. felix@1967: * felix@3272: * It also adds DataItems to store the next and previous (numerically) felix@3272: * values at which cross-section data was recorded. felix@3272: * felix@2678: * @return CrossSectionLine closest to "cross_section.km", might be null felix@2678: * if considered too far. felix@1967: */ sascha@2120: public FastCrossSectionLine searchCrossSectionLine() { felix@2678: double TOO_FAR = 1d; sascha@2120: CrossSection crossSection = CrossSectionFactory sascha@2120: .getCrossSection(getDBID()); sascha@2120: teichmann@8202: if (log.isDebugEnabled()) { teichmann@8202: log.debug("dbid " + getDBID() + " : " + crossSection); felix@1967: } sascha@2120: sascha@2120: NavigableMap kms = CrossSectionKMService sascha@2120: .getKms(crossSection.getId()); sascha@2120: sascha@2120: Double wishKM = getKm(); sascha@2120: sascha@2120: Double floor = kms.floorKey(wishKM); sascha@2120: Double ceil = kms.ceilingKey(wishKM); sascha@2120: felix@3272: Double nextKm; felix@3272: Double prevKm; felix@3272: sascha@2120: double floorD = floor != null sascha@2120: ? Math.abs(floor - wishKM) sascha@2120: : Double.MAX_VALUE; sascha@2120: sascha@2120: double ceilD = ceil != null sascha@2120: ? Math.abs(ceil - wishKM) sascha@2120: : Double.MAX_VALUE; sascha@2120: felix@3272: double km; felix@3272: if (floorD < ceilD) { felix@3272: km = floor; felix@3272: } felix@3272: else { felix@3272: km = ceil; felix@3272: } sascha@2120: felix@2678: // If we are too far from the wished km, return null. felix@2678: if (Math.abs(km - wishKM) > TOO_FAR) { felix@2678: return null; felix@2678: } felix@2678: felix@3272: // Store next and previous km. felix@3272: nextKm = kms.higherKey(km); felix@3272: prevKm = kms.lowerKey(km); felix@3272: felix@3272: if (prevKm == null) { felix@3272: prevKm = -1d; felix@3272: } felix@3272: if (nextKm == null) { felix@3272: nextKm = -1d; felix@3272: } felix@3272: felix@3272: addStringData(DATA_PREV_KM, prevKm.toString()); felix@3272: addStringData(DATA_NEXT_KM, nextKm.toString()); felix@3272: sascha@2120: return FastCrossSectionLineFactory sascha@2120: .getCrossSectionLine(crossSection, km); felix@1967: } felix@1964: } felix@1964: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :