diff --git a/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js b/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js index 9ec130f84737f..50b161259fbd5 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js @@ -94,6 +94,14 @@ export default function TileMapVisType(Private, getAppState, courier, config) { { schema: 'metric', type: 'count' } ] }, + { + group: 'metrics', + name: 'centroid', + title: 'Geo Centroid', + aggFilter: 'geo_centroid', + min: 0, + max: 1 + }, { group: 'buckets', name: 'segment', diff --git a/src/fixtures/agg_resp/geohash_grid.js b/src/fixtures/agg_resp/geohash_grid.js index 33fe18ed298ea..8b55d3eb9417c 100644 --- a/src/fixtures/agg_resp/geohash_grid.js +++ b/src/fixtures/agg_resp/geohash_grid.js @@ -9,7 +9,8 @@ export default function GeoHashGridAggResponseFixture() { // aggs:[ // { schema: 'metric', type: 'avg', params: { field: 'bytes' } }, // { schema: 'split', type: 'terms', params: { field: '@tags', size: 10 } }, - // { schema: 'segment', type: 'geohash_grid', params: { field: 'geo.coordinates', precision: 3 } } + // { schema: 'segment', type: 'geohash_grid', params: { field: 'geo.coordinates', precision: 3 } }, + // { schema: 'centroid', type: 'geo_centroid', params: { field: 'geo.coordinates' } } // ], // params: { // isDesaturated: true, @@ -34,6 +35,8 @@ export default function GeoHashGridAggResponseFixture() { .sort() .map(function (geoHash) { const count = _.random(1, 5000); + const lat = _.random(-180, 180, true); + const lon = _.random(-180, 180, true); totalDocCount += count; docCount += count; @@ -43,6 +46,12 @@ export default function GeoHashGridAggResponseFixture() { doc_count: count, 1: { value: 2048 + i + }, + 4: { + location: { + lat: lat, + lon: lon + } } }; }); diff --git a/src/ui/public/agg_response/geo_json/__tests__/geo_json.js b/src/ui/public/agg_response/geo_json/__tests__/geo_json.js index 0d7b18f02abea..776ccd076c08e 100644 --- a/src/ui/public/agg_response/geo_json/__tests__/geo_json.js +++ b/src/ui/public/agg_response/geo_json/__tests__/geo_json.js @@ -29,7 +29,8 @@ describe('GeoJson Agg Response Converter', function () { aggs: [ { schema: 'metric', type: 'avg', params: { field: 'bytes' } }, { schema: 'split', type: 'terms', params: { field: '@tags' } }, - { schema: 'segment', type: 'geohash_grid', params: { field: 'geo.coordinates', precision: 3 } } + { schema: 'segment', type: 'geohash_grid', params: { field: 'geo.coordinates', precision: 3 } }, + { schema: 'centroid', type: 'geo_centroid', params: { field: 'geo.coordinates' } } ], params: { isDesaturated: true, @@ -40,7 +41,8 @@ describe('GeoJson Agg Response Converter', function () { aggs = { metric: vis.aggs[0], split: vis.aggs[1], - geo: vis.aggs[2] + geo: vis.aggs[2], + centroid: vis.aggs[3] }; })); @@ -112,12 +114,14 @@ describe('GeoJson Agg Response Converter', function () { let chart; let geoColI; let metricColI; + let centroidColI; before(function () { table = makeTable(); chart = makeSingleChart(table); geoColI = _.findIndex(table.columns, { aggConfig: aggs.geo }); metricColI = _.findIndex(table.columns, { aggConfig: aggs.metric }); + centroidColI = _.findIndex(table.columns, { aggConfig: aggs.centroid }); }); it('should be geoJson format', function () { @@ -145,10 +149,11 @@ describe('GeoJson Agg Response Converter', function () { it('should have value properties data', function () { table.rows.forEach(function (row, i) { const props = chart.geoJson.features[i].properties; - const keys = ['value', 'geohash', 'aggConfigResult', 'rectangle', 'center']; + const keys = ['value', 'geohash', 'aggConfigResult', 'rectangle', 'center', 'centroid']; expect(props).to.be.an('object'); expect(props).to.only.have.keys(keys); expect(props.geohash).to.be.a('string'); + expect(props.centroid).to.be.an('object'); if (props.value != null) expect(props.value).to.be.a('number'); }); }); @@ -157,7 +162,7 @@ describe('GeoJson Agg Response Converter', function () { table.rows.forEach(function (row, i) { const geometry = chart.geoJson.features[i].geometry; const props = chart.geoJson.features[i].properties; - expect(props.center).to.eql(geometry.coordinates.slice(0).reverse()); + expect(props.centroid).to.eql({ lat: geometry.coordinates[1], lon: geometry.coordinates[0] }); }); }); @@ -168,10 +173,12 @@ describe('GeoJson Agg Response Converter', function () { expect(props.aggConfigResult).to.be(row[metricColI]); expect(props.value).to.be(row[metricColI].value); expect(props.geohash).to.be(row[geoColI].value); + expect(props.centroid).to.be(row[centroidColI].value); } else { expect(props.aggConfigResult).to.be(null); expect(props.value).to.be(row[metricColI]); expect(props.geohash).to.be(row[geoColI]); + expect(props.centroid).to.be(row[centroidColI]); } }); }); diff --git a/src/ui/public/agg_response/geo_json/geo_json.js b/src/ui/public/agg_response/geo_json/geo_json.js index 07c02913db364..dde57563c0b2c 100644 --- a/src/ui/public/agg_response/geo_json/geo_json.js +++ b/src/ui/public/agg_response/geo_json/geo_json.js @@ -15,10 +15,11 @@ export default function TileMapConverterFn(Private, timefilter, $compile, $rootS const geoI = columnIndex('segment'); const metricI = columnIndex('metric'); + const centroidI = columnIndex('centroid'); const geoAgg = _.get(table.columns, [geoI, 'aggConfig']); const metricAgg = _.get(table.columns, [metricI, 'aggConfig']); - const features = rowsToFeatures(table, geoI, metricI); + const features = rowsToFeatures(table, geoI, metricI, centroidI); const values = features.map(function (feature) { return feature.properties.value; }); diff --git a/src/ui/public/agg_response/geo_json/rows_to_features.js b/src/ui/public/agg_response/geo_json/rows_to_features.js index 38e2757a50466..2d7617db244b4 100644 --- a/src/ui/public/agg_response/geo_json/rows_to_features.js +++ b/src/ui/public/agg_response/geo_json/rows_to_features.js @@ -10,7 +10,7 @@ function unwrap(val) { return getAcr(val) ? val.value : val; } -function convertRowsToFeatures(table, geoI, metricI) { +function convertRowsToFeatures(table, geoI, metricI, centroidI) { return _.transform(table.rows, function (features, row) { const geohash = unwrap(row[geoI]); if (!geohash) return; @@ -23,6 +23,16 @@ function convertRowsToFeatures(table, geoI, metricI) { location.longitude[2] ]; + // fetch geo centroid and use it as point of feature if it exists + let point = centerLatLng; + const centroid = unwrap(row[centroidI]); + if (centroid) { + point = [ + centroid.lat, + centroid.lon + ]; + } + // order is nw, ne, se, sw const rectangle = [ [location.latitude[0], location.longitude[0]], @@ -37,14 +47,15 @@ function convertRowsToFeatures(table, geoI, metricI) { type: 'Feature', geometry: { type: 'Point', - coordinates: centerLatLng.slice(0).reverse() + coordinates: point.slice(0).reverse() }, properties: { geohash: geohash, value: unwrap(row[metricI]), aggConfigResult: getAcr(row[metricI]), center: centerLatLng, - rectangle: rectangle + rectangle: rectangle, + centroid: centroid } }); }, []); diff --git a/src/ui/public/agg_types/index.js b/src/ui/public/agg_types/index.js index 4409b6a1fbda3..75d7895743ddf 100644 --- a/src/ui/public/agg_types/index.js +++ b/src/ui/public/agg_types/index.js @@ -11,6 +11,7 @@ import AggTypesMetricsStdDeviationProvider from 'ui/agg_types/metrics/std_deviat import AggTypesMetricsCardinalityProvider from 'ui/agg_types/metrics/cardinality'; import AggTypesMetricsPercentilesProvider from 'ui/agg_types/metrics/percentiles'; import AggTypesMetricsPercentileRanksProvider from 'ui/agg_types/metrics/percentile_ranks'; +import AggTypesMetricsGeoCentroidProvider from 'ui/agg_types/metrics/geo_centroid'; import AggTypesBucketsDateHistogramProvider from 'ui/agg_types/buckets/date_histogram'; import AggTypesBucketsHistogramProvider from 'ui/agg_types/buckets/histogram'; import AggTypesBucketsRangeProvider from 'ui/agg_types/buckets/range'; @@ -34,7 +35,8 @@ export default function AggTypeService(Private) { Private(AggTypesMetricsCardinalityProvider), Private(AggTypesMetricsPercentilesProvider), Private(AggTypesMetricsPercentileRanksProvider), - Private(AggTypesMetricsTopHitProvider) + Private(AggTypesMetricsTopHitProvider), + Private(AggTypesMetricsGeoCentroidProvider) ], buckets: [ Private(AggTypesBucketsDateHistogramProvider), diff --git a/src/ui/public/agg_types/metrics/geo_centroid.js b/src/ui/public/agg_types/metrics/geo_centroid.js new file mode 100644 index 0000000000000..a9e3370f612a1 --- /dev/null +++ b/src/ui/public/agg_types/metrics/geo_centroid.js @@ -0,0 +1,23 @@ +import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type'; + +export default function AggTypeMetricGeoCentroidProvider(Private) { + const MetricAggType = Private(AggTypesMetricsMetricAggTypeProvider); + + return new MetricAggType({ + name: 'geo_centroid', + title: 'Geo Centroid', + makeLabel: function (aggConfig) { + return 'Geo Centroid'; + }, + params: [ + { + name: 'field', + filterFieldTypes: 'geo_point' + } + ], + getValue: function (agg, bucket) { + return bucket[agg.id] && bucket[agg.id].location; + } + }); +} + diff --git a/src/ui/public/vis_maps/__tests__/tile_maps/markers.js b/src/ui/public/vis_maps/__tests__/tile_maps/markers.js index 8b345b17d7077..37cdee9b8fb75 100644 --- a/src/ui/public/vis_maps/__tests__/tile_maps/markers.js +++ b/src/ui/public/vis_maps/__tests__/tile_maps/markers.js @@ -293,7 +293,8 @@ describe('tilemaptest - Marker Tests', function () { const index = _.random(mapData.features.length - 1); const feature = mapData.features[index]; const featureValue = feature.properties.value; - const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); + // Reverse coordinates since _dataToHeatArray returns LatLng and geoJson coordinates are in LngLat + const featureArr = feature.geometry.coordinates.slice(0).reverse().concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); @@ -306,7 +307,8 @@ describe('tilemaptest - Marker Tests', function () { const index = _.random(mapData.features.length - 1); const feature = mapData.features[index]; const featureValue = feature.properties.value / max; - const featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); + // Reverse coordinates since _dataToHeatArray returns LatLng and geoJson coordinates are in LngLat + const featureArr = feature.geometry.coordinates.slice(0).reverse().concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); diff --git a/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js b/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js index 2410752864d98..9e5b357874546 100644 --- a/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js +++ b/src/ui/public/vis_maps/visualizations/marker_types/heatmap.js @@ -175,8 +175,8 @@ export default function HeatmapMarkerFactory(Private) { const self = this; return this.geoJson.features.map(function (feature) { - const lat = feature.properties.center[0]; - const lng = feature.properties.center[1]; + const lat = feature.geometry.coordinates[1]; + const lng = feature.geometry.coordinates[0]; let heatIntensity; if (!self._attr.heatNormalizeData) {