All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.noise_planet.noisemodelling.jdbc.DelaunayReceiversMaker Maven / Gradle / Ivy

The newest version!
/**
 * NoiseModelling is a library capable of producing noise maps. It can be freely used either for research and education, as well as by experts in a professional use.
 * 

* NoiseModelling is distributed under GPL 3 license. You can read a copy of this License in the file LICENCE provided with this software. *

* Official webpage : http://noise-planet.org/noisemodelling.html * Contact: [email protected] */ package org.noise_planet.noisemodelling.jdbc; import org.h2gis.api.EmptyProgressVisitor; import org.h2gis.utilities.*; import org.h2gis.utilities.dbtypes.DBTypes; import org.h2gis.utilities.dbtypes.DBUtils; import org.locationtech.jts.densify.Densifier; import org.locationtech.jts.geom.*; import org.locationtech.jts.io.WKTWriter; import org.locationtech.jts.operation.buffer.BufferOp; import org.locationtech.jts.operation.buffer.BufferParameters; import org.locationtech.jts.simplify.TopologyPreservingSimplifier; import org.noise_planet.noisemodelling.jdbc.input.DefaultTableLoader; import org.noise_planet.noisemodelling.pathfinder.delaunay.Triangle; import org.noise_planet.noisemodelling.pathfinder.delaunay.LayerDelaunay; import org.noise_planet.noisemodelling.pathfinder.delaunay.LayerDelaunayError; import org.noise_planet.noisemodelling.pathfinder.delaunay.LayerTinfour; import org.noise_planet.noisemodelling.pathfinder.profilebuilder.Building; import org.noise_planet.noisemodelling.pathfinder.profilebuilder.Wall; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.sql.*; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.h2gis.utilities.GeometryTableUtilities.getGeometryColumnNames; /** * Create input receivers built from Delaunay for contructing a NoiseMap rendering. * SQL syntax is compatible with H2 and PostGIS. * @author Nicolas Fortin * @author SU Qi */ public class DelaunayReceiversMaker extends GridMapMaker { private static final int BATCH_MAX_SIZE = 100; private Logger logger = LoggerFactory.getLogger(DelaunayReceiversMaker.class); private double roadWidth = 2; private double maximumArea = 75; private long nbreceivers = 0; private double receiverHeight = 1.6; private double buildingBuffer = 2; private String exceptionDumpFolder = ""; private AtomicInteger constraintId = new AtomicInteger(1); private double epsilon = 1e-6; private double geometrySimplificationDistance = 1; private boolean isoSurfaceInBuildings = false; /** * Create constructor DelaunayReceiversMaker * @param buildingsTableName Buildings table * @param sourcesTableName Source table name */ public DelaunayReceiversMaker(String buildingsTableName, String sourcesTableName) { super(buildingsTableName, sourcesTableName); } /** * @return True if isosurface will be placed into buildings */ public boolean isIsoSurfaceInBuildings() { return isoSurfaceInBuildings; } /** * @param isoSurfaceInBuildings Set true in order to place isosurface in buildings */ public void setIsoSurfaceInBuildings(boolean isoSurfaceInBuildings) { this.isoSurfaceInBuildings = isoSurfaceInBuildings; } /** * Executes the Delaunay triangulation process for a grid of subdomains. * Each subdomain is handled independently and includes the generation * of receivers and triangles based on the given configuration. * * @param connection The database connection used to interact with the data. * @param verticesTableName The name of the database table where the vertices will be stored. * @param triangleTableName The name of the database table where the triangles will be stored. * @throws SQLException Thrown if a database access error or other SQL-related error occurs. */ public void run(Connection connection, String verticesTableName, String triangleTableName) throws SQLException { initialize(connection, new EmptyProgressVisitor()); AtomicInteger pk = new AtomicInteger(0); for(int i=0; i < getGridDim(); i++) { for(int j=0; j < getGridDim(); j++) { try { generateReceivers(connection, i, j, verticesTableName, triangleTableName, pk); } catch (IOException | LayerDelaunayError ex) { throw new SQLException(ex); } } } } /** * @return When an exception occur, this folder with receiver the input data */ public String getExceptionDumpFolder() { return exceptionDumpFolder; } /** * @param exceptionDumpFolder When an exception occur, this folder with receiver the input data */ public void setExceptionDumpFolder(String exceptionDumpFolder) { this.exceptionDumpFolder = exceptionDumpFolder; } /** * @return Do not add receivers closer to specified distance */ public double getBuildingBuffer() { return buildingBuffer; } /** * Do not add receivers closer to specified distance * @param buildingBuffer Distance in meters */ public void setBuildingBuffer(double buildingBuffer) { this.buildingBuffer = buildingBuffer; } /** * Explodes a geometry collection and adds polygons to the Delaunay triangulation tool. * @param intersectedGeometry * @param delaunayTool * @throws LayerDelaunayError */ private void explodeAndAddPolygon(Geometry intersectedGeometry, LayerDelaunay delaunayTool) throws LayerDelaunayError { if (intersectedGeometry instanceof GeometryCollection) { for (int j = 0; j < intersectedGeometry.getNumGeometries(); j++) { Geometry subGeom = intersectedGeometry.getGeometryN(j); explodeAndAddPolygon(subGeom, delaunayTool); } } else if(intersectedGeometry instanceof Polygon && !intersectedGeometry.isEmpty()){ delaunayTool.addPolygon((Polygon)intersectedGeometry, constraintId.getAndAdd(1)); } } /** * Merges geometries in the provided list and applies a buffer operation. * @param toUnite * @param bufferSize * @return the merged and buffered geometry. */ private Geometry merge(LinkedList toUnite, double bufferSize) { Geometry geoArray[] = new Geometry[toUnite.size()]; toUnite.toArray(geoArray); GeometryCollection polygonCollection = geometryFactory .createGeometryCollection(geoArray); BufferOp bufferOp = new BufferOp(polygonCollection, new BufferParameters(BufferParameters.DEFAULT_QUADRANT_SEGMENTS, BufferParameters.CAP_SQUARE, BufferParameters.JOIN_MITRE, BufferParameters.DEFAULT_MITRE_LIMIT)); return bufferOp.getResultGeometry(bufferSize); } /** * Prepares input geometries and feeds them into the Delaunay triangulation algorithm. * @param buildings the list of buildings to be included in the triangulation. * @param delaunayTool the Delaunay triangulation tool. * @param boundingBoxFilter the bounding box filter to confine the triangulation area. * @param srcDistance the source distance for expanding the bounding box. * @param delaunaySegments the list of road segments to be included in the triangulation. * @param minRecDist the minimum recommended distance for merging road segments. * @param buildingBuffer the buffer distance for buildings. * @throws LayerDelaunayError if an error occurs during the Delaunay triangulation process. */ private void feedDelaunay(List buildings, LayerDelaunay delaunayTool, Envelope boundingBoxFilter, double srcDistance, LinkedList delaunaySegments, double minRecDist, double buildingBuffer) throws LayerDelaunayError { Envelope extendedEnvelope = new Envelope(boundingBoxFilter); extendedEnvelope.expandBy(srcDistance * 2.); Geometry linearRing = geometryFactory.toGeometry(boundingBoxFilter); if (!(linearRing instanceof Polygon)) { return; } Polygon boundingBox = (Polygon)linearRing; LinkedList toUnite = new LinkedList<>(); Envelope fetchBox = new Envelope(boundingBoxFilter); fetchBox.expandBy(buildingBuffer); Geometry fetchGeometry = geometryFactory.toGeometry(fetchBox); for(Building building : buildings) { if(building.getGeometry().distance(fetchGeometry) < buildingBuffer) { toUnite.add(building.getGeometry()); } } // Reduce small artifacts to avoid, shortest geometry to be // over-triangulated LinkedList toUniteFinal = new LinkedList<>(); if (!toUnite.isEmpty()) { Geometry bufferBuildings = merge(toUnite, buildingBuffer); bufferBuildings = TopologyPreservingSimplifier.simplify(bufferBuildings, geometrySimplificationDistance); if(bufferBuildings.getNumPoints() > 3) { // Densify buildings to follow triangle area constraint if (maximumArea > 1) { double triangleSide = (2 * Math.pow(maximumArea, 0.5)) / Math.pow(3, 0.25); bufferBuildings = Densifier.densify(bufferBuildings, triangleSide); } toUniteFinal.add(bufferBuildings); // Add buildingsTableName to triangulation } } Geometry geom1 = geometryFactory.createPolygon(); Geometry geom2 = geometryFactory.createPolygon(); try { // Merge roads if (minRecDist > 0.01) { LinkedList toUniteRoads = new LinkedList(delaunaySegments); if (!toUniteRoads.isEmpty()) { // Build Polygons buffer from roads lines Geometry bufferRoads = merge(toUniteRoads, minRecDist / 2); // Remove small artifacts due to multiple buffer crosses bufferRoads = TopologyPreservingSimplifier.simplify(bufferRoads, geometrySimplificationDistance); // Densify roads to follow triangle area constraint if(maximumArea > 1) { double triangleSide = (2*Math.pow(maximumArea, 0.5)) / Math.pow(3, 0.25); bufferRoads = Densifier.densify(bufferRoads, triangleSide); } toUniteFinal.add(bufferRoads); // Merge roads with minRecDist m } } Geometry union = merge(toUniteFinal, 0.); // Merge roads and buildingsTableName // together // Remove geometries out of the bounding box geom1 = union; geom2 = boundingBox; union = union.intersection(boundingBox); explodeAndAddPolygon(union, delaunayTool); } catch (TopologyException ex) { WKTWriter wktWriter = new WKTWriter(3); logger.error(String.format("Error with input geometries\n%s\n%s",wktWriter.write(geom1),wktWriter.write(geom2)), ex); throw ex; } } /** * Delaunay triangulation of Sub-Domain * * @param cellMesh Final mesh target * @param mainEnvelope Global envelope * @param cellI I cell index * @param cellJ J cell index * @param maxSrcDist Maximum propagation distance * @param minRecDist Minimal distance receiver-source * @param maximumArea Maximum area of triangles * @throws LayerDelaunayError */ public void computeDelaunay(LayerDelaunay cellMesh, Envelope mainEnvelope, int cellI, int cellJ, double maxSrcDist, Collection sources, double minRecDist, double maximumArea, double buildingBuffer, List buildings) throws LayerDelaunayError { Envelope cellEnvelope = getCellEnv(mainEnvelope, cellI, cellJ, getCellWidth(), getCellHeight()); Geometry cellEnvelopeGeometry = new GeometryFactory().toGeometry(cellEnvelope); Envelope expandedCellEnvelop = new Envelope(cellEnvelope); expandedCellEnvelop.expandBy(maxSrcDist); // Build delaunay triangulation from buildings inside the extended // bounding box // ///////////////////////////////////////////////// // Add roads into delaunay tool LinkedList delaunaySegments = new LinkedList<>(); if (minRecDist > 0.1) { for (Geometry sourceGeometry : sources) { Envelope ptEnv = sourceGeometry.getEnvelopeInternal(); if (ptEnv.intersects(expandedCellEnvelop)) { if (sourceGeometry instanceof Point) { // Add square in rendering cellMesh.addPolygon((Polygon)cellEnvelopeGeometry.intersection(sourceGeometry.buffer(minRecDist, BufferParameters.CAP_SQUARE)), 1); } else { if (sourceGeometry instanceof LineString) { delaunaySegments.add((LineString) (sourceGeometry)); } else if (sourceGeometry instanceof MultiLineString) { int nbLineString = sourceGeometry.getNumGeometries(); for (int idLineString = 0; idLineString < nbLineString; idLineString++) { delaunaySegments.add((LineString) (sourceGeometry .getGeometryN(idLineString))); } } } } } } feedDelaunay(buildings, cellMesh, cellEnvelope, maxSrcDist, delaunaySegments, minRecDist, buildingBuffer); // Process delaunay logger.info("Begin delaunay"); cellMesh.setRetrieveNeighbors(false); // Add cell envelope if (maximumArea > 1) { double triangleSide = (2*Math.pow(maximumArea, 0.5)) / Math.pow(3, 0.25); Polygon polygon = (Polygon)Densifier.densify(new GeometryFactory().toGeometry(cellEnvelope), triangleSide); Coordinate[] points = polygon.getExteriorRing().getCoordinates(); for(Coordinate point : points) { cellMesh.addVertex(point); } } else { Polygon polygon = (Polygon) new GeometryFactory().toGeometry(cellEnvelope); Coordinate[] points = polygon.getExteriorRing().getCoordinates(); for(Coordinate point : points) { cellMesh.addVertex(point); } } cellMesh.processDelaunay(); logger.info("End delaunay"); } /** * Retrieves the computation envelope based on data stored in the database tables. * @param connection the database connection. * @return the computation envelope containing the bounding box of the data stored in the specified tables. * @throws SQLException */ @Override protected Envelope getComputationEnvelope(Connection connection) throws SQLException { Envelope computationEnvelope = new Envelope(); DBTypes dbTypes = DBUtils.getDBType(connection); if(!sourcesTableName.isEmpty() && JDBCUtilities.getRowCount(connection, sourcesTableName) > 0) { computationEnvelope.expandToInclude(GeometryTableUtilities.getEnvelope(connection, TableLocation.parse(sourcesTableName, dbTypes)).getEnvelopeInternal()); } if(!buildingTableParameters.buildingsTableName.isEmpty() && JDBCUtilities.getRowCount(connection, buildingTableParameters.buildingsTableName) > 0) { computationEnvelope.expandToInclude(GeometryTableUtilities.getEnvelope(connection, TableLocation.parse(buildingTableParameters.buildingsTableName, dbTypes)).getEnvelopeInternal()); } return computationEnvelope; } public double getEpsilon() { return epsilon; } public double getGeometrySimplificationDistance() { return geometrySimplificationDistance; } public void setGeometrySimplificationDistance(double geometrySimplificationDistance) { this.geometrySimplificationDistance = geometrySimplificationDistance; } public static void generateResultTable(Connection connection, String receiverTableName, String trianglesTableName, AtomicInteger receiverPK, List vertices, GeometryFactory geometryFactory, List triangles, int cellI, int cellJ, int gridDim) throws SQLException { if(!JDBCUtilities.tableExists(connection, receiverTableName)) { Statement st = connection.createStatement(); st.execute("CREATE TABLE "+TableLocation.parse(receiverTableName)+"(pk serial NOT NULL, the_geom geometry not null, PRIMARY KEY (PK))"); } if(!JDBCUtilities.tableExists(connection, trianglesTableName)) { Statement st = connection.createStatement(); st.execute("CREATE TABLE "+TableLocation.parse(trianglesTableName)+"(pk serial NOT NULL, the_geom geometry , PK_1 integer not null, PK_2 integer not null, PK_3 integer not null, cell_id integer not null, PRIMARY KEY (PK))"); } int receiverPkOffset = receiverPK.get(); // Add vertices to receivers PreparedStatement ps = connection.prepareStatement("INSERT INTO "+TableLocation.parse(receiverTableName)+" VALUES (?, ?);"); int batchSize = 0; for(Coordinate v : vertices) { ps.setInt(1, receiverPK.getAndAdd(1)); ps.setObject(2, geometryFactory.createPoint(v)); ps.addBatch(); batchSize++; if (batchSize >= BATCH_MAX_SIZE) { ps.executeBatch(); ps.clearBatch(); batchSize = 0; } } if (batchSize > 0) { ps.executeBatch(); } // Add triangles ps = connection.prepareStatement("INSERT INTO "+TableLocation.parse(trianglesTableName)+"(the_geom, PK_1, PK_2, PK_3, CELL_ID) VALUES (?, ?, ?, ?, ?);"); batchSize = 0; for(Triangle t : triangles) { ps.setObject(1, geometryFactory.createPolygon(new Coordinate[]{vertices.get(t.getA()), vertices.get(t.getB()), vertices.get(t.getC()), vertices.get(t.getA())})); ps.setInt(2, t.getA() + receiverPkOffset); ps.setInt(3, t.getC() + receiverPkOffset); ps.setInt(4, t.getB() + receiverPkOffset); ps.setInt(5, cellI * gridDim + cellJ); ps.addBatch(); batchSize++; if (batchSize >= BATCH_MAX_SIZE) { ps.executeBatch(); ps.clearBatch(); batchSize = 0; } } if (batchSize > 0) { ps.executeBatch(); } } /** * @param epsilon Merge points that are closer that this epsilon value */ public void setEpsilon(double epsilon) { this.epsilon = epsilon; } /** * Fetch source geometries and power * @param connection Active connection * @param fetchEnvelope Fetch envelope * @param doIntersection Truncate geometris * @param sourceGeometries List to feed * @throws SQLException */ public void fetchCellSource(Connection connection, Envelope fetchEnvelope, boolean doIntersection, List sourceGeometries) throws SQLException { DBTypes dbType = DBUtils.getDBType(connection.unwrap(Connection.class)); TableLocation sourceTableIdentifier = TableLocation.parse(sourcesTableName, dbType); List geomFields = getGeometryColumnNames(connection, sourceTableIdentifier); if (geomFields.isEmpty()) { throw new SQLException(String.format("The table %s does not exists or does not contain a geometry field", sourceTableIdentifier)); } String sourceGeomName = geomFields.get(0); Geometry domainConstraint = geometryFactory.toGeometry(fetchEnvelope); Tuple primaryKey = JDBCUtilities.getIntegerPrimaryKeyNameAndIndex( connection.unwrap(Connection.class), new TableLocation(sourcesTableName, dbType)); int pkIndex = primaryKey.second(); if (pkIndex < 1) { throw new IllegalArgumentException(String.format("Source table %s does not contain a primary key", sourceTableIdentifier)); } try (PreparedStatement st = connection.prepareStatement("SELECT * FROM " + sourcesTableName + " WHERE " + TableLocation.quoteIdentifier(sourceGeomName) + " && ?::geometry")) { st.setObject(1, geometryFactory.toGeometry(fetchEnvelope)); st.setFetchSize(DefaultTableLoader.DEFAULT_FETCH_SIZE); boolean autoCommit = connection.getAutoCommit(); if (autoCommit) { connection.setAutoCommit(false); } st.setFetchDirection(ResultSet.FETCH_FORWARD); try (SpatialResultSet rs = st.executeQuery().unwrap(SpatialResultSet.class)) { while (rs.next()) { Geometry geo = rs.getGeometry(); if (geo != null) { if (doIntersection) { geo = domainConstraint.intersection(geo); } if (!geo.isEmpty()) { sourceGeometries.add(geo); } } } } finally { if (autoCommit) { connection.setAutoCommit(true); } } } } public void generateReceivers(Connection connection, int cellI, int cellJ, String receiverTableName, String trianglesTableName, AtomicInteger receiverPK) throws SQLException, LayerDelaunayError, IOException { int ij = cellI * gridDim + cellJ + 1; if(verbose) { logger.info("Begin processing of cell " + ij + " / " + gridDim * gridDim); } // Compute the first pass delaunay mesh // The first pass doesn't take account of additional // vertices of neighbor cells at the borders // then, there are discontinuities in iso surfaces at each // border of cell Envelope cellEnvelope = getCellEnv(mainEnvelope, cellI, cellJ, getCellWidth(), getCellHeight()); // Fetch all source located in expandedCellEnvelop List sourceDelaunayGeometries = new LinkedList<>(); if(!sourcesTableName.isEmpty()) { fetchCellSource(connection, cellEnvelope, true, sourceDelaunayGeometries); } List buildings = new LinkedList<>(); List walls = new LinkedList<>(); Envelope expandedCell = new Envelope(cellEnvelope); expandedCell.expandBy(buildingBuffer); DefaultTableLoader.fetchCellBuildings(connection, buildingTableParameters,cellEnvelope, buildings, walls, geometryFactory); LayerTinfour cellMesh = new LayerTinfour(); cellMesh.setEpsilon(epsilon); cellMesh.setDumpFolder(exceptionDumpFolder); cellMesh.setMaxArea(maximumArea > 1 ? maximumArea : 0); try { computeDelaunay(cellMesh, mainEnvelope, cellI, cellJ, maximumPropagationDistance, sourceDelaunayGeometries, roadWidth, maximumArea, buildingBuffer, buildings); } catch (LayerDelaunayError err) { throw new SQLException(err.getLocalizedMessage(), err); } sourceDelaunayGeometries.clear(); // Make a structure to keep the following information // Triangle list with 3 vertices(int), and 3 neighbor // triangle ID // Vertices list // The evaluation of sound level must be done where the // following vertices are List vertices = new ArrayList<>(cellMesh.getVertices().size()); for(Coordinate vertex : cellMesh.getVertices()) { Coordinate translatedVertex = new Coordinate(vertex); double z = receiverHeight; translatedVertex.setOrdinate(2, z); vertices.add(translatedVertex); } // Do not add triangles associated with buildings List triangles; if (!isoSurfaceInBuildings) { triangles = new ArrayList<>(cellMesh.getTriangles().size()); for (Triangle triangle : cellMesh.getTriangles()) { if (triangle.getAttribute() == 0) { // place only triangles not associated to a building triangles.add(triangle); } } } else { triangles = cellMesh.getTriangles(); } nbreceivers += vertices.size(); generateResultTable(connection, receiverTableName, trianglesTableName, receiverPK, vertices, geometryFactory, triangles, cellI, cellJ, gridDim); } public double getRoadWidth() { return roadWidth; } public void setRoadWidth(double roadWidth) { this.roadWidth = roadWidth; } public double getMaximumArea() { return maximumArea; } public void setMaximumArea(double maximumArea) { this.maximumArea = maximumArea; } public double getReceiverHeight() { return receiverHeight; } public void setReceiverHeight(double receiverHeight) { this.receiverHeight = receiverHeight; } public long getNbreceivers() { return nbreceivers; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy