Skip to content

Commit

Permalink
New functionality to select numerator / denominator for smoothed maps (
Browse files Browse the repository at this point in the history
…fix #135)
  • Loading branch information
mthh committed Aug 28, 2024
1 parent 98c9a79 commit 6efcb37
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 41 deletions.
96 changes: 66 additions & 30 deletions src/components/PortrayalOption/SmoothingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ import { computeAppropriateResolution } from '../../helpers/geo';
import { generateIdLayer } from '../../helpers/layers';
import { generateIdLegend } from '../../helpers/legends';
import { Mpow } from '../../helpers/math';
import {
computeKdeValues, computeStewartValues, makeContourLayer,
} from '../../helpers/smoothing';
import { computeKdeValues, computeStewartValues, makeContourLayer } from '../../helpers/smoothing';
import { Variable, VariableType } from '../../helpers/typeDetection';
import { getPossibleLegendPosition } from '../LegendRenderer/common.tsx';

Expand All @@ -46,7 +44,8 @@ import { openLayerManager } from '../LeftMenu/LeftMenu.tsx';
// Types
import type { PortrayalSettingsProps } from './common';
import {
type ChoroplethLegend, type ClassificationParameters,
type ChoroplethLegend,
type ClassificationParameters,
type GeoJSONFeatureCollection,
type GridParameters,
KdeKernel,
Expand All @@ -71,6 +70,7 @@ async function onClickValidate(
thresholds: number[],
computedValues: { grid: GeoJSONFeatureCollection, values: number[] },
clippingLayerId: string,
targetDivisorVariable?: string,
) {
const referenceLayerDescription = layersDescriptionStore.layers
.find((l) => l.id === referenceLayerId);
Expand Down Expand Up @@ -105,6 +105,7 @@ async function onClickValidate(
method: smoothingMethod,
smoothingParameters: parameters,
gridParameters: gridParams,
divisorVariable: targetDivisorVariable,
} as SmoothedLayerParameters;

const rendererParameters = {
Expand All @@ -123,39 +124,51 @@ async function onClickValidate(
// Create a new layer
const newId = generateIdLayer();

// Field descriptions for the new layer
const fieldDescriptions = [
{
name: 'min_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: 'center_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: 'max_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: targetVariable,
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
},
];

if (targetDivisorVariable) {
fieldDescriptions.push({
name: targetDivisorVariable,
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
});
}

const newLayerDescription = {
id: newId,
// layerId: referenceLayerId,
name: newName,
type: 'polygon',
representationType: 'smoothed' as RepresentationType,
data: newData,
fields: [
{
name: 'min_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: 'center_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: 'max_v',
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
} as Variable,
{
name: targetVariable,
type: VariableType.stock,
hasMissingValues: false,
dataType: 'number',
},
],
fields: fieldDescriptions,
visible: true,
strokeColor: '#000000',
strokeWidth: 1,
Expand Down Expand Up @@ -242,6 +255,10 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
targetVariable,
setTargetVariable,
] = createSignal<string>(targetFields[0].name);
const [
targetDivisorVariable,
setTargetDivisorVariable,
] = createSignal<string>('');
const [
targetSmoothingMethod,
setTargetSmoothingMethod,
Expand Down Expand Up @@ -346,6 +363,7 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
thresholds()!,
computedValues()!,
clippingLayer(),
targetDivisorVariable() === '' ? undefined : targetDivisorVariable(),
).then(() => {
// Hide loading overlay
setLoading(false);
Expand All @@ -365,6 +383,9 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
label={LL().FunctionalitiesSection.CommonOptions.Variable()}
onChange={(v) => {
setTargetVariable(v);
if (targetVariable() === targetDivisorVariable()) {
setTargetDivisorVariable('');
}
}}
value={targetVariable()}
disabled={isLoading() || !!computedValues()}
Expand All @@ -373,6 +394,19 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
{(variable) => <option value={variable.name}>{variable.name}</option>}
</For>
</InputFieldSelect>
<InputFieldSelect
label={LL().FunctionalitiesSection.SmoothingOptions.DivisorVariable()}
onChange={(v) => {
setTargetDivisorVariable(v);
}}
value={targetDivisorVariable()}
disabled={isLoading() || !!computedValues()}
>
<option value={''}>{LL().FunctionalitiesSection.SmoothingOptions.NoDivisorVariable()}</option>
<For each={targetFields.filter((d) => d.name !== targetVariable())}>
{(variable) => <option value={variable.name}>{variable.name}</option>}
</For>
</InputFieldSelect>
<InputFieldSelect
label={LL().FunctionalitiesSection.SmoothingOptions.Type()}
onChange={(v) => {
Expand Down Expand Up @@ -535,6 +569,7 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
targetVariable(),
gp as GridParameters,
smoothingParams as KdeParameters,
targetDivisorVariable() === '' ? undefined : targetDivisorVariable(),
);
} else {
[grid, values] = await computeStewartValues(
Expand All @@ -543,6 +578,7 @@ export default function SmoothingSettings(props: PortrayalSettingsProps): JSX.El
targetVariable(),
gp as GridParameters,
smoothingParams as StewartParameters,
targetDivisorVariable() === '' ? undefined : targetDivisorVariable(),
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ interface SmoothedLayerParameters {
smoothingParameters: StewartParameters | KdeParameters,
// The parameters of the grid used to compute the smoothed layer
gridParameters: GridParameters,
// The (optional) divisior variable used to compute the smoothed layer
divisorVariable?: string,
}

interface CategoricalPictogramMapping {
Expand Down
109 changes: 98 additions & 11 deletions src/helpers/smoothing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,30 @@ function computeStewartInnerExponential(
return sum;
}

function computeStewartInnerExponentialWithDivisor(
xCell: number[],
yCell: number[],
xDot: number[],
yDot: number[],
values: number[],
): number {
let sum1 = 0;
let sum2 = 0;
for (let i = 0; i < this.constants.size; i++) { // eslint-disable-line no-plusplus
const dist = this.haversineDistance(
xDot[i],
yDot[i],
xCell[this.thread.x],
yCell[this.thread.x],
);
// eslint-disable-next-line prefer-exponentiation-operator, no-restricted-properties
const c = Math.exp(-this.constants.alpha * Math.pow(dist, this.constants.beta));
sum1 += (values[i * 2] * c);
sum2 += (values[i * 2 + 1] * c);
}
return sum1 / sum2;
}

function computeStewartInnerPareto(
xCell: number[],
yCell: number[],
Expand All @@ -248,6 +272,30 @@ function computeStewartInnerPareto(
return sum;
}

function computeStewartInnerParetoWithDivisor(
xCell: number[],
yCell: number[],
xDot: number[],
yDot: number[],
values: number[],
): number {
let sum1 = 0;
let sum2 = 0;
for (let i = 0; i < this.constants.size; i++) { // eslint-disable-line no-plusplus
const dist = this.haversineDistance(
xDot[i],
yDot[i],
xCell[this.thread.x],
yCell[this.thread.x],
);
// eslint-disable-next-line prefer-exponentiation-operator, no-restricted-properties
const c = Math.pow(1 + this.constants.alpha * dist, -this.constants.beta);
sum1 += (values[i * 2] * c);
sum2 += (values[i * 2 + 1] * c);
}
return sum1 / sum2;
}

function computeKdeInner(
xCell: number[],
yCell: number[],
Expand All @@ -270,10 +318,34 @@ function computeKdeInner(
return value;
}

function computeKdeInnerWithDivisor(
xCell: number[],
yCell: number[],
xDot: number[],
yDot: number[],
values: number[],
): number {
let value1 = 0;
let value2 = 0;
// let vs = 0;
for (let i = 0; i < this.constants.size; i++) { // eslint-disable-line no-plusplus
const dist = this.haversineDistance(
xDot[i],
yDot[i],
xCell[this.thread.x],
yCell[this.thread.x],
);
const kv = this.computeKde(dist, this.constants.bandwidth);
value1 += kv * values[i * 2];
value2 += kv * values[i * 2 + 1];
}
return value1 / value2;
}

const prepareArrays = (
grid: GeoJSONFeatureCollection,
inputLayer: GeoJSONFeatureCollection,
variableName: string,
variableNames: string[],
): {
xCells: number[],
yCells: number[],
Expand All @@ -299,13 +371,17 @@ const prepareArrays = (
}

for (let i = 0; i < inputLayer.features.length; i++) { // eslint-disable-line no-plusplus
const v = inputLayer.features[i].properties[variableName] as any;
if (isFiniteNumber(v)) {
const vs = [];
for (let j = 0; j < variableNames.length; j++) { // eslint-disable-line no-plusplus
const v = inputLayer.features[i].properties[variableNames[j]] as any;
vs.push(v);
}
if (vs.every((v) => isFiniteNumber(v))) {
// eslint-disable-next-line prefer-destructuring
xDots.push(inputLayer.features[i].geometry.coordinates[0]);
// eslint-disable-next-line prefer-destructuring
yDots.push(inputLayer.features[i].geometry.coordinates[1]);
values.push(+v as number);
values.push(...vs.map((v) => +v));
}
}

Expand All @@ -324,24 +400,31 @@ export async function computeStewartValues(
variableName: string,
gridParameters: GridParameters,
stewartParameters: StewartParameters,
divisorVariableName?: string,
): Promise<[GeoJSONFeatureCollection, number[]]> {
// Is there one or two variables ?
const varArray = divisorVariableName ? [variableName, divisorVariableName] : [variableName];

// Make a suitable grid of points
const grid = makePointGrid(gridParameters, true);

// Compute the inputs points from
const inputLayer = makeCentroidLayer(data, inputType, [variableName]);
const inputLayer = makeCentroidLayer(data, inputType, varArray);

// Appropriate function to compute the potential
// eslint-disable-next-line no-nested-ternary
const computeStewartInner = stewartParameters.function === 'Gaussian'
? computeStewartInnerExponential
: computeStewartInnerPareto;
? (divisorVariableName
? computeStewartInnerExponentialWithDivisor : computeStewartInnerExponential)
: (divisorVariableName
? computeStewartInnerParetoWithDivisor : computeStewartInnerPareto);

// Values ready to be used in the GPU kernel
const {
xCells, yCells,
xDots, yDots,
values,
} = prepareArrays(grid, inputLayer, variableName);
} = prepareArrays(grid, inputLayer, varArray);

// Create the GPU instance and define the kernel
const gpu = new GPU({ mode: 'webgl2' });
Expand Down Expand Up @@ -397,20 +480,24 @@ export async function computeKdeValues(
variableName: string,
gridParameters: GridParameters,
kdeParameters: KdeParameters,
divisorVariableName?: string,
): Promise<[GeoJSONFeatureCollection, number[]]> {
await setLoadingMessage('SmoothingDataPreparation');
// Is there one or two variables ?
const varArray = divisorVariableName ? [variableName, divisorVariableName] : [variableName];

// Make a suitable grid of points
const grid = makePointGrid(gridParameters, true);

// Compute the inputs points from
const inputLayer = makeCentroidLayer(data, inputType, [variableName]);
const inputLayer = makeCentroidLayer(data, inputType, varArray);

// Values ready to be used in the GPU kernel
const {
xCells, yCells,
xDots, yDots,
values,
} = prepareArrays(grid, inputLayer, variableName);
} = prepareArrays(grid, inputLayer, varArray);

// We will use this value to normalize input values
const normalizer = computeNormalizer(values);
Expand Down Expand Up @@ -546,7 +633,7 @@ export async function computeKdeValues(

const kernel = gpu
.createKernel(
computeKdeInner,
divisorVariableName ? computeKdeInnerWithDivisor : computeKdeInner,
{
constants: {
normalizer,
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ const en = {
NewLayerName: 'Discontinuity_{layerName}',
},
SmoothingOptions: {
DivisorVariable: 'Divisor variable',
NoDivisorVariable: 'None',
Type: 'Smoothing type',
Resolution: 'Grid resolution (km)',
Stewart: 'Stewart\'s potential',
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ const fr = {
NewLayerName: 'Discontinuité_{layerName}',
},
SmoothingOptions: {
DivisorVariable: 'Variable de pondération',
NoDivisorVariable: 'Aucune',
Type: 'Type de lissage',
Resolution: 'Résolution de la grille (km)',
Stewart: 'Potentiel de Stewart',
Expand Down
Loading

0 comments on commit 6efcb37

Please sign in to comment.