Skip to content

Commit 3d6d698

Browse files
mpiannucciclaude
andcommitted
Fix WASM fetch compatibility and add comprehensive examples
- Fix window.unwrap() panic in Node.js/Bun by using global fetch function - Fix DAS parser to handle global attributes outside variable blocks - Fix DDS parser to accept case-insensitive Grid keywords (Array:/Maps:) - Add Bun script example for server-side OpenDAP data analysis - Add interactive web viewer with grid visualization and statistics - Add local development server to handle CORS issues - Add comprehensive examples README with troubleshooting guide 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f14f9b8 commit 3d6d698

File tree

7 files changed

+1113
-9
lines changed

7 files changed

+1113
-9
lines changed

readap-wasm/examples/README.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# ReadAP WASM Examples
2+
3+
This directory contains examples demonstrating different ways to use the readap-wasm library for OpenDAP data access.
4+
5+
## Examples Overview
6+
7+
### 1. 📄 `fetch-example.js` - Basic WASM Usage
8+
The original comprehensive example showing all library features.
9+
10+
### 2. 🏃 `bun-example.js` - Server-Side Analysis
11+
A complete Bun script for meteorological data analysis with performance testing.
12+
13+
### 3. 🌐 `web-viewer.html` - Interactive Web Viewer
14+
A web-based grid data visualizer with interactive controls.
15+
16+
### 4. 🛠️ `serve.js` - Development Server
17+
A local development server to handle CORS issues.
18+
19+
## Quick Start
20+
21+
### Running the Bun Example
22+
```bash
23+
# Navigate to examples directory
24+
cd readap-wasm/examples
25+
26+
# Run the Bun script
27+
bun run bun-example.js
28+
```
29+
30+
### Running the Web Viewer
31+
32+
#### Option 1: Use the Development Server (Recommended)
33+
```bash
34+
# Navigate to examples directory
35+
cd readap-wasm/examples
36+
37+
# Start the local server
38+
bun run serve.js
39+
40+
# Open your browser to http://localhost:8080
41+
```
42+
43+
#### Option 2: Direct File Access
44+
Simply open `web-viewer.html` in your browser, but you may encounter CORS issues.
45+
46+
### Running the Basic Example
47+
```bash
48+
# Navigate to examples directory
49+
cd readap-wasm/examples
50+
51+
# Run with Node.js or Bun
52+
node fetch-example.js
53+
# or
54+
bun run fetch-example.js
55+
```
56+
57+
## CORS Issues and Solutions
58+
59+
When running the web viewer, you might encounter CORS (Cross-Origin Resource Sharing) errors. This is because browsers block loading WASM files from the local file system for security reasons.
60+
61+
### Solutions:
62+
63+
1. **Use the Development Server** (Recommended)
64+
```bash
65+
bun run serve.js
66+
```
67+
Then visit `http://localhost:8080`
68+
69+
2. **Chrome with Disabled Security** (For testing only)
70+
```bash
71+
chrome --disable-web-security --user-data-dir=/tmp/chrome_dev
72+
```
73+
74+
3. **Firefox Local File Access**
75+
- Type `about:config` in Firefox
76+
- Set `security.fileuri.strict_origin_policy` to `false`
77+
- ⚠️ Remember to reset this after testing
78+
79+
4. **Use a Different Web Server**
80+
```bash
81+
# Python 3
82+
python -m http.server 8080
83+
84+
# Python 2
85+
python -m SimpleHTTPServer 8080
86+
87+
# Node.js (if you have http-server)
88+
npx http-server -p 8080
89+
```
90+
91+
## Example Features
92+
93+
### Bun Script Features
94+
- 📊 Comprehensive meteorological data analysis
95+
- ⚡ Performance benchmarking
96+
- 🎯 Multiple selection strategies (index-based, value-based, chained)
97+
- 📈 Statistical analysis with min/max/average calculations
98+
- 🔄 Multi-variable batch processing
99+
- 💾 Memory-efficient data handling
100+
101+
### Web Viewer Features
102+
- 🎨 Interactive grid visualization with color coding
103+
- 📱 Responsive design for different screen sizes
104+
- 🎛️ Real-time controls for data selection
105+
- 📊 Live statistical analysis display
106+
- 🔄 Dynamic variable loading
107+
- ❌ Comprehensive error handling
108+
109+
### Common Features Across All Examples
110+
- 🌊 xarray-style data selection (`isel` and `sel`)
111+
- 🎯 Nearest neighbor coordinate lookup
112+
- 📡 Automatic metadata fetching
113+
- 🔗 Constraint-based URL building
114+
- ⚡ Efficient typed array data transfer
115+
- 🛡️ Robust error handling
116+
117+
## Dataset Information
118+
119+
All examples use the same test dataset:
120+
- **URL**: `https://compute.earthmover.io/v1/services/dap2/earthmover-demos/gfs/main/solar/opendap`
121+
- **Type**: GFS (Global Forecast System) meteorological data
122+
- **Variables**: Temperature (t2m), Total Cloud Cover (tcc), Wind Gust (gust), and more
123+
- **Coordinates**: time, latitude, longitude
124+
125+
## Troubleshooting
126+
127+
### WASM Loading Issues
128+
- Ensure you're running from a web server, not opening files directly
129+
- Check browser console for specific error messages
130+
- Try the development server: `bun run serve.js`
131+
132+
### Network Issues
133+
- The dataset URL must be accessible from your location
134+
- Some corporate firewalls may block the requests
135+
- Try running the Bun example first to test connectivity
136+
137+
### Browser Compatibility
138+
- Modern browsers with WebAssembly support required
139+
- Chrome 57+, Firefox 52+, Safari 11+, Edge 16+
140+
- Enable JavaScript if disabled
141+
142+
### Performance
143+
- Large data selections may take time to load
144+
- Start with small ranges (lat/lon 0-10) for testing
145+
- The development server includes CORS and caching headers for optimal performance
146+
147+
## Development
148+
149+
To modify or extend these examples:
150+
151+
1. Build the WASM package first:
152+
```bash
153+
cd readap-wasm
154+
wasm-pack build
155+
```
156+
157+
2. The built package will be in `pkg/` directory
158+
159+
3. Examples import from `../pkg/readap_wasm.js`
160+
161+
4. Test your changes with the development server
162+
163+
## Support
164+
165+
If you encounter issues:
166+
1. Check the browser console for error messages
167+
2. Ensure the WASM package is built (`wasm-pack build`)
168+
3. Try the development server for web-based examples
169+
4. Verify network connectivity with the Bun example
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env bun
2+
// Example Bun script demonstrating readap-wasm for OpenDAP data analysis
3+
// Run with: bun run bun-example.js
4+
5+
import init, { OpenDAPDataset } from '../pkg/readap_wasm.js';
6+
7+
const BASE_URL = 'https://compute.earthmover.io/v1/services/dap2/earthmover-demos/gfs/main/solar/opendap';
8+
9+
async function analyzeMeteorologyData() {
10+
console.log('🌊 ReadAP WASM + Bun Example - Meteorological Data Analysis');
11+
console.log('='.repeat(60));
12+
13+
// Initialize the WASM module
14+
await init();
15+
16+
try {
17+
// Load the dataset
18+
console.log('📡 Loading dataset metadata...');
19+
const dataset = await OpenDAPDataset.fromURL(BASE_URL);
20+
21+
// Display available variables
22+
const variables = dataset.getVariableNames();
23+
console.log(`📊 Found ${variables.length} variables:`);
24+
variables.forEach((name, i) => {
25+
console.log(` ${i + 1}. ${name}`);
26+
});
27+
28+
// Analyze a specific variable
29+
console.log('\n🔍 Analyzing temperature data (t2m)...');
30+
const tempInfo = JSON.parse(dataset.getVariableInfo('t2m'));
31+
console.log(` - Dimensions: ${tempInfo.dimensions?.join(', ') || 'N/A'}`);
32+
console.log(` - Attributes: ${Object.keys(tempInfo.attributes || {}).length} found`);
33+
34+
// Load coordinates for advanced selections
35+
console.log('\n📍 Loading coordinate data...');
36+
const coords = ['time', 'latitude', 'longitude'];
37+
await Promise.all(coords.map(coord => dataset.loadCoordinates(coord)));
38+
console.log(' ✅ Coordinates loaded successfully');
39+
40+
// Perform different types of data selections
41+
console.log('\n🎯 Performing data selections...');
42+
43+
// 1. Simple index-based selection
44+
console.log(' → Index-based selection (first time, lat 10-20)');
45+
const indexSelection = dataset.isel({
46+
time: { type: "single", value: 0 },
47+
latitude: { type: "range", start: 10, end: 20 }
48+
});
49+
const tempIndexData = await dataset.getVariable('t2m', indexSelection);
50+
console.log(` Data shape: ${tempIndexData.length} elements`);
51+
console.log(` Temperature range: ${Math.min(...tempIndexData.data)} to ${Math.max(...tempIndexData.data)} K`);
52+
53+
// 2. Value-based selection with nearest neighbor
54+
console.log(' → Value-based selection (NYC area)');
55+
const valueSelection = dataset.sel({
56+
latitude: [40.0, 41.0], // NYC latitude range
57+
longitude: [-75.0, -73.0] // NYC longitude range
58+
});
59+
const tempValueData = await dataset.getVariable('t2m', valueSelection);
60+
console.log(` Data shape: ${tempValueData.length} elements`);
61+
62+
// 3. Multi-variable analysis
63+
console.log(' → Multi-variable analysis');
64+
const multiVars = ['t2m', 'tcc', 'gust'];
65+
const multiData = await dataset.getVariables(multiVars, indexSelection);
66+
67+
console.log(' Variable statistics:');
68+
Object.entries(multiData).forEach(([varName, data]) => {
69+
const min = Math.min(...data.data);
70+
const max = Math.max(...data.data);
71+
const avg = data.data.reduce((a, b) => a + b, 0) / data.data.length;
72+
console.log(` ${varName}: min=${min.toFixed(2)}, max=${max.toFixed(2)}, avg=${avg.toFixed(2)}`);
73+
});
74+
75+
// 4. Chained selections for complex analysis
76+
console.log(' → Chained selection (surface data for specific region)');
77+
const chainedSelection = dataset
78+
.isel({ time: { type: "single", value: 0 } })
79+
.sel({ latitude: [35.0, 45.0], longitude: [-80.0, -70.0] });
80+
81+
const surfaceTemp = await dataset.getVariable('t2m', chainedSelection);
82+
console.log(` Surface temperature data: ${surfaceTemp.length} grid points`);
83+
84+
// Performance timing
85+
console.log('\n⏱️ Performance test - rapid data access:');
86+
const startTime = performance.now();
87+
88+
const rapidSelections = await Promise.all([
89+
dataset.getVariable('t2m', dataset.isel({ time: { type: "single", value: 0 } })),
90+
dataset.getVariable('tcc', dataset.isel({ time: { type: "single", value: 0 } })),
91+
dataset.getVariable('gust', dataset.isel({ time: { type: "single", value: 0 } }))
92+
]);
93+
94+
const endTime = performance.now();
95+
console.log(` ✅ Fetched 3 variables in ${(endTime - startTime).toFixed(2)}ms`);
96+
97+
// Summary
98+
console.log('\n📈 Analysis Summary:');
99+
console.log(` • Dataset variables: ${variables.length}`);
100+
console.log(` • Total data points analyzed: ${rapidSelections.reduce((sum, data) => sum + data.length, 0)}`);
101+
console.log(` • Coordinate systems: ${coords.length} loaded`);
102+
console.log(' • Selection methods: index-based, value-based, chained');
103+
104+
} catch (error) {
105+
console.error('❌ Error during analysis:', error);
106+
process.exit(1);
107+
}
108+
}
109+
110+
// Run the analysis
111+
console.log('🚀 Starting Bun + ReadAP WASM analysis...\n');
112+
analyzeMeteorologyData()
113+
.then(() => {
114+
console.log('\n✅ Analysis completed successfully!');
115+
process.exit(0);
116+
})
117+
.catch((error) => {
118+
console.error('\n❌ Analysis failed:', error);
119+
process.exit(1);
120+
});

readap-wasm/examples/serve.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env bun
2+
// Simple local development server for the web viewer
3+
// Run with: bun run serve.js
4+
5+
import { serve } from "bun";
6+
import { readFileSync, existsSync } from "fs";
7+
import { join, extname } from "path";
8+
9+
const PORT = 8080;
10+
const ROOT_DIR = import.meta.dirname;
11+
12+
// MIME types for different file extensions
13+
const MIME_TYPES = {
14+
'.html': 'text/html',
15+
'.js': 'application/javascript',
16+
'.wasm': 'application/wasm',
17+
'.css': 'text/css',
18+
'.json': 'application/json',
19+
'.md': 'text/markdown',
20+
'.ts': 'application/typescript'
21+
};
22+
23+
function getMimeType(filePath) {
24+
const ext = extname(filePath).toLowerCase();
25+
return MIME_TYPES[ext] || 'text/plain';
26+
}
27+
28+
function serveFile(filePath) {
29+
try {
30+
if (!existsSync(filePath)) {
31+
return new Response("File not found", { status: 404 });
32+
}
33+
34+
const content = readFileSync(filePath);
35+
const mimeType = getMimeType(filePath);
36+
37+
const headers = {
38+
'Content-Type': mimeType,
39+
'Access-Control-Allow-Origin': '*',
40+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
41+
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
42+
};
43+
44+
// Add WASM-specific headers
45+
if (mimeType === 'application/wasm') {
46+
headers['Cross-Origin-Embedder-Policy'] = 'require-corp';
47+
headers['Cross-Origin-Opener-Policy'] = 'same-origin';
48+
}
49+
50+
return new Response(content, { headers });
51+
} catch (error) {
52+
return new Response(`Error reading file: ${error.message}`, { status: 500 });
53+
}
54+
}
55+
56+
const server = serve({
57+
port: PORT,
58+
fetch(req) {
59+
const url = new URL(req.url);
60+
let pathname = url.pathname;
61+
62+
// Handle CORS preflight
63+
if (req.method === 'OPTIONS') {
64+
return new Response(null, {
65+
status: 200,
66+
headers: {
67+
'Access-Control-Allow-Origin': '*',
68+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
69+
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
70+
}
71+
});
72+
}
73+
74+
// Default to index.html for root path
75+
if (pathname === '/') {
76+
pathname = '/web-viewer.html';
77+
}
78+
79+
// Remove leading slash and resolve file path
80+
const relativePath = pathname.slice(1);
81+
82+
// Handle pkg directory access
83+
if (relativePath.startsWith('pkg/')) {
84+
const pkgPath = join(ROOT_DIR, '..', relativePath);
85+
return serveFile(pkgPath);
86+
}
87+
88+
// Handle examples directory
89+
const filePath = join(ROOT_DIR, relativePath);
90+
return serveFile(filePath);
91+
},
92+
});
93+
94+
console.log(`🌐 Local development server running at http://localhost:${PORT}`);
95+
console.log(`📁 Serving files from: ${ROOT_DIR}`);
96+
console.log(`🎯 Open http://localhost:${PORT} to view the web viewer`);
97+
console.log('');
98+
console.log('Available endpoints:');
99+
console.log(` • http://localhost:${PORT}/web-viewer.html - Interactive web viewer`);
100+
console.log(` • http://localhost:${PORT}/fetch-example.js - Original fetch example`);
101+
console.log(` • http://localhost:${PORT}/pkg/ - WASM package files`);
102+
console.log('');
103+
console.log('Press Ctrl+C to stop the server');

0 commit comments

Comments
 (0)