/* * This file is part of JGrasstools (http://www.jgrasstools.org) * (C) HydroloGIS - www.hydrologis.com * * JGrasstools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.jgrasstools.hortonmachine.modules.hydrogeomorphology.debrisvandre; import static org.jgrasstools.gears.libs.modules.JGTConstants.isNovalue; import static org.jgrasstools.gears.utils.geometry.GeometryUtilities.distance3d; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_AUTHORCONTACTS; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_AUTHORNAMES; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_KEYWORDS; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_LABEL; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_LICENSE; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_NAME; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_STATUS; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_doWholenet_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inElev_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inFlow_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inNet_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inObstacles_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inSlope_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inSoil_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_inTriggers_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_outIndexedTriggers_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_outPaths_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_outSoil_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_pDistance_DESCRIPTION; import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSDEBRISVANDRE_pMode_DESCRIPTION; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import javax.media.jai.iterator.RandomIter; import javax.media.jai.iterator.RandomIterFactory; import javax.media.jai.iterator.WritableRandomIter; import oms3.annotations.Author; import oms3.annotations.Description; import oms3.annotations.Execute; import oms3.annotations.In; import oms3.annotations.Keywords; import oms3.annotations.Label; import oms3.annotations.License; import oms3.annotations.Name; import oms3.annotations.Out; import oms3.annotations.Status; import oms3.annotations.Unit; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.jgrasstools.gears.libs.exceptions.ModelsIllegalargumentException; import org.jgrasstools.gears.libs.modules.JGTModel; import org.jgrasstools.gears.libs.modules.ModelsEngine; import org.jgrasstools.gears.utils.RegionMap; import org.jgrasstools.gears.utils.StringUtilities; import org.jgrasstools.gears.utils.coverage.CoverageUtilities; import org.jgrasstools.gears.utils.features.FeatureUtilities; import org.jgrasstools.gears.utils.geometry.GeometryUtilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Point; @Description(OMSDEBRISVANDRE_DESCRIPTION) @Author(name = OMSDEBRISVANDRE_AUTHORNAMES, contact = OMSDEBRISVANDRE_AUTHORCONTACTS) @Keywords(OMSDEBRISVANDRE_KEYWORDS) @Label(OMSDEBRISVANDRE_LABEL) @Name(OMSDEBRISVANDRE_NAME) @Status(OMSDEBRISVANDRE_STATUS) @License(OMSDEBRISVANDRE_LICENSE) public class OmsDebrisVandre extends JGTModel { @Description(OMSDEBRISVANDRE_inElev_DESCRIPTION) @In public GridCoverage2D inElev = null; @Description(OMSDEBRISVANDRE_inFlow_DESCRIPTION) @In public GridCoverage2D inFlow = null; @Description(OMSDEBRISVANDRE_inSlope_DESCRIPTION) @Unit("degree") @In public GridCoverage2D inSlope = null; @Description(OMSDEBRISVANDRE_inTriggers_DESCRIPTION) @In public GridCoverage2D inTriggers = null; @Description(OMSDEBRISVANDRE_inSoil_DESCRIPTION) @In public GridCoverage2D inSoil = null; @Description(OMSDEBRISVANDRE_inNet_DESCRIPTION) @In public GridCoverage2D inNet = null; @Description(OMSDEBRISVANDRE_doWholenet_DESCRIPTION) @In public boolean doWholenet = false; @Description(OMSDEBRISVANDRE_pDistance_DESCRIPTION) @In @Unit("m") public double pDistance = 100.0; @Description(OMSDEBRISVANDRE_inObstacles_DESCRIPTION) @In public SimpleFeatureCollection inObstacles = null; @Description(OMSDEBRISVANDRE_pMode_DESCRIPTION) @In public int pMode = 0; @Description(OMSDEBRISVANDRE_outPaths_DESCRIPTION) @Out public SimpleFeatureCollection outPaths = null; @Description(OMSDEBRISVANDRE_outIndexedTriggers_DESCRIPTION) @Out public SimpleFeatureCollection outIndexedTriggers = null; @Description(OMSDEBRISVANDRE_outSoil_DESCRIPTION) @Out public GridCoverage2D outSoil = null; /** * Alpha value of the Vandre equation: W = alpha*deltaElev. */ private double alphaVandre = 0.4; private double minDegreesBurton = 4.0; private double toggleDegreesBurton = 10.0; private double minDegreesModifiedBurton = Double.NEGATIVE_INFINITY; private double toggleDegreesModifiedBurton = 8.0; private double minDegrees = -1.0; private double toggleDegrees = -1.0; private GeometryFactory gf = GeometryUtilities.gf(); private TreeSet<String> processedtriggersMap = new TreeSet<String>(); private List<java.awt.Point> obstaclesSet = new ArrayList<java.awt.Point>(); private boolean useObstacles = false; /** * Check if the processing passed through the condition to be between slopes. * * If after that condition, the slope gets greater than the angle again * the delta elevation of the Vandre equation has to be reset to 0. */ private boolean wasBetweenSlopes = false; private int cols; private int rows; private WritableRaster outSoilWR; private WritableRandomIter outSoilIter; private RandomIter soilIter; private RandomIter netIter; private double xRes; private double yRes; private WritableRandomIter flowIter; @Execute public void process() throws Exception { checkNull(inFlow, inTriggers, inSlope); RegionMap regionMap = CoverageUtilities.getRegionParamsFromGridCoverage(inFlow); cols = regionMap.getCols(); rows = regionMap.getRows(); xRes = regionMap.getXres(); yRes = regionMap.getYres(); if (inSoil != null) { if (inNet == null) { throw new ModelsIllegalargumentException("If the soil map is supplied also the network map is needed.", this, pm); } outSoilWR = CoverageUtilities.createDoubleWritableRaster(cols, rows, null, null, Double.NaN); outSoilIter = RandomIterFactory.createWritable(outSoilWR, null); RenderedImage soilRI = inSoil.getRenderedImage(); soilIter = RandomIterFactory.create(soilRI, null); RenderedImage netRI = inNet.getRenderedImage(); netIter = RandomIterFactory.create(netRI, null); } switch( pMode ) { case 1: minDegrees = minDegreesModifiedBurton; toggleDegrees = toggleDegreesModifiedBurton; break; case 0: default: minDegrees = minDegreesBurton; toggleDegrees = toggleDegreesBurton; break; } GridGeometry2D gridGeometry = inFlow.getGridGeometry(); if (inObstacles != null) { List<Geometry> obstacleGeometries = FeatureUtilities.featureCollectionToGeometriesList(inObstacles, false, null); for( Geometry geometry : obstacleGeometries ) { java.awt.Point p = new java.awt.Point(); CoverageUtilities.colRowFromCoordinate(geometry.getCoordinate(), gridGeometry, p); obstaclesSet.add(p); } useObstacles = true; } RenderedImage elevRI = inElev.getRenderedImage(); RandomIter elevIter = RandomIterFactory.create(elevRI, null); RenderedImage flowRI = inFlow.getRenderedImage(); WritableRaster flowWR = CoverageUtilities.renderedImage2WritableRaster(flowRI, false); flowIter = RandomIterFactory.createWritable(flowWR, null); RenderedImage triggerRI = inTriggers.getRenderedImage(); RandomIter triggerIter = RandomIterFactory.create(triggerRI, null); RenderedImage slopeRI = inSlope.getRenderedImage(); RandomIter slopeIter = RandomIterFactory.create(slopeRI, null); outPaths = new DefaultFeatureCollection(); outIndexedTriggers = new DefaultFeatureCollection(); SimpleFeatureType triggersType = createTriggersType(); SimpleFeatureType pathsType = createPathType(); int featureIndex = 0; /* * FIXME the common paths are extracted many times, not good */ pm.beginTask("Extracting paths...", cols); for( int c = 0; c < cols; c++ ) { for( int r = 0; r < rows; r++ ) { double netflowValue = flowIter.getSampleDouble(c, r, 0); if (isNovalue(netflowValue)) { continue; } if (ModelsEngine.isSourcePixel(flowIter, c, r)) { // pm.message("NEW SOURCE: " + c + "/" + r); // start navigating down until you find a debris trigger int[] flowDirColRow = new int[]{c, r}; if (!moveToNextTriggerpoint(triggerIter, flowIter, flowDirColRow)) { // we reached the exit continue; } int triggerCol = flowDirColRow[0]; int triggerRow = flowDirColRow[1]; /* * analyze for this trigger, after that, continue with the next one down */ double flowValue = 0; do { /* * check if this trigger was already processed, wich can happen if the same path is run * after a confluence */ String triggerId = StringUtilities.joinStrings(null, String.valueOf(triggerCol), "_", String.valueOf(triggerRow)); if (processedtriggersMap.add(triggerId)) { // trigger point has never been touched, process it // pm.message(StringUtilities.joinStrings(null, "TRIGGER: ", // String.valueOf(triggerCol), "/", // String.valueOf(triggerRow))); List<Coordinate> pathCoordinates = new ArrayList<Coordinate>(); /* * the pathCondition defines if the current point (pathCoordinates) * contributes mass to the defined path. */ List<Boolean> isBetweenSlopesCondition = new ArrayList<Boolean>(); Coordinate triggerCoord = CoverageUtilities.coordinateFromColRow(flowDirColRow[0], flowDirColRow[1], gridGeometry); double elevationValue = elevIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); flowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); triggerCoord.z = elevationValue; pathCoordinates.add(triggerCoord); isBetweenSlopesCondition.add(false); /* * found a trigger, start recording and analizing, once created */ double slopeValue; double triggerValue; double lengthWithDegreeLessThanTogglePoint = 0; double deltaElevWithDegreeLessThanTogglePoint = 0; wasBetweenSlopes = false; boolean isMoving = true; // in the triggerpoint we assume it is moving while( isMoving ) { // go one down if (!ModelsEngine.go_downstream(flowDirColRow, flowValue)) throw new ModelsIllegalargumentException( "Unable to go downstream. There might be problems in the consistency of your data.", this, pm); if (useObstacles) { /* * if we land on a point in which an obstacle stops the path, * add the point and stop the path geometry. */ java.awt.Point currentPoint = new java.awt.Point(flowDirColRow[0], flowDirColRow[1]); if (obstaclesSet.contains(currentPoint)) { pm.message("Found obstacle in " + currentPoint.x + "/" + currentPoint.y); // we are passing an obstacle break; } } elevationValue = elevIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); slopeValue = slopeIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); triggerValue = triggerIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); flowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); if (flowValue == 10) { break; } // pm.message("---> " + flowDirColRow[0] + "/" + flowDirColRow[1]); /* * add the current coordinate to the path */ Coordinate tmpCoord = CoverageUtilities.coordinateFromColRow(flowDirColRow[0], flowDirColRow[1], gridGeometry); tmpCoord.z = elevationValue; pathCoordinates.add(tmpCoord); int size = pathCoordinates.size(); Coordinate c1 = pathCoordinates.get(size - 1); Coordinate c2 = pathCoordinates.get(size - 2); // length is calculated when slowing down starts (between slopes) if (wasBetweenSlopes) { lengthWithDegreeLessThanTogglePoint = lengthWithDegreeLessThanTogglePoint + distance3d(c1, c2, null); isBetweenSlopesCondition.add(true); // when between slopes, delta elev is constant } else { deltaElevWithDegreeLessThanTogglePoint = deltaElevWithDegreeLessThanTogglePoint + Math.abs(c1.z - c2.z); isBetweenSlopesCondition.add(false); } /* * and check if we will move on */ if (!isNovalue(triggerValue) || slopeValue >= toggleDegrees) { /* * in the cases in which we: * - have a trigger value * - the slope is major than the toggleDegrees * * => we move */ isMoving = true; if (wasBetweenSlopes) { /* * if we came into this condition from being between the slopes * we have to reset the elevation delta used by Vandre, since * the debris will get speed again. */ deltaElevWithDegreeLessThanTogglePoint = 0.0; lengthWithDegreeLessThanTogglePoint = 0.0; wasBetweenSlopes = false; // pm.message("--> RE-ENTERING IN STEEP PART AFTER BETWEEN SLOPES CONDITION"); } } else if (slopeValue <= minDegrees) { /* * supposed to be stopped if below minDegrees. We add the coordinate * which will be the last. */ isMoving = false; } else if (slopeValue > minDegrees && slopeValue < toggleDegrees) { /* * in this case we need to check on the base of the Vandre equation * with the chosen criterias */ double w = alphaVandre * deltaElevWithDegreeLessThanTogglePoint; if (lengthWithDegreeLessThanTogglePoint > w) { // debris stops isMoving = false; } else { isMoving = true; wasBetweenSlopes = true; } } else if (isNovalue(elevationValue) || isNovalue(slopeValue) || isNovalue(triggerValue) || isNovalue(flowValue)) { if (isNovalue(elevationValue)) { pm.errorMessage("Found an elevation novalue along the way"); } if (isNovalue(slopeValue)) { pm.errorMessage("Found a slope novalue along the way"); } if (isNovalue(triggerValue)) { pm.errorMessage("Found a trigger novalue along the way"); } if (isNovalue(flowValue)) { pm.errorMessage("Found a flow novalue along the way"); } isMoving = false; } else { throw new RuntimeException(); } } // while isMoving /* * create the trigger and linked path geometry */ if (pathCoordinates.size() > 2) { Point triggerPoint = gf.createPoint(pathCoordinates.get(0)); LineString pathLine = gf.createLineString(pathCoordinates.toArray(new Coordinate[0])); if (inSoil != null) { cumulateSoil(pathCoordinates, isBetweenSlopesCondition); } int id = featureIndex; SimpleFeatureBuilder builder = new SimpleFeatureBuilder(pathsType); Object[] values = new Object[]{pathLine, id}; builder.addAll(values); SimpleFeature pathFeature = builder.buildFeature(null); ((DefaultFeatureCollection) outPaths).add(pathFeature); builder = new SimpleFeatureBuilder(triggersType); values = new Object[]{triggerPoint, id}; builder.addAll(values); SimpleFeature triggerFeature = builder.buildFeature(null); ((DefaultFeatureCollection) outIndexedTriggers).add(triggerFeature); featureIndex++; } } /* * we have to go back and start again from after the trigger that * created the previous calculus */ flowDirColRow[0] = triggerCol; flowDirColRow[1] = triggerRow; // search for the next trigger if (!moveToNextTriggerpoint(triggerIter, flowIter, flowDirColRow)) { // we reached the exit break; } triggerCol = flowDirColRow[0]; triggerRow = flowDirColRow[1]; flowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); } while( flowValue != 10 ); } // isSource } pm.worked(1); } pm.done(); if (inSoil != null) { /* * make volume */ for( int c = 0; c < cols; c++ ) { for( int r = 0; r < rows; r++ ) { double value = outSoilIter.getSampleDouble(c, r, 0); if (isNovalue(value)) { continue; } value = value * xRes * yRes; outSoilIter.setSample(c, r, 0, value); } } outSoil = CoverageUtilities.buildCoverage("cumulatedsoil", outSoilWR, regionMap, inSoil.getCoordinateReferenceSystem()); } } /** * Calculate the soil cumulation map along the paths. * * @param pathCoordinates the coordinates of the path to be cumulated. * @param isBetweenSlopesCondition conditions defining in which points the path does * contribute mass. */ private void cumulateSoil( List<Coordinate> pathCoordinates, List<Boolean> isBetweenSlopesCondition ) { // pm.beginTask("Cumulate Soil...", pathCoordinates.size()); java.awt.Point point = new java.awt.Point(); GridGeometry2D gridGeometry = inSoil.getGridGeometry(); /* * check if the trigger is too far to reach the network. */ double distance = 0.0; Coordinate previousCoordinate = null; for( int i = 0; i < pathCoordinates.size(); i++ ) { Coordinate coordinate = pathCoordinates.get(i); CoverageUtilities.colRowFromCoordinate(coordinate, gridGeometry, point); double net = netIter.getSampleDouble(point.x, point.y, 0); if (previousCoordinate != null) { distance = distance + coordinate.distance(previousCoordinate); } previousCoordinate = coordinate; if (distance > pDistance) { // if distance is too large, the path can be discarded return; } if (!isNovalue(net)) { // if path gets into the network, it has to be considered break; } } /* * do the actual cumulation map */ double previousCumulated = 0.0; for( int i = 0; i < pathCoordinates.size(); i++ ) { Coordinate coordinate = pathCoordinates.get(i); CoverageUtilities.colRowFromCoordinate(coordinate, gridGeometry, point); boolean isBetweenSlopes = isBetweenSlopesCondition.get(i); double soil = soilIter.getSampleDouble(point.x, point.y, 0); if (isNovalue(soil)) { throw new ModelsIllegalargumentException("The soil map needs to cover the whole paths.", this, pm); } double net = netIter.getSampleDouble(point.x, point.y, 0); if (!isNovalue(net)) { isBetweenSlopes = true; } double cumulated = outSoilIter.getSampleDouble(point.x, point.y, 0); if (isBetweenSlopes) { if (isNovalue(cumulated)) { outSoilIter.setSample(point.x, point.y, 0, previousCumulated); } else { double newCumulated = cumulated + previousCumulated; outSoilIter.setSample(point.x, point.y, 0, newCumulated); } } else { if (isNovalue(cumulated)) { double newCumulated = soil + previousCumulated; outSoilIter.setSample(point.x, point.y, 0, newCumulated); previousCumulated = newCumulated; } else { double newCumulated = cumulated + previousCumulated; outSoilIter.setSample(point.x, point.y, 0, newCumulated); } } } if (doWholenet) { Coordinate lastCoordinate = pathCoordinates.get(pathCoordinates.size() - 1); CoverageUtilities.colRowFromCoordinate(lastCoordinate, gridGeometry, point); int[] flowDirColRow = new int[]{point.x, point.y}; double net = netIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); double tmpFlowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); // handle the last point of the path only if it was inside the net... if (!isNovalue(net)) { // first move to the next point if (!ModelsEngine.go_downstream(flowDirColRow, tmpFlowValue)) throw new ModelsIllegalargumentException("Unable to go downstream [col/row]: " + flowDirColRow[0] + "/" + flowDirColRow[1], this, pm); tmpFlowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); while( !isNovalue(tmpFlowValue) && tmpFlowValue != 10 ) { double cumulated = outSoilIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); if (isNovalue(cumulated)) { cumulated = 0.0; } double newCumulated = cumulated + previousCumulated; outSoilIter.setSample(flowDirColRow[0], flowDirColRow[1], 0, newCumulated); if (!ModelsEngine.go_downstream(flowDirColRow, tmpFlowValue)) throw new ModelsIllegalargumentException("Unable to go downstream [col/row]: " + flowDirColRow[0] + "/" + flowDirColRow[1], this, pm); tmpFlowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); } } } } /** * Moves the flowDirColRow variable to the next trigger point. * * @param triggerIter * @param flowDirColRow * @return <code>true</code> if a new trigger was found, <code>false</code> if the exit was reached. */ private boolean moveToNextTriggerpoint( RandomIter triggerIter, RandomIter flowIter, int[] flowDirColRow ) { double tmpFlowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); if (tmpFlowValue == 10) { return false; } if (!ModelsEngine.go_downstream(flowDirColRow, tmpFlowValue)) throw new ModelsIllegalargumentException("Unable to go downstream: " + flowDirColRow[0] + "/" + flowDirColRow[1], this, pm); while( isNovalue(triggerIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0)) ) { tmpFlowValue = flowIter.getSampleDouble(flowDirColRow[0], flowDirColRow[1], 0); if (tmpFlowValue == 10) { return false; } if (!ModelsEngine.go_downstream(flowDirColRow, tmpFlowValue)) throw new ModelsIllegalargumentException("Unable to go downstream: " + flowDirColRow[0] + "/" + flowDirColRow[1], this, pm); } return true; } @SuppressWarnings("nls") private SimpleFeatureType createTriggersType() { SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("indexedtriggers"); b.setCRS(inFlow.getCoordinateReferenceSystem()); b.add("the_geom", Point.class); b.add("PATHID", Integer.class); SimpleFeatureType type = b.buildFeatureType(); return type; } @SuppressWarnings("nls") private SimpleFeatureType createPathType() { SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("debrispaths"); b.setCRS(inFlow.getCoordinateReferenceSystem()); b.add("the_geom", LineString.class); b.add("ID", Integer.class); SimpleFeatureType type = b.buildFeatureType(); return type; } }