UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

1,301 lines (1,092 loc) 126 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getDefaultFilter = getDefaultFilter; exports.shouldApplyFilter = shouldApplyFilter; exports.validatePolygonFilter = validatePolygonFilter; exports.validateFilter = validateFilter; exports.validateFilterWithData = validateFilterWithData; exports.getFilterProps = getFilterProps; exports.getFieldDomain = getFieldDomain; exports.getFilterFunction = getFilterFunction; exports.updateFilterDataId = updateFilterDataId; exports.filterDataset = filterDataset; exports.getFilterRecord = getFilterRecord; exports.diffFilters = diffFilters; exports.adjustValueToFilterDomain = adjustValueToFilterDomain; exports.getNumericFieldDomain = getNumericFieldDomain; exports.getNumericStepSize = getNumericStepSize; exports.getTimestampFieldDomain = getTimestampFieldDomain; exports.histogramConstruct = histogramConstruct; exports.getHistogram = getHistogram; exports.formatNumberByStep = formatNumberByStep; exports.isInRange = isInRange; exports.isInPolygon = isInPolygon; exports.getTimeWidgetTitleFormatter = getTimeWidgetTitleFormatter; exports.getTimeWidgetHintFormatter = getTimeWidgetHintFormatter; exports.isValidFilterValue = isValidFilterValue; exports.getFilterPlot = getFilterPlot; exports.getDefaultFilterPlotType = getDefaultFilterPlotType; exports.applyFiltersToDatasets = applyFiltersToDatasets; exports.applyFilterFieldName = applyFilterFieldName; exports.mergeFilterDomainStep = mergeFilterDomainStep; exports.generatePolygonFilter = generatePolygonFilter; exports.filterDatasetCPU = filterDatasetCPU; exports.getFilterIdInFeature = exports.featureToFilterValue = exports.getPolygonFilterFunctor = exports.LAYER_FILTERS = exports.FILTER_ID_LENGTH = exports.DEFAULT_FILTER_STRUCTURE = exports.FILTER_COMPONENTS = exports.LIMITED_FILTER_EFFECT_PROPS = exports.FILTER_UPDATER_PROPS = exports.PLOT_TYPES = exports.enlargedHistogramBins = exports.histogramBins = exports.TimestampStepMap = void 0; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _d3Array = require("d3-array"); var _keymirror = _interopRequireDefault(require("keymirror")); var _lodash = _interopRequireDefault(require("lodash.get")); var _booleanWithin = _interopRequireDefault(require("@turf/boolean-within")); var _helpers = require("@turf/helpers"); var _decimal = require("decimal.js"); var _defaultSettings = require("../constants/default-settings"); var _dataUtils = require("./data-utils"); var ScaleUtils = _interopRequireWildcard(require("./data-scale-utils")); var _constants = require("../constants"); var _utils = require("./utils"); var _gpuFilterUtils = require("./gpu-filter-utils"); var _FILTER_TYPES$timeRan, _FILTER_TYPES$range, _SupportedPlotType, _FILTER_COMPONENTS; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var TimestampStepMap = [{ max: 1, step: 0.05 }, { max: 10, step: 0.1 }, { max: 100, step: 1 }, { max: 500, step: 5 }, { max: 1000, step: 10 }, { max: 5000, step: 50 }, { max: Number.POSITIVE_INFINITY, step: 1000 }]; exports.TimestampStepMap = TimestampStepMap; var histogramBins = 30; exports.histogramBins = histogramBins; var enlargedHistogramBins = 100; exports.enlargedHistogramBins = enlargedHistogramBins; var durationSecond = 1000; var durationMinute = durationSecond * 60; var durationHour = durationMinute * 60; var durationDay = durationHour * 24; var durationWeek = durationDay * 7; var durationYear = durationDay * 365; var PLOT_TYPES = (0, _keymirror["default"])({ histogram: null, lineChart: null }); exports.PLOT_TYPES = PLOT_TYPES; var FILTER_UPDATER_PROPS = (0, _keymirror["default"])({ dataId: null, name: null, layerId: null }); exports.FILTER_UPDATER_PROPS = FILTER_UPDATER_PROPS; var LIMITED_FILTER_EFFECT_PROPS = (0, _keymirror["default"])((0, _defineProperty2["default"])({}, FILTER_UPDATER_PROPS.name, null)); /** * Max number of filter value buffers that deck.gl provides */ exports.LIMITED_FILTER_EFFECT_PROPS = LIMITED_FILTER_EFFECT_PROPS; var SupportedPlotType = (_SupportedPlotType = {}, (0, _defineProperty2["default"])(_SupportedPlotType, _defaultSettings.FILTER_TYPES.timeRange, (_FILTER_TYPES$timeRan = { "default": 'histogram' }, (0, _defineProperty2["default"])(_FILTER_TYPES$timeRan, _defaultSettings.ALL_FIELD_TYPES.integer, 'lineChart'), (0, _defineProperty2["default"])(_FILTER_TYPES$timeRan, _defaultSettings.ALL_FIELD_TYPES.real, 'lineChart'), _FILTER_TYPES$timeRan)), (0, _defineProperty2["default"])(_SupportedPlotType, _defaultSettings.FILTER_TYPES.range, (_FILTER_TYPES$range = { "default": 'histogram' }, (0, _defineProperty2["default"])(_FILTER_TYPES$range, _defaultSettings.ALL_FIELD_TYPES.integer, 'lineChart'), (0, _defineProperty2["default"])(_FILTER_TYPES$range, _defaultSettings.ALL_FIELD_TYPES.real, 'lineChart'), _FILTER_TYPES$range)), _SupportedPlotType); var FILTER_COMPONENTS = (_FILTER_COMPONENTS = {}, (0, _defineProperty2["default"])(_FILTER_COMPONENTS, _defaultSettings.FILTER_TYPES.select, 'SingleSelectFilter'), (0, _defineProperty2["default"])(_FILTER_COMPONENTS, _defaultSettings.FILTER_TYPES.multiSelect, 'MultiSelectFilter'), (0, _defineProperty2["default"])(_FILTER_COMPONENTS, _defaultSettings.FILTER_TYPES.timeRange, 'TimeRangeFilter'), (0, _defineProperty2["default"])(_FILTER_COMPONENTS, _defaultSettings.FILTER_TYPES.range, 'RangeFilter'), (0, _defineProperty2["default"])(_FILTER_COMPONENTS, _defaultSettings.FILTER_TYPES.polygon, 'PolygonFilter'), _FILTER_COMPONENTS); exports.FILTER_COMPONENTS = FILTER_COMPONENTS; var DEFAULT_FILTER_STRUCTURE = { dataId: [], // [string] freeze: false, id: null, // time range filter specific fixedDomain: false, enlarged: false, isAnimating: false, speed: 1, // field specific name: [], // string type: null, fieldIdx: [], // [integer] domain: null, value: null, // plot plotType: PLOT_TYPES.histogram, yAxis: null, interval: null, // mode gpu: false }; exports.DEFAULT_FILTER_STRUCTURE = DEFAULT_FILTER_STRUCTURE; var FILTER_ID_LENGTH = 4; exports.FILTER_ID_LENGTH = FILTER_ID_LENGTH; var LAYER_FILTERS = [_defaultSettings.FILTER_TYPES.polygon]; /** * Generates a filter with a dataset id as dataId * @param {[string]} dataId * @return {object} filter */ exports.LAYER_FILTERS = LAYER_FILTERS; function getDefaultFilter(dataId) { return _objectSpread({}, DEFAULT_FILTER_STRUCTURE, { // store it as dataId and it could be one or many dataId: (0, _utils.toArray)(dataId), id: (0, _utils.generateHashId)(FILTER_ID_LENGTH) }); } /** * Check if a filter is valid based on the given dataId * @param {object} filter to validate * @param {string} dataset id to validate filter against * @return {boolean} true if a filter is valid, false otherwise */ function shouldApplyFilter(filter, datasetId) { var dataIds = (0, _utils.toArray)(filter.dataId); return dataIds.includes(datasetId) && filter.value !== null; } /** * Validates and modifies polygon filter structure * @param dataset * @param filter * @param layers * @return {object} */ function validatePolygonFilter(dataset, filter, layers) { var failed = { dataset: dataset, filter: null }; var value = filter.value, layerId = filter.layerId, type = filter.type, dataId = filter.dataId; if (!layerId || !isValidFilterValue(type, value)) { return failed; } var isValidDataset = dataId.includes(dataset.id); if (!isValidDataset) { return failed; } var layer = layers.find(function (l) { return layerId.includes(l.id); }); if (!layer) { return failed; } return { filter: _objectSpread({}, filter, { freeze: true, fieldIdx: [] }), dataset: dataset }; } /** * Custom filter validators * @type {Function} */ var filterValidators = (0, _defineProperty2["default"])({}, _defaultSettings.FILTER_TYPES.polygon, validatePolygonFilter); /** * Default validate filter function * @param dataset * @param filter * @return {*} */ function validateFilter(dataset, filter) { // match filter.dataId var failed = { dataset: dataset, filter: null }; var filterDataId = (0, _utils.toArray)(filter.dataId); var filterDatasetIndex = filterDataId.indexOf(dataset.id); if (filterDatasetIndex < 0) { // the current filter is not mapped against the current dataset return failed; } var initializeFilter = _objectSpread({}, getDefaultFilter(filter.dataId), {}, filter, { dataId: filterDataId, name: (0, _utils.toArray)(filter.name) }); var fieldName = initializeFilter.name[filterDatasetIndex]; var _applyFilterFieldName = applyFilterFieldName(initializeFilter, dataset, fieldName, filterDatasetIndex, { mergeDomain: true }), updatedFilter = _applyFilterFieldName.filter, updatedDataset = _applyFilterFieldName.dataset; if (!updatedFilter) { return failed; } updatedFilter.value = adjustValueToFilterDomain(filter.value, updatedFilter); if (updatedFilter.value === null) { // cannot adjust saved value to filter return failed; } return { filter: validateFilterYAxis(updatedFilter, updatedDataset), dataset: updatedDataset }; } /** * Validate saved filter config with new data, * calculate domain and fieldIdx based new fields and data * * @param {Object} dataset * @param {Object} filter - filter to be validate * @return {Object | null} - validated filter */ function validateFilterWithData(dataset, filter, layers) { return filterValidators.hasOwnProperty(filter.type) ? filterValidators[filter.type](dataset, filter, layers) : validateFilter(dataset, filter); } /** * Validate YAxis * @param filter * @param dataset * @return {*} */ function validateFilterYAxis(filter, dataset) { // TODO: validate yAxis against other datasets var fields = dataset.fields, allData = dataset.allData; var _filter = filter, yAxis = _filter.yAxis; // TODO: validate yAxis against other datasets if (yAxis) { var matchedAxis = fields.find(function (_ref) { var name = _ref.name, type = _ref.type; return name === yAxis.name && type === yAxis.type; }); filter = matchedAxis ? _objectSpread({}, filter, { yAxis: matchedAxis }, getFilterPlot(_objectSpread({}, filter, { yAxis: matchedAxis }), allData)) : filter; } return filter; } /** * Get default filter prop based on field type * * @param {Array<Array>} allData * @param {Object} field * @returns {Object} default filter */ function getFilterProps(allData, field) { var filterProps = _objectSpread({}, getFieldDomain(allData, field), { fieldType: field.type }); switch (field.type) { case _defaultSettings.ALL_FIELD_TYPES.real: case _defaultSettings.ALL_FIELD_TYPES.integer: return _objectSpread({}, filterProps, { value: filterProps.domain, type: _defaultSettings.FILTER_TYPES.range, typeOptions: [_defaultSettings.FILTER_TYPES.range], gpu: true }); case _defaultSettings.ALL_FIELD_TYPES["boolean"]: return _objectSpread({}, filterProps, { type: _defaultSettings.FILTER_TYPES.select, value: true, gpu: false }); case _defaultSettings.ALL_FIELD_TYPES.string: case _defaultSettings.ALL_FIELD_TYPES.date: return _objectSpread({}, filterProps, { type: _defaultSettings.FILTER_TYPES.multiSelect, value: [], gpu: false }); case _defaultSettings.ALL_FIELD_TYPES.timestamp: return _objectSpread({}, filterProps, { type: _defaultSettings.FILTER_TYPES.timeRange, enlarged: true, fixedDomain: true, value: filterProps.domain, gpu: true }); default: return {}; } } /** * Calculate field domain based on field type and data * * @param {Array<Array>} allData * @param {Object} field * @returns {Object} with domain as key */ function getFieldDomain(allData, field) { var fieldIdx = field.tableFieldIndex - 1; var isTime = field.type === _defaultSettings.ALL_FIELD_TYPES.timestamp; var valueAccessor = _dataUtils.maybeToDate.bind(null, isTime, fieldIdx, field.format); var domain; switch (field.type) { case _defaultSettings.ALL_FIELD_TYPES.real: case _defaultSettings.ALL_FIELD_TYPES.integer: // calculate domain and step return getNumericFieldDomain(allData, valueAccessor); case _defaultSettings.ALL_FIELD_TYPES["boolean"]: return { domain: [true, false] }; case _defaultSettings.ALL_FIELD_TYPES.string: case _defaultSettings.ALL_FIELD_TYPES.date: domain = ScaleUtils.getOrdinalDomain(allData, valueAccessor); return { domain: domain }; case _defaultSettings.ALL_FIELD_TYPES.timestamp: return getTimestampFieldDomain(allData, valueAccessor); default: return { domain: ScaleUtils.getOrdinalDomain(allData, valueAccessor) }; } } var getPolygonFilterFunctor = function getPolygonFilterFunctor(layer, filter) { var getPosition = layer.getPositionAccessor(); switch (layer.type) { case _constants.LAYER_TYPES.point: case _constants.LAYER_TYPES.icon: return function (data) { var pos = getPosition({ data: data }); return pos.every(Number.isFinite) && isInPolygon(pos, filter.value); }; case _constants.LAYER_TYPES.arc: case _constants.LAYER_TYPES.line: return function (data) { var pos = getPosition({ data: data }); return pos.every(Number.isFinite) && [[pos[0], pos[1]], [pos[3], pos[4]]].every(function (point) { return isInPolygon(point, filter.value); }); }; default: return function () { return true; }; } }; /** * @param field dataset Field * @param dataId Dataset id * @param filter Filter object * @param layers list of layers to filter upon * @return {*} */ exports.getPolygonFilterFunctor = getPolygonFilterFunctor; function getFilterFunction(field, dataId, filter, layers) { // field could be null var valueAccessor = function valueAccessor(data) { return field ? data[field.tableFieldIndex - 1] : null; }; switch (filter.type) { case _defaultSettings.FILTER_TYPES.range: return function (data) { return isInRange(valueAccessor(data), filter.value); }; case _defaultSettings.FILTER_TYPES.multiSelect: return function (data) { return filter.value.includes(valueAccessor(data)); }; case _defaultSettings.FILTER_TYPES.select: return function (data) { return valueAccessor(data) === filter.value; }; case _defaultSettings.FILTER_TYPES.timeRange: var mappedValue = (0, _lodash["default"])(field, ['filterProps', 'mappedValue']); var accessor = Array.isArray(mappedValue) ? function (data, index) { return mappedValue[index]; } : function (data) { return (0, _dataUtils.timeToUnixMilli)(valueAccessor(data), field.format); }; return function (data, index) { return isInRange(accessor(data, index), filter.value); }; case _defaultSettings.FILTER_TYPES.polygon: if (!layers || !layers.length) { return function () { return true; }; } var layerFilterFunctions = filter.layerId.map(function (id) { return layers.find(function (l) { return l.id === id; }); }).filter(function (l) { return l && l.config.dataId === dataId; }).map(function (layer) { return getPolygonFilterFunctor(layer, filter); }); return function (data) { return layerFilterFunctions.every(function (filterFunc) { return filterFunc(data); }); }; default: return function () { return true; }; } } function updateFilterDataId(dataId) { return getDefaultFilter(dataId); } /** * Filter data based on an array of filters * * @param {Object} dataset * @param {Array<Object>} filters * @param {Object} opt * @param {Object} opt.cpuOnly only allow cpu filtering * @param {Object} opt.ignoreDomain ignore filter for domain calculation * @returns {Object} dataset * @returns {Array<Number>} dataset.filteredIndex * @returns {Array<Number>} dataset.filteredIndexForDomain */ function filterDataset(dataset, filters, layers) { var opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var allData = dataset.allData, dataId = dataset.id, oldFilterRecord = dataset.filterRecord, fields = dataset.fields; // if there is no filters var filterRecord = getFilterRecord(dataId, filters, opt); var newDataset = (0, _utils.set)(['filterRecord'], filterRecord, dataset); if (!filters.length) { return _objectSpread({}, newDataset, { gpuFilter: (0, _gpuFilterUtils.getGpuFilterProps)(filters, dataId, fields), filteredIndex: dataset.allIndexes, filteredIndexForDomain: dataset.allIndexes }); } var changedFilters = diffFilters(filterRecord, oldFilterRecord); // generate 2 sets of filter result // filteredIndex used to calculate layer data // filteredIndexForDomain used to calculate layer Domain var shouldCalDomain = Boolean(changedFilters.dynamicDomain); var shouldCalIndex = Boolean(changedFilters.cpu); var filterResult = {}; if (shouldCalDomain || shouldCalIndex) { var dynamicDomainFilters = shouldCalDomain ? filterRecord.dynamicDomain : null; var cpuFilters = shouldCalIndex ? filterRecord.cpu : null; var filterFuncs = filters.reduce(function (acc, filter) { var fieldIndex = (0, _gpuFilterUtils.getDatasetFieldIndexForFilter)(dataset.id, filter); var field = fieldIndex !== -1 ? fields[fieldIndex] : null; return _objectSpread({}, acc, (0, _defineProperty2["default"])({}, filter.id, getFilterFunction(field, dataset.id, filter, layers))); }, {}); filterResult = filterDataByFilterTypes({ dynamicDomainFilters: dynamicDomainFilters, cpuFilters: cpuFilters, filterFuncs: filterFuncs }, allData); } return _objectSpread({}, newDataset, {}, filterResult, { gpuFilter: (0, _gpuFilterUtils.getGpuFilterProps)(filters, dataId, fields) }); } /** * * @param {Object} filters * @param {Array|null} filters.dynamicDomainFilters * @param {Array|null} filters.cpuFilters * @param {Object} filters.filterFuncs * @returns {{filteredIndex: Array, filteredIndexForDomain: Array}} filteredIndex and filteredIndexForDomain */ function filterDataByFilterTypes(_ref2, allData) { var dynamicDomainFilters = _ref2.dynamicDomainFilters, cpuFilters = _ref2.cpuFilters, filterFuncs = _ref2.filterFuncs; var result = _objectSpread({}, dynamicDomainFilters ? { filteredIndexForDomain: [] } : {}, {}, cpuFilters ? { filteredIndex: [] } : {}); var _loop = function _loop(i) { var d = allData[i]; var matchForDomain = dynamicDomainFilters && dynamicDomainFilters.every(function (filter) { return filterFuncs[filter.id](d, i); }); if (matchForDomain) { result.filteredIndexForDomain.push(i); } var matchForRender = cpuFilters && cpuFilters.every(function (filter) { return filterFuncs[filter.id](d, i); }); if (matchForRender) { result.filteredIndex.push(i); } }; for (var i = 0; i < allData.length; i++) { _loop(i); } return result; } /** * Get a record of filters based on domain type and gpu / cpu * @param {string} dataId * @param {Array<Object>} filters * @param {Object} opt.cpuOnly only allow cpu filtering * @param {Object} opt.ignoreDomain ignore filter for domain calculation * @returns {{dynamicDomain: Array, fixedDomain: Array, cpu: Array, gpu: Array}} filterRecord */ function getFilterRecord(dataId, filters) { var opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var filterRecord = { dynamicDomain: [], fixedDomain: [], cpu: [], gpu: [] }; filters.forEach(function (f) { if (isValidFilterValue(f.type, f.value) && (0, _utils.toArray)(f.dataId).includes(dataId)) { (f.fixedDomain || opt.ignoreDomain ? filterRecord.fixedDomain : filterRecord.dynamicDomain).push(f); (f.gpu && !opt.cpuOnly ? filterRecord.gpu : filterRecord.cpu).push(f); } }); return filterRecord; } /** * Compare filter records to get what has changed * @param {Object} filterRecord * @param {Object} oldFilterRecord * @returns {{dynamicDomain: Object, fixedDomain: Object, cpu: Object, gpu: Object}} changed filters based on type */ function diffFilters(filterRecord) { var oldFilterRecord = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var filterChanged = {}; Object.entries(filterRecord).forEach(function (_ref3) { var _ref4 = (0, _slicedToArray2["default"])(_ref3, 2), record = _ref4[0], items = _ref4[1]; items.forEach(function (filter) { var oldFilter = (oldFilterRecord[record] || []).find(function (f) { return f.id === filter.id; }); if (!oldFilter) { // added filterChanged = (0, _utils.set)([record, filter.id], 'added', filterChanged); } else { // check what has changed ['name', 'value', 'dataId'].forEach(function (prop) { if (filter[prop] !== oldFilter[prop]) { filterChanged = (0, _utils.set)([record, filter.id], "".concat(prop, "_changed"), filterChanged); } }); } }); (oldFilterRecord[record] || []).forEach(function (oldFilter) { // deleted if (!items.find(function (f) { return f.id === oldFilter.id; })) { filterChanged = (0, _utils.set)([record, oldFilter.id], 'deleted', filterChanged); } }); if (!filterChanged[record]) { filterChanged[record] = null; } }); return filterChanged; } /** * Call by parsing filters from URL * Check if value of filter within filter domain, if not adjust it to match * filter domain * * @param {Array<string> | string | Number | Array<Number>} value * @param {Array} filter.domain * @param {String} filter.type * @returns {*} - adjusted value to match filter or null to remove filter */ /* eslint-disable complexity */ function adjustValueToFilterDomain(value, _ref5) { var domain = _ref5.domain, type = _ref5.type; if (!domain || !type) { return false; } switch (type) { case _defaultSettings.FILTER_TYPES.range: case _defaultSettings.FILTER_TYPES.timeRange: if (!Array.isArray(value) || value.length !== 2) { return domain.map(function (d) { return d; }); } return value.map(function (d, i) { return (0, _dataUtils.notNullorUndefined)(d) && isInRange(d, domain) ? d : domain[i]; }); case _defaultSettings.FILTER_TYPES.multiSelect: if (!Array.isArray(value)) { return []; } var filteredValue = value.filter(function (d) { return domain.includes(d); }); return filteredValue.length ? filteredValue : []; case _defaultSettings.FILTER_TYPES.select: return domain.includes(value) ? value : true; default: return null; } } /* eslint-enable complexity */ /** * Calculate numeric domain and suitable step * * @param {Object[]} data * @param {function} valueAccessor * @returns {object} domain and step */ function getNumericFieldDomain(data, valueAccessor) { var domain = [0, 1]; var step = 0.1; var mappedValue = Array.isArray(data) ? data.map(valueAccessor) : []; if (Array.isArray(data) && data.length > 1) { domain = ScaleUtils.getLinearDomain(mappedValue); var diff = domain[1] - domain[0]; // in case equal domain, [96, 96], which will break quantize scale if (!diff) { domain[1] = domain[0] + 1; } step = getNumericStepSize(diff) || step; domain[0] = formatNumberByStep(domain[0], step, 'floor'); domain[1] = formatNumberByStep(domain[1], step, 'ceil'); } var _getHistogram = getHistogram(domain, mappedValue), histogram = _getHistogram.histogram, enlargedHistogram = _getHistogram.enlargedHistogram; return { domain: domain, step: step, histogram: histogram, enlargedHistogram: enlargedHistogram }; } function getNumericStepSize(diff) { diff = Math.abs(diff); if (diff > 100) { return 1; } else if (diff > 3) { return 0.01; } else if (diff > 1) { return 0.001; } else if (diff <= 1) { // Try to get at least 1000 steps - and keep the step size below that of // the (diff > 1) case. var x = diff / 1000; // Find the exponent and truncate to 10 to the power of that exponent var exponentialForm = x.toExponential(); var exponent = parseFloat(exponentialForm.split('e')[1]); // Getting ready for node 12 // this is why we need decimal.js // Math.pow(10, -5) = 0.000009999999999999999 // the above result shows in browser and node 10 // node 12 behaves correctly return new _decimal.Decimal(10).pow(exponent).toNumber(); } } /** * Calculate timestamp domain and suitable step * * @param {Array<Array>} data * @param {Function} valueAccessor * @returns {{ * domain: Array<Number>, * step: Number, * mappedValue: Array<Number>, * histogram: Array<Object>, * enlargedHistogram: Array<Object> * }} timestamp field domain */ function getTimestampFieldDomain(data, valueAccessor) { // to avoid converting string format time to epoch // every time we compare we store a value mapped to int in filter domain var mappedValue = Array.isArray(data) ? data.map(valueAccessor) : []; var domain = ScaleUtils.getLinearDomain(mappedValue); var step = 0.01; var diff = domain[1] - domain[0]; var entry = TimestampStepMap.find(function (f) { return f.max >= diff; }); if (entry) { step = entry.step; } var _getHistogram2 = getHistogram(domain, mappedValue), histogram = _getHistogram2.histogram, enlargedHistogram = _getHistogram2.enlargedHistogram; return { domain: domain, step: step, mappedValue: mappedValue, histogram: histogram, enlargedHistogram: enlargedHistogram }; } /** * * @param {Array<Number>} domain * @param {Array<Number>} mappedValue * @param {Number} bins * @returns {Array<{count: Number, x0: Number, x1: number}>} histogram */ function histogramConstruct(domain, mappedValue, bins) { return (0, _d3Array.histogram)().thresholds((0, _d3Array.ticks)(domain[0], domain[1], bins)).domain(domain)(mappedValue).map(function (bin) { return { count: bin.length, x0: bin.x0, x1: bin.x1 }; }); } /** * Calculate histogram from domain and array of values * * @param {Array<Number>} domain * @param {Array<Object>} mappedValue * @returns {{histogram: Array<Object>, enlargedHistogram: Array<Object>}} 2 sets of histogram */ function getHistogram(domain, mappedValue) { var histogram = histogramConstruct(domain, mappedValue, histogramBins); var enlargedHistogram = histogramConstruct(domain, mappedValue, enlargedHistogramBins); return { histogram: histogram, enlargedHistogram: enlargedHistogram }; } /** * round number based on step * * @param {Number} val * @param {Number} step * @param {string} bound * @returns {Number} rounded number */ function formatNumberByStep(val, step, bound) { if (bound === 'floor') { return Math.floor(val * (1 / step)) / (1 / step); } return Math.ceil(val * (1 / step)) / (1 / step); } function isInRange(val, domain) { if (!Array.isArray(domain)) { return false; } return val >= domain[0] && val <= domain[1]; } /** * Determines whether a point is within the provided polygon * * @param point as input search [lat, lng] * @param polygon Points must be within these (Multi)Polygon(s) * @return {boolean} */ function isInPolygon(point, polygon) { return (0, _booleanWithin["default"])((0, _helpers.point)(point), polygon); } function getTimeWidgetTitleFormatter(domain) { if (!Array.isArray(domain)) { return null; } var diff = domain[1] - domain[0]; return diff > durationYear ? 'MM/DD/YY' : diff > durationDay ? 'MM/DD/YY hh:mma' : 'MM/DD/YY hh:mm:ssa'; } function getTimeWidgetHintFormatter(domain) { if (!Array.isArray(domain)) { return null; } var diff = domain[1] - domain[0]; return diff > durationYear ? 'MM/DD/YY' : diff > durationWeek ? 'MM/DD' : diff > durationDay ? 'MM/DD hha' : diff > durationHour ? 'hh:mma' : 'hh:mm:ssa'; } /** * Sanity check on filters to prepare for save * @param {String} type - filter type * @param {*} value - filter value * @returns {boolean} whether filter is value */ /* eslint-disable complexity */ function isValidFilterValue(type, value) { if (!type) { return false; } switch (type) { case _defaultSettings.FILTER_TYPES.select: return value === true || value === false; case _defaultSettings.FILTER_TYPES.range: case _defaultSettings.FILTER_TYPES.timeRange: return Array.isArray(value) && value.every(function (v) { return v !== null && !isNaN(v); }); case _defaultSettings.FILTER_TYPES.multiSelect: return Array.isArray(value) && Boolean(value.length); case _defaultSettings.FILTER_TYPES.input: return Boolean(value.length); case _defaultSettings.FILTER_TYPES.polygon: var coordinates = (0, _lodash["default"])(value, ['geometry', 'coordinates']); return Boolean(value && value.id && coordinates); default: return true; } } function getFilterPlot(filter, allData) { if (filter.plotType === PLOT_TYPES.histogram || !filter.yAxis) { // histogram should be calculated when create filter return {}; } var mappedValue = filter.mappedValue; var yAxis = filter.yAxis; // return lineChart var series = allData.map(function (d, i) { return { x: mappedValue[i], y: d[yAxis.tableFieldIndex - 1] }; }).filter(function (_ref6) { var x = _ref6.x, y = _ref6.y; return Number.isFinite(x) && Number.isFinite(y); }).sort(function (a, b) { return (0, _d3Array.ascending)(a.x, b.x); }); var yDomain = (0, _d3Array.extent)(series, function (d) { return d.y; }); var xDomain = [series[0].x, series[series.length - 1].x]; return { lineChart: { series: series, yDomain: yDomain, xDomain: xDomain }, yAxis: yAxis }; } function getDefaultFilterPlotType(filter) { var filterPlotTypes = SupportedPlotType[filter.type]; if (!filterPlotTypes) { return null; } if (!filter.yAxis) { return filterPlotTypes["default"]; } return filterPlotTypes[filter.yAxis.type] || null; } /** * * @param datasetIds list of dataset ids to be filtered * @param datasets all datasets * @param filters all filters to be applied to datasets * @return {{[datasetId: string]: Object}} datasets - new updated datasets */ function applyFiltersToDatasets(datasetIds, datasets, filters, layers) { var dataIds = (0, _utils.toArray)(datasetIds); return dataIds.reduce(function (acc, dataId) { var layersToFilter = (layers || []).filter(function (l) { return l.config.dataId === dataId; }); var appliedFilters = filters.filter(function (d) { return shouldApplyFilter(d, dataId); }); return _objectSpread({}, acc, (0, _defineProperty2["default"])({}, dataId, filterDataset(datasets[dataId], appliedFilters, layersToFilter))); }, datasets); } /** * Applies a new field name value to fielter and update both filter and dataset * @param {Object} filter - to be applied the new field name on * @param {Object} dataset - dataset the field belongs to * @param {string} fieldName - field.name * @param {Number} filterDatasetIndex - field.name * @param {Number} filters - current * @param {Object} option * @return {Object} {filter, datasets} */ function applyFilterFieldName(filter, dataset, fieldName) { var filterDatasetIndex = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var _ref7 = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}, _ref7$mergeDomain = _ref7.mergeDomain, mergeDomain = _ref7$mergeDomain === void 0 ? false : _ref7$mergeDomain; // using filterDatasetIndex we can filter only the specified dataset var fields = dataset.fields, allData = dataset.allData; var fieldIndex = fields.findIndex(function (f) { return f.name === fieldName; }); // if no field with same name is found, move to the next datasets if (fieldIndex === -1) { // throw new Error(`fieldIndex not found. Dataset must contain a property with name: ${fieldName}`); return { filter: null, dataset: dataset }; } // TODO: validate field type var field = fields[fieldIndex]; var filterProps = field.hasOwnProperty('filterProps') ? field.filterProps : getFilterProps(allData, field); var newFilter = _objectSpread({}, mergeDomain ? mergeFilterDomainStep(filter, filterProps) : _objectSpread({}, filter, {}, filterProps), { name: Object.assign([].concat(filter.name), (0, _defineProperty2["default"])({}, filterDatasetIndex, field.name)), fieldIdx: Object.assign([].concat(filter.fieldIdx), (0, _defineProperty2["default"])({}, filterDatasetIndex, field.tableFieldIndex - 1)), // TODO, since we allow to add multiple fields to a filter we can no longer freeze the filter freeze: true }); var fieldWithFilterProps = _objectSpread({}, field, { filterProps: filterProps }); var newFields = Object.assign([].concat(fields), (0, _defineProperty2["default"])({}, fieldIndex, fieldWithFilterProps)); return { filter: newFilter, dataset: _objectSpread({}, dataset, { fields: newFields }) }; } /** * Merge one filter with other filter prop domain * @param filter * @param filterProps * @param fieldIndex * @param datasetIndex * @return {*} */ /* eslint-disable complexity */ function mergeFilterDomainStep(filter, filterProps) { if (!filter) { return null; } if (!filterProps) { return filter; } if (filter.fieldType && filter.fieldType !== filterProps.fieldType || !filterProps.domain) { return filter; } var combinedDomain = !filter.domain ? filterProps.domain : [].concat((0, _toConsumableArray2["default"])(filter.domain || []), (0, _toConsumableArray2["default"])(filterProps.domain || [])).sort(function (a, b) { return a - b; }); var newFilter = _objectSpread({}, filter, {}, filterProps, { domain: [combinedDomain[0], combinedDomain[combinedDomain.length - 1]] }); switch (filterProps.fieldType) { case _defaultSettings.ALL_FIELD_TYPES.string: case _defaultSettings.ALL_FIELD_TYPES.date: return _objectSpread({}, newFilter, { domain: (0, _dataUtils.unique)(combinedDomain).sort() }); case _defaultSettings.ALL_FIELD_TYPES.timestamp: var step = filter.step < filterProps.step ? filter.step : filterProps.step; return _objectSpread({}, newFilter, { step: step }); case _defaultSettings.ALL_FIELD_TYPES.real: case _defaultSettings.ALL_FIELD_TYPES.integer: default: return newFilter; } } /* eslint-enable complexity */ var featureToFilterValue = function featureToFilterValue(feature, filterId) { var properties = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return _objectSpread({}, feature, { id: feature.id, properties: _objectSpread({}, feature.properties, {}, properties, { filterId: filterId }) }); }; exports.featureToFilterValue = featureToFilterValue; var getFilterIdInFeature = function getFilterIdInFeature(f) { return (0, _lodash["default"])(f, ['properties', 'filterId']); }; /** * Generates polygon filter * @param layers array of layers * @param feature polygon to use * @return {object} filter */ exports.getFilterIdInFeature = getFilterIdInFeature; function generatePolygonFilter(layers, feature) { var _layers$reduce = layers.reduce(function (acc, layer) { return _objectSpread({}, acc, { dataId: [].concat((0, _toConsumableArray2["default"])(acc.dataId), [layer.config.dataId]), layerId: [].concat((0, _toConsumableArray2["default"])(acc.layerId), [layer.id]), name: [].concat((0, _toConsumableArray2["default"])(acc.name), [layer.config.label]) }); }, { dataId: [], layerId: [], name: [] }), dataId = _layers$reduce.dataId, layerId = _layers$reduce.layerId, name = _layers$reduce.name; var filter = getDefaultFilter(dataId); return _objectSpread({}, filter, { fixedDomain: true, type: _defaultSettings.FILTER_TYPES.polygon, name: name, layerId: layerId, value: featureToFilterValue(feature, filter.id, { isVisible: true }) }); } /** * Run filter entirely on CPU * @param {Object} state - visState * @param {string} dataId * @return {Object} state state with updated datasets */ function filterDatasetCPU(state, dataId) { var datasetFilters = state.filters.filter(function (f) { return f.dataId.includes(dataId); }); var selectedDataset = state.datasets[dataId]; if (!selectedDataset) { return state; } var opt = { cpuOnly: true, ignoreDomain: true }; if (!datasetFilters.length) { // no filter var _filtered = _objectSpread({}, selectedDataset, { filteredIdxCPU: selectedDataset.allIndexes, filterRecordCPU: getFilterRecord(dataId, state.filters, opt) }); return (0, _utils.set)(['datasets', dataId], _filtered, state); } // no gpu filter if (!datasetFilters.find(function (f) { return f.gpu; })) { var _filtered2 = _objectSpread({}, selectedDataset, { filteredIdxCPU: selectedDataset.filteredIndex, filterRecordCPU: getFilterRecord(dataId, state.filters, opt) }); return (0, _utils.set)(['datasets', dataId], _filtered2, state); } // make a copy for cpu filtering var copied = _objectSpread({}, selectedDataset, { filterRecord: selectedDataset.filterRecordCPU, filteredIndex: selectedDataset.filteredIdxCPU }); var filtered = filterDataset(copied, state.filters, state.layers, opt); var cpuFilteredDataset = _objectSpread({}, selectedDataset, { filteredIdxCPU: filtered.filteredIndex, filterRecordCPU: filtered.filterRecord }); return (0, _utils.set)(['datasets', dataId], cpuFilteredDataset, state); } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9maWx0ZXItdXRpbHMuanMiXSwibmFtZXMiOlsiVGltZXN0YW1wU3RlcE1hcCIsIm1heCIsInN0ZXAiLCJOdW1iZXIiLCJQT1NJVElWRV9JTkZJTklUWSIsImhpc3RvZ3JhbUJpbnMiLCJlbmxhcmdlZEhpc3RvZ3JhbUJpbnMiLCJkdXJhdGlvblNlY29uZCIsImR1cmF0aW9uTWludXRlIiwiZHVyYXRpb25Ib3VyIiwiZHVyYXRpb25EYXkiLCJkdXJhdGlvbldlZWsiLCJkdXJhdGlvblllYXIiLCJQTE9UX1RZUEVTIiwiaGlzdG9ncmFtIiwibGluZUNoYXJ0IiwiRklMVEVSX1VQREFURVJfUFJPUFMiLCJkYXRhSWQiLCJuYW1lIiwibGF5ZXJJZCIsIkxJTUlURURfRklMVEVSX0VGRkVDVF9QUk9QUyIsIlN1cHBvcnRlZFBsb3RUeXBlIiwiRklMVEVSX1RZUEVTIiwidGltZVJhbmdlIiwiQUxMX0ZJRUxEX1RZUEVTIiwiaW50ZWdlciIsInJlYWwiLCJyYW5nZSIsIkZJTFRFUl9DT01QT05FTlRTIiwic2VsZWN0IiwibXVsdGlTZWxlY3QiLCJwb2x5Z29uIiwiREVGQVVMVF9GSUxURVJfU1RSVUNUVVJFIiwiZnJlZXplIiwiaWQiLCJmaXhlZERvbWFpbiIsImVubGFyZ2VkIiwiaXNBbmltYXRpbmciLCJzcGVlZCIsInR5cGUiLCJmaWVsZElkeCIsImRvbWFpbiIsInZhbHVlIiwicGxvdFR5cGUiLCJ5QXhpcyIsImludGVydmFsIiwiZ3B1IiwiRklMVEVSX0lEX0xFTkdUSCIsIkxBWUVSX0ZJTFRFUlMiLCJnZXREZWZhdWx0RmlsdGVyIiwic2hvdWxkQXBwbHlGaWx0ZXIiLCJmaWx0ZXIiLCJkYXRhc2V0SWQiLCJkYXRhSWRzIiwiaW5jbHVkZXMiLCJ2YWxpZGF0ZVBvbHlnb25GaWx0ZXIiLCJkYXRhc2V0IiwibGF5ZXJzIiwiZmFpbGVkIiwiaXNWYWxpZEZpbHRlclZhbHVlIiwiaXNWYWxpZERhdGFzZXQiLCJsYXllciIsImZpbmQiLCJsIiwiZmlsdGVyVmFsaWRhdG9ycyIsInZhbGlkYXRlRmlsdGVyIiwiZmlsdGVyRGF0YUlkIiwiZmlsdGVyRGF0YXNldEluZGV4IiwiaW5kZXhPZiIsImluaXRpYWxpemVGaWx0ZXIiLCJmaWVsZE5hbWUiLCJhcHBseUZpbHRlckZpZWxkTmFtZSIsIm1lcmdlRG9tYWluIiwidXBkYXRlZEZpbHRlciIsInVwZGF0ZWREYXRhc2V0IiwiYWRqdXN0VmFsdWVUb0ZpbHRlckRvbWFpbiIsInZhbGlkYXRlRmlsdGVyWUF4aXMiLCJ2YWxpZGF0ZUZpbHRlcldpdGhEYXRhIiwiaGFzT3duUHJvcGVydHkiLCJmaWVsZHMiLCJhbGxEYXRhIiwibWF0Y2hlZEF4aXMiLCJnZXRGaWx0ZXJQbG90IiwiZ2V0RmlsdGVyUHJvcHMiLCJmaWVsZCIsImZpbHRlclByb3BzIiwiZ2V0RmllbGREb21haW4iLCJmaWVsZFR5cGUiLCJ0eXBlT3B0aW9ucyIsInN0cmluZyIsImRhdGUiLCJ0aW1lc3RhbXAiLCJ0YWJsZUZpZWxkSW5kZXgiLCJpc1RpbWUiLCJ2YWx1ZUFjY2Vzc29yIiwibWF5YmVUb0RhdGUiLCJiaW5kIiwiZm9ybWF0IiwiZ2V0TnVtZXJpY0ZpZWxkRG9tYWluIiwiU2NhbGVVdGlscyIsImdldE9yZGluYWxEb21haW4iLCJnZXRUaW1lc3RhbXBGaWVsZERvbWFpbiIsImdldFBvbHlnb25GaWx0ZXJGdW5jdG9yIiwiZ2V0UG9zaXRpb24iLCJnZXRQb3NpdGlvbkFjY2Vzc29yIiwiTEFZRVJfVFlQRVMiLCJwb2ludCIsImljb24iLCJkYXRhIiwicG9zIiwiZXZlcnkiLCJpc0Zpbml0ZSIsImlzSW5Qb2x5Z29uIiwiYXJjIiwibGluZSIsImdldEZpbHRlckZ1bmN0aW9uIiwiaXNJblJhbmdlIiwibWFwcGVkVmFsdWUiLCJhY2Nlc3NvciIsIkFycmF5IiwiaXNBcnJheSIsImluZGV4IiwibGVuZ3RoIiwibGF5ZXJGaWx0ZXJGdW5jdGlvbnMiLCJtYXAiLCJjb25maWciLCJmaWx0ZXJGdW5jIiwidXBkYXRlRmlsdGVyRGF0YUlkIiwiZmlsdGVyRGF0YXNldCIsImZpbHRlcnMiLCJvcHQiLCJvbGRGaWx0ZXJSZWNvcmQiLCJmaWx0ZXJSZWNvcmQiLCJnZXRGaWx0ZXJSZWNvcmQiLCJuZXdEYXRhc2V0IiwiZ3B1RmlsdGVyIiwiZmlsdGVyZWRJbmRleCIsImFsbEluZGV4ZXMiLCJmaWx0ZXJlZEluZGV4Rm9yRG9tYWluIiwiY2hhbmdlZEZpbHRlcnMiLCJkaWZmRmlsdGVycyIsInNob3VsZENhbERvbWFpbiIsIkJvb2xlYW4iLCJkeW5hbWljRG9tYWluIiwic2hvdWxkQ2FsSW5kZXgiLCJjcHUiLCJmaWx0ZXJSZXN1bHQiLCJkeW5hbWljRG9tYWluRmlsdGVycyIsImNwdUZpbHRlcnMiLCJmaWx0ZXJGdW5jcyIsInJlZHVjZSIsImFjYyIsImZpZWxkSW5kZXgiLCJmaWx0ZXJEYXRhQnlGaWx0ZXJUeXBlcyIsInJlc3VsdCIsImkiLCJkIiwibWF0Y2hGb3JEb21haW4iLCJwdXNoIiwibWF0Y2hGb3JSZW5kZXIiLCJmb3JFYWNoIiwiZiIsImlnbm9yZURvbWFpbiIsImNwdU9ubHkiLCJmaWx0ZXJDaGFuZ2VkIiwiT2JqZWN0IiwiZW50cmllcyIsInJlY29yZCIsIml0ZW1zIiwib2xkRmlsdGVyIiwicHJvcCIsImZpbHRlcmVkVmFsdWUiLCJnZXRMaW5lYXJEb21haW4iLCJkaWZmIiwiZ2V0TnVtZXJpY1N0ZXBTaXplIiwiZm9ybWF0TnVtYmVyQnlTdGVwIiwiZ2V0SGlzdG9ncmFtIiwiZW5sYXJnZWRIaXN0b2dyYW0iLCJNYXRoIiwiYWJzIiwieCIsImV4cG9uZW50aWFsRm9ybSIsInRvRXhwb25lbnRpYWwiLCJleHBvbmVudCIsInBhcnNlRmxvYXQiLCJzcGxpdCIsIkRlY2ltYWwiLCJwb3ciLCJ0b051bWJlciIsImVudHJ5IiwiaGlzdG9ncmFtQ29uc3RydWN0IiwiYmlucyIsInRocmVzaG9sZHMiLCJiaW4iLCJjb3VudCIsIngwIiwieDEiLCJ2YWwiLCJib3VuZCIsImZsb29yIiwiY2VpbCIsImdldFRpbWVXaWRnZXRUaXRsZUZvcm1hdHRlciIsImdldFRpbWVXaWRnZXRIaW50Rm9ybWF0dGVyIiwidiIsImlzTmFOIiwiaW5wdXQiLCJjb29yZGluYXRlcyIsInNlcmllcyIsInkiLCJzb3J0IiwiYSIsImIiLCJ5RG9tYWluIiwieERvbWFpbiIsImdldERlZmF1bHRGaWx0ZXJQbG90VHlwZSIsImZpbHRlclBsb3RUeXBlcyIsImFwcGx5RmlsdGVyc1RvRGF0YXNldHMiLCJkYXRhc2V0SWRzIiwiZGF0YXNldHMiLCJsYXllcnNUb0ZpbHRlciIsImFwcGxpZWRGaWx0ZXJzIiwiZmluZEluZGV4IiwibmV3RmlsdGVyIiwibWVyZ2VGaWx0ZXJEb21haW5TdGVwIiwiYXNzaWduIiwiY29uY2F0IiwiZmllbGRXaXRoRmlsdGVyUHJvcHMiLCJuZXdGaWVsZHMiLCJjb21iaW5lZERvbWFpbiIsImZlYXR1cmVUb0ZpbHRlclZhbHVlIiwiZmVhdHVyZSIsImZpbHRlcklkIiwicHJvcGVydGllcyIsImdldEZpbHRlcklkSW5GZWF0dXJlIiwiZ2VuZXJhdGVQb2x5Z29uRmlsdGVyIiwibGFiZWwiLCJpc1Zpc2libGUiLCJmaWx0ZXJEYXRhc2V0Q1BVIiwic3RhdGUiLCJkYXRhc2V0RmlsdGVycyIsInNlbGVjdGVkRGF0YXNldCIsImZpbHRlcmVkIiwiZmlsdGVyZWRJZHhDUFUiLCJmaWx0ZXJSZWNvcmRDUFUiLCJjb3BpZWQiLCJjcHVGaWx0ZXJlZERhdGFzZXQiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQW9CQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7QUFDQTs7Ozs7Ozs7QUFFTyxJQUFNQSxnQkFBZ0IsR0FBRyxDQUM5QjtBQUFDQyxFQUFBQSxHQUFHLEVBQUUsQ0FBTjtBQUFTQyxFQUFBQSxJQUFJLEVBQUU7QUFBZixDQUQ4QixFQUU5QjtBQUFDRCxFQUFBQSxHQUFHLEVBQUUsRUFBTjtBQUFV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