Skip to content

Commit

Permalink
Add geohex aggregation
Browse files Browse the repository at this point in the history
This aggregation will use uber's h3 to group
coordinates into H3 cell.
Created new aggregation type geohex_grid. The precision
will be between 0 and 15. This aggreation has default precision as 5,
similar to geohash and geotile.

Signed-off-by: Vijayan Balasubramanian <[email protected]>
  • Loading branch information
VijayanB committed Oct 11, 2022
1 parent 7e35b30 commit 3ab65e2
Show file tree
Hide file tree
Showing 15 changed files with 1,283 additions and 3 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ publishing {
// Dependencies
//****************************************************************************/
dependencies {
implementation "org.opensearch.plugin:geo:${opensearch_version}"
api project(":libs:h3")
yamlRestTestRuntimeOnly "org.apache.logging.log4j:log4j-core:${versions.log4j}"
testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}"
testImplementation 'org.json:json:20211205'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class XYPointParser {
* @param value input which needs to be parsed which contains x and y coordinates in object form
* @param ignoreZValue boolean parameter which decides if third coordinate needs to be ignored or not
* @return {@link XYPoint} after setting the x and y coordinates parsed from the parse
* @throws OpenSearchParseException
* @throws OpenSearchParseException if failed to parse input
*/
public static XYPoint parseXYPoint(Object value, final boolean ignoreZValue) throws OpenSearchParseException {
Objects.requireNonNull(value, "input value which needs to be parsed should not be null");
Expand Down Expand Up @@ -67,7 +67,7 @@ public static XYPoint parseXYPoint(Object value, final boolean ignoreZValue) thr
* @param ignoreZValue boolean parameter which decides if third coordinate needs to be ignored or not
* @return {@link XYPoint} after setting the x and y coordinates parsed from the parse
* @throws IOException
* @throws OpenSearchParseException
* @throws OpenSearchParseException if failed to parse input
*/
public static XYPoint parseXYPoint(XContentParser parser, final boolean ignoreZValue) throws IOException, OpenSearchParseException {
Objects.requireNonNull(parser, "parser should not be null");
Expand Down Expand Up @@ -146,7 +146,7 @@ public static XYPoint parseXYPoint(XContentParser parser, final boolean ignoreZV
* @param x x coordinate that will be set by parsing the value from array
* @param y y coordinate that will be set by parsing the value from array
* @return {@link XYPoint} after setting the x and y coordinates parsed from the parse
* @throws IOException
* @throws IOException if failed to get token
*/
private static XYPoint parseXYPointArray(XContentParser subParser, final boolean ignoreZValue, double x, double y) throws IOException {
XYPoint point = new XYPoint();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import org.opensearch.geospatial.index.query.xyshape.XYShapeQueryBuilder;
import org.opensearch.geospatial.processor.FeatureProcessor;
import org.opensearch.geospatial.rest.action.upload.geojson.RestUploadGeoJSONAction;
import org.opensearch.geospatial.search.aggregations.bucket.geogrid.GeoHexGrid;
import org.opensearch.geospatial.search.aggregations.bucket.geogrid.GeoHexGridAggregationBuilder;
import org.opensearch.geospatial.stats.upload.RestUploadStatsAction;
import org.opensearch.geospatial.stats.upload.UploadStats;
import org.opensearch.geospatial.stats.upload.UploadStatsAction;
Expand Down Expand Up @@ -120,4 +122,19 @@ public List<QuerySpec<?>> getQueries() {
// Register XYShapeQuery Builder to be delegated for query type: xy_shape
return List.of(new QuerySpec<>(XYShapeQueryBuilder.NAME, XYShapeQueryBuilder::new, XYShapeQueryBuilder::fromXContent));
}

/**
* Registering {@link GeoHexGrid} aggregation on GeoPoint field.
*/
@Override
public List<AggregationSpec> getAggregations() {

final AggregationSpec geoHashGrid = new AggregationSpec(
GeoHexGridAggregationBuilder.NAME,
GeoHexGridAggregationBuilder::new,
GeoHexGridAggregationBuilder.PARSER
).addResultReader(GeoHexGrid::new).setAggregatorRegistrar(GeoHexGridAggregationBuilder::registerAggregators);

return List.of(geoHashGrid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.geospatial.search.aggregations.bucket.geogrid;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.geo.search.aggregations.bucket.geogrid.BaseGeoGrid;
import org.opensearch.geo.search.aggregations.bucket.geogrid.BaseGeoGridBucket;
import org.opensearch.search.aggregations.InternalAggregations;

/**
* Represents a grid of cells where each cell's location is determined by a h3 cell address.
* All h3CellAddress in a grid are of the same precision
*/
public class GeoHexGrid extends BaseGeoGrid<GeoHexGridBucket> {

protected GeoHexGrid(String name, int requiredSize, List<BaseGeoGridBucket> buckets, Map<String, Object> metadata) {
super(name, requiredSize, buckets, metadata);
}

public GeoHexGrid(StreamInput in) throws IOException {
super(in);
}

@Override
protected Reader<GeoHexGridBucket> getBucketReader() {
return GeoHexGridBucket::new;
}

@Override
protected BaseGeoGrid create(String name, int requiredSize, List<BaseGeoGridBucket> buckets, Map<String, Object> metadata) {
return new GeoHexGrid(name, requiredSize, buckets, metadata);
}

@Override
protected GeoHexGridBucket createBucket(long address, long docCount, InternalAggregations internalAggregations) {
return new GeoHexGridBucket(address, docCount, internalAggregations);
}

@Override
public BaseGeoGrid create(List<BaseGeoGridBucket> list) {
return new GeoHexGrid(name, requiredSize, buckets, metadata);
}

@Override
public BaseGeoGridBucket createBucket(InternalAggregations internalAggregations, BaseGeoGridBucket baseGeoGridBucket) {
return new GeoHexGridBucket(baseGeoGridBucket.hashAsLong(), baseGeoGridBucket.getDocCount(), internalAggregations);
}

@Override
public String getWriteableName() {
return GeoHexGridAggregationBuilder.NAME;
}

final int getRequiredSize() {
return requiredSize;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.geospatial.search.aggregations.bucket.geogrid;

import static org.opensearch.geospatial.search.aggregations.bucket.geogrid.GeoHexHelper.checkPrecisionRange;

import java.io.IOException;
import java.util.Map;

import org.opensearch.OpenSearchParseException;
import org.opensearch.common.geo.GeoBoundingBox;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.xcontent.ObjectParser;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.common.xcontent.support.XContentMapValues;
import org.opensearch.geo.search.aggregations.bucket.geogrid.GeoGridAggregationBuilder;
import org.opensearch.geo.search.aggregations.metrics.GeoGridAggregatorSupplier;
import org.opensearch.index.query.QueryShardContext;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.aggregations.AggregatorFactories;
import org.opensearch.search.aggregations.AggregatorFactory;
import org.opensearch.search.aggregations.support.ValuesSourceAggregatorFactory;
import org.opensearch.search.aggregations.support.ValuesSourceConfig;
import org.opensearch.search.aggregations.support.ValuesSourceRegistry;

/**
* Aggregation Builder for geo hex grid
*/
public class GeoHexGridAggregationBuilder extends GeoGridAggregationBuilder {
private static final int DEFAULT_MAX_NUM_CELLS = 10000;
private static final int DEFAULT_PRECISION = 5;
private static final int DEFAULT_SHARD_SIZE = -1;
public static final String NAME = "geohex_grid";
public static final ValuesSourceRegistry.RegistryKey<GeoGridAggregatorSupplier> REGISTRY_KEY = new ValuesSourceRegistry.RegistryKey<>(
NAME,
GeoGridAggregatorSupplier.class
);

public static final ObjectParser<GeoHexGridAggregationBuilder, String> PARSER = createParser(
NAME,
GeoHexGridAggregationBuilder::parsePrecision,
GeoHexGridAggregationBuilder::new
);

private static int parsePrecision(XContentParser parser) throws IOException, OpenSearchParseException {
final var token = parser.currentToken();
if (token.equals(XContentParser.Token.VALUE_NUMBER)) {
return XContentMapValues.nodeIntegerValue(parser.intValue());
}
final var precision = parser.text();
return XContentMapValues.nodeIntegerValue(precision);
}

protected GeoHexGridAggregationBuilder(
GeoGridAggregationBuilder clone,
AggregatorFactories.Builder factoriesBuilder,
Map<String, Object> metadata
) {
super(clone, factoriesBuilder, metadata);
}

public GeoHexGridAggregationBuilder(String name) {
super(name);
precision(DEFAULT_PRECISION);
size(DEFAULT_MAX_NUM_CELLS);
shardSize = DEFAULT_SHARD_SIZE;
}

public GeoHexGridAggregationBuilder(StreamInput in) throws IOException {
super(in);
}

/**
* Register's Geo Hex Aggregation
* @param builder Builder to register new Aggregation
*/
public static void registerAggregators(ValuesSourceRegistry.Builder builder) {
GeoHexGridAggregatorFactory.registerAggregators(builder);
}

@Override
public GeoGridAggregationBuilder precision(int precision) {
checkPrecisionRange(precision);
this.precision = precision;
return this;
}

@Override
protected ValuesSourceAggregatorFactory createFactory(
String name,
ValuesSourceConfig config,
int precision,
int requiredSize,
int shardSize,
GeoBoundingBox geoBoundingBox,
QueryShardContext queryShardContext,
AggregatorFactory aggregatorFactory,
AggregatorFactories.Builder builder,
Map<String, Object> metadata
) throws IOException {
return new GeoHexGridAggregatorFactory(
name,
config,
precision,
requiredSize,
shardSize,
geoBoundingBox,
queryShardContext,
aggregatorFactory,
builder,
metadata
);
}

@Override
protected ValuesSourceRegistry.RegistryKey<?> getRegistryKey() {
return REGISTRY_KEY;
}

@Override
protected AggregationBuilder shallowCopy(AggregatorFactories.Builder builder, Map<String, Object> metadata) {
return new GeoHexGridAggregationBuilder(this, builder, metadata);
}

@Override
public String getType() {
return NAME;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.geospatial.search.aggregations.bucket.geogrid;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.opensearch.geo.search.aggregations.bucket.geogrid.BaseGeoGridBucket;
import org.opensearch.geo.search.aggregations.bucket.geogrid.GeoGridAggregator;
import org.opensearch.search.aggregations.Aggregator;
import org.opensearch.search.aggregations.AggregatorFactories;
import org.opensearch.search.aggregations.CardinalityUpperBound;
import org.opensearch.search.aggregations.support.ValuesSource;
import org.opensearch.search.internal.SearchContext;

public class GeoHexGridAggregator extends GeoGridAggregator<GeoHexGrid> {

public GeoHexGridAggregator(
String name,
AggregatorFactories factories,
ValuesSource.Numeric valuesSource,
int requiredSize,
int shardSize,
SearchContext aggregationContext,
Aggregator parent,
CardinalityUpperBound cardinality,
Map<String, Object> metadata
) throws IOException {
super(name, factories, valuesSource, requiredSize, shardSize, aggregationContext, parent, cardinality, metadata);
}

@Override
protected GeoHexGrid buildAggregation(String name, int requiredSize, List<BaseGeoGridBucket> buckets, Map<String, Object> metadata) {
return new GeoHexGrid(name, requiredSize, buckets, metadata);
}

@Override
protected BaseGeoGridBucket newEmptyBucket() {
return new GeoHexGridBucket(0, 0, null);
}
}
Loading

0 comments on commit 3ab65e2

Please sign in to comment.