diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fe039ad..963f3b9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@babel/runtime": "^7.16.3", "@turf/bbox": "^6.5.0", "classnames": "^2.3.1", + "colormap": "^2.3.2", "downloadjs": "^1.4.7", "fomantic-ui-less": "^2.8.8", "immer": "^9.0.7", @@ -3262,6 +3263,14 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, + "node_modules/colormap": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/colormap/-/colormap-2.3.2.tgz", + "integrity": "sha512-jDOjaoEEmA9AgA11B/jCSAvYE95r3wRoAyTf3LEHGiUVlNHJaL1mRkf5AyLSpQBVGfTEPwGEqCIzL+kgr2WgNA==", + "dependencies": { + "lerp": "^1.0.3" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -5389,6 +5398,11 @@ "node": ">= 8" } }, + "node_modules/lerp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz", + "integrity": "sha1-oYyJaPkXiW3hXM/MKNVaa3Med24=" + }, "node_modules/less": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/less/-/less-4.1.2.tgz", @@ -11551,6 +11565,14 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, + "colormap": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/colormap/-/colormap-2.3.2.tgz", + "integrity": "sha512-jDOjaoEEmA9AgA11B/jCSAvYE95r3wRoAyTf3LEHGiUVlNHJaL1mRkf5AyLSpQBVGfTEPwGEqCIzL+kgr2WgNA==", + "requires": { + "lerp": "^1.0.3" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -13150,6 +13172,11 @@ "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==" }, + "lerp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz", + "integrity": "sha1-oYyJaPkXiW3hXM/MKNVaa3Med24=" + }, "less": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/less/-/less-4.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 172cfc5..5a0452f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "@babel/runtime": "^7.16.3", "@turf/bbox": "^6.5.0", "classnames": "^2.3.1", + "colormap": "^2.3.2", "downloadjs": "^1.4.7", "fomantic-ui-less": "^2.8.8", "immer": "^9.0.7", diff --git a/frontend/src/mapstyles/index.js b/frontend/src/mapstyles/index.js index 7c468e2..d05942b 100644 --- a/frontend/src/mapstyles/index.js +++ b/frontend/src/mapstyles/index.js @@ -3,9 +3,50 @@ import _ from 'lodash' import bright from './bright.json' import positron from './positron.json' +import viridisBase from 'colormap/res/res/viridis' + export {bright, positron} export const baseMapStyles = {bright, positron} + +function simplifyColormap(colormap, maxCount = 16) { + const result = [] + const step = Math.ceil(colormap.length / maxCount) + for (let i = 0; i < colormap.length; i+= step) { + result.push(colormap[i]) + } + return result +} + +function rgbArrayToColor(arr) { + return ['rgb', ...arr.map(v => Math.round(v*255))] +} + +export function colormapToScale(colormap, value, min, max) { + return [ + 'interpolate-hcl', + ['linear'], + value, + ...colormap.flatMap((v, i, a) => [ + (i / (a.length - 1)) * (max - min) + min, + v, + ]) + ] +} + +export const viridis = simplifyColormap(viridisBase.map(rgbArrayToColor), 20); +export const grayscale = ['#FFFFFF', '#000000'] +export const reds = [['rgba', 255, 0, 0, 0], ['rgba', 255, 0, 0, 1]] + +export function colorByCount(attribute = 'event_count', maxCount, colormap = viridis) { + return colormapToScale( + colormap, + ['case', ['to-boolean', ['get', attribute]], ['get', attribute], 0], + 0, + maxCount + ) +} + export function colorByDistance(attribute = 'distance_overtaker_mean', fallback = '#ABC') { return [ 'case', diff --git a/frontend/src/pages/MapPage/LayerSidebar.tsx b/frontend/src/pages/MapPage/LayerSidebar.tsx index c8aa5ff..25d8a72 100644 --- a/frontend/src/pages/MapPage/LayerSidebar.tsx +++ b/frontend/src/pages/MapPage/LayerSidebar.tsx @@ -1,8 +1,13 @@ import React from 'react' +import _ from 'lodash' import {connect} from 'react-redux' -import {List, Select, Header, Checkbox} from 'semantic-ui-react' +import {List, Select, Input, Divider, Checkbox} from 'semantic-ui-react' -import * as mapConfigActions from 'reducers/mapConfig' +import { + MapConfig, + setMapConfigFlag as setMapConfigFlagAction, + initialState as defaultMapConfig, +} from 'reducers/mapConfig' const BASEMAP_STYLE_OPTIONS = [ {value: 'positron', key: 'positron', text: 'Positron'}, @@ -17,8 +22,14 @@ const ROAD_ATTRIBUTE_OPTIONS = [ {value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'}, ] -function LayerSidebar({mapConfig, setMapConfigFlag}) { - const showUntagged = mapConfig?.obsRoads?.showUntagged ?? true +function LayerSidebar({ + mapConfig, + setMapConfigFlag, +}: { + mapConfig: MapConfig + setMapConfigFlag: (flag: string, value: unknown) => void +}) { + const {baseMap: {style}, obsRoads: {show, showUntagged, attribute, maxCount}} = mapConfig return (