(undefined);
const [showCustomDialog, setShowCustomDialog] = useState(false);
const [cmapOptions, setCmapOptions] = useState(CMAP_OPTIONS);
+ /**
+ * Fetch or retrieve from cache the cmap image when user changes cmap selection
+ */
+ useEffect(() => {
+ async function getImage() {
+ const image = await getCmapImage(cmap);
+ setCmapImage(image);
+ }
+ getImage();
+ }, [cmap]);
+
/** Processes the histogram data so that it's ready to create the polygon in ColorMapHistogram */
const processedHistogramData = useMemo(() => {
if (histogramData) {
@@ -122,27 +132,6 @@ export function ColorMapControls(props: ColorMapControlsProps) {
}
}, [histogramData, isLogScale, isAbsoluteValue]);
- /** Fetch and set the URL to the color map image if/when cmap or its setter changes */
- useEffect(() => {
- async function getCmapImage() {
- const image = await fetch(`${SERVICE_URL}/histograms/${cmap}.png`);
- setCmapImage(image.url);
- }
- getCmapImage();
- }, [cmap, setCmapImage]);
-
- /** Fetch and set the histogram data if/when the active layer and/or setHistogramData changes */
- useEffect(() => {
- async function getHistogramData() {
- const response = await fetch(
- `${SERVICE_URL}/histograms/data/${activeBaselayerId}`
- );
- const data: HistogramResponse = await response.json();
- setHistogramData(data);
- }
- getHistogramData();
- }, [activeBaselayerId, setHistogramData]);
-
/** Determines the min, max, and step attributes for the range slider. Min and max are
found by comparing the user-controlled (or default) 'values' to the histogram's 'edges',
which allows for the range slider to resize itself according to min/max values that may
diff --git a/src/components/ColorMapHistogram.tsx b/src/components/ColorMapHistogram.tsx
index daf3a39..29391f1 100644
--- a/src/components/ColorMapHistogram.tsx
+++ b/src/components/ColorMapHistogram.tsx
@@ -1,14 +1,14 @@
import { useMemo } from 'react';
import { PointArray } from '@svgdotjs/svg.js';
-import { HistogramResponse } from '../types/maps';
+import { HistogramData } from '../types/maps';
import {
HISTOGRAM_SIZE_X,
HISTOGRAM_SIZE_Y,
} from '../configs/cmapControlSettings';
type Props = {
- /** The data from the histogram response */
- data?: HistogramResponse;
+ /** The applicable histogram data from the histogram response */
+ data?: HistogramData;
/** The user's min and max values for the range slider to use as edgeStart or edgeEnd
in the event the user sets these beyond the histogram's min or max edges */
userMinAndMaxValues: { min: number; max: number };
diff --git a/src/components/ColorMapSlider.tsx b/src/components/ColorMapSlider.tsx
index 4bb2b4f..46580ba 100644
--- a/src/components/ColorMapSlider.tsx
+++ b/src/components/ColorMapSlider.tsx
@@ -8,6 +8,8 @@ import {
import { ColorMapControlsProps } from './ColorMapControls';
import './styles/color-map-controls.css';
+let THUMB_KEY_COUNTER = 1;
+
interface ColorMapSliderProps
extends Omit<
ColorMapControlsProps,
@@ -16,6 +18,7 @@ interface ColorMapSliderProps
| 'onCmapChange'
| 'onLogScaleChange'
| 'onAbsoluteValueChange'
+ | 'histogramData'
> {
/** The URL to the color map image */
cmapImage?: string;
@@ -301,7 +304,7 @@ export function ColorMapSlider(props: ColorMapSliderProps) {
renderThumb={({ props, isDragged }) => (
t.get('id') === 'baselayer-' + activeBaselayer!.layer_id
)!;
+ const activeLayerSource = activeLayer.getSource();
+ activeLayerSource?.setUrl(
+ `${SERVICE_URL}/maps/${activeBaselayer.layer_id}/{z}/{-y}/{x}/tile.png?cmap=${activeBaselayer.cmap}&vmin=${activeBaselayer.isLogScale ? Math.pow(10, activeBaselayer.vmin!) : activeBaselayer.vmin}&vmax=${activeBaselayer.isLogScale ? Math.pow(10, activeBaselayer.vmax!) : activeBaselayer.vmax}&flip=${flipTiles}&log_norm=${activeBaselayer.isLogScale}&abs=${activeBaselayer.isAbsoluteValue}`
+ );
mapRef.current.addLayer(activeLayer);
} else {
const externalBaselayer = EXTERNAL_BASELAYERS.find(
@@ -406,7 +442,7 @@ export function OpenLayersMap({
mapRef.current.addLayer(activeLayer);
}
}
- }, [activeBaselayer, tileLayers, externalTileLayers]);
+ }, [activeBaselayer, tileLayers, externalTileLayers, flipTiles]);
/**
* Add keyboard support for switching baselayers
diff --git a/src/reducers/baselayersReducer.ts b/src/reducers/baselayersReducer.ts
index 24c043e..be08aaa 100644
--- a/src/reducers/baselayersReducer.ts
+++ b/src/reducers/baselayersReducer.ts
@@ -1,6 +1,7 @@
import {
BaselayersState,
ExternalBaselayer,
+ HistogramResponse,
InternalBaselayer,
} from '../types/maps';
import { safeLog } from '../utils/numberUtils';
@@ -20,6 +21,7 @@ export function assertInternalBaselayer(
export const initialBaselayersState: BaselayersState = {
activeBaselayer: undefined,
internalBaselayers: undefined,
+ histogramData: undefined,
};
export const CHANGE_CMAP_TYPE = 'CHANGE_CMAP';
@@ -57,11 +59,13 @@ type ChangeCmapValuesAction = {
type ChangeBaselayerAction = {
type: typeof CHANGE_BASELAYER;
newBaselayer: InternalBaselayer | ExternalBaselayer;
+ histogramData?: HistogramResponse;
};
type SetBaselayersAction = {
type: typeof SET_BASELAYERS_STATE;
internalBaselayers: InternalBaselayer[];
+ histogramData: HistogramResponse;
};
export type Action =
@@ -78,6 +82,7 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
return {
internalBaselayers: action.internalBaselayers,
activeBaselayer: action.internalBaselayers[0],
+ histogramData: action.histogramData,
};
}
case 'CHANGE_CMAP': {
@@ -87,6 +92,7 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
cmap: action.cmap,
};
return {
+ ...state,
internalBaselayers: state.internalBaselayers?.map((layer) => {
if (
layer.layer_id ===
@@ -109,6 +115,10 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
if (assertInternalBaselayer(action.activeBaselayer)) {
const { activeBaselayer, isLogScale } = action;
const { vmin, vmax } = action.activeBaselayer;
+
+ // Return early if vmin or vmax are undefined
+ if (vmin === undefined || vmax === undefined) return { ...state };
+
const safeLogMin = safeLog(vmin);
const safeLogMax = safeLog(vmax);
@@ -133,6 +143,7 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
};
return {
+ ...state,
internalBaselayers: state.internalBaselayers?.map((layer) => {
if (
layer.layer_id ===
@@ -153,8 +164,10 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
}
case 'CHANGE_ABSOLUTE_VALUE': {
if (assertInternalBaselayer(action.activeBaselayer)) {
- const { vmin, vmax, recommendedCmapValuesRange } =
- action.activeBaselayer;
+ const { vmin, vmax } = action.activeBaselayer;
+
+ // Return early if vmin or vmax are undefined
+ if (vmin === undefined || vmax === undefined) return { ...state };
let min = vmin;
let max = vmax;
@@ -163,8 +176,8 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
min = 0;
}
- if (action.isAbsoluteValue && vmax < 0) {
- max = recommendedCmapValuesRange * 0.1;
+ if (action.isAbsoluteValue && vmax < 0 && state.histogramData) {
+ max = state.histogramData.vmax - state.histogramData.vmin;
}
const updatedActiveBaselayer = {
@@ -175,6 +188,7 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
};
return {
+ ...state,
internalBaselayers: state.internalBaselayers?.map((layer) => {
if (
layer.layer_id ===
@@ -201,6 +215,7 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
vmax: action.vmax,
};
return {
+ ...state,
internalBaselayers: state.internalBaselayers?.map((layer) => {
if (
layer.layer_id ===
@@ -220,12 +235,20 @@ export function baselayersReducer(state: BaselayersState, action: Action) {
}
}
case 'CHANGE_BASELAYER': {
- const { newBaselayer } = action;
+ const { newBaselayer, histogramData } = action;
- return {
- ...state,
- activeBaselayer: newBaselayer,
- };
+ if (histogramData) {
+ return {
+ ...state,
+ histogramData,
+ activeBaselayer: newBaselayer,
+ };
+ } else {
+ return {
+ ...state,
+ activeBaselayer: newBaselayer,
+ };
+ }
}
default: {
throw Error('Unknown action');
diff --git a/src/types/maps.ts b/src/types/maps.ts
index e58a18d..c784c01 100644
--- a/src/types/maps.ts
+++ b/src/types/maps.ts
@@ -40,27 +40,37 @@ export type LayerResponse = {
units: string;
number_of_levels: number;
tile_size: number;
- vmin: number;
- vmax: number;
+ /** layers' vmin/vmax are either predefined or set to 'auto' */
+ vmin: number | 'auto';
+ vmax: number | 'auto';
cmap: string;
};
type EnhancedLayerAttributes = {
mapId: string;
bandId: string;
- recommendedCmapValuesRange: number;
isLogScale: boolean;
isAbsoluteValue: boolean;
};
-export type InternalBaselayer = LayerResponse & EnhancedLayerAttributes;
+export type InternalBaselayer = Omit & {
+ /** After processing layer response, 'auto' gets set to undefined and later
+ * set to a value from the layer's histogram response
+ */
+ vmin: undefined | number;
+ vmax: undefined | number;
+} & EnhancedLayerAttributes;
export type HistogramResponse = {
edges: number[];
histogram: number[];
band_id: number;
+ vmin: number;
+ vmax: number;
};
+export type HistogramData = Omit;
+
export type GraticuleDetails = {
pixelWidth: number;
interval: number;
@@ -129,6 +139,8 @@ export type BaselayersState = {
activeBaselayer?: InternalBaselayer | ExternalBaselayer;
/** the internal SO layers used as baselayers */
internalBaselayers?: InternalBaselayer[];
+ /** the active baselayer's histogram data */
+ histogramData?: HistogramResponse;
};
export type SubmapData = {
diff --git a/src/utils/fetchUtils.ts b/src/utils/fetchUtils.ts
index 6997ebd..df5fdca 100644
--- a/src/utils/fetchUtils.ts
+++ b/src/utils/fetchUtils.ts
@@ -7,6 +7,7 @@ import {
SourceGroup,
SourceGroupResponse,
SubmapDataWithBounds,
+ HistogramResponse,
} from '../types/maps';
import { SubmapFileExtensions } from '../configs/submapConfigs';
@@ -22,13 +23,19 @@ export async function fetchMaps() {
mapGroup.maps.forEach((map) =>
map.bands.forEach((band) =>
band.layers.forEach((layer) => {
+ // Set to undefined if 'auto' so we can know to set this value
+ // with the layer's histogram response instead
+ const vmin = layer.vmin === 'auto' ? undefined : layer.vmin;
+ const vmax = layer.vmax === 'auto' ? undefined : layer.vmax;
+
const internalBaselayer: InternalBaselayer = {
...layer,
mapId: map.map_id,
bandId: band.band_id,
isLogScale: false,
isAbsoluteValue: false,
- recommendedCmapValuesRange: layer.vmax - layer.vmin,
+ vmin,
+ vmax,
};
internalBaselayers.push(internalBaselayer);
})
@@ -132,3 +139,34 @@ export function getCookie(name: string): string | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
+
+// Create a cache for cmap images
+const cmapCache = new Map();
+
+// Get cmap image from a fetch or from the cache
+export async function getCmapImage(cmap: string) {
+ if (cmapCache.has(cmap)) {
+ return cmapCache.get(cmap) as string;
+ }
+
+ const image = await fetch(`${SERVICE_URL}/histograms/${cmap}.png`);
+ cmapCache.set(cmap, image.url);
+
+ return image.url;
+}
+
+// Create a cache of histogram data
+const histogramCache = new Map();
+
+// Get histogram data; uses a cache to only fetch the data once
+export async function getHistogramData(layerId: string) {
+ if (histogramCache.has(layerId)) {
+ return histogramCache.get(layerId) as HistogramResponse;
+ }
+
+ const response = await fetch(`${SERVICE_URL}/histograms/data/${layerId}`);
+
+ const data: HistogramResponse = await response.json();
+ histogramCache.set(layerId, data);
+ return data;
+}