/* * 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.gears.io.las; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.geotools.coverage.grid.GridCoverage2D; 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.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope3D; import org.geotools.util.WeakValueHashMap; import org.jgrasstools.gears.io.las.core.ALasReader; import org.jgrasstools.gears.io.las.core.ILasHeader; import org.jgrasstools.gears.io.las.core.LasRecord; import org.jgrasstools.gears.io.las.index.OmsLasIndexReader; import org.jgrasstools.gears.io.las.index.LasIndexer; import org.jgrasstools.gears.io.las.index.strtree.STRtreeJGT; import org.jgrasstools.gears.libs.modules.JGTConstants; import org.jgrasstools.gears.utils.CrsUtilities; import org.jgrasstools.gears.utils.coverage.CoverageUtilities; import org.jgrasstools.gears.utils.files.FileUtilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.prep.PreparedGeometry; import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; import com.vividsolutions.jts.index.strtree.AbstractSTRtree; import com.vividsolutions.jts.index.strtree.ItemBoundable; /** * A class that manages las folder data. * * @author Andrea Antonello (www.hydrologis.com) */ class LasFolderIndexDataManager extends ALasDataManager implements AutoCloseable { private WeakValueHashMap<String, Pair> fileName2LasReaderMap; private WeakValueHashMap<String, STRtreeJGT> fileName2IndexMap; private List<String> fileName4LasReaderMapSupport; // private int READERCACHE = 5; private File lasFolderIndexFile; private File lasFolder; private STRtreeJGT mainLasFolderIndex; private GridCoverage2D inDem; private double elevThreshold; private SimpleFeatureCollection overviewFeatures; private ReferencedEnvelope referencedEnvelope2D; private List<ReferencedEnvelope> referencedEnvelope2DList = new ArrayList<ReferencedEnvelope>(); private List<String> fileNamesList = new ArrayList<String>(); private ReferencedEnvelope3D referencedEnvelope3D; /** * Constructor. * * @param lasFolderIndexFile the las folder index file. * @param inDem a dem to normalize the elevation. If <code>null</code>, the original las elevation is used. * @param elevThreshold a threshold to use for the elevation normalization. * @param inCrs the data {@link org.opengis.referencing.crs.CoordinateReferenceSystem}. if null, the one of the dem is read, if available. */ LasFolderIndexDataManager( File lasFolderIndexFile, GridCoverage2D inDem, double elevThreshold, CoordinateReferenceSystem inCrs ) { this.lasFolderIndexFile = lasFolderIndexFile; this.inDem = inDem; this.elevThreshold = elevThreshold; lasFolder = lasFolderIndexFile.getParentFile(); try { // prj file rules if available inCrs = CrsUtilities.readProjectionFile(lasFolderIndexFile.getAbsolutePath(), "lasfolder"); } catch (Exception e) { // ignore and try to read } if (inCrs != null) { crs = inCrs; } else if (inDem != null) { crs = inDem.getCoordinateReferenceSystem(); } else { throw new IllegalArgumentException("The Crs can't be null."); } // indexMap = new LinkedHashMap<String, Pair>() { // @Override // protected boolean removeEldestEntry(Entry<String, Pair> eldest) { // return size() > READERCACHE; // } // }; fileName2LasReaderMap = new WeakValueHashMap<String, Pair>(); fileName2IndexMap = new WeakValueHashMap<String, STRtreeJGT>(); fileName4LasReaderMapSupport = new ArrayList<String>(); } @Override public File getFile() { return lasFolderIndexFile; } /** * Open the main folder file and read the main index. * * @throws Exception */ @Override public void open() throws Exception { mainLasFolderIndex = OmsLasIndexReader.readIndex(lasFolderIndexFile.getAbsolutePath()); } /** * Get points inside a given geometry boundary. * * @param checkGeom the {@link com.vividsolutions.jts.geom.Geometry} to use to check. * @param doOnlyEnvelope check for the geom envelope instead of a intersection with it. * @return the list of points contained in the supplied geometry. * @throws Exception */ @Override @SuppressWarnings("rawtypes") public synchronized List<LasRecord> getPointsInGeometry( Geometry checkGeom, boolean doOnlyEnvelope ) throws Exception { checkOpen(); ArrayList<LasRecord> pointsListForTile = new ArrayList<LasRecord>(); Envelope env = checkGeom.getEnvelopeInternal(); PreparedGeometry preparedGeometry = null; if (!doOnlyEnvelope) { preparedGeometry = PreparedGeometryFactory.prepare(checkGeom); } List filesList = mainLasFolderIndex.query(env); for( Object fileName : filesList ) { if (fileName instanceof String) { String name = (String) fileName; Pair pair = fileName2LasReaderMap.get(name); if (pair == null) { File lasFile = new File(lasFolder, name); File lasIndexFile = FileUtilities.substituteExtention(lasFile, "lasfix"); if (lasIndexFile.exists()) { pair = getIndexPair(lasFile); if (pair != null) { fileName2LasReaderMap.put(name, pair); fileName4LasReaderMapSupport.add(name); } } else { continue; } } List addressesList = pair.strTree.query(env); for( Object obj : addressesList ) { if (obj instanceof double[]) { double[] addresses = (double[]) obj; long from = (long) addresses[0]; long to = (long) addresses[1]; for( long pointNum = from; pointNum < to; pointNum++ ) { LasRecord lasDot = pair.reader.getPointAt(pointNum); if (!doAccept(lasDot)) { continue; } if (inDem != null) { Coordinate c = new Coordinate(lasDot.x, lasDot.y); if (env.contains(c)) { // check geom instead of only envelope? if (!doOnlyEnvelope && !preparedGeometry.contains(gf.createPoint(c))) { continue; } double value = CoverageUtilities.getValue(inDem, lasDot.x, lasDot.y); if (JGTConstants.isNovalue(value)) { continue; } double height = lasDot.z - value; if (height > elevThreshold) { // lasDot.z = height; lasDot.groundElevation = height; pointsListForTile.add(lasDot); } } } else { Coordinate c = new Coordinate(lasDot.x, lasDot.y); if (env.contains(c)) { // check geom instead of only envelope? if (!doOnlyEnvelope && !preparedGeometry.contains(gf.createPoint(c))) { continue; } pointsListForTile.add(lasDot); } } } } } } } return pointsListForTile; } /** * Retrieve all the trees envelopes that intersect the geometry. * * @param checkGeom the {@link com.vividsolutions.jts.geom.Geometry} to use to check. * @param doOnlyEnvelope check for the geom envelope instead of a intersection with it. * @param minMaxZ an array to be filled with the min and max z to be used as style. * @return the list of envelopes contained in the supplied geometry. * @throws Exception */ @Override public synchronized List<Geometry> getEnvelopesInGeometry( Geometry checkGeom, boolean doOnlyEnvelope, double[] minMaxZ ) throws Exception { checkOpen(); ArrayList<Geometry> envelopeListForTile = new ArrayList<Geometry>(); Envelope env = checkGeom.getEnvelopeInternal(); PreparedGeometry preparedGeometry = null; if (!doOnlyEnvelope) { preparedGeometry = PreparedGeometryFactory.prepare(checkGeom); } double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; List< ? > filesList = mainLasFolderIndex.query(env); for( Object fileName : filesList ) { if (fileName instanceof String) { String name = (String) fileName; File lasFile = new File(lasFolder, name); File lasIndexFile = FileUtilities.substituteExtention(lasFile, "lasfix"); String absolutePath = lasIndexFile.getAbsolutePath(); STRtreeJGT lasIndex = fileName2IndexMap.get(absolutePath); if (lasIndex == null) { lasIndex = OmsLasIndexReader.readIndex(absolutePath); fileName2IndexMap.put(absolutePath, lasIndex); } List< ? > queryBoundables = lasIndex.queryBoundables(env); for( Object object : queryBoundables ) { if (object instanceof ItemBoundable) { ItemBoundable itemBoundable = (ItemBoundable) object; double[] item = (double[]) itemBoundable.getItem(); if (item.length > 0) { Envelope bounds = (Envelope) itemBoundable.getBounds(); Polygon envelopePolygon = LasIndexer.envelopeToPolygon(bounds); envelopePolygon.setUserData(new double[]{item[2], item[3]}); if (minMaxZ != null) { min = Math.min(min, item[2]); max = Math.max(max, item[2]); } if (doOnlyEnvelope) { envelopeListForTile.add(envelopePolygon); } else { if (preparedGeometry.intersects(envelopePolygon)) { envelopeListForTile.add(envelopePolygon); } } } } } } } if (minMaxZ != null) { minMaxZ[0] = min; minMaxZ[1] = max; } return envelopeListForTile; } @Override @SuppressWarnings("rawtypes") public synchronized ReferencedEnvelope getOverallEnvelope() throws Exception { if (referencedEnvelope2D == null) { checkOpen(); Class<AbstractSTRtree> class1 = AbstractSTRtree.class; Field f1 = class1.getDeclaredField("itemBoundables"); f1.setAccessible(true); ArrayList boundablesList = (ArrayList) f1.get(mainLasFolderIndex); Envelope env = null; for( Object item : boundablesList ) { if (item instanceof ItemBoundable) { ItemBoundable boundable = (ItemBoundable) item; Envelope envelope = (Envelope) boundable.getBounds(); ReferencedEnvelope tmp = new ReferencedEnvelope(envelope, crs); referencedEnvelope2DList.add(tmp); String name = (String) boundable.getItem(); fileNamesList.add(name); if (env == null) { env = envelope; } else { env.expandToInclude(envelope.getMinX(), envelope.getMinY()); env.expandToInclude(envelope.getMaxX(), envelope.getMaxY()); } } } referencedEnvelope2D = new ReferencedEnvelope(env, crs); } return referencedEnvelope2D; } @Override public List<ReferencedEnvelope> getEnvelopeList() throws Exception { getOverallEnvelope(); return referencedEnvelope2DList; } @Override public synchronized ReferencedEnvelope3D getEnvelope3D() throws Exception { if (referencedEnvelope3D == null) { checkReadersMap(); for( String key : fileName4LasReaderMapSupport ) { Pair pair = fileName2LasReaderMap.get(key); if (pair == null) { File lasFile = new File(lasFolder, key); pair = getIndexPair(lasFile); if (pair == null) { System.err.println("Null reader pair: " + lasFile); continue; } } ILasHeader header = pair.reader.getHeader(); ReferencedEnvelope3D envelope = header.getDataEnvelope(); if (referencedEnvelope3D == null) { referencedEnvelope3D = envelope; } else { referencedEnvelope3D.expandToInclude(envelope.getMinX(), envelope.getMinY(), envelope.getMinZ()); referencedEnvelope3D.expandToInclude(envelope.getMaxX(), envelope.getMaxY(), envelope.getMaxZ()); } } } return referencedEnvelope3D; } @Override public synchronized SimpleFeatureCollection getOverviewFeatures() throws Exception { if (overviewFeatures == null) { List<ReferencedEnvelope> envelopeList = getEnvelopeList(); SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.setName("overview"); b.setCRS(crs); b.add("the_geom", Polygon.class); b.add("name", String.class); SimpleFeatureType type = b.buildFeatureType(); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); overviewFeatures = new DefaultFeatureCollection(); for( int i = 0; i < envelopeList.size(); i++ ) { String name = fileNamesList.get(i); ReferencedEnvelope envelope = envelopeList.get(i); Polygon polygon = OmsLasIndexReader.envelopeToPolygon(envelope); Object[] objs = new Object[]{polygon, name}; builder.addAll(objs); SimpleFeature feature = builder.buildFeature(null); ((DefaultFeatureCollection) overviewFeatures).add(feature); } } return overviewFeatures; } @SuppressWarnings("rawtypes") private void checkReadersMap() throws Exception { checkOpen(); if (fileName2LasReaderMap.size() == 0) { List filesList = mainLasFolderIndex.itemsTree(); for( Object fileName : filesList ) { if (fileName instanceof String) { String name = (String) fileName; getReader(name); } else if (fileName instanceof List) { List filesList2 = (List) fileName; for( Object fileName2 : filesList2 ) { if (fileName2 instanceof String) { String name2 = (String) fileName2; getReader(name2); } } } else { throw new RuntimeException(); } } } } private void getReader( String name ) throws Exception { Pair pair = fileName2LasReaderMap.get(name); if (pair == null) { File lasFile = new File(lasFolder, name); pair = getIndexPair(lasFile); if (pair != null) { fileName2LasReaderMap.put(name, pair); fileName4LasReaderMapSupport.add(name); } // System.out.println("Added: " + lasIndexFile); // } else { } } private Pair getIndexPair( File lasFile ) throws Exception { File lasIndexFile = FileUtilities.substituteExtention(lasFile, "lasfix"); if (lasIndexFile.exists()) { ALasReader reader = ALasReader.getReader(lasFile, crs); reader.open(); reader.getHeader(); STRtreeJGT lasIndex = OmsLasIndexReader.readIndex(lasIndexFile.getAbsolutePath()); Pair pair = new Pair(); pair.reader = reader; pair.strTree = lasIndex; return pair; } else { System.err.println("Doesn't exist: " + lasIndexFile); } return null; } private void checkOpen() throws Exception { if (mainLasFolderIndex == null) { open(); } } @Override public void close() throws Exception { for( String key : fileName4LasReaderMapSupport ) { Pair pair = fileName2LasReaderMap.get(key); if (pair != null) pair.close(); } fileName4LasReaderMapSupport.clear(); fileName2LasReaderMap.clear(); fileName2LasReaderMap = null; } private class Pair { ALasReader reader; STRtreeJGT strTree; public void close() { if (reader != null) try { reader.close(); } catch (Exception e) { e.printStackTrace(); } reader = null; strTree = null; } } }