diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts
index 700f5a048fce2..0acbaf4aa02a2 100644
--- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts
+++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts
@@ -18,13 +18,13 @@
  */
 
 import { i18n } from '@kbn/i18n';
+import { geohashColumns } from 'ui/vis/map/decode_geo_hash';
 import chrome from '../../chrome';
 import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
 import { AutoPrecisionParamEditor } from '../../vis/editors/default/controls/auto_precision';
 import { UseGeocentroidParamEditor } from '../../vis/editors/default/controls/use_geocentroid';
 import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/controls/is_filtered_by_collar';
 import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision';
-import { geohashColumns } from '../../utils/decode_geo_hash';
 import { AggGroupNames } from '../../vis/editors/default/agg_groups';
 import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public';
 
diff --git a/src/legacy/ui/public/utils/range.d.ts b/src/legacy/ui/public/utils/range.d.ts
deleted file mode 100644
index c484c6f43eebb..0000000000000
--- a/src/legacy/ui/public/utils/range.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export function parseRange(input: string): Range;
-
-export interface Range {
-  min: number;
-  max: number;
-  minInclusive: boolean;
-  maxInclusive: boolean;
-  within(n: number): boolean;
-}
diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx
index 23e671180e980..a490669784e14 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx
@@ -21,7 +21,7 @@ import React, { useCallback } from 'react';
 
 import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
-import { Range } from '../../../../../../utils/range';
+import { Range } from './range';
 
 export interface NumberRowProps {
   autoFocus: boolean;
diff --git a/src/legacy/ui/public/utils/__tests__/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts
similarity index 66%
rename from src/legacy/ui/public/utils/__tests__/range.js
rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts
index e7947894d3e22..e9090e5b38ef7 100644
--- a/src/legacy/ui/public/utils/__tests__/range.js
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts
@@ -17,32 +17,30 @@
  * under the License.
  */
 
-import _ from 'lodash';
-import expect from '@kbn/expect';
-import { parseRange } from '../range';
+import { forOwn } from 'lodash';
+import { parseRange } from './range';
 
-describe('Range parsing utility', function () {
-
-  it('throws an error for inputs that are not formatted properly', function () {
-    expect(function () {
+describe('Range parsing utility', () => {
+  test('throws an error for inputs that are not formatted properly', () => {
+    expect(() => {
       parseRange('');
-    }).to.throwException(TypeError);
+    }).toThrowError(TypeError);
 
-    expect(function () {
+    expect(function() {
       parseRange('p10202');
-    }).to.throwException(TypeError);
+    }).toThrowError(TypeError);
 
-    expect(function () {
+    expect(function() {
       parseRange('{0,100}');
-    }).to.throwException(TypeError);
+    }).toThrowError(TypeError);
 
-    expect(function () {
+    expect(function() {
       parseRange('[0,100');
-    }).to.throwException(TypeError);
+    }).toThrowError(TypeError);
 
-    expect(function () {
+    expect(function() {
       parseRange(')0,100(');
-    }).to.throwException(TypeError);
+    }).toThrowError(TypeError);
   });
 
   const tests = {
@@ -51,52 +49,52 @@ describe('Range parsing utility', function () {
         min: 0,
         max: 100,
         minInclusive: true,
-        maxInclusive: true
+        maxInclusive: true,
       },
       within: [
         [0, true],
         [0.0000001, true],
         [1, true],
         [99.99999, true],
-        [100, true]
-      ]
+        [100, true],
+      ],
     },
     '(26.3   ,   42]': {
       props: {
         min: 26.3,
         max: 42,
         minInclusive: false,
-        maxInclusive: true
+        maxInclusive: true,
       },
       within: [
         [26.2999999, false],
         [26.3000001, true],
         [30, true],
         [41, true],
-        [42, true]
-      ]
+        [42, true],
+      ],
     },
     '(-50,50)': {
       props: {
         min: -50,
         max: 50,
         minInclusive: false,
-        maxInclusive: false
+        maxInclusive: false,
       },
       within: [
         [-50, false],
         [-49.99999, true],
         [0, true],
         [49.99999, true],
-        [50, false]
-      ]
+        [50, false],
+      ],
     },
     '(Infinity, -Infinity)': {
       props: {
         min: -Infinity,
         max: Infinity,
         minInclusive: false,
-        maxInclusive: false
+        maxInclusive: false,
       },
       within: [
         [0, true],
@@ -105,25 +103,24 @@ describe('Range parsing utility', function () {
         [-10000000000, true],
         [-Infinity, false],
         [Infinity, false],
-      ]
-    }
+      ],
+    },
   };
 
-  _.forOwn(tests, function (spec, str) {
-
-    describe(str, function () {
+  forOwn(tests, (spec, str: any) => {
+    // eslint-disable-next-line jest/valid-describe
+    describe(str, () => {
       const range = parseRange(str);
 
-      it('creation', function () {
-        expect(range).to.eql(spec.props);
+      it('creation', () => {
+        expect(range).toEqual(spec.props);
       });
 
-      spec.within.forEach(function (tup) {
-        it('#within(' + tup[0] + ')', function () {
-          expect(range.within(tup[0])).to.be(tup[1]);
+      spec.within.forEach((tup: any[]) => {
+        it('#within(' + tup[0] + ')', () => {
+          expect(range.within(tup[0])).toBe(tup[1]);
         });
       });
     });
-
   });
 });
diff --git a/src/legacy/ui/public/utils/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts
similarity index 60%
rename from src/legacy/ui/public/utils/range.js
rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts
index 54bd1b1903346..c401e9033294f 100644
--- a/src/legacy/ui/public/utils/range.js
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts
@@ -17,8 +17,6 @@
  * under the License.
  */
 
-import _ from 'lodash';
-
 /**
  * Regexp portion that matches our number
  *
@@ -44,41 +42,39 @@ const _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))';
  *
  * @type {RegExp}
  */
-const RANGE_RE = new RegExp('^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$');
+const RANGE_RE = new RegExp(
+  '^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$'
+);
+
+export class Range {
+  constructor(
+    public minInclusive: boolean,
+    public min: number,
+    public max: number,
+    public maxInclusive: boolean
+  ) {}
 
-export function parseRange(input) {
+  within(n: number): boolean {
+    if ((this.min === n && !this.minInclusive) || this.min > n) return false;
+    if ((this.max === n && !this.maxInclusive) || this.max < n) return false;
 
+    return true;
+  }
+}
+
+export function parseRange(input: string): Range {
   const match = String(input).match(RANGE_RE);
   if (!match) {
     throw new TypeError('expected input to be in interval notation e.g., (100, 200]');
   }
 
-  return new Range(
-    match[1] === '[',
-    parseFloat(match[2]),
-    parseFloat(match[3]),
-    match[4] === ']'
-  );
-}
-
-function Range(/* minIncl, min, max, maxIncl */) {
-  const args = _.toArray(arguments);
-  if (args[1] > args[2]) args.reverse();
+  const args = [match[1] === '[', parseFloat(match[2]), parseFloat(match[3]), match[4] === ']'];
 
-  this.minInclusive = args[0];
-  this.min = args[1];
-  this.max = args[2];
-  this.maxInclusive = args[3];
-}
-
-Range.prototype.within = function (n) {
-  if (this.min === n && !this.minInclusive) return false;
-  if (this.min > n) return false;
-
-  if (this.max === n && !this.maxInclusive) return false;
-  if (this.max < n) return false;
-
-  return true;
-};
+  if (args[1] > args[2]) {
+    args.reverse();
+  }
 
+  const [minInclusive, min, max, maxInclusive] = args;
 
+  return new Range(minInclusive as boolean, min as number, max as number, maxInclusive as boolean);
+}
diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
index c6772cc108762..948dddced7458 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts
@@ -27,7 +27,7 @@ import {
   getNextModel,
   getRange,
 } from './utils';
-import { Range } from '../../../../../../utils/range';
+import { Range } from './range';
 import { NumberRowModel } from './number_row';
 
 describe('NumberList utils', () => {
diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts
index 563e8f0a6a9b7..311c9264e2246 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts
+++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts
@@ -21,7 +21,7 @@ import { last } from 'lodash';
 import { i18n } from '@kbn/i18n';
 import { htmlIdGenerator } from '@elastic/eui';
 
-import { parseRange, Range } from '../../../../../../utils/range';
+import { parseRange, Range } from './range';
 import { NumberRowModel } from './number_row';
 
 const EMPTY_STRING = '';
diff --git a/src/legacy/ui/public/vis/map/convert_to_geojson.js b/src/legacy/ui/public/vis/map/convert_to_geojson.js
index 77896490678ff..14c282b58beda 100644
--- a/src/legacy/ui/public/vis/map/convert_to_geojson.js
+++ b/src/legacy/ui/public/vis/map/convert_to_geojson.js
@@ -17,10 +17,9 @@
  * under the License.
  */
 
-import { decodeGeoHash } from 'ui/utils/decode_geo_hash';
+import { decodeGeoHash } from './decode_geo_hash';
 import { gridDimensions } from './grid_dimensions';
 
-
 export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) {
 
   let features;
diff --git a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts
similarity index 75%
rename from src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js
rename to src/legacy/ui/public/vis/map/decode_geo_hash.test.ts
index 1ffe9ca7b4df2..c1ca7e4c80383 100644
--- a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js
+++ b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts
@@ -17,27 +17,18 @@
  * under the License.
  */
 
-import { geohashColumns, decodeGeoHash } from '../decode_geo_hash';
+import { geohashColumns, decodeGeoHash } from './decode_geo_hash';
 
-test('geohashColumns', function () {
+test('geohashColumns', () => {
   expect(geohashColumns(1)).toBe(8);
   expect(geohashColumns(2)).toBe(8 * 4);
   expect(geohashColumns(3)).toBe(8 * 4 * 8);
   expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4);
 });
 
-test('decodeGeoHash', function () {
+test('decodeGeoHash', () => {
   expect(decodeGeoHash('drm3btev3e86')).toEqual({
-    latitude: [
-      41.119999922811985,
-      41.12000009045005,
-      41.12000000663102,
-    ],
-    longitude: [
-      -71.34000029414892,
-      -71.3399999588728,
-      -71.34000012651086,
-    ],
+    latitude: [41.119999922811985, 41.12000009045005, 41.12000000663102],
+    longitude: [-71.34000029414892, -71.3399999588728, -71.34000012651086],
   });
 });
-
diff --git a/src/legacy/ui/public/utils/decode_geo_hash.ts b/src/legacy/ui/public/vis/map/decode_geo_hash.ts
similarity index 100%
rename from src/legacy/ui/public/utils/decode_geo_hash.ts
rename to src/legacy/ui/public/vis/map/decode_geo_hash.ts
diff --git a/src/legacy/ui/public/vis/map/kibana_map.js b/src/legacy/ui/public/vis/map/kibana_map.js
index dc57809b6570f..cb618444af7ce 100644
--- a/src/legacy/ui/public/vis/map/kibana_map.js
+++ b/src/legacy/ui/public/vis/map/kibana_map.js
@@ -22,7 +22,7 @@ import { createZoomWarningMsg } from './map_messages';
 import L from 'leaflet';
 import $ from 'jquery';
 import _ from 'lodash';
-import { zoomToPrecision } from '../../utils/zoom_to_precision';
+import { zoomToPrecision } from './zoom_to_precision';
 import { i18n } from '@kbn/i18n';
 import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin';
 
diff --git a/src/legacy/ui/public/utils/zoom_to_precision.js b/src/legacy/ui/public/vis/map/zoom_to_precision.ts
similarity index 52%
rename from src/legacy/ui/public/utils/zoom_to_precision.js
rename to src/legacy/ui/public/vis/map/zoom_to_precision.ts
index f5c16b640d127..552c509590286 100644
--- a/src/legacy/ui/public/utils/zoom_to_precision.js
+++ b/src/legacy/ui/public/vis/map/zoom_to_precision.ts
@@ -19,39 +19,42 @@
 
 import { geohashColumns } from './decode_geo_hash';
 
-const maxPrecision = 12;
-/**
- * Map Leaflet zoom levels to geohash precision levels.
- * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide.
- */
-
-
+const defaultMaxPrecision = 12;
+const minGeoHashPixels = 16;
 
-
-const zoomPrecisionMap = {};
-const minGeohashPixels = 16;
-
-function calculateZoomToPrecisionMap(maxZoom) {
+const calculateZoomToPrecisionMap = (maxZoom: number): Map<number, number> => {
+  /**
+   * Map Leaflet zoom levels to geohash precision levels.
+   * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide.
+   */
+  const zoomPrecisionMap = new Map();
 
   for (let zoom = 0; zoom <= maxZoom; zoom += 1) {
-    if (typeof zoomPrecisionMap[zoom] === 'number') {
+    if (typeof zoomPrecisionMap.get(zoom) === 'number') {
       continue;
     }
+
     const worldPixels = 256 * Math.pow(2, zoom);
-    zoomPrecisionMap[zoom] = 1;
-    for (let precision = 2; precision <= maxPrecision; precision += 1) {
+
+    zoomPrecisionMap.set(zoom, 1);
+
+    for (let precision = 2; precision <= defaultMaxPrecision; precision += 1) {
       const columns = geohashColumns(precision);
-      if ((worldPixels / columns) >= minGeohashPixels) {
-        zoomPrecisionMap[zoom] = precision;
+
+      if (worldPixels / columns >= minGeoHashPixels) {
+        zoomPrecisionMap.set(zoom, precision);
       } else {
         break;
       }
     }
   }
-}
 
+  return zoomPrecisionMap;
+};
+
+export function zoomToPrecision(mapZoom: number, maxPrecision: number, maxZoom: number) {
+  const zoomPrecisionMap = calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21);
+  const precision = zoomPrecisionMap.get(mapZoom);
 
-export function zoomToPrecision(mapZoom, maxPrecision, maxZoom) {
-  calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21);
-  return Math.min(zoomPrecisionMap[mapZoom], maxPrecision);
+  return precision ? Math.min(precision, maxPrecision) : maxPrecision;
 }