perfect-postcode/frontend/src/lib/consts.ts

223 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { ViewState } from '../types';
export const INITIAL_RETRY_MS = 1000;
export const MAX_RETRY_MS = 10000;
/** Lower percentile for color-range clipping (0100) */
export const COLOR_RANGE_LOW_PERCENTILE = 5;
/** Upper percentile for color-range clipping (0100) */
export const COLOR_RANGE_HIGH_PERCENTILE = 95;
export const MAP_BOUNDS: [number, number, number, number] = [-9.5, 49, 5, 57];
export const MAP_MIN_ZOOM = 5.5;
export const BUFFER_MULTIPLIER = 1.5;
/** Demo free zone bounds (south, west, north, east) — must match server FREE_ZONE_BOUNDS */
export const FREE_ZONE_BOUNDS = { south: 51.44, west: -0.31, north: 51.59, east: 0.05 };
export const INITIAL_VIEW_STATE: ViewState = {
longitude: (FREE_ZONE_BOUNDS.west + FREE_ZONE_BOUNDS.east) / 2,
latitude: (FREE_ZONE_BOUNDS.south + FREE_ZONE_BOUNDS.north) / 2,
zoom: 14,
pitch: 0,
};
/**
* Zoom to H3 resolution mapping thresholds.
* Returns the H3 resolution to use for a given zoom level.
*/
export const ZOOM_TO_RESOLUTION_THRESHOLDS = [
{ maxZoom: 7, resolution: 5 },
{ maxZoom: 9, resolution: 6 },
{ maxZoom: 10.5, resolution: 7 },
{ maxZoom: 11.5, resolution: 8 },
{ maxZoom: 13, resolution: 9 },
] as const;
export const POSTCODE_ZOOM_THRESHOLD = 15;
export const FEATURE_GRADIENT: { t: number; color: [number, number, number] }[] = [
{ t: 0, color: [46, 204, 113] },
{ t: 0.33, color: [241, 196, 15] },
{ t: 0.66, color: [231, 76, 60] },
{ t: 1, color: [142, 68, 173] },
];
/** Number of properties gradient — light mode (cream → orange) */
export const DENSITY_GRADIENT: { t: number; color: [number, number, number] }[] = [
{ t: 0, color: [255, 255, 255] },
{ t: 0.1, color: [248, 233, 211] },
{ t: 0.5, color: [255, 221, 173] },
{ t: 0.8, color: [251, 171, 60] },
{ t: 1, color: [255, 162, 31] },
];
/** Number of properties gradient — dark mode (dark warm → bright amber) */
export const DENSITY_GRADIENT_DARK: { t: number; color: [number, number, number] }[] = [
{ t: 0, color: [55, 45, 35] },
{ t: 0.1, color: [85, 65, 40] },
{ t: 0.5, color: [170, 115, 50] },
{ t: 0.8, color: [230, 155, 45] },
{ t: 1, color: [255, 170, 40] },
];
/** Protomaps font glyphs URL (served locally from public/assets/) */
export const GLYPHS_URL = '/assets/fonts/{fontstack}/{range}.pbf';
/** Twemoji base URL (served locally from public/assets/) */
export const TWEMOJI_BASE = '/assets/twemoji/';
/** POI group → RGB color for category-coded map markers */
export const POI_GROUP_COLORS: Record<string, [number, number, number]> = {
'Public Transport': [59, 130, 246],
Leisure: [249, 115, 22],
Education: [139, 92, 246],
Health: [239, 68, 68],
'Emergency Services': [220, 38, 38],
Other: [107, 114, 128],
Groceries: [34, 197, 94],
'Local Businesses': [245, 158, 11],
Culture: [236, 72, 153],
Services: [6, 182, 212],
Shops: [99, 102, 241],
};
/** Default color for unknown POI groups */
export const POI_DEFAULT_COLOR: [number, number, number] = [107, 114, 128];
/** Categories only shown when zoomed in past MINOR_POI_ZOOM_THRESHOLD */
export const MINOR_POI_CATEGORIES = new Set(['Bus stop', 'Taxi rank', 'EV Charging', 'Playground']);
/** Zoom level below which minor POI categories are hidden */
export const MINOR_POI_ZOOM_THRESHOLD = 14;
/** Supercluster grouping radius in pixels */
export const POI_CLUSTER_RADIUS = 50;
/** Zoom level at which supercluster stops clustering */
export const POI_CLUSTER_MAX_ZOOM = 15;
/**
* Groups whose features should be collapsed into stacked bar charts.
* Keyed by feature group name. Each entry defines one stacked chart.
*/
export const STACKED_GROUPS: Record<
string,
{
/** Display label for the chart */
label: string;
/** If set, use this feature's stats for the total and info popup. Otherwise sum components. */
feature?: string;
/** Suffix shown after the total value (e.g. "avg/yr") */
unit?: string;
/** Feature names that make up the segments */
components: string[];
}[]
> = {
Crime: [
{
label: 'Serious crime',
feature: 'Serious crime (avg/yr)',
unit: 'avg/yr',
components: [
'Violence and sexual offences (avg/yr)',
'Robbery (avg/yr)',
'Burglary (avg/yr)',
'Possession of weapons (avg/yr)',
],
},
{
label: 'Minor crime',
feature: 'Minor crime (avg/yr)',
unit: 'avg/yr',
components: [
'Anti-social behaviour (avg/yr)',
'Criminal damage and arson (avg/yr)',
'Shoplifting (avg/yr)',
'Bicycle theft (avg/yr)',
'Theft from the person (avg/yr)',
'Other theft (avg/yr)',
'Vehicle crime (avg/yr)',
'Public order (avg/yr)',
'Drugs (avg/yr)',
'Other crime (avg/yr)',
],
},
],
Demographics: [
{
label: 'Ethnic composition',
unit: '%',
components: ['% White', '% South Asian', '% East Asian', '% Black', '% Mixed', '% Other'],
},
],
};
/**
* Groups whose enum features should be collapsed into compact multi-row charts.
* Keyed by feature group name. Each entry defines one stacked enum chart.
*/
export const STACKED_ENUM_GROUPS: Record<
string,
{
/** Display label for the chart */
label: string;
/** If set, use this feature for the info popup */
feature?: string;
/** Enum feature names that make up the rows */
components: string[];
/** Value order for consistent segment ordering */
valueOrder: string[];
/** Colors for each value (matches valueOrder) */
valueColors: string[];
}[]
> = {
Property: [
{
label: 'Property type',
feature: 'Property type',
components: ['Property type'],
valueOrder: ['Detached', 'Semi-Detached', 'Terraced', 'Flats/Maisonettes', 'Other'],
valueColors: ['#8b5cf6', '#3b82f6', '#14b8a6', '#f59e0b', '#6b7280'],
},
{
label: 'Leasehold/Freehold',
feature: 'Leasehold/Freehold',
components: ['Leasehold/Freehold'],
valueOrder: ['Freehold', 'Leasehold'],
valueColors: ['#3b82f6', '#f59e0b'],
},
],
};
/**
* Maximally-distinguishable palette for discrete enum features on the map.
* 10 colors chosen for perceptual distinctness in both light and dark modes.
*/
export const ENUM_PALETTE: [number, number, number][] = [
[59, 130, 246], // blue-500
[249, 115, 22], // orange-500
[139, 92, 246], // violet-500
[34, 197, 94], // green-500
[239, 68, 68], // red-500
[6, 182, 212], // cyan-500
[236, 72, 153], // pink-500
[245, 158, 11], // amber-500
[20, 184, 166], // teal-500
[107, 114, 128], // gray-500
];
/** Colors for stacked bar segments */
export const SEGMENT_COLORS = [
'#ef4444', // red-500
'#f97316', // orange-500
'#eab308', // yellow-500
'#22c55e', // green-500
'#14b8a6', // teal-500
'#06b6d4', // cyan-500
'#3b82f6', // blue-500
'#8b5cf6', // violet-500
'#d946ef', // fuchsia-500
'#ec4899', // pink-500
];