ws-dottie
Version:
Your friendly TypeScript companion for Washington State transportation APIs - WSDOT and WSF data with smart caching and React Query integration
542 lines (436 loc) • 18.1 kB
Markdown
# WSDOT Weather Stations API
This module provides access to the WSDOT Weather Stations API, which offers information about all weather stations maintained by WSDOT across Washington State.
## Overview
The Weather Stations API provides basic information about WSDOT weather stations including:
- Station location data (latitude and longitude coordinates)
- Station identification codes
- Station names with descriptive location information
- Complete list of all WSDOT-maintained weather stations
- Geographic distribution of weather monitoring infrastructure
## Features
| Feature | Description |
|---------|-------------|
| **Station Metadata** | Location, identification codes, and descriptive names |
| **Geographic Coverage** | Weather stations across Washington State highways |
| **Station Discovery** | Find available weather stations in specific areas |
| **Weather Data Integration** | Use station codes with Weather Information API |
| **Geographic Filtering** | Filter stations by location or region |
| **Station Mapping** | Display weather station locations on maps |
| **Route Planning** | Find weather stations along travel routes |
| **Static Data** | Relatively stable station information |
## API Endpoints
| Endpoint | Method | Description | Returns |
|----------|--------|-------------|---------|
| `GetCurrentStationsAsJson` | GET | Retrieve list of all weather stations | `WeatherStationData[]` |
### Get Weather Stations
Retrieves a list of all WSDOT weather stations with their location and identification information.
```typescript
import { getWeatherStations } from '@wsdot/api-client';
const weatherStations = await getWeatherStations();
```
**Returns:** `Promise<WeatherStationData[]>`
### Returns
See Data Types below. The stations endpoint returns an array of `WeatherStationData`.
## React Integration
For React Query hooks, TanStack Query setup, error handling, and caching strategies, see the [API Reference](../API-REFERENCE.md) documentation.
### React Hook Usage
```typescript
import { useWeatherStations } from 'ws-dottie';
function WeatherComponent() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
// Component implementation
}
```
## Data Types
### WeatherStationData
Represents weather station information from WSDOT.
```typescript
type WeatherStationData = {
Latitude: number; // Station latitude coordinate
Longitude: number; // Station longitude coordinate
StationCode: number; // Unique station identifier
StationName: string | null; // Human-readable station name with location
};
```
### WeatherStationsResponse
Array of weather station data records.
```typescript
type WeatherStationsResponse = WeatherStationData[];
```
## Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `options` | `object` | No | `{}` | Optional configuration object |
| `options.logMode` | `'none' \| 'basic' \| 'detailed'` | No | `'none'` | Logging level for API calls |
## React Hooks
| Hook | Description | Returns |
|------|-------------|---------|
| `useWeatherStations(options?)` | Fetches weather station metadata | `UseQueryResult<WeatherStationData[], WsdotApiError>` |
## Caching Strategy
| Strategy | Duration | Description |
|----------|----------|-------------|
| **Default Caching** | 24 hours | Long-term caching for static station data |
| **Stale Time** | 12 hours | Data considered fresh for 12 hours |
| **Background Refetch** | Disabled | No background refetch for static data |
| **Error Retry** | 3 attempts | Retries failed requests up to 3 times |
## Update Frequency
Station metadata is relatively static; daily to weekly refresh is sufficient.
## Common Patterns
For information about error handling, caching strategies, and other common patterns, see the [API Reference](../API-REFERENCE.md) documentation.
## Examples
### Weather Station Map
```typescript
import { useWeatherStations } from 'ws-dottie';
function WeatherStationMap() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
if (isLoading) return <div>Loading weather stations...</div>;
if (error) return <div>Error loading stations: {error.message}</div>;
return (
<div>
<h2>WSDOT Weather Stations Map</h2>
<div className="station-map">
{weatherStations?.map(station => (
<div
key={station.StationCode}
className="station-marker"
style={{
left: `${((station.Longitude + 125) / 9) * 100}%`,
top: `${((50 - station.Latitude) / 5) * 100}%`
}}
title={station.StationName || `Station ${station.StationCode}`}
>
<div className="marker-tooltip">
<strong>{station.StationName || `Station ${station.StationCode}`}</strong>
<br />
Code: {station.StationCode}
<br />
Location: {station.Latitude.toFixed(4)}, {station.Longitude.toFixed(4)}
</div>
</div>
))}
</div>
</div>
);
}
```
### Station Search and Filter
```typescript
import { useWeatherStations } from 'ws-dottie';
import { useState, useMemo } from 'react';
function WeatherStationSearch() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
const [searchTerm, setSearchTerm] = useState('');
const [region, setRegion] = useState('all');
const filteredStations = useMemo(() => {
if (!weatherStations) return [];
return weatherStations.filter(station => {
const stationName = station.StationName || `Station ${station.StationCode}`;
const matchesSearch = stationName.toLowerCase().includes(searchTerm.toLowerCase());
let matchesRegion = true;
if (region === 'seattle') {
matchesRegion = station.Latitude > 47.5 && station.Longitude > -122.5;
} else if (region === 'spokane') {
matchesRegion = station.Longitude < -117;
} else if (region === 'tacoma') {
matchesRegion = station.Latitude < 47.3 && station.Longitude > -122.5;
}
return matchesSearch && matchesRegion;
});
}, [weatherStations, searchTerm, region]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Weather Station Search</h2>
<div className="search-controls">
<input
type="text"
placeholder="Search station names..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<select value={region} onChange={(e) => setRegion(e.target.value)}>
<option value="all">All Regions</option>
<option value="seattle">Seattle Area</option>
<option value="spokane">Spokane Area</option>
<option value="tacoma">Tacoma Area</option>
</select>
</div>
<div className="results">
<p>Found {filteredStations.length} stations</p>
{filteredStations.map(station => (
<div key={station.StationCode} className="station-card">
<h3>{station.StationName || `Station ${station.StationCode}`}</h3>
<p><strong>Station Code:</strong> {station.StationCode}</p>
<p><strong>Coordinates:</strong> {station.Latitude.toFixed(4)}, {station.Longitude.toFixed(4)}</p>
<p><strong>Region:</strong> {
station.Latitude > 47.5 && station.Longitude > -122.5 ? 'Seattle Area' :
station.Longitude < -117 ? 'Spokane Area' :
station.Latitude < 47.3 && station.Longitude > -122.5 ? 'Tacoma Area' :
'Other'
}</p>
</div>
))}
</div>
</div>
);
}
```
### Station Statistics Dashboard
```typescript
import { useWeatherStations } from 'ws-dottie';
function WeatherStationStats() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const stats = {
totalStations: weatherStations?.length || 0,
seattleArea: weatherStations?.filter(s => s.Latitude > 47.5 && s.Longitude > -122.5).length || 0,
spokaneArea: weatherStations?.filter(s => s.Longitude < -117).length || 0,
tacomaArea: weatherStations?.filter(s => s.Latitude < 47.3 && s.Longitude > -122.5).length || 0,
otherAreas: weatherStations?.filter(s =>
!(s.Latitude > 47.5 && s.Longitude > -122.5) &&
!(s.Longitude < -117) &&
!(s.Latitude < 47.3 && s.Longitude > -122.5)
).length || 0
};
const avgLat = weatherStations?.reduce((sum, s) => sum + s.Latitude, 0) / stats.totalStations || 0;
const avgLon = weatherStations?.reduce((sum, s) => sum + s.Longitude, 0) / stats.totalStations || 0;
return (
<div>
<h2>Weather Station Statistics</h2>
<div className="stats-grid">
<div className="stat-card">
<h3>Total Stations</h3>
<p className="stat-number">{stats.totalStations}</p>
</div>
<div className="stat-card">
<h3>Seattle Area</h3>
<p className="stat-number">{stats.seattleArea}</p>
</div>
<div className="stat-card">
<h3>Spokane Area</h3>
<p className="stat-number">{stats.spokaneArea}</p>
</div>
<div className="stat-card">
<h3>Tacoma Area</h3>
<p className="stat-number">{stats.tacomaArea}</p>
</div>
<div className="stat-card">
<h3>Other Areas</h3>
<p className="stat-number">{stats.otherAreas}</p>
</div>
</div>
<div className="coordinate-stats">
<h3>Geographic Center</h3>
<p><strong>Average Latitude:</strong> {avgLat.toFixed(4)}</p>
<p><strong>Average Longitude:</strong> {avgLon.toFixed(4)}</p>
</div>
<div className="station-list">
<h3>All Stations</h3>
<div className="station-table">
<table>
<thead>
<tr>
<th>Station Code</th>
<th>Station Name</th>
<th>Latitude</th>
<th>Longitude</th>
<th>Region</th>
</tr>
</thead>
<tbody>
{weatherStations?.map(station => (
<tr key={station.StationCode}>
<td>{station.StationCode}</td>
<td>{station.StationName || `Station ${station.StationCode}`}</td>
<td>{station.Latitude.toFixed(4)}</td>
<td>{station.Longitude.toFixed(4)}</td>
<td>{
station.Latitude > 47.5 && station.Longitude > -122.5 ? 'Seattle' :
station.Longitude < -117 ? 'Spokane' :
station.Latitude < 47.3 && station.Longitude > -122.5 ? 'Tacoma' :
'Other'
}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
```
### Station Proximity Finder
```typescript
import { useWeatherStations } from 'ws-dottie';
import { useState, useMemo } from 'react';
function StationProximityFinder() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
const [userLat, setUserLat] = useState(47.6062);
const [userLon, setUserLon] = useState(-122.3321);
const nearbyStations = useMemo(() => {
if (!weatherStations) return [];
return weatherStations
.map(station => {
const distance = calculateDistance(
userLat, userLon,
station.Latitude, station.Longitude
);
return { ...station, distance };
})
.sort((a, b) => a.distance - b.distance)
.slice(0, 10);
}, [weatherStations, userLat, userLon]);
const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number) => {
const R = 3959; // Earth's radius in miles
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Nearby Weather Stations</h2>
<div className="location-input">
<label>
Your Latitude:
<input
type="number"
step="0.0001"
value={userLat}
onChange={(e) => setUserLat(parseFloat(e.target.value))}
/>
</label>
<label>
Your Longitude:
<input
type="number"
step="0.0001"
value={userLon}
onChange={(e) => setUserLon(parseFloat(e.target.value))}
/>
</label>
</div>
<div className="nearby-stations">
<h3>Closest Weather Stations</h3>
{nearbyStations.map((station, index) => (
<div key={station.StationCode} className="nearby-station">
<div className="station-rank">#{index + 1}</div>
<div className="station-info">
<h4>{station.StationName || `Station ${station.StationCode}`}</h4>
<p><strong>Distance:</strong> {station.distance.toFixed(2)} miles</p>
<p><strong>Station Code:</strong> {station.StationCode}</p>
<p><strong>Coordinates:</strong> {station.Latitude.toFixed(4)}, {station.Longitude.toFixed(4)}</p>
</div>
</div>
))}
</div>
</div>
);
}
```
### Station Export Utility
```typescript
import { useWeatherStations } from 'ws-dottie';
function StationExport() {
const { data: weatherStations, isLoading, error } = useWeatherStations();
const exportToCSV = () => {
if (!weatherStations) return;
const csvContent = [
'StationCode,StationName,Latitude,Longitude',
...weatherStations.map(station =>
`${station.StationCode},"${station.StationName || `Station ${station.StationCode}`}",${station.Latitude},${station.Longitude}`
)
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'wsdot-weather-stations.csv';
a.click();
window.URL.revokeObjectURL(url);
};
const exportToGeoJSON = () => {
if (!weatherStations) return;
const geoJSON = {
type: 'FeatureCollection',
features: weatherStations.map(station => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [station.Longitude, station.Latitude]
},
properties: {
stationCode: station.StationCode,
stationName: station.StationName || `Station ${station.StationCode}`
}
}))
};
const blob = new Blob([JSON.stringify(geoJSON, null, 2)], { type: 'application/json' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'wsdot-weather-stations.geojson';
a.click();
window.URL.revokeObjectURL(url);
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Weather Station Export</h2>
<div className="export-controls">
<button onClick={exportToCSV}>
Export to CSV ({weatherStations?.length || 0} stations)
</button>
<button onClick={exportToGeoJSON}>
Export to GeoJSON ({weatherStations?.length || 0} stations)
</button>
</div>
<div className="export-preview">
<h3>Preview (First 5 stations)</h3>
<pre>
{JSON.stringify(weatherStations?.slice(0, 5), null, 2)}
</pre>
</div>
</div>
);
}
```
## Common Use Cases
### Station Discovery
Find available weather stations in specific areas for weather data analysis.
### Weather Data Integration
Use station codes with Weather Information API to get current weather readings.
### Geographic Filtering
Filter stations by location or region for targeted weather monitoring.
### Station Mapping
Display weather station locations on interactive maps for visualization.
### Data Source Identification
Identify which stations provide weather data for specific locations.
### Route Planning
Find weather stations along travel routes for trip planning.
## API Documentation
- **WSDOT Documentation**: [Weather Stations Class](https://wsdot.wa.gov/traffic/api/Documentation/class_weather_stations.html)
- **API Endpoint**: [Weather Stations REST Service](https://wsdot.wa.gov/traffic/api/WeatherStations/WeatherStationsREST.svc)
## Notes
- Station codes are unique integer identifiers
- Station names typically include location information (street names, highway designations, mileposts)
- Coordinates are in decimal degrees format (WGS84)
- All stations are located within Washington State boundaries
- Station data is relatively static and doesn't change frequently
- Geographic coordinates can be used with mapping libraries for visualization
- Station codes can be used to correlate with weather data from other APIs
- Station names provide human-readable location descriptions
- The API provides a complete inventory of WSDOT weather monitoring infrastructure
- Data is cached for extended periods since station information changes rarely
- Station locations are useful for weather data analysis and geographic filtering
- Highway references in station names help identify locations along specific routes