-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add circle-processor that translates circles to polygons
- Loading branch information
Showing
10 changed files
with
898 additions
and
1 deletion.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
[role="xpack"] | ||
[testenv="basic"] | ||
[[ingest-circle-processor]] | ||
=== Circle Processor | ||
Converts circle definitions of shapes to regular polygons which approximate them. | ||
|
||
[[circle-processor-options]] | ||
.Circle Processor Options | ||
[options="header"] | ||
|====== | ||
| Name | Required | Default | Description | ||
| `field` | yes | - | The string-valued field to trim whitespace from | ||
| `target_field` | no | `field` | The field to assign the polygon shape to, by default `field` is updated in-place | ||
| `ignore_missing` | no | `false` | If `true` and `field` does not exist, the processor quietly exits without modifying the document | ||
| `error_distance` | yes | - | The difference between the resulting inscribed distance from center to side and the circle's radius (measured in meters for `geo_shape`, unit-less for `shape`) | ||
| `shape_type` | yes | - | which field mapping type is to be used when processing the circle: `geo_shape` or `shape` | ||
include::common-options.asciidoc[] | ||
|====== | ||
|
||
|
||
image:images/spatial/error_distance.png[] | ||
|
||
[source,js] | ||
-------------------------------------------------- | ||
PUT circles | ||
{ | ||
"mappings": { | ||
"properties": { | ||
"circle": { | ||
"type": "geo_shape" | ||
} | ||
} | ||
} | ||
} | ||
PUT _ingest/pipeline/polygonize_circles | ||
{ | ||
"description": "translate circle to polygon", | ||
"processors": [ | ||
{ | ||
"circle": { | ||
"field": "circle", | ||
"error_distance": 28.0, | ||
"shape_type": "geo_shape" | ||
} | ||
} | ||
] | ||
} | ||
-------------------------------------------------- | ||
// CONSOLE | ||
|
||
Using the above pipeline, we can attempt to index a document into the `circles` index. | ||
The circle can be represented as either a WKT circle or a GeoJSON circle. The resulting | ||
polygon will be represented and indexed using the same format as the input circle. WKT will | ||
be translated to a WKT polygon, and GeoJSON circles will be translated to GeoJSON polygons. | ||
|
||
==== Example: Circle defined in Well Known Text | ||
|
||
In this example a circle defined in WKT format is indexed | ||
|
||
[source,js] | ||
-------------------------------------------------- | ||
PUT circles/_doc/1?pipeline=polygonize_circles | ||
{ | ||
"circle": "CIRCLE (30 10 40)" | ||
} | ||
GET circles/_doc/1 | ||
-------------------------------------------------- | ||
// CONSOLE | ||
// TEST[continued] | ||
|
||
The response from the above index request: | ||
|
||
[source,js] | ||
-------------------------------------------------- | ||
{ | ||
"found": true, | ||
"_index": "circles", | ||
"_type": "_doc", | ||
"_id": "1", | ||
"_version": 1, | ||
"_seq_no": 22, | ||
"_primary_term": 1, | ||
"_source": { | ||
"circle": "polygon ((30.000365257263184 10.0, 30.000111397193788 10.00034284530941, 29.999706043744222 10.000213571721195, 29.999706043744222 9.999786428278805, 30.000111397193788 9.99965715469059, 30.000365257263184 10.0))" | ||
} | ||
} | ||
-------------------------------------------------- | ||
// TESTRESPONSE[s/"_seq_no": \d+/"_seq_no" : $body._seq_no/ s/"_primary_term": 1/"_primary_term" : $body._primary_term/] | ||
|
||
==== Example: Circle defined in GeoJSON | ||
|
||
In this example a circle defined in GeoJSON format is indexed | ||
|
||
[source,js] | ||
-------------------------------------------------- | ||
PUT circles/_doc/2?pipeline=polygonize_circles | ||
{ | ||
"circle": { | ||
"type": "circle", | ||
"radius": "40m", | ||
"coordinates": [30, 10] | ||
} | ||
} | ||
GET circles/_doc/2 | ||
-------------------------------------------------- | ||
// CONSOLE | ||
// TEST[continued] | ||
|
||
The response from the above index request: | ||
|
||
[source,js] | ||
-------------------------------------------------- | ||
{ | ||
"found": true, | ||
"_index": "circles", | ||
"_type": "_doc", | ||
"_id": "2", | ||
"_version": 1, | ||
"_seq_no": 22, | ||
"_primary_term": 1, | ||
"_source": { | ||
"circle": { | ||
"coordinates": [ | ||
[ | ||
[30.000365257263184, 10.0], | ||
[30.000111397193788, 10.00034284530941], | ||
[29.999706043744222, 10.000213571721195], | ||
[29.999706043744222, 9.999786428278805], | ||
[30.000111397193788, 9.99965715469059], | ||
[30.000365257263184, 10.0] | ||
] | ||
], | ||
"type": "polygon" | ||
} | ||
} | ||
} | ||
-------------------------------------------------- | ||
// TESTRESPONSE[s/"_seq_no": \d+/"_seq_no" : $body._seq_no/ s/"_primary_term": 1/"_primary_term" : $body._primary_term/] | ||
|
||
|
||
==== Notes on Accuracy | ||
|
||
Accuracy of the polygon that represents the circle is defined as `error_distance`. The smaller this | ||
difference is, the closer to a perfect circle the polygon is. | ||
|
||
Below is a table that aims to help capture how the radius of the circle affects the resulting number of sides | ||
of the polygon given different inputs. | ||
|
||
The minimum number of sides is `4` and the maximum is `1000`. | ||
|
||
[[circle-processor-accuracy]] | ||
.Circle Processor Accuracy | ||
[options="header"] | ||
|====== | ||
| error_distance | radius in meters | number of sides of polygon | ||
| 1.00 | 1.0 | 4 | ||
| 1.00 | 10.0 | 14 | ||
| 1.00 | 100.0 | 45 | ||
| 1.00 | 1000.0 | 141 | ||
| 1.00 | 10000.0 | 445 | ||
| 1.00 | 100000.0 | 1000 | ||
|====== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
package org.elasticsearch.xpack.spatial; | ||
|
||
import org.apache.lucene.util.SloppyMath; | ||
import org.elasticsearch.geometry.Circle; | ||
import org.elasticsearch.geometry.LinearRing; | ||
import org.elasticsearch.geometry.Polygon; | ||
import org.elasticsearch.index.mapper.GeoShapeIndexer; | ||
|
||
/** | ||
* Utility class for storing different helpful re-usable spatial functions | ||
*/ | ||
public class SpatialUtils { | ||
|
||
private SpatialUtils() {} | ||
|
||
/** | ||
* Makes an n-gon, centered at the provided circle's center, and each vertex approximately | ||
* {@link Circle#getRadiusMeters()} away from the center. | ||
* | ||
* This does not split the polygon across the date-line. Relies on {@link GeoShapeIndexer} to | ||
* split prepare polygon for indexing. | ||
* | ||
* Adapted from from org.apache.lucene.geo.GeoTestUtil | ||
* */ | ||
public static Polygon createRegularGeoShapePolygon(Circle circle, int gons) { | ||
double[][] result = new double[2][]; | ||
result[0] = new double[gons+1]; | ||
result[1] = new double[gons+1]; | ||
for(int i=0; i<gons; i++) { | ||
double angle = i * (360.0 / gons); | ||
double x = Math.cos(SloppyMath.toRadians(angle)); | ||
double y = Math.sin(SloppyMath.toRadians(angle)); | ||
double factor = 2.0; | ||
double step = 1.0; | ||
int last = 0; | ||
|
||
// Iterate out along one spoke until we hone in on the point that's nearly exactly radiusMeters from the center: | ||
while (true) { | ||
double lat = circle.getLat() + y * factor; | ||
double lon = circle.getLon() + x * factor; | ||
double distanceMeters = SloppyMath.haversinMeters(circle.getLat(), circle.getLon(), lat, lon); | ||
|
||
if (Math.abs(distanceMeters - circle.getRadiusMeters()) < 0.1) { | ||
// Within 10 cm: close enough! | ||
// lon/lat are left de-normalized so that indexing can properly detect dateline crossing. | ||
result[0][i] = lon; | ||
result[1][i] = lat; | ||
break; | ||
} | ||
|
||
if (distanceMeters > circle.getRadiusMeters()) { | ||
// too big | ||
factor -= step; | ||
if (last == 1) { | ||
step /= 2.0; | ||
} | ||
last = -1; | ||
} else if (distanceMeters < circle.getRadiusMeters()) { | ||
// too small | ||
factor += step; | ||
if (last == -1) { | ||
step /= 2.0; | ||
} | ||
last = 1; | ||
} | ||
} | ||
} | ||
|
||
// close poly | ||
result[0][gons] = result[0][0]; | ||
result[1][gons] = result[1][0]; | ||
return new Polygon(new LinearRing(result[0], result[1])); | ||
} | ||
|
||
/** | ||
* Makes an n-gon, centered at the provided circle's center. This assumes | ||
* distance measured in cartesian geometry. | ||
**/ | ||
public static Polygon createRegularShapePolygon(Circle circle, int gons) { | ||
double[][] result = new double[2][]; | ||
result[0] = new double[gons+1]; | ||
result[1] = new double[gons+1]; | ||
for(int i=0; i<gons; i++) { | ||
double angle = i * (360.0 / gons); | ||
double x = circle.getRadiusMeters() * Math.cos(SloppyMath.toRadians(angle)); | ||
double y = circle.getRadiusMeters() * Math.sin(SloppyMath.toRadians(angle)); | ||
|
||
result[0][i] = x + circle.getX(); | ||
result[1][i] = y + circle.getY(); | ||
} | ||
// close poly | ||
result[0][gons] = result[0][0]; | ||
result[1][gons] = result[1][0]; | ||
return new Polygon(new LinearRing(result[0], result[1])); | ||
} | ||
} |
Oops, something went wrong.