{"_id":"@amadmike/maplibre-scalar-flow","_rev":"27-1633f4a129e74fed93751e051f156d3f","name":"@amadmike/maplibre-scalar-flow","dist-tags":{"latest":"2.0.1"},"versions":{"1.1.5":{"name":"@amadmike/maplibre-scalar-flow","version":"1.1.5","keywords":["maplibre","webgl","glsl","particles","vector field","scalar field","visualization","gis"],"license":"MIT","_id":"@amadmike/maplibre-scalar-flow@1.1.5","maintainers":[{"name":"amadmike","email":"xbiznet@gmail.com"}],"homepage":"https://github.com/madmike/maplibre-scalar-flow#readme","bugs":{"url":"https://github.com/madmike/maplibre-scalar-flow/issues"},"dist":{"shasum":"a300646d73591e3f80f52220e428aebcb31e0c4f","tarball":"https://registry.npmjs.org/@amadmike/maplibre-scalar-flow/-/maplibre-scalar-flow-1.1.5.tgz","fileCount":7,"integrity":"sha512-lGIwhhJCSPJ3Y0SpcKu+QeMeIXtaBU9UzqaRNagzO2yPLk0eleU7zQW0HMpiJn3NZLhTXHTePu/8Ka2DwyemYg==","signatures":[{"sig":"MEYCIQDvJb5f60zxV8WIaVfyUDSo6XzyX0Ev2Z+J2ffp3sSSzgIhAIrFucrl9Mx3siLfyDSpVBBrhudjEExQ2U1rIh8xVQgT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":151366},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"b8b2fc2a96050dd31896158dcf31901d1641213d","scripts":{"dev":"npm run bundle-shaders && tsup src/index.ts --dts --format esm,cjs --watch","build":"node scripts/build-production.js","release":"npm publish --access public","build:dev":"npm run bundle-shaders && tsup src/index.ts --dts --format esm,cjs --clean","typecheck":"tsc --noEmit","build:prod":"node scripts/build-production.js","bundle-shaders":"node scripts/bundle-shaders.js","prepublishOnly":"npm run build","bundle-shaders:minify":"node scripts/bundle-shaders.js --minify"},"_npmUser":{"name":"amadmike","email":"xbiznet@gmail.com"},"repository":{"url":"git+https://github.com/madmike/maplibre-scalar-flow.git","type":"git"},"_npmVersion":"10.9.3","description":"MapLibre visualization plugin for scalar fields and vector-field particles.","directories":{},"sideEffects":false,"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","typescript":"^5.9.2","maplibre-gl":"^3.6.2","@plutotcool/glsl-bundler":"^1.2.1"},"peerDependencies":{"maplibre-gl":">=3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/maplibre-scalar-flow_1.1.5_1757467370559_0.7546443399509721","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@amadmike/maplibre-scalar-flow","version":"2.0.0","keywords":["maplibre","webgl","glsl","particles","vector field","scalar field","visualization","gis"],"license":"MIT","_id":"@amadmike/maplibre-scalar-flow@2.0.0","maintainers":[{"name":"amadmike","email":"xbiznet@gmail.com"}],"homepage":"https://github.com/madmike/maplibre-scalar-flow#readme","bugs":{"url":"https://github.com/madmike/maplibre-scalar-flow/issues"},"dist":{"shasum":"8b28a1d49aa708f679cac4e2ea798f3f876a1d43","tarball":"https://registry.npmjs.org/@amadmike/maplibre-scalar-flow/-/maplibre-scalar-flow-2.0.0.tgz","fileCount":14,"integrity":"sha512-6WwYq4iWzXh/JuOZ13+6rOwDgA8ytbJ4+VCFbpmBm+LdYryZp+9dvXQGs/NbJYbgyKSk07STEs1eulz1nPA8+Q==","signatures":[{"sig":"MEUCIG9XKD0sTxRigwSEfvfOLsyFF7ln09L28JybeEUrwEokAiEAp4+KcRl9514ObM7XHczvaqGX0eScmxJImH+jqXGcPUc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":230359},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"}},"gitHead":"b8b2fc2a96050dd31896158dcf31901d1641213d","scripts":{"dev":"npm run bundle-shaders && vite","build":"node scripts/build-production.js","dev:lib":"npm run bundle-shaders && tsup src/index.ts src/react/index.ts --dts --format esm,cjs --watch","release":"npm publish --access public","build:dev":"npm run bundle-shaders && tsup src/index.ts src/react/index.ts --dts --format esm,cjs --clean","typecheck":"tsc --noEmit","build:prod":"node scripts/build-production.js","bundle-shaders":"node scripts/bundle-shaders.js","prepublishOnly":"npm run build","bundle-shaders:minify":"node scripts/bundle-shaders.js --minify"},"_npmUser":{"name":"amadmike","email":"xbiznet@gmail.com"},"repository":{"url":"git+https://github.com/madmike/maplibre-scalar-flow.git","type":"git"},"_npmVersion":"11.7.0","description":"MapLibre visualization plugin for scalar fields and vector-field particles.","directories":{},"sideEffects":false,"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","vite":"^6.0.0","react":"^19.0.0","react-dom":"^19.0.0","typescript":"^5.9.2","maplibre-gl":"^5.19.0","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitejs/plugin-react":"^4.3.0","@plutotcool/glsl-bundler":"^1.2.1"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0","maplibre-gl":">=5.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/maplibre-scalar-flow_2.0.0_1776264579025_0.13181011785554775","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@amadmike/maplibre-scalar-flow","version":"2.0.1","description":"MapLibre visualization plugin for scalar fields and vector-field particles.","license":"MIT","type":"module","sideEffects":false,"repository":{"type":"git","url":"git+https://github.com/madmike/maplibre-scalar-flow.git"},"homepage":"https://github.com/madmike/maplibre-scalar-flow#readme","bugs":{"url":"https://github.com/madmike/maplibre-scalar-flow/issues"},"keywords":["maplibre","webgl","glsl","particles","vector field","scalar field","visualization","gis"],"main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"}},"scripts":{"build":"node scripts/build-production.js","build:dev":"npm run bundle-shaders && tsup src/index.ts src/react/index.ts --dts --format esm,cjs --clean","build:prod":"node scripts/build-production.js","bundle-shaders":"node scripts/bundle-shaders.js","bundle-shaders:minify":"node scripts/bundle-shaders.js --minify","dev":"npm run bundle-shaders && vite","dev:lib":"npm run bundle-shaders && tsup src/index.ts src/react/index.ts --dts --format esm,cjs --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build","release":"npm publish --access public"},"publishConfig":{"access":"public"},"peerDependencies":{"maplibre-gl":">=5.0.0","react":">=18.0.0","react-dom":">=18.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"devDependencies":{"@plutotcool/glsl-bundler":"^1.2.1","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitejs/plugin-react":"^4.3.0","maplibre-gl":"^5.19.0","react":"^19.0.0","react-dom":"^19.0.0","tsup":"^8.5.0","typescript":"^5.9.2","vite":"^6.0.0"},"gitHead":"b8b2fc2a96050dd31896158dcf31901d1641213d","_id":"@amadmike/maplibre-scalar-flow@2.0.1","_nodeVersion":"24.8.0","_npmVersion":"11.14.1","dist":{"integrity":"sha512-M5rsU5+Xd7/xaWc/ggujfOjTjAgSxr3ioXDjA+Bhf6R3Gb0BD7Ab1JeLxx+YQ7QAOGWZTx8iw68yDZ6HKHKW8g==","shasum":"4a8a8c1c303a65f04af57dddf02590937a8dfe03","tarball":"https://registry.npmjs.org/@amadmike/maplibre-scalar-flow/-/maplibre-scalar-flow-2.0.1.tgz","fileCount":14,"unpackedSize":234984,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDq2Z+IJlLjQkvnh+Rc79kPum3dluhFwpSnB9/OWPoWYAiEAgGObps70Dv4GGTBiwuoqEIMOblNGBESqQ5SJdYklxes="}]},"_npmUser":{"name":"amadmike","email":"xbiznet@gmail.com"},"directories":{},"maintainers":[{"name":"amadmike","email":"xbiznet@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/maplibre-scalar-flow_2.0.1_1781140059465_0.2947622210198184"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-10T01:22:50.461Z","modified":"2026-06-11T01:07:39.723Z","0.1.0":"2025-08-12T22:37:47.382Z","0.1.1":"2025-08-12T23:41:49.159Z","0.1.2":"2025-08-13T00:12:50.052Z","1.0.0":"2025-08-17T20:33:00.973Z","1.0.1":"2025-08-17T20:49:21.679Z","1.0.2":"2025-08-17T21:01:21.367Z","1.0.3":"2025-08-19T02:25:24.699Z","1.0.4":"2025-08-19T23:52:04.269Z","1.1.1":"2025-08-20T01:28:32.308Z","1.1.2":"2025-09-04T14:58:28.409Z","1.1.3":"2025-09-04T15:03:12.647Z","1.1.4":"2025-09-09T01:45:17.892Z","1.1.5":"2025-09-10T01:22:50.746Z","2.0.0":"2026-04-15T14:49:39.172Z","2.0.1":"2026-06-11T01:07:39.614Z"},"bugs":{"url":"https://github.com/madmike/maplibre-scalar-flow/issues"},"license":"MIT","homepage":"https://github.com/madmike/maplibre-scalar-flow#readme","keywords":["maplibre","webgl","glsl","particles","vector field","scalar field","visualization","gis"],"repository":{"type":"git","url":"git+https://github.com/madmike/maplibre-scalar-flow.git"},"description":"MapLibre visualization plugin for scalar fields and vector-field particles.","maintainers":[{"name":"amadmike","email":"xbiznet@gmail.com"}],"readme":"# MapLibre Scalar Flow\n\nA high-performance WebGL plugin for MapLibre GL JS that provides advanced visualization of scalar fields and vector flow data. Perfect for displaying weather data, oceanographic currents, wind patterns, temperature fields, and other scientific datasets.\n\n## Features\n\n🎨 **Advanced Scalar Visualization**\n- Multiple interpolation modes (Auto, Nearest, Bilinear, Bicubic, Lanczos, Smoothstep, RBF)\n- Customizable color schemes with threshold-based mapping\n- Multiple value scaling modes (Linear, Gamma, Log)\n- Real-time blur and contrast adjustments\n\n⚡ **Dynamic Particle Systems**\n- GPU-accelerated particle animation\n- Adaptive particle density based on zoom level\n- Customizable trail effects and opacity\n- Speed-based color coding\n- Configurable drop rates and movement physics\n\n🔧 **Developer-Friendly**\n- TypeScript support with full type definitions\n- Unified API for both scalar and particle layers\n- Automatic layer type inference\n- Metadata extraction from image EXIF data\n- Comprehensive error handling\n\n## Installation\n\n```bash\nnpm install @amadmike/maplibre-scalar-flow\n```\n\n## Quick Start\n\n```typescript\nimport maplibregl from 'maplibre-gl';\nimport { ScalarFlow, InterpolationMode } from '@amadmike/maplibre-scalar-flow';\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: 'https://demotiles.maplibre.org/style.json',\n  center: [-100, 40],\n  zoom: 3,\n});\n\nconst scalarFlow = new ScalarFlow(map);\n  \n// Display temperature data\nawait scalarFlow.setLayer('path/to/temperature.png', {\n  id: 'temperature',\n    colors: [\n    { threshold: -20, value: '#000080' },  // Deep blue (cold)\n    { threshold: 0, value: '#0080ff' },    // Blue (freezing)\n    { threshold: 10, value: '#00ff80' },   // Green (cool)\n    { threshold: 20, value: '#ffff00' },   // Yellow (warm)\n    { threshold: 30, value: '#ff8000' },   // Orange (hot)\n    { threshold: 40, value: '#ff0000' },   // Red (very hot)\n  ],\n  dataRange: [-30, 50],\n  interpolation: InterpolationMode.Bicubic,\n});\n```\n\n## API Reference\n\n### ScalarFlow Class\n\n#### Constructor\n```typescript\nnew ScalarFlow(map: Map, beforeLayerId?: string)\n```\n\n#### Methods\n\n##### `setLayer(source, config, kind?)`\nSets up a visualization layer with the specified configuration.\n\n```typescript\nawait scalarFlow.setLayer(\n  'path/to/data.png',\n  {\n    // ... configuration\n  },\n  'scalar' // optional, auto-detected if omitted\n);\n```\n\n##### `updateLayer(options, kind?)`\nUpdates an existing layer's configuration.\n\n```typescript\nscalarFlow.updateLayer({\n  contrast: 1.5,\n  interpolation: InterpolationMode.Lanczos,\n}, 'scalar');\n```\n\n##### `removeLayer(kind?)`\nRemoves the specified layer(s).\n\n```typescript\nscalarFlow.removeLayer('scalar');     // Remove scalar layer\nscalarFlow.removeLayer('particles');  // Remove particle layer\nscalarFlow.removeLayer('all');        // Remove all layers (default)\n```\n\n### Layer Instance Methods\n\nBoth scalar and particle layers provide additional methods for fine-grained control:\n\n#### `setImage(source)`\nSets the image source for the layer.\n\n```typescript\n// Set from URL\nlayer.setImage('path/to/data.png');\n\n// Set from image element\nconst img = new Image();\nimg.src = 'data.png';\nimg.onload = () => layer.setImage(img);\n\n// Clear the layer\nlayer.setImage(null);\n```\n\n#### `onceImageApplied(callback)`\nRegisters a callback to execute after an image is loaded and processed.\n\n```typescript\nlayer.onceImageApplied(() => {\n  console.log('Image loaded and processed');\n  // Now safe to update configuration\n  layer.updateConfig({ contrast: 1.5 });\n});\nlayer.setImage('data.png');\n```\n\n#### `clearRangeFlags()`\nClears manually set data ranges to allow metadata from new images to take precedence.\n\n```typescript\n// Clear any previously set ranges\nlayer.clearRangeFlags();\n\n// Now metadata from new images will be applied\nlayer.setImage('new-data-with-metadata.png');\n```\n\n#### `resetAutoRange()`\nResets the layer to automatic range detection mode.\n\n```typescript\n// Reset to automatic range detection\nlayer.resetAutoRange();\n```\n\n#### `setZoomDebounceInterval(interval)`\nConfigures the debounce interval for zoom interpolation. During zooming, bilinear interpolation will be used every N frames, with nearest neighbor used for all other frames.\n\n```typescript\n// Use bilinear every 5th frame during zoom (more performance, less quality)\nlayer.setZoomDebounceInterval(5);\n\n// Use bilinear every frame during zoom (smooth but slower)\nlayer.setZoomDebounceInterval(1);\n\n// Use bilinear every 3rd frame (default)\nlayer.setZoomDebounceInterval(3);\n```\n\n## Configuration\n\n### Scalar Layer Configuration\n\n```typescript\ninterface ScalarLayerConfig {\n  colors: ColorConfiguration;\n  dataRange?: [number, number] | [[number, number], [number, number]];\n  interpolation?: InterpolationMode;\n  blurSigma?: number;\n  contrast?: number;\n  valueScale?: ValueScaleMode;\n  gamma?: number;\n  logK?: number;\n}\n```\n\n### Particle Layer Configuration\n\n```typescript\ninterface ParticleLayerConfig {\n  dataRange?: [[number, number], [number, number]];\n  numParticles?: number;\n  fadeOpacity?: number;\n  speedFactor?: number;\n  dropRate?: number;\n  dropRateBump?: number;\n  colored?: boolean;\n  trailAlpha?: number;\n}\n```\n\n### Color Configuration\n\nColors can be specified in multiple formats and support up to **20 thresholds** for high-quality visualizations:\n\n```typescript\n// Object format\ncolors: [\n  { threshold: 0, value: '#000080' },\n  { threshold: 10, value: '#0080ff' },\n  { threshold: 20, value: '#00ff80' },\n]\n\n// Tuple format\ncolors: [\n  [0, '#000080'],\n  [10, '#0080ff'],\n  [20, '#00ff80'],\n]\n\n// Mixed format\ncolors: [\n  { threshold: 0, value: 'blue' },\n  [10, 'rgb(0, 128, 255)'],\n  { threshold: 20, value: 'hsl(120, 100%, 50%)' },\n]\n\n// Extended palette with 20 thresholds for high-quality visualization\ncolors: [\n  [-40, '#000033'], [-35, '#000066'], [-30, '#003399'], [-25, '#0066ff'],\n  [-20, '#00ccff'], [-15, '#00ffcc'], [-10, '#66ff66'], [-5, '#ccff00'],\n  [0, '#ffff00'], [5, '#ffcc00'], [10, '#ff9900'], [15, '#ff6600'],\n  [20, '#ff3300'], [25, '#ff0000'], [30, '#cc0000'], [35, '#990000'],\n  [40, '#660000'], [45, '#330000'], [50, '#000000']\n]\n```\n\n> **💡 Palette Optimization**: If you provide fewer than 20 colors, the library automatically pads the palette with the last color or interpolates between colors to create a smooth 20-color gradient. This ensures optimal visual quality regardless of your input palette size.\n\n### Data Range Configuration\n\nThe `dataRange` property supports both scalar and vector data:\n\n```typescript\n// Scalar data range\ndataRange: [0, 100]\n\n// Vector data range: [[uMin, uMax], [vMin, vMax]]\ndataRange: [[-50, 50], [-30, 30]]\n```\n\n> **💡 Metadata Auto-Detection**: If you don't specify `dataRange`, the library automatically extracts data ranges from image metadata (EXIF User Comment field). This eliminates the need to manually specify ranges for many datasets. See the [Image Formats and Metadata](#image-formats-and-metadata) section for details.\n\n## Image Formats and Metadata\n\n### Supported Image Formats\n\nThe library supports standard web image formats for both scalar and vector data:\n\n- **PNG** (recommended) - Lossless compression, supports transparency\n- **JPEG** - Lossy compression, smaller file sizes\n- **WebP** - Modern format with good compression and quality\n- **GIF** - Limited to 256 colors, not recommended for scientific data\n\n### Metadata Parsing\n\nThe library automatically extracts metadata from image files to improve visualization quality. This eliminates the need to manually specify data ranges in many cases.\n\n#### EXIF User Comment Metadata\n\nThe library reads metadata from the EXIF User Comment field, which can contain JSON-formatted data range information:\n\n```typescript\n// Example metadata in image EXIF User Comment\n{\n  \"min\": -40,\n  \"max\": 50,\n  \"uMin\": -100,\n  \"uMax\": 100,\n  \"vMin\": -80,\n  \"vMax\": 80,\n  \"units\": \"celsius\",\n  \"description\": \"Temperature data for North America\"\n}\n```\n\n#### Supported Metadata Fields\n\n| Field | Description | Example |\n|-------|-------------|---------|\n| `min`, `max` | Scalar data range | `{\"min\": -40, \"max\": 50}` |\n| `data_min`, `data_max` | Alternative scalar range keys | `{\"data_min\": -40, \"data_max\": 50}` |\n| `uMin`, `uMax` | U-component range for vector data | `{\"uMin\": -100, \"uMax\": 100}` |\n| `vMin`, `vMax` | V-component range for vector data | `{\"vMin\": -80, \"vMax\": 80}` |\n| `u_min`, `u_max` | Alternative U-component keys | `{\"u_min\": -100, \"u_max\": 100}` |\n| `v_min`, `v_max` | Alternative V-component keys | `{\"v_min\": -80, \"v_max\": 80}` |\n| `units` | Data units for reference | `{\"units\": \"celsius\"}` |\n| `description` | Human-readable description | `{\"description\": \"Wind vectors\"}` |\n\n#### Adding Metadata to Images\n\nYou can add metadata to your images using various tools:\n\n**Using ImageMagick:**\n```bash\n# Add metadata to PNG\nconvert input.png -set comment '{\"min\": -40, \"max\": 50, \"units\": \"celsius\"}' output.png\n\n# Add metadata to JPEG\nexiftool -comment='{\"min\": -40, \"max\": 50, \"units\": \"celsius\"}' input.jpg\n```\n\n**Using Python with Pillow:**\n```python\nfrom PIL import Image\nimport json\n\n# Load image\nimg = Image.open('temperature.png')\n\n# Add metadata\nmetadata = {\n    \"min\": -40,\n    \"max\": 50,\n    \"units\": \"celsius\",\n    \"description\": \"Temperature data\"\n}\n\n# Save with metadata\nimg.save('temperature_with_metadata.png', comment=json.dumps(metadata))\n```\n\n**Using Node.js with sharp:**\n```javascript\nconst sharp = require('sharp');\n\nawait sharp('input.png')\n  .png({ comment: JSON.stringify({\n    min: -40,\n    max: 50,\n    units: 'celsius'\n  })})\n  .toFile('output.png');\n```\n\n#### Metadata Priority\n\nWhen metadata is available, the library follows this priority order:\n\n1. **Manual configuration** - If you explicitly set `dataRange` in the config, it takes precedence\n2. **Image metadata** - EXIF User Comment data is used if no manual range is provided\n3. **Default values** - Falls back to `[0, 1]` for scalar data or `[[0, 1], [0, 1]]` for vector data\n\n#### Clearing Metadata Flags\n\nIf you want to force the library to use metadata from a new image (overriding previously set ranges), use the `clearRangeFlags()` method:\n\n```typescript\n// Clear any previously set ranges to allow metadata override\nlayer.clearRangeFlags();\n\n// Now when setting a new image, metadata ranges will be applied\nlayer.setImage('new-data-with-metadata.png');\n```\n\n### Image Requirements\n\n#### Scalar Data Images\n- **Single channel** (grayscale) or **multi-channel** (RGBA/RGBAA)\n- **Linear data** (not gamma-corrected) for best results\n- **Consistent bit depth** (8-bit, 16-bit, or 32-bit)\n- **Proper orientation** (top-left origin)\n\n#### Vector Data Images\n- **Two-channel format** where:\n  - **Red channel** = U-component (eastward velocity)\n  - **Green channel** = V-component (northward velocity)\n- **Blue channel** can be used for additional data or left as zero\n- **Alpha channel** for masking (optional)\n\n#### Recommended Image Specifications\n\n| Use Case | Format | Size | Bit Depth | Notes |\n|----------|--------|------|-----------|-------|\n| Weather data | PNG | 512x512 to 2048x2048 | 8-bit or 16-bit | Lossless, good compression |\n| Ocean currents | PNG | 1024x1024 to 4096x4096 | 16-bit | High precision needed |\n| Wind vectors | PNG | 256x256 to 1024x1024 | 8-bit | Good balance of quality/size |\n| Real-time feeds | JPEG | 512x512 to 1024x1024 | 8-bit | Faster loading, smaller size |\n\n### Performance Considerations\n\n- **Image size** affects memory usage and rendering performance\n- **PNG files** load slower but provide better quality for scientific data\n- **JPEG files** load faster but may introduce artifacts in data visualization\n- **Metadata parsing** adds minimal overhead and improves user experience\n\n## Comprehensive Examples\n\n### 1. Temperature Visualization\n\n```typescript\nawait scalarFlow.setLayer('temperature.png', {\n  colors: [\n    { threshold: -40, value: '#000033' },\n    { threshold: -20, value: '#000066' },\n    { threshold: -10, value: '#003399' },\n    { threshold: 0, value: '#0066ff' },\n    { threshold: 10, value: '#ffff00' },\n    { threshold: 20, value: '#ff9900' },\n    { threshold: 30, value: '#ff3300' },\n    { threshold: 40, value: '#ff0000' },\n  ],\n  dataRange: [-50, 50],\n  interpolation: InterpolationMode.Bicubic,\n  contrast: 1.2,\n  blurSigma: 0.5,\n});\n```\n\n### 2. Wind Flow Particles\n\n```typescript\nawait scalarFlow.setLayer('wind-vectors.png', {\n  dataRange: [[-100, 100], [-100, 100]], // u and v components in km/h\n  numParticles: 50000,\n  speedFactor: 0.8,\n  fadeOpacity: 0.98,\n  colored: true,\n  trailAlpha: 0.4,\n  dropRate: 0.008,\n  dropRateBump: 0.02,\n});\n```\n\n### 3. Ocean Current Visualization\n\n```typescript\n// First, show the current speed as a scalar field\nawait scalarFlow.setLayer('ocean-current-speed.png', {\n  colors: [\n    [0, 'rgba(0, 0, 80, 0.6)'],      // Slow - dark blue\n    [0.5, 'rgba(0, 100, 200, 0.7)'], // Medium - blue\n    [1.0, 'rgba(100, 200, 255, 0.8)'], // Fast - light blue\n    [2.0, 'rgba(255, 255, 0, 0.9)'],  // Very fast - yellow\n    [3.0, 'rgba(255, 100, 0, 1.0)'],  // Extreme - orange\n  ],\n  dataRange: [0, 3.5],\n  interpolation: InterpolationMode.Smoothstep,\n  contrast: 1.3,\n});\n\n// Then add particle flow\nawait scalarFlow.setLayer('ocean-current-vectors.png', {\n  dataRange: [[-3, 3], [-3, 3]], // Current velocity in m/s\n  numParticles: 30000,\n  speedFactor: 1.2,\n  colored: true,\n  trailAlpha: 0.3,\n  fadeOpacity: 0.99,\n}, 'particles');\n```\n\n### 4. Precipitation with Custom Scaling\n\n```typescript\nawait scalarFlow.setLayer('precipitation.png', {\n  colors: [\n    [0, 'transparent'],\n    [0.1, '#e6f3ff'],    // Very light rain\n    [1, '#99d6ff'],      // Light rain\n    [5, '#4da6ff'],      // Moderate rain\n    [10, '#0080ff'],     // Heavy rain\n    [25, '#0066cc'],     // Very heavy rain\n    [50, '#004499'],     // Extreme rain\n  ],\n  dataRange: [0, 60],\n  valueScale: ValueScaleMode.Log,\n  logK: 10,\n  interpolation: InterpolationMode.Lanczos,\n});\n```\n\n### 5. Multi-layered Weather Visualization\n\n```typescript\nconst weatherFlow = new ScalarFlow(map);\n\n// Temperature base layer\nawait weatherFlow.setLayer('temperature.png', {\n  colors: [\n    [-30, '#1a0d4d'],\n    [-20, '#2d1b69'],\n    [-10, '#4a3c8c'],\n    [0, '#6666cc'],\n    [10, '#8cb3d9'],\n    [20, '#b3d9cc'],\n    [30, '#d9e6b3'],\n    [40, '#f2f2b3'],\n    [50, '#ffcc99'],\n  ],\n  dataRange: [-35, 55],\n});\n\n// Wind vectors overlay\nawait weatherFlow.setLayer('wind.png', {\n  dataRange: [[-150, 150], [-150, 150]], // km/h\n  numParticles: 25000,\n  speedFactor: 0.6,\n  colored: false, // White particles for contrast\n  trailAlpha: 0.2,\n  fadeOpacity: 0.995,\n}, 'particles');\n\n// Update layers dynamically\nsetInterval(() => {\n  // Update with new weather data\n  weatherFlow.setLayer('temperature-updated.png', {\n    dataRange: [-35, 55],\n  });\n}, 300000); // Every 5 minutes\n```\n\n### 6. Interactive Controls\n\n```typescript\nclass WeatherVisualization {\n  private scalarFlow: ScalarFlow;\n  \n  constructor(map: maplibregl.Map) {\n    this.scalarFlow = new ScalarFlow(map);\n  }\n\n  async loadTemperatureData(url: string) {\n    await this.scalarFlow.setLayer(url, {\n      colors: this.getTemperatureColors(),\n      dataRange: [-40, 50],\n      interpolation: InterpolationMode.Auto,\n    });\n  }\n\n  updateInterpolation(mode: InterpolationMode) {\n    this.scalarFlow.updateLayer({\n      interpolation: mode,\n    }, 'scalar');\n  }\n\n  updateContrast(value: number) {\n    this.scalarFlow.updateLayer({\n      contrast: value,\n    }, 'scalar');\n  }\n\n  toggleParticles(enabled: boolean) {\n    if (enabled) {\n      this.scalarFlow.setLayer('wind-vectors.png', {\n        dataRange: [[-100, 100], [-100, 100]],\n        numParticles: 40000,\n        colored: true,\n      }, 'particles');\n    } else {\n      this.scalarFlow.removeLayer('particles');\n    }\n  }\n\n  private getTemperatureColors() {\n    return [\n      { threshold: -40, value: '#000033' },\n      { threshold: -30, value: '#000066' },\n      { threshold: -20, value: '#003399' },\n      { threshold: -10, value: '#0066ff' },\n      { threshold: 0, value: '#00ccff' },\n      { threshold: 10, value: '#66ff66' },\n      { threshold: 20, value: '#ccff00' },\n      { threshold: 30, value: '#ffff00' },\n      { threshold: 40, value: '#ff6600' },\n      { threshold: 50, value: '#ff0000' },\n    ];\n  }\n}\n\n// Usage\nconst weather = new WeatherVisualization(map);\nawait weather.loadTemperatureData('temperature.png');\n\n// Add UI controls\ndocument.getElementById('contrast-slider')?.addEventListener('input', (e) => {\n  weather.updateContrast(parseFloat((e.target as HTMLInputElement).value));\n});\n\ndocument.getElementById('particles-toggle')?.addEventListener('change', (e) => {\n  weather.toggleParticles((e.target as HTMLInputElement).checked);\n});\n```\n\n### 7. Metadata-Driven Visualization\n\nThis example shows how to leverage image metadata for automatic data range detection:\n\n```typescript\n// Create a layer without specifying dataRange - it will use metadata\nawait scalarFlow.setLayer('temperature-with-metadata.png', {\n  colors: [\n    [-40, '#000033'], // Deep blue (very cold)\n    [-20, '#003399'], // Blue (cold)\n    [0, '#00ccff'],   // Cyan (freezing)\n    [20, '#66ff66'],  // Green (cool)\n    [40, '#ffff00'],  // Yellow (warm)\n    [60, '#ff6600'],  // Orange (hot)\n  ],\n  // No dataRange specified - will be extracted from image metadata\n  interpolation: InterpolationMode.Bicubic,\n});\n\n// Later, if you want to force metadata from a new image\nconst layer = scalarFlow.scalarLayer; // Access the layer instance\nif (layer) {\n  // Clear any manually set ranges\n  layer.clearRangeFlags();\n  \n  // Now the new image's metadata will be applied\n  layer.setImage('updated-temperature-with-metadata.png');\n}\n```\n\n#### Creating Images with Metadata\n\nHere's how to prepare images with embedded metadata:\n\n**Python script for batch processing:**\n```python\nfrom PIL import Image\nimport json\nimport os\n\ndef add_metadata_to_image(input_path, output_path, metadata):\n    \"\"\"Add metadata to an image file.\"\"\"\n    with Image.open(input_path) as img:\n        # Convert metadata to JSON string\n        comment = json.dumps(metadata, separators=(',', ':'))\n        \n        # Save with metadata\n        if output_path.lower().endswith('.png'):\n            img.save(output_path, comment=comment)\n        elif output_path.lower().endswith('.jpg'):\n            img.save(output_path, comment=comment, quality=95)\n        else:\n            raise ValueError(\"Unsupported format\")\n\n# Example: Process temperature data\ntemperature_files = [\n    ('temp_jan.png', {'min': -40, 'max': 50, 'units': 'celsius', 'month': 'january'}),\n    ('temp_feb.png', {'min': -35, 'max': 55, 'units': 'celsius', 'month': 'february'}),\n    ('temp_mar.png', {'min': -20, 'max': 60, 'units': 'celsius', 'month': 'march'}),\n]\n\nfor filename, metadata in temperature_files:\n    input_file = f'raw/{filename}'\n    output_file = f'processed/{filename}'\n    \n    if os.path.exists(input_file):\n        add_metadata_to_image(input_file, output_file, metadata)\n        print(f'Processed {filename} with metadata')\n```\n\n**Node.js script for real-time data:**\n```javascript\nconst sharp = require('sharp');\nconst fs = require('fs');\n\nasync function createImageWithMetadata(data, metadata, outputPath) {\n  // Create image from data array\n  const imageBuffer = await sharp(data, {\n    raw: {\n      width: data[0].length,\n      height: data.length,\n      channels: 1\n    }\n  })\n  .png({ \n    comment: JSON.stringify(metadata),\n    compressionLevel: 9\n  })\n  .toBuffer();\n  \n  // Save to file\n  fs.writeFileSync(outputPath, imageBuffer);\n  console.log(`Created ${outputPath} with metadata`);\n}\n\n// Example: Create wind vector image with metadata\nconst windData = generateWindVectors(); // Your data generation function\nconst metadata = {\n  uMin: -100,\n  uMax: 100,\n  vMin: -80,\n  vMax: 80,\n  units: 'km/h',\n  timestamp: new Date().toISOString(),\n  description: 'Wind vectors for North America'\n};\n\nawait createImageWithMetadata(windData, metadata, 'wind-vectors.png');\n```\n\n### 8. High-Resolution Color Mapping with 20 Thresholds\n\nThis example demonstrates the library's capability to handle up to 20 color thresholds for extremely detailed visualizations:\n\n```typescript\n// Create a highly detailed temperature visualization with 20 thresholds\nawait scalarFlow.setLayer('high-res-temperature.png', {\n  colors: [\n    // Deep cold blues\n    [-40, '#000033'], [-35, '#000066'], [-30, '#003399'], [-25, '#0066ff'],\n    // Cold to freezing\n    [-20, '#00ccff'], [-15, '#00ffcc'], [-10, '#66ff66'], [-5, '#ccff00'],\n    // Freezing to cool\n    [0, '#ffff00'], [5, '#ffcc00'], [10, '#ff9900'], [15, '#ff6600'],\n    // Warm to hot\n    [20, '#ff3300'], [25, '#ff0000'], [30, '#cc0000'], [35, '#990000'],\n    // Very hot to extreme\n    [40, '#660000'], [45, '#330000'], [50, '#000000']\n  ],\n  // No dataRange specified - will use image metadata\n  interpolation: InterpolationMode.Lanczos,\n  contrast: 1.1,\n  blurSigma: 0.3,\n});\n\n// Update with real-time data using the same detailed palette\nsetInterval(async () => {\n  await scalarFlow.setLayer('updated-high-res-temp.png', {\n    // Keep the same colors for consistency\n    colors: [\n      [-40, '#000033'], [-35, '#000066'], [-30, '#003399'], [-25, '#0066ff'],\n      [-20, '#00ccff'], [-15, '#00ffcc'], [-10, '#66ff66'], [-5, '#ccff00'],\n      [0, '#ffff00'], [5, '#ffcc00'], [10, '#ff9900'], [15, '#ff6600'],\n      [20, '#ff3300'], [25, '#ff0000'], [30, '#cc0000'], [35, '#990000'],\n      [40, '#660000'], [45, '#330000'], [50, '#000000']\n    ]\n  });\n}, 60000); // Update every minute\n```\n\n## Enums and Constants\n\n### InterpolationMode\n```typescript\nenum InterpolationMode {\n  Auto = 0,        // Zoom-dependent interpolation (default)\n  Nearest = 1,     // Nearest neighbor (pixelated)\n  Bilinear = 2,    // Bilinear interpolation\n  Bicubic = 3,     // Bicubic interpolation\n  Lanczos = 4,     // Lanczos resampling\n  Smoothstep = 5,  // Smoothstep interpolation\n  Rbf = 6,         // Radial basis function\n}\n```\n\n### ValueScaleMode\n```typescript\nenum ValueScaleMode {\n  Linear = 0,      // Linear scaling (default)\n  Gamma = 1,       // Gamma correction\n  Log = 2,         // Logarithmic scaling\n}\n```\n\n## Performance Tips\n\n1. **Particle Count**: Start with 25,000-50,000 particles and adjust based on performance\n2. **Interpolation**: Use `Auto` mode for best quality/performance balance\n3. **Interaction Performance**: The library automatically optimizes during map interactions:\n   - **Scalar layers**: Use advanced temporary texture system during zoom (creates high-quality textures periodically, uses nearest neighbor from current zoom level)\n   - **Particle layers**: Completely hidden during zoom and move for maximum performance\n4. **Updates**: Use `updateLayer()` instead of recreating layers for better performance\n5. **Temporary Texture System**: During zoom operations, the library creates temporary high-quality textures:\n   - **Periodic Creation**: New textures are generated every few frames or when zoom level changes significantly\n   - **Quality Preservation**: Uses original interpolation settings for temporary textures\n   - **Memory Efficient**: Temporary textures are automatically cleaned up when zoom ends\n   - **Smooth Experience**: Nearest neighbor sampling from current zoom level provides smooth interaction\n\n## Browser Support\n\n- Chrome 60+\n- Firefox 60+\n- Safari 12+\n- Edge 79+\n\nRequires WebGL support.\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Contributing\n\nContributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.\n\n## Support\n\nFor questions, issues, or feature requests, please visit our [GitHub Issues](https://github.com/madmike/maplibre-scalar-flow/issues) page.","readmeFilename":"README.md"}