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; gernotbelger@9460: import org.dive4elements.artifactdatabase.state.Facet; gernotbelger@9460: import org.dive4elements.artifactdatabase.state.FacetActivity; gernotbelger@9460: import org.dive4elements.artifactdatabase.state.State; teichmann@5831: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.ArtifactFactory; teichmann@5831: import org.dive4elements.artifacts.CallMeta; teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess; teichmann@5831: import org.dive4elements.river.artifacts.model.CrossSectionFacet; gernotbelger@9460: import org.dive4elements.river.artifacts.model.CrossSectionFactory; teichmann@5831: import org.dive4elements.river.artifacts.model.FastCrossSectionLineFactory; gernotbelger@9460: import org.dive4elements.river.artifacts.services.CrossSectionKMService; gernotbelger@9460: import org.dive4elements.river.artifacts.states.StaticState; teichmann@5831: import org.dive4elements.river.model.CrossSection; teichmann@5831: import org.dive4elements.river.model.CrossSectionLine; gernotbelger@9460: import org.dive4elements.river.model.FastCrossSectionLine; gernotbelger@9460: import org.w3c.dom.Document; 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. */ gernotbelger@9460: private static final String DATA_IS_MASTER = "cross_section.master?"; felix@1967: felix@2040: /** Name of data item flagging whether we are the newest. */ gernotbelger@9460: private 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. */ gernotbelger@9460: private static final Logger log = Logger.getLogger(CrossSectionArtifact.class); felix@1964: sascha@3556: static { sascha@3556: // TODO: Move to configuration. gernotbelger@9460: FacetActivity.Registry.getInstance().register(CS_ARTIFACT_NAME, new FacetActivity() { gernotbelger@9460: @Override gernotbelger@9460: public Boolean isInitialActive(final Artifact artifact, final Facet facet, final String outputName) { gernotbelger@9460: if (artifact instanceof D4EArtifact) { gernotbelger@9460: final D4EArtifact flys = (D4EArtifact) artifact; gernotbelger@9460: final String data = flys.getDataAsString(DATA_IS_NEWEST); gernotbelger@9460: return data != null && data.equals("1"); sascha@3556: } gernotbelger@9460: return null; gernotbelger@9460: } gernotbelger@9460: }); 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@1985: /** Store ids, create a CrossSectionFacet. */ felix@1964: @Override gernotbelger@9460: public void setup(final String identifier, final ArtifactFactory factory, final Object context, final CallMeta callMeta, final Document data, gernotbelger@9460: final List loadFacets) { teichmann@8202: log.info("CrossSectionArtifact.setup"); felix@1964: rrenkert@7842: super.setup(identifier, factory, context, callMeta, data, loadFacets); felix@1964: gernotbelger@9460: final 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); gernotbelger@9460: } else { felix@1964: throw new IllegalArgumentException("No attribute 'ids' found!"); felix@1964: } felix@1964: gernotbelger@9460: final List fs = new ArrayList<>(); gernotbelger@9460: final CrossSection cs = CrossSectionFactory.getCrossSection(Integer.parseInt(ids)); sascha@2120: gernotbelger@9460: final List csls = cs.getLines(); sascha@2120: if (!csls.isEmpty()) { gernotbelger@9460: final CrossSectionLine csl = csls.get(0); sascha@2120: // Find min-km of cross sections, sascha@2120: // then set DATA_KM to min(DATA_KM, minCross). gernotbelger@9460: final String dataKmValue = getDataAsString(DATA_KM); gernotbelger@9460: final 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. gernotbelger@9460: final boolean isNewest = cs.shouldBeMaster(getParentKm()); gernotbelger@9460: final String newString = (isNewest) ? "1" : "0"; felix@2040: addStringData(DATA_IS_NEWEST, newString); felix@2040: addStringData(DATA_IS_MASTER, newString); gernotbelger@9460: final StringBuilder builder = new StringBuilder(); gernotbelger@9460: builder.append("CrossSectionArtifact line 128 cross_section_id = ").append(cs.getId()).append(" IS_START? : ").append(String.valueOf(isNewest)); gernotbelger@9460: log.info(builder.toString()); // TODO: remove ALL gernotbelger@9460: log.debug(builder.toString()); gernotbelger@9460: log.warn(builder.toString()); felix@2040: felix@1964: if (!fs.isEmpty()) { bjoern@4497: addFacets(getCurrentStateId(), fs); felix@1964: } felix@1964: } felix@1964: felix@2036: /** Copy km where master-artifact "starts". */ felix@1964: @Override gernotbelger@9460: protected void initialize(final Artifact master, final Object context, final CallMeta callMeta) { gernotbelger@9460: final D4EArtifact masterArtifact = (D4EArtifact) master; felix@4857: gernotbelger@9460: final RangeAccess rangeAccess = new RangeAccess(masterArtifact); gernotbelger@9460: final 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@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: /** 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@1964: /** felix@1964: * Create and return a new StaticState with charting output. felix@1964: */ felix@1964: @Override gernotbelger@9460: public State getCurrentState(final Object cc) { bjoern@4497: final List fs = getFacets(getCurrentStateId()); felix@1964: gernotbelger@9460: final StaticState state = new StaticState(STATIC_STATE_NAME) { felix@1982: @Override gernotbelger@9460: public Object staticCompute(final 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: * Get a list containing the one and only State. gernotbelger@9460: * gernotbelger@9460: * @param context gernotbelger@9460: * ignored. felix@1964: * @return list with one and only state. felix@1964: */ felix@1964: @Override gernotbelger@9460: protected List getStates(final Object context) { gernotbelger@9460: final 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: */ gernotbelger@9460: public int getDataAsIntNull(final String dataName) { gernotbelger@9460: final String val = getDataAsString(dataName); felix@1967: try { sascha@3405: return Integer.parseInt(val); felix@1967: } gernotbelger@9460: catch (final 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: /** 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@6537: * Return position (km) from parent (initial km), 0 if not found. felix@6537: */ felix@6537: private double getParentKm() { gernotbelger@9460: final 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: } gernotbelger@9460: catch (final 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() { gernotbelger@9460: final String val = getDataAsString(DATA_KM); felix@1967: try { felix@1967: return Double.valueOf(val); felix@1967: } gernotbelger@9460: catch (final 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: gernotbelger@9460: /** gernotbelger@9460: * Returns true if artifact is set to be a "master" (other facets will gernotbelger@9460: * refer to this). gernotbelger@9460: */ felix@1967: public boolean isMaster() { felix@1967: return !getDataAsString(DATA_IS_MASTER).equals("0"); 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: */ gernotbelger@9460: public double[][] getCrossSectionData() { gernotbelger@9460: log.info("getCrossSectionData() for cross_section.km " + getDataAsString(DATA_KM)); gernotbelger@9460: final FastCrossSectionLine line = searchCrossSectionLine(); felix@1967: gernotbelger@9460: return line != null ? line.fetchCrossSectionProfile() : null; 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() { gernotbelger@9460: final double TOO_FAR = 1d; gernotbelger@9460: final CrossSection crossSection = CrossSectionFactory.getCrossSection(getDBID()); sascha@2120: teichmann@8202: if (log.isDebugEnabled()) { teichmann@8202: log.debug("dbid " + getDBID() + " : " + crossSection); felix@1967: } sascha@2120: gernotbelger@9460: final NavigableMap kms = CrossSectionKMService.getKms(crossSection.getId()); sascha@2120: gernotbelger@9460: final Double wishKM = getKm(); sascha@2120: gernotbelger@9460: final Double floor = kms.floorKey(wishKM); gernotbelger@9460: final Double ceil = kms.ceilingKey(wishKM); sascha@2120: felix@3272: Double nextKm; felix@3272: Double prevKm; felix@3272: gernotbelger@9460: final double floorD = floor != null ? Math.abs(floor - wishKM) : Double.MAX_VALUE; sascha@2120: gernotbelger@9460: final double ceilD = ceil != null ? Math.abs(ceil - wishKM) : Double.MAX_VALUE; sascha@2120: felix@3272: double km; felix@3272: if (floorD < ceilD) { felix@3272: km = floor; gernotbelger@9460: } 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: gernotbelger@9460: return FastCrossSectionLineFactory.getCrossSectionLine(crossSection, km); felix@1967: } felix@1964: } felix@1964: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :