├── LICENSE.txt
├── README.markdown
├── examples
├── rangequery-pig
├── rangequery-pigeon
├── rangequery-ways
├── trajectory.pig
└── trajectory.tsv
├── pom.xml
└── src
├── main
├── assembly
│ └── assembly.xml
├── java
│ └── edu
│ │ └── umn
│ │ └── cs
│ │ └── pigeon
│ │ ├── Area.java
│ │ ├── AsHex.java
│ │ ├── AsText.java
│ │ ├── Boundary.java
│ │ ├── Break.java
│ │ ├── Buffer.java
│ │ ├── Connect.java
│ │ ├── Contains.java
│ │ ├── ConvexHull.java
│ │ ├── Crosses.java
│ │ ├── Decompose.java
│ │ ├── Difference.java
│ │ ├── ESRIGeometryParser.java
│ │ ├── ESRIShapeFromText.java
│ │ ├── ESRIShapeFromWKB.java
│ │ ├── Envelope.java
│ │ ├── Extent.java
│ │ ├── GeoException.java
│ │ ├── GeometryFromText.java
│ │ ├── GeometryFromWKB.java
│ │ ├── GridCell.java
│ │ ├── GridPartition.java
│ │ ├── Intersection.java
│ │ ├── Intersects.java
│ │ ├── IsEmpty.java
│ │ ├── IsValid.java
│ │ ├── JTSGeometryParser.java
│ │ ├── MakeBox.java
│ │ ├── MakeLine.java
│ │ ├── MakeLinePolygon.java
│ │ ├── MakePoint.java
│ │ ├── MakePolygon.java
│ │ ├── MakeSegments.java
│ │ ├── NumPoints.java
│ │ ├── OGCGeometryToESRIShapeParser.java
│ │ ├── Overlaps.java
│ │ ├── SJPlaneSweep.java
│ │ ├── Touches.java
│ │ ├── Union.java
│ │ ├── Within.java
│ │ ├── XMax.java
│ │ ├── XMin.java
│ │ ├── YMax.java
│ │ └── YMin.java
└── resources
│ ├── pigeon_import.pig
│ └── sample.pig
└── test
└── java
└── edu
└── umn
└── cs
└── pigeon
├── TestArea.java
├── TestAsText.java
├── TestBounds.java
├── TestBreak.java
├── TestBuffer.java
├── TestConnect.java
├── TestConvexHull.java
├── TestCrosses.java
├── TestDifference.java
├── TestEnvelope.java
├── TestExtent.java
├── TestGeometryParser.java
├── TestGridPartition.java
├── TestHelper.java
├── TestMakeBox.java
├── TestMakeLine.java
├── TestMakeLinePolygon.java
├── TestMakePoint.java
├── TestMakePolygon.java
├── TestMakeSegments.java
├── TestNumPoints.java
├── TestUnion.java
└── TestXMin.java
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | About This Content
2 | 2015
3 |
4 | License
5 | The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Apache License, Version 2.0. A copy of the Apache License, Version 2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
6 |
7 | If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the Apache License, Version 2.0 still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.
8 |
9 |
10 | Third Party Content
11 | The Content includes items that have been sourced from third parties as set out below. If you did not receive this Content directly from the Eclipse Foundation, the following is provided for informational purposes only, and you should look to the Redistributor’s license for terms and conditions of use.
12 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Pigeon
2 | ======
3 |
4 | Pigeon is a spatial extension to Pig that allows it to process spatial data.
5 | All functionalities in Pigeon are introduced as user-defined functions (UDFS)
6 | which makes it unobtrusive and allows it to work with your existing systems.
7 | All the spatial functionality is supported by [ESRI Geometry API]
8 | (https://github.com/Esri/geometry-api-java)
9 | a native Java open source library for spatial functionality licensed under
10 | [Apache Public License](http://www.apache.org/licenses/LICENSE-2.0.html).
11 |
12 |
13 | Our target is to have something like [PostGIS](http://postgis.net/) but for Pig
14 | instead of PostgreSQL. We use the same function names to make it easier for
15 | existing users to use Pigeon. Here is an example the computes the union of all
16 | ZIP codes in each city.
17 |
18 | zip_codes = LOAD 'zips' AS (zip, city, geom);
19 | zip_by_city = GROUP zip_codes BY city;
20 | zip_union = FOREACH zip_by_city
21 | GENERATE group AS city, ST_Union(geom);
22 |
23 |
24 | Data types
25 | ==========
26 | Currently, Pig does not support the creation of custom data types. This is not
27 | the best thing for Pigeon because we wanted to have our own data type (Geometry) similar to
28 | PostGIS. As a work around, we use the more generic type `bytearray` as our main
29 | data type. All conversions happen from `bytearray` to `Geometry` and vice-verse on
30 | the fly while the function is executed. If a function expects an input of type
31 | Geometry, it receives a `bytearray` and converts it `Geometry`. If the output is
32 | of type `Geometry`, it computes the output, converts it to `bytearray`, and returns
33 | that `bytearray` instead. This is a little bit cumbersome, but the Pig team is
34 | able to add custom data types so that we have a cleaner extension.
35 |
36 |
37 | How to compile
38 | ==============
39 | Pigeon requires ESRI Geometry API to compile. You need to
40 | download a recent version of the library and add it to the classpath of your Java compiler
41 | to be able to compile the code. Of course you also need Pig classes to be available
42 | in the classpath. The current code is tested against ESRI Geometry API 1.0 and Pig 0.11.1.
43 | Currently you have to do the compilation manually but we are planning to create
44 | an ANT build file to automate the compilation. Once you compile the code, you
45 | can create a jar file out of it and [REGISTER](http://pig.apache.org/docs/r0.11.1/basic.html#register) it in your Pig scripts.
46 |
47 |
48 | How to use
49 | ==========
50 | To use Pigeon in your Pig scripts, you need to [REGISTER](http://pig.apache.org/docs/r0.11.1/basic.html#register) the jar file in your Pig
51 | script. Then you can use the spatial functionality in your script as you cdo with
52 | normal functionality. Here are some simple examples on how to use Pigeon.
53 |
54 | Let's say you have a trajectory in the form (latitude, longitude, timestamp). We need to
55 | for a Linestring out of this trajectory when points in this linestring are sorted
56 | by timestamp.
57 |
58 |
59 | points = LOAD 'trajectory.tsv' AS (time: datetime, lat:double, lon:double);
60 | s_points = FOREACH points GENERATE ST_MakePoint(lat, lon) AS point, time;
61 | points_by_time = ORDER s_points BY time;
62 | points_grouped = GROUP points_by_time ALL;
63 | lines = FOREACH points_grouped GENERATE ST_AsText(ST_MakeLine(points_by_time));
64 | STORE lines INTO 'line';
65 |
66 |
67 | Supported functions
68 | ===================
69 | Here is a list of all functions that are currently supported.
70 |
71 | Basic Spatial Functions
72 | -----------------------
73 |
74 | + *ST_AsHex* Converts a shape to its Well-Known Binary (WKB) format encoded as Hex string
75 | + *ST_AsText* Converts a shape to its Well-Known Text (WKT) format
76 | + *ST_GeomFromText* Parses a WKT representation into an object
77 | + *ST_GeomFromWKT* Parses an object from a hex-encoded WKB representation
78 | + *ST_MakePoint* Creates a geometry point given two numeric coordinates
79 | + *ST_MakeBox* Creates a rectangle from its four coordinates (x1, y1, x2, y2)
80 | + *ST_Area* Calculates the area of a surface shape (e.g., Polygon)
81 | + *ST_Envelope* Calculates the envelope (MBR) of a shape
82 | + *ST_Buffer* Computes a buffer with the specified distance around a geometry.
83 | + *ST_NumPoints* Returns number of points in a linestring
84 | + *ST_XMin* Returns the minimum x-coordinate of an object
85 | + *ST_YMin* Returns the minimum y-coordinate of an object
86 | + *ST_XMax* Returns the maximum x-coordinate of an object
87 | + *ST_YMax* Returns the maximum y-coordinate of an object
88 |
89 |
90 | Spatial Predicates
91 | ------------------
92 |
93 | + *ST_Crosses* Checks if one polygon crosses another polygon
94 | + *ST_IsEmpty* Tests whether a shape is empty or not.
95 | + *ST_Contains* Tests for containment between two geometries
96 | + *ST_Intersects* Tests if two objects intersect
97 | + *ST_Overlaps* Tests if two objects overlap
98 | + *ST_Touches* Tests if two objects touch
99 | + *ST_Within* Tests if one geometry is withing another geometry
100 |
101 | Spatial Analysis
102 | ----------------
103 |
104 | + *ST_Buffer* Computes a buffer with the specified distance around a geometry.
105 | + *ST_ConvexHull* Computes the minimal convex polygon of a shape.
106 | + *ST_Break* Breaks a geometry into straight line segments
107 | + *ST_Difference* Calculates the difference between two polygons.
108 | + *ST_Intersection* Computes the intersection betwen two polygons.
109 | + *ST_MakeSegments* Creates a set of line segments given a set of points by connecting each two consecutive points
110 |
111 | Aggregate functions
112 | -------------------
113 |
114 | + *ST_MakeLine* Creates a line string given a bag of points
115 | + *ST_MakePolygon* Creates a polygon given a circular list of points
116 | + *ST_MakeLinePolygon* Creates a line or polygons from a list of points depending on whether the last point is the same as the first point or not
117 | + *ST_ConvexHull* Computes the convex hull from a bag of shapes
118 | + *ST_Union* Computes the spatial union of a set of surfaces (e.g., Polygons)
119 | + *ST_Extent* Computes the minimal bounding rectangle (MBR) of a set of shapes
120 | + *ST_Connect* Connects a set of linestrings together to form longer linestring or polygons
121 |
122 | Contribution
123 | ============
124 | Pigeon is open source and licensed under the Apache open source license. Your
125 | contribution is highly welcome and appreciated. Here is a simple guideline of
126 | how to contribute.
127 |
128 | 1. Clone your own copy of the source code or fork the project in github.
129 | 2. Pick an issue from the list of issues associated with the project.
130 | 3. Write a test case for the new functionality and make sure it fails.
131 | 4. Fix the code so that the test case succeeds.
132 | 5. Make sure that all existing tests still pass.
133 | 6. Revise all your changes and add comments whenever needed. Make sure you
134 | don't make any unnecessary changes (e.g., reformatting).
135 | 7. Submit a pull request if you are a github user or send a patch if you aren't.
136 | 8. We will revise the submitted patch and merge it with the code when done.
137 |
138 | When writing the test, keep in mind that we are not testing Pig or ESRI Geometry API. We are
139 | testing Pigeon which is a wrapper around ESRI Geometry API. For example, you don't have to test
140 | all the special cases of polygons if implementing an intersection function.
141 | All what we want is to make sure that you call the right function in ESRI Geometry API and
142 | return the output in the correct format.
143 |
144 | Mainly, we need to support as many as possible of the
145 | [functions](http://postgis.net/docs/manual-1.4/ch08.html) provided by PostGIS.
146 |
--------------------------------------------------------------------------------
/examples/rangequery-pig:
--------------------------------------------------------------------------------
1 | REGISTER osmx.jar;
2 | REGISTER pigeon.jar
3 | REGISTER esri-geometry-api-1.0.jar;
4 |
5 | IMPORT 'pigeon_import.pig';
6 |
7 | points = LOAD 'points' AS (id:long, lon:double, lat:double);
8 |
9 | results = FILTER points BY
10 | lon < -93.158 AND lon > -93.175 AND
11 | lat > 45.0077 AND lat < 45.0164;
12 |
13 | STORE results INTO 'results';
--------------------------------------------------------------------------------
/examples/rangequery-pigeon:
--------------------------------------------------------------------------------
1 | REGISTER osmx.jar;
2 | REGISTER pigeon.jar
3 | REGISTER esri-geometry-api-1.0.jar;
4 |
5 | IMPORT 'pigeon_import.pig';
6 |
7 | points = LOAD 'points-pigeon' AS (id:long, location);
8 |
9 | results = FILTER points BY
10 | ST_Contains(ST_MakeBox(-93.175, 45.0077, -93.158, 45.0164), location);
11 |
12 | STORE results INTO 'results-pigeon';
--------------------------------------------------------------------------------
/examples/rangequery-ways:
--------------------------------------------------------------------------------
1 | REGISTER osmx.jar;
2 | REGISTER pigeon.jar
3 | REGISTER esri-geometry-api-1.0.jar;
4 |
5 | IMPORT 'pigeon_import.pig';
6 |
7 | points = LOAD 'points-pigeon' AS (id:long, shape);
8 |
9 | results = FILTER points BY
10 | ST_Overlaps(ST_MakeBox(-93.175, 45.0077, -93.158, 45.0164), shape);
11 |
12 | STORE results INTO 'results-ways';
--------------------------------------------------------------------------------
/examples/trajectory.pig:
--------------------------------------------------------------------------------
1 | REGISTER pigeon.jar;
2 | REGISTER jts-1.8.jar;
3 |
4 | IMPORT 'pigeon_import.pig';
5 |
6 | points = LOAD 'trajectory.tsv' AS (type, time: datetime, lat:double, lon:double);
7 | s_points = FOREACH points GENERATE ST_MakePoint(lat, lon) AS point, time;
8 | points_by_time = ORDER s_points BY time;
9 | points_grouped = GROUP points_by_time ALL;
10 | lines = FOREACH points_grouped GENERATE ST_AsText(ST_MakeLine(points_by_time));
11 | STORE lines INTO 'line';
12 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | edu.umn.cs.spatialHadoop
5 | pigeon
6 | jar
7 | 0.2.2
8 | pigeon
9 | http://maven.apache.org
10 |
11 |
12 |
13 | Apache License, Version 2.0
14 | http://www.apache.org/licenses/LICENSE-2.0.txt
15 |
16 |
17 |
18 |
19 | scm:git:git@github.com:aseldawy/pigeon.git
20 | scm:git:git@github.com:aseldawy/pigeon.git
21 | git@github.com:aseldawy/pigeon.git
22 |
23 |
24 |
25 |
26 | Ahmed Eldawy
27 | eldawy@cs.ucr.edu
28 | University of California, Riverside
29 | http://www.ucr.edu/
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-compiler-plugin
39 | 3.1
40 |
41 | 1.6
42 | 1.6
43 |
44 |
45 |
46 |
47 | maven-assembly-plugin
48 | 2.6
49 |
50 |
51 | src/main/assembly/assembly.xml
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | junit
61 | junit
62 | 4.11
63 | test
64 |
65 |
66 |
67 | org.apache.pig
68 | pigunit
69 | 0.17.0
70 | test
71 |
72 |
73 |
74 |
75 | org.apache.hadoop
76 | hadoop-client
77 | 3.2.0
78 |
79 |
80 |
81 | org.apache.pig
82 | pig
83 | 0.17.0
84 |
85 |
86 |
87 | com.esri.geometry
88 | esri-geometry-api
89 | 2.2.4
90 |
91 |
92 |
93 |
94 | org.locationtech.jts
95 | jts-core
96 | 1.18.1
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/main/assembly/assembly.xml:
--------------------------------------------------------------------------------
1 |
8 |
10 | bin
11 |
12 | tar.gz
13 |
14 |
15 | false
16 |
17 |
18 |
19 |
20 | ${project.basedir}
21 | /
22 |
23 | README*
24 | LICENSE*
25 |
26 |
27 |
28 |
29 | src/main/resources
30 |
31 |
32 |
33 |
34 | ${project.build.directory}
35 |
36 |
37 | ${artifactId}-${version}.jar
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | com.vividsolutions:jts
47 | com.esri.geometry:esri-geometry-api
48 |
49 | false
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Area.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.Geometry;
16 | import com.esri.core.geometry.ogc.OGCGeometry;
17 |
18 |
19 | /**
20 | * A UDF that returns the area of a geometry as calculated by
21 | * {@link Geometry#calculateArea2D()}
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Area extends EvalFunc {
26 |
27 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
28 |
29 | @Override
30 | public Double exec(Tuple input) throws IOException {
31 | OGCGeometry geom = null;
32 | try {
33 | Object v = input.get(0);
34 | geom = geometryParser.parseGeom(v);
35 | return geom.getEsriGeometry().calculateArea2D();
36 | } catch (ExecException ee) {
37 | throw new GeoException(geom, ee);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/AsHex.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.Tuple;
13 |
14 | import com.esri.core.geometry.ogc.OGCGeometry;
15 |
16 | /**
17 | * Returns the Well-Known Binary (WKB) representation of a geometry object
18 | * represented as hex string.
19 | * @author Ahmed Eldawy
20 | *
21 | */
22 | public class AsHex extends EvalFunc {
23 |
24 | private ESRIGeometryParser geometryParser = new ESRIGeometryParser();
25 |
26 | @Override
27 | public String exec(Tuple t) throws IOException {
28 | if (t.size() != 1)
29 | throw new GeoException("AsHex expects one geometry argument");
30 | OGCGeometry geom = geometryParser.parseGeom(t.get(0));
31 | return ESRIGeometryParser.bytesToHex(geom.asBinary().array());
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/AsText.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.Tuple;
13 |
14 | import com.esri.core.geometry.ogc.OGCGeometry;
15 |
16 | /**
17 | * Returns the Well-Known Text (WKT) representation of a geometry object.
18 | * @author Ahmed Eldawy
19 | *
20 | */
21 | public class AsText extends EvalFunc {
22 |
23 | private ESRIGeometryParser geometryParser = new ESRIGeometryParser();
24 |
25 | @Override
26 | public String exec(Tuple t) throws IOException {
27 | if (t.size() != 1)
28 | throw new GeoException("ST_AsText expects one geometry argument");
29 | OGCGeometry geom = geometryParser.parseGeom(t.get(0));
30 | try {
31 | return geom.asText();
32 | } catch (Exception e) {
33 | throw new GeoException(e);
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Boundary.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import org.locationtech.jts.geom.Geometry;
17 | import org.locationtech.jts.io.WKBWriter;
18 |
19 |
20 | /**
21 | * A UDF that returns the outer boundary of a shape
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Boundary extends EvalFunc {
26 |
27 | private final WKBWriter WKB_WRITER = new WKBWriter();
28 | private final JTSGeometryParser GEOMETRY_PARSER = new JTSGeometryParser();
29 |
30 | @Override
31 | public DataByteArray exec(Tuple input) throws IOException {
32 | Geometry geom = null;
33 | try {
34 | Object v = input.get(0);
35 | geom = GEOMETRY_PARSER.parseGeom(v);
36 | Geometry boundary = geom.getBoundary();
37 | return new DataByteArray(WKB_WRITER.write(boundary));
38 | } catch (ExecException e) {
39 | throw new GeoException(geom, e);
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Break.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.Vector;
11 |
12 | import org.apache.pig.EvalFunc;
13 | import org.apache.pig.data.BagFactory;
14 | import org.apache.pig.data.DataBag;
15 | import org.apache.pig.data.DataType;
16 | import org.apache.pig.data.Tuple;
17 | import org.apache.pig.data.TupleFactory;
18 | import org.apache.pig.impl.logicalLayer.schema.Schema;
19 | import org.apache.pig.impl.logicalLayer.schema.Schema.FieldSchema;
20 |
21 | import org.locationtech.jts.geom.Coordinate;
22 | import org.locationtech.jts.geom.Geometry;
23 | import org.locationtech.jts.geom.GeometryCollection;
24 | import org.locationtech.jts.geom.LineString;
25 | import org.locationtech.jts.geom.Point;
26 | import org.locationtech.jts.geom.Polygon;
27 |
28 | /**
29 | * Breaks down a linestring or a polygon into straight line segments.
30 | * The generated segments are returned as a bag of tuples with the common
31 | * schema (Segment_ID, x1, y1, x2, y2)
32 | *
33 | * @author Ahmed Eldawy
34 | */
35 | public class Break extends EvalFunc{
36 |
37 | private JTSGeometryParser geometryParser = new JTSGeometryParser();
38 |
39 | @Override
40 | public DataBag exec(Tuple b) throws IOException {
41 | if (b.size() != 1)
42 | throw new GeoException("Invalid number of arguments. Expected 1 but found "+b.size());
43 | Geometry geom = geometryParser.parseGeom(b.get(0));
44 | Vector segments = new Vector();
45 | breakGeom(geom, segments);
46 | DataBag segmentsBag = BagFactory.getInstance().newDefaultBag();
47 | for (int i = 0; i < segments.size(); i++) {
48 | Tuple segmentTuple = TupleFactory.getInstance().newTuple(5);
49 | segmentTuple.set(0, i);
50 | segmentTuple.set(1, segments.get(i)[0].x);
51 | segmentTuple.set(2, segments.get(i)[0].y);
52 | segmentTuple.set(3, segments.get(i)[1].x);
53 | segmentTuple.set(4, segments.get(i)[1].y);
54 | segmentsBag.add(segmentTuple);
55 | }
56 | return segmentsBag;
57 | }
58 |
59 | private void breakGeom(Geometry geom, Vector segments) {
60 | if (geom == null)
61 | return;
62 | if (geom instanceof LineString) {
63 | LineString linestring = (LineString) geom;
64 | Coordinate[] coordinates = linestring.getCoordinates();
65 | for (int i = 1; i < coordinates.length; i++) {
66 | Coordinate[] segment = new Coordinate[2];
67 | segment[0] = new Coordinate(coordinates[i-1]);
68 | segment[1] = new Coordinate(coordinates[i]);
69 | segments.add(segment);
70 | }
71 | } else if (geom instanceof Polygon) {
72 | Polygon polygon = (Polygon) geom;
73 | breakGeom(polygon.getExteriorRing(), segments);
74 | for (int n = 0; n < polygon.getNumInteriorRing(); n++) {
75 | breakGeom(polygon.getInteriorRingN(n), segments);
76 | }
77 | } else if (geom instanceof GeometryCollection) {
78 | GeometryCollection geomCollection = (GeometryCollection) geom;
79 | for (int n = 0; n < geomCollection.getNumGeometries(); n++) {
80 | breakGeom(geomCollection.getGeometryN(n), segments);
81 | }
82 | } else if (geom instanceof Point) {
83 | // Skip
84 | } else {
85 | throw new RuntimeException("Cannot break geometry of type "+geom.getClass());
86 | }
87 | }
88 |
89 | public Schema outputSchema(Schema input) {
90 | try {
91 | Schema segmentSchema = new Schema();
92 | segmentSchema.add(new Schema.FieldSchema("position", DataType.INTEGER));
93 | segmentSchema.add(new Schema.FieldSchema("x1", DataType.DOUBLE));
94 | segmentSchema.add(new Schema.FieldSchema("y1", DataType.DOUBLE));
95 | segmentSchema.add(new Schema.FieldSchema("x2", DataType.DOUBLE));
96 | segmentSchema.add(new Schema.FieldSchema("y2", DataType.DOUBLE));
97 |
98 | FieldSchema breakSchema = new Schema.FieldSchema("segments", segmentSchema);
99 | breakSchema.type = DataType.BAG;
100 |
101 | return new Schema(breakSchema);
102 | } catch (Exception e) {
103 | return null;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Buffer.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import com.esri.core.geometry.ogc.OGCGeometry;
17 |
18 |
19 | /**
20 | * A UDF that returns the minimal bounding rectangle (MBR) of a shape.
21 | * @author Ahmed Eldawy
22 | *
23 | */
24 | public class Buffer extends EvalFunc {
25 |
26 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
27 |
28 | @Override
29 | public DataByteArray exec(Tuple input) throws IOException {
30 | OGCGeometry geom = null;
31 | try {
32 | Object v = input.get(0);
33 | geom = geometryParser.parseGeom(v);
34 | double dist;
35 | Object distance = input.get(1);
36 | if (distance instanceof Double)
37 | dist = (Double) distance;
38 | else if (distance instanceof Float)
39 | dist = (Float) distance;
40 | else if (distance instanceof Integer)
41 | dist = (Integer) distance;
42 | else if (distance instanceof Long)
43 | dist = (Long) distance;
44 | else if (distance instanceof String)
45 | dist = Double.parseDouble((String) distance);
46 | else if (distance instanceof DataByteArray)
47 | dist = Double.parseDouble(new String(((DataByteArray) distance).get()));
48 | else
49 | throw new GeoException("Invalid second argument in call to Buffer. Expecting Double, Integer or Long");
50 | return new DataByteArray(geom.buffer(dist).asBinary().array());
51 | } catch (ExecException ee) {
52 | throw new GeoException(geom, ee);
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Connect.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.Iterator;
11 | import java.util.Vector;
12 |
13 | import org.apache.pig.EvalFunc;
14 | import org.apache.pig.backend.executionengine.ExecException;
15 | import org.apache.pig.data.DataBag;
16 | import org.apache.pig.data.DataByteArray;
17 | import org.apache.pig.data.Tuple;
18 |
19 | import com.esri.core.geometry.Line;
20 | import com.esri.core.geometry.MultiPath;
21 | import com.esri.core.geometry.Point;
22 | import com.esri.core.geometry.Polygon;
23 | import com.esri.core.geometry.Polyline;
24 | import com.esri.core.geometry.Segment;
25 | import com.esri.core.geometry.SpatialReference;
26 | import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection;
27 | import com.esri.core.geometry.ogc.OGCGeometry;
28 | import com.esri.core.geometry.ogc.OGCGeometryCollection;
29 | import com.esri.core.geometry.ogc.OGCLineString;
30 | import com.esri.core.geometry.ogc.OGCPolygon;
31 |
32 | /**
33 | * Connects together a set of Linestrings to form a longer Linestring or
34 | * a polygon.
35 | * @author Ahmed Eldawy
36 | */
37 | public class Connect extends EvalFunc{
38 |
39 | private ESRIGeometryParser geometryParser = new ESRIGeometryParser();
40 |
41 | @Override
42 | public DataByteArray exec(Tuple b) throws IOException {
43 | try {
44 | // Read information from input
45 | Iterator firstPointIdIter = ((DataBag) b.get(0)).iterator();
46 | Iterator lastPointIdIter = ((DataBag) b.get(1)).iterator();
47 | Iterator shapesIter = ((DataBag) b.get(2)).iterator();
48 |
49 | // Shapes that are created after connected line segments
50 | Vector createdShapes = new Vector();
51 | Vector linestrings = new Vector();
52 | Vector firstPointId = new Vector();
53 | Vector lastPointId = new Vector();
54 |
55 | while (firstPointIdIter.hasNext() && lastPointIdIter.hasNext() &&
56 | shapesIter.hasNext()) {
57 | OGCGeometry geom = geometryParser.parseGeom(shapesIter.next().get(0));
58 | long first_point_id = (Long) firstPointIdIter.next().get(0);
59 | long last_point_id = (Long)lastPointIdIter.next().get(0);
60 | if (geom.isEmpty()) {
61 | // Skip empty geometries
62 | } else if (geom instanceof OGCPolygon) {
63 | // Copy to output directly. Polygons cannot be connected to other shapes.
64 | createdShapes.add(geom);
65 | } else if (geom instanceof OGCLineString) {
66 | linestrings.add((OGCLineString) geom);
67 | firstPointId.add(first_point_id);
68 | lastPointId.add(last_point_id);
69 | } else {
70 | throw new GeoException("Cannot connect shapes of type "+geom.getClass());
71 | }
72 |
73 | }
74 |
75 | if (firstPointIdIter.hasNext() || lastPointIdIter.hasNext() ||
76 | shapesIter.hasNext()) {
77 | throw new ExecException("All parameters should be of the same size ("
78 | + firstPointId.size() + "," + lastPointId.size() + ","
79 | + linestrings.size() + ")");
80 | }
81 |
82 | // Stores an ordered list of line segments in current connected block
83 | Vector connected_lines = new Vector();
84 | // Total number of points in all visited linestrings
85 | int sumPoints = 0;
86 | // Which linestrings to reverse upon connection
87 | Vector reverse = new Vector();
88 | long first_point_id = -1;
89 | long last_point_id = -1;
90 |
91 | // Reorder linestrings to form a contiguous list of connected linestrings
92 | while (!linestrings.isEmpty()) {
93 | // Loop invariant:
94 | // At the beginning of each iteration, the lines in connected_lines are connected.
95 | // In each iteration, we move one linestring from linestrings to connected_lines
96 | // while keeping them connected
97 | int size_before = connected_lines.size();
98 | for (int i = 0; i < linestrings.size();) {
99 | if (connected_lines.isEmpty()) {
100 | // First linestring
101 | first_point_id = firstPointId.remove(i);
102 | last_point_id = lastPointId.remove(i);
103 | reverse.add(false);
104 | sumPoints += linestrings.get(i).numPoints();
105 | connected_lines.add(linestrings.remove(i));
106 | } else if (lastPointId.get(i) == first_point_id) {
107 | // This linestring goes to the beginning of the list as-is
108 | lastPointId.remove(i);
109 | first_point_id = firstPointId.remove(i);
110 | sumPoints += linestrings.get(i).numPoints();
111 | connected_lines.add(0, linestrings.remove(i));
112 | reverse.add(0, false);
113 | } else if (firstPointId.get(i) == first_point_id) {
114 | // Should go to the beginning after being reversed
115 | firstPointId.remove(i);
116 | first_point_id = lastPointId.remove(i);
117 | sumPoints += linestrings.get(i).numPoints();
118 | connected_lines.add(0, linestrings.remove(i));
119 | reverse.add(0, true);
120 | } else if (firstPointId.get(i) == last_point_id) {
121 | // This linestring goes to the end of the list as-is
122 | firstPointId.remove(i);
123 | last_point_id = lastPointId.remove(i);
124 | sumPoints += linestrings.get(i).numPoints();
125 | connected_lines.add(linestrings.remove(i));
126 | reverse.add(false);
127 | } else if (lastPointId.get(i) == last_point_id) {
128 | // Should go to the end after being reversed
129 | lastPointId.remove(i);
130 | last_point_id = firstPointId.remove(i);
131 | sumPoints += linestrings.get(i).numPoints();
132 | connected_lines.add(linestrings.remove(i));
133 | reverse.add(true);
134 | } else {
135 | i++;
136 | }
137 | }
138 |
139 | if (connected_lines.size() == size_before || linestrings.isEmpty()) {
140 | // Cannot connect any more lines to the current block. Emit as a shape
141 | boolean isPolygon = first_point_id == last_point_id;
142 | Point[] points = new Point[sumPoints - connected_lines.size() + (isPolygon? 0 : 1)];
143 | int n = 0;
144 | for (int i = 0; i < connected_lines.size(); i++) {
145 | OGCLineString linestring = connected_lines.get(i);
146 | boolean isReverse = reverse.get(i);
147 | int last_i = (isPolygon || i < connected_lines.size() - 1)?
148 | linestring.numPoints() - 1 : linestring.numPoints();
149 | for (int i_point = 0; i_point < last_i; i_point++) {
150 | points[n++] = (Point) linestring.pointN(
151 | isReverse? linestring.numPoints() - 1 - i_point : i_point
152 | ).getEsriGeometry();
153 | }
154 | }
155 |
156 | MultiPath multi_path = isPolygon ? new Polygon() : new Polyline();
157 | for (int i = 1; i 1) {
179 | OGCGeometryCollection collection = new OGCConcreteGeometryCollection(createdShapes, createdShapes.get(0).getEsriSpatialReference());
180 | return new DataByteArray(collection.asBinary().array());
181 | } else {
182 | throw new GeoException("No shapes to connect");
183 | }
184 | } catch (Exception e) {
185 | throw new GeoException(e);
186 | }
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Contains.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 |
18 | /**
19 | * A UDF that tests whether a geometry contains another geometry or not
20 | * @author Ahmed Eldawy
21 | *
22 | */
23 | public class Contains extends FilterFunc {
24 |
25 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
26 |
27 | @Override
28 | public Boolean exec(Tuple input) throws IOException {
29 | OGCGeometry geom1 = null, geom2 = null;
30 | try {
31 | geom1 = geometryParser.parseGeom(input.get(0));
32 | geom2 = geometryParser.parseGeom(input.get(1));
33 | return geom1.contains(geom2);
34 | } catch (ExecException ee) {
35 | throw new GeoException(geom1, geom2, ee);
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/ConvexHull.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 |
12 | import org.apache.pig.Algebraic;
13 | import org.apache.pig.EvalFunc;
14 | import org.apache.pig.backend.executionengine.ExecException;
15 | import org.apache.pig.data.DataBag;
16 | import org.apache.pig.data.DataByteArray;
17 | import org.apache.pig.data.Tuple;
18 | import org.apache.pig.data.TupleFactory;
19 |
20 | import com.esri.core.geometry.GeometryException;
21 | import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection;
22 | import com.esri.core.geometry.ogc.OGCGeometry;
23 | import com.esri.core.geometry.ogc.OGCGeometryCollection;
24 |
25 | /**
26 | * Finds the convex null of a set of shapes. This is an algebraic function which
27 | * works more efficiently by computing convex hull of a subset of the shapes and
28 | * finally computing the overall convex hull.
29 | *
30 | * @author Ahmed Eldawy
31 | *
32 | */
33 | public class ConvexHull extends EvalFunc implements Algebraic {
34 |
35 | private static final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
36 |
37 | @Override
38 | public DataByteArray exec(Tuple input) throws IOException {
39 | OGCGeometry geom = null;
40 | try {
41 | if (input.get(0) instanceof DataBag) {
42 | return new DataByteArray(convexHull(input).asBinary().array());
43 | }
44 | geom = geometryParser.parseGeom(input.get(0));
45 | try {
46 | return new DataByteArray(geom.convexHull().asBinary().array());
47 | } catch (ArrayIndexOutOfBoundsException e) {
48 | e.printStackTrace();
49 | throw new RuntimeException(geom.asText(), e);
50 | }
51 | } catch (GeometryException e) {
52 | throw new GeoException(geom, e);
53 | } catch (ArrayIndexOutOfBoundsException e) {
54 | throw new GeoException(geom, e);
55 | }
56 | }
57 |
58 | @Override
59 | public String getInitial() { return Initial.class.getName();}
60 |
61 | @Override
62 | public String getIntermed() { return Intermed.class.getName();}
63 |
64 | @Override
65 | public String getFinal() { return Final.class.getName(); }
66 |
67 | static public class Initial extends EvalFunc {
68 | @Override
69 | public Tuple exec(Tuple input) throws IOException {
70 | // Retrieve the first element (tuple) in the given bag
71 | return ((DataBag)input.get(0)).iterator().next();
72 | }
73 | }
74 |
75 | static public class Intermed extends EvalFunc {
76 | @Override
77 | public Tuple exec(Tuple input) throws IOException {
78 | return TupleFactory.getInstance().newTuple(
79 | new DataByteArray(convexHull(input).asBinary().array()));
80 | }
81 | }
82 |
83 | static public class Final extends EvalFunc {
84 | @Override
85 | public DataByteArray exec(Tuple input) throws IOException {
86 | return new DataByteArray(convexHull(input).asBinary().array());
87 | }
88 | }
89 |
90 | static protected OGCGeometry convexHull(Tuple input) throws ExecException {
91 | DataBag values = (DataBag)input.get(0);
92 | if (values.size() == 0)
93 | return null;
94 | ArrayList all_geoms =
95 | new ArrayList();
96 | for (Tuple one_geom : values) {
97 | OGCGeometry parsedGeom = geometryParser.parseGeom(one_geom.get(0));
98 | all_geoms.add(parsedGeom);
99 | }
100 |
101 | // Do a convex null of all_geometries
102 | OGCGeometryCollection geom_collection = new OGCConcreteGeometryCollection(
103 | all_geoms, all_geoms.get(0).getEsriSpatialReference());
104 | return geom_collection.convexHull();
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Crosses.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.Geometry;
16 | import com.esri.core.geometry.ogc.OGCGeometry;
17 |
18 |
19 | /**
20 | * A UDF that returns the area of a geometry as calculated by
21 | * {@link OGCGeometry#crosses(OGCGeometry)} ()}
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Crosses extends FilterFunc {
26 |
27 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
28 |
29 | @Override
30 | public Boolean exec(Tuple input) throws IOException {
31 | OGCGeometry geom1 = null, geom2 = null;
32 | try {
33 | geom1 = geometryParser.parseGeom(input.get(0));
34 | geom2 = geometryParser.parseGeom(input.get(1));
35 | return geom1.crosses(geom2);
36 | } catch (ExecException ee) {
37 | throw new GeoException(geom1, geom2, ee);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Decompose.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.Stack;
11 | import java.util.Vector;
12 |
13 | import org.apache.pig.EvalFunc;
14 | import org.apache.pig.backend.executionengine.ExecException;
15 | import org.apache.pig.data.BagFactory;
16 | import org.apache.pig.data.DataBag;
17 | import org.apache.pig.data.DataByteArray;
18 | import org.apache.pig.data.DataType;
19 | import org.apache.pig.data.Tuple;
20 | import org.apache.pig.data.TupleFactory;
21 | import org.apache.pig.impl.logicalLayer.schema.Schema;
22 | import org.apache.pig.impl.logicalLayer.schema.Schema.FieldSchema;
23 |
24 | import org.locationtech.jts.geom.Coordinate;
25 | import org.locationtech.jts.geom.Geometry;
26 | import org.locationtech.jts.geom.GeometryCollection;
27 | import org.locationtech.jts.geom.GeometryFactory;
28 | import org.locationtech.jts.geom.Polygon;
29 | import org.locationtech.jts.io.WKBWriter;
30 |
31 | /**
32 | * Decomposes a polygon into smaller polygons using a Quad-tree-based
33 | * decomposition
34 | *
35 | * @author Ahmed Eldawy
36 | */
37 | public class Decompose extends EvalFunc {
38 |
39 | /**Default size threshold*/
40 | public static final int DefaultThreshold = 100;
41 |
42 | private final WKBWriter wkbWriter = new WKBWriter();
43 |
44 | private JTSGeometryParser geometryParser = new JTSGeometryParser();
45 |
46 | /**Geometry factory to create smaller geometries*/
47 | private GeometryFactory geomFactory = new GeometryFactory();
48 |
49 | @Override
50 | public DataBag exec(Tuple b) throws IOException {
51 | Geometry geom = geometryParser.parseGeom(b.get(0));
52 | int threshold = b.size() == 1? DefaultThreshold : (Integer)b.get(1);
53 | if (threshold < 4)
54 | throw new GeoException("Size threshold must be at least 4");
55 | DataBag output = BagFactory.getInstance().newDefaultBag();
56 | Stack toDecompose = new Stack();
57 | toDecompose.push(geom);
58 | while (!toDecompose.isEmpty()) {
59 | geom = toDecompose.pop();
60 | if (NumPoints.getGeometrySize(geom) <= threshold) {
61 | // Simple enough. Add to the output
62 | DataByteArray data = new DataByteArray(wkbWriter.write(geom));
63 | Tuple tuple = TupleFactory.getInstance().newTuple(1);
64 | tuple.set(0, data);
65 | output.add(tuple);
66 | } else {
67 | // Large geometry. Decompose into four
68 | Geometry[] parts = decompose(geom);
69 | for (Geometry part : parts)
70 | if (!part.isEmpty())
71 | toDecompose.push(part);
72 | }
73 | }
74 | return output;
75 | }
76 |
77 | /**
78 | * Decomposes a geometry into smaller one.
79 | * @param geom
80 | * @return
81 | */
82 | public Geometry[] decompose(Geometry geom) {
83 | if (geom instanceof GeometryCollection) {
84 | GeometryCollection coll = (GeometryCollection) geom;
85 | Vector output = new Vector();
86 | for (int i = 0; i < coll.getNumGeometries(); i++) {
87 | geom = coll.getGeometryN(i);
88 | Geometry[] partialAnswer = decompose(geom);
89 | for (Geometry g : partialAnswer)
90 | if (!g.isEmpty())
91 | output.add(g);
92 | }
93 | return output.toArray(new Geometry[output.size()]);
94 | }
95 |
96 | Geometry[] parts = new Geometry[4];
97 | Geometry envelope = geom.getEnvelope();
98 | Coordinate[] coords = envelope.getCoordinates();
99 | Coordinate[][] corners = new Coordinate[3][3];
100 | double x1 = Math.min(coords[0].x, coords[2].x);
101 | double x2 = Math.max(coords[0].x, coords[2].x);
102 | double y1 = Math.min(coords[0].y, coords[2].y);
103 | double y2 = Math.max(coords[0].y, coords[2].y);
104 | double cx = (x1 + x2) / 2;
105 | double cy = (y1 + y2) / 2;
106 | corners[0][0] = new Coordinate(x1, y1);
107 | corners[0][1] = new Coordinate(x1, cy);
108 | corners[0][2] = new Coordinate(x1, y2);
109 | corners[1][0] = new Coordinate(cx, y1);
110 | corners[1][1] = new Coordinate(cx, cy);
111 | corners[1][2] = new Coordinate(cx, y2);
112 | corners[2][0] = new Coordinate(x2, y1);
113 | corners[2][1] = new Coordinate(x2, cy);
114 | corners[2][2] = new Coordinate(x2, y2);
115 |
116 | Polygon q0 = geomFactory
117 | .createPolygon(
118 | geomFactory.createLinearRing(new Coordinate[] { corners[0][0],
119 | corners[1][0], corners[1][1], corners[0][1], corners[0][0] }),
120 | null);
121 | parts[0] = q0.intersection(geom);
122 | Polygon q1 = geomFactory
123 | .createPolygon(
124 | geomFactory.createLinearRing(new Coordinate[] { corners[0][1],
125 | corners[1][1], corners[1][2], corners[0][2], corners[0][1]}),
126 | null);
127 | parts[1] = q1.intersection(geom);
128 | Polygon q2 = geomFactory
129 | .createPolygon(
130 | geomFactory.createLinearRing(new Coordinate[] { corners[1][0],
131 | corners[2][0], corners[2][1], corners[1][1], corners[1][0] }),
132 | null);
133 | parts[2] = q2.intersection(geom);
134 | Polygon q3 = geomFactory
135 | .createPolygon(
136 | geomFactory.createLinearRing(new Coordinate[] { corners[1][1],
137 | corners[2][1], corners[2][2], corners[1][2], corners[1][1] }),
138 | null);
139 | parts[3] = q3.intersection(geom);
140 |
141 | return parts;
142 | }
143 |
144 | public Schema outputSchema(Schema input) {
145 | try {
146 | Schema partSchema = new Schema();
147 | partSchema.add(new Schema.FieldSchema("geom", DataType.BYTEARRAY));
148 |
149 | FieldSchema outSchema = new Schema.FieldSchema("geometries", partSchema);
150 | outSchema.type = DataType.BAG;
151 |
152 | return new Schema(outSchema);
153 | } catch (Exception e) {
154 | return null;
155 | }
156 | }
157 | }
158 |
159 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Difference.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import com.esri.core.geometry.ogc.OGCGeometry;
17 |
18 |
19 | /**
20 | * A UDF that returns the spatial difference of two shapes as calculated by
21 | * {@link OGCGeometry#difference(OGCGeometry)}
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Difference extends EvalFunc {
26 |
27 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
28 |
29 | @Override
30 | public DataByteArray exec(Tuple input) throws IOException {
31 | OGCGeometry geom1 = null, geom2 = null;
32 | try {
33 | geom1 = geometryParser.parseGeom(input.get(0));
34 | geom2 = geometryParser.parseGeom(input.get(1));
35 | return new DataByteArray(geom1.difference(geom2).asBinary().array());
36 | } catch (ExecException ee) {
37 | throw new GeoException(geom1, geom2, ee);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/ESRIGeometryParser.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.nio.ByteBuffer;
10 |
11 | import org.apache.pig.backend.executionengine.ExecException;
12 | import org.apache.pig.data.DataByteArray;
13 |
14 | import com.esri.core.geometry.ogc.OGCGeometry;
15 |
16 | /**
17 | * Retrieves a geometry from a pig attribute. It detects the type of the column
18 | * and the data stored in that column and automatically detects its format
19 | * and tries to get the geometry object from it. In particular, here are the
20 | * checks done in order:
21 | * 1- If the object is of type bytearray, it is parsed as a well known binary
22 | * (WKB). If the parsing fails with a parse exception, the binary array
23 | * is converted to a string and the next step is carried out.
24 | * 2- If the object is of type chararrray or step 1 fails, the string is parsed
25 | * as a well known text (WKT). If the parsing fails and the string contains
26 | * only hex characters (0-9 and A-Z), it is converted to binary and parsed
27 | * as a well known binary (WKB).
28 | * @author Ahmed Eldawy
29 | *
30 | */
31 | public class ESRIGeometryParser {
32 |
33 | public OGCGeometry parseGeom(Object o) throws ExecException {
34 | if (o == null)
35 | return null;
36 | if (o instanceof DataByteArray) {
37 | try {
38 | // Parse data as well known binary (WKB)
39 | byte[] bytes = ((DataByteArray) o).get();
40 | return OGCGeometry.fromBinary(ByteBuffer.wrap(bytes));
41 | } catch (RuntimeException e) {
42 | // Treat it as an encoded string (WKT)
43 | o = new String(((DataByteArray) o).get());
44 | }
45 | }
46 | if (o instanceof String) {
47 | try {
48 | // Parse string as well known text (WKT)
49 | return OGCGeometry.fromText((String) o);
50 | } catch (IllegalArgumentException e) {
51 | try {
52 | // Error parsing from WKT, try hex string instead
53 | byte[] binary = hexToBytes((String) o);
54 | return OGCGeometry.fromBinary(ByteBuffer.wrap(binary));
55 | } catch (RuntimeException e1) {
56 | // Cannot parse text.
57 | throw new ExecException("Cannot parse '"+o+"'", e);
58 | }
59 | }
60 | }
61 | throw new ExecException("Cannot parse unknown type '"+o+"'");
62 | }
63 |
64 | public static double parseDouble(Object o) {
65 | if (o instanceof Integer)
66 | return (Integer)o;
67 | if (o instanceof Double)
68 | return (Double)o;
69 | if (o instanceof DataByteArray)
70 | return Double.parseDouble(new String(((DataByteArray) o).get()));
71 | throw new RuntimeException("Cannot parse "+o+" into double");
72 | }
73 |
74 | private static final byte[] HexLookupTable = {
75 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
76 | 'F'
77 | };
78 |
79 | /**
80 | * Convert binary array to a hex string.
81 | * @param binary
82 | * @return
83 | */
84 | public static String bytesToHex(byte[] binary) {
85 | // Each byte is converted to two hex values
86 | byte[] hex = new byte[binary.length * 2];
87 | for (int i = 0; i < binary.length; i++) {
88 | hex[2*i] = HexLookupTable[(binary[i] & 0xFF) >>> 4];
89 | hex[2*i+1] = HexLookupTable[binary[i] & 0xF];
90 | }
91 | return new String(hex);
92 | }
93 |
94 | /**
95 | * Convert a string containing a hex string to a byte array of binary.
96 | * For example, the string "AABB" is converted to the byte array {0xAA, 0XBB}
97 | * @param hex
98 | * @return
99 | */
100 | public static byte[] hexToBytes(String hex) {
101 | byte[] bytes = new byte[(hex.length() + 1) / 2];
102 | for (int i = 0; i < hex.length(); i++) {
103 | byte x = (byte) hex.charAt(i);
104 | if (x >= '0' && x <= '9')
105 | x -= '0';
106 | else if (x >= 'a' && x <= 'f')
107 | x = (byte) ((x - 'a') + 0xa);
108 | else if (x >= 'A' && x <= 'F')
109 | x = (byte) ((x - 'A') + 0xA);
110 | else
111 | throw new RuntimeException("Invalid hex char "+x);
112 | if (i % 2 == 0)
113 | x <<= 4;
114 | bytes[i / 2] |= x;
115 | }
116 | return bytes;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/ESRIShapeFromText.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 | /**
18 | * @author Carlos Balduz
19 | *
20 | */
21 | public class ESRIShapeFromText extends EvalFunc {
22 |
23 | @Override
24 | public DataByteArray exec(Tuple input) throws IOException {
25 |
26 | if (input.size() != 1)
27 | throw new IOException("ESRIShapeFromText takes one bytearray argument");
28 |
29 | String s = input.get(0).toString();
30 | return new DataByteArray(OGCGeometryToESRIShapeParser.ogcGeomToEsriShape(s));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/ESRIShapeFromWKB.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | /**
16 | * @author Carlos Balduz
17 | *
18 | */
19 | public class ESRIShapeFromWKB extends EvalFunc {
20 |
21 | @Override
22 | public DataByteArray exec(Tuple input) throws IOException {
23 |
24 | if (input.size() != 1)
25 | throw new IOException("ESRIShapeFromWKB takes one bytearray argument");
26 |
27 | Object o = input.get(0);
28 | return new DataByteArray(OGCGeometryToESRIShapeParser.ogcGeomToEsriShape(o));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Envelope.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import org.locationtech.jts.geom.Geometry;
17 | import org.locationtech.jts.io.WKBWriter;
18 |
19 |
20 | /**
21 | * A UDF that returns the minimal bounding rectangle (MBR) of a shape.
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Envelope extends EvalFunc {
26 |
27 | private final WKBWriter WKB_WRITER = new WKBWriter();
28 | private final JTSGeometryParser GEOMETRY_PARSER = new JTSGeometryParser();
29 |
30 | @Override
31 | public DataByteArray exec(Tuple input) throws IOException {
32 | Geometry geom = null;
33 | try {
34 | Object v = input.get(0);
35 | geom = GEOMETRY_PARSER.parseGeom(v);
36 | Geometry envelope = geom.getEnvelope();
37 | return new DataByteArray(WKB_WRITER.write(envelope));
38 | } catch (ExecException ee) {
39 | throw new GeoException(geom, ee);
40 | }
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Extent.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 |
12 | import org.apache.pig.Algebraic;
13 | import org.apache.pig.EvalFunc;
14 | import org.apache.pig.backend.executionengine.ExecException;
15 | import org.apache.pig.data.DataBag;
16 | import org.apache.pig.data.DataByteArray;
17 | import org.apache.pig.data.Tuple;
18 | import org.apache.pig.data.TupleFactory;
19 |
20 | import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection;
21 | import com.esri.core.geometry.ogc.OGCGeometry;
22 | import com.esri.core.geometry.ogc.OGCGeometryCollection;
23 |
24 | /**
25 | * Finds the minimal bounding rectangle (MBR) of a set of shapes.
26 | * @author Ahmed Eldawy
27 | *
28 | */
29 | public class Extent extends EvalFunc implements Algebraic {
30 |
31 | private static final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
32 |
33 | @Override
34 | public DataByteArray exec(Tuple input) throws IOException {
35 | return new DataByteArray(extent(input).asBinary().array());
36 | }
37 |
38 | @Override
39 | public String getInitial() { return Initial.class.getName();}
40 |
41 | @Override
42 | public String getIntermed() { return Intermed.class.getName();}
43 |
44 | @Override
45 | public String getFinal() { return Final.class.getName(); }
46 |
47 | static public class Initial extends EvalFunc {
48 | @Override
49 | public Tuple exec(Tuple input) throws IOException {
50 | // Retrieve the first element (tuple) in the given bag
51 | return ((DataBag)input.get(0)).iterator().next();
52 | }
53 | }
54 |
55 | static public class Intermed extends EvalFunc {
56 | @Override
57 | public Tuple exec(Tuple input) throws IOException {
58 | return TupleFactory.getInstance().newTuple(
59 | new DataByteArray(extent(input).asBinary().array()));
60 | }
61 | }
62 |
63 | static public class Final extends EvalFunc {
64 | @Override
65 | public DataByteArray exec(Tuple input) throws IOException {
66 | return new DataByteArray(extent(input).asBinary().array());
67 | }
68 | }
69 |
70 | static protected OGCGeometry extent(Tuple input) throws ExecException {
71 | DataBag values = (DataBag)input.get(0);
72 | if (values.size() == 0)
73 | return null;
74 | ArrayList all_geoms = new ArrayList();
75 | for (Tuple one_geom : values) {
76 | OGCGeometry parsedGeom = geometryParser.parseGeom(one_geom.get(0));
77 | all_geoms.add(parsedGeom);
78 | }
79 |
80 | // Do a union of all_geometries in the recommended way (using buffer(0))
81 | OGCGeometryCollection geom_collection = new OGCConcreteGeometryCollection(
82 | all_geoms, all_geoms.get(0).getEsriSpatialReference());
83 | return geom_collection.envelope();
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/GeoException.java:
--------------------------------------------------------------------------------
1 | package edu.umn.cs.pigeon;
2 |
3 | import com.esri.core.geometry.ogc.OGCGeometry;
4 | import org.locationtech.jts.geom.Geometry;
5 | import org.apache.pig.backend.executionengine.ExecException;
6 |
7 | /**
8 | * An exception to signal an error with any geo function
9 | */
10 | public class GeoException extends ExecException {
11 | public GeoException(Geometry geom, Exception e) {
12 | super("Error processing the shape " + geom == null? "" : geom.toText(), e);
13 | }
14 |
15 | public GeoException(Geometry geom1, Geometry geom2, Exception e) {
16 | super("Error processing the shape " + (geom1 == null? "" : geom1.toText())+
17 | " & "+(geom2 == null? "" : geom2.toText()), e);
18 | }
19 |
20 | public GeoException(Geometry geom) {
21 | super("Error processing the shape " + (geom == null? "" : geom.toText()));
22 | }
23 |
24 | public GeoException(OGCGeometry geom, Exception e) {
25 | super("Error processing the shape " + (geom == null? "" : geom.asText()), e);
26 | }
27 |
28 | public GeoException(OGCGeometry geom1, OGCGeometry geom2, Exception e) {
29 | super("Error processing the shape " + (geom1 == null? "" : geom1.asText())+
30 | " & "+(geom2 == null? "" : geom2.asText()), e);
31 | }
32 |
33 |
34 | public GeoException(OGCGeometry geom) {
35 | super("Error processing the shape " + (geom == null? "" : geom.asText()));
36 | }
37 |
38 |
39 | public GeoException(String message) {
40 | super(message);
41 | }
42 |
43 | public GeoException(Throwable e) {
44 | super(e);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/GeometryFromText.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 | /**
18 | * @author Tuan Pham
19 | *
20 | */
21 | public class GeometryFromText extends EvalFunc {
22 |
23 | @Override
24 | public DataByteArray exec(Tuple input) throws IOException {
25 |
26 | if (input.size() != 1)
27 | throw new GeoException("GeometryFromText takes one bytearray argument");
28 |
29 | ESRIGeometryParser gp = new ESRIGeometryParser();
30 | OGCGeometry geom = gp.parseGeom(input.get(0).toString());
31 | return new DataByteArray(geom.asBinary().array());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/GeometryFromWKB.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 | /**
18 | * @author Tuan Pham
19 | *
20 | */
21 | public class GeometryFromWKB extends EvalFunc {
22 |
23 | @Override
24 | public DataByteArray exec(Tuple input) throws IOException {
25 |
26 | if (input.size() != 1)
27 | throw new GeoException("GeometryFromWKB takes one bytearray argument");
28 |
29 | ESRIGeometryParser gp = new ESRIGeometryParser();
30 | OGCGeometry geom = gp.parseGeom(input.get(0));
31 | return new DataByteArray(geom.asBinary().array());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/GridCell.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import org.locationtech.jts.geom.Coordinate;
16 | import org.locationtech.jts.geom.Geometry;
17 | import org.locationtech.jts.geom.GeometryFactory;
18 | import org.locationtech.jts.geom.Polygon;
19 | import org.locationtech.jts.io.WKBWriter;
20 |
21 | /**
22 | * Returns the boundaries of one cell
23 | * GridPartition(cellid, gridMBR, gridSize)
24 | * where:
25 | * cellid: the ID of the cell to retrieve.
26 | * gridMBR: the rectangle that defines the boundaries of the grid
27 | * gridSize: number of rows and columns assuming a uniform grid
28 | * @author Ahmed Eldawy
29 | */
30 | public class GridCell extends EvalFunc {
31 |
32 | private final JTSGeometryParser geometryParser = new JTSGeometryParser();
33 | private GeometryFactory geometryFactory = new GeometryFactory();
34 | private WKBWriter wkbWriter = new WKBWriter();
35 |
36 | @Override
37 | public DataByteArray exec(Tuple b) throws IOException {
38 | int cellID = (Integer) b.get(0);
39 | Geometry gridMBR = geometryParser.parseGeom(b.get(1)).getEnvelope();
40 | int gridSize = (Integer)b.get(2);
41 |
42 | Coordinate[] gridCoords = gridMBR.getCoordinates();
43 | double gridX1 = Math.min(gridCoords[0].x, gridCoords[2].x);
44 | double gridY1 = Math.min(gridCoords[0].y, gridCoords[2].y);
45 | double gridX2 = Math.max(gridCoords[0].x, gridCoords[2].x);
46 | double gridY2 = Math.max(gridCoords[0].y, gridCoords[2].y);
47 |
48 | int column = cellID % gridSize;
49 | int row = cellID / gridSize;
50 | double cellX1 = column * (gridX2 - gridX1) / gridSize + gridX1;
51 | double cellX2 = (column + 1) * (gridX2 - gridX1) / gridSize + gridX1;
52 | double cellY1 = row * (gridY2 - gridY1) / gridSize + gridY1;
53 | double cellY2 = (row + 1) * (gridY2 - gridY1) / gridSize + gridY1;
54 |
55 | Coordinate[] corners = new Coordinate[5];
56 | corners[0] = new Coordinate(cellX1, cellY1);
57 | corners[1] = new Coordinate(cellX1, cellY2);
58 | corners[2] = new Coordinate(cellX2, cellY2);
59 | corners[3] = new Coordinate(cellX2, cellY1);
60 | corners[4] = corners[0];
61 |
62 | Polygon box = geometryFactory.createPolygon(geometryFactory.createLinearRing(corners), null);
63 |
64 | return new DataByteArray(wkbWriter.write(box));
65 | }
66 |
67 | }
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/GridPartition.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.BagFactory;
13 | import org.apache.pig.data.DataBag;
14 | import org.apache.pig.data.DataType;
15 | import org.apache.pig.data.Tuple;
16 | import org.apache.pig.data.TupleFactory;
17 | import org.apache.pig.impl.logicalLayer.schema.Schema;
18 | import org.apache.pig.impl.logicalLayer.schema.Schema.FieldSchema;
19 |
20 | import org.locationtech.jts.geom.Coordinate;
21 | import org.locationtech.jts.geom.Geometry;
22 |
23 | /**
24 | * Checks which grid cells overlap a geometric shape based on its MBR.
25 | * General format
26 | * GridPartition(geom, gridMBR, gridSize)
27 | * where:
28 | * geom: the geometry to test
29 | * gridMBR: the rectangle that defines the boundaries of the grid
30 | * gridSize: number of rows and columns assuming a uniform grid
31 | * @author Ahmed Eldawy
32 | */
33 | public class GridPartition extends EvalFunc {
34 |
35 | private final JTSGeometryParser geometryParser = new JTSGeometryParser();
36 |
37 | @Override
38 | public DataBag exec(Tuple b) throws IOException {
39 | Geometry geomMBR = geometryParser.parseGeom(b.get(0)).getEnvelope();
40 | Geometry gridMBR = geometryParser.parseGeom(b.get(1)).getEnvelope();
41 | int gridSize = (Integer)b.get(2);
42 |
43 | DataBag output = BagFactory.getInstance().newDefaultBag();
44 |
45 | Coordinate[] gridCoords = gridMBR.getCoordinates();
46 | double gridX1 = Math.min(gridCoords[0].x, gridCoords[2].x);
47 | double gridY1 = Math.min(gridCoords[0].y, gridCoords[2].y);
48 | double gridX2 = Math.max(gridCoords[0].x, gridCoords[2].x);
49 | double gridY2 = Math.max(gridCoords[0].y, gridCoords[2].y);
50 |
51 | Coordinate[] geomCoords = geomMBR.getCoordinates();
52 | double geomX1, geomY1, geomX2, geomY2;
53 | if (geomCoords.length == 1) {
54 | // A special case for point
55 | geomX1 = geomX2 = geomCoords[0].x;
56 | geomY1 = geomY2 = geomCoords[0].y;
57 | } else if (geomCoords.length == 2) {
58 | // An orthogonal (horizontal or vertical) line
59 | geomX1 = Math.min(geomCoords[0].x, geomCoords[1].x);
60 | geomY1 = Math.min(geomCoords[0].y, geomCoords[1].y);
61 | geomX2 = Math.max(geomCoords[0].x, geomCoords[1].x);
62 | geomY2 = Math.max(geomCoords[0].y, geomCoords[1].y);
63 | } else {
64 | geomX1 = Math.min(geomCoords[0].x, geomCoords[2].x);
65 | geomY1 = Math.min(geomCoords[0].y, geomCoords[2].y);
66 | geomX2 = Math.max(geomCoords[0].x, geomCoords[2].x);
67 | geomY2 = Math.max(geomCoords[0].y, geomCoords[2].y);
68 | }
69 |
70 |
71 | int col1 = (int) (Math.floor((geomX1 - gridX1) * gridSize / (gridX2 - gridX1)));
72 | int row1 = (int) (Math.floor((geomY1 - gridY1) * gridSize / (gridY2 - gridY1)));
73 | int col2 = (int) (Math.ceil((geomX2 - gridX1) * gridSize / (gridX2 - gridX1)));
74 | int row2 = (int) (Math.ceil((geomY2 - gridY1) * gridSize / (gridY2 - gridY1)));
75 |
76 | for (int col = col1; col < col2; col++) {
77 | for (int row = row1; row < row2; row++) {
78 | int cellID = row * gridSize + col;
79 | Tuple tuple = TupleFactory.getInstance().newTuple(1);
80 | tuple.set(0, cellID);
81 | output.add(tuple);
82 | }
83 | }
84 | return output;
85 | }
86 |
87 | public Schema outputSchema(Schema input) {
88 | try {
89 | Schema partSchema = new Schema();
90 | partSchema.add(new Schema.FieldSchema("cellID", DataType.INTEGER));
91 |
92 | FieldSchema outSchema = new Schema.FieldSchema("overlapCells", partSchema);
93 | outSchema.type = DataType.BAG;
94 |
95 | return new Schema(outSchema);
96 | } catch (Exception e) {
97 | return null;
98 | }
99 | }
100 | }
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Intersection.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import com.esri.core.geometry.ogc.OGCGeometry;
17 |
18 |
19 | /**
20 | * A UDF that returns the spatial intersection of two shapes as calculated by
21 | * {@link OGCGeometry#intersection(OGCGeometry)}
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class Intersection extends EvalFunc {
26 |
27 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
28 |
29 | @Override
30 | public DataByteArray exec(Tuple input) throws IOException {
31 | OGCGeometry geom1 = null, geom2 = null;
32 | try {
33 | geom1 = geometryParser.parseGeom(input.get(0));
34 | geom2 = geometryParser.parseGeom(input.get(1));
35 | return new DataByteArray(geom1.intersection(geom2).asBinary().array());
36 | } catch (ExecException ee) {
37 | throw new GeoException(geom1, geom2, ee);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Intersects.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 |
18 | /**
19 | * A UDF that tests whether a geometry intersects another geometry or not
20 | * @author Tuan Pham
21 | *
22 | */
23 | public class Intersects extends FilterFunc {
24 |
25 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
26 |
27 | @Override
28 | public Boolean exec(Tuple input) throws IOException {
29 | OGCGeometry geom1 = null, geom2 = null;
30 | try {
31 | geom1 = geometryParser.parseGeom(input.get(0));
32 | geom2 = geometryParser.parseGeom(input.get(1));
33 | return geom1.intersects(geom2);
34 | } catch (ExecException ee) {
35 | throw new GeoException(geom1, geom2, ee);
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/IsEmpty.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 |
18 | /**
19 | * A predicate that tells whether a specific geometry is empty or not.
20 | * A wrapper call to {@link OGCGeometry#isEmpty()}
21 | * @author Ahmed Eldawy
22 | *
23 | */
24 | public class IsEmpty extends FilterFunc {
25 |
26 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
27 |
28 | @Override
29 | public Boolean exec(Tuple input) throws IOException {
30 | OGCGeometry geom = null;
31 | try {
32 | Object v = input.get(0);
33 | geom = geometryParser.parseGeom(v);
34 | return geom.isEmpty();
35 | } catch (ExecException ee) {
36 | throw new GeoException(geom, ee);
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/IsValid.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 | import org.locationtech.jts.geom.Geometry;
17 |
18 |
19 | /**
20 | * A predicate that tells whether a specific geometry is empty or not.
21 | * A wrapper call to {@link OGCGeometry#isEmpty()}
22 | * @author Ahmed Eldawy
23 | *
24 | */
25 | public class IsValid extends FilterFunc {
26 |
27 | private final JTSGeometryParser geometryParser = new JTSGeometryParser();
28 |
29 | @Override
30 | public Boolean exec(Tuple input) throws IOException {
31 | Geometry geom = null;
32 | try {
33 | Object v = input.get(0);
34 | geom = geometryParser.parseGeom(v);
35 | return geom.isValid();
36 | } catch (ExecException ee) {
37 | throw new GeoException(geom, ee);
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/JTSGeometryParser.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import org.locationtech.jts.io.WKBWriter;
10 | import org.apache.pig.backend.executionengine.ExecException;
11 | import org.apache.pig.data.DataByteArray;
12 |
13 | import org.locationtech.jts.geom.Geometry;
14 | import org.locationtech.jts.io.ParseException;
15 | import org.locationtech.jts.io.WKBReader;
16 | import org.locationtech.jts.io.WKTReader;
17 |
18 | /**
19 | * Retrieves a geometry from a pig attribute. It detects the type of the column
20 | * and the data stored in that column and automatically detects its format
21 | * and tries to get the geometry object from it. In particular, here are the
22 | * checks done in order:
23 | * 1- If the object is of type bytearray, it is parsed as a well known binary
24 | * (WKB). If the parsing fails with a parse exception, the binary array
25 | * is converted to a string and the next step is carried out.
26 | * 2- If the object is of type chararrray or step 1 fails, the string is parsed
27 | * as a well known text (WKT). If the parsing fails and the string contains
28 | * only hex characters (0-9 and A-Z), it is converted to binary and parsed
29 | * as a well known binary (WKB).
30 | * @author Ahmed Eldawy
31 | *
32 | */
33 | public class JTSGeometryParser {
34 |
35 | private final WKTReader wkt_reader = new WKTReader();
36 | private final WKBReader wkb_reader = new WKBReader();
37 | private final WKBWriter wkb_writer = new WKBWriter();
38 |
39 | public Geometry parseGeom(Object o) throws ExecException {
40 | if (o == null)
41 | return null;
42 | if (o instanceof DataByteArray) {
43 | byte[] bytes = ((DataByteArray) o).get();
44 | try {
45 | // Parse data as well known binary (WKB)
46 | return wkb_reader.read(bytes);
47 | } catch (ParseException e) {
48 | // Convert bytes to text and try text parser
49 | o = new String(bytes);
50 | }
51 | }
52 | if (o instanceof String) {
53 | try {
54 | // Parse string as well known text (WKT)
55 | return wkt_reader.read((String) o);
56 | } catch (ParseException e) {
57 | // Parse string as a hex string of a well known binary (WKB)
58 | String hex = (String) o;
59 | boolean isHex = true;
60 | for (int i = 0; isHex && i < hex.length(); i++) {
61 | char digit = hex.charAt(i);
62 | isHex = (digit >= '0' && digit <= '9') ||
63 | (digit >= 'a' && digit <= 'f') ||
64 | (digit >= 'A' && digit <= 'F');
65 | }
66 | if (isHex) {
67 | byte[] binary = WKBReader.hexToBytes(hex);
68 | try {
69 | return wkb_reader.read(binary);
70 | } catch (ParseException e1) {
71 | throw new ExecException("Error parsing '"+o+"'", e);
72 | }
73 | }
74 | }
75 | }
76 | throw new ExecException("Error parsing unknown type '"+o+"'");
77 | }
78 |
79 | public static double parseDouble(Object o) {
80 | if (o instanceof Integer)
81 | return (Integer)o;
82 | if (o instanceof Double)
83 | return (Double)o;
84 | if (o instanceof DataByteArray)
85 | return Double.parseDouble(new String(((DataByteArray) o).get()));
86 | throw new RuntimeException("Cannot parse "+o+" into double");
87 | }
88 |
89 | public DataByteArray geomToBytes(Geometry geom) {
90 | return new DataByteArray(wkb_writer.write(geom));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/MakeBox.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import org.locationtech.jts.geom.Coordinate;
16 | import org.locationtech.jts.geom.GeometryFactory;
17 | import org.locationtech.jts.geom.Polygon;
18 | import org.locationtech.jts.io.WKBWriter;
19 |
20 | /**
21 | * A UDF to create a box (rectangle) from the coordinates of the two corners
22 | * x1, y1, x2, and y2.
23 | * @author Ahmed Eldawy
24 | *
25 | */
26 | public class MakeBox extends EvalFunc {
27 |
28 | private GeometryFactory geometryFactory = new GeometryFactory();
29 | private WKBWriter wkbWriter = new WKBWriter();
30 |
31 | @Override
32 | public DataByteArray exec(Tuple input) throws IOException {
33 | if (input.size() != 4)
34 | throw new GeoException("MakeBox takes four numerical arguments");
35 | double x1 = ESRIGeometryParser.parseDouble(input.get(0));
36 | double y1 = ESRIGeometryParser.parseDouble(input.get(1));
37 | double x2 = ESRIGeometryParser.parseDouble(input.get(2));
38 | double y2 = ESRIGeometryParser.parseDouble(input.get(3));
39 | Coordinate[] corners = new Coordinate[5];
40 | corners[0] = new Coordinate(x1, y1);
41 | corners[1] = new Coordinate(x1, y2);
42 | corners[2] = new Coordinate(x2, y2);
43 | corners[3] = new Coordinate(x2, y1);
44 | corners[4] = corners[0];
45 |
46 | Polygon box = geometryFactory.createPolygon(geometryFactory.createLinearRing(corners), null);
47 |
48 | return new DataByteArray(wkbWriter.write(box));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/MakeLine.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataBag;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import org.locationtech.jts.geom.Coordinate;
17 | import org.locationtech.jts.geom.Geometry;
18 | import org.locationtech.jts.geom.GeometryFactory;
19 | import org.locationtech.jts.io.WKBWriter;
20 |
21 | /**
22 | * Generates a geometry of type LineString out of a bag of points.
23 | * @author Ahmed Eldawy
24 | */
25 | public class MakeLine extends EvalFunc{
26 |
27 | private GeometryFactory geometryFactory = new GeometryFactory();
28 | private JTSGeometryParser geometryParser = new JTSGeometryParser();
29 | private WKBWriter wkbWriter = new WKBWriter();
30 |
31 | @Override
32 | public DataByteArray exec(Tuple b) throws IOException {
33 | DataBag points = (DataBag) b.get(0);
34 | Coordinate[] coordinates = new Coordinate[(int) points.size()];
35 | int i = 0;
36 | for (Tuple t : points) {
37 | Geometry point = geometryParser.parseGeom(t.get(0));
38 | coordinates[i++] = point.getCoordinate();
39 | }
40 | Geometry line = geometryFactory.createLineString(coordinates);
41 | return new DataByteArray(wkbWriter.write(line));
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/MakeLinePolygon.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 | import java.util.Iterator;
11 |
12 | import org.apache.pig.EvalFunc;
13 | import org.apache.pig.data.DataBag;
14 | import org.apache.pig.data.DataByteArray;
15 | import org.apache.pig.data.Tuple;
16 |
17 | import org.locationtech.jts.geom.Coordinate;
18 | import org.locationtech.jts.geom.Geometry;
19 | import org.locationtech.jts.geom.GeometryFactory;
20 | import org.locationtech.jts.io.WKBWriter;
21 |
22 | /**
23 | * Takes a list of point locations and IDs and creates either a linestring or
24 | * polygon based on whether the last point is the same as the first point or not.
25 | * @author Ahmed Eldawy
26 | */
27 | public class MakeLinePolygon extends EvalFunc{
28 |
29 | private GeometryFactory geometryFactory = new GeometryFactory();
30 | private JTSGeometryParser geometryParser = new JTSGeometryParser();
31 | private WKBWriter wkbWriter = new WKBWriter();
32 |
33 | @Override
34 | public DataByteArray exec(Tuple b) throws IOException {
35 | DataBag pointIDs = (DataBag) b.get(0);
36 | DataBag pointLocations = (DataBag) b.get(1);
37 | Coordinate[] coordinates = new Coordinate[(int) pointLocations.size()];
38 | int i = 0;
39 | Iterator iter_id = pointIDs.iterator();
40 | long first_point_id = -1;
41 | boolean is_polygon = false;
42 | for (Tuple t : pointLocations) {
43 | Object point_id_obj = iter_id.next().get(0);
44 | Geometry point = geometryParser.parseGeom(t.get(0));
45 | long point_id = point_id_obj instanceof Integer?
46 | (Integer) point_id_obj :
47 | (Long) point_id_obj;
48 | if (i == 0) {
49 | first_point_id = point_id;
50 | coordinates[i++] = point.getCoordinate();
51 | } else if (i == pointIDs.size() - 1) {
52 | is_polygon = point_id == first_point_id;
53 | if (is_polygon)
54 | coordinates[i++] = coordinates[0];
55 | else
56 | coordinates[i++] = point.getCoordinate();
57 | } else {
58 | coordinates[i++] = point.getCoordinate();
59 | }
60 | }
61 | Geometry shape;
62 | if (coordinates.length == 1 || (coordinates.length == 2 && is_polygon)) {
63 | // A point
64 | shape = geometryFactory.createPoint(coordinates[0]);
65 | } else {
66 | if (is_polygon && coordinates.length <= 3) {
67 | // Cannot create a polygon with two corners, convert to Linestring
68 | Coordinate[] new_coords = new Coordinate[coordinates.length - 1];
69 | System.arraycopy(coordinates, 0, new_coords, 0, new_coords.length);
70 | coordinates = new_coords;
71 | is_polygon = false;
72 | }
73 | if (is_polygon) {
74 | shape = geometryFactory.createPolygon(geometryFactory.createLinearRing(coordinates), null);
75 | } else {
76 | shape = geometryFactory.createLineString(coordinates);
77 | }
78 | }
79 | return new DataByteArray(wkbWriter.write(shape));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/MakePoint.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataByteArray;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.Point;
16 | import com.esri.core.geometry.SpatialReference;
17 | import com.esri.core.geometry.ogc.OGCPoint;
18 |
19 | /**
20 | * @author Ahmed Eldawy
21 | *
22 | */
23 | public class MakePoint extends EvalFunc {
24 |
25 | @Override
26 | public DataByteArray exec(Tuple input) throws IOException {
27 | if (input.size() != 2)
28 | throw new GeoException("MakePoint takes two numerical arguments");
29 | double x = ESRIGeometryParser.parseDouble(input.get(0));
30 | double y = ESRIGeometryParser.parseDouble(input.get(1));
31 | Point point = new Point(x, y);
32 | OGCPoint ogc_point = new OGCPoint(point, SpatialReference.create(4326));
33 | return new DataByteArray(ogc_point.asBinary().array());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/MakePolygon.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.data.DataBag;
13 | import org.apache.pig.data.DataByteArray;
14 | import org.apache.pig.data.Tuple;
15 |
16 | import com.esri.core.geometry.Line;
17 | import com.esri.core.geometry.Point;
18 | import com.esri.core.geometry.Polygon;
19 | import com.esri.core.geometry.Segment;
20 | import com.esri.core.geometry.SpatialReference;
21 | import com.esri.core.geometry.ogc.OGCPolygon;
22 |
23 | /**
24 | * Generates a geometry of type Polygon out of a bag of points. The last point
25 | * in the bag should be the same as the first point.
26 | * @author Ahmed Eldawy
27 | */
28 | public class MakePolygon extends EvalFunc{
29 |
30 | private ESRIGeometryParser geometryParser = new ESRIGeometryParser();
31 |
32 | @Override
33 | public DataByteArray exec(Tuple b) throws IOException {
34 | DataBag points = (DataBag) b.get(0);
35 | Point[] coordinates = new Point[(int) points.size()];
36 | int i = 0;
37 | for (Tuple t : points) {
38 | coordinates[i++] =
39 | (Point) (geometryParser.parseGeom(t.get(0))).getEsriGeometry();
40 | }
41 | Polygon multi_path = new Polygon();
42 | for (i = 1; i {
30 |
31 | private JTSGeometryParser geometryParser = new JTSGeometryParser();
32 |
33 | @Override
34 | public DataBag exec(Tuple b) throws IOException {
35 | DataBag pointIDs = (DataBag) b.get(0);
36 | DataBag pointLocations = (DataBag) b.get(1);
37 | long[] ids = new long[(int) pointIDs.size()];
38 | Coordinate[] coordinates = new Coordinate[(int) pointLocations.size()];
39 | int i = 0;
40 | Iterator iter_id = pointIDs.iterator();
41 | for (Tuple t : pointLocations) {
42 | Object point_id_obj = iter_id.next().get(0);
43 | Geometry point = geometryParser.parseGeom(t.get(0));
44 | long point_id = point_id_obj instanceof Integer?
45 | (Integer) point_id_obj :
46 | (Long) point_id_obj;
47 | ids[i] = point_id;
48 | coordinates[i++] = point.getCoordinate();
49 | }
50 |
51 | DataBag segmentsBag = BagFactory.getInstance().newDefaultBag();
52 | for (int n = 1; n < coordinates.length; n++) {
53 | Tuple segmentTuple = TupleFactory.getInstance().newTuple(7);
54 | segmentTuple.set(0, n - 1);
55 | segmentTuple.set(1, ids[n-1]);
56 | segmentTuple.set(2, coordinates[n-1].x);
57 | segmentTuple.set(3, coordinates[n-1].y);
58 | segmentTuple.set(4, ids[n]);
59 | segmentTuple.set(5, coordinates[n].x);
60 | segmentTuple.set(6, coordinates[n].y);
61 |
62 | segmentsBag.add(segmentTuple);
63 | }
64 | return segmentsBag;
65 | }
66 |
67 | public Schema outputSchema(Schema input) {
68 | try {
69 | Schema segmentSchema = new Schema();
70 | segmentSchema.add(new Schema.FieldSchema("position", DataType.INTEGER));
71 | segmentSchema.add(new Schema.FieldSchema("id1", DataType.LONG));
72 | segmentSchema.add(new Schema.FieldSchema("x1", DataType.DOUBLE));
73 | segmentSchema.add(new Schema.FieldSchema("y1", DataType.DOUBLE));
74 | segmentSchema.add(new Schema.FieldSchema("id2", DataType.LONG));
75 | segmentSchema.add(new Schema.FieldSchema("x2", DataType.DOUBLE));
76 | segmentSchema.add(new Schema.FieldSchema("y2", DataType.DOUBLE));
77 |
78 | FieldSchema breakSchema = new Schema.FieldSchema("segments", segmentSchema);
79 | breakSchema.type = DataType.BAG;
80 |
81 | return new Schema(breakSchema);
82 | } catch (Exception e) {
83 | return null;
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/NumPoints.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.EvalFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import org.locationtech.jts.geom.Geometry;
16 | import org.locationtech.jts.geom.GeometryCollection;
17 | import org.locationtech.jts.geom.LineString;
18 | import org.locationtech.jts.geom.Point;
19 | import org.locationtech.jts.geom.Polygon;
20 |
21 |
22 | /**
23 | * Returns the size of a geometry in terms of number of points.
24 | * For {@link Point} it always returns one.
25 | * For {@link LineString} it returns number of points.
26 | * For {@link Polygon} it returns number of edges.
27 | * @author Ahmed Eldawy
28 | *
29 | */
30 | public class NumPoints extends EvalFunc {
31 |
32 | private final JTSGeometryParser geometryParser = new JTSGeometryParser();
33 |
34 | @Override
35 | public Integer exec(Tuple input) throws IOException {
36 | Geometry geom = null;
37 | try {
38 | Object v = input.get(0);
39 | geom = geometryParser.parseGeom(v);
40 | return getGeometrySize(geom);
41 | } catch (ExecException ee) {
42 | throw new GeoException(geom, ee);
43 | }
44 | }
45 |
46 | protected static int getGeometrySize(Geometry geom) throws ExecException {
47 | if (geom instanceof Point) {
48 | return 1;
49 | } else if (geom instanceof LineString) {
50 | return ((LineString)geom).getNumPoints();
51 | } else if (geom instanceof Polygon) {
52 | Polygon poly = (Polygon) geom;
53 | int size = 0;
54 | size += getGeometrySize(poly.getExteriorRing()) - 1;
55 | for (int i = 0; i < poly.getNumInteriorRing(); i++)
56 | size += getGeometrySize(poly.getInteriorRingN(i)) - 1;
57 | return size;
58 | } else if (geom instanceof GeometryCollection) {
59 | int size = 0;
60 | GeometryCollection coll = (GeometryCollection) geom;
61 | for (int i = 0; i < coll.getNumGeometries(); i++)
62 | size += getGeometrySize(coll.getGeometryN(i));
63 | return size;
64 | } else {
65 | throw new GeoException("size() not defined for shapes of type: "+geom.getClass());
66 | }
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/OGCGeometryToESRIShapeParser.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import org.apache.pig.backend.executionengine.ExecException;
10 | import org.apache.pig.data.DataByteArray;
11 |
12 | import com.esri.core.geometry.ogc.OGCGeometry;
13 | import com.esri.core.geometry.GeometryEngine;
14 |
15 | /**
16 | * Converts an OGCGeometry to an ESRI Shape byte array so it can later be used
17 | * in Hive without the need to perform additional parsings in Hive.
18 | * @author Carlos Balduz
19 | */
20 | public class OGCGeometryToESRIShapeParser {
21 | private static final int SIZE_WKID = 4;
22 | private static final int SIZE_TYPE = 1;
23 |
24 | public static byte[] ogcGeomToEsriShape(Object o) throws ExecException {
25 | ESRIGeometryParser parser = new ESRIGeometryParser();
26 | OGCGeometry geom = parser.parseGeom(o);
27 | int wkid = geom.SRID();
28 | int geomType = getGeometryType(geom);
29 |
30 | byte[] shape = GeometryEngine.geometryToEsriShape(geom.getEsriGeometry());
31 | byte[] shapeWithData = new byte[shape.length + SIZE_TYPE + SIZE_WKID];
32 |
33 | System.arraycopy(shape, 0, shapeWithData, SIZE_WKID + SIZE_TYPE, shape.length);
34 | System.arraycopy(intToBytes(wkid), 0, shapeWithData, 0, SIZE_WKID);
35 | shapeWithData[SIZE_WKID] = (byte) geomType;
36 |
37 | return shapeWithData;
38 | }
39 |
40 | /**
41 | * Convert int to byte array.
42 | * @param i
43 | * @return
44 | */
45 | private static byte[] intToBytes(int value) {
46 | return new byte[] {
47 | (byte) (value >>> 24),
48 | (byte) (value >>> 16),
49 | (byte) (value >>> 8),
50 | (byte) value
51 | };
52 | }
53 |
54 | /**
55 | * Get the geometry type of a OGCGeometry object.
56 | * @param i
57 | * @return
58 | */
59 | private static int getGeometryType(OGCGeometry geom) {
60 | String typeName = geom.geometryType();
61 | int ogcType = 0;
62 |
63 | if (typeName.equals("Point"))
64 | ogcType = 1;
65 | else if (typeName.equals("LineString"))
66 | ogcType = 2;
67 | else if (typeName.equals("Polygon"))
68 | ogcType = 3;
69 | else if (typeName.equals("MultiPoint"))
70 | ogcType = 4;
71 | else if (typeName.equals("MultiLineString"))
72 | ogcType = 5;
73 | else if (typeName.equals("MultiPolygon"))
74 | ogcType = 6;
75 |
76 | return ogcType;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/Overlaps.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | * Copyright (C) 2014 by Regents of the University of Minnesota. *
3 | * *
4 | * This Software is released under the Apache License, Version 2.0 *
5 | * http://www.apache.org/licenses/LICENSE-2.0 *
6 | *******************************************************************/
7 | package edu.umn.cs.pigeon;
8 |
9 | import java.io.IOException;
10 |
11 | import org.apache.pig.FilterFunc;
12 | import org.apache.pig.backend.executionengine.ExecException;
13 | import org.apache.pig.data.Tuple;
14 |
15 | import com.esri.core.geometry.ogc.OGCGeometry;
16 |
17 |
18 | /**
19 | * A UDF that tests whether two geometries overlap or not
20 | * @author Ahmed Eldawy
21 | *
22 | */
23 | public class Overlaps extends FilterFunc {
24 |
25 | private final ESRIGeometryParser geometryParser = new ESRIGeometryParser();
26 |
27 | @Override
28 | public Boolean exec(Tuple input) throws IOException {
29 | OGCGeometry geom1 = null, geom2 = null;
30 | try {
31 | geom1 = geometryParser.parseGeom(input.get(0));
32 | geom2 = geometryParser.parseGeom(input.get(1));
33 | return geom1.overlaps(geom2);
34 | } catch (ExecException ee) {
35 | throw new GeoException(geom1, geom2, ee);
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/edu/umn/cs/pigeon/SJPlaneSweep.java:
--------------------------------------------------------------------------------
1 | /***********************************************************************
2 | * Copyright (c) 2015 by Regents of the University of Minnesota.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Apache License, Version 2.0 which
5 | * accompanies this distribution and is available at
6 | * http://www.opensource.org/licenses/apache2.0.php.
7 | *
8 | *************************************************************************/
9 | package edu.umn.cs.pigeon;
10 |
11 | import java.io.IOException;
12 | import java.util.Iterator;
13 | import java.util.List;
14 |
15 | import org.apache.hadoop.util.IndexedSortable;
16 | import org.apache.hadoop.util.QuickSort;
17 | import org.apache.pig.EvalFunc;
18 | import org.apache.pig.backend.executionengine.ExecException;
19 | import org.apache.pig.data.BagFactory;
20 | import org.apache.pig.data.DataBag;
21 | import org.apache.pig.data.DataType;
22 | import org.apache.pig.data.Tuple;
23 | import org.apache.pig.data.TupleFactory;
24 | import org.apache.pig.impl.logicalLayer.schema.Schema;
25 | import org.apache.pig.impl.logicalLayer.schema.Schema.FieldSchema;
26 |
27 | import org.locationtech.jts.geom.Coordinate;
28 | import org.locationtech.jts.geom.Geometry;
29 |
30 | /**
31 | * Performs a spatial join using the plane-sweep algorithm.
32 | * General usage
33 | * SJPlaneSweep(dataset1, dataset2, duplicate-avoidance-rectangle,
34 | * column-index1, column-index2)
35 | * dataset1: The left dataset
36 | * dataset2: The right dataset
37 | * duplicate-avoidance-rectangle: The rectangle to use to perform duplicate
38 | * avoidance. If not set, no duplicate avoidance is carried out.
39 | * column-index1: The index (position) of the geometric column in dataset1
40 | * column-index2: The index (position) of the geometric column in dataset2
41 | * @author Ahmed Eldawy
42 | *
43 | */
44 | public class SJPlaneSweep extends EvalFunc {
45 |
46 | private final JTSGeometryParser geomParser = new JTSGeometryParser();
47 | private TupleFactory tupleFactory;
48 |
49 | @Override
50 | public DataBag exec(Tuple input) throws IOException {
51 | DataBag lRelation = (DataBag) input.get(0);
52 | DataBag rRelation = (DataBag) input.get(1);
53 |
54 | boolean dupAvoidance = false;
55 | double mbrX1 = 0, mbrY1 = 0, mbrX2 = 0, mbrY2 = 0;
56 | if (input.size() > 2) {
57 | // Implement duplicate avoidance based on the MBR as specified by
58 | // the third argument
59 | Geometry cellMBR = geomParser.parseGeom(input.get(2));
60 | if (cellMBR != null) {
61 | dupAvoidance = true;
62 | Coordinate[] mbrCoords = cellMBR.getCoordinates();
63 | mbrX1 = Math.min(mbrCoords[0].x, mbrCoords[2].x);
64 | mbrY1 = Math.min(mbrCoords[0].y, mbrCoords[2].y);
65 | mbrX2 = Math.max(mbrCoords[0].x, mbrCoords[2].x);
66 | mbrY2 = Math.max(mbrCoords[0].y, mbrCoords[2].y);
67 | }
68 | }
69 |
70 | Iterator irGeoms = null;
71 | if (input.size() > 4 && input.get(4) instanceof DataBag) {
72 | // User specified a column of values for right geometries
73 | DataBag rGeoms = (DataBag) input.get(4);
74 | if (rGeoms.size() != rRelation.size())
75 | throw new ExecException(String.format(
76 | "Mismatched sizes of right records column (%d) and geometry column (%d)",
77 | rRelation.size(), rGeoms.size()));
78 | irGeoms = rGeoms.iterator();
79 | }
80 |
81 | // TODO ensure that the left bag is the smaller one for efficiency
82 | if (lRelation.size() > Integer.MAX_VALUE)
83 | throw new ExecException("Size of left dataset is too large "+lRelation.size());
84 |
85 | // Read all of the left dataset in memory
86 | final Tuple[] lTuples = new Tuple[(int) lRelation.size()];
87 | int leftSize = 0;
88 | tupleFactory = TupleFactory.getInstance();
89 | for (Tuple t : lRelation) {
90 | lTuples[leftSize++] = tupleFactory.newTupleNoCopy(t.getAll());
91 | }
92 |
93 | // Extract MBRs of objects for filter-refine approach
94 | final double[] lx1 = new double[(int) lRelation.size()];
95 | final double[] ly1 = new double[(int) lRelation.size()];
96 | final double[] lx2 = new double[(int) lRelation.size()];
97 | final double[] ly2 = new double[(int) lRelation.size()];
98 |
99 | if (input.size() > 3 && input.get(3) instanceof DataBag) {
100 | // User specified a column of values that contain the geometries
101 | DataBag lGeoms = (DataBag) input.get(3);
102 | if (lRelation.size() != lGeoms.size())
103 | throw new ExecException(String.format(
104 | "Mismatched sizes of left records column (%d) and geometry column (%d)",
105 | lRelation.size(), lGeoms.size()));
106 | Iterator ilGeoms = lGeoms.iterator();
107 | for (int i = 0; i < lTuples.length; i++) {
108 | Geometry geom = geomParser.parseGeom(ilGeoms.next().get(0));
109 | Coordinate[] mbrCoords = geom.getEnvelope().getCoordinates();
110 | lx1[i] = Math.min(mbrCoords[0].x, mbrCoords[2].x);
111 | ly1[i] = Math.min(mbrCoords[0].y, mbrCoords[2].y);
112 | lx2[i] = Math.max(mbrCoords[0].x, mbrCoords[2].x);
113 | ly2[i] = Math.max(mbrCoords[0].y, mbrCoords[2].y);
114 | }
115 | } else {
116 | int lGeomColumn;
117 | if (input.size() > 3 && input.get(3) instanceof Integer)
118 | lGeomColumn = (Integer) input.get(3);
119 | else if (input.size() > 3 && input.get(3) instanceof Long)
120 | lGeomColumn = (int) ((long)(Long) input.get(3));
121 | else
122 | lGeomColumn = detectGeomColumn(lTuples[0]);
123 |
124 | for (int i = 0; i < lTuples.length; i++) {
125 | Geometry geom = geomParser.parseGeom(lTuples[i].get(lGeomColumn));
126 | Coordinate[] mbrCoords = geom.getEnvelope().getCoordinates();
127 | lx1[i] = Math.min(mbrCoords[0].x, mbrCoords[2].x);
128 | ly1[i] = Math.min(mbrCoords[0].y, mbrCoords[2].y);
129 | lx2[i] = Math.max(mbrCoords[0].x, mbrCoords[2].x);
130 | ly2[i] = Math.max(mbrCoords[0].y, mbrCoords[2].y);
131 | }
132 | }
133 |
134 | // Sort left MBRs by x to prepare for the plane-sweep algorithm
135 | IndexedSortable lSortable = new IndexedSortable() {
136 | @Override
137 | public void swap(int i, int j) {
138 | Tuple tt = lTuples[i]; lTuples[i] = lTuples[j]; lTuples[j] = tt;
139 | double td = lx1[i]; lx1[i] = lx1[j]; lx1[j] = td;
140 | td = ly1[i]; ly1[i] = ly1[j]; ly1[j] = td;
141 | td = lx2[i]; lx2[i] = lx2[j]; lx2[j] = td;
142 | td = ly2[i]; ly2[i] = ly2[j]; ly2[j] = td;
143 | }
144 |
145 | @Override
146 | public int compare(int i, int j) {
147 | if (lx1[i] < lx1[j])
148 | return -1;
149 | if (lx2[i] > lx2[j])
150 | return 1;
151 | return 0;
152 | }
153 | };
154 | QuickSort quickSort = new QuickSort();
155 | quickSort.sort(lSortable, 0, lTuples.length);
156 |
157 | // Retrieve objects from the right relation in batches and join with left
158 | Iterator ri = rRelation.iterator();
159 | final int batchSize = 10000;
160 | final Tuple[] rTuples = new Tuple[batchSize];
161 | final double[] rx1 = new double[batchSize];
162 | final double[] ry1 = new double[batchSize];
163 | final double[] rx2 = new double[batchSize];
164 | final double[] ry2 = new double[batchSize];
165 | IndexedSortable rSortable = new IndexedSortable() {
166 | @Override
167 | public void swap(int i, int j) {
168 | Tuple tt = rTuples[i]; rTuples[i] = rTuples[j]; rTuples[j] = tt;
169 | double td = rx1[i]; rx1[i] = rx1[j]; rx1[j] = td;
170 | td = ry1[i]; ry1[i] = ry1[j]; ry1[j] = td;
171 | td = rx2[i]; rx2[i] = rx2[j]; rx2[j] = td;
172 | td = ry2[i]; ry2[i] = ry2[j]; ry2[j] = td;
173 | }
174 |
175 | @Override
176 | public int compare(int i, int j) {
177 | if (rx1[i] < rx1[j]) return -1;
178 | if (rx2[i] > rx2[j]) return 1;
179 | return 0;
180 | }
181 | };
182 | int rSize = 0;
183 | DataBag output = BagFactory.getInstance().newDefaultBag();
184 | int rGeomColumn = -1;
185 | while (ri.hasNext()) {
186 | rTuples[rSize++] = tupleFactory.newTupleNoCopy(ri.next().getAll());
187 | if (rSize == batchSize || !ri.hasNext()) {
188 | // Extract MBRs of geometries on the right
189 | if (irGeoms != null) {
190 | for (int i = 0; i < rSize; i++) {
191 | Geometry geom = geomParser.parseGeom(irGeoms.next().get(0));
192 | Coordinate[] mbrCoords = geom.getEnvelope().getCoordinates();
193 | rx1[i] = Math.min(mbrCoords[0].x, mbrCoords[2].x);
194 | ry1[i] = Math.min(mbrCoords[0].y, mbrCoords[2].y);
195 | rx2[i] = Math.max(mbrCoords[0].x, mbrCoords[2].x);
196 | ry2[i] = Math.max(mbrCoords[0].y, mbrCoords[2].y);
197 | }
198 | } else {
199 | if (rGeomColumn == -1)
200 | rGeomColumn = detectGeomColumn(rTuples[0]);
201 | for (int i = 0; i < rSize; i++) {
202 | Geometry geom = geomParser.parseGeom(rTuples[i].get(rGeomColumn));
203 | Coordinate[] mbrCoords = geom.getEnvelope().getCoordinates();
204 | rx1[i] = Math.min(mbrCoords[0].x, mbrCoords[2].x);
205 | ry1[i] = Math.min(mbrCoords[0].y, mbrCoords[2].y);
206 | rx2[i] = Math.max(mbrCoords[0].x, mbrCoords[2].x);
207 | ry2[i] = Math.max(mbrCoords[0].y, mbrCoords[2].y);
208 | }
209 | }
210 |
211 |
212 | // Perform the join now
213 | quickSort.sort(rSortable, 0, rSize);
214 | int i = 0, j = 0;
215 |
216 | while (i < lTuples.length && j < rSize) {
217 | if (lx1[i] < rx1[j]) {
218 | int jj = j;
219 | // Compare left object i to all right object jj
220 | while (jj < rSize && rx1[jj] <= lx2[i]) {
221 | if (lx2[i] > rx1[jj] && rx2[jj] > lx1[i] &&
222 | ly2[i] > ry1[jj] && ry2[jj] > ly1[i]) {
223 | boolean report = true;
224 | if (dupAvoidance) {
225 | // Performs the reference point technique to avoid duplicates
226 | double intersectX = Math.max(lx1[i], rx1[jj]);
227 | if (intersectX >= mbrX1 && intersectX < mbrX2) {
228 | double intersectY = Math.max(ly1[i], ry1[jj]);
229 | report = intersectY >= mbrY1 && intersectY < mbrY2;
230 | } else {
231 | report = false;
232 | }
233 | }
234 | if (report) {
235 | addToAnswer(output, lTuples[i], rTuples[jj]);
236 | }
237 | }
238 | jj++;
239 | progress();
240 | }
241 | i++;
242 | } else {
243 | int ii = i;
244 | // Compare all left objects ii to the right object j
245 | while (ii < lTuples.length && lx1[ii] <= rx2[j]) {
246 | if (lx2[ii] > rx1[j] && rx2[j] > lx1[ii] &&
247 | ly2[ii] > ry1[j] && ry2[j] > ly1[ii]) {
248 | boolean report = true;
249 | if (dupAvoidance) {
250 | // Performs the reference point technique to avoid duplicates
251 | double intersectX = Math.max(lx1[ii], rx1[j]);
252 | if (intersectX >= mbrX1 && intersectX < mbrX2) {
253 | double intersectY = Math.max(ly1[ii], ry1[j]);
254 | report = intersectY >= mbrY1 && intersectY < mbrY2;
255 | } else {
256 | report = false;
257 | }
258 | }
259 | if (report) {
260 | addToAnswer(output, lTuples[ii], rTuples[j]);
261 | }
262 | }
263 | ii++;
264 | progress();
265 | }
266 | j++;
267 | }
268 | progress();
269 | }
270 | }
271 | }
272 | return output;
273 | }
274 |
275 |
276 | private void addToAnswer(DataBag output, Tuple lTuple, Tuple rTuple) {
277 | List