standardized = {
const formatDistance = (value) => {
return value !== null ? `${value.toFixed(0)} m` : 'N/A';
};
const transformers = [
{
weight: catWeight,
feature: "cat_distance",
preprocessor: (value) => {return Math.E ** (-1 * value / 250)},
label: 'CAT distance',
format: formatDistance,
},
{
weight: utsWeight,
feature: "uts_distance",
preprocessor: (value) => {return Math.E ** (-1 * value / 250)},
label: 'UTS distance',
format: formatDistance,
},
{
weight: parkWeight,
feature: "parks_distance",
preprocessor: (value) => {return Math.E ** (-1 * value / 250)},
label: 'Park distance',
format: formatDistance,
},
{
weight: schoolWeight,
feature: "schools_distance",
preprocessor: (value) => {return Math.E ** (-1 * value / 250)},
label: 'School distance',
format: formatDistance,
},
{
weight: uvaWeight,
feature: "uva_distance",
preprocessor: (value) => {return Math.E ** (-1 * value / 500)},
label: 'UVA distance',
format: formatDistance,
},
{
weight: roadWeight,
feature: "roads_distance_local",
preprocessor: (value) => {return Math.E ** (-1 * value / 250)},
label: 'Road distance',
format: formatDistance,
},
{
weight: walkWeight,
feature: "walkscore",
preprocessor: (value) => {return value},
label: 'Walk Score',
format: (value) => {return value.toFixed(0);},
},
{
weight: bikeWeight,
feature: "bikescore",
preprocessor: (value) => {return value},
label: 'Bike score',
format: (value) => {return value.toFixed(0);},
},
{
weight: landValueWeight,
feature: "landvaluepersqmrank_parcel",
preprocessor: (value) => {return value},
label: 'Land value %ile',
format: (value) => {
return value !== null ? `${(value * 100).toFixed(0)}%` : 'N/A';
},
},
{
weight: whiteOnlyWeight,
feature: "prop_white",
preprocessor: (value) => {return value},
label: 'Proportion white',
format: (value) => {
return value ? `${(value * 100).toFixed(0)}%` : 'N/A';
},
},
{
weight: incomeWeight,
feature: "income",
preprocessor: (value) => {return value},
label: 'Median income',
format: (value) => {
return value ? `$${value.toFixed(0)} / year` : 'N/A';
},
},
];
const zscore = (values) => {
const filtered = values.filter(value => {
return value !== null;
});
const avg = filtered.reduce((sum, value) => {
return sum + value;
}, 0) / filtered.length;
const variance = filtered.reduce((sum, value) => {
return sum + (value - avg) ** 2;
}, 0) / filtered.length;
const stddev = Math.sqrt(variance);
return values.map(value => {
return value !== null ? (value - avg) / stddev : 0;
});
};
const standardized = Object.fromEntries(transformers.map(transformer => {
return [
transformer.feature,
zscore(features.map(feature => {
return transformer.preprocessor(feature[transformer.feature]);
}))
];
}));
const evaluate = (feature, index) => {
return transformers.reduce((sum, transformer) => {
return sum + transformer.weight * standardized[transformer.feature][index];
}, 0);
};
const formatLayer = (layer) => {
const feature = gpinToFeature[layer.feature.properties.gpin];
return transformers.map(transformer => {
const value = feature[transformer.feature];
const formatted = transformer.format(value);
return `<div>${transformer.label}: ${formatted}</div>`;
}).join('\n');
};
const parcels = container._leaflet_parcels;
const values = features.map((feature, index) => {
return evaluate(feature, index);
});
console.log('range', Math.min(...values), Math.max(...values));
const scale = chroma.scale(['yellow', 'red', 'black']).correctLightness().domain([Math.min(...values), Math.max(...values)]);
parcels.eachLayer(layer => {
const feature = gpinToFeature[layer.feature.properties.gpin];
let color;
let score;
if (feature) {
score = evaluate(feature, gpinToIndex[layer.feature.properties.gpin]);
color = score !== 0 ? scale(score) : "white";
layer.bindPopup(`<h4>Parcel ${layer.feature.properties.gpin}: ${layer.feature.properties.addresses}</h4> ${formatLayer(layer)}`);
} else {
color = "white";
}
layer.setStyle({
fillColor: color,
});
});
return standardized;
}