1

So I have a shp-file containing a bunch of polygons. In this case, a polygon is a body of in-land water (like lakes and that kind of stuff).

My system is tracking a moving object, so in order to determine what this object is, I would like to see if this object is in water or on land AND how far it is to NEAREST shore (yes, both if it's in or out of water). I will take a sample point from the object once in a while and test it.

The system is written in Java, and I have imported GeoTools snapshot 17. But if other utils is easier to use, there is no requirements to use this.

To test if the point is IN water (that is, inside a polygon), this methods works:

private void findPolygonsForPoint(Coordinate point) {
    Filter filter = null;
    SimpleFeatureIterator iterator = null;
    try {
        filter = CQL.toFilter("CONTAINS(the_geom, POINT(" + point.x + " " + point.y + "))");

        SimpleFeatureCollection collection = source.getFeatures(filter);
        if(collection.size() < 1) {
            System.out.println(coordinate2String(point) + " is NOT in a polygon");
        } else {
            System.out.println(coordinate2String(point) + " IS in a polygon");
            insidePolygon++;
            iterator = collection.features();

            while(iterator.hasNext()) {
                SimpleFeature feature = iterator.next();
                //find nearest edge of the polygon
            }
        }
    } catch(CQLException e) {
        aLog.error("", e);
    } catch(IOException e) {
        aLog.error("", e);
    } finally {
        if(iterator != null) {
            iterator.close();
        }
    }
}

Now the questions:

1) If the point is NOT in a polygon, how do I find the nearest polygon in the source (being a SimpleFeatureSource)?

2) How do I find the distance to the edge of the polygon that is closest?

Any help would be highly appreciated! Especially code examples - I'm kind of rusty on math and geometry.

Thank you.

Mars
  • 45
  • 1
  • 6

1 Answers1

4

The easiest answer is to use a SpatialIndexFeatureCollection to do the heavy lifting for you, it will find the nearest polygon, then you can check if you are inside or outside.

So a simple class like:

public class NearestPolygon {
  private static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
  private static GeometryFactory gf = new GeometryFactory();
  private SpatialIndexFeatureCollection index;
  private SimpleFeature lastMatched;

  public NearestPolygon(SimpleFeatureCollection features) {

    index = new SpatialIndexFeatureCollection(features.getSchema());
    index.addAll(features);
  }



  public Point findNearestPolygon(Point p) {
    final double MAX_SEARCH_DISTANCE = index.getBounds().getSpan(0);
    Coordinate coordinate = p.getCoordinate();
    ReferencedEnvelope search = new ReferencedEnvelope(new Envelope(coordinate),
        index.getSchema().getCoordinateReferenceSystem());
    search.expandBy(MAX_SEARCH_DISTANCE);
    BBOX bbox = ff.bbox(ff.property(index.getSchema().getGeometryDescriptor().getName()), (BoundingBox) search);
    SimpleFeatureCollection candidates = index.subCollection(bbox);

    double minDist = MAX_SEARCH_DISTANCE + 1.0e-6;
    Coordinate minDistPoint = null;
    try (SimpleFeatureIterator itr = candidates.features()) {
      while (itr.hasNext()) {

        SimpleFeature feature = itr.next();
        LocationIndexedLine line = new LocationIndexedLine(((MultiPolygon) feature.getDefaultGeometry()).getBoundary());
        LinearLocation here = line.project(coordinate);
        Coordinate point = line.extractPoint(here);
        double dist = point.distance(coordinate);
        if (dist < minDist) {
          minDist = dist;
          minDistPoint = point;
          lastMatched = feature;
        }
      }
    }
    Point ret = null;
    if (minDistPoint == null) {
      ret = gf.createPoint((Coordinate) null);
    } else {
      ret = gf.createPoint(minDistPoint);
    }
    return ret;
  }

  public SimpleFeature getLastMatched() {
    return lastMatched;
  }
}

Can be called using some code like:

  public static void main(String[] args) throws IOException {
    String lakes = "/data/natural_earth/10m_physical/ne_10m_lakes.shp";
    HashMap<String, Object> params = new HashMap<>();
    params.put("url", DataUtilities.fileToURL(new File(lakes)));
    DataStore ds = DataStoreFinder.getDataStore(params);

    String name = ds.getTypeNames()[0];
    SimpleFeatureSource source = ds.getFeatureSource(name);
    SimpleFeatureCollection features = source.getFeatures();
    NearestPolygon polyFinder = new NearestPolygon(features);
    for (int i = 0; i < 100; i++) {
      Point p = GenerateRandomData.createRandomPoint();
      Point pointOnLine = polyFinder.findNearestPolygon(p);
      if (!pointOnLine.isEmpty()) {
        System.out.println(i+" At " + pointOnLine + " is closest to " + p);
        SimpleFeature lastMatched2 = polyFinder.getLastMatched();
        String attribute = (String) lastMatched2.getAttribute("name");
        if(attribute.isEmpty()) {
          attribute = (String) lastMatched2.getAttribute("note");
        }
        if (((Geometry) (lastMatched2.getDefaultGeometry())).contains(p)) {
          System.out.println("is in lake " + attribute);
        } else {
          System.out.println("nearest lake is " + attribute);
        }

      }
    }
  }
Ian Turton
  • 10,018
  • 1
  • 28
  • 47
  • That did the trick - thank you so much! A bonus question... Suppose I have more than one shp-file (they might compliment each other with data, or be of different areas), what might be the best approach? - Gathering all features in one source (it seems to get a lot slower as the amount of features rise). - Or just for-looping through each source, and then compare the results from each source (skipping the ones where index.getBounds() doesn't contain the point)? Any thoughts on what might be faster? Again, thanks a lot :-) – Mars May 19 '17 at 05:27
  • if you can exclude a whole file by checking it's bounds then that will be fastest. With the index it shouldn't get much slower as the file size increases. if you have large amounts of data then using a database like postgis will be quickest – Ian Turton May 19 '17 at 08:15
  • Another, related, question: I'm reading 3 shapefiles (for now) to check my points against. 2 of them works perfectly fine, but the last one (containing coastlines of Europe) is in some different coordinate system. So when I call source.getFeatures().getBounds() to get the boundingbox I want to find out if my point is in, I get something like ReferencedEnvelope[943609.7539000002 : 7601958.499999998, -375446.03119999985 : 6825119.3209000025], and not LatLong coordinates as I was expecting. How do I transform it to something I can use? – Mars May 29 '17 at 06:30
  • you should ask new questions rather than using the comments on an existing question - http://docs.geotools.org/latest/tutorials/geometry/geometrycrs.html – Ian Turton May 29 '17 at 10:09
  • Label points are where to place labels on the map, you want just land. – Ian Turton Jul 17 '17 at 08:30
  • @iant:Hi, can I ask you what if `index.getBounds().isEmpty()` is true?.Can we define `MAX_SEARCH_DISTANCE = Double.MAX_VALUE`?Is this ok? – George Sep 15 '17 at 13:45
  • that would mean that no point was found, or that candidates was empty – Ian Turton Sep 15 '17 at 13:57
  • Hi can someone tell me the version details and class imports, I am having issues with diff. versions and between libs i.e jts & geotools – Niraj Sanghani Jul 30 '19 at 04:26
  • see https://docs.geotools.org/stable/userguide/welcome/upgrade.html for details of what changes from version to version – Ian Turton Jul 30 '19 at 07:39