Skip to content

Commit

Permalink
[Feat] add h3 typed column (#2822)
Browse files Browse the repository at this point in the history
Detect H3 column as a new h3-type column, like the existing geocolumn.

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
  • Loading branch information
igorDykhta authored Dec 10, 2024
1 parent c5d42dd commit cef3faf
Show file tree
Hide file tree
Showing 17 changed files with 123 additions and 40 deletions.
20 changes: 18 additions & 2 deletions src/common-utils/src/data-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {ALL_FIELD_TYPES} from '@kepler.gl/constants';
import {console as globalConsole} from 'global/window';
import {range} from 'd3-array';
import {isHexWkb, notNullorUndefined} from './data';
import {h3IsValid} from './h3-utils';

const H3_ANALYZER_TYPE = 'H3';

export const ACCEPTED_ANALYZER_TYPES = [
AnalyzerDATA_TYPES.DATE,
Expand All @@ -22,7 +25,8 @@ export const ACCEPTED_ANALYZER_TYPES = [
AnalyzerDATA_TYPES.PAIR_GEOMETRY_FROM_STRING,
AnalyzerDATA_TYPES.ZIPCODE,
AnalyzerDATA_TYPES.ARRAY,
AnalyzerDATA_TYPES.OBJECT
AnalyzerDATA_TYPES.OBJECT,
H3_ANALYZER_TYPE
];

const IGNORE_DATA_TYPES = Object.keys(AnalyzerDATA_TYPES).filter(
Expand Down Expand Up @@ -127,6 +131,8 @@ export function analyzerTypeToFieldType(aType: string): string {
case STRING:
case ZIPCODE:
return ALL_FIELD_TYPES.string;
case H3_ANALYZER_TYPE:
return ALL_FIELD_TYPES.h3;
default:
globalConsole.warn(`Unsupported analyzer type: ${aType}`);
return ALL_FIELD_TYPES.string;
Expand Down Expand Up @@ -198,7 +204,17 @@ export function getFieldsFromData(data: RowData, fieldOrder: string[]): Field[]
let type = fieldMeta?.type || 'STRING';
const format = fieldMeta?.format || '';

// check if string is hex wkb
// quick check if first valid string in column is H3
if (type === AnalyzerDATA_TYPES.STRING) {
for (let i = 0, n = data.length; i < n; ++i) {
if (notNullorUndefined(data[i][name])) {
type = h3IsValid(data[i][name] || '') ? H3_ANALYZER_TYPE : type;
break;
}
}
}

// quick check if string is hex wkb
if (type === AnalyzerDATA_TYPES.STRING) {
type = data.some(d => isHexWkb(d[name])) ? AnalyzerDATA_TYPES.GEOMETRY : type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import {h3GetResolution, H3Index, h3IsValid, h3ToGeo, h3ToGeoBoundary} from 'h3-js';
import {ALL_FIELD_TYPES} from '@kepler.gl/constants';
import {notNullorUndefined} from '@kepler.gl/common-utils';

export {h3GetResolution, h3IsValid};

Expand Down Expand Up @@ -38,12 +37,7 @@ export function idToPolygonGeo(object?: {id: H3Index}, properties?: any) {
}

export const isHexField = (field, fieldIdx, dataContainer) => {

Check warning on line 39 in src/common-utils/src/h3-utils.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'fieldIdx' is defined but never used

Check warning on line 39 in src/common-utils/src/h3-utils.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'dataContainer' is defined but never used
if (field.type !== ALL_FIELD_TYPES.string) {
return false;
}

const firstDP = dataContainer.find(d => notNullorUndefined(d.valueAt(fieldIdx)), true);
return firstDP && h3IsValid(firstDP.valueAt(fieldIdx));
return field.type === ALL_FIELD_TYPES.h3;
};

export const getHexFields = (fields, dataContainer) =>
Expand Down
3 changes: 3 additions & 0 deletions src/common-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
export * from './data';
export * from './data-type';
export * from './string';

export {getCentroid, getHexFields, h3IsValid, idToPolygonGeo} from './h3-utils';
export type {Centroid} from './h3-utils';
3 changes: 1 addition & 2 deletions src/components/src/map/map-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {injectIntl, IntlShape} from 'react-intl';
import {FormattedMessage} from '@kepler.gl/localization';
import {RootContext} from '../context';
import {parseGeoJsonRawFeature} from '@kepler.gl/layers';
import {idToPolygonGeo} from '@kepler.gl/utils';
import {generateHashId} from '@kepler.gl/common-utils';
import {generateHashId, idToPolygonGeo} from '@kepler.gl/common-utils';
import {LAYER_TYPES} from '@kepler.gl/constants';
import {LayerHoverProp} from '@kepler.gl/reducers';
import {Feature, FeatureSelectionContext} from '@kepler.gl/types';
Expand Down
18 changes: 17 additions & 1 deletion src/constants/src/default-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,8 @@ export const ALL_FIELD_TYPES = keyMirror({
point: null,
array: null,
object: null,
geoarrow: null
geoarrow: null,
h3: null
});

// Data Table
Expand Down Expand Up @@ -563,6 +564,10 @@ export const FIELD_TYPE_DISPLAY = {
[ALL_FIELD_TYPES.object]: {
label: 'object',
color: GREEN2
},
[ALL_FIELD_TYPES.h3]: {
label: 'h3',
color: BLUE
}
};

Expand Down Expand Up @@ -809,6 +814,17 @@ export const FIELD_OPTS = {
legend: () => '...',
tooltip: []
}
},
[ALL_FIELD_TYPES.h3]: {
type: 'h3',
scale: {
...notSupportedScaleOpts,
...notSupportAggrOpts
},
format: {
legend: d => '...',
tooltip: []
}
}
};

Expand Down
27 changes: 17 additions & 10 deletions src/layers/src/arc-layer/arc-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ import {GeoArrowArcLayer} from '@kepler.gl/deckgl-arrow-layers';
import {FilterArrowExtension} from '@kepler.gl/deckgl-layers';
import {ArcLayer as DeckArcLayer} from '@deck.gl/layers';

import {hexToRgb, maybeHexToGeo, DataContainerInterface, ArrowDataContainer} from '@kepler.gl/utils';
import {
hexToRgb,
DataContainerInterface,
maybeHexToGeo,
ArrowDataContainer
} from '@kepler.gl/utils';
import ArcLayerIcon from './arc-layer-icon';
import {isLayerHoveredFromArrow, createGeoArrowPointVector, getFilteredIndex} from '../layer-utils';
import {
DEFAULT_LAYER_COLOR,
ColorRange,
PROJECTED_PIXEL_SIZE_MULTIPLIER
PROJECTED_PIXEL_SIZE_MULTIPLIER,
ALL_FIELD_TYPES
} from '@kepler.gl/constants';

import {
Expand Down Expand Up @@ -138,10 +144,11 @@ const DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;
const brushingExtension = new BrushingExtension();
const arrowCPUFilterExtension = new FilterArrowExtension();

function isOtherFieldString(columns, allFields, key) {
function isH3Field(columns, allFields, key) {
const field = allFields[columns[key].fieldIdx];
return field && field.type === 'string';
return field?.type === ALL_FIELD_TYPES.h3;
}

export const arcPosAccessor =
({lat0, lng0, lat1, lng1, lat, lng, geoarrow0, geoarrow1}: ArcLayerColumnsConfig, columnMode) =>
(dc: DataContainerInterface) => {
Expand Down Expand Up @@ -259,12 +266,12 @@ export default class ArcLayer extends Layer {
// if one of the lat or lng column is string type, we allow it
// will try to pass it as hex
return {
lat0: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lng0'),
lng0: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lat0'),
lat1: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lng1'),
lng1: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lat1'),
lat: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lng'),
lng: (column, columns, allFields) => isOtherFieldString(columns, allFields, 'lat')
lat0: (column, columns, allFields) => isH3Field(columns, allFields, 'lng0'),
lng0: (column, columns, allFields) => isH3Field(columns, allFields, 'lat0'),
lat1: (column, columns, allFields) => isH3Field(columns, allFields, 'lng1'),
lng1: (column, columns, allFields) => isH3Field(columns, allFields, 'lat1'),
lat: (column, columns, allFields) => isH3Field(columns, allFields, 'lng'),
lng: (column, columns, allFields) => isH3Field(columns, allFields, 'lat')
};
}

Expand Down
8 changes: 3 additions & 5 deletions src/layers/src/h3-hexagon-layer/h3-hexagon-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ import {
idToPolygonGeo,
h3IsValid,
getHexFields,
Centroid,
findDefaultColorField,
DataContainerInterface,
createDataContainer
} from '@kepler.gl/utils';
Centroid
} from '@kepler.gl/common-utils';
import {findDefaultColorField, DataContainerInterface, createDataContainer} from '@kepler.gl/utils';
import H3HexagonLayerIcon from './h3-hexagon-layer-icon';
import {
CHANNEL_SCALES,
Expand Down
2 changes: 1 addition & 1 deletion src/layers/src/hexagon-layer/hexagon-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import {WebMercatorViewport} from '@deck.gl/core';
import Console from 'global/console';
import {Centroid} from '@kepler.gl/utils';
import type {Centroid} from '@kepler.gl/common-utils';

export function hexagonToPolygonGeo(object, properties, radius, mapState) {
const viewport = new WebMercatorViewport(mapState);
Expand Down
11 changes: 9 additions & 2 deletions src/processors/src/data-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
hasOwnProperty,
isPlainObject
} from '@kepler.gl/utils';
import {notNullorUndefined, toArray} from '@kepler.gl/common-utils';
import {
analyzerTypeToFieldType,
getSampleForTypeAnalyze,
getFieldsFromData,
analyzerTypeToFieldType
h3IsValid,
notNullorUndefined,
toArray
} from '@kepler.gl/common-utils';
import {KeplerGlSchema, ParsedDataset, SavedMap, LoadedMap} from '@kepler.gl/schemas';
import {Feature} from '@nebula.gl/edit-modes';
Expand Down Expand Up @@ -66,6 +68,11 @@ export const PARSE_FIELD_VALUE_FROM_STRING = {
[ALL_FIELD_TYPES.array]: {
valid: Array.isArray,
parse: tryParseJsonString
},

[ALL_FIELD_TYPES.h3]: {
valid: d => h3IsValid(d),
parse: d => d
}
};

Expand Down
1 change: 1 addition & 0 deletions src/table/src/kepler-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ class KeplerTable<F extends Field = Field> {
return {domain: [true, false]};

case ALL_FIELD_TYPES.string:
case ALL_FIELD_TYPES.h3:
case ALL_FIELD_TYPES.date:
domain = getOrdinalDomain(dataContainer, valueAccessor);
return {domain};
Expand Down
3 changes: 2 additions & 1 deletion src/utils/src/data-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ export const FIELD_DISPLAY_FORMAT: {
: '',
[ALL_FIELD_TYPES.geoarrow]: d => d,
[ALL_FIELD_TYPES.object]: JSON.stringify,
[ALL_FIELD_TYPES.array]: JSON.stringify
[ALL_FIELD_TYPES.array]: JSON.stringify,
[ALL_FIELD_TYPES.h3]: defaultFormatter
};

/**
Expand Down
8 changes: 7 additions & 1 deletion src/utils/src/dataset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
ProtoDataset
} from '@kepler.gl/types';
import {TooltipFormat} from '@kepler.gl/constants';
import {notNullorUndefined} from '@kepler.gl/common-utils';
import {notNullorUndefined, h3IsValid} from '@kepler.gl/common-utils';

import {isPlainObject} from './utils';
import {getFormatter} from './data-utils';
Expand Down Expand Up @@ -272,6 +272,12 @@ export function validateInputData(data: ProtoDataset['data']): ProcessorResult {
return analyzedType && analyzedType.category === 'TIME' && analyzedType.format === f.format;
}

// check existing string field is H3 type
if (f.type === ALL_FIELD_TYPES.string) {
const sample = findNonEmptyRowsAtField(rows, i, 10).map(r => r[i]);
return sample.every(item => !h3IsValid(item));
}

return true;
});

Expand Down
5 changes: 3 additions & 2 deletions src/utils/src/filter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ import {
AnimationConfig
} from '@kepler.gl/types';

import {generateHashId, toArray, notNullorUndefined} from '@kepler.gl/common-utils';
import {generateHashId, toArray, notNullorUndefined, getCentroid} from '@kepler.gl/common-utils';
import {DataContainerInterface} from './data-container-interface';
import {set} from './utils';
import {timeToUnixMilli, unique} from './data-utils';
import {getCentroid} from './h3-utils';
import {updateTimeFilterPlotType, updateRangeFilterPlotType} from './plot';
import {KeplerTableModel} from './types';

Expand Down Expand Up @@ -340,6 +339,7 @@ export function getFilterProps(
};

case ALL_FIELD_TYPES.string:
case ALL_FIELD_TYPES.h3:
case ALL_FIELD_TYPES.date:
// @ts-expect-error
return {
Expand Down Expand Up @@ -996,6 +996,7 @@ export function mergeFilterDomainStep(

switch (filterProps.fieldType) {
case ALL_FIELD_TYPES.string:
case ALL_FIELD_TYPES.h3:
case ALL_FIELD_TYPES.date:
return {
...newFilter,
Expand Down
3 changes: 0 additions & 3 deletions src/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,6 @@ export type {ColorBreak, ColorBreakOrdinal, DomainQuantiles, DomainStops} from '

export {DataRow} from './data-row';

export {getCentroid, getHexFields, h3IsValid, idToPolygonGeo} from './h3-utils';
export type {Centroid} from './h3-utils';

// Application config
export {getApplicationConfig, initApplicationConfig} from './application-config';
export type {KeplerApplicationConfig, MapLibInstance} from './application-config';
2 changes: 1 addition & 1 deletion test/browser/layer-tests/h3-hexagon-layer-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
hexagonIdLayerMeta
} from 'test/helpers/layer-utils';
import {KeplerGlLayers, h3DefaultElevation as defaultElevation} from '@kepler.gl/layers';
import {getCentroid, idToPolygonGeo} from '@kepler.gl/utils';
import {getCentroid, idToPolygonGeo} from '@kepler.gl/common-utils';

import {copyTableAndUpdate} from '@kepler.gl/table';

Expand Down
27 changes: 25 additions & 2 deletions test/fixtures/test-hex-id-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ export default `hex_id,value

export const dataId = 'h3-hex-id';

export const expectedFields = [
{
analyzerType: 'H3',
displayName: 'hex_id',
fieldIdx: 0,
format: '',
id: 'hex_id',
name: 'hex_id',
type: 'h3',
valueAccessor: values => values[0]
},
{
analyzerType: 'INT',
displayName: 'value',
fieldIdx: 1,
format: '',
id: 'value',
name: 'value',
type: 'integer',
valueAccessor: values => values[1]
}
];

export const hexIdDataConfig = {
dataId,
config: {
Expand Down Expand Up @@ -181,8 +204,8 @@ const mergedFields = [
displayName: 'hex_id',
format: '',
fieldIdx: 0,
type: 'string',
analyzerType: 'STRING',
type: 'h3',
analyzerType: 'H3',
valueAccessor: values => values[0]
},
{
Expand Down
Loading

0 comments on commit cef3faf

Please sign in to comment.