Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ Please note that [geo_shape data type](https://www.elastic.co/guide/en/elasticse

## Usage
### Install
Currently supported Elasticsearch version is 8.x.

Install plugin with:
`./bin/elasticsearch-plugin install https://github.com/opendatasoft/elasticsearch-aggregation-geoclustering/releases/download/v8.19.6.1/geopoint-clustering-aggregation-8.19.6.1.zip`
`./bin/elasticsearch-plugin install https://github.com/opendatasoft/elasticsearch-aggregation-geoclustering/releases/download/v8.19.6.2/geopoint-clustering-aggregation-8.19.6.2.zip`


### Quickstart
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
es_version = 8.19.6
plugin_version = 8.19.6.1
plugin_version = 8.19.6.2
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,12 @@ private void computeDistance(Bucket bucket, Bucket potentialNeighbor, List<Bucke
// Compute new weighted centroid
double newCentroidLat = (bucket.centroid.getLat() * bucket.docCount + potentialNeighbor.centroid.getLat()
* potentialNeighbor.docCount) / mergedDocCount;
double newCentroidLon = (bucket.centroid.getLon() * bucket.docCount + potentialNeighbor.centroid.getLon()
* potentialNeighbor.docCount) / mergedDocCount;
double newCentroidLon = computeWeightedCentroidLon(
bucket.centroid.getLon(),
bucket.docCount,
potentialNeighbor.centroid.getLon(),
potentialNeighbor.docCount
);
bucket.centroid = new GeoPoint(newCentroidLat, newCentroidLon);

// Update document count and reduce sub-aggregations
Expand All @@ -401,6 +405,37 @@ else if (revisit != null && ratio > 0 && neighborDistance / fixedRadius < ratio)
}
}

/**
* Computes the weighted centroid longitude, handling the antimeridian (±180°) correctly.
* When points are on opposite sides of the antimeridian, a simple average would place
* the centroid on the wrong side of the globe. This method detects such cases and
* adjusts the calculation accordingly.
*/
private static double computeWeightedCentroidLon(double lon1, long weight1, double lon2, long weight2) {
long totalWeight = weight1 + weight2;
Comment thread
folkvir marked this conversation as resolved.

// Check if points are on opposite sides of the antimeridian
// If the difference is greater than 180°, they cross the antimeridian
double lonDiff = Math.abs(lon1 - lon2);
if (lonDiff > 180) {
// Normalize longitudes to [0, 360) for calculation
double normalizedLon1 = lon1 < 0 ? lon1 + 360 : lon1;
double normalizedLon2 = lon2 < 0 ? lon2 + 360 : lon2;

// Compute weighted average in normalized space
double weightedLon = (normalizedLon1 * (double) weight1 + normalizedLon2 * (double) weight2) / (double) totalWeight;

// Convert back to [-180, 180] range
if (weightedLon > 180) {
weightedLon -= 360;
}
return weightedLon;
} else {
// Standard weighted average when not crossing antimeridian
return ((lon1 * (double) weight1 + lon2 * (double) weight2) / (double) totalWeight);
}
}

@Override
public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException {
builder.startArray(CommonFields.BUCKETS.getPreferredName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,92 @@ setup:
- match: { aggregations.gc.buckets.0.doc_count: 3 }
- match: { aggregations.gc.buckets.0.centroid.lat: 48.84307164698839 }
- match: { aggregations.gc.buckets.0.centroid.lon: 2.3428986221551895 }

---
"Test that that centroid calculation with points around the antimeridian is correct (negative result)":
- do:
indices.create:
index: antimeridian_index
body:
mappings:
"properties":
"point":
"type": "geo_point"

- do:
index:
index: antimeridian_index
id: 1
body: { "point": [-179.685, 66.138] }

- do:
index:
index: antimeridian_index
id: 2
body: { "point": [179.89293, 66.138] }

- do:
indices.refresh:
index: antimeridian_index

- do:
search:
index: antimeridian_index
body:
size: 0
aggs:
gc:
geo_point_clustering:
field: point
zoom: 1

- match: { aggregations.gc.buckets.0.centroid.lat: 66.13799999468029 }
- match: { aggregations.gc.buckets.0.centroid.lon: -179.8960350221023 }
Comment thread
5k4nd marked this conversation as resolved.


---
"Test that that centroid calculation with points around the antimeridian is correct (positive result)":
- do:
indices.create:
index: antimeridian_index
body:
mappings:
"properties":
"point":
"type": "geo_point"

- do:
index:
index: antimeridian_index
id: 1
body: { "point": [-179.685, 66.138] }

- do:
index:
index: antimeridian_index
id: 2
body: { "point": [179.89293, 66.138] }

- do:
index:
index: antimeridian_index
id: 3
body: { "point": [179.5, 66.138] }

- do:
indices.refresh:
index: antimeridian_index

- do:
search:
index: antimeridian_index
body:
size: 0
aggs:
gc:
geo_point_clustering:
field: point
zoom: 1

- match: { aggregations.gc.buckets.0.centroid.lat: 66.13799999468029 }
- match: { aggregations.gc.buckets.0.centroid.lon: 179.9026433005929 }