From 01c807b906cd1634460d49456e39c061c62dec66 Mon Sep 17 00:00:00 2001 From: Emily Gouge Date: Tue, 31 Aug 2021 13:47:32 -0700 Subject: [PATCH] fix for cases when aoi intersects with catchment and has multiple terminal nodes --- .../skeletonizer/points/PointEngine.java | 34 ++- .../flowpathconstructor/PointEngineTest.java | 281 ++++++++++++++++++ 2 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 chyf-flowpath-constructor/src/test/java/net/refractions/chyf/flowpathconstructor/PointEngineTest.java diff --git a/chyf-flowpath-constructor/src/main/java/net/refractions/chyf/flowpathconstructor/skeletonizer/points/PointEngine.java b/chyf-flowpath-constructor/src/main/java/net/refractions/chyf/flowpathconstructor/skeletonizer/points/PointEngine.java index 62c85c2..2a51964 100644 --- a/chyf-flowpath-constructor/src/main/java/net/refractions/chyf/flowpathconstructor/skeletonizer/points/PointEngine.java +++ b/chyf-flowpath-constructor/src/main/java/net/refractions/chyf/flowpathconstructor/skeletonizer/points/PointEngine.java @@ -109,10 +109,18 @@ public static void doWork(IFlowpathDataSource dataSource, ChyfProperties propert logger.info("POINT GENERATOR: " + cnt); cnt++; - + +// if (toProcess.getAttribute(idAttribute).toString().equalsIgnoreCase("04445a93-e7b7-44ea-8ff2-cbe9814134bc")) { +// System.out.println("break"); +// }else { +// System.out.println(toProcess.getAttribute(idAttribute).toString()); +// continue; +// } + ftouch.clear(); ptouch.clear(); + Polygon workingPolygon = ChyfDataSource.getPolygon(toProcess); if (!workingPolygon.isValid()) throw new Exception("Polygon not a valid geometry. Centroid: " + workingPolygon.getCentroid().toText()); @@ -163,7 +171,9 @@ public static void doWork(IFlowpathDataSource dataSource, ChyfProperties propert //write point layers dataSource.createConstructionsPoints(generator.getPoints()); } - private static List getBoundary(IFlowpathDataSource dataSource, ChyfProperties properties) throws Exception{ + + + public static List getBoundary(IFlowpathDataSource dataSource, ChyfProperties properties) throws Exception{ logger.info("computing boundary points"); @@ -312,25 +322,25 @@ private static List getBoundary(IFlowpathDataSource dataSource, Ch if (x instanceof LineString) { //find the nearest point within 0.004 units LineString ls = (LineString)x; - - Point found = null; + + List found = new ArrayList<>(); for (Coordinate c : ls.getCoordinates()) { for (Point p : inoutpoints) { if (c.equals2D(p.getCoordinate())) { - found = p; - break; + found.add(p); } } - if (found != null) break; } FlowDirection d = FlowDirection.UNKNOWN; - if (found != null) { - if (found.getUserData() != null) { - d = (FlowDirection) found.getUserData(); + if (!found.isEmpty()) { + for (Point f : found) { + if (f.getUserData() != null) { + d = (FlowDirection) f.getUserData(); + } + BoundaryEdge be = new BoundaryEdge(d, (LineString)x, f); + edges.add(be); } - BoundaryEdge be = new BoundaryEdge(d, (LineString)x, found); - edges.add(be); }else { BoundaryEdge be = new BoundaryEdge(d, (LineString)x, null); edges.add(be); diff --git a/chyf-flowpath-constructor/src/test/java/net/refractions/chyf/flowpathconstructor/PointEngineTest.java b/chyf-flowpath-constructor/src/test/java/net/refractions/chyf/flowpathconstructor/PointEngineTest.java new file mode 100644 index 0000000..428be63 --- /dev/null +++ b/chyf-flowpath-constructor/src/test/java/net/refractions/chyf/flowpathconstructor/PointEngineTest.java @@ -0,0 +1,281 @@ +/* + * Copyright 2021 Canadian Wildlife Federation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.refractions.chyf.flowpathconstructor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.geotools.data.DataUtilities; +import org.geotools.data.FeatureReader; +import org.geotools.data.Transaction; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.junit.Assert; +import org.junit.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.io.WKTReader; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.filter.Filter; +import org.opengis.filter.identity.FeatureId; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import net.refractions.chyf.datasource.ChyfAttribute; +import net.refractions.chyf.datasource.ILayer; +import net.refractions.chyf.datasource.RankType; +import net.refractions.chyf.flowpathconstructor.ChyfProperties.Property; +import net.refractions.chyf.flowpathconstructor.datasource.IFlowpathDataSource; +import net.refractions.chyf.flowpathconstructor.skeletonizer.points.BoundaryEdge; +import net.refractions.chyf.flowpathconstructor.skeletonizer.points.ConstructionPoint; +import net.refractions.chyf.flowpathconstructor.skeletonizer.points.PointEngine; +import net.refractions.chyf.flowpathconstructor.skeletonizer.voronoi.SkelLineString; + + +/** + * Test cases for PointEngine + * @author Emily + * + */ +public class PointEngineTest { + + private ChyfProperties getChyfProperties() { + ChyfProperties prop = new ChyfProperties(); + prop.setProperty(Property.PNT_VERTEX_DISTANCE, 0.0001); + return prop; + } + + /** + * Test the basic case where waterbody touches aoi on line + * @throws Exception + */ + @Test + public void testGetBoundarySingle() throws Exception{ + SingleDataSource ds = new SingleDataSource(); + + List edges = PointEngine.getBoundary(ds, getChyfProperties()); + Assert.assertEquals("invalid number of boundary edges", 1, edges.size()); + + for (Point pnt : ds.getTerminalNodes()) { + boolean found = false; + for (BoundaryEdge be : edges) { + if (be.getInOut().getCoordinate().equals2D(pnt.getCoordinate())) { + found = true; + break; + } + } + if (!found) { + Assert.fail("no boundary edge found for found: " + pnt.toText()); + } + } + } + + + /** + * Test the case where we need multiple outputs along a waterbody that touches the aoi + * boundary. This could be because of multiple single line outputs in the corresponding + * aoi. + * + * @throws Exception + */ + @Test + public void testGetBoundaryMulti() throws Exception{ + MultiDataSource ds = new MultiDataSource(); + + List edges = PointEngine.getBoundary(ds, getChyfProperties()); + Assert.assertEquals("invalid number of boundary edges", 3, edges.size()); + + for (Point pnt : ds.getTerminalNodes()) { + boolean found = false; + for (BoundaryEdge be : edges) { + if (be.getInOut().getCoordinate().equals2D(pnt.getCoordinate())) { + found = true; + break; + } + } + if (!found) { + Assert.fail("no boundary edge found for found: " + pnt.toText()); + } + } + + } + + + private class MultiDataSource implements IFlowpathDataSource{ + + @Override + public FeatureReader query(ILayer layer) throws IOException { + return null; + } + + @Override + public FeatureReader query(ILayer layer, ReferencedEnvelope bounds) + throws IOException { + return null; + } + + @Override + public FeatureReader query(ILayer layer, Filter filter) throws IOException { + return null; + } + + @Override + public FeatureReader query(ILayer layer, ReferencedEnvelope bounds, + Filter filter) throws IOException { + return null; + } + + @Override + public FeatureReader getWaterbodies() throws IOException { + + String text = "POLYGON (( 724 565, 676 653, 492 686, 338 622, 331 610, 326 595, 323 578, 321 563, 331 468, 458 394, 712 381, 724 565 ))"; + Polygon wb = null; + try { + WKTReader reader = new WKTReader(); + wb = (Polygon)reader.read(text); + }catch (Exception ex) { + throw new IOException(ex); + } + + SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); + builder.add(ChyfAttribute.INTERNAL_ID.getFieldName(),String.class); + builder.add("geometry", Polygon.class); + builder.setName("SimpleTest"); + SimpleFeatureType type = builder.buildFeatureType(); + + SimpleFeatureBuilder fbuilder = new SimpleFeatureBuilder(type); + fbuilder.set(ChyfAttribute.INTERNAL_ID.getFieldName(), "1"); + fbuilder.set("geometry", wb); + + SimpleFeature sf = fbuilder.buildFeature("1"); + + return DataUtilities.reader(Collections.singletonList(sf)); + } + + @Override + public SimpleFeatureType getFeatureType(ILayer layer) throws IOException { + return null; + } + + @Override + public void logError(String message, Geometry location) throws IOException { + System.out.println(message); + } + + @Override + public void logWarning(String message, Geometry location) throws IOException { + System.out.println(message); + } + + @Override + public void close() throws Exception { + } + + @Override + public CoordinateReferenceSystem getCoordinateReferenceSystem() { + return null; + } + + @Override + public List getAoi() throws IOException { + WKTReader reader = new WKTReader(); + try { + Polygon p = (Polygon)reader.read("POLYGON (( 724 565, 676 653, 492 686, 338 622, 331 610, 326 595, 323 578, 321 563, 331 468, 458 394, 712 381, 724 565 ))"); + return Collections.singletonList(p); + }catch (Exception ex) { + throw new IOException(ex); + } + } + + @Override + public List getTerminalNodes() throws Exception { + WKTReader reader = new WKTReader(); + try { + List points = new ArrayList<>(); + Point p = (Point)reader.read("POINT ( 331 610 )"); + points.add(p); + p = (Point)reader.read("POINT ( 326 595 )"); + points.add(p); + p = (Point)reader.read("POINT ( 323 578 )"); + points.add(p); + return points; + }catch (Exception ex) { + throw new IOException(ex); + } + } + + + @Override + public void updateWaterbodyGeometries(Collection polygons) throws IOException { } + + @Override + public void updateCoastline(FeatureId fid, LineString newls, Transaction tx) throws IOException { } + + @Override + public void createConstructionsPoints(List points) throws IOException { } + + @Override + public void writeSkeletons(Collection skeletons) throws IOException { } + + @Override + public void removeExistingSkeletons(boolean bankOnly) throws IOException { } + + @Override + public void addRankAttribute() throws Exception { } + + @Override + public void flipFlowEdges(Collection pathstoflip, Collection processed) throws Exception { } + + @Override + public void writeRanks(Map ranks) throws Exception { } + + @Override + public List getConstructionsPoints(Object catchmentId) throws IOException { + return null; + } + + @Override + public Set getOutputConstructionPoints() throws Exception { + return null; + } + } + + private class SingleDataSource extends MultiDataSource { + + + @Override + public List getTerminalNodes() throws Exception { + WKTReader reader = new WKTReader(); + try { + List points = new ArrayList<>(); + Point p = (Point)reader.read("POINT ( 331 610 )"); + points.add(p); + return points; + }catch (Exception ex) { + throw new IOException(ex); + } + } + } +}