Skip to content

Commit

Permalink
Add FeatureSelection tool (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
padawannn authored Feb 1, 2022
1 parent 25df5ae commit 5f78921
Show file tree
Hide file tree
Showing 37 changed files with 697 additions and 428 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Not released

- Upgrade storybook and fix custom-component stories [#303](https://github.com/CartoDB/carto-react/pull/303)
- Name refactor in DrawingTool for FeatureSelection [#271](https://github.com/CartoDB/carto-react/pull/271)
- Add compatibility between Google Maps and FeatureSelectionLayer [#271](https://github.com/CartoDB/carto-react/pull/271)
- Update deck.gl version to 8.7.0-beta.2 and integrate new deck.gl MaskExtension, required by FeatureSelectionLayer [#271](https://github.com/CartoDB/carto-react/pull/271)

## 1.2

### 1.2.1-alpha.8 (2022-01-27)
Expand Down
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"packages/*"
],
"devDependencies": {
"@deck.gl/carto": "^8.7.0-alpha.11",
"@deck.gl/core": "^8.7.0-alpha.11",
"@deck.gl/extensions": "^8.7.0-alpha.11",
"@deck.gl/geo-layers": "^8.7.0-alpha.11",
"@deck.gl/google-maps": "^8.7.0-alpha.11",
"@deck.gl/layers": "^8.7.0-alpha.11",
"@deck.gl/mesh-layers": "^8.7.0-alpha.11",
"@deck.gl/carto": "^8.7.0-beta.2",
"@deck.gl/core": "^8.7.0-beta.2",
"@deck.gl/extensions": "^8.7.0-beta.2",
"@deck.gl/geo-layers": "^8.7.0-beta.2",
"@deck.gl/google-maps": "^8.7.0-beta.2",
"@deck.gl/layers": "^8.7.0-beta.2",
"@deck.gl/mesh-layers": "^8.7.0-beta.2",
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
Expand Down Expand Up @@ -53,6 +53,8 @@
"test:coverage": "lerna run --parallel test:coverage && node scripts/mergeCoverage.js",
"link-all": "lerna exec --parallel yarn link",
"unlink-all": "lerna exec --parallel yarn unlink",
"link-deck": "yarn link @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/google-maps @deck.gl/carto @deck.gl/extensions",
"unlink-deck": "yarn unlink @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/google-maps @deck.gl/carto @deck.gl/extensions",
"storybook:start": "lerna --scope @carto/react-ui exec -- npm run storybook:start",
"storybook:build": "lerna --scope @carto/react-ui exec -- npm run storybook:build",
"storybook:publish": "lerna --scope @carto/react-ui exec -- npm run storybook:publish",
Expand Down
29 changes: 29 additions & 0 deletions packages/react-api/__tests__/hooks/mask-extension-util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MASK_ID } from '@carto/react-core/';
import { MaskExtension } from '@deck.gl/extensions';
import { getMaskExtensionProps } from '../../src/hooks/maskExtensionUtil';

describe('mask-extension-util', () => {
test('correct values without maskPolygon', () => {
const myMaskPolygon = undefined;
const { maskId, maskEnabled, extensions } = getMaskExtensionProps(myMaskPolygon);

expect(maskId).toEqual(MASK_ID);
expect(maskEnabled).toEqual(false);
expect(extensions[0]).toBeInstanceOf(MaskExtension);
});

test('correct values width maskPolygon', () => {
const myMaskPolygon = [
[-41.484375, 35.17380831799959],
[0.3515625, 35.17380831799959],
[0.3515625, 53.330872983017066],
[-41.484375, 53.330872983017066],
[-41.484375, 35.17380831799959]
];
const { maskId, maskEnabled, extensions } = getMaskExtensionProps(myMaskPolygon);

expect(maskId).toEqual(MASK_ID);
expect(maskEnabled).toEqual(true);
expect(extensions[0]).toBeInstanceOf(MaskExtension);
});
});
23 changes: 19 additions & 4 deletions packages/react-api/__tests__/hooks/useCartoLayerProps.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DataFilterExtension } from '@deck.gl/extensions';
import { DataFilterExtension, MaskExtension } from '@deck.gl/extensions';
import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto';
import { MASK_ID } from '@carto/react-core/';
import { renderHook } from '@testing-library/react-hooks';
import useCartoLayerProps from '../../src/hooks/useCartoLayerProps';
import { mockReduxHooks, mockClear } from '../mockReduxHooks';
Expand All @@ -19,7 +20,9 @@ describe('useCartoLayerProps', () => {
'filterRange',
'updateTriggers',
'getFilterValue',
'extensions'
'extensions',
'maskId',
'maskEnabled'
];

describe('when maps_api_version is V2', () => {
Expand Down Expand Up @@ -188,11 +191,12 @@ describe('useCartoLayerProps', () => {
]);
});

test('extensions should have an unique instance of DataFilterExtension', () => {
test('extensions should have an instance of DataFilterExtension and MaskExtension', () => {
const { result } = renderHook(() => useCartoLayerProps({}));

expect(result.current.extensions.length).toBe(1);
expect(result.current.extensions.length).toBe(2);
expect(result.current.extensions[0]).toBeInstanceOf(DataFilterExtension);
expect(result.current.extensions[1]).toBeInstanceOf(MaskExtension);
});

test(`filter size should be ${MAX_GPU_FILTERS}`, () => {
Expand All @@ -206,6 +210,17 @@ describe('useCartoLayerProps', () => {

expect(result.current.updateTriggers).toHaveProperty('getFilterValue');
});

test('maskEnabled should be present and be false by default', () => {
const { result } = renderHook(() => useCartoLayerProps({}));
expect(result.current.maskEnabled).toEqual(false);
});

test('maskId should be present and should have the correct value', () => {
const { result } = renderHook(() => useCartoLayerProps({}));

expect(result.current.maskId).toEqual(MASK_ID);
});
});

mockClear();
Expand Down
12 changes: 6 additions & 6 deletions packages/react-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@
"@babel/runtime": "^7.13.9"
},
"peerDependencies": {
"@carto/react-core": "^1.1.4",
"@carto/react-redux": "^1.1.4",
"@carto/react-workers": "^1.1.4",
"@deck.gl/carto": "^8.7.0-alpha.11",
"@deck.gl/core": "^8.7.0-alpha.11",
"@deck.gl/extensions": "^8.7.0-alpha.11",
"@carto/react-core": "^1.2.1-alpha.8",
"@carto/react-redux": "^1.2.1-alpha.8",
"@carto/react-workers": "^1.2.1-alpha.8",
"@deck.gl/carto": "^8.7.0-beta.2",
"@deck.gl/core": "^8.7.0-beta.2",
"@deck.gl/extensions": "^8.7.0-beta.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
Expand Down
3 changes: 2 additions & 1 deletion packages/react-api/src/hooks/dataFilterExtensionUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { _buildFeatureFilter, _FilterTypes } from '@carto/react-core/';

// Don't change this value to maintain compatibility with builder
export const MAX_GPU_FILTERS = 4;
const dataFilterExtension = new DataFilterExtension({ filterSize: MAX_GPU_FILTERS });

function getFiltersByType(filters) {
const filtersWithoutTimeType = {};
Expand Down Expand Up @@ -84,6 +85,6 @@ export function getDataFilterExtensionProps(filters = {}) {
filterRange: getFilterRange(timeFilter),
updateTriggers: getUpdateTriggers(filtersWithoutTimeType, timeFilter),
getFilterValue: getFilterValue(filtersWithoutTimeType, timeFilter),
extensions: [new DataFilterExtension({ filterSize: MAX_GPU_FILTERS })]
extensions: [dataFilterExtension]
};
}
12 changes: 12 additions & 0 deletions packages/react-api/src/hooks/maskExtensionUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { MASK_ID } from '@carto/react-core/';
import { MaskExtension } from '@deck.gl/extensions';

const maskExtension = new MaskExtension();

export function getMaskExtensionProps(maskPolygon) {
return {
maskId: MASK_ID,
maskEnabled: Boolean(maskPolygon && maskPolygon.length),
extensions: [maskExtension]
};
}
10 changes: 9 additions & 1 deletion packages/react-api/src/hooks/useCartoLayerProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { selectSpatialFilter } from '@carto/react-redux';
import useGeojsonFeatures from './useGeojsonFeatures';
import useTileFeatures from './useTileFeatures';
import { getDataFilterExtensionProps } from './dataFilterExtensionUtil';
import { getMaskExtensionProps } from './maskExtensionUtil';

export default function useCartoLayerProps({
source,
Expand Down Expand Up @@ -50,6 +51,11 @@ export default function useCartoLayerProps({
}

const dataFilterExtensionProps = getDataFilterExtensionProps(source?.filters);
const maskExtensionProps = getMaskExtensionProps(spatialFilter?.geometry?.coordinates);
const extensions = [
...dataFilterExtensionProps.extensions,
...maskExtensionProps.extensions
];

return {
...props,
Expand All @@ -59,6 +65,8 @@ export default function useCartoLayerProps({
connection: source?.connection,
credentials: source?.credentials,
clientId: 'carto-for-react',
...dataFilterExtensionProps
...dataFilterExtensionProps,
...maskExtensionProps,
extensions
};
}
6 changes: 4 additions & 2 deletions packages/react-api/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataFilterExtension } from '@deck.gl/extensions';
import { DataFilterExtension, MaskExtension } from '@deck.gl/extensions';
import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto';
import { FeatureCollection } from 'geojson';

Expand Down Expand Up @@ -33,10 +33,12 @@ export type UseCartoLayerFilterProps = {
onDataLoad?: Function;
getFilterValue: Function;
filterRange: [number, number];
extensions: DataFilterExtension[];
extensions: [DataFilterExtension, MaskExtension];
updateTriggers: {
getFilterValue: Record<string, unknown>;
};
maskEnabled: boolean;
maskId: string;
} & SourceProps;

export type ExecuteSQLResponse<Response = FeatureCollection | {}[]> = Promise<Response>;
2 changes: 1 addition & 1 deletion packages/react-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@babel/runtime": "^7.13.9"
},
"peerDependencies": {
"@carto/react-core": "^1.1.4",
"@carto/react-core": "^1.2.1-alpha.8",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/react-basemaps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
"@babel/runtime": "^7.13.9"
},
"peerDependencies": {
"@carto/react-core": "^1.1.4",
"@deck.gl/google-maps": "^8.7.0-alpha.11",
"@carto/react-core": "^1.2.1-alpha.8",
"@deck.gl/google-maps": "^8.7.0-beta.2",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
Expand Down
13 changes: 9 additions & 4 deletions packages/react-basemaps/src/basemaps/GoogleMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function GoogleMap(props) {
const mapNotConnected = containerRef.current.children.length === 0;
if (!window.cartoGmap || mapNotConnected) {
const map = new window.google.maps.Map(containerRef.current, options);
const deckOverlay = new GoogleMapsOverlay({ layers, getTooltip });
const deckOverlay = new GoogleMapsOverlay({ getTooltip });

const handleViewportChange = () => {
const center = map.getCenter();
Expand Down Expand Up @@ -104,6 +104,7 @@ export function GoogleMap(props) {

window.cartoGmap = map;
window.cartoDeck = deckOverlay;
window.cartoDeck.setMap(window.cartoGmap);
} else {
const { center, heading, tilt, zoom, ...rest } = options;
const newViewState = {
Expand All @@ -120,12 +121,16 @@ export function GoogleMap(props) {
window.cartoGmap.setZoom(zoom);
}
window.cartoGmap.setOptions(rest);
window.cartoDeck.setProps({ layers, getTooltip });
window.cartoDeck.setProps({ getTooltip });
}

window.cartoDeck.setMap(window.cartoGmap);
};

// Set layers should be outside of the useEffect to avoid problems with layers that changes
// dinamically with move events like the FeatureSelectionLayer
if (window.cartoDeck) {
window.cartoDeck.setProps({ layers });
}

useEffect(() => {
if (!document.querySelector('#gmaps')) {
const script = document.createElement(`script`);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-core/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export { AggregationFunctions, GroupByFeature, HistogramFeature, Viewport, TileF
export { GroupDateTypes } from './operations/GroupDateTypes';
export { groupValuesByDateColumn } from './operations/groupByDate';

export { DRAW_MODES, EDIT_MODES } from './utils/drawingToolConstants';
export { FEATURE_SELECTION_MODES, EDIT_MODES, MASK_ID } from './utils/featureSelectionConstants';
6 changes: 5 additions & 1 deletion packages/react-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export { geojsonFeatures } from './filters/geojsonFeatures';
export { GroupDateTypes } from './operations/GroupDateTypes';
export { groupValuesByDateColumn } from './operations/groupByDate';

export { DRAW_MODES, EDIT_MODES } from './utils/drawingToolConstants';
export {
FEATURE_SELECTION_MODES,
EDIT_MODES,
MASK_ID
} from './utils/featureSelectionConstants';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const DRAW_MODES = Object.freeze({
// Don't rename values. These values come from nebula.
export const FEATURE_SELECTION_MODES = Object.freeze({
POLYGON: 'DrawPolygonMode',
RECTANGLE: 'DrawRectangleMode',
CIRCLE: 'DrawCircleFromCenterMode',
Expand All @@ -8,3 +9,5 @@ export const DRAW_MODES = Object.freeze({
export const EDIT_MODES = Object.freeze({
EDIT: 'edit'
});

export const MASK_ID = 'feature_selection_mask';
8 changes: 4 additions & 4 deletions packages/react-redux/__tests__/cartoSlice.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,12 @@ describe('carto slice', () => {
});
});

describe('drawing tool mode', () => {
test('should set drawing tool mode', () => {
describe('feature selection mode', () => {
test('should set feature selection mode', () => {
const MODE = 'abracadabra';
store.dispatch(cartoSlice.setDrawingToolMode(MODE));
store.dispatch(cartoSlice.setFeatureSelectionMode(MODE));
const state = store.getState();
expect(state.carto.drawingToolMode).toEqual(MODE);
expect(state.carto.featureSelectionMode).toEqual(MODE);
});
});

Expand Down
8 changes: 4 additions & 4 deletions packages/react-redux/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
"@babel/runtime": "^7.13.9"
},
"peerDependencies": {
"@carto/react-core": "^1.1.4",
"@carto/react-workers": "^1.1.4",
"@deck.gl/carto": "^8.7.0-alpha.11",
"@deck.gl/core": "^8.7.0-alpha.11",
"@carto/react-core": "^1.2.1-alpha.8",
"@carto/react-workers": "^1.2.1-alpha.8",
"@deck.gl/carto": "^8.7.0-beta.2",
"@deck.gl/core": "^8.7.0-beta.2",
"@reduxjs/toolkit": "^1.5.0"
}
}
14 changes: 8 additions & 6 deletions packages/react-redux/src/slices/cartoSlice.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ declare enum CartoActions {
CLEAR_FILTERS = 'carto/clearFilters',
SET_FEATURES_READY = 'carto/setFeaturesReady',
SET_CREDENTIALS = 'carto/setCredentials',
SET_DRAWING_TOOL_MODE = 'carto/setDrawingToolMode',
SET_DRAWING_TOOL_ENABLED = 'carto/setDrawingToolEnabled'
SET_FEATURE_SELECTION_MODE = 'carto/setFeatureSelectionMode',
SET_FEATURE_SELECTION_ENABLED = 'carto/setFeatureSelectionEnabled',
}

export function createCartoSlice(
Expand Down Expand Up @@ -141,14 +141,16 @@ export function setCredentials(credentials: Credentials): {
payload: Credentials;
};

export function setDrawingToolMode(mode: string): {
type: CartoActions.SET_DRAWING_TOOL_MODE;
export function setFeatureSelectionMode(mode: string): {
type: CartoActions.SET_FEATURE_SELECTION_MODE;
payload: string;
};

export function setDrawingToolEnabled(enabled: boolean): {
type: CartoActions.SET_DRAWING_TOOL_MODE;
export function setFeatureSelectionEnabled(enabled: boolean): {
type: CartoActions.SET_FEATURE_SELECTION_ENABLED;
payload: boolean;
};

export function selectSpatialFilter(state: any, sourceId?: string): object | null;

export function selectFeatureSelectionMode(state: any): string | null;
Loading

0 comments on commit 5f78921

Please sign in to comment.