Skip to content

Commit

Permalink
Merge pull request #129 from earthrise-media/sand-soil
Browse files Browse the repository at this point in the history
Add sand and soil, crop groups feature
  • Loading branch information
Martin Bernard authored Jan 18, 2024
2 parents 17874d2 + f4ee2ae commit f8c41d9
Show file tree
Hide file tree
Showing 42 changed files with 11,828 additions and 10,459 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
20,472 changes: 10,236 additions & 10,236 deletions vacs-map-app/public/data/crop-yields-mean-models.csv

Large diffs are not rendered by default.

106 changes: 102 additions & 4 deletions vacs-map-app/scripts/format-yields-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ const source_filename = 'public/data-raw/crop-yields-mean-models.csv';
const yields_target_filename = 'public/data/crop-yields-mean-models.csv';
const grid_target_filename = 'public/data/grid.csv';

const file = fs.readFileSync(source_filename, 'utf-8')
const crop_info_filename = 'public/data/crop-info-general.csv';

const yields = d3.csvParse(file, (d, i) => {
const yields_file = fs.readFileSync(source_filename, 'utf-8')
const crop_info_file = fs.readFileSync(crop_info_filename, 'utf-8')

const cropInfo = d3.csvParse(crop_info_file, d3.autoType);

// ---------------------------------------
// SPLIT SOURCE YIELDS FILE INTO TWO, ONE THAT STORES THE DATA AND ASSOCIATES
// EACH GRID CELL WITH AND ID, AND ONE THAT STORES THE GRID IDS AND COORDS
const yields = d3.csvParse(yields_file, (d, i) => {
let obj = Object.fromEntries(
Object.entries(d).slice(1).map(([k, v]) => {
return [k, v && v !== '' ? +v : null]
Expand All @@ -17,7 +25,7 @@ const yields = d3.csvParse(file, (d, i) => {
return obj;
})

const grid = d3.csvParse(file, (d, i) => {
const grid = d3.csvParse(yields_file, (d, i) => {
const regExp = /\(([^)]+)\)/g;
const coords = [...d.geometry.matchAll(regExp)].flat()[1]
return {
Expand All @@ -26,7 +34,97 @@ const grid = d3.csvParse(file, (d, i) => {
Y: coords.split(' ')[1]
}
})
// ---------------------------------------
// PRECALCULATE YIELD RATIOS FOR EACH GRID CELL AND CONSOLIDATE THE INFORMATION STORED
const yieldKeys = Object.keys(yields[0]).filter((k) => k.startsWith('yield'))

// add yield ratio columns for all crops and scenarios
const yieldsWithRatios = yields.map((d, i) => {
const rowWithYields = Object.fromEntries(Object.entries(d).filter(([k, v]) => v !== null))

yieldKeys.forEach((k) => {
const [_, crop, timeframe, scenario] = k.split('_')
if (timeframe === 'historical') return

// NB: abbreviating these could get more performance improvements
const historicalKey = ['yield', crop, 'historical'].join('_')

if (!yieldKeys.includes(historicalKey)) return

const yieldRatioKey = ['yieldratio', crop, timeframe, scenario].join('_')

let yieldRatioValue = null
if (
rowWithYields[k] !== null &&
rowWithYields[historicalKey] !== null &&
rowWithYields[historicalKey]
) {
yieldRatioValue =
(rowWithYields[k] - rowWithYields[historicalKey]) / rowWithYields[historicalKey]
}

if (yieldRatioValue === null) return
rowWithYields[yieldRatioKey] = yieldRatioValue
})

return rowWithYields
})

// ---------------------------------------
// PRECACLULATE THE INFO FOR CROP GROUP MAPS
const cropGroups = Array.from(new Set(cropInfo.map((d) => d.crop_group)))
const futureScenarios = ['future_ssp126', 'future_ssp370']

const groupYieldRatioKeysHash = Object.fromEntries(
d3.cross(futureScenarios, cropGroups).map(([scenario, cropGroup]) => {
return [`${cropGroup}_${scenario}`,
cropInfo
.filter((c) => c.crop_group === cropGroup)
.map((c) => {
return ['yieldratio', c.id, scenario].join('_')
})
];
})
);

const yieldsWithCropGroups = yieldsWithRatios.map((row, i) => {
d3.cross(futureScenarios, cropGroups)
.forEach(([scenario, cropGroup]) => {
const groupKey = [cropGroup, scenario].join('_')
const groupYieldRatioKeys = groupYieldRatioKeysHash[groupKey];
const rowHasYieldRatios = Object.keys(row)
.some((k) => groupYieldRatioKeys.includes(k))
if (!rowHasYieldRatios) {
return
}
const obj = {
maxCrop: 'none',
minCrop: 'none',
maxVal: null,
minVal: null
}

// TODO might be faster just to pull out the relevant entries and sort
// them?
groupYieldRatioKeys.forEach((k) => {
if (row[k] === null) return;
if (row[k] > obj.maxVal) {
obj.maxVal = row[k]
obj.maxCrop = k.split('_')[1]
}
if (row[k] < obj.minVal) {
obj.minVal = row[k]
obj.minCrop = k.split('_')[1]
}
})

Object.entries(obj).forEach(([k, v]) => {
row[groupKey+"_"+k] = v;
})
})
return { ...row };
})

fs.writeFileSync(yields_target_filename, d3.csvFormat(yields));
fs.writeFileSync(yields_target_filename, d3.csvFormat(yieldsWithCropGroups));
fs.writeFileSync(grid_target_filename, d3.csvFormat(grid));

4 changes: 2 additions & 2 deletions vacs-map-app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ const documentHeight = () => {
}
onMounted(() => {
cropInformationStore.load()
gridStore.load()
cropYieldsStore.load()
cropInformationStore.load()
window.addEventListener('resize', documentHeight)
documentHeight
})
onUnmounted(() => {
window.removeEventListener('resize')
window.removeEventListener('resize', documentHeight)
})
</script>

Expand Down
114 changes: 70 additions & 44 deletions vacs-map-app/src/MapExplorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,33 @@
<LayoutOpen>
<div class="map-wrapper-row">
<div class="map-wrapper">
<component :is="selectedMapComponent">
<MapContainerExploreMap>
<template v-slot="{ map, mapReady }">
<RegionPicker :map="map" :map-ready="mapReady" />
<span class="region-picker-message hover-message"> Zoom to a region </span>
<RegionPicker :map="map" :map-ready="mapReady" :class="{ hidden: showSandAndSoil }" />
<span v-if="!showSandAndSoil" class="region-picker-message hover-message">
Zoom to a region
</span>
</template>
</component>
</MapContainerExploreMap>
<div class="overlay-wrapper">
<OverviewTop class="interactive" />
<div class="map-overlay desktop">
<div class="overlay-left">
<ExploreSidebar class="interactive" ref="overlayLeftRef" />
<MapLegend class="interactive" />
<MapLegendCropGroups v-if="showCropGroupMap" class="interactive" :class="{ hidden: showSandAndSoil }"/>
<MapLegend v-else class="interactive" :class="{ hidden: showSandAndSoil }"/>
</div>

<div class="overlay-right">
<select v-model="selectedMap" class="interactive">
<option v-for="map in availableMaps" :value="map.id">{{ map.name }}</option>
</select>
<span class="layer-selector-message hover-message"> Add layer </span>
<button
class="interactive"
@click="showSandAndSoil = !showSandAndSoil"
:class="{ active: showSandAndSoil }"
>
<img v-if="showSandAndSoil" src="./assets/img/layers.svg" alt="" />
<img v-else src="./assets/img/layers-light.svg" alt="" />
<span class="widescreen">Sand and soil</span>
</button>
</div>
</div>
<MobileExploreMapControls />
Expand All @@ -36,30 +45,17 @@
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useResizeObserver } from '@vueuse/core'
import MapContainerColorAcrossScenarios from './components/MapContainerColorAcrossScenarios.vue'
import MapContainerColorSandSoil from '@/components/MapContainerColorSandSoil.vue'
import { useMapExploreStore } from '@/stores/mapExplore'
import LayoutOpen from './components/layouts/LayoutOpen.vue'
import ExploreSidebar from './components/ExploreSidebar.vue'
import RegionPicker from './components/RegionPicker.vue'
import MobileExploreMapControls from './components/MobileExploreMapControls.vue'
import MapContainerExploreMap from '@/components/MapContainerExploreMap.vue'
import LayoutOpen from '@/components/layouts/LayoutOpen.vue'
import ExploreSidebar from '@/components/ExploreSidebar.vue'
import RegionPicker from '@/components/RegionPicker.vue'
import MobileExploreMapControls from '@/components/MobileExploreMapControls.vue'
import MapTooltip from '@/components/MapTooltip.vue'
import MapLegend from '@/components/MapLegend.vue'
import OverviewTop from '@/components/OverviewTop.vue'
import DataDisclaimer from './components/DataDisclaimer.vue'
const availableMaps = [
{
id: 'color-across-scenarios',
name: 'Default',
component: MapContainerColorAcrossScenarios
},
{
id: 'sand-soil',
name: 'Sand + Soil',
component: MapContainerColorSandSoil
}
]
import DataDisclaimer from '@/components/DataDisclaimer.vue'
import MapLegendCropGroups from '@/components/MapLegendCropGroups.vue'
const basePadding = 50
const leftWidth = ref(null)
Expand All @@ -75,16 +71,12 @@ useResizeObserver(overlayLeftRef, ([entry]) => {
})
const mapExploreStore = useMapExploreStore()
const { selectedMap, mapPadding } = storeToRefs(mapExploreStore)
const { selectedMap, mapPadding, showCropGroupMap, showSandAndSoil } = storeToRefs(mapExploreStore)
if (!selectedMap.value) {
selectedMap.value = availableMaps[0].id
selectedMap.value = 'explore-map'
}
const selectedMapComponent = computed(() => {
return availableMaps.find(({ id }) => id === selectedMap.value).component
})
onMounted(() => {
mapPadding.value = {
top: basePadding,
Expand Down Expand Up @@ -146,12 +138,15 @@ onUnmounted(() => {
}
.overlay-right {
position: relative;
position: absolute;
margin-right: var(--page-horizontal-margin);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
right: 9rem;
bottom: 3rem;
}
.interactive {
Expand Down Expand Up @@ -179,6 +174,36 @@ select:hover {
background-color: var(--ui-blue-hover);
}
button {
border: none;
border-radius: 6.25rem;
background: var(--dark-gray);
background: var(--dark-gray);
color: var(--white);
display: flex;
padding: 0.625rem 0.75rem;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
cursor: pointer;
}
button.active {
background: var(--ui-blue);
color: var(--black);
}
button:hover {
background: var(--dark-gray-hover);
}
button.active:hover {
background: var(--ui-blue-hover);
}
.hover-message {
opacity: 0;
transition: all 0.5s ease;
Expand All @@ -188,20 +213,15 @@ select:hover {
padding: 0.25rem 0.5rem;
}
.layer-selector-message {
position: absolute;
left: 1rem;
transform: translateX(-130%);
}
.region-picker-message {
position: absolute;
right: calc(var(--page-horizontal-margin) - 0.75rem);
bottom: 11rem;
}
select:hover + .layer-selector-message {
opacity: 1;
.hidden {
pointer-events: none;
opacity: 10%;
}
@media only screen and (max-width: 720px) {
Expand Down Expand Up @@ -235,4 +255,10 @@ select:hover + .layer-selector-message {
display: none;
}
}
@media only screen and (max-width: 1260px) {
.widescreen {
display: none;
}
}
</style>
1 change: 1 addition & 0 deletions vacs-map-app/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--ui-blue-light-bg: #5473a5;
--font-family-header: PPeiko-heavy;
--font-family-h2: PPeiko-medium;
--font-family-h3: PPeiko-thin;
--font-family-body: 'Work Sans';
--shadow: 0px 0px 20px 0px rgba(255, 254, 254, 0.1);

Expand Down
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/arrow-right-pointy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions vacs-map-app/src/assets/img/layers-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/negative-yield.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/positive-yield.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vacs-map-app/src/assets/img/reference-crop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions vacs-map-app/src/components/BaseMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ onMounted(() => {
})
onUnmounted(() => {
map.value.remove();
});
map.value.remove()
})
watch(mapPadding, () => {
if (mapPadding.value) map.value.setPadding(mapPadding.value)
Expand All @@ -72,7 +72,7 @@ watch(mapPadding, () => {
}
.mapboxgl-ctrl-top-right {
top: 8.75rem;
top: 5.5rem;
right: calc(var(--page-horizontal-margin));
}
Expand Down
Loading

0 comments on commit f8c41d9

Please sign in to comment.