223 lines
6.8 KiB
TypeScript
223 lines
6.8 KiB
TypeScript
import type { ViewState } from '../types';
|
||
|
||
export const INITIAL_RETRY_MS = 1000;
|
||
export const MAX_RETRY_MS = 10000;
|
||
|
||
/** Lower percentile for color-range clipping (0–100) */
|
||
export const COLOR_RANGE_LOW_PERCENTILE = 5;
|
||
/** Upper percentile for color-range clipping (0–100) */
|
||
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
|
||
];
|