diff --git a/.env b/.env
index b454ca74..0ad5ec11 100644
--- a/.env
+++ b/.env
@@ -1 +1,2 @@
DATABASE_URL="postgresql://user:password@host:port/db"
+WINDY_API_KEY="YOUR_WINDY_API_KEY"
diff --git a/.env.local.example b/.env.local.example
index 485fd803..209643e3 100644
--- a/.env.local.example
+++ b/.env.local.example
@@ -3,6 +3,9 @@
SMITHERY_PROFILE_ID="your_smithery_profile_id_here"
SMITHERY_API_KEY="your_smithery_api_key_here"
+# Windy.com Webcams API Key
+WINDY_API_KEY="your_windy_api_key_here"
+
# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN is already used by mapbox-map.tsx
# Ensure it's also in your .env.local file if you haven't set it up yet.
# NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN="your_mapbox_public_token_here"
diff --git a/components/header.tsx b/components/header.tsx
index 0a370612..354637d8 100644
--- a/components/header.tsx
+++ b/components/header.tsx
@@ -7,12 +7,12 @@ import { Button } from '@/components/ui/button'
import {
Search,
CircleUserRound,
- Map,
CalendarDays,
- TentTree
+ TentTree,
} from 'lucide-react'
import { MapToggle } from './map-toggle'
import { ProfileToggle } from './profile-toggle'
+import { SensorToggle } from './sensor-toggle'
export const Header = () => {
return (
@@ -43,9 +43,7 @@ export const Header = () => {
-
+
diff --git a/components/map-toggle-context.tsx b/components/map-toggle-context.tsx
index e3d3b552..c225d35e 100644
--- a/components/map-toggle-context.tsx
+++ b/components/map-toggle-context.tsx
@@ -6,6 +6,7 @@ export enum MapToggleEnum {
FreeMode,
RealTimeMode,
DrawingMode, // Added new mode for drawing and measurement
+ SensorMode,
}
interface MapToggleContextType {
diff --git a/components/map-toggle.tsx b/components/map-toggle.tsx
index 1ad35f59..fe441462 100644
--- a/components/map-toggle.tsx
+++ b/components/map-toggle.tsx
@@ -17,9 +17,8 @@ export function MapToggle() {
return (
-
@@ -30,7 +29,7 @@ export function MapToggle() {
My Maps
{setMapType(MapToggleEnum.DrawingMode)}}>
-
+
Draw & Measure
diff --git a/components/map/mapbox-map.tsx b/components/map/mapbox-map.tsx
index e9ace780..83592f59 100644
--- a/components/map/mapbox-map.tsx
+++ b/components/map/mapbox-map.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useEffect, useRef, useCallback } from 'react' // Removed useState
+import { useEffect, useRef, useCallback, useState } from 'react'
import mapboxgl from 'mapbox-gl'
import MapboxDraw from '@mapbox/mapbox-gl-draw'
import * as turf from '@turf/turf'
@@ -11,6 +11,7 @@ import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
import { useMapToggle, MapToggleEnum } from '../map-toggle-context'
import { useMapData } from './map-data-context'; // Add this import
import { useMapLoading } from '../map-loading-context'; // Import useMapLoading
+import { getSensors, Sensor } from '@/lib/sensors';
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string;
@@ -32,6 +33,9 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
const { mapData, setMapData } = useMapData(); // Consume the new context, get setMapData
const { setIsMapLoaded } = useMapLoading(); // Get setIsMapLoaded from context
const previousMapTypeRef = useRef(null)
+ const [sensors, setSensors] = useState([]);
+ const sensorMarkersRef = useRef<{ [id: string]: mapboxgl.Marker }>({});
+
// Refs for long-press functionality
const longPressTimerRef = useRef(null);
@@ -505,6 +509,58 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
}
}, [position, updateMapPosition, mapType])
+ useEffect(() => {
+ if (mapType === MapToggleEnum.SensorMode) {
+ const fetchSensors = async () => {
+ try {
+ const windyResponse = await getSensors();
+ setSensors(windyResponse.webcams);
+ } catch (error) {
+ console.error('Error fetching sensors:', error);
+ toast.error('Failed to fetch sensors.');
+ }
+ };
+ fetchSensors();
+ } else {
+ // Clear sensors and markers when not in SensorMode
+ setSensors([]);
+ Object.values(sensorMarkersRef.current).forEach(marker => marker.remove());
+ sensorMarkersRef.current = {};
+ }
+ }, [mapType]);
+
+ useEffect(() => {
+ if (map.current && mapType === MapToggleEnum.SensorMode) {
+ // Clear existing markers
+ Object.values(sensorMarkersRef.current).forEach(marker => marker.remove());
+ sensorMarkersRef.current = {};
+
+ sensors.forEach(sensor => {
+ const el = document.createElement('div');
+ el.className = 'sensor-marker';
+ el.style.width = '30px';
+ el.style.height = '30px';
+ el.style.borderRadius = '50%';
+ el.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
+ el.style.color = 'white';
+ el.style.display = 'flex';
+ el.style.justifyContent = 'center';
+ el.style.alignItems = 'center';
+ el.style.cursor = 'pointer';
+ el.innerHTML = '↗';
+
+ const marker = new mapboxgl.Marker(el)
+ .setLngLat([sensor.location.longitude, sensor.location.latitude])
+ .setPopup(new mapboxgl.Popup({ offset: 25 }).setHTML(
+ `${sensor.title}
`
+ ))
+ .addTo(map.current!);
+
+ sensorMarkersRef.current[sensor.id] = marker;
+ });
+ }
+ }, [sensors, mapType]);
+
// Effect to handle map updates from MapDataContext
useEffect(() => {
if (mapData.targetPosition && map.current) {
diff --git a/components/mobile-icons-bar.tsx b/components/mobile-icons-bar.tsx
index a5a2af9a..97a25f4c 100644
--- a/components/mobile-icons-bar.tsx
+++ b/components/mobile-icons-bar.tsx
@@ -9,14 +9,14 @@ import {
CircleUserRound,
Map,
CalendarDays,
- TentTree,
Paperclip,
ArrowRight,
Plus
} from 'lucide-react'
import { History } from '@/components/history'
-import { MapToggle } from './map-toggle'
import { ModeToggle } from './mode-toggle'
+import { MapToggle } from './map-toggle'
+import { SensorToggle } from './sensor-toggle'
export const MobileIconsBar: React.FC = () => {
const [, setMessages] = useUIState()
@@ -42,9 +42,7 @@ export const MobileIconsBar: React.FC = () => {
-
-
-
+
diff --git a/components/sensor-toggle.tsx b/components/sensor-toggle.tsx
new file mode 100644
index 00000000..15b97bcf
--- /dev/null
+++ b/components/sensor-toggle.tsx
@@ -0,0 +1,32 @@
+'use client'
+
+import * as React from 'react'
+import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger
+} from '@/components/ui/dropdown-menu'
+import { TentTree, RadioTower } from 'lucide-react'
+import { useMapToggle, MapToggleEnum } from './map-toggle-context'
+
+export function SensorToggle() {
+ const { setMapType } = useMapToggle();
+
+ return (
+
+
+
+
+
+
+
+ {setMapType(MapToggleEnum.SensorMode)}}>
+
+ Sensors
+
+
+
+ )
+}
diff --git a/lib/sensors.ts b/lib/sensors.ts
new file mode 100644
index 00000000..8b886cc9
--- /dev/null
+++ b/lib/sensors.ts
@@ -0,0 +1,56 @@
+export interface Sensor {
+ id: string;
+ title: string;
+ location: {
+ latitude: number;
+ longitude: number;
+ };
+ images: {
+ current: {
+ preview: string;
+ };
+ daylight: {
+ preview: string;
+ }
+ };
+ urls: {
+ webcam: string;
+ };
+}
+
+export interface WindyResponse {
+ webcams: Sensor[];
+}
+
+export async function getSensors(): Promise {
+ const apiKey = process.env.WINDY_API_KEY;
+ if (!apiKey || apiKey === 'YOUR_WINDY_API_KEY') {
+ console.error('WINDY_API_KEY is not defined or not set in the environment variables.');
+ throw new Error('WINDY_API_KEY is not defined or not set in the environment variables.');
+ }
+
+ try {
+ console.log('Fetching sensors with API key:', apiKey ? 'present' : 'missing');
+ const response = await fetch('https://api.windy.com/webcams/api/v3/webcams?include=location,images,urls', {
+ method: 'GET',
+ headers: {
+ 'x-windy-api-key': apiKey,
+ },
+ });
+
+ console.log('Windy API response status:', response.status);
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('Failed to fetch sensors:', response.statusText, errorText);
+ throw new Error(`Failed to fetch sensors: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ console.log('Successfully fetched sensor data:', data);
+ return data;
+ } catch (error) {
+ console.error('Error in getSensors:', error);
+ throw error;
+ }
+}