/* * 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.lesto.modules.raster.adaptivetinfilter; import static org.jgrasstools.gears.utils.geometry.GeometryUtilities.distance3d; import static org.jgrasstools.gears.utils.geometry.GeometryUtilities.getAngleBetweenLinePlane; import static org.jgrasstools.gears.utils.geometry.GeometryUtilities.getLineWithPlaneIntersection; import static org.jgrasstools.gears.utils.geometry.GeometryUtilities.getTriangleCentroid; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; 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.io.las.ALasDataManager; import org.jgrasstools.gears.io.las.core.LasRecord; import org.jgrasstools.gears.libs.modules.ThreadedRunnable; import org.jgrasstools.gears.libs.monitor.IJGTProgressMonitor; import org.jgrasstools.gears.utils.geometry.GeometryUtilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.algorithm.locate.SimplePointInAreaLocator; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Location; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.index.strtree.STRtree; import com.vividsolutions.jts.triangulate.DelaunayTriangulationBuilder; /** * A helper class for tin handling. * * @author Andrea Antonello (www.hydrologis.com) */ @SuppressWarnings("nls") public class TinHandler { public static final double POINTENVELOPE_EXPAND = 0.1; /** * The list of coordinates that will be used at the next tin generation. */ private List<Coordinate> tinCoordinateList = new ArrayList<Coordinate>(); /** * The list of coordinates that are left out as non ground points at each filtering. */ private List<Coordinate> leftOverCoordinateList = new ArrayList<Coordinate>(); /** * The geometries of the last calculated tin. */ private Geometry[] tinGeometries = null; private GeometryFactory gf = GeometryUtilities.gf(); private final IJGTProgressMonitor pm; private boolean didInitialize = false; private boolean isFirstStatsCalculation = true; private final CoordinateReferenceSystem crs; private final double distanceThreshold; private final int threadsNum; private double calculatedAngleThreshold; private double calculatedDistanceThreshold; private final ReadWriteLock monitor = new ReentrantReadWriteLock(); private final Double maxEdgeLength; private double maxEdgeLengthThreshold; private final double angleThreshold; /** * Constructor. * * @param pm the monitor. * @param crs the crs to use for feature creation. * @param distanceThreshold the fixed maximum distance threshold to use. * @param maxEdgeLength the max edge length for the longest edge of a triangle (if larger, it is ignored) * @param threadsNum the number of threads to use for parallel operations. */ public TinHandler( IJGTProgressMonitor pm, CoordinateReferenceSystem crs, double angleThreshold, double distanceThreshold, Double maxEdgeLength, int threadsNum ) { this.pm = pm; this.crs = crs; this.angleThreshold = angleThreshold; this.distanceThreshold = distanceThreshold; this.maxEdgeLength = maxEdgeLength; this.threadsNum = threadsNum; } /** * Sets the initial coordinates to start with. * * <p>Generates the tin on the first set of coordinates and adds the * coordinates to the {@link #tinCoordinateList} for future use.</p> * * <p><b>Note that it is mandatory to call this method to initialize.</b></p> * * @param coordinateList the initial list of coordinates. */ public void setStartCoordinates( List<Coordinate> coordinateList ) { generateTin(coordinateList); for( int i = 0; i < tinGeometries.length; i++ ) { Coordinate[] coordinates = tinGeometries[i].getCoordinates(); if (!tinCoordinateList.contains(coordinates[0])) { tinCoordinateList.add(coordinates[0]); } if (!tinCoordinateList.contains(coordinates[1])) { tinCoordinateList.add(coordinates[1]); } if (!tinCoordinateList.contains(coordinates[2])) { tinCoordinateList.add(coordinates[2]); } } didInitialize = true; } /** * Get the triangles of the current active tin. * * @return the array of polygons. */ public Geometry[] getTriangles() { checkTinGeometries(); return tinGeometries; } /** * Returns the current size of the {@link #tinCoordinateList} representing teh current ground points. * * @return the size of the tin coords list. */ public int getCurrentGroundPointsNum() { return tinCoordinateList.size(); } /** * Returns the size of the points currently defined as being non-ground. * * @return the size of the non ground points list. */ public int getCurrentNonGroundPointsNum() { synchronized (leftOverCoordinateList) { return leftOverCoordinateList.size(); } } /** * Filter data on thresholds of all available data on the tin. * * <p><b>Note: At the first run of this method, only thresholds are calculated.</b></p> * * @param lasHandler the las data handler of all data. * @throws Exception */ public void filterOnAllData( final ALasDataManager lasHandler ) throws Exception { final ConcurrentSkipListSet<Double> angleSet = new ConcurrentSkipListSet<Double>(); final ConcurrentSkipListSet<Double> distanceSet = new ConcurrentSkipListSet<Double>(); if (isFirstStatsCalculation) { pm.beginTask("Calculating initial statistics...", tinGeometries.length); } else { pm.beginTask("Filtering all data on seeds tin...", tinGeometries.length); } try { final List<Coordinate> newTotalLeftOverCoordinateList = new ArrayList<Coordinate>(); if (threadsNum > 1) { // multithreaded ThreadedRunnable tRun = new ThreadedRunnable(threadsNum, null); for( final Geometry tinGeom : tinGeometries ) { tRun.executeRunnable(new Runnable(){ public void run() { List<Coordinate> leftOverList = runFilterOnAllData(lasHandler, angleSet, distanceSet, tinGeom); synchronized (newTotalLeftOverCoordinateList) { newTotalLeftOverCoordinateList.addAll(leftOverList); } } }); } tRun.waitAndClose(); } else { for( final Geometry tinGeom : tinGeometries ) { List<Coordinate> leftOverList = runFilterOnAllData(lasHandler, angleSet, distanceSet, tinGeom); newTotalLeftOverCoordinateList.addAll(leftOverList); } } pm.done(); leftOverCoordinateList.clear(); leftOverCoordinateList.addAll(newTotalLeftOverCoordinateList); /* * now recalculate the thresholds */ if (angleSet.size() > 1) { calculatedAngleThreshold = getMedianFromSet(angleSet); pm.message("Calculated angle threshold: " + calculatedAngleThreshold + " (range: " + angleSet.first() + " to " + angleSet.last() + ")"); } else if (angleSet.size() == 0) { return; } else { calculatedAngleThreshold = angleSet.first(); pm.message("Single angle left: " + calculatedAngleThreshold); } if (distanceSet.size() > 1) { calculatedDistanceThreshold = getMedianFromSet(distanceSet); pm.message("Calculated distance threshold: " + calculatedDistanceThreshold + " (range: " + distanceSet.first() + " to " + distanceSet.last() + ")"); } else if (distanceSet.size() == 0) { return; } else { calculatedDistanceThreshold = distanceSet.first(); pm.message("Single distance left: " + calculatedDistanceThreshold); } if (isFirstStatsCalculation) { isFirstStatsCalculation = false; } } catch (Exception e) { e.printStackTrace(); } } private List<Coordinate> runFilterOnAllData( final ALasDataManager lasHandler, final ConcurrentSkipListSet<Double> angleSet, final ConcurrentSkipListSet<Double> distanceSet, final Geometry tinGeom ) { final List<Coordinate> newTotalLeftOverCoordinateList = new ArrayList<Coordinate>(); try { Coordinate[] tinCoords = tinGeom.getCoordinates(); Coordinate triangleCentroid = getTriangleCentroid(tinCoords[0], tinCoords[1], tinCoords[2]); List<LasRecord> pointsInGeom = lasHandler.getPointsInGeometry(tinGeom, false); /* * now sort the points in the triangle in distance order * from the triangle centroid, nearest first */ TreeSet<Coordinate> centroidNearestSet = new TreeSet<Coordinate>(new PointsToCoordinateComparator(triangleCentroid)); for( LasRecord pointInGeom : pointsInGeom ) { Coordinate c = new Coordinate(pointInGeom.x, pointInGeom.y, pointInGeom.z); if (c.equals(tinCoords[0]) || c.equals(tinCoords[1]) || c.equals(tinCoords[2])) { // the seed point was reread continue; } centroidNearestSet.add(c); } // find first possible ground coordinate boolean foundOne = false; for( Coordinate c : centroidNearestSet ) { if (foundOne && !isFirstStatsCalculation) { if (!newTotalLeftOverCoordinateList.contains(c)) newTotalLeftOverCoordinateList.add(c); } else { /* * find the nearest node and distance */ Coordinate[] nodes = getOrderedNodes(c, tinCoords[0], tinCoords[1], tinCoords[2]); double nearestDistance = distance3d(nodes[0], c, null); if (!isFirstStatsCalculation) { /* * if we are here, we are doing filtering and calc of thresholds * for the next round only on the kept data. */ if (nearestDistance > calculatedDistanceThreshold) { if (!newTotalLeftOverCoordinateList.contains(c)) newTotalLeftOverCoordinateList.add(c); continue; } } /* * calculate the angle between the facet normal and the line * connecting the point and the nearest facet node. */ double angle = getAngleBetweenLinePlane(c, nodes[0], nodes[1], nodes[2]); if (Double.isNaN(angle)) { pm.errorMessage("Found NaN angle, set to 0..."); angle = 0.0; } if (!isFirstStatsCalculation) { if (angle > calculatedAngleThreshold) { if (!newTotalLeftOverCoordinateList.contains(c)) newTotalLeftOverCoordinateList.add(c); continue; } else { // add it to the next tin synchronized (tinCoordinateList) { if (!tinCoordinateList.contains(c)) { tinCoordinateList.add(c); foundOne = true; angleSet.add(angle); distanceSet.add(nearestDistance); } } } } else { angleSet.add(angle); distanceSet.add(nearestDistance); } } } } catch (Exception e) { e.printStackTrace(); } pm.worked(1); return newTotalLeftOverCoordinateList; } public void filterOnLeftOverData() { if (isFirstStatsCalculation) { throw new IllegalArgumentException("The first round needs to be filtered on all data."); } pm.beginTask("Creating points indexes...", leftOverCoordinateList.size()); final STRtree leftOverCoordinatesTree = new STRtree(leftOverCoordinateList.size()); for( Coordinate c : leftOverCoordinateList ) { leftOverCoordinatesTree.insert(new Envelope(c), c); } pm.done(); if (maxEdgeLength != null) { maxEdgeLengthThreshold = maxEdgeLength; } Geometry[] triangles = getTriangles(); final ConcurrentSkipListSet<Double> angleSet = new ConcurrentSkipListSet<Double>(); final ConcurrentSkipListSet<Double> distanceSet = new ConcurrentSkipListSet<Double>(); final List<Coordinate> newTotalLeftOverCoordinateList = new ArrayList<Coordinate>(); pm.beginTask("Filtering leftover coordinates on previous tin...", triangles.length); if (threadsNum > 1) { ThreadedRunnable tRun = new ThreadedRunnable(threadsNum, null); for( int i = 0; i < triangles.length; i++ ) { final Geometry triangle = triangles[i]; tRun.executeRunnable(new Runnable(){ public void run() { if (maxEdgeLength != null && triangle.getLength() < maxEdgeLengthThreshold * 3.0) { return; } List<Coordinate> leftOverList = runfilterOnLeftOverData(leftOverCoordinatesTree, angleSet, distanceSet, triangle); synchronized (newTotalLeftOverCoordinateList) { newTotalLeftOverCoordinateList.addAll(leftOverList); } } }); } tRun.waitAndClose(); } else { for( int i = 0; i < triangles.length; i++ ) { Geometry triangle = triangles[i]; List<Coordinate> leftOverList = runfilterOnLeftOverData(leftOverCoordinatesTree, angleSet, distanceSet, triangle); newTotalLeftOverCoordinateList.addAll(leftOverList); } } pm.done(); leftOverCoordinateList.clear(); leftOverCoordinateList.addAll(newTotalLeftOverCoordinateList); /* * now recalculate the thresholds */ if (angleSet.size() > 1) { calculatedAngleThreshold = getMedianFromSet(angleSet); pm.message("Calculated angle threshold: " + calculatedAngleThreshold + " (range: " + angleSet.first() + " to " + angleSet.last() + ")"); } else if (angleSet.size() == 0) { return; } else { calculatedAngleThreshold = angleSet.first(); pm.message("Single angle left: " + calculatedAngleThreshold); } if (distanceSet.size() > 1) { calculatedDistanceThreshold = getMedianFromSet(distanceSet); pm.message("Calculated distance threshold: " + calculatedDistanceThreshold + " (range: " + distanceSet.first() + " to " + distanceSet.last() + ")"); } else if (distanceSet.size() == 0) { return; } else { calculatedDistanceThreshold = distanceSet.first(); pm.message("Single distance left: " + calculatedDistanceThreshold); } if (calculatedDistanceThreshold < distanceThreshold) { calculatedDistanceThreshold = distanceThreshold; pm.message("Corrected calculated distance threshold to be: " + calculatedDistanceThreshold); } } @SuppressWarnings("rawtypes") private List<Coordinate> runfilterOnLeftOverData( final STRtree leftOverCoordsTree, final ConcurrentSkipListSet<Double> angleSet, final ConcurrentSkipListSet<Double> distanceSet, final Geometry triangle ) { List<Coordinate> newLeftOverCoordinateList = new ArrayList<Coordinate>(); Coordinate[] tinCoords = triangle.getCoordinates(); Coordinate triangleCentroid = getTriangleCentroid(tinCoords[0], tinCoords[1], tinCoords[2]); // get left coords around triangle List triangleCoordinates = null; monitor.readLock().lock(); try { triangleCoordinates = leftOverCoordsTree.query(triangle.getEnvelopeInternal()); } finally { monitor.readLock().unlock(); } /* * now sort the points in the triangle in distance order * from the triangle centroid, nearest first */ TreeSet<Coordinate> centroidNearestSet = new TreeSet<Coordinate>(new PointsToCoordinateComparator(triangleCentroid)); for( Object coordinateObj : triangleCoordinates ) { Coordinate c = (Coordinate) coordinateObj; if (c.equals(tinCoords[0]) || c.equals(tinCoords[1]) || c.equals(tinCoords[2])) { // the seed point was reread continue; } int loc = SimplePointInAreaLocator.locate(c, triangle); if (loc == Location.INTERIOR) { centroidNearestSet.add(c); } } // find first possible ground coordinate boolean foundOne = false; for( Coordinate c : centroidNearestSet ) { if (foundOne && !isFirstStatsCalculation) { if (!newLeftOverCoordinateList.contains(c)) newLeftOverCoordinateList.add(c); } else { /* * find the nearest node and distance */ Coordinate[] nodes = getOrderedNodes(c, tinCoords[0], tinCoords[1], tinCoords[2]); double nearestDistance = distance3d(nodes[0], c, null); /* * if we are here, we are doing filtering and calc of thresholds * for the next round only on the kept data. */ if (nearestDistance > calculatedDistanceThreshold) { if (!newLeftOverCoordinateList.contains(c)) newLeftOverCoordinateList.add(c); continue; } /* * calculate the angle between the facet normal and the line * connecting the point and the nearest facet node. */ double angle = getAngleBetweenLinePlane(c, nodes[0], nodes[1], nodes[2]); if (Double.isNaN(angle)) { pm.errorMessage("Found NaN angle, set to 0..."); angle = 0.0; } if (angle > calculatedAngleThreshold && angle > angleThreshold) { // TODO if (!newLeftOverCoordinateList.contains(c)) newLeftOverCoordinateList.add(c); continue; } else { // add it to the next tin synchronized (tinCoordinateList) { if (!tinCoordinateList.contains(c)) { tinCoordinateList.add(c); foundOne = true; angleSet.add(angle); distanceSet.add(nearestDistance); } } } } } pm.worked(1); return newLeftOverCoordinateList; } public void finalCleanup( final double pFinalCleanupDist ) { if (isFirstStatsCalculation) { throw new IllegalArgumentException("The first round needs to be filtered on all data."); } pm.beginTask("Creating points indexes...", leftOverCoordinateList.size()); final STRtree leftOverCoordinatesTree = new STRtree(leftOverCoordinateList.size()); for( Coordinate c : leftOverCoordinateList ) { leftOverCoordinatesTree.insert(new Envelope(c), c); } pm.done(); final AtomicInteger removedCount = new AtomicInteger(); Geometry[] triangles = getTriangles(); final List<Coordinate> newLeftOverCoordinateList = new ArrayList<Coordinate>(); pm.beginTask("Final cleanup through triangle to point distance filter...", triangles.length); ThreadedRunnable tRun = new ThreadedRunnable(threadsNum, null); for( int i = 0; i < triangles.length; i++ ) { final Geometry triangle = triangles[i]; tRun.executeRunnable(new Runnable(){ public void run() { runFinalFilter(leftOverCoordinatesTree, newLeftOverCoordinateList, triangle, pFinalCleanupDist, removedCount); } }); } tRun.waitAndClose(); pm.done(); pm.message("Final points removed from non ground: " + removedCount.get()); pm.message("Final points left as non ground: " + newLeftOverCoordinateList.size()); leftOverCoordinateList.clear(); leftOverCoordinateList.addAll(newLeftOverCoordinateList); } private void runFinalFilter( final STRtree leftOverCoordsTree, List<Coordinate> newLeftOverCoordinateList, final Geometry triangle, double pFinalCleanupDist, AtomicInteger removedCount ) { Coordinate[] tinCoords = triangle.getCoordinates(); // get left coords around triangle List triangleCoordinates = null; monitor.readLock().lock(); try { triangleCoordinates = leftOverCoordsTree.query(triangle.getEnvelopeInternal()); } finally { monitor.readLock().unlock(); } /* * now sort the points in the triangle in distance order * from the triangle centroid, nearest first */ int count = 0; for( Object coordinateObj : triangleCoordinates ) { Coordinate c = (Coordinate) coordinateObj; int loc = SimplePointInAreaLocator.locate(c, triangle); if (loc == Location.INTERIOR) { Coordinate c1 = new Coordinate(c.x, c.y, 1E6); Coordinate c2 = new Coordinate(c.x, c.y, -1E6); Coordinate intersection = getLineWithPlaneIntersection(c1, c2, tinCoords[0], tinCoords[1], tinCoords[2]); double distance = distance3d(intersection, c, null); if (distance > pFinalCleanupDist) { count++; synchronized (newLeftOverCoordinateList) { newLeftOverCoordinateList.add(c); } } } } removedCount.addAndGet(count); pm.worked(1); } public void resetTin() { tinGeometries = null; } /** * Generate a tin from a given coords list. The internal tin geoms array is set from the result. * * @param coordinateList the coords to use for the tin generation. */ private void generateTin( List<Coordinate> coordinateList ) { pm.beginTask("Generate tin...", -1); DelaunayTriangulationBuilder b = new DelaunayTriangulationBuilder(); b.setSites(coordinateList); Geometry tinTriangles = b.getTriangles(gf); tinGeometries = new Geometry[tinTriangles.getNumGeometries()]; for( int i = 0; i < tinTriangles.getNumGeometries(); i++ ) { tinGeometries[i] = tinTriangles.getGeometryN(i); } pm.done(); } /** * Generate a spatial index on the tin geometries. */ public STRtree generateTinIndex( Double maxEdgeLength ) { double maxEdge = maxEdgeLength != null ? maxEdgeLength : 0.0; pm.beginTask("Creating tin indexes...", tinGeometries.length); final STRtree tinTree = new STRtree(tinGeometries.length); for( Geometry geometry : tinGeometries ) { if (maxEdgeLength != null) { Coordinate[] coordinates = geometry.getCoordinates(); double maxLength = distance3d(coordinates[0], coordinates[1], null); double tmpLength = distance3d(coordinates[1], coordinates[2], null); if (tmpLength > maxLength) { maxLength = tmpLength; } tmpLength = distance3d(coordinates[2], coordinates[0], null); if (tmpLength > maxLength) { maxLength = tmpLength; } // triangles below a certain edge length are not adapted if (maxLength < maxEdge) { continue; } } tinTree.insert(geometry.getEnvelopeInternal(), geometry); } pm.done(); return tinTree; } /** * Checks if the tin is done. If not, it generates it with the available {@link #tinCoordinateList}. */ private void checkTinGeometries() { if (!didInitialize) { throw new IllegalArgumentException("Not initialized properly. Did you call setStartCoordinates?"); } if (tinGeometries == null) { generateTin(tinCoordinateList); } } /** * Order coordinates to have the first coordinate in the array as the nearest to a given * coordinate 'c'. The second and third are not ordered, but randomly added. * * @param c * @param coordinate1 * @param coordinate2 * @param coordinate3 * @return */ private Coordinate[] getOrderedNodes( Coordinate c, Coordinate coordinate1, Coordinate coordinate2, Coordinate coordinate3 ) { double d = distance3d(c, coordinate1, null); Coordinate nearest = coordinate1; Coordinate c2 = coordinate2; Coordinate c3 = coordinate3; double d2 = distance3d(c, coordinate2, null); if (d2 < d) { nearest = coordinate2; d = d2; c2 = coordinate1; c3 = coordinate3; } double d3 = distance3d(c, coordinate3, null); if (d3 < d) { nearest = coordinate3; c2 = coordinate1; c3 = coordinate2; } return new Coordinate[]{nearest, c2, c3}; } private double getMedianFromSet( final ConcurrentSkipListSet<Double> set ) { double threshold = 0; int halfNum = set.size() / 2; int count = 0; for( double value : set ) { if (count == halfNum) { threshold = value; break; } count++; } return threshold; } private double getAverageFromSet( final ConcurrentSkipListSet<Double> set ) { double sum = 0; int count = 0; for( double value : set ) { sum = sum + value; count++; } return sum / count; } private double getCenterFromSet( final ConcurrentSkipListSet<Double> set ) { return (set.last() - set.first()) / 2; } /** * Create a {@link SimpleFeatureCollection FeatureCollection} from the current tin triangles * with information about the vertexes elevation. * * @return the feature collection of the tin. */ public SimpleFeatureCollection toFeatureCollection() { checkTinGeometries(); SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("triangle"); b.setCRS(crs); DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); b.add("the_geom", Polygon.class); b.add("elev0", Double.class); b.add("elev1", Double.class); b.add("elev2", Double.class); SimpleFeatureType type = b.buildFeatureType(); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); for( Geometry g : tinGeometries ) { Coordinate[] coordinates = g.getCoordinates(); Object[] values; int windingRule = GeometryUtilities.getTriangleWindingRule(coordinates[0], coordinates[1], coordinates[2]); if (windingRule > 0) { // need to reverse the triangle values = new Object[]{g, coordinates[0].z, coordinates[2].z, coordinates[1].z}; } else { values = new Object[]{g, coordinates[0].z, coordinates[1].z, coordinates[2].z}; } builder.addAll(values); SimpleFeature feature = builder.buildFeature(null); newCollection.add(feature); } return newCollection; } public SimpleFeatureCollection toFeatureCollectionOthers() { SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("points"); b.setCRS(crs); DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); b.add("the_geom", Point.class); b.add("elev", Double.class); SimpleFeatureType type = b.buildFeatureType(); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); for( Coordinate c : leftOverCoordinateList ) { Object[] values = new Object[]{gf.createPoint(c), c.z}; builder.addAll(values); SimpleFeature feature = builder.buildFeature(null); newCollection.add(feature); } return newCollection; } public SimpleFeatureCollection toFeatureCollectionTinPoints() { SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("points"); b.setCRS(crs); DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); b.add("the_geom", Point.class); b.add("elev", Double.class); SimpleFeatureType type = b.buildFeatureType(); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); for( Coordinate c : tinCoordinateList ) { Object[] values = new Object[]{gf.createPoint(c), c.z}; builder.addAll(values); SimpleFeature feature = builder.buildFeature(null); newCollection.add(feature); } return newCollection; } public double[] getMinMaxElev() { double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for( Coordinate coordinate : tinCoordinateList ) { max = Math.max(max, coordinate.z); min = Math.min(min, coordinate.z); } return new double[]{min, max}; } // private AtomicInteger count = new AtomicInteger(); // private void dumpPointsInGeom( Geometry tinGeom, List<LasRecord> pointsInGeom, // List<Coordinate> addedPoints ) // throws IOException { // String path = "/home/moovida/dati_unibz/outshape/tinfilter/triangles/"; // int i = count.getAndIncrement(); // { // SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); // b.setName("t" + i); // b.setCRS(crs); // // DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); // b.add("the_geom", Polygon.class); // b.add("elev1", Double.class); // b.add("elev2", Double.class); // b.add("elev3", Double.class); // SimpleFeatureType type = b.buildFeatureType(); // SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); // Coordinate[] coordinates = tinGeom.getCoordinates(); // Object[] values = new Object[]{tinGeom, coordinates[0].z, coordinates[1].z, // coordinates[2].z}; // builder.addAll(values); // SimpleFeature feature = builder.buildFeature(null); // newCollection.add(feature); // // OmsVectorWriter.writeVector(path + "t" + i + ".shp", newCollection); // } // { // SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); // b.setName("p" + i); // b.setCRS(crs); // // DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); // b.add("the_geom", Point.class); // b.add("elev", Double.class); // SimpleFeatureType type = b.buildFeatureType(); // SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); // // for( LasRecord lasRecord : pointsInGeom ) { // Point point = gf.createPoint(new Coordinate(lasRecord.x, lasRecord.y, lasRecord.z)); // Object[] values = new Object[]{point, lasRecord.z}; // builder.addAll(values); // SimpleFeature feature = builder.buildFeature(null); // newCollection.add(feature); // } // // OmsVectorWriter.writeVector(path + "p" + i + ".shp", newCollection); // } // { // SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); // b.setName("a" + i); // b.setCRS(crs); // // DefaultFeatureCollection newCollection = new DefaultFeatureCollection(); // b.add("the_geom", Point.class); // b.add("elev", Double.class); // SimpleFeatureType type = b.buildFeatureType(); // SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); // // for( Coordinate coord : addedPoints ) { // Point point = gf.createPoint(coord); // Object[] values = new Object[]{point, coord.z}; // builder.addAll(values); // SimpleFeature feature = builder.buildFeature(null); // newCollection.add(feature); // } // // OmsVectorWriter.writeVector(path + "a" + i + ".shp", newCollection); // } // } }