Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f0194a3
Delete now unused/obsolete backend part of the repo
jmsssc Oct 6, 2025
ca2cf5c
Refactor to bring grid approach back and group state more logically
jmsssc Oct 6, 2025
ff5a122
Restore Grid view via hooks approach, and using actual NUTS API direc…
jmsssc Oct 7, 2025
2e922bf
Add mock API data files for testing different geographic regions
jmsssc Oct 9, 2025
10d3701
Coordinates to mock data backend easier and fake webserver to serve t…
jmsssc Oct 9, 2025
f996c9f
Refactor to MobX stores with NUTs + Grid approach
jmsssc Oct 13, 2025
eaabfa1
Refactor to full Mobx approach (Grid mode requesting fine with corres…
jmsssc Oct 14, 2025
b760d24
Working grid cell (still not viewport or alter then data resolution s…
jmsssc Oct 14, 2025
9ebbc15
Remove now unncessary files, and apply better z index for cities so t…
jmsssc Oct 14, 2025
c432814
Fixes for grid cell alignment when zoomed in and add debug code to wo…
jmsssc Oct 21, 2025
6f00ba3
Fix build errors (w/ AI)
jmsssc Oct 21, 2025
6177028
Commit context files from custom hooks --> Stores change
jmsssc Oct 22, 2025
a81d063
Temporarily remove cities layer as it caused significant lag
jmsssc Oct 22, 2025
9bd262c
Pass missing arg for Grid mode data
jmsssc Oct 23, 2025
17d1e55
Pass missing arg for Grid mode data
jmsssc Oct 23, 2025
077bb2f
Add Map UI Interactions store
jmsssc Oct 23, 2025
e49e790
Include pnpm run build fixes.
jmsssc Oct 23, 2025
8310a10
Working Viewport bounds with debugging statements left in and add bio…
jmsssc Oct 23, 2025
6f9e68f
Use real NUTS Meta polygons data from backend to ensure consistency o…
jmsssc Dec 12, 2025
4dbad3d
Refactor to reduce the codebase complexity and remove obsolete files
jmsssc Dec 12, 2025
c936971
Grid size rerender based on users zoom level, and better default, for…
jmsssc Dec 12, 2025
d134a20
Grid size rerender based on users zoom level, and better default, for…
jmsssc Dec 13, 2025
d224434
Fix vertical lines issue from binning
jmsssc Dec 13, 2025
48ef5e8
NUTS API
jmsssc Dec 16, 2025
ab1ceaf
Simplify NUTS API
jmsssc Dec 16, 2025
34a5e65
Fix build error
jmsssc Dec 16, 2025
6ea4a45
Fix build error (Part 2)
jmsssc Dec 16, 2025
46e7645
Merge branch 'main' into get_region_metadata_from_db_api
jmsssc Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ It can be configured to request and display NUTS3 regions, or worldwide equivale
For the worldwide view, the data is projected from individual points into polygons on the frontend.

## Main components diagram:
(Out of date since 6/10/2025 Grid Refactoring to restore grid functionality and new NUTS API)
<img width="1370" height="847" alt="image" src="https://github.com/user-attachments/assets/bb7daba0-d421-477b-9005-d5a116a119b2" />
(Out of date, new diagram needed)

## Example: Worldwide Simple R0 example:
<img width="3700" height="1648" alt="image" src="https://github.com/user-attachments/assets/625d4432-faad-4e5f-b60f-4af96d6848b1" />
Expand All @@ -25,7 +24,7 @@ For the worldwide view, the data is projected from individual points into polygo


# Installation guide
- First, make sure the `onehealth-db` repository is running with the API accessible. The API must be able to return generated data for 2016 and 2017.
- First, make sure the `onehealth-db` repository is running with the API accessible. It depends upon a running postgres database, typically docker name `my-postgres`. The API must be able to return generated data for 2016 and 2017.
- Run `pnpm i` to install dependencies
- Run `pnpm run dev` to launch the application

Expand Down
13 changes: 0 additions & 13 deletions backend/README.md

This file was deleted.

120 changes: 0 additions & 120 deletions backend/sampleData/make_timelineable_stub_data.py

This file was deleted.

33 changes: 33 additions & 0 deletions frontend/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}
76 changes: 76 additions & 0 deletions frontend/src/component/Mapper/AdaptiveGridLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import L from "leaflet";
import { useMemo } from "react";
import { Popup, Rectangle } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { observer } from "mobx-react-lite";
import { gridProcessingStore } from "../../stores/GridProcessingStore";
import { temperatureDataStore } from "../../stores/TemperatureDataStore";
import { getColorFromGradient } from "./utilities/gradientUtilities";

const AdaptiveGridLayer = observer(() => {
const canvasRenderer = useMemo(() => L.canvas({ padding: 0.5 }), []);
const renderStart = performance.now();
const gridCells = gridProcessingStore.gridCells;
const processedDataExtremes = temperatureDataStore.processedDataExtremes;

console.log("📱 AdaptiveGridLayer render START - cells:", gridCells.length);

const getGridCellStyle = (temperature: number) => {
if (!processedDataExtremes)
return {
fillColor: "#ccc",
weight: 0.5,
opacity: 0.8,
color: "#666",
fillOpacity: 0.8,
};
const color = getColorFromGradient(temperature, processedDataExtremes);
return {
fillColor: color,
weight: 0.5,
opacity: 0.8,
color: "#666",
fillOpacity: 0.8,
};
};

// zIndex less than the city layer so those appear on top.
const result = (
<div style={{ zIndex: "335" }}>
{gridCells.map(
(
cell, // these adapt to different scales because cell.bounds changes.
) => (
<Rectangle
key={cell.id}
bounds={cell.bounds}
renderer={canvasRenderer}
interactive
pathOptions={getGridCellStyle(cell.temperature)}
eventHandlers={{
click: (e) => {
e.target.openPopup();
e.originalEvent?.stopPropagation();
},
}}
>
<Popup className="grid-popup">
<p>{cell.temperature.toFixed(2)}</p>
<p>Coordinates: {cell.id}</p>
</Popup>
</Rectangle>
),
)}
</div>
); // todo: Replace just teh cell.temperature above: rename temperature to .value (as it is used for R0 too etc) and provide the label.
// the label should be in some kind of mobx store or passed as prop.

const renderTime = performance.now() - renderStart;
console.log(
`📱 AdaptiveGridLayer render COMPLETE - ${gridCells.length} cells in ${renderTime.toFixed(2)}ms`,
);

return result;
});

export default AdaptiveGridLayer;
71 changes: 27 additions & 44 deletions frontend/src/component/Mapper/CitiesLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,28 @@
import * as turf from "@turf/turf";
import type { MultiPolygon, Polygon } from "geojson";
import L from "leaflet";
import { useEffect, useState } from "react";
import { CircleMarker, Marker, Pane, Popup } from "react-leaflet";
import type { NutsGeoJSON, WorldwideGeoJSON } from "./types";
import { MAX_ZOOM, MIN_ZOOM } from "./utilities/mapDataUtils.tsx";

interface City {
name: string;
lat: number;
lng: number;
population: number;
country: string;
}

interface CityCSV {
city: string;
city_ascii: string;
lat: string;
lng: string;
pop: string;
country: string;
iso2: string;
iso3: string;
province: string;
}

interface CitiesLayerProps {
zoom: number;
dataRegions?: NutsGeoJSON | WorldwideGeoJSON | null;
}
const CitiesLayer = ({
zoom: _zoom,
dataRegions: _dataRegions,
}: CitiesLayerProps) => <div />;

// Temporarily shim because I realised CitiesLayer was the source of lag, not either of the new APIs!
// with reference to: https://github.com/ssciwr/onehealth-map-frontend/issues/54#issuecomment-3433209012

const CitiesLayer = ({ zoom, dataRegions }: CitiesLayerProps) => {
/*
const CitiesLayer2 = ({ zoom, dataRegions }: CitiesLayerProps) => {
const [cities, setCities] = useState<City[]>([]);

useEffect(() => {
const loadCities = async () => {
try {
// Load from CSV data source (MIT licensed)
/* This data was downloaded in early August 2025, from https://www.simplemaps.aspiringeconomist.com/resources/world-cities-data
- the Natural Earth Populated Places 2015 data with 7,300 cities/towns.
Importantly the data is from 2015.
*/
// This data was downloaded in early August 2025, from https://www.simplemaps.aspiringeconomist.com/resources/world-cities-data
// - the Natural Earth Populated Places 2015 data with 7,300 cities/towns.
// Importantly the data is from 2015.
const response = await fetch("/data/world_cities.csv");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
Expand Down Expand Up @@ -203,6 +184,7 @@ const CitiesLayer = ({ zoom, dataRegions }: CitiesLayerProps) => {
});
};

console.log("Current zoom for cities layer is: ", zoom);
const threshold = getPopulationThreshold(zoom);
const citiesInDataRegions = filterCitiesByDataRegions(cities);
const visibleCities = citiesInDataRegions.filter(
Expand All @@ -211,7 +193,6 @@ const CitiesLayer = ({ zoom, dataRegions }: CitiesLayerProps) => {

return (
<>
{/* City marker dots - lower z-index */}
<Pane name="cityMarkersPane" style={{ zIndex: 100 }}>
{visibleCities.map((city) => (
<CircleMarker
Expand All @@ -224,22 +205,23 @@ const CitiesLayer = ({ zoom, dataRegions }: CitiesLayerProps) => {
weight={1}
opacity={0.8}
>
<Popup>
<div style={{ fontSize: "14px", lineHeight: "1.4" }}>
<strong>{city.name}</strong>
<br />
<span style={{ color: "#666" }}>{city.country}</span>
<br />
<span style={{ fontSize: "12px" }}>
Population: {formatPopulation(city.population)}
</span>
</div>
</Popup>
<div style={{ zIndex: 390 }}>
<Popup>
<div style={{ fontSize: "14px", lineHeight: "1.4" }}>
<strong>{city.name}</strong>
<br />
<span style={{ color: "#666" }}>{city.country}</span>
<br />
<span style={{ fontSize: "12px" }}>
Population: {formatPopulation(city.population)}
</span>
</div>
</Popup>
</div>
</CircleMarker>
))}
</Pane>

{/* City name labels - higher z-index */}
<Pane name="cityLabelsPane" style={{ zIndex: 110 }}>
{visibleCities.map((city) => (
<Marker
Expand All @@ -252,5 +234,6 @@ const CitiesLayer = ({ zoom, dataRegions }: CitiesLayerProps) => {
</>
);
};
*/

export default CitiesLayer;
Loading
Loading