Search code examples
javaoracle-spatial

How to determine if a point is inside a polygon using Oracle Spatial Java API?


First I tried:

public static boolean isInPolygon(java.awt.geom.Point2D point) {
        // ...
        // ... build prepared statement
        ResultSet rs = statement.executeQuery();
        STRUCT geoStruct = (STRUCT) rs.getObject("SDO_GEOMETRY_COLUMN");

        JGeometry geo = JGeometry.load(geoStruct);
        Shape shape = geo.createShape();
        return shape.contains(point);           
    }

This did not work, as JGeometry.createShape() returns a java.awt.geom.GeneralPath object, which does not have double precision, therefore shape.contains(...) returned wrong results around the edges of the polygon.

Then I tried using JGeometry.createDoubleShape() which returns a oracle.spatial.util.SDODoubleGeneralPath object instead:

// ...
JGeometry geo = JGeometry.load(geoStruct);
Shape shape = geo.createDoubleShape();
return shape.contains(point);

When the contains method is called, I get the following error:

Exception in thread "main" java.lang.NoSuchMethodError: sun.awt.geom.Curve.crossingsForPath(Ljava/awt/geom/PathIterator;DD)I
    at oracle.spatial.util.SDODoubleGeneralPath.contains(SDODoubleGeneralPath.java:498)
    at igea_html.test.Test.handleGeo(Test.java:258)
    at igea_html.test.Test.jgeometryTest(Test.java:204)
    at igea_html.test.Main.main(Main.java:74)

Apparently SDODoubleGeneralPath has some implementation issues if its contains method throws such an error.

Is there an alternative?

I would like to avoid implementing myself an algorithm for determining if a point is inside a polygon, I'd rather trust something already implemented from a reputable source. I also cannot use the Oracle Spatial functions directly in the database due to a license restriction.

Obs.: My isInPolygon method uses Point2D as an argument, but it is ok if I have to use a point in the form of a JGeometry object in case the solution requires it.

Further details as requested:
Oracle version is 11g and I'm working on a web app that is supposed to validate geometrical data in the database. In particular, one of the validations involve checking if certain points and polygons are inside other polygons. The documentation I'm referencing is this: https://docs.oracle.com/cd/E18283_01/appdev.112/e11829/toc.htm
The architecture of those features is still a work in progress so the decision if this should be made by the web app itself or inside the database or some other method is still open. The second option was thrown aside due to license restrictions, the required functions, i.e. SDO_RELATE, are exclusive to Oracle Spatial in Oracle 11 apparently in contrast to Oracle 12 where they are available with just Oracle Locator.
EDIT: This is incorrect, as indicated by @Albert Godfrind below. SDO_RELATE and other spatial operators are supported by Oracle Locator 11 as indicated in Oracle's documentation, both in release 1 and 2:
https://docs.oracle.com/cd/B28359_01/appdev.111/b28400/sdo_locator.htm#CFACCEEG
https://docs.oracle.com/cd/E11882_01/appdev.112/e11830/sdo_locator.htm#CFACCEEG

Also, the JGeometry class of the Java API for Oracle 11 does not contain methods such as isInside() or anyInteract() like it does on its Oracle 12 counterpart. Oracle 12 Java API docs: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/spajv/index.html?oracle/spatial/geometry/JGeometry.html


Solution

  • You say

    I also cannot use the Oracle Spatial functions directly in the database due to a license restriction.

    That is not correct. All vector processing functions for 2D come for free with your database licenses. It is called Oracle Locator. See appendix B of the users guide for the fine print details of what Locator covers (https://docs.oracle.com/en/database/oracle/oracle-database/18/spatl/oracle-locator.html#GUID-EC6DEA23-8FD7-4109-A0C1-93C0CE3D6FF2). Note that this is for 12.2. Older versions were more restrictive, but all come with the basic ability to do the geometry filtering you need.

    The Oracle Spatial & Graph license complements the basic functions with support for things like 3D, rasters, networks, geocoding, visualization as well as semantic and property graphs.

    Other than that, you can freely use the JAVA API (it comes with Oracle Locator). Note however that it exclusively works on JGeometry objects. In particular method isInside() will tell you whether one JGeometry object (your point) is fully contained inside another JGeometry object (your polygon). If you want points falling on the boundary of the polygon to also be returned, then use the anyInteract() method instead.

    However if your main processing is in the database, then just use plain SQL with the SDO_INSIDE (or SDO_ANYINTERACT predicate to find all points that fall inside a polygon.

    Can you say more about your actual use case ?

    EDIT

    Here is an example of using the SQL API. This is valid for all database releases, all the way back to 11gR1 and is covered by Oracle Locator (i.e. no need for any Spatial license).

    In this example, I am using the following two tables:

    A table containing the location of major US cities:

    Name                                     Null?    Type
    ---------------------------------------- -------- ----------------------------
    ID                                       NOT NULL NUMBER
    CITY                                              VARCHAR2(42 CHAR)
    POP90                                             NUMBER
    RANK90                                            NUMBER
    LOCATION                                          MDSYS.SDO_GEOMETRY
    

    A table containing the shape of US states:

    Name                                     Null?    Type
    ---------------------------------------- -------- ----------------------------
    ID                                       NOT NULL NUMBER
    STATE                                             VARCHAR2(26 CHAR)
    STATE_ABRV                                        VARCHAR2(2 CHAR)
    FIPSST                                            VARCHAR2(2 CHAR)
    LANDSQMI                                          NUMBER
    POPPSQMI                                          NUMBER
    GEOM                                              MDSYS.SDO_GEOMETRY
    

    The following query returns all cities in New York state:

    select c.city, c.pop90
    from us_cities c, us_states s
    where s.state = 'New York'
    and sdo_inside (c.location, s.geom) = 'TRUE';
    

    which returns:

    CITY                                POP90
    ------------------------------ ----------
    New York                          7322564
    Buffalo                            328123
    Rochester                          231636
    Yonkers                            188082
    Syracuse                           163860
    Albany                             101082
    
    6 rows selected.
    

    All you need is to submit this statement via JDBC and fetch the results. If you want to also return the location of the points (to highlight them on a map) then also include the location column. This you can interpret in your Java code via the Spatial Java API. Or you could also extract the coordinates from the points:

    select c.city, c.pop90, c.location.sdo_point.x longitude, c.location.sdo_point.y latitude
    from us_cities c, us_states s
    where s.state = 'New York'
    and sdo_inside (c.location, s.geom) = 'TRUE';
    
    CITY                                POP90  LONGITUDE   LATITUDE
    ------------------------------ ---------- ---------- ----------
    New York                          7322564 -73.943849    40.6698
    Buffalo                            328123 -78.859684    42.8898
    Rochester                          231636 -77.615838  43.168651
    Yonkers                            188082 -73.867514  40.947033
    Syracuse                           163860 -76.144067  43.041059
    Albany                             101082 -73.799017   42.66575
    
    6 rows selected.