diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2026-02-11 16:33:02 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2026-02-11 16:33:02 +0100 |
| commit | b2700b9ef9e34cebc90d669fd53bde91401cae52 (patch) | |
| tree | 32de878517fe04bd77f4bd40bf55e0f54bfeedae | |
| parent | a187bcf97de6d043cb663dd973c83cc887665d3a (diff) | |
Use provided colours in map
Closes #131
| -rw-r--r-- | src/frontend/app/components/stop/StopMapModal.tsx | 91 | ||||
| -rw-r--r-- | src/frontend/app/data/LineColors.ts | 64 | ||||
| -rw-r--r-- | src/frontend/app/data/LinesData.ts | 256 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.tsx | 8 | ||||
| -rw-r--r-- | src/frontend/app/utils/colours.ts | 26 |
5 files changed, 41 insertions, 404 deletions
diff --git a/src/frontend/app/components/stop/StopMapModal.tsx b/src/frontend/app/components/stop/StopMapModal.tsx index 688ec2e..30ac63f 100644 --- a/src/frontend/app/components/stop/StopMapModal.tsx +++ b/src/frontend/app/components/stop/StopMapModal.tsx @@ -8,9 +8,7 @@ import React, { } from "react"; import { Layer, Marker, Source, type MapRef } from "react-map-gl/maplibre"; import { Sheet } from "react-modal-sheet"; -import { useApp } from "~/AppContext"; import { AppMap } from "~/components/shared/AppMap"; -import { getLineColour } from "~/data/LineColors"; import type { Stop } from "~/data/StopDataProvider"; import "./StopMapModal.css"; @@ -23,18 +21,15 @@ export interface Position { export interface ConsolidatedCirculationForMap { id: string; - line: string; - route: string; currentPosition?: Position; stopShapeIndex?: number; - isPreviousTrip?: boolean; - previousTripShapeId?: string | null; - schedule?: { - shapeId?: string | null; - }; + colour: string; + textColour: string; shape?: any; } +// TODO: Replace `circulations`+`selectedCirculationId` with a single `selectedCirculation` prop + interface StopMapModalProps { stop: Stop; circulations: ConsolidatedCirculationForMap[]; @@ -50,12 +45,10 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ onClose, selectedCirculationId, }) => { - const { theme } = useApp(); const mapRef = useRef<MapRef | null>(null); const hasFitBounds = useRef(false); const userInteracted = useRef(false); const [shapeData, setShapeData] = useState<any | null>(null); - const [previousShapeData, setPreviousShapeData] = useState<any | null>(null); // Filter circulations that have GPS coordinates const busesWithPosition = useMemo( @@ -163,7 +156,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ } } else { // Current trip: Start from Bus (if not previous), End at User Stop - if (!previousShapeData && currentPos) { + if (currentPos) { const busIdx = findClosestStopIndex(stops, { lat: currentPos.latitude, lon: currentPos.longitude, @@ -234,7 +227,6 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ } }; - addShapePoints(previousShapeData, true); addShapePoints(shapeData, false); if (points.length === 0) { @@ -292,7 +284,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ } as any); } } catch {} - }, [stop, selectedBus, shapeData, previousShapeData]); + }, [stop, selectedBus, shapeData]); // Resize map and fit bounds when modal opens useEffect(() => { @@ -324,7 +316,6 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ hasFitBounds.current = false; userInteracted.current = false; setShapeData(null); - setPreviousShapeData(null); } }, [isOpen]); @@ -332,19 +323,16 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ useEffect(() => { if (!isOpen || !selectedBus) { setShapeData(null); - setPreviousShapeData(null); return; } if (selectedBus.shape) { setShapeData(selectedBus.shape); - setPreviousShapeData(null); handleCenter(); return; } setShapeData(null); - setPreviousShapeData(null); }, [isOpen, selectedBus]); if (!selectedBus && busesWithPosition.length === 0) { @@ -392,58 +380,6 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ handleCenter(); }} > - {/* Previous Shape Layer */} - {previousShapeData && selectedBus && ( - <Source - id="prev-route-shape" - type="geojson" - data={previousShapeData} - > - {/* 1. Black border */} - <Layer - id="prev-route-shape-border" - type="line" - paint={{ - "line-color": "#000000", - "line-width": 6, - "line-opacity": 0.8, - }} - layout={{ - "line-cap": "round", - "line-join": "round", - }} - /> - {/* 2. White background */} - <Layer - id="prev-route-shape-white" - type="line" - paint={{ - "line-color": "#FFFFFF", - "line-width": 4, - }} - layout={{ - "line-cap": "round", - "line-join": "round", - }} - /> - {/* 3. Colored dashes */} - <Layer - id="prev-route-shape-inner" - type="line" - paint={{ - "line-color": getLineColour(selectedBus.line) - .background, - "line-width": 4, - "line-dasharray": [2, 2], - }} - layout={{ - "line-cap": "round", - "line-join": "round", - }} - /> - </Source> - )} - {/* Shape Layer */} {shapeData && selectedBus && ( <Source id="route-shape" type="geojson" data={shapeData}> @@ -451,8 +387,8 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ id="route-shape-border" type="line" paint={{ - "line-color": "#000000", - "line-width": 5, + "line-color": selectedBus.textColour, + "line-width": 7, "line-opacity": 0.6, }} layout={{ @@ -464,10 +400,8 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ id="route-shape-inner" type="line" paint={{ - "line-color": getLineColour(selectedBus.line) - .background, - "line-width": 3, - "line-opacity": 0.7, + "line-color": selectedBus.colour, + "line-width": 5, }} layout={{ "line-cap": "round", @@ -484,8 +418,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ "circle-color": "#FFFFFF", "circle-radius": 4, "circle-stroke-width": 2, - "circle-stroke-color": getLineColour(selectedBus.line) - .background, + "circle-stroke-color": selectedBus.colour, }} /> </Source> @@ -557,7 +490,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({ > <path d="M12 2 L22 22 L12 17 L2 22 Z" - fill={getLineColour(selectedBus.line).background} + fill={selectedBus.colour} stroke="#000" strokeWidth="2" strokeLinejoin="round" diff --git a/src/frontend/app/data/LineColors.ts b/src/frontend/app/data/LineColors.ts deleted file mode 100644 index d24d870..0000000 --- a/src/frontend/app/data/LineColors.ts +++ /dev/null @@ -1,64 +0,0 @@ -interface LineColorInfo { - background: string; - text: string; -} - -const vigoLineColors: Record<string, LineColorInfo> = { - c1: { background: "rgb(237, 71, 19)", text: "#ffffff" }, - c3d: { background: "rgb(255, 204, 0)", text: "#000000" }, - c3i: { background: "rgb(255, 204, 0)", text: "#000000" }, - l4a: { background: "rgb(0, 153, 0)", text: "#ffffff" }, - l4c: { background: "rgb(0, 153, 0)", text: "#ffffff" }, - l5a: { background: "rgb(0, 176, 240)", text: "#000000" }, - l5b: { background: "rgb(0, 176, 240)", text: "#000000" }, - l6: { background: "rgb(204, 51, 153)", text: "#ffffff" }, - l7: { background: "rgb(150, 220, 153)", text: "#000000" }, - l9b: { background: "rgb(244, 202, 140)", text: "#000000" }, - l10: { background: "rgb(153, 51, 0)", text: "#ffffff" }, - l11: { background: "rgb(226, 0, 38)", text: "#ffffff" }, - l12a: { background: "rgb(106, 150, 190)", text: "#000000" }, - l12b: { background: "rgb(106, 150, 190)", text: "#000000" }, - l13: { background: "rgb(0, 176, 240)", text: "#000000" }, - l14: { background: "rgb(129, 142, 126)", text: "#ffffff" }, - l15a: { background: "rgb(216, 168, 206)", text: "#000000" }, - l15b: { background: "rgb(216, 168, 206)", text: "#000000" }, - l15c: { background: "rgb(216, 168, 168)", text: "#000000" }, - l16: { background: "rgb(129, 142, 126)", text: "#ffffff" }, - l17: { background: "rgb(214, 245, 31)", text: "#000000" }, - l18a: { background: "rgb(212, 80, 168)", text: "#ffffff" }, - l18b: { background: "rgb(212, 80, 168)", text: "#ffffff" }, - l18h: { background: "rgb(212, 80, 168)", text: "#ffffff" }, - l23: { background: "rgb(0, 70, 210)", text: "#ffffff" }, - l24: { background: "rgb(191, 191, 191)", text: "#000000" }, - l25: { background: "rgb(172, 100, 4)", text: "#ffffff" }, - l27: { background: "rgb(112, 74, 42)", text: "#ffffff" }, - l28: { background: "rgb(176, 189, 254)", text: "#000000" }, - l29: { background: "rgb(248, 184, 90)", text: "#000000" }, - l31: { background: "rgb(255, 255, 0)", text: "#000000" }, - a: { background: "rgb(119, 41, 143)", text: "#ffffff" }, - h: { background: "rgb(0, 96, 168)", text: "#ffffff" }, - h1: { background: "rgb(0, 96, 168)", text: "#ffffff" }, - h2: { background: "rgb(0, 96, 168)", text: "#ffffff" }, - h3: { background: "rgb(0, 96, 168)", text: "#ffffff" }, - lzd: { background: "rgb(61, 78, 167)", text: "#ffffff" }, - n1: { background: "rgb(191, 191, 191)", text: "#000000" }, - n4: { background: "rgb(102, 51, 102)", text: "#ffffff" }, - psa1: { background: "rgb(0, 153, 0)", text: "#ffffff" }, - psa4: { background: "rgb(0, 153, 0)", text: "#ffffff" }, - ptl: { background: "rgb(150, 220, 153)", text: "#000000" }, - turistico: { background: "rgb(102, 51, 102)", text: "#ffffff" }, - u1: { background: "rgb(172, 100, 4)", text: "#ffffff" }, - u2: { background: "rgb(172, 100, 4)", text: "#ffffff" }, -}; - -const defaultLineColor: LineColorInfo = { - background: "#d32f2f", - text: "#ffffff", -}; - -export function getLineColour(line: string): LineColorInfo { - let formattedLine = /^[a-zA-Z]/.test(line) ? line : `L${line}`; - formattedLine = formattedLine.toLowerCase().trim(); - - return vigoLineColors[formattedLine.toLowerCase().trim()] ?? defaultLineColor; -} diff --git a/src/frontend/app/data/LinesData.ts b/src/frontend/app/data/LinesData.ts deleted file mode 100644 index cd661b3..0000000 --- a/src/frontend/app/data/LinesData.ts +++ /dev/null @@ -1,256 +0,0 @@ -export interface LineInfo { - lineNumber: string; - routeName: string; - scheduleUrl: string; -} - -/** - * Sourced from https://vitrasa.es/lineas-y-horarios/todas-las-lineas - * - Array.from(document.querySelectorAll(".line-information")).map(el => { - return { - lineNumber: el.querySelector(".square-info").innerText, - routeName: el.querySelector(".all-lines-descripcion-prh").innerText, - scheduleUrl: `https://vitrasa.es/documents/5893389/6130928/${el.querySelector("input[type=checkbox]").value}.pdf` - } - }); - - */ - -export const VIGO_LINES: LineInfo[] = [ - { - lineNumber: "C1", - routeName: "P.América - C. Castillo - P.Sanz - G.Via - P.América", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1.pdf", - }, - { - lineNumber: "C3d", - routeName: - "Bouzas/Coia - E.Fadrique - Encarnación (dereita) - Pza España - Bouzas/Coia", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/3001.pdf", - }, - { - lineNumber: "C3i", - routeName: - "Bouzas/Coia - Pza España - Encarnación (esquerda) - E.Fadrique - Bouzas/Coia", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/3002.pdf", - }, - { - lineNumber: "4A", - routeName: "Coia - Camelias - Centro - Aragón", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/4001.pdf", - }, - { - lineNumber: "4C", - routeName: "Coia - Camelias - Centro - M.Garrido - Gregorio Espino", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/4003.pdf", - }, - { - lineNumber: "5A", - routeName: "Navia - Florida - L.Mora - Urzaiz - T.Vigo - Teis", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/5001.pdf", - }, - { - lineNumber: "5B", - routeName: "Navia - Coia - L.Mora - Pi Margall - G.Barbón - Teis", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/5004.pdf", - }, - { - lineNumber: "6", - routeName: "H.Cunqueiro - Beade - Bembrive - Pza. España", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/6.pdf", - }, - { - lineNumber: "7", - routeName: "Zamans/Valladares - Fragoso - P.América - P.España - Centro", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/7.pdf", - }, - { - lineNumber: "9B", - routeName: "Centro - Choróns - San Cristovo - Rabadeira", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/9002.pdf", - }, - { - lineNumber: "10", - routeName: - "Teis - G.Barbón - Torrecedeira - Av. Atlántida - Samil - Vao - Saiáns", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/10.pdf", - }, - { - lineNumber: "11", - routeName: - "San Miguel - Vao - P. América - Urzaiz - Ramón Nieto - Grileira", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/11.pdf", - }, - { - lineNumber: "12A", - routeName: - "Saiáns - Muiños - Castelao - Pi Margall - P.España - H.Meixoeiro", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1201.pdf", - }, - { - lineNumber: "12B", - routeName: "H.Cunqueiro - Castrelos - Camelias - P.España - H.Meixoeiro", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1202.pdf", - }, - { - lineNumber: "13", - routeName: "Navia - Bouzas - Gran Vía - P.España - H.Meixoeiro", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/13.pdf", - }, - { - lineNumber: "14", - routeName: "Gran Vía - Miraflores - Moledo - Chans", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/14.pdf", - }, - { - lineNumber: "15A", - routeName: "Av. Ponte - Choróns - Gran Vía - Castelao - Navia - Samil", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1501.pdf", - }, - { - lineNumber: "15B", - routeName: "Xestoso - Choróns - P.Sanz - Beiramar - Bouzas - Samil", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1506.pdf", - }, - { - lineNumber: "15C", - routeName: "CUVI - Choróns - P.Sanz - Torrecedeira - Bouzas - Samil", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1507.pdf", - }, - { - lineNumber: "16", - routeName: "Coia - Balaídos - Zamora - P.España - Colón - Guixar", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/16.pdf", - }, - { - lineNumber: "17", - routeName: "Matamá/Freixo - Fragoso - Camelias - G.Barbón - Ríos/A Guía", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/17.pdf", - }, - { - lineNumber: "18A", - routeName: "AREAL/COLÓN - SÁRDOMA/POULEIRA", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/18.pdf", - }, - { - lineNumber: "18B", - routeName: "URZAIZ / P.ESPAÑA - POULEIRA", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1801.pdf", - }, - { - lineNumber: "18H", - routeName: "URZAIZ / P. ESPAÑA - H. ALV. CUNQUEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/1802.pdf", - }, - { - lineNumber: "23", - routeName: "M. ECHEGARAY - Balaídos - Gran Vía - Choróns - Gregorio Espino", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/23.pdf", - }, - { - lineNumber: "24", - routeName: "Poulo - Vía Norte - Colón - Guixar", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/24.pdf", - }, - { - lineNumber: "25", - routeName: "PZA. ESPAÑA – SABAXÁNS / CAEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/25.pdf", - }, - { - lineNumber: "27", - routeName: "BEADE (C. CULTURAL) – RABADEIRA", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/27.pdf", - }, - { - lineNumber: "28", - routeName: "VIGOZOO - SAN PAIO - BOUZAS", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/28.pdf", - }, - { - lineNumber: "29", - routeName: "FRAGOSELO / S. ANDRÉS – PZA. ESPAÑA", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/29.pdf", - }, - { - lineNumber: "31", - routeName: "SAN LOURENZO – HOSP. MEIXOEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/31.pdf", - }, - { - lineNumber: "A", - routeName: "ARENAL – PORTO / UNIVERSIDADE", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/8.pdf", - }, - { - lineNumber: "H", - routeName: "NAVIA - BOUZAS - HOSPITAL ALVARO CUNQUEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/104.pdf", - }, - { - lineNumber: "H1", - routeName: "POLICARPO SANZ – HOSPITAL ÁLVARO CUNQUEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/101.pdf", - }, - { - lineNumber: "H2", - routeName: "GREGORIO ESPINO – HOSPITAL ÁLVARO CUNQU", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/102.pdf", - }, - { - lineNumber: "H3", - routeName: "GARCÍA BARBÓN – HOSPITAL ÁLVARO CUNQUEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/105.pdf", - }, - { - lineNumber: "LZD", - routeName: "STELLANTIS - ALV. CUNQUEIRO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/751.pdf", - }, - { - lineNumber: "N1", - routeName: "SAMIL – BUENOS AIRES", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/30.pdf", - }, - { - lineNumber: "N4", - routeName: "NAVIA - G. ESPINO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/3305.pdf", - }, - { - lineNumber: "PSA1", - routeName: "STELLANTIS - G.BARBON", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/301.pdf", - }, - { - lineNumber: "PSA4", - routeName: "STELLANTIS - G. BARBON", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/4004.pdf", - }, - { - lineNumber: "PTL", - routeName: "PARQUE TECNOLÓXICO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/304.pdf", - }, - { - lineNumber: "TUR", - routeName: "TURISTICO", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/500.pdf", - }, - { - lineNumber: "U1", - routeName: "LANZADEIRA PZA. AMÉRICA – UNIVERSIDADE", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/201.pdf", - }, - { - lineNumber: "U2", - routeName: "LANZADEIRA PZA. DE ESPAÑA – UNIVERSIDADE", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/202.pdf", - }, - { - lineNumber: "VTS", - routeName: "CABRAL - BASE", - scheduleUrl: "https://vitrasa.es/documents/5893389/6130928/3010.pdf", - }, -]; diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index d4301cc..bff8c7f 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -11,6 +11,7 @@ import { PullToRefresh } from "~/components/PullToRefresh"; import { StopHelpModal } from "~/components/stop/StopHelpModal"; import { StopMapModal } from "~/components/stop/StopMapModal"; import { usePageTitle } from "~/contexts/PageTitleContext"; +import { formatHex } from "~/utils/colours"; import StopDataProvider from "../data/StopDataProvider"; import "./stops-$id.css"; @@ -231,13 +232,10 @@ export default function Estimates() { }} circulations={(data ?? []).map((a) => ({ id: getArrivalId(a), - line: a.route.shortName, - route: a.headsign.destination, currentPosition: a.currentPosition ?? undefined, stopShapeIndex: a.stopShapeIndex ?? undefined, - schedule: { - shapeId: undefined, - }, + colour: formatHex(a.route.colour), + textColour: formatHex(a.route.textColour), shape: a.shape, }))} isOpen={isMapModalOpen} diff --git a/src/frontend/app/utils/colours.ts b/src/frontend/app/utils/colours.ts new file mode 100644 index 0000000..aa939f7 --- /dev/null +++ b/src/frontend/app/utils/colours.ts @@ -0,0 +1,26 @@ +// TODO: Standardise this shit server-side +export function formatHex(hex: string, poundSign = true): string { + if (hex.length === 6) { + return (poundSign ? "#" : "") + hex; + } else if (hex.length === 3) { + return ( + (poundSign ? "#" : "") + + hex + .split("") + .map((c) => c + c) + .join("") + ); + } else if (hex.length === 7 && hex.startsWith("#")) { + return poundSign ? hex : hex.substring(1); + } else if (hex.length === 4 && hex.startsWith("#")) { + return poundSign + ? hex + : hex + .substring(1) + .split("") + .map((c) => c + c) + .join(""); + } else { + throw new Error("Invalid hex color format"); + } +} |
