From b70132a5228dd0172cfc22aa123323baf596099f Mon Sep 17 00:00:00 2001 From: shittu adewale Date: Mon, 23 Feb 2026 16:09:21 +0100 Subject: [PATCH 1/2] feat: implement mapswipe legend --- frontend/src/config/index.ts | 2 +- .../src/constants/ui-contents/map-content.ts | 5 ++ frontend/src/enums/mapswipe.ts | 6 +++ .../mapswipe/components/agreement-legend.tsx | 30 ++++++++++++ .../components/maps/training-area-map.tsx | 40 +++++++++++---- .../components/map/legend-control.tsx | 49 ++++++++++++------- frontend/src/types/ui-contents.ts | 5 ++ 7 files changed, 107 insertions(+), 30 deletions(-) create mode 100644 frontend/src/features/mapswipe/components/agreement-legend.tsx diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index 7421efd18..07ae4edd6 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -527,6 +527,6 @@ export const ELEMENT_DISTANCE_FROM_NAVBAR: number = 10; * Mapswipe agreement colors for fill and outline. * Green for agreement is 1, Red for agreement is 0, and Purple for 0 < agreement < 1. */ -export const MAPSWIPE_AGREEMENT_FILL_COLORS = { green: "#22c55e", red: "#ef4444", purple: "#663399" }; +export const MAPSWIPE_AGREEMENT_FILL_COLORS = { green: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.ACCEPTED], red: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.REJECTED], purple: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.UNTOUCHED] }; export const MAPSWIPE_AGREEMENT_OUTLINE_COLORS = { green: "#16a34a", red: "#dc2626", purple: "#4b2270" }; diff --git a/frontend/src/constants/ui-contents/map-content.ts b/frontend/src/constants/ui-contents/map-content.ts index e5f4923d6..2c80cbed4 100644 --- a/frontend/src/constants/ui-contents/map-content.ts +++ b/frontend/src/constants/ui-contents/map-content.ts @@ -6,4 +6,9 @@ export const MAP_CONTENT: TMapContent = { tooltip: "Click to adjust the map view to fit the bounds.", }, }, + agreementLegend: { + fullAgreement: "Full Agreement (1)", + partialAgreement: "Partial Agreement (0–1)", + noAgreement: "No Agreement (0)", +}, }; diff --git a/frontend/src/enums/mapswipe.ts b/frontend/src/enums/mapswipe.ts index b1b69f625..55a550a7a 100644 --- a/frontend/src/enums/mapswipe.ts +++ b/frontend/src/enums/mapswipe.ts @@ -16,3 +16,9 @@ export enum MapSwipeProcessingStatus { READY_TO_PUBLISH = "READY_TO_PUBLISH", DRAFT = "DRAFT", } + +export enum AgreementStatus { + FULL = "full", + PARTIAL = "partial", + NONE = "none", +} diff --git a/frontend/src/features/mapswipe/components/agreement-legend.tsx b/frontend/src/features/mapswipe/components/agreement-legend.tsx new file mode 100644 index 000000000..c3243e095 --- /dev/null +++ b/frontend/src/features/mapswipe/components/agreement-legend.tsx @@ -0,0 +1,30 @@ +import { Legend } from "@/features/start-mapping/components"; +import { AgreementStatus } from "@/enums/mapswipe"; +import { MAP_CONTENT } from "@/constants"; +import { LegendItem } from "@/features/start-mapping/components/map/legend-control"; +import { MAPSWIPE_AGREEMENT_FILL_COLORS } from "@/config"; + +const agreementLegendItems: (LegendItem & { status: AgreementStatus })[] = [ + { + status: AgreementStatus.FULL, + label: MAP_CONTENT.agreementLegend.fullAgreement, + fillColor: MAPSWIPE_AGREEMENT_FILL_COLORS.green, + fillOpacity: 0.3, + }, + { + status: AgreementStatus.PARTIAL, + label: MAP_CONTENT.agreementLegend.partialAgreement, + fillColor: MAPSWIPE_AGREEMENT_FILL_COLORS.purple, + fillOpacity: 0.3, + }, + { + status: AgreementStatus.NONE, + label: MAP_CONTENT.agreementLegend.noAgreement, + fillColor: MAPSWIPE_AGREEMENT_FILL_COLORS.red, + fillOpacity: 0.3, + }, +]; + +export const AgreementLegend = () => { + return ; +}; diff --git a/frontend/src/features/models/components/maps/training-area-map.tsx b/frontend/src/features/models/components/maps/training-area-map.tsx index 40df1da12..c9217558d 100644 --- a/frontend/src/features/models/components/maps/training-area-map.tsx +++ b/frontend/src/features/models/components/maps/training-area-map.tsx @@ -9,10 +9,11 @@ import { MapLayerMouseEvent, Popup, SourceSpecification, - ExpressionSpecification + ExpressionSpecification, } from "maplibre-gl"; +import { AgreementLegend } from "@/features/mapswipe/components/agreement-legend"; -import { showErrorToast, addLayers, addSources} from "@/utils"; +import { showErrorToast, addLayers, addSources } from "@/utils"; import { MAPSWIPE_AGREEMENT_FILL_COLORS, MAPSWIPE_AGREEMENT_OUTLINE_COLORS, @@ -28,16 +29,20 @@ import { TRAINING_AREAS_AOI_OUTLINE_WIDTH, } from "@/config"; +type VectorLayerMeta = LayerSpecification & { + fields?: Record; +}; + type Metadata = { name?: string; type?: string; tilestats?: unknown; - vector_layers: LayerSpecification[]; + vector_layers: VectorLayerMeta[]; }; // Choropleth fill color for MapSwipe results based on "agreement" property: // agreement === 1 = green // agreement === 0 = red -// 0 < agreement < 1 = purple (rebeccapurple #663399) +// 0 < agreement < 1 = purple const buildAgreementColorExpression = ( defaultColor: string, colors: { green: string; red: string; purple: string }, @@ -52,7 +57,6 @@ const buildAgreementColorExpression = ( colors.purple, ]; - const getLayerConfigs = ( layerType: string, isPredictionResult: boolean = false, @@ -69,7 +73,10 @@ const getLayerConfigs = ( return { fill: { "fill-color": isPredictionResult - ? buildAgreementColorExpression(defaultFillColor, MAPSWIPE_AGREEMENT_FILL_COLORS) + ? buildAgreementColorExpression( + defaultFillColor, + MAPSWIPE_AGREEMENT_FILL_COLORS, + ) : defaultFillColor, "fill-opacity": isPredictionResult ? 0.6 @@ -79,7 +86,10 @@ const getLayerConfigs = ( }, outline: { "line-color": isPredictionResult - ? buildAgreementColorExpression(defaultOutlineColor, MAPSWIPE_AGREEMENT_OUTLINE_COLORS) + ? buildAgreementColorExpression( + defaultOutlineColor, + MAPSWIPE_AGREEMENT_OUTLINE_COLORS, + ) : defaultOutlineColor, "line-width": isAoi ? TRAINING_AREAS_AOI_OUTLINE_WIDTH @@ -122,6 +132,7 @@ export const TrainingAreaMap = ({ const { mapContainerRef, map } = useMapInstance(true); const [vectorLayers, setVectorLayers] = useState([]); + const [hasAgreement, setHasAgreement] = useState(false); const popupRef = useRef(null); @@ -141,7 +152,10 @@ export const TrainingAreaMap = ({ const mapLayers: LayerSpecification[] = useMemo( () => vectorLayers.flatMap((layer) => { - const { fill, outline, circle } = getLayerConfigs(layer.id, isPredictionResult); + const { fill, outline, circle } = getLayerConfigs( + layer.id, + isPredictionResult, + ); const layers: LayerSpecification[] = [ { @@ -302,7 +316,11 @@ export const TrainingAreaMap = ({ const metadata = (await pmtilesFile.getMetadata()) as Metadata; const layers = metadata.vector_layers; - + if (isPredictionResult) { + setHasAgreement( + layers.some((layer) => layer.fields && "agreement" in layer.fields), + ); + } setVectorLayers(layers); } catch (error) { console.error("Error loading PMTiles:", error); @@ -362,6 +380,8 @@ export const TrainingAreaMap = ({ mapContainerRef={mapContainerRef} map={map} showCurrentZoom - /> + > + {isPredictionResult && hasAgreement && } + ); }; diff --git a/frontend/src/features/start-mapping/components/map/legend-control.tsx b/frontend/src/features/start-mapping/components/map/legend-control.tsx index 4689cb74f..9ecb1af4b 100644 --- a/frontend/src/features/start-mapping/components/map/legend-control.tsx +++ b/frontend/src/features/start-mapping/components/map/legend-control.tsx @@ -6,6 +6,13 @@ import { useState, useCallback } from "react"; import { PredictedFeatureStatus } from "@/enums/start-mapping"; import { PREDICTED_LAYER_STATUS_COLORS } from "@/config"; + +export type LegendItem = { + label: string; + fillColor: string; + fillOpacity: number; +}; + const statusLegend = [ { status: PredictedFeatureStatus.ACCEPTED, @@ -52,9 +59,11 @@ const FillLegendStyle = ({ export const Legend = ({ disableDefaultPrediction = false, title = "Predictions", + items, }: { disableDefaultPrediction?: boolean; title?: string; + items?: LegendItem[]; }) => { const { isSmallViewport } = useScreenSize(); const [expandLegend, setExpandLegend] = useState(true); @@ -63,6 +72,14 @@ export const Legend = ({ setExpandLegend((prev) => !prev); }, []); + const legendItems: LegendItem[] = items + ? items + : statusLegend.filter((v) => + disableDefaultPrediction + ? v.status !== PredictedFeatureStatus.UNTOUCHED + : v, + ); + return ( ); -}; +}; \ No newline at end of file diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index 72026583c..53501abbf 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -720,6 +720,11 @@ export type TMapContent = { tooltip: string; }; }; + agreementLegend: { + fullAgreement: string; + partialAgreement: string; + noAgreement: string; + }; }; // Map content types end. From 4fc0a315bf06bc7cb4e0b770818c1915938d596b Mon Sep 17 00:00:00 2001 From: shittu adewale Date: Tue, 24 Feb 2026 13:41:09 +0100 Subject: [PATCH 2/2] refactor: perform formatting and set mapswipe outline to prediction --- frontend/src/config/index.ts | 15 +++++++++++---- frontend/src/constants/ui-contents/map-content.ts | 8 ++++---- .../components/map/legend-control.tsx | 3 +-- frontend/src/types/ui-contents.ts | 2 +- frontend/src/utils/general-utils.ts | 2 -- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index 07ae4edd6..0f781af54 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -524,9 +524,16 @@ export const OFFSET_STEP: number = parseIntEnv(ENVS.OFFSET_STEP, 0.5); export const ELEMENT_DISTANCE_FROM_NAVBAR: number = 10; /** - * Mapswipe agreement colors for fill and outline. + * Mapswipe agreement colors for fill and outline. * Green for agreement is 1, Red for agreement is 0, and Purple for 0 < agreement < 1. */ -export const MAPSWIPE_AGREEMENT_FILL_COLORS = { green: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.ACCEPTED], red: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.REJECTED], purple: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.UNTOUCHED] }; -export const MAPSWIPE_AGREEMENT_OUTLINE_COLORS = { green: "#16a34a", red: "#dc2626", purple: "#4b2270" }; - +export const MAPSWIPE_AGREEMENT_FILL_COLORS = { + green: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.ACCEPTED], + red: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.REJECTED], + purple: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.UNTOUCHED], +}; +export const MAPSWIPE_AGREEMENT_OUTLINE_COLORS = { + green: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.ACCEPTED], + red: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.REJECTED], + purple: PREDICTED_LAYER_STATUS_COLORS[PredictedFeatureStatus.UNTOUCHED], +}; diff --git a/frontend/src/constants/ui-contents/map-content.ts b/frontend/src/constants/ui-contents/map-content.ts index 2c80cbed4..605494b1c 100644 --- a/frontend/src/constants/ui-contents/map-content.ts +++ b/frontend/src/constants/ui-contents/map-content.ts @@ -7,8 +7,8 @@ export const MAP_CONTENT: TMapContent = { }, }, agreementLegend: { - fullAgreement: "Full Agreement (1)", - partialAgreement: "Partial Agreement (0–1)", - noAgreement: "No Agreement (0)", -}, + fullAgreement: "Full Agreement (1)", + partialAgreement: "Partial Agreement (0–1)", + noAgreement: "No Agreement (0)", + }, }; diff --git a/frontend/src/features/start-mapping/components/map/legend-control.tsx b/frontend/src/features/start-mapping/components/map/legend-control.tsx index 9ecb1af4b..9817a86e9 100644 --- a/frontend/src/features/start-mapping/components/map/legend-control.tsx +++ b/frontend/src/features/start-mapping/components/map/legend-control.tsx @@ -6,7 +6,6 @@ import { useState, useCallback } from "react"; import { PredictedFeatureStatus } from "@/enums/start-mapping"; import { PREDICTED_LAYER_STATUS_COLORS } from "@/config"; - export type LegendItem = { label: string; fillColor: string; @@ -146,4 +145,4 @@ export const Legend = ({ )} ); -}; \ No newline at end of file +}; diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index 53501abbf..767763912 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -720,7 +720,7 @@ export type TMapContent = { tooltip: string; }; }; - agreementLegend: { + agreementLegend: { fullAgreement: string; partialAgreement: string; noAgreement: string; diff --git a/frontend/src/utils/general-utils.ts b/frontend/src/utils/general-utils.ts index 4abd9e1a3..5044ca15c 100644 --- a/frontend/src/utils/general-utils.ts +++ b/frontend/src/utils/general-utils.ts @@ -150,5 +150,3 @@ export const getYouTubeEmbedUrl = (url: string) => { ? `https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1` : BACKUP_VIDEO_URL; // fallback }; - -