{{ theme.skipToContentLabel || 'Skip to content' }}

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 conversion

Setup

1. Install Dependencies

bash
pnpm install

The following dependency is already added:

  • @types/google.maps (devDependency)

2. Configure Environment Variables

Add your Google Maps API key to .env.local:

bash
VITE_GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here

How to get a Google Maps API Key:

  1. Go to Google Cloud Console
  2. Create or select a project
  3. Enable the following APIs:
    • Maps JavaScript API (required)
    • Places API (optional)
  4. Create credentials → API Key
  5. Enable the Visualization Library in the Maps JavaScript API settings
  6. (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.json on mount
  • The map centers and zooms to fit all data points
  • Default visualization settings are applied

Uploading Custom Data

  1. Click the file input in the "Data Import" section
  2. Select a JSON file with the following structure:
json
[
  {
    "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_DateTimeUtc
  • Location_TimeZoneShortName
  • Asset_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 .pmtiles files in Cloudflare R2 (map-tiles bucket)
  • A Cloudflare Worker (workers/map-tiles/) reads tiles from PMTiles via range requests
  • Front-end uses google.maps.ImageMapType with TMS Y-axis convention
  • CORS restricted to https://dashboard.dustac.com.au and http://localhost:3000

Current Overlays

SitePMTiles FileSizeZoom Levels
Marandoomarandoo.pmtiles172MB14–20
Gudai Darrigudai-darri.pmtiles11MB14–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

  1. Prepare tiles as a TMS directory of PNGs ({z}/{x}/{y}.png)

  2. Run the conversion script:

    bash
    ./scripts/convert-tiles-to-pmtiles.sh path/to/tiles site-name

    This converts PNG → WebP (quality 80) → MBTiles → PMTiles. Output goes to build/pmtiles/site-name.pmtiles.

  3. Upload to R2:

    bash
    npx wrangler r2 object put map-tiles/site-name.pmtiles --file=build/pmtiles/site-name.pmtiles --remote
  4. Add 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.xml generated alongside the tiles.

  5. No Worker changes needed — it automatically maps {site} to {site}.pmtiles in R2.

Tile Serving Details

  • The Worker has a legacy fallback: if {site}.pmtiles doesn'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:

  • cwebpbrew install webp
  • mb-utilpip install mbutil
  • pmtilesbrew install pmtiles or npm install -g pmtiles

The script is idempotent — it skips steps if intermediate outputs already exist. Delete them to reconvert.

Technical Details

Data Processing

  1. Parsing: The parseHeatmapRows function:

    • Validates required fields
    • Flattens nested arrays
    • Normalizes string/number values
    • Filters out invalid records
  2. Conversion: The toWeightedLocations function:

    • Converts parsed records to Google Maps WeightedLocation objects
    • Applies weight scaling
    • Filters out points with invalid coordinates
  3. Statistics: The calculateStats function 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:

  1. Verify VITE_GOOGLE_MAPS_API_KEY is set in .env.local
  2. Check the API key is valid and hasn't been restricted
  3. Ensure Maps JavaScript API is enabled in Google Cloud Console
  4. Check browser console for specific error messages

No Data Displayed

Issue: Map loads but no heatmap visible

Solutions:

  1. Check if data points loaded (see point count in bottom-left overlay)
  2. Try adjusting opacity and radius sliders
  3. Verify JSON file format matches expected structure
  4. Check browser console for parsing errors

Data File Too Large

Issue: JSON file upload fails or is slow

Solutions:

  1. Reduce the dataset size
  2. Consider server-side processing for large files
  3. Use pagination or data sampling
  4. 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:

  1. Styling: Converted from standalone Card/Button components to inline Tailwind classes matching the dashboard theme
  2. Icons: Using @iconify/react consistent with the rest of the app
  3. Routing: Integrated into existing React Router structure at /heatmap
  4. Data: Sample data moved to /public/data/ for accessibility
  5. Documentation: Added comprehensive setup and troubleshooting guides

Original project structure and implementation were preserved where possible to maintain functionality.

References