Scenario

The Challenge

Event planners managing multiple outdoor venues (weddings, concerts, festivals, corporate events) need to make critical decisions about event scheduling, venue selection, and backup plans based on weather conditions. Traditional weather apps don't provide the location-specific precision needed for multi-venue event management.

The Solution

This implementation demonstrates how to build an interactive dashboard that displays weather data for multiple event venues on an interactive map, provides venue-specific weather suitability scores, and helps planners make informed decisions about event logistics and contingency planning.

Implementation Benefits

Risk Mitigation

Weather-based alerts prevent costly event cancellations

Significant reduction in weather-related event issues

Venue Optimization

Data-driven venue selection based on weather patterns

Improved guest satisfaction and event success rates

Equipment Planning

Precise weather data for equipment and logistics decisions

Optimized setup costs and resource allocation

Guest Experience

Proactive communication about weather conditions

Enhanced attendee preparation and comfort

API Request

API Request

Request Parameters

ParameterValueType
location"21.3099,-157.8581"string
apikey"YOUR_API_KEY"string

Request URL

https://api.tomorrow.io/v4/weather/forecast?location=21.3099%2C-157.8581&apikey=YOUR_API_KEY

cURL

curl -X GET "https://api.tomorrow.io/v4/weather/forecast?location=21.3099%2C-157.8581&apikey=YOUR_API_KEY"

JavaScript

const response = await fetch('https://api.tomorrow.io/v4/weather/forecast?location=21.3099%2C-157.8581&apikey=YOUR_API_KEY', {
  method: 'GET',
});

const data = await response.json();

API Response

🎪 Event Planning Significance of Key Fields

temperature: Guest comfort and equipment needs (°C)
precipitationProbability: Tent/cover requirements (%)
windSpeed: Tent stability and stage safety (m/s)
visibility: Photography and videography quality (km)
uvIndex: Sun protection planning for guests
intervals: Hourly data for event timeline planning

API Response

Status: 2007/15/2025, 12:40:44 PM

Key Response Fields

temperature
timelines.0.intervals.0.values.temperature
16
precipitationProbability
timelines.0.intervals.0.values.precipitationProbability
0
windSpeed
timelines.0.intervals.0.values.windSpeed
4.1
visibility
timelines.0.intervals.0.values.visibility
13.31
uvIndex
timelines.0.intervals.0.values.uvIndex
0
lat
location.lat
21.3099
lon
location.lon
-157.8581

Complete Response

{
  "timelines": [
    {
      "timestep": "1h",
      "intervals": [
        {
          "time": "2025-01-06T14:00:00Z",
          "values": {
            "temperature": 16,
            "humidity": 86,
            "precipitationProbability": 0,
            "rainIntensity": 0,
            "windSpeed": 4.1,
            "windDirection": 83,
            "windGust": 6.8,
            "weatherCode": 1001,
            "visibility": 13.31,
            "uvIndex": 0,
            "cloudCover": 100
          }
        },
        {
          "time": "2025-01-06T15:00:00Z",
          "values": {
            "temperature": 17,
            "humidity": 69,
            "precipitationProbability": 0,
            "rainIntensity": 0,
            "windSpeed": 3.5,
            "windDirection": 68,
            "windGust": 6.9,
            "weatherCode": 1001,
            "visibility": 13.14,
            "uvIndex": 1,
            "cloudCover": 100
          }
        },
        {
          "time": "2025-01-06T16:00:00Z",
          "values": {
            "temperature": 19,
            "humidity": 68,
            "precipitationProbability": 0,
            "rainIntensity": 0,
            "windSpeed": 3.4,
            "windDirection": 76,
            "windGust": 7,
            "weatherCode": 1001,
            "visibility": 12.97,
            "uvIndex": 2,
            "cloudCover": 100
          }
        },
        {
          "time": "2025-01-06T17:00:00Z",
          "values": {
            "temperature": 20,
            "humidity": 65,
            "precipitationProbability": 15,
            "rainIntensity": 0.2,
            "windSpeed": 4.2,
            "windDirection": 78,
            "windGust": 8.1,
            "weatherCode": 4000,
            "visibility": 11.5,
            "uvIndex": 1,
            "cloudCover": 95
          }
        },
        {
          "time": "2025-01-06T18:00:00Z",
          "values": {
            "temperature": 21,
            "humidity": 62,
            "precipitationProbability": 75,
            "rainIntensity": 1.8,
            "windSpeed": 5.1,
            "windDirection": 82,
            "windGust": 9.2,
            "weatherCode": 4001,
            "visibility": 8.3,
            "uvIndex": 0,
            "cloudCover": 98
          }
        }
      ]
    }
  ],
  "location": {
    "lat": 21.3099,
    "lon": -157.8581
  }
}

Response Structure

Data Type: object
Top-level Keys: timelines, location
Response Size: ~2KB

Live Implementation

This interactive dashboard demonstrates how to combine Tomorrow.io weather data with Leaflet.js mapping for multi-venue event planning and risk assessment.

Event Planning Workflow

1Venue Selection: Define event venues with coordinates and capacity
2Weather Fetching: Retrieve forecasts for each venue location
3Risk Calculation: Score venues based on weather suitability
4Map Visualization: Display venues with color-coded risk levels
5Decision Support: Provide actionable recommendations

Real Weather Data from Hawaii's Microclimates: Each venue shows actual weather conditions from different locations across Hawaii, demonstrating how dramatic climate variations can occur within a small geographic area. Click markers to compare real-world suitability scores across diverse Hawaiian microclimates.

Interactive OpenStreetMap: Real street map with weather-powered venue markers
🗺️ Zoom, pan, and click markers for detailed weather information • No API keys required!
Real weather data from June 9, 2025 (captured from Tomorrow.io API across Hawaiian microclimates)

Event Suitability Legend

Excellent (80-100): Ideal conditions for outdoor events
Good (60-79): Suitable with minor precautions
Challenging (0-59): Consider backup plans or rescheduling

🗺️ Leaflet.js Implementation

This implementation uses Leaflet.js with OpenStreetMap for zero-cost mapping. Features include:

  • • Interactive Leaflet map with custom weather-based venue markers
  • • OpenStreetMap tiles (completely free, no API keys required)
  • • Real-time weather data integration from Tomorrow.io API
  • • Custom popup windows with detailed weather information
  • • Microclimate simulation for realistic venue variations
  • • Color-coded risk assessment and suitability scoring

Implementation Code

// Initialize Leaflet map with OpenStreetMap tiles
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

const map = L.map('map-container', {
  center: [20.5, -156.5], // Hawaii center coordinates [lat, lng]
  zoom: 8,
  zoomControl: true,
  scrollWheelZoom: true
});

// Add OpenStreetMap tile layer (no API key required!)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors',
  maxZoom: 19
}).addTo(map);

// Event venues across Hawaii's diverse microclimates
const venues = [
  { 
    id: 'honolulu-beach', 
    name: 'Honolulu Beach Resort', 
    lat: 21.3099, 
    lon: -157.8581,
    eventType: 'Wedding Reception',
    capacity: 200
  },
  { 
    id: 'hilo-gardens', 
    name: 'Hilo Tropical Gardens', 
    lat: 19.7297, 
    lon: -155.0900,
    eventType: 'Corporate Retreat',
    capacity: 300
  },
  { 
    id: 'kona-coast', 
    name: 'Kona Coast Venue', 
    lat: 19.8968, 
    lon: -155.5828,
    eventType: 'Music Festival',
    capacity: 1000
  },
  { 
    id: 'mauna-kea', 
    name: 'Mauna Kea Observatory', 
    lat: 19.8207, 
    lon: -155.4680,
    eventType: 'Stargazing Event',
    capacity: 150
  }
];

// Fetch weather data for all venues
async function updateVenueWeather(apiKey) {
  console.log('Fetching weather data for', venues.length, 'venues...');
  
  const weatherPromises = venues.map(async (venue) => {
    try {
      const url = `https://api.tomorrow.io/v4/weather/forecast?location=${venue.lat},${venue.lon}&apikey=${apiKey}&timesteps=1h&fields=temperature,humidity,precipitationProbability,windSpeed,visibility`;
      
      console.log(`Fetching weather for ${venue.name} at ${venue.lat},${venue.lon}`);
      
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }
      
      const data = await response.json();
      const currentWeather = data.timelines[0].intervals[0].values;
      
      return { 
        ...venue, 
        weather: currentWeather,
        score: calculateEventScore(currentWeather)
      };
      
    } catch (error) {
      console.error(`Failed to fetch weather for ${venue.name}:`, error);
      return { 
        ...venue, 
        weather: null, 
        score: 0 
      };
    }
  });
  
  const venuesWithWeather = await Promise.all(weatherPromises);
  return venuesWithWeather;
}

// Calculate event suitability score based on weather conditions
function calculateEventScore(weather) {
  if (!weather) return 0;
  
  let score = 100;
  
  // Temperature comfort (optimal: 18-24°C)
  if (weather.temperature < 10 || weather.temperature > 30) score -= 30;
  else if (weather.temperature < 15 || weather.temperature > 27) score -= 15;
  
  // Rain probability impact
  if (weather.precipitationProbability > 70) score -= 40;
  else if (weather.precipitationProbability > 30) score -= 20;
  
  // Wind conditions (high winds problematic for tents, stages)
  if (weather.windSpeed > 10) score -= 25;
  else if (weather.windSpeed > 6) score -= 10;
  
  // Visibility for photography/videography
  if (weather.visibility < 5) score -= 15;
  else if (weather.visibility < 10) score -= 5;
  
  return Math.max(0, Math.round(score));
}

// Add weather-based markers to map
async function displayVenuesOnMap(apiKey) {
  const venuesWithWeather = await updateVenueWeather(apiKey);
  
  venuesWithWeather.forEach(venue => {
    const score = venue.score;
    const color = score >= 80 ? '#10b981' : score >= 60 ? '#f59e0b' : '#ef4444';
    
    // Create custom marker element
    const markerElement = document.createElement('div');
    markerElement.style.cssText = `
      width: 30px; height: 30px;
      background: ${color};
      border: 3px solid white;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: bold;
      color: white;
      font-size: 10px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.4);
    `;
    markerElement.textContent = score.toString();
    
    // Create custom Leaflet icon
    const customIcon = L.divIcon({
      html: markerElement.outerHTML,
      className: 'custom-marker',
      iconSize: [30, 30],
      iconAnchor: [15, 15]
    });
    
    // Create detailed popup content
    const popupContent = `
      <div style="padding: 12px; font-family: sans-serif; max-width: 250px;">
        <h3 style="margin: 0 0 8px 0; color: #1f2937;">${venue.name}</h3>
        <div style="margin-bottom: 8px; padding: 6px; background: ${score >= 80 ? '#ecfdf5' : score >= 60 ? '#fefce8' : '#fef2f2'}; border-radius: 4px;">
          <strong>Suitability Score: ${score}/100</strong>
        </div>
        ${venue.weather ? `
          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 12px;">
            <div><strong>🌡️ Temperature:</strong><br/>${venue.weather.temperature}°C</div>
            <div><strong>🌧️ Rain Chance:</strong><br/>${venue.weather.precipitationProbability}%</div>
            <div><strong>💨 Wind Speed:</strong><br/>${venue.weather.windSpeed} m/s</div>
            <div><strong>👁️ Visibility:</strong><br/>${venue.weather.visibility} km</div>
          </div>
        ` : '<div style="color: red;">Weather data unavailable</div>'}
        <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #e5e7eb; font-size: 11px; color: #6b7280;">
          <strong>Event Type:</strong> ${venue.eventType}<br/>
          <strong>Capacity:</strong> ${venue.capacity} guests
        </div>
      </div>
    `;
    
    // Add marker to map
    L.marker([venue.lat, venue.lon], { icon: customIcon })
      .bindPopup(popupContent)
      .addTo(map);
  });
  
  console.log('All venue markers added to map');
  return venuesWithWeather;
}

// Initialize the venue weather dashboard
displayVenuesOnMap('YOUR_API_KEY').then(venues => {
  console.log('Venue weather dashboard initialized with', venues.length, 'locations');
});

Leaflet.js Integration Steps

// 1. Install Leaflet.js (no API keys required!)
npm install leaflet @types/leaflet

// 2. Import Leaflet and CSS
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

// 3. Initialize map with OpenStreetMap
const map = L.map('map-container', {
  center: [40.7589, -73.9851], // NYC coordinates [lat, lng]
  zoom: 12,
  zoomControl: true,
  scrollWheelZoom: true
});

// 4. Add OpenStreetMap tile layer (completely free!)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors',
  maxZoom: 19
}).addTo(map);

// 5. Create custom markers with weather data
function addWeatherMarker(venue, weatherData) {
  const score = calculateEventScore(weatherData);
  const color = score >= 80 ? '#10b981' : score >= 60 ? '#f59e0b' : '#ef4444';
  
  // Custom marker element
  const markerElement = document.createElement('div');
  markerElement.style.cssText = `
    width: 30px; height: 30px;
    background: ${color};
    border: 3px solid white;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    color: white;
  `;
  markerElement.textContent = score.toString();
  
  // Create Leaflet marker
  const customIcon = L.divIcon({
    html: markerElement.outerHTML,
    className: 'custom-marker',
    iconSize: [30, 30],
    iconAnchor: [15, 15]
  });
  
  L.marker([venue.lat, venue.lon], { icon: customIcon })
    .bindPopup(`<div>
      <h3>${venue.name}</h3>
      <p>Score: ${score}/100</p>
      <p>🌡️ ${weatherData.temperature}°C</p>
    </div>`)
    .addTo(map);
}

// 6. Error handling for API requests
async function fetchVenueWeather(venues, apiKey) {
  try {
    const promises = venues.map(async (venue) => {
      const response = await fetch(
        `https://api.tomorrow.io/v4/weather/forecast?location=${venue.lat},${venue.lon}&apikey=${apiKey}`
      );
      
      if (!response.ok) {
        throw new Error(`Weather API error: ${response.status}`);
      }
      
      const data = await response.json();
      return { ...venue, weather: data.timelines[0].intervals[0].values };
    });
    
    return await Promise.all(promises);
  } catch (error) {
    console.error('Failed to fetch venue weather:', error);
    return venues; // Return venues without weather data
  }
}