Skip to content

Commit

Permalink
Calculation of widget using maps API under FF (#658)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josmorsot committed Aug 4, 2023
1 parent 6a44524 commit 513c24f
Show file tree
Hide file tree
Showing 37 changed files with 603 additions and 146 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Not released

- Calculation of widget using maps API under FF [#658](https://github.com/CartoDB/carto-react/pull/658)

## 1.5

### 1.5.1 (2023-02-06)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"lint-staged": "^10.5.3",
"nyc": "^15.1.0",
"patch-package": "6.4.7",
"quadbin": "^0.1.5",
"quadbin": "^0.1.9",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
Expand Down
12 changes: 11 additions & 1 deletion packages/react-api/src/api/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const AVAILABLE_MODELS = ['category', 'histogram', 'formula', 'timeseries', 'ran
/**
* Execute a SQL model request.
*
* @typedef { import('geojson').Polygon | import('geojson').MultiPolygon } SpatialFilter
* @param { object } props
* @param { string } props.model - widget's model that we want to get the data for
* @param { object } props.source - source that owns the column
* @param { object } props.params - widget's props
* @param { SpatialFilter= } props.spatialFilter - restrict widget calculation to an area
* @param { object= } props.opts - Additional options for the HTTP request
*/
export function executeModel(props) {
Expand All @@ -27,7 +29,7 @@ export function executeModel(props) {
)}`
);

const { source, model, params, opts } = props;
const { source, model, params, spatialFilter, opts } = props;

checkCredentials(source.credentials);

Expand All @@ -52,13 +54,21 @@ export function executeModel(props) {
filtersLogicalOperator
};

if (spatialFilter) {
queryParams.spatialFilter = JSON.stringify(spatialFilter);
}

const isGet = url.length + JSON.stringify(queryParams).length <= URL_LENGTH;
if (isGet) {
url += '?' + new URLSearchParams(queryParams).toString();
} else {
// undo the JSON.stringify, @todo find a better pattern
queryParams.params = params;
queryParams.filters = filters;
queryParams.queryParameters = source.queryParameters;
if (spatialFilter) {
queryParams.spatialFilter = spatialFilter;
}
}
return makeCall({
url,
Expand Down
4 changes: 2 additions & 2 deletions packages/react-api/src/hooks/useCartoLayerProps.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { selectSpatialFilter } from '@carto/react-redux';
import { selectSpatialFilter, selectViewport } from '@carto/react-redux';
import useGeojsonFeatures from './useGeojsonFeatures';
import useTileFeatures from './useTileFeatures';
import { getDataFilterExtensionProps } from './dataFilterExtensionUtil';
Expand All @@ -15,7 +15,7 @@ export default function useCartoLayerProps({
viewportFeatures = true,
viewporFeaturesDebounceTimeout = 250
}) {
const viewport = useSelector((state) => state.carto.viewport);
const viewport = useSelector(selectViewport);
const spatialFilter = useSelector((state) => selectSpatialFilter(state, source?.id));

const [onDataLoadForGeojson] = useGeojsonFeatures({
Expand Down
46 changes: 25 additions & 21 deletions packages/react-api/src/hooks/useGeojsonFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,27 +43,31 @@ export default function useGeojsonFeatures({
[computeFeatures]
);

useEffect(() => {
if (sourceId && isGeoJsonLoaded) {
clearDebounce();
setSourceFeaturesReady(false);
debounceIdRef.current = debouncedComputeFeatures({
viewport,
spatialFilter,
uniqueIdProperty
});
}
}, [
viewport,
spatialFilter,
uniqueIdProperty,
sourceId,
isGeoJsonLoaded,
debouncedComputeFeatures,
setSourceFeaturesReady,
clearDebounce,
debounceIdRef
]);
useEffect(
() => {
if (sourceId && isGeoJsonLoaded) {
clearDebounce();
setSourceFeaturesReady(false);
debounceIdRef.current = debouncedComputeFeatures({
viewport,
spatialFilter,
uniqueIdProperty
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// eslint-disable-next-line react-hooks/exhaustive-deps
spatialFilter ? spatialFilter : viewport,
uniqueIdProperty,
sourceId,
isGeoJsonLoaded,
debouncedComputeFeatures,
setSourceFeaturesReady,
clearDebounce,
debounceIdRef
]
);

const onDataLoad = useCallback(
(geojson) => {
Expand Down
46 changes: 25 additions & 21 deletions packages/react-api/src/hooks/useTileFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,31 @@ export default function useTileFeatures({
loadTiles
]);

useEffect(() => {
if (sourceId && isTilesetLoaded) {
clearDebounce();
setSourceFeaturesReady(false);
debounceIdRef.current = debouncedComputeFeatures({
viewport,
spatialFilter,
uniqueIdProperty
});
}
}, [
viewport,
spatialFilter,
uniqueIdProperty,
debouncedComputeFeatures,
sourceId,
isTilesetLoaded,
setSourceFeaturesReady,
clearDebounce,
debounceIdRef
]);
useEffect(
() => {
if (sourceId && isTilesetLoaded) {
clearDebounce();
setSourceFeaturesReady(false);
debounceIdRef.current = debouncedComputeFeatures({
viewport,
spatialFilter,
uniqueIdProperty
});
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// eslint-disable-next-line react-hooks/exhaustive-deps
spatialFilter ? spatialFilter : viewport,
uniqueIdProperty,
debouncedComputeFeatures,
sourceId,
isTilesetLoaded,
setSourceFeaturesReady,
clearDebounce,
debounceIdRef
]
);

const onViewportLoad = useCallback(
(tiles) => {
Expand Down
63 changes: 63 additions & 0 deletions packages/react-core/__tests__/utils/geo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import bboxPolygon from '@turf/bbox-polygon';
import { isGlobalViewport, getGeometryToIntersect } from '../../src/utils/geo';

/** @type { import('../../src').Viewport } */
const viewport = [-10, -10, 10, 10]; // west - south - east - north
const viewportGeometry = bboxPolygon(viewport).geometry;

/** @type { import('geojson').Polygon } */
const filterGeometry = {
type: 'Polygon',
coordinates: [
[
[-1, -1],
[1, -1],
[1, 1],
[-1, 1],
[-1, -1]
]
]
};

describe('isGlobalViewport', () => {
const normalViewports = [
{ v: null },
{ v: viewport },
{
v: [-344.2596303029739, -75.05112877980663, 230.26452782294038, 75.05112877980655]
},
{ v: [-125.2596303029739, -85.05112877980663, 230.26452782294038, 85.05112877980655] }
];
const globalViewports = [
{ v: [-344.2596303029739, -85.05112877980663, 230.26452782294038, 85.05112877980655] }
];

test.each(normalViewports)('return false for normal viewports', ({ v }) => {
expect(!isGlobalViewport(v));
});

test.each(globalViewports)('return true for global viewports', ({ v }) => {
console.log(viewport);
expect(isGlobalViewport(v));
});
});

describe('getGeometryToIntersect', () => {
test('returns null in case no or invalid viewport or geometry is present', () => {
expect(getGeometryToIntersect(null, null)).toStrictEqual(null);
expect(getGeometryToIntersect([], null)).toStrictEqual(null);
expect(getGeometryToIntersect(null, {})).toStrictEqual(null);
expect(getGeometryToIntersect([], {})).toStrictEqual(null);
});

test('returns the viewport as geometry', () => {
expect(getGeometryToIntersect(viewport, null)).toStrictEqual(viewportGeometry);
});

test('returns the filter as geometry', () => {
expect(getGeometryToIntersect(null, filterGeometry)).toStrictEqual(filterGeometry);
expect(getGeometryToIntersect(viewport, filterGeometry)).toStrictEqual(
filterGeometry
);
});
});
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@
"@turf/boolean-within": "^6.3.0",
"@turf/intersect": "^6.3.0",
"h3-js": "^3.7.2",
"quadbin": "^0.1.5"
"quadbin": "^0.1.9"
}
}
2 changes: 1 addition & 1 deletion packages/react-core/src/filters/geojsonFeatures.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import intersects from '@turf/boolean-intersects';
import { getGeometryToIntersect } from './tileFeatures';
import { getGeometryToIntersect } from '../utils/geo';

export function geojsonFeatures({ geojson, viewport, geometry, uniqueIdProperty }) {
let uniqueIdx = 0;
Expand Down
3 changes: 0 additions & 3 deletions packages/react-core/src/filters/tileFeatures.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
import { TileFeatures, TileFeaturesResponse } from '../types';
import { Geometry, Feature, Polygon, MultiPolygon } from 'geojson';

export function getGeometryToIntersect(viewport: number[], geometry: Geometry | null): Feature<Polygon | MultiPolygon> | null;
export function tileFeatures(arg: TileFeatures): TileFeaturesResponse;
7 changes: 1 addition & 6 deletions packages/react-core/src/filters/tileFeatures.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import bboxPolygon from '@turf/bbox-polygon';
import intersect from '@turf/intersect';
import { getGeometryToIntersect } from '../utils/geo';
import tileFeaturesGeometries from './tileFeaturesGeometries';
import tileFeaturesSpatialIndex from './tileFeaturesSpatialIndex';

export function getGeometryToIntersect(viewport, geometry) {
return geometry ? intersect(bboxPolygon(viewport), geometry) : bboxPolygon(viewport);
}

export function tileFeatures({
tiles,
viewport,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function tileFeaturesSpatialIndex({
}

tile.data.forEach((d) => {
if (cellsDictionary[d.id]) {
if (d.id in cellsDictionary) {
map.set(d.id, { ...d.properties, [spatialIndexIDName]: d.id });
}
});
Expand Down
10 changes: 10 additions & 0 deletions packages/react-core/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { _FeatureFlags } from '.';

export {
getRequest,
postRequest,
Expand All @@ -13,6 +15,7 @@ export { debounce } from './utils/debounce';
export { throttle } from './utils/throttle';
export { randomString } from './utils/randomString';
export { assert as _assert } from './utils/assert';
export { getGeometryToIntersect, isGlobalViewport } from './utils/geo';

export { makeIntervalComplete } from './utils/makeIntervalComplete';

Expand All @@ -36,3 +39,10 @@ export { groupValuesByDateColumn } from './operations/groupByDate';
export { SpatialIndex } from './operations/constants/SpatialIndexTypes'

export { FEATURE_SELECTION_MODES, EDIT_MODES, MASK_ID } from './utils/featureSelectionConstants';

export {
Flags as _FeatureFlags,
hasFlag as _hasFeatureFlag,
setFlags as _setFeatureFlags,
clearFlags as _clearFeatureFlags
} from './utils/featureFlags';
8 changes: 8 additions & 0 deletions packages/react-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { debounce } from './utils/debounce';
export { throttle } from './utils/throttle';
export { randomString } from './utils/randomString';
export { assert as _assert } from './utils/assert';
export { getGeometryToIntersect, isGlobalViewport } from './utils/geo';

export { makeIntervalComplete } from './utils/makeIntervalComplete';

Expand Down Expand Up @@ -47,3 +48,10 @@ export {
EDIT_MODES,
MASK_ID
} from './utils/featureSelectionConstants';

export {
Flags as _FeatureFlags,
hasFlag as _hasFeatureFlag,
setFlags as _setFeatureFlags,
clearFlags as _clearFeatureFlags
} from './utils/featureFlags';
6 changes: 6 additions & 0 deletions packages/react-core/src/utils/featureFlags.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum Flags {
REMOTE_WIDGETS = '2023-remote-widgets'
}
export function setFlags(flags: Record<string, any> | string[]): void
export function clearFlags(): void
export function hasFlag(flag: string): boolean
34 changes: 34 additions & 0 deletions packages/react-core/src/utils/featureFlags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
let featureFlags = [];

export const Flags = Object.freeze({
REMOTE_WIDGETS: '2023-remote-widgets'
});

export function setFlags(flags) {
const isValidFlag = (f) => typeof f === 'string' && f;

if (Array.isArray(flags) && flags.every(isValidFlag)) {
featureFlags = flags;
} else if (
!Array.isArray(flags) &&
typeof flags === 'object' &&
Object.keys(flags).every(isValidFlag)
) {
featureFlags = [];
for (const [flag, value] of Object.entries(flags)) {
if (value) {
featureFlags.push(flag);
}
}
} else {
throw new Error(`Invalid feature flags: ${flags}`);
}
}

export function clearFlags() {
featureFlags = [];
}

export function hasFlag(flag) {
return featureFlags.includes(flag);
}
6 changes: 6 additions & 0 deletions packages/react-core/src/utils/geo.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Viewport } from '../types';
import { Polygon, MultiPolygon } from 'geojson';

export function getGeometryToIntersect(viewport: Viewport | null, geometry: Polygon | MultiPolygon | null): Polygon | MultiPolygon | null;

export function isGlobalViewport(viewport: Viewport | null): boolean;
Loading

0 comments on commit 513c24f

Please sign in to comment.