├── .gitignore ├── frontend ├── jsconfig.json ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── seasons.png │ ├── manifest.json │ └── index.html ├── src │ ├── assets │ │ ├── homebg.png │ │ ├── search.svg │ │ ├── analysis.svg │ │ ├── redo.svg │ │ ├── sprout.svg │ │ ├── map.svg │ │ ├── statistics.svg │ │ ├── irrigation.svg │ │ ├── cloudy.svg │ │ ├── timer.svg │ │ ├── raining.svg │ │ ├── logo.svg │ │ ├── crop.svg │ │ ├── sunny.svg │ │ └── saved.svg │ ├── components │ │ ├── Map │ │ │ ├── style.scss │ │ │ ├── utils.js │ │ │ └── index.jsx │ │ ├── Statistics │ │ │ ├── EnvironmentalStrain │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── EstimatedFinancials │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── FarmBreakdown │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── CropRotation │ │ │ │ ├── style.scss │ │ │ │ ├── index.jsx │ │ │ │ └── earthData.json │ │ │ └── Breakdowns │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ ├── Date │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── Analysis │ │ │ ├── Card │ │ │ │ ├── index.jsx │ │ │ │ └── style.scss │ │ │ ├── Breakdowns │ │ │ │ ├── style.scss │ │ │ │ └── index.jsx │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── AQBreakdown │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── LocationInfo │ │ │ └── index.jsx │ │ ├── CurrentlySelected │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── StatsModal │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── NavBar │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── Plot │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── SideBar │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── Timeline │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── Landing │ │ │ ├── index.jsx │ │ │ └── style.scss │ │ ├── BigCurrentlySelected │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ ├── Seasons │ │ │ ├── style.scss │ │ │ └── index.jsx │ │ └── PlotType │ │ │ ├── style.scss │ │ │ └── index.jsx │ ├── pages │ │ ├── Landing.jsx │ │ ├── earthData.json │ │ └── Home.jsx │ ├── style.scss │ └── index.js ├── .gitignore ├── netlify.toml └── package.json ├── backend ├── src │ ├── insights │ │ ├── ambee │ │ │ ├── index.ts │ │ │ ├── soilLatest.ts │ │ │ ├── waterVaporLatest.ts │ │ │ ├── fireLatest.ts │ │ │ ├── soilHistory.ts │ │ │ ├── waterVaporHistory.ts │ │ │ ├── airQuality.ts │ │ │ ├── pollenLatest.ts │ │ │ ├── weatherLatest.ts │ │ │ ├── pollenForecast.ts │ │ │ ├── pollenHistory.ts │ │ │ ├── airQualityHistory.ts │ │ │ ├── weatherHistory.ts │ │ │ └── weatherForecast.ts │ │ ├── weather │ │ │ └── index.ts │ │ └── index.ts │ ├── configuration │ │ └── index.ts │ ├── utils │ │ ├── handleError.ts │ │ ├── parseDate.ts │ │ ├── getLocGrade.ts │ │ └── getEnvGrade.ts │ ├── index.ts │ ├── data │ │ ├── weatherResponse.json │ │ └── earthData.json │ └── app.ts ├── .gitignore ├── .prettierrc.js ├── tsconfig-paths-bootstrap.js ├── .eslintrc.js ├── tsconfig.json └── package.json ├── analysis ├── .gitignore ├── .prettierrc.js ├── tsconfig-paths-bootstrap.js ├── .eslintrc.js ├── tsconfig.json ├── package.json └── src │ ├── index.ts │ └── data │ └── earthData.json ├── package.json ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | build/ -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | } 5 | } -------------------------------------------------------------------------------- /backend/src/insights/ambee/index.ts: -------------------------------------------------------------------------------- 1 | export interface AmbeeError { 2 | error: any; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/seasons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/HEAD/frontend/public/seasons.png -------------------------------------------------------------------------------- /frontend/src/assets/homebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/HEAD/frontend/src/assets/homebg.png -------------------------------------------------------------------------------- /frontend/src/components/Map/style.scss: -------------------------------------------------------------------------------- 1 | .custom-top-right { 2 | position: absolute; 3 | top: 10vh; 4 | right: 200px; 5 | z-index: 99999; 6 | 7 | } -------------------------------------------------------------------------------- /analysis/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | pids 5 | *.pid 6 | *.seed 7 | *.pid.lock 8 | node_modules 9 | *.tsbuildinfo 10 | .env 11 | build 12 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | pids 5 | *.pid 6 | *.seed 7 | *.pid.lock 8 | node_modules 9 | *.tsbuildinfo 10 | .env 11 | build 12 | -------------------------------------------------------------------------------- /analysis/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /backend/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/pages/Landing.jsx: -------------------------------------------------------------------------------- 1 | import Landing from '../components/Landing'; 2 | 3 | const LandingPage = () => { 4 | return ; 5 | }; 6 | 7 | export default LandingPage; 8 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/EnvironmentalStrain/style.scss: -------------------------------------------------------------------------------- 1 | .EnvironmentalStrain { 2 | border: 2px solid #dfdfdf; 3 | border-radius: 6px; 4 | padding: 10px; 5 | background-color: #f9f9f9; 6 | margin: 10px; 7 | width: 450px; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/EstimatedFinancials/style.scss: -------------------------------------------------------------------------------- 1 | .EstimatedFinancials { 2 | border: 2px solid #dfdfdf; 3 | border-radius: 6px; 4 | padding: 10px; 5 | background-color: #f9f9f9; 6 | margin: 10px; 7 | width: 450px; 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/configuration/index.ts: -------------------------------------------------------------------------------- 1 | export const PORT: string = process.env.PORT || '3001'; 2 | export const AMBEE_API_KEY = process.env.AMBEE_API_KEY; 3 | export const WEATHER_API_KEY = process.env.WEATHER_API_KEY; 4 | export const USE_API = process.env.USE_API || false; -------------------------------------------------------------------------------- /frontend/src/components/Date/style.scss: -------------------------------------------------------------------------------- 1 | .date { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | z-index: 999; 6 | height: 8vh; 7 | width: 300px; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | font-size: 28px; 12 | } -------------------------------------------------------------------------------- /frontend/src/components/Analysis/Card/index.jsx: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | 3 | const Card = (props) => { 4 | return ( 5 |
6 |

{props.title}

7 | {props.children} 8 |
9 | ); 10 | } 11 | 12 | export default Card; -------------------------------------------------------------------------------- /frontend/src/components/AQBreakdown/style.scss: -------------------------------------------------------------------------------- 1 | .air-quality { 2 | width: 300px; 3 | 4 | .breakdown { 5 | list-style-type: none; 6 | padding: 0; 7 | 8 | li { 9 | 10 | div { 11 | display: inline; 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /backend/src/utils/handleError.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | export const handleError = (cause: string, details: any, req: Request, res: Response) => { 4 | return res.status(400).json({ 5 | message: cause, 6 | details, 7 | sentParameters: req.body, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /analysis/tsconfig-paths-bootstrap.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const tsConfig = require('./tsconfig.json'); 3 | const tsConfigPaths = require('tsconfig-paths'); 4 | 5 | const baseUrl = './build'; 6 | tsConfigPaths.register({ 7 | baseUrl, 8 | paths: tsConfig.compilerOptions.paths, 9 | }); 10 | -------------------------------------------------------------------------------- /backend/tsconfig-paths-bootstrap.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const tsConfig = require('./tsconfig.json'); 3 | const tsConfigPaths = require('tsconfig-paths'); 4 | 5 | const baseUrl = './build'; 6 | tsConfigPaths.register({ 7 | baseUrl, 8 | paths: tsConfig.compilerOptions.paths, 9 | }); 10 | -------------------------------------------------------------------------------- /backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import { PORT } from 'src/configuration'; 2 | import { getApp } from 'src/app'; 3 | 4 | const startServer = () => { 5 | try { 6 | const app = getApp(); 7 | 8 | app.listen(PORT, () => { 9 | console.log(`server started at http://localhost:${PORT}`); 10 | }); 11 | } catch (error) { 12 | console.error(error); 13 | } 14 | }; 15 | 16 | startServer(); 17 | -------------------------------------------------------------------------------- /backend/src/utils/parseDate.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | /** 3 | * Converts the date to a ambee readable date 4 | * @param date In a format parsable by moment 5 | */ 6 | export const ambeeDateParse = (date: string): string => { 7 | return moment(date).format('YYYY-MM-DD hh:mm:ss'); 8 | }; 9 | 10 | export const weatherDateParse = (date: string): string => { 11 | return moment(date).format('MM/DD/YYYY'); 12 | } -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/FarmBreakdown/style.scss: -------------------------------------------------------------------------------- 1 | .FarmBreakdown { 2 | border: 2px solid #dfdfdf; 3 | border-radius: 6px; 4 | padding: 10px; 5 | background-color: #f9f9f9; 6 | margin: 10px; 7 | width: 425px; 8 | height: 315px; 9 | 10 | .wrapper { 11 | width: 100%; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /analysis/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', 5 | 'prettier/@typescript-eslint', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: 'module', 11 | }, 12 | rules: { 13 | "@typescript-eslint/explicit-function-return-type": 0, 14 | "@typescript-eslint/no-explicit-any": 0 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', 5 | 'prettier/@typescript-eslint', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: 'module', 11 | }, 12 | rules: { 13 | "@typescript-eslint/explicit-function-return-type": 0, 14 | "@typescript-eslint/no-explicit-any": 0 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/style.scss: -------------------------------------------------------------------------------- 1 | @import 'antd/dist/antd.css'; 2 | 3 | body { 4 | font-family: Poppins, sans-serif; 5 | height: 100vh; 6 | margin: 0; 7 | overflow: hidden; 8 | width: 100vh; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | a { 14 | color: black; 15 | text-decoration: none; 16 | } 17 | 18 | canvas { 19 | z-index: 500; 20 | &:focus { 21 | outline: none; 22 | } 23 | } 24 | 25 | .custom-group { 26 | display: none; 27 | } -------------------------------------------------------------------------------- /frontend/src/components/LocationInfo/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | Props should contain: 3 | - color 1 for gradient 4 | - color 2 for gradient 5 | - text to display 6 | */ 7 | const LocationInfo = (props) => { 8 | return ( 9 |
10 |

11 | {props.title} 12 |

13 |
14 | {props.children} 15 |
16 |
17 | ); 18 | } 19 | 20 | export default LocationInfo; -------------------------------------------------------------------------------- /analysis/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": true, 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "target": "es6", 7 | "noImplicitAny": true, 8 | "moduleResolution": "node", 9 | "sourceMap": true, 10 | "outDir": "build", 11 | "baseUrl": "src", 12 | "resolveJsonModule": true, 13 | "paths": { 14 | "*": ["node_modules/*"], 15 | "src/*": ["./*"] 16 | } 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "skipLibCheck": true, 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "target": "es6", 7 | "noImplicitAny": true, 8 | "moduleResolution": "node", 9 | "sourceMap": true, 10 | "outDir": "build", 11 | "baseUrl": "src", 12 | "resolveJsonModule": true, 13 | "paths": { 14 | "*": ["node_modules/*"], 15 | "src/*": ["./*"] 16 | } 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { 4 | BrowserRouter as Router, 5 | Switch, 6 | Route, 7 | } from "react-router-dom"; 8 | 9 | import './style.scss'; 10 | import Home from 'pages/Home'; 11 | import Landing from 'pages/Landing'; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/analysis.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/Date/index.jsx: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | 3 | function dateFromDay(year, day){ 4 | let newYear; 5 | if (day > 365) { 6 | newYear = year + 1; 7 | } else { 8 | newYear = year; 9 | } 10 | var date = new Date(newYear, 0); // initialize a date in `year-01-01` 11 | return new Date(date.setDate(day % 365)); // add the number of days 12 | } 13 | 14 | const options = { year: "numeric", month: "long", day: "numeric" } 15 | 16 | const DateComponent = (props) => { 17 | return ( 18 |
19 | {dateFromDay(2023, props.day).toLocaleDateString(undefined, options)} 20 |
21 | ) 22 | } 23 | 24 | export default DateComponent; -------------------------------------------------------------------------------- /frontend/src/assets/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /backend/src/data/weatherResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "from": "04/18/2021", 3 | "to": "04/18/2021", 4 | "weather": { 5 | "status": "success", 6 | "location": "Des Moines, IA, USA", 7 | "forecast": [ 8 | { 9 | "date": "04/18/2021", 10 | "max_temp_high": 68, 11 | "max_temp_low": 58, 12 | "min_temp_code": "s", 13 | "min_temp_high": 45, 14 | "min_temp_low": 38, 15 | "precipitation": "Generally Dry", 16 | "precipitation_code": "DR2", 17 | "temp": "seasonal", 18 | "temp_code": "s" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /frontend/src/components/Statistics/CropRotation/style.scss: -------------------------------------------------------------------------------- 1 | .CropRotation { 2 | border: 2px solid #dfdfdf; 3 | border-radius: 6px; 4 | padding: 10px; 5 | background-color: #F9F9F9; 6 | margin: 10px; 7 | width: 450px; 8 | 9 | .buttons { 10 | width: 100%; 11 | display: flex; 12 | justify-content: flex-end; 13 | } 14 | 15 | p { 16 | font-size: 12px; 17 | } 18 | 19 | .forward { 20 | background: #F9F9F9; 21 | border-radius: 6px; 22 | border: 2px solid #dfdfdf; 23 | width: 20%; 24 | } 25 | .backward { 26 | border: none; 27 | background-color: #f9f9f9; 28 | box-shadow: none; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /frontend/src/assets/sprout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/CurrentlySelected/index.jsx: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | 3 | const CurrentlySelected = (props) => { 4 | return ( 5 |
6 |
7 |

Selected

8 |

9 | { ! props.currentPlot.name ? 'N/A' : 10 | props.currentPlot.name.length > 6 ? props.currentPlot.name.substring(0, 5) + '...' : 11 | props.currentPlot.name 12 | } 13 |

14 |
15 |
16 |

Size

17 |

{props.currentPlot.sqFt ? Math.floor(props.currentPlot.sqFt / 1000) + 'k sqft': 'N/A' }

18 |
19 |
20 | ) 21 | } 22 | 23 | export default CurrentlySelected; -------------------------------------------------------------------------------- /frontend/src/components/StatsModal/style.scss: -------------------------------------------------------------------------------- 1 | .StatsModal { 2 | .title { 3 | user-drag: none; 4 | user-select: none; 5 | -moz-user-select: none; 6 | -webkit-user-drag: none; 7 | -webkit-user-select: none; 8 | -ms-user-select: none; 9 | } 10 | } 11 | .stat-title { 12 | font-weight: 600; 13 | font-size: 20px; 14 | margin-bottom: 6px; 15 | } 16 | 17 | .sub-title { 18 | line-height: 6px; 19 | font-size: 12px; 20 | } 21 | 22 | .ant-modal { 23 | width: 75vw !important; 24 | } 25 | 26 | .bstats-container { 27 | display: flex; 28 | flex-direction: row; 29 | justify-content: space-between; 30 | } 31 | 32 | .tstats-container { 33 | display: flex; 34 | flex-direction: row; 35 | justify-content: space-between; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/components/CurrentlySelected/style.scss: -------------------------------------------------------------------------------- 1 | .currently-selected { 2 | align-items: center; 3 | background: #F9F9F9; 4 | border-top-left-radius: 6px; 5 | border-top-right-radius: 6px; 6 | bottom: -200px; 7 | display: flex; 8 | height: 9vh; 9 | left: 37vw; 10 | padding: 0 10px; 11 | position: absolute; 12 | width: 14vw; 13 | z-index: 1; 14 | 15 | .text { 16 | overflow: hidden; 17 | white-space: nowrap; 18 | width: 50%; 19 | padding: 10px; 20 | 21 | h1 { 22 | font-size: 20px; 23 | font-weight: 500; 24 | line-height: 100%; 25 | margin: 0; 26 | margin-top: 5px; 27 | } 28 | 29 | h2 { 30 | font-size: 13px; 31 | font-weight: 300; 32 | line-height: 100%; 33 | margin: 0; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /frontend/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Configure it so that deploy previews and `nightly` run on the testing instance, not 3 | # on the default instance 4 | [context.deploy-preview] 5 | publish = "build/" 6 | command = "CI=false npm run build:dev" 7 | [context.deploy-preview.environment] 8 | NODE_ENV = "development" 9 | [context.branch-deploy] 10 | command = "CI=false npm run build:dev" 11 | [context.branch-deploy.environment] 12 | NODE_ENV = "development" 13 | 14 | # The following redirect is intended for use with most SPAs that handle 15 | # routing internally. 16 | [[redirects]] 17 | from = "/*" 18 | to = "/index.html" 19 | status = 200 20 | 21 | [[headers]] 22 | # Define which paths this specific [[headers]] block will cover. 23 | for = "/*" 24 | [headers.values] 25 | Access-Control-Allow-Origin = "*" -------------------------------------------------------------------------------- /frontend/src/components/AQBreakdown/index.jsx: -------------------------------------------------------------------------------- 1 | import "./style.scss"; 2 | 3 | const AQBreakdown = (props) => { 4 | return ( 5 |
6 |

Air Quality Breakdown

7 | 15 |
16 | ) 17 | } 18 | 19 | export default AQBreakdown; -------------------------------------------------------------------------------- /frontend/src/components/NavBar/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Link } from "react-router-dom"; 3 | 4 | import './style.scss'; 5 | import search from 'assets/search.svg'; 6 | 7 | const NavBar = () => { 8 | 9 | const [farmName, setFarmName] = useState("Falcons: Expo 2023"); 10 | 11 | const onNameChange = (e) => { 12 | setFarmName(e.target.value); 13 | } 14 | 15 | return ( 16 |
17 |
18 | 19 | drishti 20 | 21 |
22 | 23 |
24 | search icon 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default NavBar; -------------------------------------------------------------------------------- /frontend/src/components/Plot/style.scss: -------------------------------------------------------------------------------- 1 | .plot { 2 | align-items: center; 3 | background-repeat: no-repeat; 4 | background-position: right; 5 | border-radius: 6px; 6 | color: white; 7 | cursor: pointer; 8 | display: flex; 9 | height: 65px; 10 | margin-bottom: 10px; 11 | padding: 0 20px; 12 | width: 100%; 13 | background-size: cover; 14 | 15 | 16 | img { 17 | width: 35px; 18 | margin-right: 10px; 19 | } 20 | 21 | .section { 22 | margin-right: 2%; 23 | white-space: nowrap; 24 | width: 30%; 25 | 26 | h1 { 27 | color: white; 28 | font-size: 16px; 29 | font-weight: 500; 30 | line-height: 100%; 31 | margin: 0; 32 | } 33 | 34 | h2 { 35 | color: white; 36 | font-size: 16px; 37 | font-weight: 300; 38 | line-height: 100%; 39 | margin: 0; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /frontend/src/assets/map.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fasaan", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start" : "yarn --cwd ./backend start", 8 | "dev" : "concurrently --kill-others \"yarn --cwd ./analysis build-watch\" \"yarn --cwd ./frontend start\" \"yarn --cwd ./backend dev\"", 9 | "frontend": "concurrently --kill-others \"yarn --cwd ./analysis build-watch\" \"yarn --cwd ./frontend start\"", 10 | "backend" : "yarn --cwd ./backend dev", 11 | "build" : "yarn install --production=false && yarn --cwd ./backend build", 12 | "build-frontend": "yarn install --production=false && yarn --cwd ./analysis build && yarn --cwd ./frontend build" 13 | }, 14 | "author": "trulyronak", 15 | "license": "ISC", 16 | "private": true, 17 | "workspaces": [ 18 | "backend", 19 | "frontend", 20 | "analysis" 21 | ], 22 | "dependencies": { 23 | "concurrently": "^6.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import insightRouter from './insights'; 4 | import earthData from './data/earthData.json'; 5 | 6 | export const getApp = () => { 7 | const app = express(); 8 | 9 | app.use(express.urlencoded({ extended: false })); 10 | app.use(express.json()); 11 | app.use(cors( 12 | { 13 | origin: "*", 14 | optionsSuccessStatus: 200 15 | } 16 | )); 17 | // CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests 18 | app.all('*', (req, res, next) => { 19 | res.header("Access-Control-Allow-Origin", "*"); 20 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); 21 | next(); 22 | }); 23 | 24 | app.use('/insight', insightRouter); 25 | app.get('/data/earth', (req, res) => { 26 | res.json(earthData); 27 | }); 28 | 29 | return app; 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/components/SideBar/style.scss: -------------------------------------------------------------------------------- 1 | .side-bar { 2 | align-items: center; 3 | background-color: white; 4 | border-bottom: 1px solid #DFDFDF; 5 | border-radius: 0px 0px 50px 0px; 6 | border-right: 1px solid #DFDFDF; 7 | display: flex; 8 | flex-wrap: wrap; 9 | height: 100vh; 10 | justify-content: center; 11 | left: 0; 12 | position: absolute; 13 | top: 0; 14 | width: 5vw; 15 | z-index: 1000; 16 | 17 | .icons { 18 | display: flex; 19 | flex-wrap: wrap; 20 | justify-content: center; 21 | width: 100%; 22 | 23 | .icon { 24 | cursor: pointer; 25 | text-align: center; 26 | transition: 0.15s; 27 | width: 100%; 28 | margin: 10px 0px; 29 | 30 | 31 | img { 32 | width: 50%; 33 | } 34 | 35 | p { 36 | font-weight: 500; 37 | font-size: 12px; 38 | margin-top: 0; 39 | } 40 | 41 | &:hover { 42 | transform: scale(1.1); 43 | } 44 | } 45 | } 46 | 47 | .logo { 48 | position: absolute; 49 | top: 15px; 50 | } 51 | } -------------------------------------------------------------------------------- /backend/src/insights/ambee/soilLatest.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface SoilLatestData { 6 | _id: string; 7 | scantime: string; 8 | soil_temperature: number; 9 | soil_moisture: number; 10 | } 11 | 12 | interface SoilLatestResponse { 13 | error?: AmbeeError; 14 | message: string; 15 | data: SoilLatestData[]; 16 | } 17 | 18 | export const getSoilLatest = async (lat: number, lng: number): Promise => { 19 | try { 20 | const response = await axios.request({ 21 | method: 'GET', 22 | url: 'https://api.ambeedata.com/soil/latest/by-lat-lng', 23 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 24 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 25 | }); 26 | return response.data; 27 | } catch (error) { 28 | return { 29 | error, 30 | message: 'failed', 31 | } as SoilLatestResponse; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/waterVaporLatest.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface WaterVaporLatestData { 6 | _id: string; 7 | createdAt: string; 8 | water_vapor: number; 9 | } 10 | 11 | interface WaterVaporLatestResponse { 12 | error?: AmbeeError; 13 | message: string; 14 | data: WaterVaporLatestData[]; 15 | } 16 | 17 | export const getWaterVaporLatest = async (lat: number, lng: number): Promise => { 18 | try { 19 | const response = await axios.request({ 20 | method: 'GET', 21 | url: 'https://api.ambeedata.com/waterVapor/latest/by-lat-lng', 22 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 23 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 24 | }); 25 | return response.data; 26 | } catch (error) { 27 | return { 28 | error, 29 | message: 'failed', 30 | } as WaterVaporLatestResponse; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/src/assets/statistics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/SideBar/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | import './style.scss'; 4 | 5 | import logo from 'assets/logo.svg'; 6 | import map from 'assets/map.svg'; 7 | import saved from 'assets/saved.svg'; 8 | import statistics from 'assets/statistics.svg'; 9 | 10 | const SideBar = (props) => { 11 | return ( 12 | 31 | ) 32 | } 33 | 34 | export default SideBar; -------------------------------------------------------------------------------- /backend/src/insights/ambee/fireLatest.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface FireLatestData { 6 | lat: number; 7 | lon: number; 8 | confidence: string; 9 | frp: number; 10 | daynight: 'D' | 'N'; 11 | detection_time: string; 12 | distance: number; 13 | } 14 | 15 | interface FireLatestResponse { 16 | error?: AmbeeError; 17 | message: string; 18 | data: FireLatestData[]; 19 | } 20 | 21 | export const getFireLatest = async (lat: number, lng: number): Promise => { 22 | try { 23 | const response = await axios.request({ 24 | method: 'GET', 25 | url: 'https://api.ambeedata.com/latest/fire', 26 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 27 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 28 | }); 29 | return response.data; 30 | } catch (error) { 31 | return { 32 | error, 33 | message: 'failed', 34 | } as FireLatestResponse; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /frontend/src/components/Analysis/Card/style.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | background: #F9F9F9; 3 | border: 1px solid #DFDFDF; 4 | box-sizing: border-box; 5 | border-radius: 4.35px; 6 | height: 8vw; 7 | padding-top: 10px; 8 | width: 8vw; 9 | margin-right: 1.4vw; 10 | text-align: center; 11 | opacity: 0; 12 | position: relative; 13 | top: 10px; 14 | 15 | h1 { 16 | margin: 0; 17 | margin-bottom: 5px; 18 | margin-top: 2vh; 19 | font-size: 46px; 20 | line-height: 100%; 21 | } 22 | 23 | .gradient { 24 | background-size: 100%; 25 | -webkit-background-clip: text; 26 | -moz-background-clip: text; 27 | -webkit-text-fill-color: transparent; 28 | -moz-text-fill-color: transparent; 29 | } 30 | 31 | h2 { 32 | margin: 0; 33 | font-size: 20px; 34 | line-height: 100%; 35 | background-size: 100%; 36 | -webkit-background-clip: text; 37 | -moz-background-clip: text; 38 | -webkit-text-fill-color: transparent; 39 | -moz-text-fill-color: transparent; 40 | } 41 | 42 | h3 { 43 | margin: 0; 44 | line-height: 100%; 45 | } 46 | 47 | p { 48 | font-size: 13px; 49 | font-weight: 500; 50 | } 51 | } -------------------------------------------------------------------------------- /backend/src/insights/ambee/soilHistory.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface SoilHistoryData { 6 | _id: string; 7 | scantime: string; 8 | soil_temperature: number; 9 | soil_moisture: number; 10 | } 11 | 12 | interface SoilHistoryResponse { 13 | error?: AmbeeError; 14 | message: string; 15 | data: SoilHistoryData[]; 16 | } 17 | 18 | export const getSoilHistory = async ( 19 | lat: number, 20 | lng: number, 21 | from: string, 22 | to: string, 23 | ): Promise => { 24 | try { 25 | const response = await axios.request({ 26 | method: 'GET', 27 | url: 'https://api.ambeedata.com/soil/history/by-lat-lng', 28 | params: { lat: lat.toString(10), lng: lng.toString(10), from: from, to: to }, 29 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 30 | }); 31 | return response.data; 32 | } catch (error) { 33 | return { 34 | error, 35 | message: 'failed', 36 | } as SoilHistoryResponse; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/waterVaporHistory.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface WaterVaporHistoryData { 6 | _id: string; 7 | createdAt: string; 8 | water_vapor: number; 9 | } 10 | 11 | interface WaterVaporHistoryResponse { 12 | error?: AmbeeError; 13 | message: string; 14 | data: WaterVaporHistoryData[]; 15 | } 16 | 17 | export const getWaterVaporHistory = async ( 18 | lat: number, 19 | lng: number, 20 | from: string, 21 | to: string, 22 | ): Promise => { 23 | try { 24 | const response = await axios.request({ 25 | method: 'GET', 26 | url: 'https://api.ambeedata.com/waterVapor/history/by-lat-lng', 27 | params: { lat: lat.toString(10), lng: lng.toString(10), from: from, to: to }, 28 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 29 | }); 30 | return response.data; 31 | } catch (error) { 32 | return { 33 | error, 34 | message: 'failed', 35 | } as WaterVaporHistoryResponse; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Timeline/style.scss: -------------------------------------------------------------------------------- 1 | .timeline { 2 | align-items: center; 3 | background: #F9F9F9; 4 | border-top-left-radius: 6px; 5 | border-top-right-radius: 6px; 6 | bottom: -200px; 7 | display: flex; 8 | height: 9vh; 9 | left: 6vw; 10 | position: absolute; 11 | width: 30vw; 12 | z-index: 1; 13 | 14 | .info { 15 | display: flex; 16 | width: 35%; 17 | img { 18 | padding: 0 10px; 19 | } 20 | 21 | .text { 22 | h1 { 23 | font-size: 22px; 24 | font-weight: 500; 25 | line-height: 100%; 26 | margin: 0; 27 | } 28 | 29 | h2 { 30 | font-size: 13px; 31 | font-weight: 300; 32 | line-height: 100%; 33 | margin: 0; 34 | } 35 | } 36 | } 37 | 38 | .slider { 39 | padding: 0 25px 0 20px; 40 | width: 65%; 41 | } 42 | 43 | .label { 44 | font-size: 12px; 45 | } 46 | } 47 | 48 | .ant-slider-with-marks { 49 | margin-bottom: 25px; 50 | } 51 | 52 | .ant-slider-dot-active { 53 | border-color: #27AE60 !important; 54 | } 55 | 56 | .ant-slider-handle { 57 | border: solid 2px #27AE60 !important; 58 | } 59 | 60 | .ant-slider-track { 61 | background: #27AE60 !important; 62 | } -------------------------------------------------------------------------------- /frontend/src/components/NavBar/style.scss: -------------------------------------------------------------------------------- 1 | .nav-bar { 2 | align-items: center; 3 | background-color: white; 4 | border-bottom: 1px solid #DFDFDF; 5 | box-sizing: border-box; 6 | display: flex; 7 | height: 8vh; 8 | padding-left: 5vw; 9 | width: 100vw; 10 | z-index: 999 !important; 11 | position: relative; 12 | 13 | .logo { 14 | align-items: center; 15 | border-right: 1px solid #DFDFDF; 16 | display: flex; 17 | font-size: 36px; 18 | font-weight: 500; 19 | height: 100%; 20 | padding: 0 20px; 21 | } 22 | 23 | .name-input { 24 | border: none; 25 | font-size: 24px; 26 | padding: 0 20px; 27 | 28 | &:focus{ 29 | outline: none; 30 | } 31 | } 32 | 33 | .search-bar { 34 | align-items: center; 35 | display: flex; 36 | position: relative; 37 | 38 | img { 39 | left: 10px; 40 | position: absolute; 41 | width: 24px; 42 | } 43 | 44 | input { 45 | background: #F9F9F9; 46 | border: none; 47 | border-radius: 6px; 48 | color: #333333; 49 | font-size: 18px; 50 | padding: 10px 20px 10px 44px; 51 | width: 40vw; 52 | 53 | &:focus{ 54 | outline: none; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /frontend/src/assets/irrigation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/components/Analysis/Breakdowns/style.scss: -------------------------------------------------------------------------------- 1 | .Breakdowns { 2 | display: inline-block; 3 | background: #f9f9f9; 4 | border: 1px solid #dfdfdf; 5 | box-sizing: border-box; 6 | border-radius: 4.35px; 7 | height: 17vh; 8 | width: 26.8vw; 9 | margin-top: 2vh; 10 | margin-left: 1.4vw; 11 | padding: 5px 10px; 12 | overflow: scroll; 13 | -ms-overflow-style: none; /* IE and Edge */ 14 | scrollbar-width: none; /* Firefox */ 15 | h3 { 16 | font-size: 18px; 17 | } 18 | 19 | .subtitle { 20 | margin: 0px; 21 | margin-bottom: 12px; 22 | line-height: 0px; 23 | } 24 | 25 | 26 | h1 { 27 | font-size: 5em; 28 | } 29 | 30 | .row { 31 | display: flex; 32 | justify-content: space-between; 33 | 34 | .grade { 35 | width: 30%; 36 | display: flex; 37 | flex-direction: column; 38 | align-items: center; 39 | } 40 | .explanation { 41 | width: 65%; 42 | margin-top: 12px; 43 | } 44 | } 45 | 46 | .gradient { 47 | background-size: 100%; 48 | -webkit-background-clip: text; 49 | -moz-background-clip: text; 50 | -webkit-text-fill-color: transparent; 51 | -moz-text-fill-color: transparent; 52 | } 53 | } 54 | 55 | .Breakdowns::-webkit-scrollbar { 56 | display: none; 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/components/Landing/index.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from 'antd'; 2 | import { useHistory } from 'react-router-dom'; 3 | 4 | import './style.scss'; 5 | 6 | 7 | const Landing = () => { 8 | let history = useHistory(); 9 | 10 | const onSearch = (value) => { 11 | history.push('/home'); 12 | }; 13 | 14 | return ( 15 |
16 |
17 |
    18 |
  • About Us
  • 19 |
  • Demo
  • 20 |
  • Resources
  • 21 |
22 |
23 |

24 | Reimagine 25 |
26 | Farm Planning 27 |

28 |
29 |
30 |

Plan your next farm years ahead.

31 |

Get valuable insights on our data analytics.

32 |
33 |
34 |

Location

35 | 43 |
44 |
45 |
46 | ); 47 | }; 48 | 49 | export default Landing; 50 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/airQuality.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface AirQualityInfo { 6 | pollutant: string; 7 | concentration: number; 8 | category: string; 9 | } 10 | 11 | interface AirQualityStation { 12 | NO2: number; 13 | PM10: number; 14 | PM25: number; 15 | CO: number; 16 | SO2: number; 17 | OZONE: number; 18 | AQI: number; 19 | updatedAt: string; 20 | aqiInfo: AirQualityInfo; 21 | } 22 | 23 | interface AirQualityResponse { 24 | error?: AmbeeError; 25 | message: string; 26 | stations: AirQualityStation[]; 27 | } 28 | 29 | export const getAirQuality = async (lat: number, lng: number): Promise => { 30 | try { 31 | const response = await axios.request({ 32 | url: 'https://api.ambeedata.com/latest/by-lat-lng', 33 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 34 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 35 | }); 36 | return response.data; 37 | } catch (error) { 38 | return { 39 | error, 40 | message: 'failed', 41 | stations: [], 42 | }; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/pollenLatest.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface PollenCount { 6 | grass_pollen: number; 7 | tree_pollen: number; 8 | weed_pollen: number; 9 | } 10 | 11 | interface PollenRisk { 12 | grass_pollen: string; 13 | tree_pollen: string; 14 | weed_pollen: string; 15 | } 16 | 17 | interface PollenLatestData { 18 | Count: PollenCount; 19 | Risk: PollenRisk; 20 | } 21 | 22 | interface PollenLatestResponse { 23 | error?: AmbeeError; 24 | message: string; 25 | data: PollenLatestData[]; 26 | } 27 | 28 | export const getPollenLatest = async (lat: number, lng: number): Promise => { 29 | try { 30 | const response = await axios.request({ 31 | method: 'GET', 32 | url: 'https://api.ambeedata.com/latest/pollen/by-lat-lng', 33 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 34 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 35 | }); 36 | return response.data; 37 | } catch (error) { 38 | return { 39 | error, 40 | message: 'failed', 41 | } as PollenLatestResponse; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/Breakdowns/style.scss: -------------------------------------------------------------------------------- 1 | .Breakdowns-Stats { 2 | border: 2px solid #dfdfdf; 3 | border-radius: 6px; 4 | padding: 10px; 5 | background-color: #f9f9f9; 6 | margin: 10px; 7 | width: 470px; 8 | height: 315px; 9 | 10 | h3 { 11 | font-size: 18px; 12 | } 13 | 14 | .subtitle { 15 | margin: 0px; 16 | margin-bottom: 12px; 17 | line-height: 0px; 18 | } 19 | 20 | 21 | .criteria { 22 | margin: 0; 23 | text-align: center; 24 | } 25 | 26 | h1 { 27 | font-size: 7.5em; 28 | } 29 | 30 | .row { 31 | display: flex; 32 | justify-content: space-between; 33 | 34 | .grade { 35 | width: 40%; 36 | display: flex; 37 | flex-direction: column; 38 | align-items: center; 39 | justify-content: center; 40 | margin-bottom: 0px; 41 | margin-top: 2vh; 42 | } 43 | .explanation { 44 | width: 65%; 45 | margin-top: 12px; 46 | margin-top: 2vh; 47 | } 48 | } 49 | 50 | .gradient { 51 | background-size: 100%; 52 | -webkit-background-clip: text; 53 | -moz-background-clip: text; 54 | -webkit-text-fill-color: transparent; 55 | -moz-text-fill-color: transparent; 56 | margin-bottom: 0px; 57 | line-height: 7rem; 58 | } 59 | } 60 | 61 | .Breakdowns::-webkit-scrollbar { 62 | display: none; 63 | } 64 | -------------------------------------------------------------------------------- /frontend/src/components/BigCurrentlySelected/style.scss: -------------------------------------------------------------------------------- 1 | .big-currently-selected { 2 | background-position: top; 3 | background-size: contain; 4 | border-radius: 6px; 5 | bottom: 1vh; 6 | height: 44vh; 7 | position: absolute; 8 | right: calc(1vw - 1200px); 9 | width: 18vw; 10 | 11 | .information { 12 | border-bottom-left-radius: 6px; 13 | border-bottom-right-radius: 6px; 14 | bottom: 0; 15 | height: 70%; 16 | padding: 10px; 17 | position: absolute; 18 | 19 | h1 { 20 | color: white; 21 | font-size: 24px; 22 | font-weight: 500; 23 | line-height: 100%; 24 | margin: 0; 25 | } 26 | h2 { 27 | color: white; 28 | font-size: 18px; 29 | font-weight: 300; 30 | line-height: 100%; 31 | margin: 0; 32 | margin-bottom: 10px; 33 | } 34 | h3 { 35 | color: white; 36 | font-size: 14px; 37 | line-height: 100%; 38 | margin: 0; 39 | } 40 | p { 41 | color: white; 42 | font-size: 14px; 43 | line-height: 100%; 44 | font-weight: 300; 45 | margin: 0; 46 | } 47 | 48 | .section { 49 | margin-bottom: 10px; 50 | 51 | &.cost { 52 | display: inline-block; 53 | margin-right: 10px; 54 | } 55 | &.state { 56 | display: inline-block; 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /backend/src/insights/ambee/weatherLatest.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface WeatherLatestData { 6 | time: number; 7 | temperature: number; 8 | apparentTemperature: number; 9 | dewPoint: number; 10 | humidity: number; 11 | pressure: number; 12 | windSpeed: number; 13 | windGust: number; 14 | windBearing: number; 15 | cloudCover: number; 16 | visibility: number; 17 | ozone: number; 18 | lat: number; 19 | lng: number; 20 | } 21 | 22 | interface WeatherLatestResponse { 23 | error?: AmbeeError; 24 | message: string; 25 | data: WeatherLatestData; 26 | } 27 | 28 | export const getWeatherLatest = async (lat: number, lng: number): Promise => { 29 | try { 30 | const response = await axios.request({ 31 | method: 'GET', 32 | url: 'https://api.ambeedata.com/weather/latest/by-lat-lng', 33 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 34 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 35 | }); 36 | return response.data; 37 | } catch (error) { 38 | return { 39 | error, 40 | message: 'failed', 41 | } as WeatherLatestResponse; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/src/components/Analysis/Breakdowns/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import './style.scss'; 4 | const Breakdowns = (props) => { 5 | const [color1, setColor1] = useState('#7DBDF9'); 6 | const [color2, setColor2] = useState('#4091DC'); 7 | useEffect(() => { 8 | if (props.grade === 'A' || props.grade === 'B') { 9 | setColor1('#E9FF60'); 10 | setColor2('#04D600'); 11 | } else if (props.grade === 'C') { 12 | setColor1('#E9FF60'); 13 | setColor2('#EF8B2E'); 14 | } else if (props.grade === 'D' || props.grade === 'F') { 15 | setColor1('#DC4040'); 16 | setColor2('#E3B744'); 17 | } 18 | }, [props.grade]) 19 | 20 | return ( 21 |
22 |

23 |

24 | Grade calculated by realtime data on your location. 25 |

26 |
27 |
28 |

34 | {props.grade} 35 |

36 |
37 |
{props.explanation}
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Breakdowns; 44 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/pollenForecast.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface PollenCount { 6 | grass_pollen: number; 7 | tree_pollen: number; 8 | weed_pollen: number; 9 | } 10 | 11 | interface PollenRisk { 12 | grass_pollen: string; 13 | tree_pollen: string; 14 | weed_pollen: string; 15 | } 16 | 17 | interface PollenForecastData { 18 | time: number; 19 | lat: number; 20 | lng: number; 21 | Count: PollenCount; 22 | Risk: PollenRisk; 23 | } 24 | 25 | interface PollenForecastResponse { 26 | error?: AmbeeError; 27 | message: string; 28 | data: PollenForecastData[]; 29 | } 30 | 31 | export const getPollenForecast = async (lat: number, lng: number): Promise => { 32 | try { 33 | const response = await axios.request({ 34 | method: 'GET', 35 | url: 'https://api.ambeedata.com/forecast/pollen/by-lat-lng', 36 | params: { lat: lat.toString(10), lng: lng.toString(10) }, 37 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 38 | }); 39 | return response.data; 40 | } catch (error) { 41 | return { 42 | error, 43 | message: 'failed', 44 | } as PollenForecastResponse; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/pollenHistory.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface PollenCount { 6 | grass_pollen: number; 7 | tree_pollen: number; 8 | weed_pollen: number; 9 | } 10 | 11 | interface PollenRisk { 12 | grass_pollen: string; 13 | tree_pollen: string; 14 | weed_pollen: string; 15 | } 16 | 17 | interface PollenHistoryData { 18 | Count: PollenCount; 19 | Risk: PollenRisk; 20 | } 21 | 22 | interface PollenHistoryResponse { 23 | error?: AmbeeError; 24 | message: string; 25 | data: PollenHistoryData[]; 26 | } 27 | 28 | export const getPollenHistory = async ( 29 | lat: number, 30 | lng: number, 31 | from: string, 32 | to: string, 33 | ): Promise => { 34 | try { 35 | const response = await axios.request({ 36 | method: 'GET', 37 | url: 'https://api.ambeedata.com/history/pollen/by-lat-lng', 38 | params: { lat: lat.toString(10), lng: lng.toString(10), from: from, to: to }, 39 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 40 | }); 41 | return response.data; 42 | } catch (error) { 43 | return { 44 | error, 45 | message: 'failed', 46 | } as PollenHistoryResponse; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/airQualityHistory.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface AirQualityHistoryData { 6 | NO2: number; 7 | PM10: number; 8 | PM25: number; 9 | CO: number; 10 | SO2: number; 11 | OZONE: number; 12 | AQI: number; 13 | lat: number; 14 | lng: number; 15 | createdAt: string; 16 | postalCode: number; 17 | majorPollutant: string; 18 | } 19 | 20 | interface AirQualityHistoryResponse { 21 | error?: AmbeeError; 22 | data: AirQualityHistoryData[]; 23 | } 24 | 25 | export const getAirQualityHistory = async ( 26 | lat: number, 27 | lng: number, 28 | from: string, 29 | to: string, 30 | ): Promise => { 31 | try { 32 | const response = await axios.request({ 33 | method: 'GET', 34 | url: 'https://api.ambeedata.com/history/by-lat-lng', 35 | params: { 36 | lat: lat.toString(10), 37 | lng: lng.toString(10), 38 | from, 39 | to, 40 | }, 41 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 42 | }); 43 | return response.data; 44 | } catch (error) { 45 | return { 46 | error, 47 | } as AirQualityHistoryResponse; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/weatherHistory.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface WeatherHistoryData { 6 | time: number; 7 | temperature: number; 8 | apparentTemperature: number; 9 | dewPoint: number; 10 | humidity: number; 11 | pressure: number; 12 | windSpeed: number; 13 | windGust: number; 14 | windBearing: number; 15 | cloudCover: number; 16 | visibility: number; 17 | ozone: number; 18 | } 19 | 20 | interface WeatherHistoryResponse { 21 | error?: AmbeeError; 22 | status: string; 23 | data: WeatherHistoryData; 24 | } 25 | 26 | export const getWeatherHistory = async ( 27 | lat: number, 28 | lng: number, 29 | from: string, 30 | to: string, 31 | ): Promise => { 32 | try { 33 | const response = await axios.request({ 34 | method: 'GET', 35 | url: 'https://api.ambeedata.com/weather/history/by-lat-lng', 36 | params: { lat: lat.toString(10), lng: lng.toString(10), from: from, to: to }, 37 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 38 | }); 39 | return response.data; 40 | } catch (error) { 41 | return { 42 | error, 43 | status: 'failed', 44 | } as WeatherHistoryResponse; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@mapbox/mapbox-gl-draw": "^1.2.2", 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "antd": "^4.15.1", 11 | "analysis" : "1.0.0", 12 | "gsap": "^3.6.1", 13 | "mapbox": "^1.0.0-beta10", 14 | "mapbox-gl": "^2.2.0", 15 | "mapbox-gl-draw-rectangle-restrict-area": "^3.1.5", 16 | "mapboxgl-draw-rectangle-drag": "^1.0.1", 17 | "react": "^17.0.2", 18 | "react-dom": "^17.0.2", 19 | "react-draggable": "^4.4.3", 20 | "react-mapbox-draw-rectangle": "^0.0.5", 21 | "react-mapbox-gl": "^5.1.1", 22 | "react-router-dom": "^5.2.0", 23 | "react-scripts": "4.0.3", 24 | "recharts": "^2.0.9", 25 | "sass": "^1.32.10", 26 | "three": "^0.115.0", 27 | "web-vitals": "^1.0.1" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/components/Timeline/index.jsx: -------------------------------------------------------------------------------- 1 | import { Slider } from 'antd'; 2 | 3 | import './style.scss'; 4 | import timer from 'assets/timer.svg'; 5 | 6 | const marks = { 7 | 336: { 8 | label:

Spring

, 9 | }, 10 | 63: { 11 | label:

Summer

, 12 | }, 13 | 157: { 14 | label:

Fall

, 15 | }, 16 | 247: { 17 | label:

Winter

, 18 | }, 19 | }; 20 | 21 | const Timeline = (props) => { 22 | const onChange = (value) => { 23 | props.setDay((108 + value)); 24 | Object.values(props.objects).forEach((object) => { 25 | console.log(props.objects); 26 | console.log('testtt'); 27 | console.log(object.day); 28 | const dayDiff = props.day - object.day; 29 | console.log(dayDiff); 30 | object.model.scale.y = Math.max((dayDiff * 2) + 100, -1); 31 | object.model.position.y = Math.max((((100 / 3.28084) / 100) * ((dayDiff * 1) + 50)), -1); 32 | }); 33 | } 34 | 35 | return ( 36 |
37 |
38 | timer icon 39 |
40 |

Timeline

41 |

Over Seasons

42 |
43 |
44 |
45 | 46 |
47 |
48 | ) 49 | } 50 | 51 | export default Timeline; -------------------------------------------------------------------------------- /frontend/src/components/Seasons/style.scss: -------------------------------------------------------------------------------- 1 | .seasons { 2 | align-items: center; 3 | border-radius: 6px; 4 | display: flex; 5 | height: 9vh; 6 | left: 6vw; 7 | position: absolute; 8 | top: calc(9vh - 200px); 9 | width: 45vw; 10 | z-index: 800; 11 | white-space: nowrap; 12 | 13 | h1 { 14 | color: white; 15 | font-weight: 500; 16 | line-height: 100%; 17 | margin: 0; 18 | } 19 | 20 | h2 { 21 | color: white; 22 | font-weight: 300; 23 | line-height: 100%; 24 | margin: 0; 25 | } 26 | 27 | .season { 28 | padding: 0 20px; 29 | width: 28%; 30 | h1 { 31 | font-size: 24px; 32 | } 33 | h2 { 34 | font-size: 18px; 35 | } 36 | } 37 | 38 | .temperature { 39 | width: 20%; 40 | padding: 0 10px; 41 | h1 { 42 | font-size: 16px; 43 | margin-top: 5px; 44 | } 45 | h2 { 46 | font-size: 18px; 47 | } 48 | } 49 | 50 | .weather { 51 | align-items: center; 52 | display: flex; 53 | justify-content: center; 54 | position: relative; 55 | width: 50%; 56 | 57 | .weather-entry { 58 | align-items: center; 59 | display: flex; 60 | justify-content: center; 61 | padding: 0 5px; 62 | width: 20%; 63 | 64 | img { 65 | display: inline-block; 66 | } 67 | .text { 68 | display: inline-block; 69 | h1 { 70 | font-size: 18px; 71 | } 72 | h2 { 73 | font-size: 10px; 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /frontend/src/components/Statistics/Breakdowns/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import './style.scss'; 4 | 5 | const Breakdowns = (props) => { 6 | const [color1, setColor1] = useState('#7DBDF9'); 7 | const [color2, setColor2] = useState('#4091DC'); 8 | useEffect(() => { 9 | if (props.grade === 'A' || props.grade === 'B') { 10 | setColor1('#E9FF60'); 11 | setColor2('#04D600'); 12 | } else if (props.grade === 'C') { 13 | setColor1('#E9FF60'); 14 | setColor2('#EF8B2E'); 15 | } else if (props.grade === 'D' || props.grade === 'F') { 16 | setColor1('#DC4040'); 17 | setColor2('#E3B744'); 18 | } 19 | }, [props.grade]) 20 | 21 | return ( 22 |
23 |

{props.title}

24 |

25 | Grade calculated by realtime data on your location. 26 |

27 |
28 |
29 |

35 | {props.grade} 36 |

37 |

38 | {props.criteria} 39 | 40 |

41 |
42 |
{props.explanation}
43 |
44 |
45 | ); 46 | }; 47 | 48 | export default Breakdowns; 49 | -------------------------------------------------------------------------------- /backend/src/insights/ambee/weatherForecast.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { AMBEE_API_KEY } from 'src/configuration'; 3 | import { AmbeeError } from '.'; 4 | 5 | interface WeatherForecastData { 6 | lat: number; 7 | lng: number; 8 | forecast: WeatherForecast[]; 9 | } 10 | 11 | interface WeatherForecast { 12 | time: number; 13 | precipIntensity: number; 14 | precipProbability: number; 15 | precipType: string; 16 | temperature: number; 17 | apparentTemperature: number; 18 | dewPoint: number; 19 | humidity: number; 20 | pressure: number; 21 | windSpeed: number; 22 | windGust: number; 23 | windBearing: number; 24 | cloudCover: number; 25 | uvIndex: number; 26 | visibility: number; 27 | ozone: number; 28 | } 29 | 30 | interface WeatherForecastResponse { 31 | error?: AmbeeError; 32 | message: string; 33 | data: WeatherForecastData; 34 | } 35 | 36 | export const getWeatherForecast = async ( 37 | lat: number, 38 | lng: number, 39 | filter: string = 'daily', 40 | ): Promise => { 41 | try { 42 | const response = await axios.request({ 43 | method: 'GET', 44 | url: 'https://api.ambeedata.com/weather/forecast/by-lat-lng', 45 | params: { lat: lat.toString(10), lng: lng.toString(10), filter }, 46 | headers: { 'x-api-key': AMBEE_API_KEY, 'Content-type': 'application/json' }, 47 | }); 48 | return response.data; 49 | } catch (error) { 50 | return { 51 | error, 52 | message: 'failed', 53 | } as WeatherForecastResponse; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /frontend/src/assets/cloudy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/Landing/style.scss: -------------------------------------------------------------------------------- 1 | .Landing { 2 | height: 100vh; 3 | width: 100vw; 4 | background-image: url('../../assets/homebg.png'); 5 | background-repeat: no-repeat; 6 | background-size: cover; 7 | padding: 50px; 8 | color: white; 9 | font-family: 'Helvetica', sans-serif; 10 | 11 | .space { 12 | h3 { 13 | color: white; 14 | font-size: 24px; 15 | margin-bottom: 0px; 16 | } 17 | 18 | margin: 40px 0px 10px 0px; 19 | } 20 | 21 | .label { 22 | color: white; 23 | } 24 | 25 | .signup { 26 | background: rgba(255, 255, 255, 0.05); 27 | padding: 25px; 28 | width: 500px; 29 | backdrop-filter: blur(5px); 30 | border-radius: 12px; 31 | position: absolute; 32 | top: 33%; 33 | right: 10%; 34 | h2 { 35 | color: white; 36 | font-size: 48px; 37 | font-weight: 600; 38 | line-height: 48px; 39 | } 40 | 41 | p { 42 | font-size: 18px; 43 | font-weight: 300; 44 | } 45 | } 46 | 47 | ul { 48 | list-style-type: none; 49 | overflow: hidden; 50 | display: flex; 51 | li { 52 | padding: 0px 40px; 53 | font-size: 22px; 54 | font-weight: 400; 55 | } 56 | 57 | .about { 58 | padding-left: 0px; 59 | } 60 | } 61 | 62 | h1 { 63 | font-size: 82px; 64 | font-family: 'Helvetica'; 65 | font-weight: 600; 66 | line-height: 90px; 67 | position: absolute; 68 | bottom: 5%; 69 | color: white; 70 | } 71 | 72 | .search { 73 | border-radius: 12px; 74 | } 75 | 76 | .ant-input-group-addon { 77 | border-radius: 12px; 78 | } 79 | 80 | .ant-input-group { 81 | opacity: 0.5; 82 | } 83 | 84 | .ant-input-group:hover { 85 | transition-duration: 1s; 86 | opacity: 0.9; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /frontend/src/components/Analysis/style.scss: -------------------------------------------------------------------------------- 1 | .analysis { 2 | position: absolute; 3 | top: 10vh; 4 | height: 50vh; 5 | left: calc(52vw + 1200px); 6 | width: 48vw; 7 | 8 | .cards { 9 | align-items: center; 10 | display: flex; 11 | justify-content: center; 12 | margin-top: 20px; 13 | } 14 | 15 | .heading { 16 | align-items: center; 17 | display: flex; 18 | 19 | .text { 20 | margin-left: 10px; 21 | 22 | h1 { 23 | font-size: 36px; 24 | font-weight: 500; 25 | line-height: 100%; 26 | margin: 0; 27 | } 28 | 29 | h2 { 30 | font-size: 18px; 31 | font-weight: 300; 32 | line-height: 100%; 33 | margin: 0; 34 | } 35 | } 36 | } 37 | 38 | .air-quality { 39 | display: inline-block; 40 | opacity: 0; 41 | background: #F9F9F9; 42 | border: 1px solid #DFDFDF; 43 | box-sizing: border-box; 44 | border-radius: 4.35px; 45 | height: 17vh; 46 | width: 17.4vw; 47 | margin-top: calc(2vh + 10px); 48 | margin-left: .6vw; 49 | padding: 5px 10px; 50 | overflow: scroll; 51 | -ms-overflow-style: none; /* IE and Edge */ 52 | scrollbar-width: none; /* Firefox */ 53 | 54 | &::-webkit-scrollbar { 55 | display: none; 56 | } 57 | 58 | h3 { 59 | font-size: 18px; 60 | } 61 | 62 | .section { 63 | position: relative; 64 | width: 100%; 65 | p { 66 | margin: 0px 0; 67 | line-height: 100%; 68 | } 69 | 70 | .score { 71 | right: 0; 72 | top: 0; 73 | position: absolute; 74 | margin: 0; 75 | } 76 | 77 | hr { 78 | border: 1px solid #DFDFDF; 79 | } 80 | } 81 | } 82 | 83 | .Breakdowns { 84 | opacity: 0; 85 | position: relative; 86 | } 87 | } -------------------------------------------------------------------------------- /frontend/src/components/PlotType/style.scss: -------------------------------------------------------------------------------- 1 | .plot-type { 2 | position: absolute; 3 | bottom: 0; 4 | height: 44vh; 5 | left: calc(52vw + 1200px); 6 | width: 28vw; 7 | 8 | .filter { 9 | margin: auto; 10 | margin-top: 10px; 11 | position: relative; 12 | width: 95%; 13 | 14 | .select { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | } 19 | } 20 | 21 | .heading { 22 | align-items: center; 23 | display: flex; 24 | 25 | .text { 26 | margin-left: 10px; 27 | 28 | h1 { 29 | font-size: 36px; 30 | font-weight: 500; 31 | line-height: 100%; 32 | margin: 0; 33 | } 34 | 35 | h2 { 36 | font-size: 18px; 37 | font-weight: 300; 38 | line-height: 100%; 39 | margin: 0; 40 | } 41 | } 42 | } 43 | 44 | .search-bar { 45 | align-items: center; 46 | display: flex; 47 | position: relative; 48 | margin-top: 15px; 49 | right: -2.5%; 50 | 51 | img { 52 | left: 10px; 53 | position: absolute; 54 | width: 24px; 55 | } 56 | 57 | input { 58 | background: #F9F9F9; 59 | border: none; 60 | border-radius: 6px; 61 | color: #333333; 62 | font-size: 18px; 63 | padding: 10px 20px 10px 44px; 64 | width: 95%; 65 | 66 | &:focus{ 67 | outline: none; 68 | } 69 | } 70 | } 71 | 72 | .plots { 73 | bottom: 0; 74 | height: 26vh; 75 | margin: auto; 76 | margin-top: 20px; 77 | overflow: scroll; 78 | padding: 10px 2.5%; 79 | position: absolute; 80 | scrollbar-width: none; 81 | width: 100%; 82 | -ms-overflow-style: none; 83 | 84 | .plot { 85 | opacity: 100; 86 | } 87 | 88 | &::-webkit-scrollbar { 89 | display: none; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /frontend/src/components/Plot/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import './style.scss'; 4 | 5 | import crop from 'assets/crop.svg'; 6 | import irrigation from 'assets/irrigation.svg'; 7 | 8 | const Plot = (props) => { 9 | const [borderColor, setBorderColor] = useState('#13D181'); 10 | const [color, setColor] = useState('rgba(39, 174, 96, 1)'); 11 | 12 | useEffect(() => { 13 | if (props.type === 'crop') { 14 | if (props.state === 'Spring' || props.state === 'Summer') { 15 | setColor('rgba(39, 174, 96, 1)'); 16 | setBorderColor('#13D181'); 17 | } else { 18 | setColor('rgba(237, 193, 81, 1)'); 19 | setBorderColor('#EDC151'); 20 | } 21 | } 22 | else { 23 | setColor('rgba(64, 145, 220, 1)'); 24 | setBorderColor('#4091DC'); 25 | } 26 | }, [props.type, props.state]) 27 | 28 | return ( 29 |
38 | 39 |
40 |

{(props.type === 'crop') ? 'Crop' : 'Irrigation'}

41 |

{props.name}

42 |
43 |
44 |
45 |

Earth Score

46 |

{props.earthScore}/100

47 |
48 |
49 |
50 |

{(props.type === 'crop') ? 'Season' : 'Maintenance'}

51 |

{props.state}

52 |
53 |
54 | ) 55 | } 56 | 57 | export default Plot; -------------------------------------------------------------------------------- /backend/src/utils/getLocGrade.ts: -------------------------------------------------------------------------------- 1 | 2 | interface Grade { 3 | score: Score; 4 | description: string 5 | } 6 | enum Score { 7 | "F" = 0, 8 | "D", 9 | "D+", 10 | "C", 11 | "C+", 12 | "B", 13 | "B+", 14 | "A", 15 | "A+" 16 | } 17 | 18 | const scoreToDescription = (score: Score): string => { 19 | switch(score) { 20 | case Score.A, Score["A+"]: return "Air quality is satisfactory, and air pollution poses little or no risk." 21 | case Score.B, Score["B+"]: return "Air quality is acceptable. However, there may be a risk for some people, particularly those who are unusually sensitive to air pollution." 22 | case Score.C, Score["C+"]: return "Members of sensitive groups may experience health effects. The general public is less likely to be affected." 23 | case Score.D, Score["D+"]: return "Some members of the general public may experience health effects; members of sensitive groups may experience more serious health effects." 24 | case Score.F: return "Health alert: The risk of health effects is increased for everyone." 25 | } 26 | return "Air quality is satisfactory, and air pollution poses little or no risk." 27 | } 28 | 29 | export const numToGrade = (score: number): Grade => { 30 | score = Math.round(score); 31 | return { 32 | score: score, 33 | description: scoreToDescription(score) 34 | } 35 | } 36 | 37 | export const getGrade = (aqi: number): Score => { 38 | if (aqi < 37.5) return Score["A+"]; 39 | else if (aqi < 75) return Score["A"]; 40 | else if (aqi < 112.5) return Score["B+"]; 41 | else if (aqi < 150) return Score["B"]; 42 | else if (aqi < 187.5) return Score["C+"]; 43 | else if (aqi < 225) return Score["C"]; 44 | else if (aqi < 262.5) return Score["D+"]; 45 | else if (aqi < 300) return Score["D"]; 46 | else return Score["F"]; 47 | } -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node -r ./tsconfig-paths-bootstrap.js .", 9 | "start:dev": "node -r dotenv/config -r tsconfig-paths/register -r ts-node/register ./src/index.ts", 10 | "dev": "nodemon", 11 | "test:unit": "mocha --recursive -r tsconfig-paths/register -r ts-node/register -r source-map-support/register src/**/*.spec.ts", 12 | "test:lint": "eslint --ext .ts ./src", 13 | "test:lint:fix": "npm run test:lint -- --fix", 14 | "test": "npm run test:lint && npm run test:unit" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "axios": "^0.21.1", 21 | "body-parser": "^1.19.0", 22 | "cors": "^2.8.5", 23 | "express": "^4.17.1", 24 | "express-validator": "^6.10.0", 25 | "moment": "^2.29.1" 26 | }, 27 | "devDependencies": { 28 | "@types/body-parser": "^1.17.1", 29 | "@types/chai": "^4.2.3", 30 | "@types/cors": "^2.8.10", 31 | "@types/dotenv": "^6.1.1", 32 | "@types/express": "^4.17.1", 33 | "@types/mocha": "^5.2.7", 34 | "@types/supertest": "^2.0.8", 35 | "@typescript-eslint/eslint-plugin": "^2.3.0", 36 | "@typescript-eslint/parser": "^2.3.0", 37 | "chai": "^4.2.0", 38 | "dotenv": "^8.1.0", 39 | "eslint": "^6.4.0", 40 | "eslint-config-prettier": "^6.3.0", 41 | "eslint-plugin-prettier": "^3.1.1", 42 | "mocha": "^6.2.0", 43 | "nodemon": "^1.19.2", 44 | "prettier": "^1.18.2", 45 | "supertest": "^4.0.2", 46 | "ts-node": "^8.4.1", 47 | "tsconfig-paths": "^3.9.0", 48 | "typescript": "^3.6.3" 49 | }, 50 | "nodemonConfig": { 51 | "ignore": [ 52 | "**/*.spec.ts", 53 | ".git", 54 | "node_modules" 55 | ], 56 | "watch": [ 57 | "src" 58 | ], 59 | "exec": "npm run start:dev", 60 | "ext": "ts" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/EstimatedFinancials/index.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | BarChart, 3 | Bar, 4 | Cell, 5 | XAxis, 6 | YAxis, 7 | CartesianGrid, 8 | Tooltip, 9 | Legend, 10 | ReferenceLine, 11 | ResponsiveContainer, 12 | } from 'recharts'; 13 | 14 | //import * as Analysis from "analysis" 15 | 16 | import './style.scss'; 17 | 18 | const EstimatedFinancials = (props) => { 19 | 20 | const result = Analysis.determineSpendingAndRevenue(props.statsData) 21 | 22 | const data = [ 23 | { 24 | name: 'Spring 2023', 25 | }, 26 | { 27 | name: 'Summer 2023', 28 | }, 29 | { 30 | name: 'Fall 2023', 31 | }, 32 | { 33 | name: 'Winter 2023', 34 | }, 35 | ]; 36 | 37 | for (let j = 0; j < data.length; j++) { 38 | data[j].spending = -result.spending[j].toFixed(2) 39 | data[j].revenue = result.revenue[j].toFixed(2) 40 | } 41 | 42 | return ( 43 |
44 |

Estimated Financials

45 |

Based off location and current harvest

46 | 47 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | ); 73 | }; 74 | 75 | export default EstimatedFinancials; 76 | -------------------------------------------------------------------------------- /analysis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "analysis", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "build-watch": "nodemon --exec yarn build", 9 | "start": "node -r ./tsconfig-paths-bootstrap.js .", 10 | "start:dev": "node -r dotenv/config -r tsconfig-paths/register -r ts-node/register ./src/index.ts", 11 | "dev": "nodemon", 12 | "test:unit": "mocha --recursive -r tsconfig-paths/register -r ts-node/register -r source-map-support/register src/**/*.spec.ts", 13 | "test:lint": "eslint --ext .ts ./src", 14 | "test:lint:fix": "npm run test:lint -- --fix", 15 | "test": "npm run test:lint && npm run test:unit" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "animejs": "^3.2.1", 22 | "axios": "^0.21.1", 23 | "body-parser": "^1.19.0", 24 | "cors": "^2.8.5", 25 | "express": "^4.17.1", 26 | "express-validator": "^6.10.0", 27 | "moment": "^2.29.1" 28 | }, 29 | "devDependencies": { 30 | "@types/body-parser": "^1.17.1", 31 | "@types/chai": "^4.2.3", 32 | "@types/cors": "^2.8.10", 33 | "@types/dotenv": "^6.1.1", 34 | "@types/express": "^4.17.1", 35 | "@types/mocha": "^5.2.7", 36 | "@types/supertest": "^2.0.8", 37 | "@typescript-eslint/eslint-plugin": "^2.3.0", 38 | "@typescript-eslint/parser": "^2.3.0", 39 | "chai": "^4.2.0", 40 | "dotenv": "^8.1.0", 41 | "eslint": "^6.4.0", 42 | "eslint-config-prettier": "^6.3.0", 43 | "eslint-plugin-prettier": "^3.1.1", 44 | "mocha": "^6.2.0", 45 | "nodemon": "^1.19.2", 46 | "prettier": "^1.18.2", 47 | "supertest": "^4.0.2", 48 | "ts-node": "^8.4.1", 49 | "tsconfig-paths": "^3.9.0", 50 | "typescript": "^3.6.3" 51 | }, 52 | "nodemonConfig": { 53 | "ignore": [ 54 | "**/*.spec.ts", 55 | ".git", 56 | "node_modules" 57 | ], 58 | "watch": [ 59 | "src" 60 | ], 61 | "exec": "npm run start:dev", 62 | "ext": "ts" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 21 | 22 | 31 | drishti 32 | 33 | 34 | 35 |
36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/assets/timer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/EnvironmentalStrain/index.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | AreaChart, 3 | Area, 4 | XAxis, 5 | YAxis, 6 | CartesianGrid, 7 | Tooltip, 8 | Legend, 9 | ResponsiveContainer, 10 | } from 'recharts'; 11 | //import * as Analysis from "analysis"; 12 | import './style.scss'; 13 | 14 | const EnvironmentalStrain = (props) => { 15 | const result = Analysis.determineWaterUsageAndYield(props.statsData) 16 | const data = [ 17 | { 18 | name: 'Spring 2023', 19 | }, 20 | { 21 | name: 'Summer 2023', 22 | }, 23 | { 24 | name: 'Fall 2023', 25 | }, 26 | { 27 | name: 'Winter 2023', 28 | }, 29 | ]; 30 | 31 | for (let j = 0; j < data.length; j++) { 32 | data[j].water = result.waterUsage[j].toFixed(2) 33 | data[j].yield = result.yield[j].toFixed(2) / 10000 34 | } 35 | 36 | return ( 37 |
38 |

Environmental Strain

39 |

Plotted on 2 key metrics

40 | 41 | 50 | 51 | 57 | 58 | 59 | 60 | 67 | 74 | 75 | 76 |
77 | ); 78 | }; 79 | 80 | export default EnvironmentalStrain; 81 | -------------------------------------------------------------------------------- /frontend/src/components/BigCurrentlySelected/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import './style.scss'; 4 | 5 | const BigCurrentlySelected = (props) => { 6 | const [color, setColor] = useState('rgba(39, 174, 96, 1)'); 7 | 8 | useEffect(() => { 9 | if (props.currentPlot && props.currentPlot.type === 'crop') { 10 | if (props.currentPlot.state === 'Spring' || props.currentPlot.state === 'Summer') { 11 | setColor('rgba(39, 174, 96, 1)'); 12 | } else { 13 | setColor('rgba(237, 193, 81, 1)'); 14 | } 15 | } 16 | else { 17 | setColor('rgba(64, 145, 220, 1)'); 18 | } 19 | }, [props.currentPlot]) 20 | 21 | return ( 22 |
29 |
35 |

Current Selection

36 |

{props.currentPlot.name || 'N/A'}

37 |
38 |

What is it

39 |

{props.currentPlot.summary || 'N/A'}

40 |
41 |
42 |

Cost

43 |

{props.currentPlot.cost || 'N/A'}

44 |
45 |
46 |

Maintenance

47 |

{props.currentPlot.state || 'N/A'}

48 |
49 |
50 |

Environmental Impact

51 |

{props.currentPlot.environmentalImpact || 'N/A'}

52 |
53 |
54 |

Verdict

55 |

{props.currentPlot.verdict || 'N/A'}

56 |
57 |
58 |
59 | ) 60 | } 61 | 62 | export default BigCurrentlySelected; -------------------------------------------------------------------------------- /frontend/src/components/Statistics/CropRotation/index.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "antd"; 2 | import Plot from "../../Plot"; 3 | import insights from "./earthData.json"; 4 | // import * as Analysis from "../../../../../analysis"; 5 | import redo from "../../../assets/redo.svg"; 6 | import "./style.scss"; 7 | import { useState } from "react"; 8 | 9 | const CropRotation = (props) => { 10 | 11 | const data = props.statsData.map((currentCrop) => { 12 | const cropData = Analysis.dataForCrop(currentCrop.crop); 13 | const newCrop = Analysis.findAlternateCrop(cropData); 14 | return { 15 | currentCrop: cropData, 16 | newCrop 17 | }; 18 | }); 19 | 20 | const [currentIdx, setIdx] = useState(0) 21 | const currentData = data[currentIdx]; 22 | 23 | const nextPage = () => { 24 | if (currentIdx === data.length - 1) { 25 | setIdx(0) 26 | } else { 27 | setIdx(currentIdx + 1) 28 | } 29 | } 30 | const prevPage = () => { 31 | if (currentIdx === 0) { 32 | setIdx(data.length - 1) 33 | } else { 34 | setIdx(currentIdx - 1) 35 | } 36 | } 37 | 38 | return ( 39 |
40 |

Crop Rotations

41 |

Recommendation based off nutrient levels

42 | 43 |

Recommended Rotations

44 |
45 |
46 | 47 |
48 |

49 | 50 | redo 51 | 52 | Rotates To 53 |

54 |
55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | export default CropRotation; 68 | -------------------------------------------------------------------------------- /backend/src/utils/getEnvGrade.ts: -------------------------------------------------------------------------------- 1 | 2 | interface Grade { 3 | score: Score; 4 | description: string 5 | } 6 | enum Score { 7 | "F" = 0, 8 | "D", 9 | "D+", 10 | "C", 11 | "C+", 12 | "B", 13 | "B+", 14 | "A", 15 | "A+" 16 | } 17 | 18 | const scoreToDescription = (score: Score): string => { 19 | switch(score) { 20 | case Score.A, Score["A+"]: return "This place is great overall, the temperature is near perfect, and the moisture is flawless as well!" 21 | case Score.B, Score["B+"]: return "This place is not bad, the temperature is pretty good, but the soil moisture isn't the best — watering and fertilizing will be key to get the most out of this space." 22 | case Score.C, Score["C+"]: return "This place isn't the best — it's temperature isn't in a great range nor is the soil moisture suitable for industrial farming. You'll need a lot of heaters / fans + fertilizer + water to build on this spot." 23 | case Score.D, Score["D+"]: return "We wouldn't recommend farming here. The temperature is not conducive to agricultural growth" 24 | case Score.F: return "We cannot give any recommendations for farming here — The temperature is not conducive to agricultural growth and the soil moisture is horrible" 25 | } 26 | } 27 | 28 | /* 29 | A: This place is great overall, the temperature is near perfect, and the moisture is flawless as well! 30 | B: This place is not bad, the temperature is pretty good, but the soil moisture isn't the best — watering and fertilizing will be key to get the most out of this space. 31 | C: This place isn't the best — it's temperature isn't in a great range nor is the soil moisture suitable for industrial farming. You'll need a lot of heaters / fans + fertilizer + water to build on this spot. 32 | D: We wouldn't recommend farming here. The temperature is not conducive to agricultural growth 33 | F: We cannot give any recommendations for farming here — The temperature is not conducive to agricultural growth and the soil moisture is horrible 34 | */ 35 | export const numToGrade = (score: number): Grade => { 36 | score = Math.round(score); 37 | return { 38 | score: score, 39 | description: scoreToDescription(score) 40 | } 41 | } 42 | 43 | export const getGrade = (moisture: number,temp: number): number => { 44 | const weights = [0.1,0.9]; 45 | const moistureScore = 1 - Math.abs(40 - moisture)/40; 46 | const tempScore = 1 - Math.abs(70 - temp)/70; 47 | return (moistureScore*weights[0] + tempScore*weights[1]) * 9; 48 | } -------------------------------------------------------------------------------- /frontend/src/assets/raining.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /backend/src/insights/weather/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Router, Request, Response } from 'express'; 3 | import { validationResult, check } from 'express-validator'; 4 | import { WEATHER_API_KEY, USE_API } from 'src/configuration'; 5 | import { weatherDateParse } from 'src/utils/parseDate'; 6 | import weatherResponse from "src/data/weatherResponse.json" 7 | 8 | const router = Router(); 9 | 10 | const handler = async (req: Request, res: Response) => { 11 | 12 | await check('from') 13 | .isString() 14 | .run(req); 15 | await check('to') 16 | .isString() 17 | .run(req); 18 | 19 | const errors = validationResult(req); 20 | if (!errors.isEmpty()) { 21 | return res.status(400).json({ 22 | message: 'Invalid Parameters', 23 | errors: errors.array(), 24 | parametersSent: req.body, 25 | }); 26 | } else { 27 | const from = weatherDateParse(req.body.from) 28 | const to = weatherDateParse(req.body.to) 29 | 30 | try { 31 | if (!USE_API) { 32 | throw new Error("Problems") 33 | } 34 | const result = await getWeatherForecast(from, to); 35 | return res.json({ 36 | from, 37 | to, 38 | weather: "result", 39 | }); 40 | } catch (err) { 41 | // return cached data 42 | console.log("Using cached response") 43 | return res.json(weatherResponse) 44 | } 45 | } 46 | }; 47 | 48 | router.post('/', handler) 49 | router.get('/', handler) 50 | 51 | interface WeatherForecast { 52 | date: string; 53 | max_temp_high: number; 54 | max_temp_low: number; 55 | min_temp_code: string; 56 | min_temp_high: number; 57 | min_temp_low: number; 58 | precipitation: string; 59 | precipitation_code: string; 60 | temp: string; 61 | temp_code: string; 62 | } 63 | 64 | interface WeatherResponse { 65 | status: string; 66 | error?: any; 67 | location?: string; 68 | forecast?: WeatherForecast[]; 69 | } 70 | 71 | const getWeatherForecast = async (from: string, to: string): Promise => { 72 | try { 73 | const response = await axios.request({ 74 | url: `https://weatherplanner.azure-api.net/v1/Forecast/Des Moines/${from}/${to}`, 75 | params: { 'subscription-key': WEATHER_API_KEY }, 76 | headers: { 'Content-type': 'application/json' }, 77 | }); 78 | return response.data; 79 | } catch (error) { 80 | return { 81 | status: 'failed', 82 | error: error, 83 | }; 84 | } 85 | }; 86 | 87 | export default router; 88 | -------------------------------------------------------------------------------- /frontend/src/components/PlotType/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Select } from 'antd'; 3 | 4 | import './style.scss'; 5 | 6 | import search from 'assets/search.svg'; 7 | import sprout from 'assets/sprout.svg'; 8 | 9 | import Plot from 'components/Plot'; 10 | 11 | const { Option } = Select; 12 | 13 | const PlotType = (props) => { 14 | const [filter, setFilter] = useState('all'); 15 | const [searchValue, setSearchValue] = useState(''); 16 | 17 | const handleChange = (value) => { 18 | setFilter(value); 19 | } 20 | 21 | const handleSearch = (event) => { 22 | setSearchValue(event.target.value); 23 | } 24 | 25 | return ( 26 |
27 |
28 | sprout icon 29 |
30 |

Select Plot Type

31 |

Includes crops, buildings, and irrigation

32 |
33 |
34 |
35 | search icon 36 | 37 |
38 |
39 |

Recommended by AI

40 |
41 | 46 |
47 |
48 |
49 | {props.plots.map((plot) => { 50 | if ( 51 | (plot.name.toLowerCase().startsWith(searchValue.toLowerCase()) || 52 | plot.type.toLowerCase().startsWith(searchValue.toLowerCase()) || 53 | plot.state.toLowerCase().startsWith(searchValue.toLowerCase())) && 54 | (filter === 'all' || 55 | (filter === 'crops' && plot.type === 'crop') || 56 | (filter === 'irrigation' && plot.type==='irrigation')) 57 | ) { 58 | return ( 59 | { 66 | props.setCurrentPlot(plot); 67 | if (props.draw) { 68 | props.draw.draw.changeMode('draw_rectangle'); 69 | } 70 | }} 71 | /> 72 | ) 73 | } 74 | })} 75 |
76 |
77 | ); 78 | } 79 | 80 | export default PlotType; -------------------------------------------------------------------------------- /frontend/src/components/StatsModal/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal, Button } from 'antd'; 3 | import Draggable from 'react-draggable'; 4 | 5 | import CropRotation from '../Statistics/CropRotation'; 6 | import EstimatedFinancials from '../Statistics/EstimatedFinancials'; 7 | import EnvironmentalStrain from '../Statistics/EnvironmentalStrain'; 8 | import FarmBreakdown from '../Statistics/FarmBreakdown'; 9 | import Breakdowns from '../Statistics/Breakdowns'; 10 | 11 | import title from '../../assets/harveststats.svg'; 12 | import './style.scss'; 13 | 14 | const StatsModal = (props) => { 15 | const [bounds, setBounds] = useState({ 16 | bounds: { left: 0, top: 0, bottom: 0, right: 0 }, 17 | }); 18 | 19 | let draggleRef = React.createRef(); 20 | 21 | const onStart = (event, uiData) => { 22 | const { clientWidth, clientHeight } = window?.document?.documentElement; 23 | const targetRect = draggleRef?.current?.getBoundingClientRect(); 24 | setBounds({ 25 | bounds: { 26 | left: -targetRect?.left + uiData?.x, 27 | right: clientWidth - (targetRect?.right - uiData?.x), 28 | top: -targetRect?.top + uiData?.y, 29 | bottom: clientHeight - (targetRect?.bottom - uiData?.y), 30 | }, 31 | }); 32 | }; 33 | 34 | return ( 35 |
36 | ( 44 | onStart(event, uiData)} 48 | > 49 |
{modal}
50 |
51 | )} 52 | > 53 | title 54 |
55 | 62 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 |
76 |
77 |
78 | ); 79 | }; 80 | 81 | export default StatsModal; 82 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/FarmBreakdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import { PieChart, Pie, Sector, ResponsiveContainer } from 'recharts'; 3 | 4 | import './style.scss'; 5 | 6 | const renderActiveShape = (props) => { 7 | const RADIAN = Math.PI / 180; 8 | const { 9 | cx, 10 | cy, 11 | midAngle, 12 | innerRadius, 13 | outerRadius, 14 | startAngle, 15 | endAngle, 16 | fill, 17 | payload, 18 | percent, 19 | value, 20 | } = props; 21 | const sin = Math.sin(-RADIAN * midAngle); 22 | const cos = Math.cos(-RADIAN * midAngle); 23 | const sx = cx + (outerRadius + 10) * cos; 24 | const sy = cy + (outerRadius + 10) * sin; 25 | const mx = cx + (outerRadius + 30) * cos; 26 | const my = cy + (outerRadius + 30) * sin; 27 | const ex = mx + (cos >= 0 ? 1 : -1) * 22; 28 | const ey = my; 29 | const textAnchor = cos >= 0 ? 'start' : 'end'; 30 | 31 | 32 | 33 | return ( 34 | 35 | 36 | {payload.name} 37 | 38 | 47 | 56 | 61 | 62 | = 0 ? 1 : -1) * 12} 64 | y={ey} 65 | textAnchor={textAnchor} 66 | fill="#333" 67 | >{`${value} acres`} 68 | = 0 ? 1 : -1) * 12} 70 | y={ey} 71 | dy={18} 72 | textAnchor={textAnchor} 73 | fill="#999" 74 | > 75 | {`(${(percent * 100).toFixed(2)}%)`} 76 | 77 | 78 | ); 79 | }; 80 | 81 | const FarmBreakdown = (props) => { 82 | 83 | const data = props.statsData.map(input => ({ 84 | name: input.crop, 85 | value: input.acre 86 | })); 87 | const [activeIndex, setActiveIndex] = useState(0); 88 | const onPieEnter = useCallback( 89 | (_, index) => { 90 | setActiveIndex(index); 91 | }, 92 | [setActiveIndex] 93 | ); 94 | 95 | return ( 96 |
97 |

Farm Breakdown

98 |

Broken down by percentage

99 |
100 | 101 | 112 | 113 |
114 |
115 | ); 116 | }; 117 | 118 | export default FarmBreakdown; 119 | -------------------------------------------------------------------------------- /frontend/src/assets/crop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/sunny.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/saved.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/Analysis/index.jsx: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | 3 | import analysis from 'assets/analysis.svg'; 4 | import Card from './Card'; 5 | import Breakdowns from './Breakdowns'; 6 | 7 | const FireRisk = ["Low", "Medium", "High"]; 8 | const Grade = ["A", "B", "C", "D", "E", "F"]; 9 | const Exp = ["Best", "Good", "Average", "Risky", "Bad"]; 10 | const num = Grade[Math.floor(Math.random() * Grade.length)]; 11 | const Analysis = (props) => { 12 | return ( 13 |
14 |
15 | sprout icon 16 |
17 |

Location Analysis

18 |

Realtime data based off current location

19 |
20 |
21 |
22 | 23 |

29 | {props.analysis.soilTemp ? props.analysis.soilTemp + '°' : Math.floor(Math.random() * (85 - 55 + 1)) + 55 + '°'} 30 |

31 |

Fahrenheit

32 |
33 | 34 |

40 | {props.analysis.soilMoisture 41 | ? props.analysis.soilMoisture + '%' 42 | : Math.floor(Math.random() * (87 - 34 + 1)) + 34 +'%'} 43 |

44 |

Volumetric Pressure

45 |
46 | 47 |

53 | {props.analysis.waterVapor || Math.floor(Math.random() * (45 - 24 + 1)) + 24}% 54 |

55 |

Relative Humidity

56 |
57 | 58 |

🔥

59 |

64 | {props.analysis.fire || FireRisk[Math.floor(Math.random() * FireRisk.length)]} 65 |

66 |
67 | 68 |

🌿

69 |

74 | {props.analysis.pollen || FireRisk[Math.floor(Math.random() * FireRisk.length)]} 75 |

76 |
77 |
78 |
79 |

Air Quality Breakdown

80 |
81 |

Air Quality Index

82 |

83 | {props.analysis.airQuality 84 | ? Math.floor(props.analysis.airQuality['AQI']) 85 | : Math.floor(Math.random() * (10 - 3 + 1)) + 3 + '%'} 86 |

87 |
88 |
89 |
90 |

Nitrogen Dioxide

91 |

92 | {props.analysis.airQuality 93 | ? Math.floor(props.analysis.airQuality['NO2']) 94 | : Math.floor(Math.random() * (15 - 5 + 1)) + 5 + '%'} 95 |

96 |
97 |
98 |
99 |

Particulate Matter (2.5)

100 |

101 | {props.analysis.airQuality 102 | ? Math.floor(props.analysis.airQuality['PM25']) 103 | : Math.floor(Math.random() * (5 - 1 + 1)) + 1 + '%'} 104 |

105 |
106 |
107 |
108 |

Carbon Monoxide

109 |

110 | {props.analysis.airQuality 111 | ? Math.floor(props.analysis.airQuality['CO']) 112 | : Math.floor(Math.random() * (20 - 9 + 1)) + 9 + '%'} 113 |

114 |
115 |
116 |
117 |

Ozone

118 |

119 | {props.analysis.airQuality 120 | ? Math.floor(props.analysis.airQuality['OZONE']) 121 | : Math.floor(Math.random() * (30 - 5 + 1)) + 5 + '%'} 122 |

123 |
124 |
125 |
126 |

Sulfur Dioxide

127 |

128 | {props.analysis.airQuality 129 | ? Math.floor(props.analysis.airQuality['SO2']) 130 | : Math.floor(Math.random() * (15 - 2 + 1)) + 2 + '%'} 131 |

132 |
133 |
134 |
135 | 140 |
141 | ); 142 | }; 143 | 144 | export default Analysis; 145 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Hello 👋. We welcome you all to Fasaan🎊🎊 4 | = 5 | 6 | ## Contributing to this repository 7 | 8 | We are creating a platform that is helpful forfarmers 9 | 10 |
11 | 12 | ## Adding a new topic 13 | 14 | If you are adding a new domain/field, you need to create a new branch. That branch should be created from branch `main` and not from any other branch. The naming convention of the branch should follow `Pascal_Snake_Case`. Examples are given below: 15 | 16 | If you want to add the feature, please add `Feature` specifications in the Issue , the name of the corresponding branch and folder should be
17 | The starting letter of every word should be in uppercase. Do not use spaces or hyphen(-). Instead use underscore (_) to join words. 18 | All branches and folder and sub-folder names should follow this naming convention to maintain a uniformity in the repository 19 | 20 |
21 | 22 | --- 23 | 24 |
25 | 26 | ## Basics of Git and GitHub 27 | 28 | ### Git & GitHub 29 | 30 | Before we proceed, it's better to know the difference between Git and Github. Git is a version control system (VCS) that allows us to keep track of the history of our source code , whereas GitHub is a service that hosts Git projects. 31 | 32 | We assume you have created an account on Github and installed Git on your System. 33 | 34 | Now enter your name and E-mail (used on Github) address in Git, by using following command. 35 | 36 | `$ git config --global user.name "YOUR NAME"` 37 | ` $ git config --global user.email "YOUR EMAIL ADDRESS"` 38 | This is an important step to mark your commits to your name and email. 39 | 40 |
41 | 42 | ### Fork a project 43 | 44 | You can make a copy of the project to your account. This process is called forking a project to your Github account. On Upper right side of project page on Github, you can see - 45 | 46 |

47 | Click on fork to create a copy of project to your account. This creates a separate copy for you to work on. 48 | 49 |
50 | 51 |
52 | 53 | 54 | ### Clone the forked project 55 | 56 | You have forked the project you want to contribute to your github account. To get this project on your development machine we use clone command of git. 57 | 58 | `$ git clone https://github.com/girlscript/winter-of-contributing.git`
59 | Now you have the project on your local machine. 60 | 61 |
62 | 63 | ### Add a remote (upstream) to original project repository 64 | 65 | Remote means the remote location of project on Github. By cloning, we have a remote called origin which points to your forked repository. Now we will add a remote to the original repository from where we had forked. 66 | 67 | `$ cd ` 68 | `$ git remote add upstream https://github.com/girlscript/winter-of-contributing.git`
69 | You will see the benefits of adding remote later. 70 | 71 |
72 | 73 | ### Synchronizing your fork 74 | 75 | Open Source projects have a number of contributors who can push code anytime. So it is necessary to make your forked copy equal with the original repository. The remote added above called Upstream helps in this. 76 | 77 | `$ git checkout main` 78 | `$ git fetch upstream` 79 | `$ git merge upstream/main` 80 | `$ git push origin main`
81 | The last command pushes the latest code to your forked repository on Github. The origin is the remote pointing to your forked repository on github. 82 | 83 |
84 | 85 | ### Create a new branch for a feature or bugfix 86 | 87 | Usually, all repositories have a main branch that is regarded to be stable, and any new features should be developed on a separate branch before being merged into the main branch. As a result, we should establish a new branch for our feature or bugfix and go to work on the issue. 88 | 89 | `$ git checkout -b ` 90 | This will create a new branch out of master branch. Now start working on the problem and commit your changes. 91 | 92 | `$ git add --all` 93 | `$ git commit -m ""` 94 | The first command adds all the files or you can add specific files by removing -a and adding the file names. The second command gives a message to your changes so you can know in future what changes this commit makes. If you are solving an issue on original repository, you should add the issue number like #35 to your commit message. This will show the reference to commits in the issue. 95 | 96 |
97 | 98 | ### Push code and create a pull request 99 | 100 | You now have a new branch containing the modifications you want in the project you forked. Now, push your new branch to your remote github fork. 101 | 102 | `$ git push origin ` 103 | Now you are ready to help the project by opening a pull request means you now tell the project managers to add the feature or bug fix to original repository. You can open a pull request by clicking on green icon - 104 | 105 |

106 | 107 | Remember your upstream base branch should be main and source should be your feature branch. Click on create pull request and add a name to your pull request. You can also describe your feature. 108 | 109 | Fantastic! You've already made your first contribution.🥳 110 | 111 | #### BE OPEN! 112 | 113 | #### Happy Coding 👩‍💻👩‍💻 114 | -------------------------------------------------------------------------------- /frontend/src/components/Seasons/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import './style.scss'; 4 | 5 | import cloudy from 'assets/cloudy.svg'; 6 | import raining from 'assets/raining.svg'; 7 | import sunny from 'assets/sunny.svg'; 8 | 9 | function dateFromDay(year, day){ 10 | let newYear; 11 | if (day > 365) { 12 | newYear = year + 1; 13 | } else { 14 | newYear = year; 15 | } 16 | var date = new Date(newYear, 0); // initialize a date in `year-01-01` 17 | return new Date(date.setDate(day % 365)); // add the number of days 18 | } 19 | 20 | const options = { month: "short", day: "numeric" }; 21 | 22 | const Seasons = (props) => { 23 | const [season, setSeason] = useState('Spring'); 24 | const [color, setColor] = useState('rgba(39, 174, 96, 1)'); 25 | const [high, setHigh] = useState('63'); 26 | const [low, setLow] = useState('39'); 27 | const [allWeather, setWeather] = useState([ 28 | { 29 | avgTemp: '41°', 30 | date: 'April 19', 31 | weather: 'rainy' 32 | }, 33 | { 34 | avgTemp: '46°', 35 | date: 'April 20', 36 | weather: 'cloudy' 37 | }, 38 | { 39 | avgTemp: '49°', 40 | date: 'April 21', 41 | weather: 'cloudy' 42 | }, 43 | { 44 | avgTemp: '59°', 45 | date: 'April 22', 46 | weather: 'cloudy' 47 | }, 48 | { 49 | avgTemp: '58°', 50 | date: 'April 23', 51 | weather: 'rainy' 52 | } 53 | ]) 54 | 55 | useEffect(() => { 56 | const realDay = props.day % 365; 57 | let year = (props.day > 365) ? '2024' : '2023'; 58 | let multiplier = 73; 59 | let weather1, weather2, weather3, weather4, weather5 = null; 60 | if (realDay >= 79 && realDay < 171) { 61 | setSeason('Spring ' + year); 62 | setColor('rgba(39, 174, 96, 1)'); 63 | multiplier = 53; 64 | weather1 = 'cloudy'; 65 | weather2 = 'rainy'; 66 | weather3 = 'rainy'; 67 | weather4 = 'cloudy'; 68 | weather5 = 'cloudy' 69 | setHigh(Math.floor(multiplier + Math.random() * 10)); 70 | setLow(Math.floor(multiplier - Math.random() * 10)); 71 | } else if (realDay >= 171 && realDay < 265) { 72 | setSeason('Summer ' + year); 73 | setColor('rgba(237, 193, 81, 1)'); 74 | weather1 = 'cloudy'; 75 | weather2 = 'sunny'; 76 | weather3 = 'sunny'; 77 | weather4 = 'sunny'; 78 | weather5 = 'sunny'; 79 | setHigh(Math.floor(multiplier + Math.random() * 10)); 80 | setLow(Math.floor(multiplier - Math.random() * 10)); 81 | } else if (realDay >= 265 && realDay < 355) { 82 | setSeason('Fall ' + year); 83 | setColor('rgba(237, 193, 81, 1)'); 84 | multiplier = 40; 85 | weather1 = 'sunny'; 86 | weather2 = 'cloudy'; 87 | weather3 = 'cloudy'; 88 | weather4 = 'cloudy'; 89 | weather5 = 'cloudy'; 90 | setHigh(Math.floor(multiplier + Math.random() * 10)); 91 | setLow(Math.floor(multiplier - Math.random() * 10)); 92 | } else { 93 | setSeason('Winter ' + year); 94 | setColor('rgba(64, 145, 220, 1)'); 95 | multiplier = 9; 96 | weather1 = 'cloudy'; 97 | weather2 = 'rainy'; 98 | weather3 = 'rainy'; 99 | weather4 = 'rainy'; 100 | weather5 = 'rainy'; 101 | setHigh(Math.floor(multiplier + Math.random() * 10)); 102 | setLow(Math.floor(multiplier - Math.random() * 10)); 103 | } 104 | 105 | setWeather([ 106 | { 107 | avgTemp: Math.floor(multiplier + Math.random() * 5) + '°', 108 | date: dateFromDay(year, (props.day + 1) % 365).toLocaleDateString(undefined, options), 109 | weather: weather1 110 | }, 111 | { 112 | avgTemp: Math.floor(multiplier + Math.random() * 5) + '°', 113 | date: dateFromDay(year, (props.day + 2) % 365).toLocaleDateString(undefined, options), 114 | weather: weather2 115 | }, 116 | { 117 | avgTemp: Math.floor(multiplier + Math.random() * 5) + '°', 118 | date: dateFromDay(year, (props.day + 3) % 365).toLocaleDateString(undefined, options), 119 | weather: weather3 120 | }, 121 | { 122 | avgTemp: Math.floor(multiplier + Math.random() * 5) + '°', 123 | date: dateFromDay(year, (props.day + 4) % 365).toLocaleDateString(undefined, options), 124 | weather: weather4 125 | }, 126 | { 127 | avgTemp: Math.floor(multiplier + Math.random() * 5) + '°', 128 | date: dateFromDay(year, (props.day + 5) % 365).toLocaleDateString(undefined, options), 129 | weather: weather5 130 | } 131 | ]); 132 | }, [props.day]) 133 | 134 | return ( 135 |
141 |
142 |

Season

143 |

{season}

144 |
145 |
146 |

Temp

147 |

{high}°/{low}°

148 |
149 |
150 | { 151 | allWeather.map((entry) => { 152 | let currWeather = sunny; 153 | if (entry.weather === 'cloudy') { 154 | currWeather = cloudy; 155 | } else if (entry.weather === 'raining') { 156 | currWeather = raining; 157 | } 158 | return ( 159 |
160 | current weather 161 |
162 |

{entry.avgTemp}

163 |

{entry.date}

164 |
165 |
166 | ) 167 | }) 168 | } 169 |
170 |
171 | ) 172 | } 173 | 174 | export default Seasons -------------------------------------------------------------------------------- /analysis/src/index.ts: -------------------------------------------------------------------------------- 1 | import earthData from './data/earthData.json'; 2 | 3 | 4 | export const data = earthData; 5 | export interface CropInformation { 6 | type: string; 7 | name: string; 8 | earthScore: number; 9 | state: Season; 10 | image: string; 11 | summary: string; 12 | environmentalImpact: string; 13 | costPerAcre: number; 14 | verdict: string; 15 | daysToGrow: number; 16 | waterUsed: number; 17 | lbsPerAcre: number; 18 | valuePerAcre: number; 19 | } 20 | 21 | 22 | 23 | export const dataForCrop = (crop: string): CropInformation => { 24 | return earthData.find(candidate => { 25 | return candidate.name == crop; 26 | }) as CropInformation; 27 | }; 28 | 29 | export const findAlternateCrop = (crop: CropInformation): CropInformation => { 30 | return earthData.find(candidate => { 31 | return candidate.name !== crop.name && candidate.state === crop.state 32 | }) as CropInformation; 33 | }; 34 | /** 35 | Array 36 | 0 1 2 3 37 | Spring Summer Fall Winter 38 | */ 39 | export interface CropAmount { 40 | crop: string; 41 | acre: number; // in acres (remember to convert) 42 | } 43 | 44 | export enum Season { 45 | Spring = 'Spring', 46 | Summer = 'Summer', 47 | Fall = 'Fall', 48 | Winter = 'Winter', 49 | } 50 | 51 | export const SeasonToIdx = (season: Season): number => { 52 | switch (season) { 53 | case Season.Spring: return 0 54 | case Season.Summer: return 1 55 | case Season.Fall: return 2 56 | case Season.Winter: return 3 57 | } 58 | } 59 | 60 | export enum Month { 61 | January = 0, 62 | February, 63 | March, 64 | April, 65 | May, 66 | June, 67 | July, 68 | August, 69 | September, 70 | October, 71 | November, 72 | December, 73 | } 74 | 75 | export const monthToSeason = (month: Month) => { 76 | if (month >= 2 && month < 5 ) { 77 | return Season.Spring; 78 | } else if (month >= 5 && month < 8) { 79 | return Season.Summer; 80 | } else if (month >= 8 && month < 11) { 81 | return Season.Fall; 82 | } else { 83 | return Season.Winter; 84 | } 85 | }; 86 | 87 | export const seasonToMonth = (season: Season) => { 88 | switch (season) { 89 | case Season.Spring: 90 | return Month.March; 91 | case Season.Summer: 92 | return Month.June; 93 | case Season.Fall: 94 | return Month.September; 95 | case Season.Winter: 96 | return Month.December; 97 | } 98 | }; 99 | 100 | export const cleanMonth = (month: number): Month => { 101 | // somehow normalize month to be within 0 and 11 102 | return (month + 12) % 12; 103 | } 104 | 105 | export const sqftToAcre = (sqft: number) => { 106 | // https://www.thecalculatorsite.com/conversions/area/square-feet-to-acres.php 107 | return 0.000022956841138659 * sqft; 108 | }; 109 | 110 | export interface WaterUsageAndYieldOutput { 111 | waterUsage: number[]; 112 | yield: number[]; 113 | } 114 | 115 | export const determineWaterUsageAndYield = (array: CropAmount[]): WaterUsageAndYieldOutput => { 116 | const output: WaterUsageAndYieldOutput = { 117 | waterUsage: [0,0,0,0], 118 | yield: [0,0,0,0], 119 | }; 120 | 121 | for (const item of array) { 122 | const cropData = dataForCrop(item.crop); 123 | const season = cropData.state; 124 | const endMonth = seasonToMonth(season); 125 | const numOfMonths = cropData.daysToGrow / 30; 126 | const startMonth = cleanMonth(endMonth - numOfMonths); 127 | console.log(`for ${item.crop}, startMonth: ${startMonth}, endMonth: ${endMonth}`) 128 | 129 | for (let j = 0; j <= numOfMonths; j++) { 130 | const month = cleanMonth(startMonth + j); 131 | const season = monthToSeason(month); 132 | const idx = SeasonToIdx(season); 133 | 134 | output.waterUsage[idx] += cropData.waterUsed * 91.25; 135 | } 136 | output.yield[SeasonToIdx(season)] += cropData.lbsPerAcre * item.acre; 137 | } 138 | return output; 139 | }; 140 | 141 | interface SpendingAndRevenueOutput { 142 | spending: number[]; 143 | revenue: number[]; 144 | } 145 | 146 | export const determineSpendingAndRevenue = (array: CropAmount[]): SpendingAndRevenueOutput => { 147 | const output: SpendingAndRevenueOutput = { 148 | spending: [0,0,0,0], 149 | revenue: [0,0,0,0], 150 | }; 151 | 152 | for (const item of array) { 153 | const cropData = dataForCrop(item.crop); 154 | console.log(item.crop) 155 | const season = cropData.state; 156 | const endMonth = seasonToMonth(season); 157 | const numOfMonths = cropData.daysToGrow / 30; 158 | const startMonth = cleanMonth(endMonth - numOfMonths); 159 | 160 | for (let j = 0; j <= numOfMonths; j++) { 161 | const month = cleanMonth(startMonth + j); 162 | const season = monthToSeason(month); 163 | const idx = SeasonToIdx(season); 164 | 165 | const cost = cropData.costPerAcre * item.acre; 166 | console.log(cropData.costPerAcre) 167 | console.log(item.acre) 168 | console.log(cost) 169 | const revenue = cropData.valuePerAcre * item.acre; 170 | output.spending[idx] += cost; 171 | output.revenue[idx] += revenue; 172 | } 173 | } 174 | 175 | return output; 176 | }; 177 | 178 | 179 | -------------------------------------------------------------------------------- /frontend/src/components/Map/utils.js: -------------------------------------------------------------------------------- 1 | import { MercatorCoordinate } from 'mapbox-gl'; 2 | import * as THREE from 'three'; 3 | import { TweenMax } from 'gsap'; 4 | 5 | let allLayers = []; 6 | const height=100; 7 | 8 | /** 9 | * Creates a plot at a location 10 | **/ 11 | 12 | export const loadLocation = (map, lng, lat, id, width, length, color, minLat, maxLat, minLng, maxLng, currentPlot, day, callback) => { 13 | console.log(width); 14 | console.log(length); 15 | 16 | // parameters to ensure the model is georeferenced correctly on the map 17 | const modelOrigin = [lng, lat]; 18 | const modelAltitude = 0; 19 | const modelRotate = [Math.PI / 2, 0, 0]; 20 | 21 | const modelAsMercatorCoordinate = MercatorCoordinate.fromLngLat( 22 | modelOrigin, 23 | modelAltitude 24 | ); 25 | 26 | const modelTransform = { 27 | translateX: modelAsMercatorCoordinate.x, 28 | translateY: modelAsMercatorCoordinate.y, 29 | translateZ: modelAsMercatorCoordinate.z, 30 | rotateX: modelRotate[0], 31 | rotateY: modelRotate[1], 32 | rotateZ: modelRotate[2], 33 | /* Since our 3D model is in real world meters, a scale transform needs to be 34 | * applied since the CustomLayerInterface expects units in MercatorCoordinates. 35 | */ 36 | scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() 37 | }; 38 | 39 | // Insert the layer beneath any symbol layer. 40 | const layers = map.getStyle().layers; 41 | 42 | let labelLayerId; 43 | for (let i = 0; i < layers.length; i++) { 44 | if (layers[i].type === 'custom') { 45 | labelLayerId = layers[i].id; 46 | break; 47 | } 48 | } 49 | 50 | const customLayer = { 51 | id: lat.toString() + lng.toString(), 52 | type: 'custom', 53 | renderingMode: '3d', 54 | 'fill-extrusion-height': [ 55 | 'interpolate', 56 | ['linear'], 57 | ['zoom'], 58 | 15, 59 | 0, 60 | 15.05, 61 | ['get', 'height'] 62 | ], 63 | 'fill-extrusion-base': [ 64 | 'interpolate', 65 | ['linear'], 66 | ['zoom'], 67 | 15, 68 | 0, 69 | 15.05, 70 | ['get', 'min_height'] 71 | ], 72 | onAdd: function(map, gl) { 73 | this.camera = new THREE.Camera(); 74 | this.scene = new THREE.Scene(); 75 | 76 | // create two three.js lights to illuminate the model 77 | const directionalLight = new THREE.DirectionalLight(0xeeeeee); 78 | directionalLight.position.set(40, -70, 100).normalize(); 79 | this.scene.add(directionalLight); 80 | 81 | const directionalLight2 = new THREE.DirectionalLight(0xeeeeee); 82 | directionalLight2.position.set(40, 70, 100).normalize(); 83 | this.scene.add(directionalLight2); 84 | 85 | const ftHeight = (height / 3.28084) / 100; 86 | 87 | const geometry = new THREE.BoxGeometry( width , ftHeight, length ); 88 | const material = new THREE.MeshPhongMaterial({ 89 | color, 90 | opacity: 0.75, 91 | transparent: true, 92 | }); 93 | const cube = new THREE.Mesh( geometry, material ); 94 | this.scene.add( cube ); 95 | TweenMax.to(cube.scale, .5, { x: 1, y: 100, z: 1 }); 96 | TweenMax.to(cube.position, .5, {y: ftHeight * 50}); 97 | this.map = map; 98 | 99 | // use the Mapbox GL JS map canvas for three.js 100 | this.renderer = new THREE.WebGLRenderer({ 101 | canvas: map.getCanvas(), 102 | context: gl, 103 | antialias: true 104 | }); 105 | 106 | callback(id, { 107 | model: cube, 108 | minLat: minLat, 109 | maxLat: maxLat, 110 | minLng: minLng, 111 | maxLng: maxLng, 112 | centerLat: lng, 113 | centerLng: lat, 114 | currentPlot: currentPlot, 115 | width: width, 116 | length: length, 117 | day: day 118 | }); 119 | 120 | this.renderer.autoClear = false; 121 | }, 122 | render: function(gl, matrix) { 123 | const rotationX = new THREE.Matrix4().makeRotationAxis( 124 | new THREE.Vector3(1, 0, 0), 125 | modelTransform.rotateX 126 | ); 127 | const rotationY = new THREE.Matrix4().makeRotationAxis( 128 | new THREE.Vector3(0, 1, 0), 129 | modelTransform.rotateY 130 | ); 131 | const rotationZ = new THREE.Matrix4().makeRotationAxis( 132 | new THREE.Vector3(0, 0, 1), 133 | modelTransform.rotateZ 134 | ); 135 | 136 | const m = new THREE.Matrix4().fromArray(matrix); 137 | const l = new THREE.Matrix4() 138 | .makeTranslation( 139 | modelTransform.translateX, 140 | modelTransform.translateY, 141 | modelTransform.translateZ 142 | ) 143 | .scale( 144 | new THREE.Vector3( 145 | modelTransform.scale, 146 | -modelTransform.scale, 147 | modelTransform.scale 148 | ) 149 | ) 150 | .multiply(rotationX) 151 | .multiply(rotationY) 152 | .multiply(rotationZ); 153 | 154 | this.camera.projectionMatrix = m.multiply(l); 155 | this.renderer.state.reset(); 156 | this.renderer.render(this.scene, this.camera); 157 | this.map.triggerRepaint(); 158 | } 159 | }; 160 | 161 | try { 162 | map.addLayer(customLayer, labelLayerId); 163 | allLayers.push(customLayer.id); 164 | window.map = map; 165 | } 166 | catch (error) { 167 | // TODO: Notify 168 | //notify("Can't Build", "There's already a building there!"); 169 | } 170 | } 171 | //console.log(lng); 172 | //console.log(lat); 173 | /*map.addLayer({ 174 | id: 'plot-' + id, 175 | type: 'custom', 176 | renderingMode: '3d', 177 | onAdd: function (map, mbxContext) { 178 | 179 | window.tb = new Threebox( 180 | map, 181 | mbxContext, 182 | { defaultLights: true } 183 | ); 184 | 185 | // initialize geometry and material of our cube object 186 | const geometry = new THREE.BoxGeometry(100, 100, 100); 187 | 188 | const redMaterial = new THREE.MeshPhongMaterial( { 189 | color: 0x009900, 190 | side: THREE.DoubleSide 191 | }); 192 | 193 | //var cube = new THREE.Mesh(geometry, redMaterial); 194 | 195 | var sphere = window.tb.sphere({color: 'red', material: 'MeshStandardMaterial'}) 196 | .setCoords([lng, lat]) 197 | 198 | window.tb.add(sphere); 199 | 200 | //var cube2 = new THREE.Mesh(geometry, redMaterial); 201 | 202 | //cube2 = window.tb.Object3D({obj: cube}).setCoords([lng + 0.01, lat + 0.01]) 203 | 204 | //window.tb.add(cube2); 205 | }, 206 | render: function(gl, matrix){ 207 | window.tb.update(); 208 | } 209 | });*/ -------------------------------------------------------------------------------- /backend/src/insights/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | import { matchedData, validationResult, check } from 'express-validator'; 3 | import { ambeeDateParse } from 'src/utils/parseDate'; 4 | import { getAirQuality } from './ambee/airQuality'; 5 | import { getAirQualityHistory } from './ambee/airQualityHistory'; 6 | import { getFireLatest } from './ambee/fireLatest'; 7 | import { getPollenForecast } from './ambee/pollenForecast'; 8 | import { getPollenHistory } from './ambee/pollenHistory'; 9 | import { getSoilLatest } from './ambee/soilLatest'; 10 | import { getSoilHistory } from './ambee/soilHistory'; 11 | import { getWaterVaporHistory } from './ambee/waterVaporHistory'; 12 | import { getWaterVaporLatest } from './ambee/waterVaporLatest'; 13 | import { getWeatherForecast } from './ambee/weatherForecast'; 14 | import { getWeatherHistory } from './ambee/weatherHistory'; 15 | import { getWeatherLatest } from './ambee/weatherLatest'; 16 | import { getPollenLatest } from './ambee/pollenLatest'; 17 | import weatherRouter from './weather' 18 | import { AMBEE_API_KEY, USE_API } from 'src/configuration'; 19 | import exampleResponse from '../data/perfectResponse.json'; 20 | import * as EnvironmentGrade from '../utils/getEnvGrade'; 21 | import * as LocationGrade from '../utils/getLocGrade'; 22 | 23 | const router = Router(); 24 | 25 | const handler = async (req: Request, res: Response) => { 26 | await check('lat') 27 | .isFloat({ min: -90, max: 90 }) 28 | .run(req); 29 | await check('lng') 30 | .isFloat({ min: -180, max: 180 }) 31 | .run(req); 32 | await check('from') 33 | .isString() 34 | .optional({ checkFalsy: true }) 35 | .run(req); 36 | await check('to') 37 | .isString() 38 | .optional({ checkFalsy: true }) 39 | .run(req); 40 | 41 | const endpoints = [ 42 | { 43 | name: 'airQuality', 44 | api: getAirQuality, 45 | }, 46 | { 47 | name: 'airQualityHistory', 48 | api: getAirQualityHistory, 49 | }, 50 | { 51 | name: 'fireLatest', 52 | api: getFireLatest, 53 | }, 54 | { 55 | name: 'pollenForecast', 56 | api: getPollenForecast, 57 | }, 58 | { 59 | name: 'pollenHistory', 60 | api: getPollenHistory, 61 | }, 62 | { 63 | name: 'soilLatest', 64 | api: getSoilLatest, 65 | }, 66 | { 67 | name: 'soilHistory', 68 | api: getSoilHistory, 69 | }, 70 | { 71 | name: 'waterVaporHistory', 72 | api: getWaterVaporHistory, 73 | }, 74 | { 75 | name: 'waterVaporLatest', 76 | api: getWaterVaporLatest, 77 | }, 78 | { 79 | name: 'weatherForecast', 80 | api: getWeatherForecast, 81 | }, 82 | { 83 | name: 'weatherHistory', 84 | api: getWeatherHistory, 85 | }, 86 | { 87 | name: 'weatherLatest', 88 | api: getWeatherLatest, 89 | }, 90 | { 91 | name: 'pollenLatest', 92 | api: getPollenLatest, 93 | }, 94 | ]; 95 | 96 | const errors = validationResult(req); 97 | 98 | if (!errors.isEmpty()) { 99 | return res.status(400).json({ 100 | message: 'Invalid Parameters', 101 | errors: errors.array(), 102 | parametersSent: req.body, 103 | }); 104 | } else { 105 | try { 106 | const { lat, lng }: { lat: number; lng: number } = req.body; 107 | let from: string, to: string; 108 | const usingTimes = !!req.body.from && !!req.body.to; 109 | if (usingTimes) { 110 | from = ambeeDateParse(from); 111 | to = ambeeDateParse(to); 112 | } 113 | 114 | let data: any = {}; 115 | 116 | // kyle do magic here 117 | const soilData = (USE_API) ? await getSoilLatest(lat, lng) : (exampleResponse.insights as any)["soilLatest"] 118 | data["soilLatest"] = soilData; 119 | 120 | const aqi = (USE_API) ? await (await getAirQuality(lat, lng)).stations[0].AQI : exampleResponse.insights.airQuality.stations[0].AQI 121 | const locScore = LocationGrade.getGrade(aqi); 122 | const {score : locationGrade, description:locationDescription} = LocationGrade.numToGrade(locScore) 123 | 124 | const envgradeScore = EnvironmentGrade.getGrade(soilData.data[0].soil_moisture, soilData.data[0].soil_temperature); 125 | const {score: environmentGrade, description: environmentDescription} = EnvironmentGrade.numToGrade(envgradeScore); 126 | console.log(locationDescription) 127 | for (const endpoint of endpoints) { 128 | if (["soilLatest"].includes(endpoint.name)) { 129 | // skip these since we already did the analysis 130 | continue; 131 | } else if (req.body[endpoint.name] == 'API') { 132 | console.log(`requesting ${endpoint.name} using the api`) 133 | const result = await endpoint.api(lat, lng, from, to) 134 | const success = !result.error 135 | if (success) { 136 | data[endpoint.name] = result; 137 | continue; 138 | } else { 139 | console.log(`request for ${endpoint.name} failed — using cache`) 140 | } 141 | } 142 | console.log(`using cached data from ${endpoint.name}`) 143 | data[endpoint.name] = (exampleResponse.insights as any)[endpoint.name] 144 | } 145 | 146 | return res.json({ 147 | lat, 148 | lng, 149 | environment: { 150 | environmentGrade, environmentDescription 151 | }, 152 | location: { 153 | locationGrade, locationDescription 154 | }, 155 | insights: data 156 | }); 157 | } catch (err) { 158 | return res.json(exampleResponse) 159 | } 160 | } 161 | }; 162 | 163 | router.get('/', handler); 164 | router.post('/', handler); 165 | 166 | router.use('/weather', weatherRouter) 167 | export default router; 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

CareFACT : Envisioning Farms for Future

2 |

3 | 4 |

5 | 6 | 7 | [![CareFACT Badge](https://img.shields.io/badge/Project-CareFACT-orange?style=for-the-badge&logo=github)](https://github.com/hariketsheth/CareFACT) 8 | 9 | ### :sparkles:About CareFACT 10 | Agriculture is very significant in India's economy and jobs. Farmers' most prevalent problem is that they are unable to make informed decisions on which crops are supported in their areas, as well as market and profit prices. As a result, they have lower productivity and profit margins. The goal of this study is to propose an attempt to estimate agricultural output and price using machine learning, which will aid in crop selection based on soil type and meteorological conditions. 11 | 12 | Farmers do not have a single point of contact for all of their needs, including crop, market price, soil type, government programs, and the latest news. To make this easier for farmers, a portal based on machine learning is being developed. 13 | 14 | ### :sparkles:About the Project 15 | Several studies were undertaken recently in India, including the one on DFI, have brought out the problems afflicting the agriculture sector. 16 | 17 | The recommendations of these studies include, inter alia, ensuring timely availability of inputs, an increased focus on measures to enhance the productivity - especially of small and marginal farms, adoption of modern agricultural practices, optimal use of inputs, choice of the right crops through macro and micro-level planning. CareFACT comes from the Hindi terms "Fasal" and "Kisaan," which mean "crops" and "farmers," respectively. As the name says, we work as a farmer's companion. Farmers play a critical role in both meeting our basic food needs and altering the agricultural environment. Farmers manage agricultural practices to maintain the long-term survival of the entire flora and fauna. 18 | 19 | Agriculture presently occupies one-third of the world's land surface. For a big majority of the world's population, it is the most essential activity since it produces food for people all over the world. 20 | 21 | > 22 | > The features include: 23 | > 1. **Gain Insights** - Centralized source of truth for farmers to gain insights into their land. Our software allows farmers to optimally plan farm layouts and maximize efficiency and revenue. 24 | > 2. **Plan Out Field** - The user is able to indicate plots of land where they want to plant and indicate the crop they want to plant there. Based on real-time data about air quality, soil and weather conditions, 25 | > 3. **Real Time Analysis with Environment Impact** - CareFACT will determine how suitable a particular area is for planting a particular crop, and provide a general estimation on the expected value of a plot. 26 | > 4. **Environment Degradation** - CareFACT would determine a CareFACT Score that would determine the degradation the particular farm field is creating because of the crops and irrigation sources 27 | > 5. **AI Based Help** - CareFACT is AI based solution which makes it the best platform to suggest farmers crops and also given them suggestions about not-so-good combinations and crop rotation techniques 28 | > 6. **Crop Yield and Estimation** - CareFACT will help ypu detrermine the growth of your formed field in the platform - how well it performs in all the 4 seasons. This would help the farmers know about the estimated cost anf fundings to be taken beforehand. 29 | 30 |
31 | 32 | ## :sparkles:Take a look at our website 33 |

34 | 35 | ![Desktop - 6](https://github.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/assets/72455881/a9d74e8f-8f53-45c8-abb9-be630cbcc87c) 36 | 37 | ![Desktop - 7](https://github.com/hariketsheth/Carefact---Envisioning-Farms-for-Future/assets/72455881/513fb53c-9ca4-49a4-bdd6-5ac4d7989e03) 38 | 39 | 40 | 41 |

42 | 43 | ## :sparkles: Project Information 44 | [![Language Used](https://img.shields.io/badge/FrontEnd-HTML,%20CSS,%20JavaScript,%20JQuery,%20React-blue)](https://github.com/hariketsheth/CareFACT)       45 | [![Build](https://img.shields.io/badge/build-passing-green)](https://github.com/hariketsheth/CareFACT) 46 | 47 | [![Language Used](https://img.shields.io/badge/Backend-JavaScript,%20NodeJS,%20TypeScript-red)](https://github.com/hariketsheth/CareFACT)       48 | 49 |
50 | 51 | ## :sparkles: Instructions for Cloning this Repository Locally 52 | - Use the command: `git clone https://github.com/hariketsheth/CareFACT---Envisioning-Farms-for-Future` 53 | - Run `npm install` or `yarn` separately in root directory, frontend, backend and analysis directory 54 | - Use `yarn start` or `npm start` to start the project on local server 55 | 56 | 57 | 58 | ## :sparkles: Instructions to contribute 59 | 60 | To contribute fork this repository and clone it. Make changes and create a pull request. Follow the step below to contribute. 61 | 62 | #### Add your email and user name in git bash 63 | `$ git config --global user.name "YOUR NAME"` 64 |
65 | ` $ git config --global user.email "YOUR EMAIL ADDRESS"` 66 | 67 | #### Fork the repo 68 | On the upper right you can see a option to fork the repository. Fork it to make changes. 69 | 70 | #### Clone the project 71 | Use the below command to clone the repo into your local system. 72 | 73 | ` $ git clone https://hariketsheth/CareFACT---Envisioning-Farms-for-Future.git ` 74 | 75 | #### Add upstream to repository 76 | `$ cd `
77 | `$ git remote add upstream https://github.com/hariketsheth/CareFACT---Envisioning-Farms-for-Future.git`
78 | `$ git checkout main`
79 | `$ git fetch upstream`
80 | `$ git merge upstream/main`
81 | `$ git push origin main`
82 | 83 | #### Creating branch for the new change on feature or bug fix 84 | `$ git checkout -b `
85 | `$ git add --all`
86 | `$ git commit -m ""`
87 | 88 | #### Creating pull request 89 | `$ git push origin main` 90 | 91 | ## :sparkles: Get in touch with us 92 | 93 | Instagram 94 | Gmail 95 | Gmail 96 | 97 | ## :sparkles: License 98 | 99 | 100 | [![GPLv3 License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/) 101 | 102 | 103 | -------------------------------------------------------------------------------- /analysis/src/data/earthData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "crop", 4 | "name": "Strawberry", 5 | "earthScore": 86, 6 | "state": "Summer", 7 | "image": "https://i.imgur.com/u0rOCBQ.png", 8 | "summary": "Strawberries are low-growing herbaceous plants with a fibrous root system and a crown from which arise basal leaves", 9 | "environmentalImpact": "Medium", 10 | "costPerAcre": 1600, 11 | "verdict": "Highly Recommended", 12 | "daysToGrow" : 90, 13 | "waterUsed" : 0.36, 14 | "lbsPerAcre" : 10000, 15 | "valuePerAcre" : 2000 16 | }, 17 | { 18 | "type": "crop", 19 | "name": "Corn", 20 | "earthScore": 72, 21 | "state": "Fall", 22 | "image": "https://i.imgur.com/Fm1Svvq.png", 23 | "summary": "Corn is a cereal grain first domesticated by indigenous peoples in southern Mexico about 10,000 years ago", 24 | "environmentalImpact": "Medium", 25 | "costPerAcre": 248, 26 | "verdict": "Highly Recommended", 27 | "daysToGrow" : 120, 28 | "waterUsed" : 0.19, 29 | "lbsPerAcre" : 7280, 30 | "valuePerAcre" : 699 31 | }, 32 | { 33 | "type": "crop", 34 | "name": "Soybean", 35 | "earthScore": 70, 36 | "state": "Fall", 37 | "image": "https://cdn.britannica.com/28/154828-050-05C6239A/Soybeans.jpg", 38 | "summary": "The soybean or soya bean is a species of legume native to East Asia, widely grown for its edible bean, which has numerous uses.", 39 | "environmentalImpact": "Medium", 40 | "costPerAcre": 204, 41 | "verdict": "Highly Recommended", 42 | "daysToGrow" : 65, 43 | "waterUsed" : 0.19, 44 | "lbsPerAcre" : 2946, 45 | "valuePerAcre" : 0 46 | }, 47 | { 48 | "type": "crop", 49 | "name": "Wheat", 50 | "earthScore": 78, 51 | "state": "Fall", 52 | "image": "https://i.imgur.com/eKB3yQ8.jpeg", 53 | "summary": "Wheat is a grass widely cultivated for its seed, a cereal grain which is a worldwide staple food", 54 | "environmentalImpact": "Low", 55 | "costPerAcre": 170, 56 | "verdict": "Highly Recommended", 57 | "daysToGrow" : 120, 58 | "waterUsed" : 0.14, 59 | "lbsPerAcre" : 3900, 60 | "valuePerAcre" : 0 61 | }, 62 | { 63 | "type": "crop", 64 | "name": "Rice", 65 | "earthScore": 64, 66 | "state": "Summer", 67 | "image": "https://i.imgur.com/yegWy51.jpeg", 68 | "summary": "Rice is the most widely consumed staple food for over half the world's population", 69 | "environmentalImpact": "High", 70 | "costPerAcre": 225, 71 | "verdict": "Recommended", 72 | "daysToGrow" : 120, 73 | "waterUsed" : 0.26, 74 | "lbsPerAcre" : 8000, 75 | "valuePerAcre" : 892 76 | }, 77 | { 78 | "type": "crop", 79 | "name": "Onion", 80 | "earthScore": 82, 81 | "state": "Summer", 82 | "image": "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/the-health-benefits-of-onions-main-image-700-350-8425535.jpg?quality=90&resize=768,574", 83 | "summary": "The onion plant has a fan of hollow, bluish-green leaves and its bulb at the base of the plant begins to swell when a certain day-length is reached", 84 | "environmentalImpact": "Medium", 85 | "costPerAcre": 1750, 86 | "verdict": "Highly Recommended", 87 | "daysToGrow" : 100, 88 | "waterUsed" : 0.06, 89 | "lbsPerAcre" : 32000, 90 | "valuePerAcre" : 4200 91 | }, 92 | { 93 | "type": "crop", 94 | "name": "Cotton", 95 | "earthScore": 81, 96 | "state": "Fall", 97 | "image": "https://upload.wikimedia.org/wikipedia/commons/6/68/CottonPlant.JPG", 98 | "summary": "Cotton is a shrub native to tropical and subtropical regions around the world, including the Americas, Africa, Egypt and India", 99 | "environmentalImpact": "High", 100 | "costPerAcre": 485, 101 | "verdict": "Recommended", 102 | "daysToGrow" : 150, 103 | "waterUsed" : 0.09, 104 | "lbsPerAcre" : 685, 105 | "valuePerAcre" : 500 106 | }, 107 | { 108 | "type": "crop", 109 | "name": "Iceberg Lettuce", 110 | "earthScore": 78, 111 | "state": "Spring", 112 | "image": "https://media.newyorker.com/photos/5b6b08d3a676470b4ea9b91f/4:3/w_1920,h_1440,c_limit/Rosner-Lettuce.jpg", 113 | "summary": "Iceberg lettuce, also known as crisphead lettuce, has pale green leaves and grows in cabbage-like bulbs", 114 | "environmentalImpact": "High", 115 | "costPerAcre": 7999, 116 | "verdict": "Recommended", 117 | "daysToGrow" : 30, 118 | "waterUsed" : 0.09, 119 | "lbsPerAcre" : 32000, 120 | "valuePerAcre" : 10400 121 | }, 122 | { 123 | "type": "crop", 124 | "name": "Romaine Lettuce", 125 | "earthScore": 78, 126 | "state": "Spring", 127 | "image": "https://www.foodsafetynews.com/files/2019/11/romaine1200x680.jpg", 128 | "summary": "Romaine or cos lettuce is a variety of lettuce that grows in a tall head of sturdy dark green leaves with firm ribs down their centers.", 129 | "environmentalImpact": "High", 130 | "costPerAcre": 8847, 131 | "verdict": "Recommended", 132 | "daysToGrow" : 30, 133 | "waterUsed" : 0, 134 | "lbsPerAcre" : 32000, 135 | "valuePerAcre" : 10400 136 | }, 137 | { 138 | "type": "irrigation", 139 | "name": "Surface", 140 | "earthScore": 90, 141 | "state": "Medium", 142 | "image": "https://upload.wikimedia.org/wikipedia/commons/9/9b/Furrow_irrigated_Sugar.JPG", 143 | "summary": "This type of irrigation relies on gravity flow to distribute water. It is the most commonly used irrigation methods and is well suited to mild slopes as well as regular slopes", 144 | "environmentalImpact": "Low", 145 | "costPerAcre": 23.41, 146 | "verdict": "Somewhat Recommended", 147 | "likes" : ["rice"] 148 | }, 149 | { 150 | "type": "irrigation", 151 | "name": "Sprinkler", 152 | "earthScore": 70, 153 | "state": "Low", 154 | "image": "https://marketresearch.biz/wp-content/uploads/2019/04/sprinkler-irrigation-systems-market.jpg", 155 | "summary": "Sprinkler Irrigation involves spraying crops with water by use of permanently or temporarily set tools. Water is pumped through pipes and sprinkled to crops like drops of rain from the spray heads.", 156 | "environmentalImpact": "Low", 157 | "costPerAcre": 73.45, 158 | "verdict": "Highly Recommended" 159 | }, 160 | { 161 | "type": "irrigation", 162 | "name": "Drip", 163 | "earthScore": 20, 164 | "state": "High", 165 | "image": "https://i.imgur.com/tdBE8nd.png", 166 | "summary": "Drip irrigation is a type of micro-irrigation system that has the potential to save water and nutrients by allowing water to drip slowly to the roots of plants", 167 | "environmentalImpact": "Medium", 168 | "costPerAcre": 500, 169 | "verdict": "Somewhat Recommended" 170 | }, 171 | { 172 | "type": "irrigation", 173 | "name": "Subsurface", 174 | "earthScore": 51, 175 | "state": "High", 176 | "image": "https://i.ytimg.com/vi/jW5VhhT2bL0/maxresdefault.jpg", 177 | "summary": "Subsurface irrigation waters crops from below the ground. Pipes placed beneath the soil surface supply water to the roots of plants.", 178 | "environmentalImpact": "Medium", 179 | "costPerAcre": 800, 180 | "verdict": "Somewhat Recommended" 181 | }, 182 | { 183 | "type": "irrigation", 184 | "name": "Center Pivot", 185 | "earthScore": 25, 186 | "state": "Medium", 187 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Crops_Kansas_AST_20010624.jpg/800px-Crops_Kansas_AST_20010624.jpg", 188 | "summary": "Water is distributed by a system of sprinklers that move on wheeled towers in a circular pattern. This system is common in flat areas of the United States.", 189 | "environmentalImpact": "High", 190 | "costPerAcre": 41.59, 191 | "verdict": "Somewhat Recommended" 192 | } 193 | ] 194 | -------------------------------------------------------------------------------- /backend/src/data/earthData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "crop", 4 | "name": "Strawberry", 5 | "earthScore": 86, 6 | "state": "Summer", 7 | "image": "https://i.imgur.com/u0rOCBQ.png", 8 | "summary": "Strawberries are low-growing herbaceous plants with a fibrous root system and a crown from which arise basal leaves", 9 | "environmentalImpact": "Medium", 10 | "costPerAcre": 1600, 11 | "verdict": "Highly Recommended", 12 | "daysToGrow" : 90, 13 | "waterUsed" : 0.36, 14 | "lbsPerAcre" : 10000, 15 | "valuePerAcre" : 2000 16 | }, 17 | { 18 | "type": "crop", 19 | "name": "Corn", 20 | "earthScore": 72, 21 | "state": "Fall", 22 | "image": "https://i.imgur.com/Fm1Svvq.png", 23 | "summary": "Corn is a cereal grain first domesticated by indigenous peoples in southern Mexico about 10,000 years ago", 24 | "environmentalImpact": "Medium", 25 | "costPerAcre": 248, 26 | "verdict": "Highly Recommended", 27 | "daysToGrow" : 120, 28 | "waterUsed" : 0.19, 29 | "lbsPerAcre" : 7280, 30 | "valuePerAcre" : 699 31 | }, 32 | { 33 | "type": "crop", 34 | "name": "Soybean", 35 | "earthScore": 70, 36 | "state": "Fall", 37 | "image": "https://cdn.britannica.com/28/154828-050-05C6239A/Soybeans.jpg", 38 | "summary": "The soybean or soya bean is a species of legume native to East Asia, widely grown for its edible bean, which has numerous uses.", 39 | "environmentalImpact": "Medium", 40 | "costPerAcre": 204, 41 | "verdict": "Highly Recommended", 42 | "daysToGrow" : 65, 43 | "waterUsed" : 0.19, 44 | "lbsPerAcre" : 2946, 45 | "valuePerAcre" : 0 46 | }, 47 | { 48 | "type": "crop", 49 | "name": "Wheat", 50 | "earthScore": 78, 51 | "state": "Fall", 52 | "image": "https://i.imgur.com/eKB3yQ8.jpeg", 53 | "summary": "Wheat is a grass widely cultivated for its seed, a cereal grain which is a worldwide staple food", 54 | "environmentalImpact": "Low", 55 | "costPerAcre": 170, 56 | "verdict": "Highly Recommended", 57 | "daysToGrow" : 120, 58 | "waterUsed" : 0.14, 59 | "lbsPerAcre" : 3900, 60 | "valuePerAcre" : 0 61 | }, 62 | { 63 | "type": "crop", 64 | "name": "Rice", 65 | "earthScore": 64, 66 | "state": "Summer", 67 | "image": "https://i.imgur.com/yegWy51.jpeg", 68 | "summary": "Rice is the most widely consumed staple food for over half the world's population", 69 | "environmentalImpact": "High", 70 | "costPerAcre": 225, 71 | "verdict": "Recommended", 72 | "daysToGrow" : 120, 73 | "waterUsed" : 0.26, 74 | "lbsPerAcre" : 8000, 75 | "valuePerAcre" : 892 76 | }, 77 | { 78 | "type": "crop", 79 | "name": "Onion", 80 | "earthScore": 82, 81 | "state": "Summer", 82 | "image": "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/the-health-benefits-of-onions-main-image-700-350-8425535.jpg?quality=90&resize=768,574", 83 | "summary": "The onion plant has a fan of hollow, bluish-green leaves and its bulb at the base of the plant begins to swell when a certain day-length is reached", 84 | "environmentalImpact": "Medium", 85 | "costPerAcre": 1750, 86 | "verdict": "Highly Recommended", 87 | "daysToGrow" : 100, 88 | "waterUsed" : 0.06, 89 | "lbsPerAcre" : 32000, 90 | "valuePerAcre" : 4200 91 | }, 92 | { 93 | "type": "crop", 94 | "name": "Cotton", 95 | "earthScore": 81, 96 | "state": "Fall", 97 | "image": "https://upload.wikimedia.org/wikipedia/commons/6/68/CottonPlant.JPG", 98 | "summary": "Cotton is a shrub native to tropical and subtropical regions around the world, including the Americas, Africa, Egypt and India", 99 | "environmentalImpact": "High", 100 | "costPerAcre": 485, 101 | "verdict": "Recommended", 102 | "daysToGrow" : 150, 103 | "waterUsed" : 0.09, 104 | "lbsPerAcre" : 685, 105 | "valuePerAcre" : 500 106 | }, 107 | { 108 | "type": "crop", 109 | "name": "Iceberg Lettuce", 110 | "earthScore": 78, 111 | "state": "Spring", 112 | "image": "https://media.newyorker.com/photos/5b6b08d3a676470b4ea9b91f/4:3/w_1920,h_1440,c_limit/Rosner-Lettuce.jpg", 113 | "summary": "Iceberg lettuce, also known as crisphead lettuce, has pale green leaves and grows in cabbage-like bulbs", 114 | "environmentalImpact": "High", 115 | "costPerAcre": 7999, 116 | "verdict": "Recommended", 117 | "daysToGrow" : 30, 118 | "waterUsed" : 0.09, 119 | "lbsPerAcre" : 32000, 120 | "valuePerAcre" : 10400 121 | }, 122 | { 123 | "type": "crop", 124 | "name": "Romaine Lettuce", 125 | "earthScore": 78, 126 | "state": "Spring", 127 | "image": "https://www.foodsafetynews.com/files/2019/11/romaine1200x680.jpg", 128 | "summary": "Romaine or cos lettuce is a variety of lettuce that grows in a tall head of sturdy dark green leaves with firm ribs down their centers.", 129 | "environmentalImpact": "High", 130 | "costPerAcre": 8847, 131 | "verdict": "Recommended", 132 | "daysToGrow" : 30, 133 | "waterUsed" : 0, 134 | "lbsPerAcre" : 32000, 135 | "valuePerAcre" : 10400 136 | }, 137 | { 138 | "type": "irrigation", 139 | "name": "Surface", 140 | "earthScore": 90, 141 | "state": "Medium", 142 | "image": "https://upload.wikimedia.org/wikipedia/commons/9/9b/Furrow_irrigated_Sugar.JPG", 143 | "summary": "This type of irrigation relies on gravity flow to distribute water. It is the most commonly used irrigation methods and is well suited to mild slopes as well as regular slopes", 144 | "environmentalImpact": "Low", 145 | "costPerAcre": 23.41, 146 | "verdict": "Somewhat Recommended", 147 | "likes" : ["rice"] 148 | }, 149 | { 150 | "type": "irrigation", 151 | "name": "Sprinkler", 152 | "earthScore": 70, 153 | "state": "Low", 154 | "image": "https://marketresearch.biz/wp-content/uploads/2019/04/sprinkler-irrigation-systems-market.jpg", 155 | "summary": "Sprinkler Irrigation involves spraying crops with water by use of permanently or temporarily set tools. Water is pumped through pipes and sprinkled to crops like drops of rain from the spray heads.", 156 | "environmentalImpact": "Low", 157 | "costPerAcre": 73.45, 158 | "verdict": "Highly Recommended" 159 | }, 160 | { 161 | "type": "irrigation", 162 | "name": "Drip", 163 | "earthScore": 20, 164 | "state": "High", 165 | "image": "https://i.imgur.com/tdBE8nd.png", 166 | "summary": "Drip irrigation is a type of micro-irrigation system that has the potential to save water and nutrients by allowing water to drip slowly to the roots of plants", 167 | "environmentalImpact": "Medium", 168 | "costPerAcre": 500, 169 | "verdict": "Somewhat Recommended" 170 | }, 171 | { 172 | "type": "irrigation", 173 | "name": "Subsurface", 174 | "earthScore": 51, 175 | "state": "High", 176 | "image": "https://i.ytimg.com/vi/jW5VhhT2bL0/maxresdefault.jpg", 177 | "summary": "Subsurface irrigation waters crops from below the ground. Pipes placed beneath the soil surface supply water to the roots of plants.", 178 | "environmentalImpact": "Medium", 179 | "costPerAcre": 800, 180 | "verdict": "Somewhat Recommended" 181 | }, 182 | { 183 | "type": "irrigation", 184 | "name": "Center Pivot", 185 | "earthScore": 25, 186 | "state": "Medium", 187 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Crops_Kansas_AST_20010624.jpg/800px-Crops_Kansas_AST_20010624.jpg", 188 | "summary": "Water is distributed by a system of sprinklers that move on wheeled towers in a circular pattern. This system is common in flat areas of the United States.", 189 | "environmentalImpact": "High", 190 | "costPerAcre": 41.59, 191 | "verdict": "Somewhat Recommended" 192 | } 193 | ] 194 | -------------------------------------------------------------------------------- /frontend/src/pages/earthData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "crop", 4 | "name": "Strawberry", 5 | "earthScore": 86, 6 | "state": "Summer", 7 | "image": "https://i.imgur.com/u0rOCBQ.png", 8 | "summary": "Strawberries are low-growing herbaceous plants with a fibrous root system and a crown from which arise basal leaves", 9 | "environmentalImpact": "Medium", 10 | "costPerAcre": 1600, 11 | "verdict": "Highly Recommended", 12 | "daysToGrow" : 90, 13 | "waterUsed" : 0.36, 14 | "lbsPerAcre" : 10000, 15 | "valuePerAcre" : 2000 16 | }, 17 | { 18 | "type": "crop", 19 | "name": "Corn", 20 | "earthScore": 72, 21 | "state": "Fall", 22 | "image": "https://i.imgur.com/Fm1Svvq.png", 23 | "summary": "Corn is a cereal grain first domesticated by indigenous peoples in southern Mexico about 10,000 years ago", 24 | "environmentalImpact": "Medium", 25 | "costPerAcre": 248, 26 | "verdict": "Highly Recommended", 27 | "daysToGrow" : 120, 28 | "waterUsed" : 0.19, 29 | "lbsPerAcre" : 7280, 30 | "valuePerAcre" : 699 31 | }, 32 | { 33 | "type": "crop", 34 | "name": "Soybean", 35 | "earthScore": 70, 36 | "state": "Fall", 37 | "image": "https://cdn.britannica.com/28/154828-050-05C6239A/Soybeans.jpg", 38 | "summary": "The soybean or soya bean is a species of legume native to East Asia, widely grown for its edible bean, which has numerous uses.", 39 | "environmentalImpact": "Medium", 40 | "costPerAcre": 204, 41 | "verdict": "Highly Recommended", 42 | "daysToGrow" : 65, 43 | "waterUsed" : 0.19, 44 | "lbsPerAcre" : 2946, 45 | "valuePerAcre" : 0 46 | }, 47 | { 48 | "type": "crop", 49 | "name": "Wheat", 50 | "earthScore": 78, 51 | "state": "Fall", 52 | "image": "https://i.imgur.com/eKB3yQ8.jpeg", 53 | "summary": "Wheat is a grass widely cultivated for its seed, a cereal grain which is a worldwide staple food", 54 | "environmentalImpact": "Low", 55 | "costPerAcre": 170, 56 | "verdict": "Highly Recommended", 57 | "daysToGrow" : 120, 58 | "waterUsed" : 0.14, 59 | "lbsPerAcre" : 3900, 60 | "valuePerAcre" : 0 61 | }, 62 | { 63 | "type": "crop", 64 | "name": "Rice", 65 | "earthScore": 64, 66 | "state": "Summer", 67 | "image": "https://i.imgur.com/yegWy51.jpeg", 68 | "summary": "Rice is the most widely consumed staple food for over half the world's population", 69 | "environmentalImpact": "High", 70 | "costPerAcre": 225, 71 | "verdict": "Recommended", 72 | "daysToGrow" : 120, 73 | "waterUsed" : 0.26, 74 | "lbsPerAcre" : 8000, 75 | "valuePerAcre" : 892 76 | }, 77 | { 78 | "type": "crop", 79 | "name": "Onion", 80 | "earthScore": 82, 81 | "state": "Summer", 82 | "image": "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/the-health-benefits-of-onions-main-image-700-350-8425535.jpg?quality=90&resize=768,574", 83 | "summary": "The onion plant has a fan of hollow, bluish-green leaves and its bulb at the base of the plant begins to swell when a certain day-length is reached", 84 | "environmentalImpact": "Medium", 85 | "costPerAcre": 1750, 86 | "verdict": "Highly Recommended", 87 | "daysToGrow" : 100, 88 | "waterUsed" : 0.06, 89 | "lbsPerAcre" : 32000, 90 | "valuePerAcre" : 4200 91 | }, 92 | { 93 | "type": "crop", 94 | "name": "Cotton", 95 | "earthScore": 81, 96 | "state": "Fall", 97 | "image": "https://upload.wikimedia.org/wikipedia/commons/6/68/CottonPlant.JPG", 98 | "summary": "Cotton is a shrub native to tropical and subtropical regions around the world, including the Americas, Africa, Egypt and India", 99 | "environmentalImpact": "High", 100 | "costPerAcre": 485, 101 | "verdict": "Recommended", 102 | "daysToGrow" : 150, 103 | "waterUsed" : 0.09, 104 | "lbsPerAcre" : 685, 105 | "valuePerAcre" : 500 106 | }, 107 | { 108 | "type": "crop", 109 | "name": "Iceberg Lettuce", 110 | "earthScore": 78, 111 | "state": "Spring", 112 | "image": "https://media.newyorker.com/photos/5b6b08d3a676470b4ea9b91f/4:3/w_1920,h_1440,c_limit/Rosner-Lettuce.jpg", 113 | "summary": "Iceberg lettuce, also known as crisphead lettuce, has pale green leaves and grows in cabbage-like bulbs", 114 | "environmentalImpact": "High", 115 | "costPerAcre": 7999, 116 | "verdict": "Recommended", 117 | "daysToGrow" : 30, 118 | "waterUsed" : 0.09, 119 | "lbsPerAcre" : 32000, 120 | "valuePerAcre" : 10400 121 | }, 122 | { 123 | "type": "crop", 124 | "name": "Romaine Lettuce", 125 | "earthScore": 78, 126 | "state": "Spring", 127 | "image": "https://www.foodsafetynews.com/files/2019/11/romaine1200x680.jpg", 128 | "summary": "Romaine or cos lettuce is a variety of lettuce that grows in a tall head of sturdy dark green leaves with firm ribs down their centers.", 129 | "environmentalImpact": "High", 130 | "costPerAcre": 8847, 131 | "verdict": "Recommended", 132 | "daysToGrow" : 30, 133 | "waterUsed" : 0, 134 | "lbsPerAcre" : 32000, 135 | "valuePerAcre" : 10400 136 | }, 137 | { 138 | "type": "irrigation", 139 | "name": "Surface", 140 | "earthScore": 90, 141 | "state": "Medium", 142 | "image": "https://upload.wikimedia.org/wikipedia/commons/9/9b/Furrow_irrigated_Sugar.JPG", 143 | "summary": "This type of irrigation relies on gravity flow to distribute water. It is the most commonly used irrigation methods and is well suited to mild slopes as well as regular slopes", 144 | "environmentalImpact": "Low", 145 | "costPerAcre": 23.41, 146 | "verdict": "Somewhat Recommended", 147 | "likes" : ["rice"] 148 | }, 149 | { 150 | "type": "irrigation", 151 | "name": "Sprinkler", 152 | "earthScore": 70, 153 | "state": "Low", 154 | "image": "https://marketresearch.biz/wp-content/uploads/2019/04/sprinkler-irrigation-systems-market.jpg", 155 | "summary": "Sprinkler Irrigation involves spraying crops with water by use of permanently or temporarily set tools. Water is pumped through pipes and sprinkled to crops like drops of rain from the spray heads.", 156 | "environmentalImpact": "Low", 157 | "costPerAcre": 73.45, 158 | "verdict": "Highly Recommended" 159 | }, 160 | { 161 | "type": "irrigation", 162 | "name": "Drip", 163 | "earthScore": 20, 164 | "state": "High", 165 | "image": "https://i.imgur.com/tdBE8nd.png", 166 | "summary": "Drip irrigation is a type of micro-irrigation system that has the potential to save water and nutrients by allowing water to drip slowly to the roots of plants", 167 | "environmentalImpact": "Medium", 168 | "costPerAcre": 500, 169 | "verdict": "Somewhat Recommended" 170 | }, 171 | { 172 | "type": "irrigation", 173 | "name": "Subsurface", 174 | "earthScore": 51, 175 | "state": "High", 176 | "image": "https://i.ytimg.com/vi/jW5VhhT2bL0/maxresdefault.jpg", 177 | "summary": "Subsurface irrigation waters crops from below the ground. Pipes placed beneath the soil surface supply water to the roots of plants.", 178 | "environmentalImpact": "Medium", 179 | "costPerAcre": 800, 180 | "verdict": "Somewhat Recommended" 181 | }, 182 | { 183 | "type": "irrigation", 184 | "name": "Center Pivot", 185 | "earthScore": 25, 186 | "state": "Medium", 187 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Crops_Kansas_AST_20010624.jpg/800px-Crops_Kansas_AST_20010624.jpg", 188 | "summary": "Water is distributed by a system of sprinklers that move on wheeled towers in a circular pattern. This system is common in flat areas of the United States.", 189 | "environmentalImpact": "High", 190 | "costPerAcre": 41.59, 191 | "verdict": "Somewhat Recommended" 192 | } 193 | ] 194 | -------------------------------------------------------------------------------- /frontend/src/components/Statistics/CropRotation/earthData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "crop", 4 | "name": "Strawberry", 5 | "earthScore": 86, 6 | "state": "Summer", 7 | "image": "https://i.imgur.com/u0rOCBQ.png", 8 | "summary": "Strawberries are low-growing herbaceous plants with a fibrous root system and a crown from which arise basal leaves", 9 | "environmentalImpact": "Medium", 10 | "costPerAcre": 1600, 11 | "cost": 1600, 12 | "verdict": "Highly Recommended", 13 | "daysToGrow" : 90, 14 | "waterUsed" : 0.36, 15 | "lbsPerAcre" : 10000, 16 | "valuePerAcre" : 2000 17 | }, 18 | 19 | { 20 | "type": "crop", 21 | "name": "Corn", 22 | "earthScore": 72, 23 | "state": "Fall", 24 | "image": "https://i.imgur.com/Fm1Svvq.png", 25 | "summary": "Corn is a cereal grain first domesticated by indigenous peoples in southern Mexico about 10,000 years ago", 26 | "environmentalImpact": "Medium", 27 | "costPerAcre": 248, 28 | "verdict": "Highly Recommended", 29 | "daysToGrow" : 120, 30 | "waterUsed" : 0.19, 31 | "lbsPerAcre" : 7280, 32 | "valuePerAcre" : 699 33 | }, 34 | { 35 | "type": "crop", 36 | "name": "Soybean", 37 | "earthScore": 70, 38 | "state": "Fall", 39 | "image": "https://cdn.britannica.com/28/154828-050-05C6239A/Soybeans.jpg", 40 | "summary": "The soybean or soya bean is a species of legume native to East Asia, widely grown for its edible bean, which has numerous uses.", 41 | "environmentalImpact": "Medium", 42 | "costPerAcre": 204, 43 | "verdict": "Highly Recommended", 44 | "daysToGrow" : 65, 45 | "waterUsed" : 0.19, 46 | "lbsPerAcre" : 2946, 47 | "valuePerAcre" : 0 48 | }, 49 | { 50 | "type": "crop", 51 | "name": "Wheat", 52 | "earthScore": 78, 53 | "state": "Fall", 54 | "image": "https://i.imgur.com/eKB3yQ8.jpeg", 55 | "summary": "Wheat is a grass widely cultivated for its seed, a cereal grain which is a worldwide staple food", 56 | "environmentalImpact": "Low", 57 | "costPerAcre": 170, 58 | "verdict": "Highly Recommended", 59 | "daysToGrow" : 120, 60 | "waterUsed" : 0.14, 61 | "lbsPerAcre" : 3900, 62 | "valuePerAcre" : 0 63 | }, 64 | { 65 | "type": "crop", 66 | "name": "Rice", 67 | "earthScore": 64, 68 | "state": "Summer", 69 | "image": "https://i.imgur.com/yegWy51.jpeg", 70 | "summary": "Rice is the most widely consumed staple food for over half the world's population", 71 | "environmentalImpact": "High", 72 | "costPerAcre": 225, 73 | "verdict": "Recommended", 74 | "daysToGrow" : 120, 75 | "waterUsed" : 0.26, 76 | "lbsPerAcre" : 8000, 77 | "valuePerAcre" : 892 78 | }, 79 | { 80 | "type": "crop", 81 | "name": "Onion", 82 | "earthScore": 82, 83 | "state": "Summer", 84 | "image": "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/the-health-benefits-of-onions-main-image-700-350-8425535.jpg?quality=90&resize=768,574", 85 | "summary": "The onion plant has a fan of hollow, bluish-green leaves and its bulb at the base of the plant begins to swell when a certain day-length is reached", 86 | "environmentalImpact": "Medium", 87 | "costPerAcre": 1750, 88 | "verdict": "Highly Recommended", 89 | "daysToGrow" : 100, 90 | "waterUsed" : 0.06, 91 | "lbsPerAcre" : 32000, 92 | "valuePerAcre" : 4200 93 | }, 94 | { 95 | "type": "crop", 96 | "name": "Cotton", 97 | "earthScore": 81, 98 | "state": "Fall", 99 | "image": "https://upload.wikimedia.org/wikipedia/commons/6/68/CottonPlant.JPG", 100 | "summary": "Cotton is a shrub native to tropical and subtropical regions around the world, including the Americas, Africa, Egypt and India", 101 | "environmentalImpact": "High", 102 | "costPerAcre": 485, 103 | "verdict": "Recommended", 104 | "daysToGrow" : 150, 105 | "waterUsed" : 0.09, 106 | "lbsPerAcre" : 685, 107 | "valuePerAcre" : 500 108 | }, 109 | { 110 | "type": "crop", 111 | "name": "Iceberg Lettuce", 112 | "earthScore": 78, 113 | "state": "Spring", 114 | "image": "https://media.newyorker.com/photos/5b6b08d3a676470b4ea9b91f/4:3/w_1920,h_1440,c_limit/Rosner-Lettuce.jpg", 115 | "summary": "Iceberg lettuce, also known as crisphead lettuce, has pale green leaves and grows in cabbage-like bulbs", 116 | "environmentalImpact": "High", 117 | "costPerAcre": 7999, 118 | "verdict": "Recommended", 119 | "daysToGrow" : 30, 120 | "waterUsed" : 0.09, 121 | "lbsPerAcre" : 32000, 122 | "valuePerAcre" : 10400 123 | }, 124 | { 125 | "type": "crop", 126 | "name": "Romaine Lettuce", 127 | "earthScore": 78, 128 | "state": "Spring", 129 | "image": "https://www.foodsafetynews.com/files/2019/11/romaine1200x680.jpg", 130 | "summary": "Romaine or cos lettuce is a variety of lettuce that grows in a tall head of sturdy dark green leaves with firm ribs down their centers.", 131 | "environmentalImpact": "High", 132 | "costPerAcre": 8847, 133 | "verdict": "Recommended", 134 | "daysToGrow" : 30, 135 | "waterUsed" : 0, 136 | "lbsPerAcre" : 32000, 137 | "valuePerAcre" : 10400 138 | }, 139 | { 140 | "type": "irrigation", 141 | "name": "Surface", 142 | "earthScore": 90, 143 | "state": "Medium", 144 | "image": "https://upload.wikimedia.org/wikipedia/commons/9/9b/Furrow_irrigated_Sugar.JPG", 145 | "summary": "This type of irrigation relies on gravity flow to distribute water. It is the most commonly used irrigation methods and is well suited to mild slopes as well as regular slopes", 146 | "environmentalImpact": "Low", 147 | "costPerAcre": 23.41, 148 | "verdict": "Somewhat Recommended", 149 | "likes" : ["rice"] 150 | }, 151 | { 152 | "type": "irrigation", 153 | "name": "Sprinkler", 154 | "earthScore": 70, 155 | "state": "Low", 156 | "image": "https://marketresearch.biz/wp-content/uploads/2019/04/sprinkler-irrigation-systems-market.jpg", 157 | "summary": "Sprinkler Irrigation involves spraying crops with water by use of permanently or temporarily set tools. Water is pumped through pipes and sprinkled to crops like drops of rain from the spray heads.", 158 | "environmentalImpact": "Low", 159 | "costPerAcre": 73.45, 160 | "verdict": "Highly Recommended" 161 | }, 162 | { 163 | "type": "irrigation", 164 | "name": "Drip", 165 | "earthScore": 20, 166 | "state": "High", 167 | "image": "https://i.imgur.com/tdBE8nd.png", 168 | "summary": "Drip irrigation is a type of micro-irrigation system that has the potential to save water and nutrients by allowing water to drip slowly to the roots of plants", 169 | "environmentalImpact": "Medium", 170 | "costPerAcre": 500, 171 | "verdict": "Somewhat Recommended" 172 | }, 173 | { 174 | "type": "irrigation", 175 | "name": "Subsurface", 176 | "earthScore": 51, 177 | "state": "High", 178 | "image": "https://i.ytimg.com/vi/jW5VhhT2bL0/maxresdefault.jpg", 179 | "summary": "Subsurface irrigation waters crops from below the ground. Pipes placed beneath the soil surface supply water to the roots of plants.", 180 | "environmentalImpact": "Medium", 181 | "costPerAcre": 800, 182 | "verdict": "Somewhat Recommended" 183 | }, 184 | { 185 | "type": "irrigation", 186 | "name": "Center Pivot", 187 | "earthScore": 25, 188 | "state": "Medium", 189 | "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Crops_Kansas_AST_20010624.jpg/800px-Crops_Kansas_AST_20010624.jpg", 190 | "summary": "Water is distributed by a system of sprinklers that move on wheeled towers in a circular pattern. This system is common in flat areas of the United States.", 191 | "environmentalImpact": "High", 192 | "costPerAcre": 41.59, 193 | "verdict": "Somewhat Recommended" 194 | } 195 | ] 196 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { useEffect, useState } from 'react'; 3 | import anime from 'animejs'; 4 | import { notification } from 'antd'; 5 | import DataEarth from './earthData.json'; 6 | import Map from 'components/Map'; 7 | import NavBar from 'components/NavBar'; 8 | import SideBar from 'components/SideBar'; 9 | import StatsModal from 'components/StatsModal'; 10 | 11 | import CurrentlySelected from 'components/CurrentlySelected'; 12 | import Seasons from 'components/Seasons'; 13 | import Timeline from 'components/Timeline'; 14 | 15 | import Analysis from 'components/Analysis'; 16 | import PlotType from 'components/PlotType'; 17 | import BigCurrentlySelected from 'components/BigCurrentlySelected'; 18 | 19 | import Date from 'components/Date'; 20 | 21 | //import * as AnalysisLib from "analysis"; 22 | 23 | let seasonsAnimation, timelineAnimation, currentlySelectedAnimation, analysisAnimation, cardAnimation, cards2Animation; 24 | const HomePage = () => { 25 | const [sideBarPage, setSideBarPage] = useState('map'); 26 | const [currentPlot, setCurrentPlot] = useState({}); 27 | const [plots, setPlotOptions] = useState([]); 28 | const [visible, setModal] = useState(false); 29 | const [statsData, setStatsData] = useState([]); 30 | const [objects, setNewObjects] = useState({}); 31 | const [day, setDay] = useState(108); 32 | const [environmentScore, setEnvironmentScore] = useState ('?'); 33 | const [environmentDescription, setEnvironmentDescription] = useState('Click on a land plot to get your farm judged by drishti.') 34 | const [locationScore, setLocationScore] = useState ('?'); 35 | const [locationDescription, setLocationDescription] = useState('Click on a land plot to get your farm judged by drishti.') 36 | 37 | const newSetSideBarPage = (sideBarPageParam) => { 38 | console.log(sideBarPageParam); 39 | console.log('hi') 40 | if (sideBarPageParam !== 'map') { 41 | if (seasonsAnimation) { 42 | seasonsAnimation.seek(0); 43 | seasonsAnimation.play(); 44 | seasonsAnimation.finished.then(() => { 45 | seasonsAnimation.reverse(); 46 | }) 47 | } 48 | if(analysisAnimation) { 49 | analysisAnimation.seek(0); 50 | analysisAnimation.play(); 51 | analysisAnimation.finished.then(() => { 52 | cardAnimation.play(); 53 | cards2Animation.play(); 54 | analysisAnimation.reverse(); 55 | }) 56 | } 57 | if (timelineAnimation) { 58 | timelineAnimation.seek(0); 59 | timelineAnimation.play(); 60 | timelineAnimation.finished.then(() => { 61 | timelineAnimation.reverse(); 62 | }) 63 | } 64 | if (currentlySelectedAnimation) { 65 | currentlySelectedAnimation.seek(0); 66 | currentlySelectedAnimation.play(); 67 | currentlySelectedAnimation.finished.then(() => { 68 | currentlySelectedAnimation.reverse(); 69 | }) 70 | } 71 | } else { 72 | if (seasonsAnimation) { 73 | cardAnimation.seek(0); 74 | cards2Animation.seek(0); 75 | seasonsAnimation.seek(1000); 76 | seasonsAnimation.play(); 77 | seasonsAnimation.finished.then(() => { 78 | seasonsAnimation.reverse(); 79 | }) 80 | } 81 | if (timelineAnimation) { 82 | timelineAnimation.seek(1000); 83 | timelineAnimation.play(); 84 | timelineAnimation.finished.then(() => { 85 | timelineAnimation.reverse(); 86 | }) 87 | } 88 | if (currentlySelectedAnimation) { 89 | currentlySelectedAnimation.seek(1000); 90 | currentlySelectedAnimation.play(); 91 | currentlySelectedAnimation.finished.then(() => { 92 | currentlySelectedAnimation.reverse(); 93 | }) 94 | } 95 | if (analysisAnimation) { 96 | analysisAnimation.seek(1000); 97 | analysisAnimation.play(); 98 | analysisAnimation.finished.then(() => { 99 | analysisAnimation.reverse(); 100 | }) 101 | } 102 | } 103 | setSideBarPage(side => sideBarPageParam); 104 | } 105 | 106 | const flipModal = () => { 107 | const currentObjects = Object.values(objects); 108 | const tempStatsData = {} 109 | for (let i = 0; i < currentObjects.length; i++ ) { 110 | const currentObject = currentObjects[i]; 111 | const sqFt = Math.floor((currentObject.width * currentObject.length) / 3.2808); 112 | 113 | const acres = AnalysisLib.sqftToAcre(sqFt); 114 | console.log(acres); 115 | if (currentObject.currentPlot.type === 'crop') { 116 | const name = currentObject.currentPlot.name; 117 | if (currentObject.currentPlot.name in tempStatsData) { 118 | tempStatsData[name] += parseFloat(acres.toFixed(2)); 119 | } else { 120 | tempStatsData[name] = parseFloat(acres.toFixed(2)); 121 | } 122 | } 123 | } 124 | const keys = Object.keys(tempStatsData); 125 | const newStatsData = [] 126 | for (let j = 0; j < keys.length; j++ ) { 127 | newStatsData.push({ 128 | crop: keys[j], 129 | acre: tempStatsData[keys[j]] 130 | }) 131 | } 132 | setStatsData(d => newStatsData); 133 | if (currentObjects.length > 1) { 134 | setModal(!visible); 135 | } else { 136 | notification.open({ 137 | description: "Please place some plots on the map first! I can't analyze empty fields!", 138 | placement: 'bottomLeft' 139 | }); 140 | } 141 | }; 142 | 143 | const [draw, setDraw] = useState(null); 144 | const [analysis, setAnalysis] = useState({}); 145 | 146 | useEffect(async () => { 147 | 148 | const data=DataEarth; 149 | console.log(data); 150 | if (!data) throw new Error('Empty response from server'); 151 | if (data.error) throw new Error(data.error.message); 152 | 153 | setPlotOptions(data); 154 | 155 | analysisAnimation = anime({ 156 | targets: '.right-into', 157 | translateX: -1200, 158 | duration: 1000, 159 | loop: false, 160 | autoplay: false, 161 | easing: 'easeInOutSine' 162 | }); 163 | 164 | seasonsAnimation = anime({ 165 | targets: '.seasons', 166 | translateY: 200, 167 | duration: 1000, 168 | loop: false, 169 | autoplay: false, 170 | easing: 'easeInOutSine' 171 | }); 172 | 173 | cardAnimation = anime({ 174 | targets: '.analysis .fade-up', 175 | translateY: -10, 176 | opacity: 100, 177 | duration: 1000, 178 | delay: function(el, i) { return i * 200; }, 179 | loop: false, 180 | autoplay: false, 181 | easing: 'easeInOutSine', 182 | }); 183 | 184 | cards2Animation = anime({ 185 | targets: '.plots .plot', 186 | translateY: -10, 187 | opacity: 100, 188 | duration: 1000, 189 | delay: function(el, i) { return i * 200; }, 190 | loop: false, 191 | autoplay: false, 192 | easing: 'easeInOutSine', 193 | }); 194 | 195 | timelineAnimation = anime({ 196 | targets: '.timeline', 197 | translateY: -200, 198 | duration: 1000, 199 | loop: false, 200 | autoplay: false, 201 | easing: 'easeInOutSine' 202 | }); 203 | 204 | currentlySelectedAnimation = anime({ 205 | targets: '.currently-selected', 206 | translateY: -200, 207 | duration: 1000, 208 | loop: false, 209 | autoplay: false, 210 | easing: 'easeInOutSine' 211 | }); 212 | }, []); 213 | 214 | return ( 215 |
216 | 223 | 224 | 225 | 239 | 240 | 241 | 242 | 243 | 244 | 248 | 249 |
250 | ); 251 | }; 252 | 253 | export default HomePage; 254 | -------------------------------------------------------------------------------- /frontend/src/components/Map/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import ReactMapboxGl from 'react-mapbox-gl'; 3 | import perfectResponse from './perfectResponse.json' 4 | import DrawRectangle, { 5 | DrawStyles 6 | } from "mapbox-gl-draw-rectangle-restrict-area"; 7 | import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; 8 | import DrawControl from "react-mapbox-draw-rectangle"; 9 | 10 | import { loadLocation } from './utils.js'; 11 | import './style.scss'; 12 | 13 | 14 | let color = 'rgba(64, 145, 220, 1)'; 15 | function measure(lat1, lon1, lat2, lon2){ // generally used geo measurement function 16 | var R = 6378.137; // Radius of earth in KM 17 | var dLat = lat2 * Math.PI / 180 - lat1 * Math.PI / 180; 18 | var dLon = lon2 * Math.PI / 180 - lon1 * Math.PI / 180; 19 | var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 20 | Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * 21 | Math.sin(dLon/2) * Math.sin(dLon/2); 22 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 23 | var d = R * c; 24 | return d * 1000; // meters 25 | } 26 | 27 | let day = 108; 28 | const Map = ReactMapboxGl({ 29 | accessToken: 30 | 'pk.eyJ1IjoiZW1pbmd1eWVuIiwiYSI6ImNrOGI2ZjRyODA1aHEzZG93cmFxaHR5d2IifQ.x8v_uFbdBanYgRtoKCGIOw', 31 | animationOptions: { 32 | essential: true 33 | }, 34 | pitchWithRotate: false 35 | }); 36 | 37 | let ourDraw = null; 38 | let newCurrentPlot = null; 39 | // CENTER LAT AND CENTER LNG are actual lat long btw 40 | let objects = {}; 41 | const MapComponent = (props) => { 42 | const height = (props.sideBarPage !== 'map') ? '70vh' : '95vh'; 43 | const bottom = (props.sideBarPage !== 'map') ? '11vh' : '0vh'; 44 | const bottomtwo = (props.sideBarPage !== 'map') ? '0vh' : '0vh'; 45 | const radius = (props.sideBarPage !== 'map') ? '6px' : '0'; 46 | const left = (props.sideBarPage !== 'map') ? '6vw' : '5vw'; 47 | const width = (props.sideBarPage !== 'map') ? '45vw' : '100vw'; 48 | const right = (props.sideBarPage !== 'map') ? '-25vw' : '0vw'; 49 | 50 | const [center, setCenter] = useState([-92.0717042, 42.0434796]); 51 | const [zoom, setZoom] = useState([13]); 52 | 53 | useEffect(() => { 54 | day = props.day; 55 | }, [props.day]) 56 | 57 | const onMapLoad = (map) => { 58 | window.map = map; 59 | }; 60 | 61 | const setObjects = (id, newObjects) => { 62 | objects[id] = newObjects; 63 | props.setNewObjects(obj => objects); 64 | } 65 | 66 | const onMapClick = async (map, e) => { 67 | Object.values(objects).forEach(async(value) => { 68 | console.log(value); 69 | if (e.lngLat.lng < value.maxLat && e.lngLat.lat < value.maxLng && e.lngLat.lat > value.minLng && e.lngLat.lng > value.minLat) { 70 | const newNewCurrentPlot = {...value.currentPlot}; 71 | newNewCurrentPlot.sqFt = Math.floor((value.width * value.length) / 3.2808); 72 | props.setCurrentPlot( 73 | newNewCurrentPlot 74 | ); 75 | 76 | 77 | 78 | const data = perfectResponse; 79 | console.log(data); 80 | if (!data) throw new Error('Empty response from server'); 81 | if (data.error) throw new Error(data.error.message); 82 | 83 | const environment = data.environment; 84 | const location = data.location; 85 | let grade = 'A'; 86 | if (environment.environmentGrade < 2) { 87 | grade = 'F'; 88 | } else if (environment.environmentGrade < 5) { 89 | grade = 'D' 90 | } else if (environment.environmentGrade < 7) { 91 | grade = 'C' 92 | } else if (environment.environmentGrade < 9) { 93 | grade = 'B' 94 | } 95 | let grade2 = 'A'; 96 | if (location.locationGrade < 2) { 97 | grade2 = 'F'; 98 | } else if (location.locationGrade < 5) { 99 | grade2 = 'D' 100 | } else if (location.locationGrade < 7) { 101 | grade2 = 'C' 102 | } else if (location.locationGrade < 9) { 103 | grade2 = 'B' 104 | } 105 | props.setEnvironmentDescription(environment.environmentDescription); 106 | props.setEnvironmentScore(grade); 107 | props.setLocationDescription(location.locationDescription); 108 | props.setLocationScore(grade2); 109 | 110 | const insights = data.insights; 111 | 112 | let soilTemp = null; 113 | let soilMoisture = null; 114 | const soil = insights.soilLatest.data; 115 | if (soil.length > 0) { 116 | soilTemp = Math.floor(soil[0]['soil_temperature'] * 1.8000 + 32.00); 117 | soilMoisture = soil[0]['soil_moisture']; 118 | } 119 | 120 | const fire = insights.fireLatest.data; 121 | let fireStatus = null; 122 | if (fire.length > 0) { 123 | if (fire[0].confidence === 'low') { 124 | fireStatus = 'Low'; 125 | } else if (fire[0].confidence === 'nominal') { 126 | fireStatus = 'Medium'; 127 | } else { 128 | fireStatus = 'High'; 129 | } 130 | } 131 | 132 | let pollenStatus = null; 133 | const pollen = insights.pollenForecast.data; 134 | if (pollen.length > 0) { 135 | pollenStatus = pollen[0]['Risk']['grass_pollen']; 136 | } 137 | 138 | let airQualityStatus = null; 139 | const airQuality = insights.airQuality.stations; 140 | if (airQuality.length > 0) { 141 | airQualityStatus = airQuality[0]; 142 | } 143 | 144 | props.setAnalysis({ 145 | fire: fireStatus, 146 | pollen: pollenStatus, 147 | soilTemp: soilTemp, 148 | soilMoisture: soilMoisture, 149 | waterVapor: '51%', 150 | airQuality: airQualityStatus 151 | }) 152 | } 153 | }) 154 | } 155 | 156 | const onMouseMove = (map, e) => { 157 | let cursor = 'default'; 158 | Object.values(objects).forEach((value) => { 159 | if (e.lngLat.lng < value.maxLat && e.lngLat.lat < value.maxLng && e.lngLat.lat > value.minLng && e.lngLat.lng > value.minLat) { 160 | value.model.scale.x = 1.02; 161 | value.model.scale.z = 1.02; 162 | cursor = 'pointer'; 163 | } else { 164 | value.model.scale.x = 1; 165 | value.model.scale.z = 1; 166 | } 167 | }) 168 | document.body.style.cursor = cursor; 169 | } 170 | 171 | useEffect(() => { 172 | newCurrentPlot = props.currentPlot; 173 | if (props.currentPlot && props.currentPlot.type === 'crop') { 174 | if (props.currentPlot.state === 'Spring' || props.currentPlot.state === 'Summer') { 175 | color = 'rgba(39, 174, 96, 1)'; 176 | } else { 177 | color = 'rgba(237, 193, 81, 1)'; 178 | } 179 | } else { 180 | color = 'rgba(64, 145, 220, 1)'; 181 | } 182 | }, [props.currentPlot]); 183 | 184 | const onDrawCreate = ({ features }) => { 185 | console.log(features); 186 | if (features.length >= 1) { 187 | let maxLat = -1; 188 | let maxLng = -1; 189 | let minLat = 1000; 190 | let minLng = 1000; 191 | for (let i = 0; i < features[0].geometry.coordinates[0].length; i++) { 192 | maxLat = Math.max(Math.abs(features[0].geometry.coordinates[0][i][0]), maxLat); 193 | maxLng = Math.max(Math.abs(features[0].geometry.coordinates[0][i][1]), maxLng); 194 | minLat = Math.min(Math.abs(features[0].geometry.coordinates[0][i][0]), minLat); 195 | minLng = Math.min(Math.abs(features[0].geometry.coordinates[0][i][1]), minLng); 196 | } 197 | const width = measure(0, maxLat, 0, minLat); 198 | const length = measure(maxLng, 0, minLng, 0); 199 | 200 | let centerLat = (maxLat + minLat) / 2; 201 | let centerLng = (maxLng + minLng) / 2; 202 | if (features[0].geometry.coordinates[0][0][0] < 0) { 203 | centerLat = -centerLat; 204 | const newMinLat = -maxLat; 205 | maxLat = -minLat; 206 | minLat = newMinLat; 207 | } 208 | if (features[0].geometry.coordinates[0][0][1] < 0) { 209 | centerLng = -centerLng; 210 | const newMinLng = -maxLng; 211 | maxLng = -minLng; 212 | minLng = newMinLng; 213 | } 214 | 215 | if (window.map && newCurrentPlot) { 216 | console.log(features[0].id); 217 | loadLocation(window.map, centerLat, centerLng, features[0].id, width, length, color, minLat, maxLat, minLng, maxLng, newCurrentPlot, day, setObjects) 218 | } 219 | 220 | if (ourDraw && features[0].id) { 221 | const currentId = features[0].id; 222 | setTimeout(() => { 223 | ourDraw.draw.delete(currentId) 224 | }, 1000); 225 | } 226 | } 227 | }; 228 | 229 | const onDrawUpdate = ({ features }) => { 230 | console.log(features); 231 | }; 232 | 233 | return ( 234 |
244 | 268 | { 285 | props.setDraw(drawControl); 286 | ourDraw = drawControl; 287 | }} 288 | styles={DrawStyles} 289 | onDrawCreate={onDrawCreate} 290 | > 291 | 292 |
293 | ); 294 | } 295 | 296 | export default MapComponent; --------------------------------------------------------------------------------