import React from 'react';
import { BoundsViewport, Bounds } from '../../map/Types';
import { allColorsHex, chartMarkerColorsHex } from '../../map/config/map';
import { leafletZoomLevelToGeohashLevel } from '../../map/utils/leaflet-geohash';
import DonutMarker, { DonutMarkerProps } from '../../map/DonutMarker';
import ChartMarker from '../../map/ChartMarker';
import { LeafletMouseEvent } from 'leaflet';
function sleep(ms: number): Promise<() => void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export const getSpeciesDonuts = async (
{ bounds, zoomLevel }: BoundsViewport,
duration: number,
setLegendData: (
legendData: Array<{ label: string; value: number; color: string }>
) => void,
handleMarkerClick: (e: LeafletMouseEvent) => void,
delay: number = 0,
zoomLevelToGeohashLevel: (
zoomLevel: number
) => number = leafletZoomLevelToGeohashLevel,
donutSize: number = 40
) => {
const geohash_level = zoomLevelToGeohashLevel(zoomLevel);
delay && (await sleep(delay));
const response = await fetch('data/geoclust-species-testing-all-levels.json');
const speciesData = await response.json();
const buckets = (speciesData as { [key: string]: any })[
`geohash_${geohash_level}`
].facets.geo.buckets.filter((bucket: any) => {
const lat: number = bucket.ltAvg;
const long: number = bucket.lnAvg;
const south = bounds.southWest.lat;
const north = bounds.northEast.lat;
const west = bounds.southWest.lng;
const east = bounds.northEast.lng;
const lambda = 1e-8; // accommodate tiny rounding errors
return (
lat > south &&
lat < north &&
(west < east - lambda
? long > west && long < east
: west > east + lambda
? !(long > east && long < west)
: true)
);
});
// make a first pass and calculate the legend totals
// and rank the species for color assignment
let speciesToCount = new Map();
buckets.forEach((bucket: any) => {
bucket.term.buckets.forEach((bucket: any) => {
const species = bucket.val;
let prevCount = speciesToCount.get(species);
if (prevCount == null) prevCount = 0;
speciesToCount.set(species, prevCount + bucket.count);
});
});
// sort by the count (Map returns keys in insertion order)
speciesToCount = new Map(
Array.from(speciesToCount).sort(([_1, v1], [_2, v2]) =>
v1 > v2 ? -1 : v2 > v1 ? 1 : 0
)
);
// make the species to color lookup
const speciesToColor = new Map(
Array.from(speciesToCount).map(([k, _], index) => {
if (index < 10) {
return [k, allColorsHex[index]];
} else {
return [k, 'silver'];
}
})
);
// reformat as legendData
const legendData = Array.from(speciesToCount.keys()).map((key) => ({
label: key,
value: speciesToCount.get(key) || -1,
color: speciesToColor.get(key) || 'silver',
}));
setLegendData(legendData);
return buckets.map((bucket: any) => {
const lat: number = bucket.ltAvg;
const lng: number = bucket.lnAvg;
const bounds: Bounds = {
southWest: { lat: bucket.ltMin, lng: bucket.lnMin },
northEast: { lat: bucket.ltMax, lng: bucket.lnMax },
};
let data: DonutMarkerProps['data'] = [];
bucket.term.buckets.forEach((bucket: any) => {
const species = bucket.val;
data.push({
label: species,
value: bucket.count,
color: speciesToColor.get(species) || 'silver',
});
});
// check isAtomic
let atomicValue =
bucket.atomicCount && bucket.atomicCount === 1 ? true : false;
// anim key
const key = bucket.val;
return (
);
});
};
export const getSpeciesBasicMarkers = async (
{ bounds, zoomLevel }: BoundsViewport,
duration: number,
handleMarkerClick: (e: LeafletMouseEvent) => void
) => {
const geohash_level = leafletZoomLevelToGeohashLevel(zoomLevel);
const response = await fetch('data/geoclust-species-testing-all-levels.json');
const speciesData = await response.json();
const buckets = (speciesData as { [key: string]: any })[
`geohash_${geohash_level}`
].facets.geo.buckets.filter((bucket: any) => {
const lat: number = bucket.ltAvg;
const long: number = bucket.lnAvg;
const south = bounds.southWest.lat;
const north = bounds.northEast.lat;
const west = bounds.southWest.lng;
const east = bounds.northEast.lng;
const lambda = 1e-8; // accommodate tiny rounding errors
return (
lat > south &&
lat < north &&
(west < east - lambda
? long > west && long < east
: west > east + lambda
? !(long > east && long < west)
: true)
);
});
return buckets.map((bucket: any) => {
const lat: number = bucket.ltAvg;
const lng: number = bucket.lnAvg;
const bounds: Bounds = {
southWest: { lat: bucket.ltMin, lng: bucket.lnMin },
northEast: { lat: bucket.ltMax, lng: bucket.lnMax },
};
// let sum = 0;
// bucket.term.buckets.forEach((bucket : any) => sum += bucket.count);
const data: DonutMarkerProps['data'] = [
{
label: 'unknown',
value: bucket.count,
color: 'white',
},
];
// check isAtomic
let atomicValue =
bucket.atomicCount && bucket.atomicCount === 1 ? true : false;
// anim key
const key = bucket.val;
return (
);
});
};
// define bucket prop, which is for buckets[]
interface bucketProps {
term: {
between: { count: number };
after: { count: number };
before: { count: number };
buckets: Array<{
count: number;
val: string;
}>;
};
atomicCount: number;
val: string;
count: number;
ltAvg: number;
ltMin: number;
ltMax: number;
lnAvg: number;
lnMin: number;
lnMax: number;
}
export const getCollectionDateChartMarkers = async (
{ bounds, zoomLevel }: BoundsViewport,
duration: number,
setLegendData: (
legendData: Array<{ label: string; value: number; color: string }>
) => void,
handleMarkerClick: (e: LeafletMouseEvent) => void,
legendRadioValue: string,
setDependentAxisRange: (dependentAxisRange: number[]) => void,
delay: number = 0,
dependentAxisLogScale?: boolean
) => {
const geohash_level = leafletZoomLevelToGeohashLevel(zoomLevel);
delay && (await sleep(delay));
const response = await fetch(
'data/geoclust-date-binning-testing-all-levels.json'
);
const collectionDateData = await response.json();
let legendSums: number[] = [];
let legendLabels: string[] = [];
let legendColors: string[] = [];
let yAxisRange: number[] = []; // This sets range to 'local' mode
let yAxisRangeAll: number[] = [];
const buckets = (collectionDateData as { [key: string]: any })[
`geohash_${geohash_level}`
].facets.geo.buckets.filter((bucket: any) => {
const lat: number = bucket.ltAvg;
const long: number = bucket.lnAvg;
const south = bounds.southWest.lat;
const north = bounds.northEast.lat;
const west = bounds.southWest.lng;
const east = bounds.northEast.lng;
const lambda = 1e-8; // accommodate tiny rounding errors
return (
lat > south &&
lat < north &&
(west < east - lambda
? long > west && long < east
: west > east + lambda
? !(long > east && long < west)
: true)
);
});
// change this to always show Reginal scale value
yAxisRangeAll = [
0,
buckets.reduce((currentMax: number, bucket: bucketProps) => {
return Math.max(
currentMax,
bucket.count -
bucket.term.before.count -
bucket.term.after.count -
bucket.term.between.count, // no data count
bucket.term.buckets.reduce(
(currentMax: number, bucket: { count: number; val: string }) =>
Math.max(currentMax, bucket.count),
0
) // current bucket max value
);
}, 0),
];
// set yAxisRange only if Regional
if (legendRadioValue === 'Regional') {
yAxisRange = yAxisRangeAll;
}
// add setDependentAxisRange: be careful of type of setDependentAxisRange
setDependentAxisRange(yAxisRangeAll);
const markers = buckets.map((bucket: bucketProps) => {
const lat = bucket.ltAvg;
const lng = bucket.lnAvg;
const bounds: Bounds = {
southWest: { lat: bucket.ltMin, lng: bucket.lnMin },
northEast: { lat: bucket.ltMax, lng: bucket.lnMax },
};
let markerData = [];
let noDataValue: number = 0;
bucket.term.buckets.forEach(
(bucket: { count: number; val: string }, index: number) => {
const start = bucket.val.substring(0, 4);
const end = parseInt(start, 10) + 3;
const label = `${start}-${end}`;
markerData.push({
label,
value: bucket.count,
color: chartMarkerColorsHex[index],
});
// sum all counts for legend
if (legendSums[index] == null) {
legendSums[index] = 0;
legendLabels[index] = label;
legendColors[index] = chartMarkerColorsHex[index];
}
legendSums[index] += bucket.count;
}
);
// calculate the number of no data (or data before first bin, or after last bin) and make 6th bar
noDataValue = bucket.count - bucket.term.between.count;
markerData.push({
label: 'noDataOrOutOfBounds',
value: noDataValue,
color: 'silver', // fill the last color
});
legendLabels[5] = 'no data/out of bounds';
if (legendSums[5] == null) legendSums[5] = 0;
legendSums[5] += noDataValue;
legendColors[5] = 'silver';
// check isAtomic for push pin for chart marker
let atomicValue =
bucket.atomicCount && bucket.atomicCount === 1 ? true : false;
// anim key
const key = bucket.val;
const yAxisRangeValue = yAxisRange.length ? yAxisRange : null;
// BM: important to provide the key 'prop' (which is not a true prop) at
// this outermost level
return (
);
});
const legendData = legendSums.map((count, index) => {
return {
label: legendLabels[index],
value: count,
color: legendColors[index],
};
});
setLegendData(legendData);
return markers;
};
export const getCollectionDateBasicMarkers = async (
{ bounds, zoomLevel }: BoundsViewport,
duration: number,
handleMarkerClick: (e: LeafletMouseEvent) => void
) => {
const geohash_level = leafletZoomLevelToGeohashLevel(zoomLevel);
const response = await fetch(
'data/geoclust-date-binning-testing-all-levels.json'
);
const collectionDateData = await response.json();
const buckets = (collectionDateData as { [key: string]: any })[
`geohash_${geohash_level}`
].facets.geo.buckets.filter((bucket: any) => {
const lat: number = bucket.ltAvg;
const long: number = bucket.lnAvg;
const south = bounds.southWest.lat;
const north = bounds.northEast.lat;
const west = bounds.southWest.lng;
const east = bounds.northEast.lng;
const lambda = 1e-8; // accommodate tiny rounding errors
return (
lat > south &&
lat < north &&
(west < east - lambda
? long > west && long < east
: west > east + lambda
? !(long > east && long < west)
: true)
);
});
// go through all buckets to get sum(count) of each bucket and make a single barchart value (color white)
const markers = buckets.map((bucket: bucketProps) => {
const lat = bucket.ltAvg;
const lng = bucket.lnAvg;
const bounds: Bounds = {
southWest: { lat: bucket.ltMin, lng: bucket.lnMin },
northEast: { lat: bucket.ltMax, lng: bucket.lnMax },
};
// check isAtomic for push pin for chart marker
const atomicValue =
bucket.atomicCount && bucket.atomicCount === 1 ? true : false;
// anim key
const key = bucket.val;
// BM: important to provide the key 'prop' (which is not a true prop) at
// this outermost level
return (
);
});
return markers;
};