map legend

This commit is contained in:
Owen Rees
2023-06-06 10:25:14 +02:00
parent b28510b63d
commit 5396df5d2b
6 changed files with 108 additions and 74 deletions
-2
View File
@@ -1,3 +1 @@
marker legend marker legend
Warning: validateDOMNesting(...): <a> cannot appear as a child of <tr>.
@@ -1,8 +1,10 @@
import clientPromise from "@/lib/mongodb"; import clientPromise from "@/lib/mongodb";
// TODO collate only the country of France - redundant for now but will be needed when new countries are added
// probably do this by passing the country name as parameter
/** /**
* Tournament data API endpoint * Tournament data API endpoint
* @route /api/tournaments * @route /api/tournaments/france
* @internal * @internal
*/ */
export async function GET() { export async function GET() {
+6 -14
View File
@@ -1,8 +1,11 @@
import { Tournament } from "@/types";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import TournamentTable from "@/components/TournamentTable"; import TournamentTable from "@/components/TournamentTable";
import { Tournament } from "@/types"; import getTournaments from "@/utils/getTournamentData";
// TODO can these functions be put into a custom hook?
/** /**
* Imports the tournament map component, ensuring CSR only. * Imports the tournament map component, ensuring CSR only.
* @remarks SSR is not supported by react-leaflet * @remarks SSR is not supported by react-leaflet
@@ -16,23 +19,12 @@ const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
), ),
}); });
/**
* Retrieves tournament data from /api/tournaments
* @remarks The result is cached for the revalidation period in seconds
*/
async function getTournaments() {
const res = await fetch("http://localhost:3000/api/tournaments", {
next: { revalidate: 300 },
});
return await res.json();
}
export default async function Tournaments() { export default async function Tournaments() {
const tournamentData: Tournament[] = await getTournaments(); const tournamentData: Tournament[] = await getTournaments("france");
return ( return (
<Layout> <Layout>
<main className="grid lg:grid-cols-2"> <main className="relative grid lg:grid-cols-2">
<div className="relative h-screen"> <div className="relative h-screen">
<TournamentMap tournamentData={tournamentData} /> <TournamentMap tournamentData={tournamentData} />
</div> </div>
+37 -56
View File
@@ -1,75 +1,55 @@
"use client"; "use client";
// Types
import { TournamentDataProps } from "@/types";
import { LatLngLiteral } from "leaflet";
// Leaflet + icon fixes
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css"; import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
import L from "leaflet"; import L from "leaflet";
import "leaflet-defaulticon-compatibility"; import "leaflet-defaulticon-compatibility";
import { MapContainer, TileLayer, LayersControl, useMap } from "react-leaflet";
import { import { createLayerGroups } from "@/utils/layerGroups";
MapContainer, import { useEffect } from "react";
TileLayer,
Marker,
Popup,
LayersControl,
LayerGroup,
} from "react-leaflet";
import { LatLngLiteral } from "leaflet";
import { TournamentDataProps } from "@/types";
export default function TournamentMap({ tournamentData }: TournamentDataProps) { export default function TournamentMap({ tournamentData }: TournamentDataProps) {
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 }; const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
// TODO consider putting in page.tsx so that it is SSR const classicalMarkers = createLayerGroups("Cadence Lente", "green", {
// TODO move to own hook/util tournamentData,
// TODO wrap in useEffect on initial load []
function layerGroups(timeControl: string, colour: string) {
const filteredTournaments = tournamentData.filter(
(t) => t.time_control === timeControl
);
const iconOptions = new L.Icon({
iconUrl: `images/leaflet/marker-icon-2x-${colour}.png`,
shadowUrl: "images/leaflet/marker-shadow.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41],
}); });
const rapidMarkers = createLayerGroups("Rapide", "blue", { tournamentData });
const blitzMarkers = createLayerGroups("Blitz", "yellow", { tournamentData });
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
return ( // TODO move into its own hook
<LayersControl.Overlay checked name={timeControl}> function Legend() {
<LayerGroup> const map = useMap();
{filteredTournaments.map((t) => {
const coordinates = { useEffect(() => {
lat: t.coordinates[0] + Math.random() * (-0.01 - 0.01) + 0.01, if (map) {
lng: t.coordinates[1] + Math.random() * (-0.01 - 0.01) + 0.01, const legend = L.control({ position: "bottomleft" });
legend.onAdd = () => {
const div = L.DomUtil.create("div", "map-legend");
div.style =
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;";
div.innerHTML = `<ul>
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Cadence Lente</li>
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Rapide</li>
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Blitz</li>
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>1h KO</li>
</ul>`;
return div;
}; };
return ( legend.addTo(map);
<Marker position={coordinates} key={t._id} icon={iconOptions}> }
<Popup> }, [map]);
<p> return null;
{t.date}
<br />
<a href={t.url} target="_blank" rel="noopener noreferrer">
{t.tournament}
</a>
</p>
géolocalisation approximative
</Popup>
</Marker>
);
})}
</LayerGroup>
</LayersControl.Overlay>
);
} }
const classicalMarkers = layerGroups("Cadence Lente", "green");
const rapidMarkers = layerGroups("Rapide", "blue");
const blitzMarkers = layerGroups("Blitz", "yellow");
const otherMarkers = layerGroups("1h KO", "red");
return ( return (
<section id="tournament-map" className="w-full lg:col-start-1 lg:col-end-2"> <section id="tournament-map" className="w-full lg:col-start-1 lg:col-end-2">
@@ -89,6 +69,7 @@ export default function TournamentMap({ tournamentData }: TournamentDataProps) {
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/> />
<Legend />
<LayersControl> <LayersControl>
{classicalMarkers} {classicalMarkers}
{rapidMarkers} {rapidMarkers}
+10
View File
@@ -0,0 +1,10 @@
/**
* Retrieves tournament data from /api/tournaments/:country
* @remarks The result is cached for the revalidation period in seconds
*/
export default async function getTournaments(country: string) {
const res = await fetch(`http://localhost:3000/api/tournaments/${country}`, {
next: { revalidate: 300 },
});
return await res.json();
}
+51
View File
@@ -0,0 +1,51 @@
import { TournamentDataProps } from "@/types";
import L from "leaflet";
import { LayerGroup, LayersControl, Marker, Popup } from "react-leaflet";
export const createLayerGroups = (
timeControl: string,
colour: string,
{ tournamentData }: TournamentDataProps
) => {
const filteredTournaments = tournamentData.filter(
(t) => t.time_control === timeControl
);
const iconOptions = new L.Icon({
iconUrl: `images/leaflet/marker-icon-2x-${colour}.png`,
shadowUrl: "images/leaflet/marker-shadow.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41],
});
return (
<LayersControl.Overlay checked name={timeControl}>
<LayerGroup>
{filteredTournaments.map((t) => {
const coordinates = {
lat: t.coordinates[0] + Math.random() * (-0.01 - 0.01) + 0.01,
lng: t.coordinates[1] + Math.random() * (-0.01 - 0.01) + 0.01,
};
return (
<Marker position={coordinates} key={t._id} icon={iconOptions}>
<Popup>
<p>
{t.date}
<br />
<a href={t.url} target="_blank" rel="noopener noreferrer">
{t.tournament}
</a>
</p>
géolocalisation approximative
</Popup>
</Marker>
);
})}
</LayerGroup>
</LayersControl.Overlay>
);
};