Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
339 changes: 126 additions & 213 deletions src/UI/components/map/mapView/MapHUD-v3.tsx

Large diffs are not rendered by default.

600 changes: 503 additions & 97 deletions src/UI/components/map/mapView/downloadImageHandler.tsx

Large diffs are not rendered by default.

169 changes: 163 additions & 6 deletions src/UI/components/map/mapView/map-v3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,46 @@ import { OverlayPanel } from '../layers/OverlayPanel';
import { TimeSeriesMapSlider } from './DateTimeSlider';
import { registerDownloadHandler } from './downloadImageHandler';
import theme from '../../../styles/theme';
import { VectorAtlasFilters } from '../../../state/state.types';
import {
VectorAtlasFilters,
WMTSWorkspacesEnum,
} from '../../../state/state.types';

const SPECIES_WITH_COLORS = new Set([
'arabiensis',
'coluzzii_gambiae_m form',
'funestus',
'gambiae_s form',
'gambiae_s form_m form',
'melas',
'merus',
'moucheti',
'nili',
'coustani',
'coustani complex',
'funestus complex',
'gambiae complex',
'hybrid_coluzzii_melas',
'hybrid_funestus_rivulorum-like',
'hybrid_gambiae_melas',
'leesoni',
'marshallii',
'marshallii complex',
'multicolor',
'nili complex',
'ovengensis',
'paludis',
'parensis',
'pharoensis',
'rivulorum',
'rivulorum complex',
'sergentii',
'stephensi',
'theileri',
'vaneedeni',
'wellcomei',
'ziemanni',
]);

type MapWrapperV3Props = {
doiResolverId?: string;
Expand Down Expand Up @@ -115,6 +154,11 @@ const MapWrapperV3: React.FC<MapWrapperV3Props> = ({ doiResolverId }) => {

const occurrenceData = useAppSelector((s) => s.map.occurrence_data);

// Pull the timeSeries state slice alongside your existing selectors
const timeSeries = useAppSelector((s) => s.map.timeSeries);

const wmtsLayers = useAppSelector((s) => s.map.wmtsLayers);

const filters = useAppSelector((s) => s.map.filters);

const drawerOpen = useAppSelector((s) => s.map.map_drawer.open);
Expand All @@ -126,8 +170,6 @@ const MapWrapperV3: React.FC<MapWrapperV3Props> = ({ doiResolverId }) => {
const mapOverlays = useAppSelector((s) => s.map.map_overlays);

const fullSpeciesList = useAppSelector((s) => s.map.filterValues.species);

const wmtsLayers = useAppSelector((s) => s.map.wmtsLayers);
const preloadingLayers = useAppSelector((s) => s.map.preloadingLayers);

const areaModeOn = useAppSelector((s) => s.map.areaSelectModeOn);
Expand Down Expand Up @@ -871,6 +913,9 @@ const MapWrapperV3: React.FC<MapWrapperV3Props> = ({ doiResolverId }) => {
center: transform([20, -5], 'EPSG:4326', 'EPSG:3857'),

zoom: 4,
// [top, right, bottom, left] padding in pixels
// Reserves 320px on the right and 80px on the bottom for canvas legends
padding: [20, 320, 80, 20],
}),
});

Expand Down Expand Up @@ -1295,14 +1340,126 @@ const MapWrapperV3: React.FC<MapWrapperV3Props> = ({ doiResolverId }) => {
hoverAbsenceSource.changed();
}, [hoveredSpecies, showDetected, showNotDetected]);

// 1. Compute Active Insecticide Overlay Name
const activeIrOverlayName = useMemo(() => {
// Check direct WMTS layer
const visibleLayer = wmtsLayers?.find(
(l: any) =>
l.isVisible && (l.workspace === 'ir_maps' || l.workspace === 'ir')
);
if (visibleLayer) {
return visibleLayer.name.replace(/^ir_/i, '').replace(/_/g, ' ');
}

// Check active Time Series group
if (timeSeries?.groups) {
const activeGroup = Object.values(timeSeries.groups).find(
(g: any) => g.isPlaybackActive && g.category === 'ir'
);
if (activeGroup) {
return activeGroup.groupName;
}
}

return null;
}, [wmtsLayers, timeSeries?.groups]);

// 2. Compute Active Species Raster Overlay Name (Checks WMTS layers & Time Series)
const activeSpeciesOverlayName = useMemo(() => {
// Check direct WMTS species raster layer
const visibleLayer = wmtsLayers?.find(
(l: any) =>
l.isVisible &&
(l.workspace === 'species' || l.workspace === 'species_maps')
);
if (visibleLayer) {
return visibleLayer.name.replace(/^species_/i, '').replace(/_/g, ' ');
}

// Check active Time Series species group
if (timeSeries?.groups) {
const activeGroup = Object.values(timeSeries.groups).find(
(g: any) =>
g.isPlaybackActive &&
(g.category === 'species' || g.category === 'raster')
);
if (activeGroup) {
return activeGroup.groupName;
}
}

return null;
}, [wmtsLayers, timeSeries?.groups]);

// 3. Compute Active Year from Epoch Timestamp
const activeYear = useMemo(() => {
if (!timeSeries?.currentTime || !timeSeries?.groups) return null;

const activeGroup = Object.values(timeSeries.groups).find(
(g: any) => g.isPlaybackActive
);
if (!activeGroup || !Array.isArray(activeGroup.temporalLayers)) return null;

const matched = activeGroup.temporalLayers.find(
(tl: any) =>
timeSeries.currentTime! >= tl.startTime &&
timeSeries.currentTime! <= tl.endTime
);

return matched?.timeString ?? null;
}, [timeSeries?.currentTime, timeSeries?.groups]);

// 4. Derive active species name (Raster Overlay first, fallback to point filter)
const selectedSpeciesName =
activeSpeciesOverlayName ??
(filters.species?.value && filters.species.value.length > 0
? filters.species.value[0]
: null);

// Compute active species list for image export legend
const activeExportSpecies = useMemo(() => {
if (filters.species?.value && filters.species.value.length > 0) {
return filters.species.value;
}

return fullSpeciesList.filter((s) => {
const clean = normalize(s);
return (
SPECIES_WITH_COLORS.has(clean) && !clean.includes('other anopheles')
);
});
}, [filters.species, fullSpeciesList]);

/* Register map download handler */
useEffect(() => {
if (!map) return;
return registerDownloadHandler(map, filters.species, speciesStyles);
}, [map, filters.species, speciesStyles]);

const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
// Only build overlayLabel if an overlay or species filter is ACTUALLY selected
const overlayLabel = activeIrOverlayName
? `INSECTICIDE: ${activeIrOverlayName}`
: selectedSpeciesName
? `SPECIES: ${selectedSpeciesName}`
: ''; // Empty string so download handler knows NO overlay is selected

return registerDownloadHandler(
map,
{
species: activeExportSpecies,
overlay: overlayLabel.toUpperCase(),
year: activeYear ?? '',
},
speciesStyles
);
}, [
map,
activeIrOverlayName,
activeYear,
selectedSpeciesName,
activeExportSpecies,
speciesStyles,
]);

const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down('sm'));
/* ---------------- render ---------------- */

return (
Expand Down
11 changes: 11 additions & 0 deletions src/UI/public/messages/en-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,5 +827,16 @@
"roleRequestSuccess": "Role request submitted.",
"notificationEnabledSuccess": "Notifications enabled"
}
},
"MapHUD": {
"vectorPanel": "VECTOR PANEL",
"totalLoadedRecords": "Total Loaded Occurrence Records",
"clickCardToToggle": "Click a card to toggle map visibility",
"detected": "Detected",
"notDetected": "Not detected",
"on": "ON",
"off": "OFF",
"vectorsOnMap": "Vectors On Map",
"otherAnopheles": "Other Anopheles"
}
}
11 changes: 11 additions & 0 deletions src/UI/public/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,17 @@
"byArea": "Select by area",
"removeSelection": "Remove Selection"
}
},
"MapHUD": {
"vectorPanel": "VECTOR PANEL",
"totalLoadedRecords": "Total Loaded Occurrence Records",
"clickCardToToggle": "Click a card to toggle map visibility",
"detected": "Detected",
"notDetected": "Not detected",
"on": "ON",
"off": "OFF",
"vectorsOnMap": "Vectors on Map",
"otherAnopheles": "Other Anopheles"
},
"UserInfo": {
"hello": "Hello",
Expand Down
11 changes: 11 additions & 0 deletions src/UI/public/messages/fr-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -496,5 +496,16 @@
"transformSuccess": "Modèle téléchargé et transformé",
"deleteSuccess": "Modèle supprimé avec succès"
}
},
"MapHUD": {
"vectorPanel": "PANNEAU VECTORIEL",
"totalLoadedRecords": "Total des enregistrements d'occurrences chargés",
"clickCardToToggle": "Cliquez sur une carte pour basculer la visibilité de la carte",
"detected": "Détecté",
"notDetected": "Não detectado",
"on": "ACTIVÉ",
"off": "DÉSACTIVÉ",
"vectorsOnMap": "Vecteurs sur la carte",
"otherAnopheles": "Autres Anophèles"
}
}
11 changes: 11 additions & 0 deletions src/UI/public/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,17 @@
"byArea": "Sélectionner par région",
"removeSelection": "Supprimer la sélection"
}
},
"MapHUD": {
"vectorPanel": "PANNEAU VECTORIEL",
"totalLoadedRecords": "Total des enregistrements d'occurrences chargés",
"clickCardToToggle": "Cliquez sur une carte pour basculer la visibilité de la carte",
"detected": "Détecté",
"notDetected": "Non détecté",
"on": "ACTIVÉ",
"off": "DÉSACTIVÉ",
"vectorsOnMap": "Vecteurs sur la carte",
"otherAnopheles": "Autres Anophèles"
},
"UserInfo": {
"hello": "Bonjour",
Expand Down
11 changes: 11 additions & 0 deletions src/UI/public/messages/pt-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -503,5 +503,16 @@
"approveSuccess": "DOI aprovado com sucesso",
"rejectSuccess": "DOI rejeitado com sucesso"
}
},
"MapHUD": {
"vectorPanel": "PAINEL DE VETORES",
"totalLoadedRecords": "Total de registros de ocorrências carregados",
"clickCardToToggle": "Clique em um card para alternar a visibilidade do mapa",
"detected": "Detectado",
"notDetected": "Não detectado",
"on": "LIGADO",
"off": "DESLIGADO",
"vectorsOnMap": "Vetores no Mapa",
"otherAnopheles": "Outros Anopheles"
}
}
11 changes: 11 additions & 0 deletions src/UI/public/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,17 @@
"byArea": "Selecione por área",
"removeSelection": "Remova a seleção"
}
},
"MapHUD": {
"vectorPanel": "PAINEL DE VETORES",
"totalLoadedRecords": "Total de registros de ocorrências carregados",
"clickCardToToggle": "Clique em um card para alternar a visibilidade do mapa",
"detected": "Detectado",
"notDetected": "Não detectado",
"on": "LIGADO",
"off": "DESLIGADO",
"vectorsOnMap": "Vetores no Mapa",
"otherAnopheles": "Outros Anopheles"
},
"UserInfo": {
"hello": "Olá",
Expand Down
1 change: 1 addition & 0 deletions src/UI/state/map/actions/getOccurrenceData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
const emptyFilters: MapState['filters'] = {
country: { value: [] },
species: { value: [] },
year: { value: [] },
bionomics: { value: [] },
insecticide: { value: [] },
binary_presence: { value: [] },
Expand Down
1 change: 1 addition & 0 deletions src/UI/state/map/mapSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const initialState: () => MapState = () => ({
filters: {
country: { value: [] },
species: { value: [] },
year: { value: [] },
bionomics: { value: [] },
insecticide: { value: [] },
binary_presence: { value: [] },
Expand Down
8 changes: 8 additions & 0 deletions src/UI/state/speciesInformation/speciesInformationSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,12 @@ export const {
setCurrentInfoDetails,
} = speciesInformationSlice.actions;

export const selectAllSpecies = (state: {
speciesInfo: SpeciesInformationState;
}) => state.speciesInfo.speciesDict.items;

export const selectSpeciesLoading = (state: {
speciesInfo: SpeciesInformationState;
}) => state.speciesInfo.loading;

export default speciesInformationSlice.reducer;
1 change: 1 addition & 0 deletions src/UI/state/state.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type VectorAtlasFilters = {
country: MapFilter<string[] | string>;
species: MapFilter<string[]>;
insecticide: MapFilter<string[]>;
year: MapFilter<string[] | string>;
binary_presence: MapFilter<string[]>;
abundance_data: MapFilter<string[]>;
bionomics: MapFilter<boolean[]>;
Expand Down
Loading