nexrad_render/color.rs
1//! Color scales for radar data visualization.
2//!
3//! This module provides types for mapping radar moment values to colors.
4//! The primary type is [`DiscreteColorScale`], which maps value ranges to
5//! specific colors based on threshold levels. For rendering, the scale is
6//! converted to a [`ColorLookupTable`] which provides O(1) color lookups.
7
8/// An RGBA color with components in the range 0.0 to 1.0.
9///
10/// This type is used for defining color scales. Colors are converted to
11/// 8-bit RGBA values during rendering.
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Color {
14 /// Red component (0.0 to 1.0)
15 pub r: f64,
16 /// Green component (0.0 to 1.0)
17 pub g: f64,
18 /// Blue component (0.0 to 1.0)
19 pub b: f64,
20 /// Alpha component (0.0 to 1.0)
21 pub a: f64,
22}
23
24impl Color {
25 /// Creates a new color from RGB components (alpha defaults to 1.0).
26 ///
27 /// Components should be in the range 0.0 to 1.0.
28 pub const fn rgb(r: f64, g: f64, b: f64) -> Self {
29 Self { r, g, b, a: 1.0 }
30 }
31
32 /// Creates a new color from RGBA components.
33 ///
34 /// Components should be in the range 0.0 to 1.0.
35 pub const fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
36 Self { r, g, b, a }
37 }
38
39 /// Converts the color to 8-bit RGBA values.
40 pub fn to_rgba8(&self) -> [u8; 4] {
41 [
42 (self.r * 255.0) as u8,
43 (self.g * 255.0) as u8,
44 (self.b * 255.0) as u8,
45 (self.a * 255.0) as u8,
46 ]
47 }
48
49 /// Black color.
50 pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
51
52 /// White color.
53 pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
54
55 /// Transparent (fully transparent black).
56 pub const TRANSPARENT: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
57}
58
59/// A single level in a discrete color scale.
60///
61/// Represents a threshold value and its associated color. Values at or above
62/// this threshold (but below the next higher threshold) will be rendered with
63/// this color.
64#[derive(Debug, Clone)]
65pub struct ColorScaleLevel {
66 value: f32,
67 color: Color,
68}
69
70impl ColorScaleLevel {
71 /// Creates a new color scale level.
72 ///
73 /// # Arguments
74 ///
75 /// * `value` - The threshold value
76 /// * `color` - The color to use for values at or above this threshold
77 pub fn new(value: f32, color: Color) -> Self {
78 Self { value, color }
79 }
80}
81
82/// A discrete color scale that maps value ranges to colors.
83///
84/// The scale works by finding the highest threshold that the input value
85/// exceeds, and returning the corresponding color. Levels are automatically
86/// sorted from highest to lowest threshold during construction.
87///
88/// # Example
89///
90/// ```
91/// use nexrad_render::{ColorScaleLevel, DiscreteColorScale, Color};
92///
93/// let scale = DiscreteColorScale::new(vec![
94/// ColorScaleLevel::new(0.0, Color::BLACK),
95/// ColorScaleLevel::new(30.0, Color::rgb(0.0, 1.0, 0.0)),
96/// ColorScaleLevel::new(50.0, Color::rgb(1.0, 0.0, 0.0)),
97/// ]);
98///
99/// // Values >= 50 return red, >= 30 return green, >= 0 return black
100/// ```
101#[derive(Debug, Clone)]
102pub struct DiscreteColorScale {
103 levels: Vec<ColorScaleLevel>,
104}
105
106impl DiscreteColorScale {
107 /// Creates a new discrete color scale from the given levels.
108 ///
109 /// Levels are automatically sorted from highest to lowest threshold.
110 pub fn new(mut levels: Vec<ColorScaleLevel>) -> Self {
111 levels.sort_by(|a, b| b.value.total_cmp(&a.value));
112 Self { levels }
113 }
114
115 /// Returns the color for the given value.
116 ///
117 /// Finds the highest threshold that the value exceeds and returns its color.
118 /// If the value is below all thresholds, returns the color of the lowest threshold.
119 pub fn color(&self, value: f32) -> Color {
120 let mut color = Color::BLACK;
121
122 for level in &self.levels {
123 if value >= level.value {
124 return level.color;
125 }
126
127 color = level.color;
128 }
129
130 color
131 }
132
133 /// Returns the levels in this color scale (sorted highest to lowest).
134 pub fn levels(&self) -> &[ColorScaleLevel] {
135 &self.levels
136 }
137}
138
139/// A color stop for a continuous color scale.
140///
141/// Defines a value-to-color control point. Colors are linearly interpolated
142/// between stops.
143#[derive(Debug, Clone)]
144pub struct ColorStop {
145 /// The value at this stop.
146 pub value: f32,
147 /// The color at this stop.
148 pub color: Color,
149}
150
151impl ColorStop {
152 /// Creates a new color stop.
153 pub fn new(value: f32, color: Color) -> Self {
154 Self { value, color }
155 }
156}
157
158/// A continuous color scale that linearly interpolates between color stops.
159///
160/// Unlike [`DiscreteColorScale`], this scale produces smooth color gradients
161/// between control points. This is useful for products where smooth transitions
162/// are more informative than discrete steps.
163///
164/// # Example
165///
166/// ```
167/// use nexrad_render::{ColorStop, ContinuousColorScale, Color};
168///
169/// let scale = ContinuousColorScale::new(vec![
170/// ColorStop::new(0.0, Color::rgb(0.0, 0.0, 1.0)), // Blue at 0
171/// ColorStop::new(50.0, Color::rgb(0.0, 1.0, 0.0)), // Green at 50
172/// ColorStop::new(100.0, Color::rgb(1.0, 0.0, 0.0)), // Red at 100
173/// ]);
174///
175/// // Value 25 produces a blue-green blend
176/// let color = scale.color(25.0);
177/// ```
178#[derive(Debug, Clone)]
179pub struct ContinuousColorScale {
180 stops: Vec<ColorStop>,
181}
182
183impl ContinuousColorScale {
184 /// Creates a new continuous color scale from color stops.
185 ///
186 /// Stops are automatically sorted by value (lowest to highest).
187 pub fn new(mut stops: Vec<ColorStop>) -> Self {
188 stops.sort_by(|a, b| a.value.total_cmp(&b.value));
189 Self { stops }
190 }
191
192 /// Returns the linearly interpolated color for the given value.
193 ///
194 /// Values below the lowest stop get the lowest stop's color.
195 /// Values above the highest stop get the highest stop's color.
196 pub fn color(&self, value: f32) -> Color {
197 if self.stops.is_empty() {
198 return Color::BLACK;
199 }
200
201 if value <= self.stops[0].value {
202 return self.stops[0].color;
203 }
204
205 let last = self.stops.len() - 1;
206 if value >= self.stops[last].value {
207 return self.stops[last].color;
208 }
209
210 // Find the two stops that bracket this value
211 for i in 0..last {
212 let low = &self.stops[i];
213 let high = &self.stops[i + 1];
214
215 if value >= low.value && value <= high.value {
216 let range = high.value - low.value;
217 if range == 0.0 {
218 return low.color;
219 }
220 let t = ((value - low.value) / range) as f64;
221 return Color::rgba(
222 low.color.r + (high.color.r - low.color.r) * t,
223 low.color.g + (high.color.g - low.color.g) * t,
224 low.color.b + (high.color.b - low.color.b) * t,
225 low.color.a + (high.color.a - low.color.a) * t,
226 );
227 }
228 }
229
230 self.stops[last].color
231 }
232
233 /// Returns the stops in this color scale (sorted lowest to highest).
234 pub fn stops(&self) -> &[ColorStop] {
235 &self.stops
236 }
237}
238
239/// A color scale that can be either discrete or continuous.
240///
241/// This enum allows rendering functions to accept either type of color scale.
242#[derive(Debug, Clone)]
243pub enum ColorScale {
244 /// A discrete step-function color scale.
245 Discrete(DiscreteColorScale),
246 /// A continuous linear-interpolation color scale.
247 Continuous(ContinuousColorScale),
248}
249
250impl ColorScale {
251 /// Returns the color for the given value.
252 pub fn color(&self, value: f32) -> Color {
253 match self {
254 ColorScale::Discrete(scale) => scale.color(value),
255 ColorScale::Continuous(scale) => scale.color(value),
256 }
257 }
258}
259
260impl From<DiscreteColorScale> for ColorScale {
261 fn from(scale: DiscreteColorScale) -> Self {
262 ColorScale::Discrete(scale)
263 }
264}
265
266impl From<ContinuousColorScale> for ColorScale {
267 fn from(scale: ContinuousColorScale) -> Self {
268 ColorScale::Continuous(scale)
269 }
270}
271
272/// A pre-computed lookup table for O(1) color lookups.
273///
274/// This table maps a range of values to RGBA colors using a fixed-size array.
275/// It is created from a [`DiscreteColorScale`] and provides fast color lookups
276/// during rendering.
277///
278/// # Example
279///
280/// ```
281/// use nexrad_render::{ColorLookupTable, nws_reflectivity_scale};
282///
283/// let scale = nws_reflectivity_scale();
284/// let lut = ColorLookupTable::from_scale(&scale, -32.0, 95.0, 256);
285///
286/// // O(1) lookup returning [R, G, B, A] bytes
287/// let color = lut.color(45.0);
288/// ```
289#[derive(Debug, Clone)]
290pub struct ColorLookupTable {
291 /// RGBA color values indexed by quantized input value.
292 table: Vec<[u8; 4]>,
293 /// Minimum value in the mapped range.
294 min_value: f32,
295 /// Value range (max - min).
296 range: f32,
297}
298
299impl ColorLookupTable {
300 /// Creates a lookup table from a discrete color scale.
301 ///
302 /// # Arguments
303 ///
304 /// * `scale` - The color scale to sample from
305 /// * `min_value` - The minimum value to map
306 /// * `max_value` - The maximum value to map
307 /// * `size` - The number of entries in the lookup table (256 recommended)
308 ///
309 /// Values outside the min/max range will be clamped to the nearest entry.
310 pub fn from_scale(
311 scale: &DiscreteColorScale,
312 min_value: f32,
313 max_value: f32,
314 size: usize,
315 ) -> Self {
316 let range = max_value - min_value;
317 let mut table = Vec::with_capacity(size);
318
319 for i in 0..size {
320 let value = min_value + (i as f32 / (size - 1) as f32) * range;
321 let color = scale.color(value);
322 table.push(color.to_rgba8());
323 }
324
325 Self {
326 table,
327 min_value,
328 range,
329 }
330 }
331
332 /// Creates a lookup table from any [`ColorScale`] (discrete or continuous).
333 ///
334 /// This is the preferred way to create a LUT for the new rendering entry points.
335 pub fn from_color_scale(
336 scale: &ColorScale,
337 min_value: f32,
338 max_value: f32,
339 size: usize,
340 ) -> Self {
341 let range = max_value - min_value;
342 let mut table = Vec::with_capacity(size);
343
344 for i in 0..size {
345 let value = min_value + (i as f32 / (size - 1) as f32) * range;
346 let color = scale.color(value);
347 table.push(color.to_rgba8());
348 }
349
350 Self {
351 table,
352 min_value,
353 range,
354 }
355 }
356
357 /// Returns the RGBA color for the given value.
358 ///
359 /// This is an O(1) operation using direct array indexing.
360 #[inline]
361 pub fn color(&self, value: f32) -> [u8; 4] {
362 let normalized = (value - self.min_value) / self.range;
363 let index = (normalized * (self.table.len() - 1) as f32) as usize;
364 let index = index.min(self.table.len() - 1);
365 self.table[index]
366 }
367}
368
369/// Returns the standard NWS (National Weather Service) reflectivity color scale.
370///
371/// This scale uses colors commonly seen in weather radar displays, ranging
372/// from cyan/blue for light precipitation to magenta/white for extreme values.
373///
374/// | dBZ Range | Color | Meaning |
375/// |-----------|-------|---------|
376/// | 0-5 | Black | Below detection threshold |
377/// | 5-20 | Cyan/Blue | Light precipitation |
378/// | 20-35 | Green | Light to moderate precipitation |
379/// | 35-50 | Yellow/Orange | Moderate to heavy precipitation |
380/// | 50-65 | Red/Magenta | Heavy precipitation, possible hail |
381/// | 65+ | Purple/White | Extreme precipitation, likely hail |
382pub fn nws_reflectivity_scale() -> DiscreteColorScale {
383 DiscreteColorScale::new(vec![
384 ColorScaleLevel::new(0.0, Color::rgb(0.0000, 0.0000, 0.0000)),
385 ColorScaleLevel::new(5.0, Color::rgb(0.0000, 1.0000, 1.0000)),
386 ColorScaleLevel::new(10.0, Color::rgb(0.5294, 0.8078, 0.9216)),
387 ColorScaleLevel::new(15.0, Color::rgb(0.0000, 0.0000, 1.0000)),
388 ColorScaleLevel::new(20.0, Color::rgb(0.0000, 1.0000, 0.0000)),
389 ColorScaleLevel::new(25.0, Color::rgb(0.1961, 0.8039, 0.1961)),
390 ColorScaleLevel::new(30.0, Color::rgb(0.1333, 0.5451, 0.1333)),
391 ColorScaleLevel::new(35.0, Color::rgb(0.9333, 0.9333, 0.0000)),
392 ColorScaleLevel::new(40.0, Color::rgb(0.9333, 0.8627, 0.5098)),
393 ColorScaleLevel::new(45.0, Color::rgb(0.9333, 0.4627, 0.1294)),
394 ColorScaleLevel::new(50.0, Color::rgb(1.0000, 0.1882, 0.1882)),
395 ColorScaleLevel::new(55.0, Color::rgb(0.6902, 0.1882, 0.3765)),
396 ColorScaleLevel::new(60.0, Color::rgb(0.6902, 0.1882, 0.3765)),
397 ColorScaleLevel::new(65.0, Color::rgb(0.7294, 0.3333, 0.8275)),
398 ColorScaleLevel::new(70.0, Color::rgb(1.0000, 0.0000, 1.0000)),
399 ColorScaleLevel::new(75.0, Color::rgb(1.0000, 1.0000, 1.0000)),
400 ])
401}
402
403/// Returns a color scale for radial velocity data.
404///
405/// This divergent scale uses green for motion toward the radar (negative values)
406/// and red for motion away from the radar (positive values), with gray near zero.
407/// Range: -64 to +64 m/s (standard precipitation mode Doppler velocity).
408///
409/// | Velocity (m/s) | Color | Meaning |
410/// |----------------|-------|---------|
411/// | -64 to -48 | Dark Green | Strong inbound |
412/// | -48 to -32 | Green | Moderate inbound |
413/// | -32 to -16 | Light Green | Light inbound |
414/// | -16 to -4 | Pale Green | Very light inbound |
415/// | -4 to +4 | Gray | Near zero / RF |
416/// | +4 to +16 | Pale Red | Very light outbound |
417/// | +16 to +32 | Light Red/Pink | Light outbound |
418/// | +32 to +48 | Red | Moderate outbound |
419/// | +48 to +64 | Dark Red | Strong outbound |
420pub fn velocity_scale() -> DiscreteColorScale {
421 DiscreteColorScale::new(vec![
422 // Strong inbound (toward radar) - dark green
423 ColorScaleLevel::new(-64.0, Color::rgb(0.0000, 0.3922, 0.0000)),
424 ColorScaleLevel::new(-48.0, Color::rgb(0.0000, 0.5451, 0.0000)),
425 ColorScaleLevel::new(-32.0, Color::rgb(0.0000, 0.8039, 0.0000)),
426 ColorScaleLevel::new(-16.0, Color::rgb(0.5647, 0.9333, 0.5647)),
427 // Near zero - gray
428 ColorScaleLevel::new(-4.0, Color::rgb(0.6627, 0.6627, 0.6627)),
429 ColorScaleLevel::new(4.0, Color::rgb(0.6627, 0.6627, 0.6627)),
430 // Outbound (away from radar) - reds
431 ColorScaleLevel::new(16.0, Color::rgb(1.0000, 0.7529, 0.7961)),
432 ColorScaleLevel::new(32.0, Color::rgb(1.0000, 0.4118, 0.4118)),
433 ColorScaleLevel::new(48.0, Color::rgb(0.8039, 0.0000, 0.0000)),
434 ColorScaleLevel::new(64.0, Color::rgb(0.5451, 0.0000, 0.0000)),
435 ])
436}
437
438/// Returns a color scale for spectrum width data.
439///
440/// This sequential scale ranges from cool colors (low turbulence) to warm colors
441/// (high turbulence). Range: 0 to 30 m/s.
442///
443/// | Width (m/s) | Color | Meaning |
444/// |-------------|-------|---------|
445/// | 0-4 | Gray | Very low turbulence |
446/// | 4-8 | Blue | Low turbulence |
447/// | 8-12 | Cyan | Light turbulence |
448/// | 12-16 | Green | Moderate turbulence |
449/// | 16-20 | Yellow | Moderate-high turbulence |
450/// | 20-25 | Orange | High turbulence |
451/// | 25-30 | Red | Very high turbulence |
452pub fn spectrum_width_scale() -> DiscreteColorScale {
453 DiscreteColorScale::new(vec![
454 ColorScaleLevel::new(0.0, Color::rgb(0.5020, 0.5020, 0.5020)),
455 ColorScaleLevel::new(4.0, Color::rgb(0.0000, 0.0000, 0.8039)),
456 ColorScaleLevel::new(8.0, Color::rgb(0.0000, 0.8039, 0.8039)),
457 ColorScaleLevel::new(12.0, Color::rgb(0.0000, 0.8039, 0.0000)),
458 ColorScaleLevel::new(16.0, Color::rgb(0.9333, 0.9333, 0.0000)),
459 ColorScaleLevel::new(20.0, Color::rgb(1.0000, 0.6471, 0.0000)),
460 ColorScaleLevel::new(25.0, Color::rgb(1.0000, 0.0000, 0.0000)),
461 ])
462}
463
464/// Returns a color scale for differential reflectivity (ZDR) data.
465///
466/// This divergent scale shows negative values (vertically-oriented particles) in
467/// blue/purple, near-zero in gray, and positive values (horizontally-oriented
468/// particles like large raindrops) in yellow/orange/red.
469/// Range: -2 to +6 dB.
470///
471/// | ZDR (dB) | Color | Meaning |
472/// |----------|-------|---------|
473/// | -2 to -1 | Purple | Vertically oriented |
474/// | -1 to 0 | Blue | Slightly vertical |
475/// | 0 to 0.5 | Gray | Spherical particles |
476/// | 0.5 to 1.5 | Light Green | Slightly oblate |
477/// | 1.5 to 2.5 | Yellow | Oblate drops |
478/// | 2.5 to 4 | Orange | Large oblate drops |
479/// | 4 to 6 | Red | Very large drops/hail |
480pub fn differential_reflectivity_scale() -> DiscreteColorScale {
481 DiscreteColorScale::new(vec![
482 // Negative (vertically oriented)
483 ColorScaleLevel::new(-2.0, Color::rgb(0.5020, 0.0000, 0.5020)),
484 ColorScaleLevel::new(-1.0, Color::rgb(0.0000, 0.0000, 0.8039)),
485 // Near zero (spherical)
486 ColorScaleLevel::new(0.0, Color::rgb(0.6627, 0.6627, 0.6627)),
487 // Positive (horizontally oriented / oblate)
488 ColorScaleLevel::new(0.5, Color::rgb(0.5647, 0.9333, 0.5647)),
489 ColorScaleLevel::new(1.5, Color::rgb(0.9333, 0.9333, 0.0000)),
490 ColorScaleLevel::new(2.5, Color::rgb(1.0000, 0.6471, 0.0000)),
491 ColorScaleLevel::new(4.0, Color::rgb(1.0000, 0.0000, 0.0000)),
492 ])
493}
494
495/// Returns a color scale for correlation coefficient (CC/RhoHV) data.
496///
497/// This sequential scale emphasizes high values (0.9-1.0) which indicate
498/// meteorological targets. Lower values may indicate non-meteorological
499/// echoes, mixed precipitation, or tornadic debris.
500/// Range: 0.0 to 1.0.
501///
502/// | CC | Color | Meaning |
503/// |----|-------|---------|
504/// | 0.0-0.7 | Purple/Blue | Non-met or debris |
505/// | 0.7-0.85 | Cyan/Teal | Mixed phase/melting |
506/// | 0.85-0.92 | Light Green | Possible hail/graupel |
507/// | 0.92-0.96 | Green | Rain/snow mix |
508/// | 0.96-0.98 | Yellow | Pure rain or snow |
509/// | 0.98-1.0 | White/Light Gray | Uniform precipitation |
510pub fn correlation_coefficient_scale() -> DiscreteColorScale {
511 DiscreteColorScale::new(vec![
512 // Low CC - non-meteorological or debris
513 ColorScaleLevel::new(0.0, Color::rgb(0.0000, 0.0000, 0.0000)),
514 ColorScaleLevel::new(0.2, Color::rgb(0.3922, 0.0000, 0.5882)),
515 ColorScaleLevel::new(0.5, Color::rgb(0.0000, 0.0000, 0.8039)),
516 ColorScaleLevel::new(0.7, Color::rgb(0.0000, 0.5451, 0.5451)),
517 // Medium CC - mixed precipitation
518 ColorScaleLevel::new(0.85, Color::rgb(0.0000, 0.8039, 0.4000)),
519 ColorScaleLevel::new(0.92, Color::rgb(0.0000, 0.8039, 0.0000)),
520 // High CC - pure meteorological
521 ColorScaleLevel::new(0.96, Color::rgb(0.9333, 0.9333, 0.0000)),
522 ColorScaleLevel::new(0.98, Color::rgb(0.9020, 0.9020, 0.9020)),
523 ])
524}
525
526/// Returns a color scale for differential phase (PhiDP) data.
527///
528/// This sequential scale covers the full 0-360 degree range. Differential
529/// phase increases with propagation through precipitation.
530/// Range: 0 to 360 degrees.
531///
532/// | PhiDP (deg) | Color |
533/// |-------------|-------|
534/// | 0-45 | Purple |
535/// | 45-90 | Blue |
536/// | 90-135 | Cyan |
537/// | 135-180 | Green |
538/// | 180-225 | Yellow |
539/// | 225-270 | Orange |
540/// | 270-315 | Red |
541/// | 315-360 | Magenta |
542pub fn differential_phase_scale() -> DiscreteColorScale {
543 DiscreteColorScale::new(vec![
544 ColorScaleLevel::new(0.0, Color::rgb(0.5020, 0.0000, 0.5020)),
545 ColorScaleLevel::new(45.0, Color::rgb(0.0000, 0.0000, 0.8039)),
546 ColorScaleLevel::new(90.0, Color::rgb(0.0000, 0.8039, 0.8039)),
547 ColorScaleLevel::new(135.0, Color::rgb(0.0000, 0.8039, 0.0000)),
548 ColorScaleLevel::new(180.0, Color::rgb(0.9333, 0.9333, 0.0000)),
549 ColorScaleLevel::new(225.0, Color::rgb(1.0000, 0.6471, 0.0000)),
550 ColorScaleLevel::new(270.0, Color::rgb(1.0000, 0.0000, 0.0000)),
551 ColorScaleLevel::new(315.0, Color::rgb(1.0000, 0.0000, 1.0000)),
552 ])
553}
554
555/// Returns a color scale for clutter filter power (CFP) data.
556///
557/// This divergent scale centers around 0 dB, showing negative values
558/// (filtered reflectivity lower than unfiltered) in blues and positive
559/// values in reds.
560/// Default range: -20 to +20 dB.
561///
562/// | CFP (dB) | Color | Meaning |
563/// |----------|-------|---------|
564/// | -20 to -10 | Dark Blue | Strong filtering |
565/// | -10 to -5 | Blue | Moderate filtering |
566/// | -5 to -1 | Light Blue | Light filtering |
567/// | -1 to +1 | Gray | Near zero |
568/// | +1 to +5 | Light Red | Slight increase |
569/// | +5 to +10 | Red | Moderate increase |
570/// | +10 to +20 | Dark Red | Strong increase |
571pub fn clutter_filter_power_scale() -> DiscreteColorScale {
572 DiscreteColorScale::new(vec![
573 ColorScaleLevel::new(-20.0, Color::rgb(0.0000, 0.0000, 0.5451)),
574 ColorScaleLevel::new(-10.0, Color::rgb(0.0000, 0.0000, 0.8039)),
575 ColorScaleLevel::new(-5.0, Color::rgb(0.6784, 0.8471, 0.9020)),
576 ColorScaleLevel::new(-1.0, Color::rgb(0.6627, 0.6627, 0.6627)),
577 ColorScaleLevel::new(1.0, Color::rgb(0.6627, 0.6627, 0.6627)),
578 ColorScaleLevel::new(5.0, Color::rgb(1.0000, 0.7529, 0.7961)),
579 ColorScaleLevel::new(10.0, Color::rgb(1.0000, 0.4118, 0.4118)),
580 ColorScaleLevel::new(20.0, Color::rgb(0.8039, 0.0000, 0.0000)),
581 ])
582}