Skip to content
Merged
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
15 changes: 11 additions & 4 deletions frontend/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "#22c55e", red: "#ef4444", purple: "#663399" };
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],
};
5 changes: 5 additions & 0 deletions frontend/src/constants/ui-contents/map-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
},
};
6 changes: 6 additions & 0 deletions frontend/src/enums/mapswipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ export enum MapSwipeProcessingStatus {
READY_TO_PUBLISH = "READY_TO_PUBLISH",
DRAFT = "DRAFT",
}

export enum AgreementStatus {
FULL = "full",
PARTIAL = "partial",
NONE = "none",
}
30 changes: 30 additions & 0 deletions frontend/src/features/mapswipe/components/agreement-legend.tsx
Original file line number Diff line number Diff line change
@@ -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 <Legend title="Agreement" items={agreementLegendItems} />;
};
40 changes: 30 additions & 10 deletions frontend/src/features/models/components/maps/training-area-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -28,16 +29,20 @@ import {
TRAINING_AREAS_AOI_OUTLINE_WIDTH,
} from "@/config";

type VectorLayerMeta = LayerSpecification & {
fields?: Record<string, string>;
};

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 },
Expand All @@ -52,7 +57,6 @@ const buildAgreementColorExpression = (
colors.purple,
];


const getLayerConfigs = (
layerType: string,
isPredictionResult: boolean = false,
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -122,6 +132,7 @@ export const TrainingAreaMap = ({
const { mapContainerRef, map } = useMapInstance(true);

const [vectorLayers, setVectorLayers] = useState<LayerSpecification[]>([]);
const [hasAgreement, setHasAgreement] = useState(false);

const popupRef = useRef<Popup | null>(null);

Expand All @@ -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[] = [
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -362,6 +380,8 @@ export const TrainingAreaMap = ({
mapContainerRef={mapContainerRef}
map={map}
showCurrentZoom
/>
>
{isPredictionResult && hasAgreement && <AgreementLegend />}
</MapComponent>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ 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,
Expand Down Expand Up @@ -52,9 +58,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);
Expand All @@ -63,6 +71,14 @@ export const Legend = ({
setExpandLegend((prev) => !prev);
}, []);

const legendItems: LegendItem[] = items
? items
: statusLegend.filter((v) =>
disableDefaultPrediction
? v.status !== PredictedFeatureStatus.UNTOUCHED
: v,
);

return (
<button
className={`flex z-10 items-center gap-x-4 bg-white p-2.5 rounded-xl ${
Expand Down Expand Up @@ -103,24 +119,18 @@ export const Legend = ({
<div
className={`flex w-full ${isSmallViewport ? "flex-row gap-x-2" : "flex-col"} gap-y-3`}
>
{statusLegend
.filter((v) =>
disableDefaultPrediction
? v.status !== PredictedFeatureStatus.UNTOUCHED
: v,
)
.map(({ label, fillColor, fillOpacity }, id) => (
<p
className="w-full flex items-center text-dark gap-x-2 text-body-4 md:text-body-3 text-nowrap"
key={id}
>
<FillLegendStyle
fillColor={fillColor}
fillOpacity={fillOpacity}
/>
{label}
</p>
))}
{legendItems.map(({ label, fillColor, fillOpacity }, id) => (
<p
className="w-full flex items-center text-dark gap-x-2 text-body-4 md:text-body-3 text-nowrap"
key={id}
>
<FillLegendStyle
fillColor={fillColor}
fillOpacity={fillOpacity}
/>
{label}
</p>
))}
</div>
)}

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/types/ui-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,11 @@ export type TMapContent = {
tooltip: string;
};
};
agreementLegend: {
fullAgreement: string;
partialAgreement: string;
noAgreement: string;
};
};

// Map content types end.
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/utils/general-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,3 @@ export const getYouTubeEmbedUrl = (url: string) => {
? `https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1`
: BACKUP_VIDEO_URL; // fallback
};