Heat Map Feature
Overview
The Heat Map feature provides an interactive visualization of dust monitoring data using Google Maps with a heatmap overlay. This feature was successfully migrated from the standalone demo-react-heatmap project.
Features
- Interactive Google Maps: Hybrid map view with heatmap visualization layer
- Customizable Controls:
- Color gradients (Google default, Blue→Red, Green→Red, Purple→Yellow)
- Radius adjustment (5-80px)
- Opacity control (0.1-1.0)
- Weight multiplier (0.1x-5x)
- Max intensity (auto or manual)
- Dissipating effect toggle
- Data Import: Upload JSON files with heatmap data
- Default Data: Automatically loads sample data from
/public/data/heatmap_response.json - Real-time Statistics: Display point count, value range, and map status
File Structure
src/
├── features/
│ └── heatmap/
│ ├── types.ts # TypeScript type definitions
│ ├── constants.ts # Default values and configurations
│ ├── utils.ts # Utility functions for data processing
│ ├── overlayConfig.ts # Mine site overlay configuration (PMTiles)
│ ├── customOverlay.ts # Custom Google Maps overlay classes (canvas heatmap)
│ └── useGoogleMapsApi.ts # Hook for loading Google Maps API
└── app/
└── (admin)/
└── (pages)/
└── heatmap/
└── index.tsx # Main HeatMap page component
workers/
└── map-tiles/ # Cloudflare Worker for tile serving
├── src/index.ts # Worker with PMTiles + R2 integration
├── wrangler.toml # Cloudflare config (R2 bucket binding)
├── package.json
└── tsconfig.json
scripts/
└── convert-tiles-to-pmtiles.sh # PNG tiles → WebP → PMTiles conversionSetup
1. Install Dependencies
pnpm installThe following dependency is already added:
@types/google.maps(devDependency)
2. Configure Environment Variables
Add your Google Maps API key to .env.local:
VITE_GOOGLE_MAPS_API_KEY=your_google_maps_api_key_hereHow to get a Google Maps API Key:
- Go to Google Cloud Console
- Create or select a project
- Enable the following APIs:
- Maps JavaScript API (required)
- Places API (optional)
- Create credentials → API Key
- Enable the Visualization Library in the Maps JavaScript API settings
- (Recommended) Restrict the API key to your domain for security
See docs/setup/ENVIRONMENT_VARIABLES.md for more details.
3. Access the Feature
Navigate to /heatmap in the application.
Usage
Default Behavior
- The page automatically loads sample data from
/public/data/heatmap_response.jsonon mount - The map centers and zooms to fit all data points
- Default visualization settings are applied
Uploading Custom Data
- Click the file input in the "Data Import" section
- Select a JSON file with the following structure:
[
{
"Location_Latitude": "-22.6557",
"Location_Longitude": "118.1893",
"Location_AssetValue": "42.5",
"Location_DateTimeUtc": "2024-01-01T00:00:00Z",
"Location_TimeZoneShortName": "AWST",
"Asset_AssetId": "DUST-001"
}
// ... more points
]Required Fields:
Location_Latitude(string or number)Location_Longitude(string or number)Location_AssetValue(string or number) - represents the intensity/weight
Optional Fields:
Location_DateTimeUtcLocation_TimeZoneShortNameAsset_AssetId- Any other custom fields
The parser is flexible and can handle:
- Nested arrays:
[[{...}], [{...}]]→ flattened automatically - Mixed string/number formats → normalized to strings internally
Adjusting Visualization
Use the control panel on the left to adjust:
- Color Gradient: Change the color scheme of the heatmap
- Radius: Control the size of each heat point
- Opacity: Adjust the transparency of the heatmap layer
- Weight Multiplier: Scale the intensity values
- Max Intensity: Set a ceiling for intensity values (0 = auto)
- Dissipating: Enable/disable the dissipating effect at zoom levels
Click "Reset to defaults" to restore initial settings.
Mine Site Tile Overlays
The heatmap supports satellite/aerial imagery overlays for mine sites, rendered as tile layers on top of Google Maps. All overlays use the PMTiles format — a single-file archive of map tiles served via HTTP range requests.
Architecture
Google Maps (ImageMapType)
→ GET https://map-tiles.fudong.workers.dev/{site}/{z}/{x}/{y}.png
→ Cloudflare Worker (pmtiles JS library)
→ R2 bucket: {site}.pmtiles (HTTP range request)
→ Response: WebP tile image- Tiles are stored as
.pmtilesfiles in Cloudflare R2 (map-tilesbucket) - A Cloudflare Worker (
workers/map-tiles/) reads tiles from PMTiles via range requests - Front-end uses
google.maps.ImageMapTypewith TMS Y-axis convention - CORS restricted to
https://dashboard.dustac.com.auandhttp://localhost:3000
Current Overlays
| Site | PMTiles File | Size | Zoom Levels |
|---|---|---|---|
| Marandoo | marandoo.pmtiles | 172MB | 14–20 |
| Gudai Darri | gudai-darri.pmtiles | 11MB | 14–20 |
Overlay configuration is in src/features/heatmap/overlayConfig.ts. Each entry defines the tile base path, zoom range, and geographic bounds.
Adding a New Mine Site Overlay
Prepare tiles as a TMS directory of PNGs (
{z}/{x}/{y}.png)Run the conversion script:
bash./scripts/convert-tiles-to-pmtiles.sh path/to/tiles site-nameThis converts PNG → WebP (quality 80) → MBTiles → PMTiles. Output goes to
build/pmtiles/site-name.pmtiles.Upload to R2:
bashnpx wrangler r2 object put map-tiles/site-name.pmtiles --file=build/pmtiles/site-name.pmtiles --remoteAdd an entry in
overlayConfig.ts:typescript{ type: "tile", id: "site-name-overlay", name: "Site Name", mineSiteId: "uuid-from-cfg_mine_sites", tileBasePath: "https://map-tiles.fudong.workers.dev/site-name", minZoom: 14, maxZoom: 20, bounds: { north: ..., south: ..., east: ..., west: ... }, }Bounds can be found in the
tilemapresource.xmlgenerated alongside the tiles.No Worker changes needed — it automatically maps
{site}to{site}.pmtilesin R2.
Tile Serving Details
- The Worker has a legacy fallback: if
{site}.pmtilesdoesn't exist in R2, it tries{site}/{z}/{x}/{y}.png(scattered files) - Tiles are cached with
Cache-Control: max-age=31536000, immutable - The Worker caches PMTiles instances in-memory per isolate for performance
- TMS to XYZ Y-axis conversion:
xyzY = (1 << z) - 1 - tmsY
Conversion Script
scripts/convert-tiles-to-pmtiles.sh requires:
cwebp—brew install webpmb-util—pip install mbutilpmtiles—brew install pmtilesornpm install -g pmtiles
The script is idempotent — it skips steps if intermediate outputs already exist. Delete them to reconvert.
Technical Details
Data Processing
Parsing: The
parseHeatmapRowsfunction:- Validates required fields
- Flattens nested arrays
- Normalizes string/number values
- Filters out invalid records
Conversion: The
toWeightedLocationsfunction:- Converts parsed records to Google Maps WeightedLocation objects
- Applies weight scaling
- Filters out points with invalid coordinates
Statistics: The
calculateStatsfunction computes:- Total point count
- Min/max values for intensity
- Used for UI display and auto-scaling
Google Maps Integration
The useGoogleMapsApi hook:
- Dynamically loads the Google Maps JavaScript API
- Handles loading states (idle → loading → ready/error)
- Ensures the API is only loaded once
- Requires the visualization library for heatmap support
Performance Considerations
- Large datasets (10k+ points) may impact performance
- The default sample file contains ~139k records
- Consider pagination or data filtering for very large datasets
- Map rendering is handled by Google Maps and is generally efficient
Troubleshooting
Map Not Loading
Error: "Failed to load Google Maps JavaScript API"
Solutions:
- Verify
VITE_GOOGLE_MAPS_API_KEYis set in.env.local - Check the API key is valid and hasn't been restricted
- Ensure Maps JavaScript API is enabled in Google Cloud Console
- Check browser console for specific error messages
No Data Displayed
Issue: Map loads but no heatmap visible
Solutions:
- Check if data points loaded (see point count in bottom-left overlay)
- Try adjusting opacity and radius sliders
- Verify JSON file format matches expected structure
- Check browser console for parsing errors
Data File Too Large
Issue: JSON file upload fails or is slow
Solutions:
- Reduce the dataset size
- Consider server-side processing for large files
- Use pagination or data sampling
- The default sample file is 5.8MB - consider this a practical limit
Future Enhancements
Potential improvements:
- [ ] Data point clustering for large datasets
- [ ] Time-based filtering and animation
- [ ] Export heatmap as image
- [ ] Multiple data layers
- [ ] Integration with uploaded dust monitoring data
- [ ] Real-time data updates via websockets
- [ ] Custom marker info windows on hover
- [ ] Heatmap comparison mode (before/after)
Migration Notes
This feature was migrated from a standalone Vite + React project with the following changes:
- Styling: Converted from standalone Card/Button components to inline Tailwind classes matching the dashboard theme
- Icons: Using @iconify/react consistent with the rest of the app
- Routing: Integrated into existing React Router structure at
/heatmap - Data: Sample data moved to
/public/data/for accessibility - Documentation: Added comprehensive setup and troubleshooting guides
Original project structure and implementation were preserved where possible to maintain functionality.