From b893cd133b3a94e027c74d80a923111e06876e87 Mon Sep 17 00:00:00 2001 From: Jacob Brandt Date: Wed, 28 Dec 2016 16:45:24 -0700 Subject: [PATCH 1/4] Geo centroid metric aggregation type added --- .../kbn_vislib_vis_types/public/tile_map.js | 8 ++++++++ .../public/agg_response/geo_json/geo_json.js | 3 ++- .../agg_response/geo_json/rows_to_features.js | 14 +++++++++++-- src/ui/public/agg_types/index.js | 4 +++- .../public/agg_types/metrics/geo_centroid.js | 20 +++++++++++++++++++ .../agg_types/metrics/metric_agg_type.js | 5 +++++ .../visualizations/marker_types/heatmap.js | 4 ++-- 7 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/ui/public/agg_types/metrics/geo_centroid.js 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..b002a11517c3b 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: 1, + max: 1 + }, { group: 'buckets', name: 'segment', 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..25029e949a1b8 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,7 +47,7 @@ function convertRowsToFeatures(table, geoI, metricI) { type: 'Feature', geometry: { type: 'Point', - coordinates: centerLatLng.slice(0).reverse() + coordinates: point.slice(0).reverse() }, properties: { geohash: geohash, 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..22f60a5988b14 --- /dev/null +++ b/src/ui/public/agg_types/metrics/geo_centroid.js @@ -0,0 +1,20 @@ +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' + } + ] + }); +} + diff --git a/src/ui/public/agg_types/metrics/metric_agg_type.js b/src/ui/public/agg_types/metrics/metric_agg_type.js index 12f712cce4f09..606634e9269fb 100644 --- a/src/ui/public/agg_types/metrics/metric_agg_type.js +++ b/src/ui/public/agg_types/metrics/metric_agg_type.js @@ -30,6 +30,11 @@ export default function MetricAggTypeProvider(Private) { // `Count` handles empty sets properly if (!bucket[agg.id] && isSettableToZero) return 0; + // Geo centroid metric type value is stored in property name 'location' + if ('geo_centroid'.indexOf(agg.__type.name) !== -1) { + return bucket[agg.id] && bucket[agg.id].location; + } + return bucket[agg.id] && bucket[agg.id].value; }; 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) { From a11f8e827d2ac38aced8d31632361d17b5f14bf6 Mon Sep 17 00:00:00 2001 From: Jacob Brandt Date: Thu, 29 Dec 2016 11:54:18 -0700 Subject: [PATCH 2/4] Fix Heatmaps marker tests related to _dataToHeatArray --- src/ui/public/vis_maps/__tests__/tile_maps/markers.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); }); }); From 294beedafcbb837e91e210d98dadb12ffde4195e Mon Sep 17 00:00:00 2001 From: Jacob Brandt Date: Fri, 30 Dec 2016 11:11:28 -0700 Subject: [PATCH 3/4] Allow geo centroid metric to be disabled or removed once added --- src/core_plugins/kbn_vislib_vis_types/public/tile_map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b002a11517c3b..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 @@ -99,7 +99,7 @@ export default function TileMapVisType(Private, getAppState, courier, config) { name: 'centroid', title: 'Geo Centroid', aggFilter: 'geo_centroid', - min: 1, + min: 0, max: 1 }, { From f060147b60abb65fee41524d51c8d397fb3dfca7 Mon Sep 17 00:00:00 2001 From: Jacob Brandt Date: Mon, 9 Jan 2017 14:15:22 -0700 Subject: [PATCH 4/4] Expand agg_response test for geoJson to include centroid data. --- src/fixtures/agg_resp/geohash_grid.js | 11 ++++++++++- .../agg_response/geo_json/__tests__/geo_json.js | 15 +++++++++++---- .../agg_response/geo_json/rows_to_features.js | 3 ++- src/ui/public/agg_types/metrics/geo_centroid.js | 5 ++++- .../public/agg_types/metrics/metric_agg_type.js | 5 ----- 5 files changed, 27 insertions(+), 12 deletions(-) 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/rows_to_features.js b/src/ui/public/agg_response/geo_json/rows_to_features.js index 25029e949a1b8..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 @@ -54,7 +54,8 @@ function convertRowsToFeatures(table, geoI, metricI, centroidI) { value: unwrap(row[metricI]), aggConfigResult: getAcr(row[metricI]), center: centerLatLng, - rectangle: rectangle + rectangle: rectangle, + centroid: centroid } }); }, []); diff --git a/src/ui/public/agg_types/metrics/geo_centroid.js b/src/ui/public/agg_types/metrics/geo_centroid.js index 22f60a5988b14..a9e3370f612a1 100644 --- a/src/ui/public/agg_types/metrics/geo_centroid.js +++ b/src/ui/public/agg_types/metrics/geo_centroid.js @@ -14,7 +14,10 @@ export default function AggTypeMetricGeoCentroidProvider(Private) { name: 'field', filterFieldTypes: 'geo_point' } - ] + ], + getValue: function (agg, bucket) { + return bucket[agg.id] && bucket[agg.id].location; + } }); } diff --git a/src/ui/public/agg_types/metrics/metric_agg_type.js b/src/ui/public/agg_types/metrics/metric_agg_type.js index 606634e9269fb..12f712cce4f09 100644 --- a/src/ui/public/agg_types/metrics/metric_agg_type.js +++ b/src/ui/public/agg_types/metrics/metric_agg_type.js @@ -30,11 +30,6 @@ export default function MetricAggTypeProvider(Private) { // `Count` handles empty sets properly if (!bucket[agg.id] && isSettableToZero) return 0; - // Geo centroid metric type value is stored in property name 'location' - if ('geo_centroid'.indexOf(agg.__type.name) !== -1) { - return bucket[agg.id] && bucket[agg.id].location; - } - return bucket[agg.id] && bucket[agg.id].value; };