From 282ddac7cd45267e9efdccdf03b15e89843aa8ed Mon Sep 17 00:00:00 2001 From: Jared Erickson Date: Tue, 16 Jun 2020 17:16:11 -0700 Subject: [PATCH] Add spatial index module --- doc/api/index.rst | 1 + doc/api/index/quadtree.rst | 57 ++++++++++++++++ doc/api/index/strtree.rst | 42 ++++++++++++ .../java/org/geoscript/js/geom/Geometry.java | 14 ++++ .../java/org/geoscript/js/index/Module.java | 51 ++++++++++++++ .../java/org/geoscript/js/index/Quadtree.java | 66 +++++++++++++++++++ .../java/org/geoscript/js/index/STRtree.java | 57 ++++++++++++++++ .../org/geoscript/js/index/SpatialIndex.java | 18 +++++ .../org/geoscript/js/proj/Projection.java | 1 + .../org/geoscript/js/lib/geoscript.js | 1 + .../org/geoscript/js/lib/geoscript/index.js | 30 +++++++++ .../js/tests/geoscript/test_index.js | 37 +++++++++++ .../org/geoscript/js/tests/test_geoscript.js | 1 + 13 files changed, 376 insertions(+) create mode 100644 doc/api/index/quadtree.rst create mode 100644 doc/api/index/strtree.rst create mode 100644 src/main/java/org/geoscript/js/index/Module.java create mode 100644 src/main/java/org/geoscript/js/index/Quadtree.java create mode 100644 src/main/java/org/geoscript/js/index/STRtree.java create mode 100644 src/main/java/org/geoscript/js/index/SpatialIndex.java create mode 100644 src/main/resources/org/geoscript/js/lib/geoscript/index.js create mode 100644 src/test/resources/org/geoscript/js/tests/geoscript/test_index.js diff --git a/doc/api/index.rst b/doc/api/index.rst index 920597a9..3f828c70 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -16,6 +16,7 @@ Modules feature filter proj + index/* layer workspace style diff --git a/doc/api/index/quadtree.rst b/doc/api/index/quadtree.rst new file mode 100644 index 00000000..e8e5697a --- /dev/null +++ b/doc/api/index/quadtree.rst @@ -0,0 +1,57 @@ +:class:`index.Quadtree` +========================== + +.. class:: index.Quadtree() + + Create a Quadtree Spatial Index. + + +Properties +---------- + +.. attribute:: size + + ``Int`` + The number of items in the spatial index. + + +Methods +------- + +.. function:: Quadtree.query + + :arg bounds: :class:`geom.Bounds` The Bounds. + :returns: :class:`Array` + + Query the spatial index by Bounds. + +.. function:: Quadtree.queryAll + + :returns: :class:`Array` + + Get all item in the spatial index. + +.. function:: Quadtree.insert + + :arg bounds: :class:`geom.Bounds` The Bounds. + :arg item: :class:`Object` The value. + :returns: :class:`boolean` Whether an item was removed or not + + Remove an item from the spatial index + +.. function:: Quadtree.remove + + :arg bounds: :class:`geom.Bounds` The Bounds. + :arg item: :class:`Object` The value. + :returns: :class:`boolean` Whether an item was removed or not + + Remove an item from the spatial index + + Get all item in the spatial index. + + + + + + + diff --git a/doc/api/index/strtree.rst b/doc/api/index/strtree.rst new file mode 100644 index 00000000..d009ef79 --- /dev/null +++ b/doc/api/index/strtree.rst @@ -0,0 +1,42 @@ +:class:`index.STRtree` +========================== + +.. class:: index.STRtree() + + Create a STRtree Spatial Index. + + +Properties +---------- + +.. attribute:: size + + ``Int`` + The number of items in the spatial index. + + +Methods +------- + +.. function:: STRtree.query + + :arg bounds: :class:`geom.Bounds` The Bounds. + :returns: :class:`Array` + + Query the spatial index by Bounds. + +.. function:: STRtree.insert + + :arg bounds: :class:`geom.Bounds` The Bounds. + :arg item: :class:`Object` The value. + :returns: :class:`boolean` Whether an item was removed or not + + Remove an item from the spatial index + + + + + + + + diff --git a/src/main/java/org/geoscript/js/geom/Geometry.java b/src/main/java/org/geoscript/js/geom/Geometry.java index f2815b9b..61bb04db 100644 --- a/src/main/java/org/geoscript/js/geom/Geometry.java +++ b/src/main/java/org/geoscript/js/geom/Geometry.java @@ -4,6 +4,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.Objects; import org.locationtech.jts.densify.Densifier; import org.geoscript.js.GeoObject; @@ -646,4 +647,17 @@ public String toFullString() { return arrayRepr(getCoordinates()); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Geometry geometry1 = (Geometry) o; + return Objects.equals(geometry, geometry1.geometry) && + Objects.equals(projection, geometry1.projection); + } + + @Override + public int hashCode() { + return Objects.hash(geometry, projection); + } } diff --git a/src/main/java/org/geoscript/js/index/Module.java b/src/main/java/org/geoscript/js/index/Module.java new file mode 100644 index 00000000..67da3507 --- /dev/null +++ b/src/main/java/org/geoscript/js/index/Module.java @@ -0,0 +1,51 @@ +package org.geoscript.js.index; + +import org.geoscript.js.GeoObject; +import org.geoscript.js.index.*; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class Module { + + static HashMap prototypes; + + /** + * Define all geometry constructors in the given module scope. If the + * provided scope is not a "top level" scope, constructors will be defined + * in the top level scope for the given scope. + * @param scope + * @throws IllegalAccessException + * @throws InstantiationException + * @throws InvocationTargetException + */ + public static void init(Scriptable scope) throws IllegalAccessException, InstantiationException, InvocationTargetException { + + scope = ScriptableObject.getTopLevelScope(scope); + + @SuppressWarnings("unchecked") + List> classes = Arrays.asList(Quadtree.class, STRtree.class); + + prototypes = new HashMap(); + for (Class cls : classes) { + String name = ScriptableObject.defineClass(scope, cls, false, true); + Scriptable prototype = ScriptableObject.getClassPrototype(scope, name); + prototypes.put(name, prototype); + } + + } + + protected static Scriptable getClassPrototype(Class cls) { + String name = cls.getName(); + if (prototypes == null || !prototypes.containsKey(name)) { + throw new RuntimeException( + "Attempt to access prototype before requiring module: " + name); + } + return prototypes.get(name); + } + +} diff --git a/src/main/java/org/geoscript/js/index/Quadtree.java b/src/main/java/org/geoscript/js/index/Quadtree.java new file mode 100644 index 00000000..14335991 --- /dev/null +++ b/src/main/java/org/geoscript/js/index/Quadtree.java @@ -0,0 +1,66 @@ +package org.geoscript.js.index; + +import org.geoscript.js.geom.Bounds; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.annotations.JSConstructor; +import org.mozilla.javascript.annotations.JSFunction; +import org.mozilla.javascript.annotations.JSGetter; + +public class Quadtree extends SpatialIndex { + + public Quadtree() { + super(new org.locationtech.jts.index.quadtree.Quadtree()); + } + + public Quadtree(Scriptable scope) { + this(); + this.setParentScope(scope); + this.setPrototype(Module.getClassPrototype(Quadtree.class)); + } + + + @JSFunction + public void insert(Bounds bounds, Object item) { + this.index.insert(bounds.unwrap(), item); + } + + @JSFunction + public Object query(Bounds bounds) { + return javaToJS(this.index.query(bounds.unwrap()), getParentScope()); + } + + @JSGetter + public int getSize() { + return ((org.locationtech.jts.index.quadtree.Quadtree)this.index).size(); + } + + @JSFunction + public Object queryAll() { + return javaToJS(((org.locationtech.jts.index.quadtree.Quadtree)this.index).queryAll(), getParentScope()); + } + + @JSFunction + public boolean remove(Bounds bounds, Object item) { + return this.index.remove(bounds.unwrap(), item); + } + + /** + * JavaScript constructor. + * @param cx + * @param args + * @param ctorObj + * @param isNewExpr + * @return + */ + @JSConstructor + public static Object constructor(Context cx, Object[] args, Function ctorObj, boolean isNewExpr) { + if (isNewExpr) { + return new Quadtree(); + } else { + return new Quadtree(ctorObj.getParentScope()); + } + } + +} diff --git a/src/main/java/org/geoscript/js/index/STRtree.java b/src/main/java/org/geoscript/js/index/STRtree.java new file mode 100644 index 00000000..dbf4c432 --- /dev/null +++ b/src/main/java/org/geoscript/js/index/STRtree.java @@ -0,0 +1,57 @@ +package org.geoscript.js.index; + +import org.geoscript.js.geom.Bounds; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.annotations.JSConstructor; +import org.mozilla.javascript.annotations.JSFunction; +import org.mozilla.javascript.annotations.JSGetter; + +import java.util.List; + +public class STRtree extends SpatialIndex { + + public STRtree() { + super(new org.locationtech.jts.index.strtree.STRtree()); + } + + public STRtree(Scriptable scope) { + this(); + this.setParentScope(scope); + this.setPrototype(Module.getClassPrototype(STRtree.class)); + } + + @JSFunction + public void insert(Bounds bounds, Object item) { + this.index.insert(bounds.unwrap(), item); + } + + @JSFunction + public List query(Bounds bounds) { + return this.index.query(bounds.unwrap()); + } + + @JSGetter + public int getSize() { + return ((org.locationtech.jts.index.strtree.STRtree)this.index).size(); + } + + /** + * JavaScript constructor. + * @param cx + * @param args + * @param ctorObj + * @param isNewExpr + * @return + */ + @JSConstructor + public static Object constructor(Context cx, Object[] args, Function ctorObj, boolean isNewExpr) { + if (isNewExpr) { + return new STRtree(); + } else { + return new STRtree(ctorObj.getParentScope()); + } + } + +} diff --git a/src/main/java/org/geoscript/js/index/SpatialIndex.java b/src/main/java/org/geoscript/js/index/SpatialIndex.java new file mode 100644 index 00000000..c20b409f --- /dev/null +++ b/src/main/java/org/geoscript/js/index/SpatialIndex.java @@ -0,0 +1,18 @@ +package org.geoscript.js.index; + +import org.geoscript.js.GeoObject; +import org.mozilla.javascript.Wrapper; + +public abstract class SpatialIndex extends GeoObject implements Wrapper { + + protected final org.locationtech.jts.index.SpatialIndex index; + + public SpatialIndex(org.locationtech.jts.index.SpatialIndex index) { + this.index = index; + } + + @Override + public Object unwrap() { + return index; + } +} diff --git a/src/main/java/org/geoscript/js/proj/Projection.java b/src/main/java/org/geoscript/js/proj/Projection.java index f49d44b4..87371dae 100644 --- a/src/main/java/org/geoscript/js/proj/Projection.java +++ b/src/main/java/org/geoscript/js/proj/Projection.java @@ -1,5 +1,6 @@ package org.geoscript.js.proj; +import java.util.Objects; import java.util.logging.Logger; import org.geoscript.js.GeoObject; diff --git a/src/main/resources/org/geoscript/js/lib/geoscript.js b/src/main/resources/org/geoscript/js/lib/geoscript.js index f532b370..e6d3c33a 100644 --- a/src/main/resources/org/geoscript/js/lib/geoscript.js +++ b/src/main/resources/org/geoscript/js/lib/geoscript.js @@ -7,3 +7,4 @@ exports.workspace = require("./geoscript/workspace"); exports.viewer = require("./geoscript/viewer"); exports.style = require("./geoscript/style"); exports.map = require("./geoscript/map"); +exports.index = require("./geoscript/index"); diff --git a/src/main/resources/org/geoscript/js/lib/geoscript/index.js b/src/main/resources/org/geoscript/js/lib/geoscript/index.js new file mode 100644 index 00000000..c9f1fcf1 --- /dev/null +++ b/src/main/resources/org/geoscript/js/lib/geoscript/index.js @@ -0,0 +1,30 @@ +var Util = require("./util"); +var Registry = require("./registry").Registry; +var Factory = require("./factory").Factory; + +Packages.org.geoscript.js.index.Module.init(this); +exports.QuadTree = this["org.geoscript.js.index.Quadtree"]; +exports.STRtree = this["org.geoscript.js.index.STRtree"]; + +/** private: method[create] + * :arg config: ``Object`` Configuration object. + * :returns: :class:`index.QuadTree` + * + * Create a map given a configuration object. + */ +var registry = new Registry(); +exports.create = registry.create; + +// register a QuadTree factory for the module +registry.register(new Factory(exports.QuadTree, { + handles: function(config) { + return true; + } +})); + +// register a STRtree factory for the module +registry.register(new Factory(exports.STRtree, { + handles: function(config) { + return true; + } +})); \ No newline at end of file diff --git a/src/test/resources/org/geoscript/js/tests/geoscript/test_index.js b/src/test/resources/org/geoscript/js/tests/geoscript/test_index.js new file mode 100644 index 00000000..154a844e --- /dev/null +++ b/src/test/resources/org/geoscript/js/tests/geoscript/test_index.js @@ -0,0 +1,37 @@ +var assert = require("assert"); +var index = require("geoscript/index"); +var geom = require("geoscript/geom"); + +exports["test: quadtree index"] = function() { + + var quadtree = new index.QuadTree(); + quadtree.insert(new geom.Bounds([0,0,10,10]), new geom.Point([5,5])); + quadtree.insert(new geom.Bounds([2,2,6,6]), new geom.Point([4,4])); + quadtree.insert(new geom.Bounds([20,20,60,60]), new geom.Point([30,30])); + quadtree.insert(new geom.Bounds([22,22,44,44]), new geom.Point([32,32])); + assert.strictEqual(4, quadtree.size, "QuadTree index should have 4 entries"); + + var results = quadtree.query(new geom.Bounds([1,1,5,5])); + assert.strictEqual(4, results.length); + + var allResults = quadtree.queryAll(); + assert.strictEqual(4, allResults.length); + + var isRemoved = quadtree.remove(new geom.Bounds([22,22,44,44]), new geom.Point([32,32])); + assert.ok(isRemoved) + + allResults = quadtree.queryAll() + assert.strictEqual(3, allResults.length); + +}; + +exports["test: strtree index"] = function() { + + var strtree = new index.STRtree(); + strtree.insert(new geom.Bounds([0,0,10,10]), new geom.Point([5,5])); + strtree.insert(new geom.Bounds([2,2,6,6]), new geom.Point([4,4])); + strtree.insert(new geom.Bounds([20,20,60,60]), new geom.Point([30,30])); + strtree.insert(new geom.Bounds([22,22,44,44]), new geom.Point([32,32])); + assert.strictEqual(4, strtree.size, "QuadTree index should have 4 entries"); + +}; \ No newline at end of file diff --git a/src/test/resources/org/geoscript/js/tests/test_geoscript.js b/src/test/resources/org/geoscript/js/tests/test_geoscript.js index 259ecb53..0002cbe8 100644 --- a/src/test/resources/org/geoscript/js/tests/test_geoscript.js +++ b/src/test/resources/org/geoscript/js/tests/test_geoscript.js @@ -8,6 +8,7 @@ exports["test: workspace"] = require("./geoscript/test_workspace"); exports["test: style"] = require("./geoscript/test_style"); exports["test: map"] = require("./geoscript/test_map"); exports["test: util"] = require("./geoscript/test_util"); +exports["test: index"] = require("./geoscript/test_index"); if (require.main == module.id) { system.exit(require("test").run(exports));